router.push('/dashboard')}
+ config={{
+ embeddedWallets: {
+ createOnLogin: 'all-users'
+ }
+ }}
+>
+```
+
+Log out of your application, then log in again and see that your user has an additional `linkedAccount` which is the Privy Embedded Wallet:
+
+```tsx
+{
+ "address": "0xD5063967BA703D485e3Ca40Ecd61882dfa5F49b2",
+ "type": "wallet",
+ "verifiedAt": "2023-11-28T20:52:22.000Z",
+ "chainType": "ethereum",
+ "chainId": "eip155:1",
+ "walletClient": "privy",
+ "walletClientType": "privy",
+ "connectorType": "embedded",
+ "recoveryMethod": "privy"
+},
+```
+
+More information on Privy's Embedded Wallets, including information about the addresses, signing transactions, funding the wallet and more, can be found here: [https://docs.privy.io/guide/frontend/embedded/overview]
+
+## Implementing the Paymaster
+
+A _paymaster_ is a type of smart contract account, introduced in [ERC-4337], that is able to pay for gas on behalf of another account. In this step-by-step, you'll modify an example created by [Privy], move it to another onchain app, and use it to call a smart contract function. Along the way, you'll encounter and resolve some of the confusing pitfalls associated with working with smart contract accounts.
+
+
+The tutorial below does not account for recent changes to the [Base Paymaster]. Please reference the linked repo and adjust. We'll update the tutorial soon!
+
+
+
+### Reviewing the Example
+
+Start by reviewing the [paymaster example]. The address in the `about` section of the Github page links to a deployed version of the app. It's the same app you get from [Privy's Quick Start], with the addition of a mint button (the versions may be a little older).
+
+The app is limited to social auth, so log in with either your Google account or email. You'll see the dashboard, with the addition of a `Mint NFT` button at the top.
+
+Click the button and you'll see a toast notification informing you of updates to the transaction status. Note that this happens **without** you needing to approve a transaction or fund a wallet!
+
+Click to see the transaction when it's done to open BaseScan. If you missed it, mint another NFT, it's not like you're paying gas!
+
+### Reviewing the Transaction and Contract
+
+The transaction page should appear fairly standard. You can see from it that an NFT was minted by the [NFT Contract] and transferred to the smart wallet address listed on the dashboard. Digging in a little more, you'll see some things that might be different than what you'd expect.
+
+#### Tokens Transferred and the NFT Contract
+
+In the `ERC-721 Tokens Transferred` section, click the link to `NFT Name (NFT)` to open up the overview page for the token. You'll see a list of transfers, with yours likely on the top. Click the address for the contract to open up the [view for the contract itself].
+
+You may be surprised to see that there are very few transactions listed for this contract, despite the list of transfers you can see on the token page, or the `Events` tab. Currently, Etherscan and BaseScan won't display transactions done via the paymaster.
+
+
+Blockchain explorers are service providers that provide information about the state of various blockchains. They are **not** a source of truth.
+
+It is possible for onchain activity to be missing from these services.
+
+
+
+#### The Bundler (Transaction Sender)
+
+Return to the transactions summary and look at the `From:` field. It will contain `0x1e6754b227c6ae4b0ca61d82f79d60660737554a`. What is this address? It's not your smart wallet address or signer address. If you mint another NFT from a different login, you'll get the same sender.
+
+This address is the [bundler], which is a special node that bundles user operations from the alt mempool into a single transaction and sends them to the single [EntryPoint] contract.
+
+#### EntryPoint
+
+The EntryPoint contract for Base Goerli is located at `0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789`. Strangely, in your transaction receipt you'll see that the transaction includes a transfer of ETH **from** the EntryPoint **to** the bundler. This transaction is how the bundler gets compensated for performing the service of bundling user ops and turning them into transactions -- the EntryPoint calculates the gas used by user ops and multiplies that by the fee percentage and send it to the bundler.
+
+### Review the Example Code
+
+Return to the [paymaster example] and review the readme. The section on _Copying into your code_ lists the three files you'll need to copy over to implement the paymaster in your own app. All three are extensively documented via comments. You'll also want to review how the demo app uses these to call a function.
+
+#### `SmartAccountContext.tsx`
+
+The first file, [`hooks/SmartAccountContext.tsx`] uses a React Context provider to create a ` SmartAccountProvider`` and pass it into your app. You can see it in use in `\_app.tsx`, with the regular `PrivyProvider` around it. Review the file in detail.
+
+Starting on line 63, the exported `SmartAccountProvider` does the following:
+
+1. Fetch the user's wallets and find their Privy wallet. This wallet is provided, and if need be created, by the `PrivyProvider`
+1. Set up state variables to manage and share connection status and the smart account itself
+1. Initialize an RPC client for the [Base Paymaster] (on Goerli). The URL is hardcoded in `lib/constants.ts`
+1. Initialize an ERC-4337 RPC client for Alchemy's network. This network is where the bundler address comes from
+1. Create a smart wallet. In this case, the `signer` is your EOA embedded wallet created by Privy and fetched in the first step
+1. The EOA address is displayed in the example app as `YOUR SIGNER ADDRESS`
+1. Initialize an Alchemy provider for the smart account signer, using Alchemy's [Account Kit].
+1. This creates the smart account and its address, which is displayed in the example app as `YOUR SMART WALLET ADDRESS`
+
+Finally, the `sendSponsoredUserOperation` function takes a traditional transaction, turns it into a user operation, adds the data for the paymaster to pay the gas, signs it, and sends it. Whew!
+
+If you want a deeper dive into the inner workings of this process, review the helper functions in [`user-operations.ts`].
+
+#### How to Call a Smart Contract Function with the Paymaster
+
+Open [`pages/dashboard.tsx`] and take a look at the `onMint` function on line 32. This function is used as the `oncClick` handler for the `Mint` button at the top of the dashboard.
+
+If you're used to working with wagmi, you'll find the process of sending and awaiting for confirmation of a transaction a little on the manual side. Most of this will be familiar if you've used viem directly, or have worked with _Ethers_.
+
+When a user clicks, the app first creates a viem `RpcTransactionRequest` for the `mint` function on the smart contract. The `smartContractAddress` is supplied by the `SmartAccountProvider`, and the `ABI` and contract `NFT_ADDRESS` are loaded from `lib/constants.ts`:
+
+```tsx
+{
+ from: smartAccountAddress,
+ to: NFT_ADDRESS,
+ data: encodeFunctionData({
+ abi: ABI,
+ functionName: "mint",
+ args: [smartAccountAddress],
+ }),
+}
+```
+
+The app then updates the toast component to update the users while it `await`s first the `userOpHash`, then the `transactionHash`, indicating that transaction has completed successfully. It then updates the link in the toast to send the user to that transaction on Goerli BaseScan.
+
+### Implementing the Paymaster in your own App
+
+Create a new project using Privy's [`create-next-app`] template, and complete the setup instructions in the readme.
+
+Add an environment variable for `NEXT_PUBLIC_ALCHEMY_API_KEY` and paste in the an API key for a Base Goerli app. If you need a key, go to [add an app] and create a new one.
+
+#### Copying and Updating the Source Files
+
+Copy the `hooks` and `lib` folders into your _new_ project. You'll need to install some more dependencies. Use `npm` or `yarn` to add:
+
+- viem
+- react-dom
+- @alchemy/aa-accounts
+- @alchemy/aa-alchemy
+- @alchemy/aa-core
+
+Open `SmartAccountContext.tsx` in your project. You'll see an error for `getDefaultLightAccountFactory`. The name of this function has been updated to `getDefaultLightAccountFactoryAddress`. Change it in the import, and where it is used in the file in the call to `LightSmartContractAccount`.
+
+#### Updating to Use the User's Wallet
+
+The app is currently configured to find and use the user's embedded Privy wallet as the signer. To change this, modify the instantiation of the `SmartAccountProvider`. Instead of `find`ing the user's Privy wallet:
+
+```tsx
+// Old code to change
+
+// Get a list of all of the wallets (EOAs) the user has connected to your site
+const { wallets } = useWallets();
+// Find the embedded wallet by finding the entry in the list with a `walletClientType` of 'privy'
+const embeddedWallet = wallets.find((wallet) => wallet.walletClientType === 'privy');
+```
+
+Simply grab the first wallet in the list (you'll want to do something more elegant for a production app):
+
+```tsx
+// Updated Code
+
+// Get a list of all of the wallets (EOAs) the user has connected to your site
+const { wallets } = useWallets();
+
+// Grab the first wallet on the list
+// TODO: Implement the option to allow the user to choose another wallet
+const wallet = wallets[0];
+```
+
+Then, update the call at the bottom of `useEffect` to `createSmartWallet` if there is an `embeddedWallet` to instead create it if there is a `wallet`, using that `wallet`. You'll also need to update the dependency in the dependency array.
+
+```tsx
+useEffect(() => {
+ // Other code
+
+ if (wallet) createSmartWallet(wallet);
+}, [wallet?.address]);
+```
+
+#### Configuring the PrivyProvider and Adding SmartAccountProvider
+
+By default, the `PrivyProvider` allows logging in with a wallet or email address. To limit it to only the wallet, update the config. You can also set the default chain here. You'll need to import `baseGoerli` to do so.
+
+You also need to import and wrap the app with `SmartAccountProvider`, imported from `hooks/SmartAccountContext.tsx`.
+
+
+The `@alchemy/aa-core` package also exports `SmartAccountProvider` and this export takes precedence when VSCode attempts to help you by automatically adding the import. You'll know you've got the wrong one if `SmartAccountProvider` generates an error that:
+
+```text
+'SmartAccountProvider' cannot be used as a JSX component.
+Its instance type 'SmartAccountProvider' is not a valid JSX element.
+```
+
+
+
+```tsx
+ router.push('/dashboard')}
+ config={{
+ loginMethods: ['wallet'],
+ defaultChain: baseGoerli,
+ }}
+>
+
+
+
+
+```
+
+#### Checking Progress
+
+Grab the snippet from the original demo that displays the user's addresses, and add it to `dashboard.tsx` in the new project:
+
+```tsx
+
+ Your Smart Wallet Address
+
+
+ {smartAccountAddress}
+
+
+ Your Signer Address
+
+
+ {eoa?.address}
+
+```
+
+Paste it above the `` for the `User Object` window.
+
+You'll need to import `BASE_GOERLI_SCAN_URL` from `constants.ts`. The `useSmartAccount` hook returns `smartAccountProvider` and `eoa`. Import it and add it under the `usePrivy` hook. You don't need them just yet, but go ahead and decompose `smartAccountProvider` and `sendSponsoredUserOperation` as well:
+
+```tsx
+const router = useRouter();
+const {
+ ready,
+ authenticated,
+ user,
+ logout,
+ linkEmail,
+ linkWallet,
+ unlinkEmail,
+ linkPhone,
+ unlinkPhone,
+ unlinkWallet,
+ linkGoogle,
+ unlinkGoogle,
+ linkTwitter,
+ unlinkTwitter,
+ linkDiscord,
+ unlinkDiscord,
+} = usePrivy();
+const { smartAccountAddress, smartAccountProvider, sendSponsoredUserOperation, eoa } =
+ useSmartAccount();
+```
+
+Run the app. You'll now see your familiar wallet address as `YOUR SIGNER ADDRESS`!
+
+```caution
+
+The app sometimes gets confused with login state after you've made changes to `config`. If you see the `Log In` button but clicking it does nothing, try manually navigating to `localhost:3000/dashboard` or clearing the cache.
+
+```
+
+#### Calling a Smart Contract Function
+
+You've adjusted the foundation of the app to allow you to use the Base Goerli Paymaster with your normal wallet as the signer. Now, it's time to call a smart contract function.
+
+Start by using the `mint` function in the original example. In the `DashboardPage` component, add a state variable holding an empty element:
+
+```tsx
+const [transactionLink, setTransactionLink] = useState(<>>);
+```
+
+Then, add a variant of the original `onMint` function that sets this variable and has the code related to the toast removed.
+
+**Note:** make sure you change your wallet address in `args` to make sure the NFT is sent to your EOA wallet address!
+
+```tsx
+const onMint = async () => {
+ // The mint button is disabled if either of these are undefined
+ if (!smartAccountProvider || !smartAccountAddress) return;
+
+ try {
+ // From a viem `RpcTransactionRequest` (e.g. calling an ERC-721's `mint` method),
+ // build and send a user operation. Gas fees will be sponsored by the Base Paymaster.
+ const userOpHash = await sendSponsoredUserOperation({
+ from: smartAccountAddress,
+ to: NFT_ADDRESS,
+ data: encodeFunctionData({
+ abi: ABI,
+ functionName: 'mint',
+ args: [eoa?.address],
+ }),
+ });
+
+ // Once we have a hash for the user operation, watch it until the transaction has
+ // been confirmed.
+ const transactionHash = await smartAccountProvider.waitForUserOperationTransaction(userOpHash);
+
+ setTransactionLink(
+
+ Successfully minted! Click here to see your transaction.
+ ,
+ );
+ } catch (error) {
+ setTransactionLink(
{'Mint failed with error: ' + error}
);
+ }
+};
+```
+
+Finally, above where you added the addresses, add a button to call the function, and display the link to the transaction:
+
+```tsx
+
+ Mint NFT
+ ;
+{
+ transactionLink;
+}
+```
+
+Run it and confirm it works. You need the full transaction receipt for the process to finish, so expect to wait as long as 10 or 15 seconds.
+
+
+For simplicity, we've stripped out the code to disable the button while it is minting. You'll want to implement your own solution to avoid confusing your users!
+
+
+
+#### Calling Another Function
+
+The [Base Paymaster] on Goerli is very permissive. To call another function, all you need to do is to change the `RpcTransactionRequest` in `sendSponsoredUserOperation` to match the address, abi, function name, and arguments of your function on your smart contract.
+
+For example, to call the `claim` function in the Weighted Voting contract we've used in other tutorials, you'd simply need to import the Hardhat-style [artifact] for the contract and use it to call the function:
+
+```tsx
+const userOpHash = await sendSponsoredUserOperation({
+ from: smartAccountAddress,
+ to: weightedVoting.address as `0x${string}`,
+ data: encodeFunctionData({
+ abi: weightedVoting.abi,
+ functionName: 'claim',
+ }),
+});
+```
+
+
+The function in this example can only be called once per address. It will then fail, because one wallet cannot claim more than one batch of tokens.
+
+
+
+## Conclusion
+
+In this article, we've explored the transformative potential of Account Abstraction for the Ethereum ecosystem, highlighting how it enables smart contract accounts to initiate transactions without altering the core protocol. This innovation, coupled with the utilization of Privy for streamlined user onboarding and secure data management, marks a significant advancement towards reducing onboarding friction for onchain applications. Through a practical implementation involving Privy and the [Base Paymaster], we demonstrated how users can perform onchain actions without incurring gas fees, showcasing the adaptability and user-centric benefits of these technologies. This tutorial not only sheds light on the technical workings of Account Abstraction but also illustrates its practical application in enhancing the blockchain user experience.
+
+[ERC-4337]: https://eips.ethereum.org/EIPS/eip-4337
+[ethereum.org]: https://ethereum.org/en/developers/docs/accounts/#key-differences
+[Base Learn]: https://base.org/learn
+[Next.js]: https://nextjs.org/
+[Base Paymaster]: https://github.com/base-org/paymaster
+[Privy]: https://www.privy.io/
+[Alchemy's Account Kit]: https://www.alchemy.com/account-kit
+[Privy]: https://www.privy.io/
+[https://docs.privy.io/guide/frontend/embedded/overview]: https://docs.privy.io/guide/frontend/embedded/overview
+[Alchemy's Account Kit]: https://www.alchemy.com/account-kit
+[Privy's Quick Start]: https://docs.privy.io/guide/quickstart
+[https://github.com/privy-io/create-next-app]: https://github.com/privy-io/create-next-app
+[`PrivyClientConfig`]: https://docs.privy.io/reference/react-auth/modules#privyclientconfig
+[documented here]: https://docs.privy.io/reference/react-auth/interfaces/PrivyInterface
+[securely stored]: https://docs.privy.io/guide/security#user-data-management
+[paymaster example]: https://github.com/privy-io/base-paymaster-example/blob/main/README.md
+[ERC-4337]: https://eips.ethereum.org/EIPS/eip-4337
+[NFT Contract]: https://goerli.basescan.org/address/0x6527e5052de5521fe370ae5ec0afcc6cd5a221de
+[view for the contract itself]: https://goerli.basescan.org/address/0x6527e5052de5521fe370ae5ec0afcc6cd5a221de
+[Base Paymaster]: https://github.com/base-org/paymaster
+[EntryPoint]: https://github.com/eth-infinitism/account-abstraction/releases
+[bundler]: https://www.alchemy.com/overviews/what-is-a-bundler
+[`create-next-app`]: https://github.com/privy-io/create-next-app
+[add an app]: https://dashboard.alchemy.com/apps
+[Account Kit]: https://www.alchemy.com/blog/introducing-account-kit
+[`hooks/SmartAccountContext.tsx`]: https://github.com/privy-io/base-paymaster-example/blob/main/hooks/SmartAccountContext.tsx
+[`user-operations.ts`]: https://github.com/privy-io/base-paymaster-example/blob/main/lib/user-operations.ts
+[`pages/dashboard.tsx`]: https://github.com/privy-io/base-paymaster-example/blob/main/pages/dashboard.tsx
+[artifact]: https://gist.github.com/briandoyle81CB/2c2849b5723058792bece666f0a318cb
+
diff --git a/_pages/cookbook/account-abstraction/gasless-transactions-with-paymaster.mdx b/_pages/cookbook/account-abstraction/gasless-transactions-with-paymaster.mdx
new file mode 100644
index 00000000..565f50de
--- /dev/null
+++ b/_pages/cookbook/account-abstraction/gasless-transactions-with-paymaster.mdx
@@ -0,0 +1,360 @@
+---
+title: "Gasless Transactions on Base using Base Paymaster"
+---
+
+
+Still trying to onboard users to your app? Want to break free from the worries of gas transactions and sponsor them for your users on Base? Look no further!
+
+Base transaction fees are typically less than a penny, but the concept of gas can still be confusing for new users. You can abstract this away and improve your UX by using the **Base Paymaster**. The Paymaster allows you to:
+
+- Batch multi-step transactions
+- Create custom gasless experiences
+- Sponsor up to $10k monthly on mainnet (unlimited on testnet)
+
+> **Note:** If you need an increase in your sponsorship limit, please [reach out on Discord][Discord]!
+
+## Objectives
+
+1. Configure security measures to ensure safe and reliable transactions.
+2. Manage and allocate resources for sponsored transactions.
+3. Subsidize transaction fees for users, enhancing the user experience by making transactions free.
+4. Set up and manage sponsored transactions on various schedules, including weekly, monthly, and daily cadences.
+
+## Prerequisites
+
+This tutorial assumes you have:
+
+1. **A Coinbase Cloud Developer Platform Account**
+ If not, sign up on the [CDP site]. Once you have your account, you can manage projects and utilize tools like the Paymaster.
+
+2. **Familiarity with Smart Accounts and ERC 4337**
+ Smart Accounts are the backbone of advanced transaction patterns (e.g., bundling, sponsorship). If you’re new to ERC 4337, check out external resources like the official [EIP-4337 explainer](https://eips.ethereum.org/EIPS/eip-4337) before starting.
+
+3. **Foundry**
+ Foundry is a development environment, testing framework, and smart contract toolkit for Ethereum. You’ll need it installed locally for generating key pairs and interacting with smart contracts.
+
+> **Testnet vs. Mainnet**
+> If you prefer not to spend real funds, you can switch to **Base Goerli** (testnet). The steps below are conceptually the same. Just select _Base Goerli_ in the Coinbase Developer Platform instead of _Base Mainnet_, and use a contract deployed on Base testnet for your allowlisted methods.
+
+## Set Up a Base Paymaster & Bundler
+
+In this section, you will configure a Paymaster to sponsor payments on behalf of a specific smart contract for a specified amount.
+
+1. **Navigate to the [Coinbase Developer Platform].**
+2. Create or select your project from the upper left corner of the screen.
+3. Click on the **Paymaster** tool from the left navigation.
+4. Go to the **Configuration** tab and copy the **RPC URL** to your clipboard — you’ll need this shortly in your code.
+
+### Screenshots
+
+- **Selecting your project**
+ 
+
+- **Navigating to the Paymaster tool**
+ 
+
+- **Configuration screen**
+ 
+
+### Allowlist a Sponsorable Contract
+
+1. From the Configuration page, ensure **Base Mainnet** (or **Base Goerli** if you’re testing) is selected.
+2. Enable your paymaster by clicking the toggle button.
+3. Click **Add** to add an allowlisted contract.
+4. For this example, add [`0x83bd615eb93eE1336acA53e185b03B54fF4A17e8`][simple NFT contract], and add the function `mintTo(address)`.
+
+
+
+> **Use your own contract**
+> We use a [simple NFT contract][simple NFT contract] on Base mainnet as an example. Feel free to substitute your own.
+
+### Global & Per User Limits
+
+Scroll down to the **Per User Limit** section. You can set:
+
+- **Dollar amount limit** or **number of UserOperations** per user
+- **Limit cycles** that reset daily, weekly, or monthly
+
+For example, you might set:
+
+- `max USD` to `$0.05`
+- `max UserOperation` to `1`
+
+This means **each user** can only have \$0.05 in sponsored gas and **1** user operation before the cycle resets.
+
+> **Limit Cycles**
+> These reset based on the selected cadence (daily, weekly, monthly).
+
+Next, **Set the Global Limit**. For example, set this to `$0.07` so that once the entire paymaster has sponsored \$0.07 worth of gas (across all users), no more sponsorship occurs unless you raise the limit.
+
+
+
+## Test Your Paymaster Policy
+
+Now let’s verify that these policies work. We’ll:
+
+1. Create two local key pairs (or use private keys you own).
+2. Generate two Smart Accounts.
+3. Attempt to sponsor multiple transactions to see your policy in action.
+
+### Installing Foundry
+
+1. Ensure you have **Rust** installed. If not:
+ ```bash [Terminal]
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+ ```
+2. Install Foundry:
+ ```bash [Terminal]
+ curl -L https://foundry.paradigm.xyz | bash
+ foundryup
+ ```
+3. Verify it works:
+ ```bash [Terminal]
+ cast --help
+ ```
+ If you see Foundry usage info, you’re good to go!
+
+### Create Your Project & Generate Key Pairs
+
+1. Make a new folder and install dependencies:
+ ```bash [Terminal]
+ mkdir sponsored_transactions
+ cd sponsored_transactions
+ npm init es6
+ npm install permissionless
+ npm install viem
+ touch index.js
+ ```
+2. Generate two key pairs with Foundry:
+ ```bash [Terminal]
+ cast wallet new
+ cast wallet new
+ ```
+ You’ll see something like:
+ ```bash [Terminal]
+ Successfully created new keypair.
+ Address: 0xD440D746...
+ Private key: 0x01c9720c1dfa3c9...
+ ```
+ **Store these private keys somewhere safe** — ideally in a `.env` file.
+
+### Project Structure With Environment Variables
+
+Create a `.env` file in `sponsored_transactions`:
+
+```bash [Terminal]
+PAYMASTER_RPC_URL=https://api.developer.coinbase.com/rpc/v1/base/
+PRIVATE_KEY_1=0x01c9720c1dfa3c9...
+PRIVATE_KEY_2=0xbcd6fbc1dfa3c9...
+```
+
+> **Security**
+> **Never** commit `.env` files to a public repo!
+
+## Example `index.js` (Using Twoslash)
+
+Below is a full example of how you might structure `index.js`.
+We’ll use **twoslash** code blocks (````js twoslash`) to highlight key lines and explanations.
+
+```js twoslash
+// --- index.js ---
+// @noErrors
+
+// 1. Import modules and environment variables
+import 'dotenv/config';
+import { http, createPublicClient, encodeFunctionData } from 'viem';
+import { base } from 'viem/chains';
+import { createSmartAccountClient } from 'permissionless';
+import { privateKeyToSimpleSmartAccount } from 'permissionless/accounts';
+import { createPimlicoPaymasterClient } from 'permissionless/clients/pimlico';
+
+// 2. Retrieve secrets from .env
+// Highlight: environment variables for paymaster, private keys
+const rpcUrl = process.env.PAYMASTER_RPC_URL; // highlight
+const firstPrivateKey = process.env.PRIVATE_KEY_1; // highlight
+const secondPrivateKey = process.env.PRIVATE_KEY_2; // highlight
+
+// 3. Declare Base addresses (entrypoint & factory)
+const baseEntryPoint = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789';
+const baseFactoryAddress = '0x15Ba39375ee2Ab563E8873C8390be6f2E2F50232';
+
+// 4. Create a public client for Base
+const publicClient = createPublicClient({
+ chain: base,
+ transport: http(rpcUrl),
+});
+
+// 5. Setup Paymaster client
+const cloudPaymaster = createPimlicoPaymasterClient({
+ chain: base,
+ transport: http(rpcUrl),
+ entryPoint: baseEntryPoint,
+});
+
+// 6. Create Smart Accounts from the private keys
+async function initSmartAccounts() {
+ const simpleAccount = await privateKeyToSimpleSmartAccount(publicClient, {
+ privateKey: firstPrivateKey,
+ factoryAddress: baseFactoryAddress,
+ entryPoint: baseEntryPoint,
+ });
+
+ const simpleAccount2 = await privateKeyToSimpleSmartAccount(publicClient, {
+ privateKey: secondPrivateKey,
+ factoryAddress: baseFactoryAddress,
+ entryPoint: baseEntryPoint,
+ });
+
+ // 7. Create SmartAccountClient for each
+ const smartAccountClient = createSmartAccountClient({
+ account: simpleAccount,
+ chain: base,
+ bundlerTransport: http(rpcUrl),
+ middleware: {
+ sponsorUserOperation: cloudPaymaster.sponsorUserOperation,
+ },
+ });
+
+ const smartAccountClient2 = createSmartAccountClient({
+ account: simpleAccount2,
+ chain: base,
+ bundlerTransport: http(rpcUrl),
+ middleware: {
+ sponsorUserOperation: cloudPaymaster.sponsorUserOperation,
+ },
+ });
+
+ return { smartAccountClient, smartAccountClient2 };
+}
+
+// 8. ABI for the NFT contract
+const nftAbi = [
+ // ...
+ // truncated for brevity
+];
+
+// 9. Example function to send a transaction from a given SmartAccountClient
+async function sendTransaction(client, recipientAddress) {
+ try {
+ // encode the "mintTo" function call
+ const callData = encodeFunctionData({
+ abi: nftAbi,
+ functionName: 'mintTo',
+ args: [recipientAddress], // highlight: specify who gets the minted NFT
+ });
+
+ const txHash = await client.sendTransaction({
+ account: client.account,
+ to: '0x83bd615eb93eE1336acA53e185b03B54fF4A17e8', // address of the NFT contract
+ data: callData,
+ value: 0n,
+ });
+
+ console.log(`✅ Transaction successfully sponsored for ${client.account.address}`);
+ console.log(`🔍 View on BaseScan: https://basescan.org/tx/${txHash}`);
+ } catch (error) {
+ console.error('Transaction failed:', error);
+ }
+}
+
+// 10. Main flow: init accounts, send transactions
+(async () => {
+ const { smartAccountClient, smartAccountClient2 } = await initSmartAccounts();
+
+ // Send a transaction from the first account
+ await sendTransaction(smartAccountClient, smartAccountClient.account.address);
+
+ // Send a transaction from the second account
+ // For variety, let’s also mint to the second account's own address
+ await sendTransaction(smartAccountClient2, smartAccountClient2.account.address);
+})();
+```
+
+> **Note**:
+>
+> - Run this via `node index.js` from your project root.
+> - If your Paymaster settings are strict (e.g., limit 1 transaction per user), the second time you run the script, you may get a “request denied” error, indicating the policy is working.
+
+## Hitting Policy Limits & Troubleshooting
+
+1. **Per-User Limit**
+ If you see an error like:
+
+ ```json
+ {
+ "code": -32001,
+ "message": "request denied - rejected due to maximum per address transaction count reached"
+ }
+ ```
+
+ That means you’ve hit your **UserOperation** limit for a single account. Return to the [Coinbase Developer Platform] UI to adjust the policy.
+
+2. **Global Limit**
+ If you repeatedly run transactions and eventually see:
+ ```json
+ {
+ "code": -32001,
+ "message": "request denied - rejected due to max global usd spend limit reached"
+ }
+ ```
+ You’ve hit the **global** limit of sponsored gas. Increase it in the CDP dashboard and wait a few minutes for changes to take effect.
+
+## Verifying Token Ownership (Optional)
+
+Want to confirm the token actually minted? You can read the NFT’s `balanceOf` function:
+
+```js
+import { readContract } from 'viem'; // highlight
+
+// example function
+async function checkNftBalance(publicClient, contractAddress, abi, ownerAddress) {
+ const balance = await publicClient.readContract({
+ address: contractAddress,
+ abi,
+ functionName: 'balanceOf',
+ args: [ownerAddress],
+ });
+ console.log(`NFT balance of ${ownerAddress} is now: ${balance}`);
+}
+```
+
+## Conclusion
+
+In this tutorial, you:
+
+- Set up and **configured** a Base Paymaster on the Coinbase Developer Platform.
+- **Allowlisted** a contract and specific function (`mintTo`) for sponsorship.
+- Established **per-user** and **global** sponsorship **limits** to control costs.
+- Demonstrated the **sponsorship flow** with Smart Accounts using `permissionless`, `viem`, and Foundry-generated private keys.
+
+This approach can greatly improve your dApp’s user experience by removing gas friction. For more complex sponsorship schemes (like daily or weekly cycles), simply tweak your per-user and global limit settings in the Coinbase Developer Platform.
+
+> **Next Steps**
+>
+> - Use a [proxy service][proxy service] for better endpoint security.
+> - Deploy your own contracts and allowlist them.
+> - Experiment with bundling multiple calls into a single sponsored transaction.
+
+## References
+
+- [list of factory addresses]
+- [Discord]
+- [CDP site]
+- [Coinbase Developer Platform]
+- [UI]
+- [proxy service]
+- [Paymaster Tool]
+- [Foundry Book installation guide]
+- [simple NFT contract]
+
+[list of factory addresses]: https://docs.alchemy.com/reference/factory-addresses
+[Discord]: https://discord.com/invite/cdp
+[CDP site]: https://portal.cdp.coinbase.com/
+[Coinbase Developer Platform]: https://portal.cdp.coinbase.com/
+[UI]: https://portal.cdp.coinbase.com/products/bundler-and-paymaster
+[proxy service]: https://www.smartwallet.dev/guides/paymasters
+[Paymaster Tool]: https://portal.cdp.coinbase.com/products/bundler-and-paymaster
+[Foundry Book installation guide]: https://book.getfoundry.sh/getting-started/installation
+[simple NFT contract]: https://basescan.org/token/0x83bd615eb93ee1336aca53e185b03b54ff4a17e8
+
+**Happy Building on Base!**
diff --git a/_pages/cookbook/client-side-development/introduction-to-providers.mdx b/_pages/cookbook/client-side-development/introduction-to-providers.mdx
new file mode 100644
index 00000000..54d96343
--- /dev/null
+++ b/_pages/cookbook/client-side-development/introduction-to-providers.mdx
@@ -0,0 +1,297 @@
+---
+title: 'Introduction to Providers'
+slug: /intro-to-providers
+description: A tutorial that teaches what providers are, why you need one, and how to configure several providers and use them to connect to the blockchain.
+author: briandoyle81
+---
+
+# Introduction to Providers
+
+This tutorial provides an introduction to providers and shows you how to connect your frontend to the blockchain using JSON RPC blockchain providers, and the [RainbowKit], [wagmi], and [viem] stack.
+
+## Objectives
+
+By the end of this tutorial, you should be able to:
+
+- Compare and contrast public providers vs. vendor providers vs. wallet providers
+- Select the appropriate provider for several use cases
+- Set up a provider in wagmi and use it to connect a wallet
+- Protect API keys that will be exposed to the front end
+
+## Prerequisites
+
+### 1. Be familiar with modern, frontend web development
+
+In this tutorial, we'll be working with a React frontend built with [Next.js]. While you don't need to be an expert, we'll assume that you're comfortable with the basics.
+
+### 2. Possess a general understanding of the EVM and smart contract development
+
+This tutorial assumes that you're reasonably comfortable writing basic smart contracts. If you're just getting started, jump over to our [Base Learn] guides and start learning!
+
+## Types of Providers
+
+Onchain apps need frontends, sometimes called dApps, to enable your users to interact with your smart contracts. A _provider_ makes the connection from frontend to the blockchain, and is used to read data and send transactions.
+
+In blockchain development, the term _provider_ describes a company or service that provides an API enabling access to the blockchain as a service. This is distinct from the providers you wrap your app in using the [React Context API], though you'll use one of those to pass your blockchain provider deeply into your app.
+
+These services enable interacting with smart contracts without the developer needing to run and maintain their own blockchain node. Running a node is expensive, complicated, and challenging. In most cases, you'll want to start out with a provider. Once you start to get traction, you can evaluate the need to [run your own node], or switch to a more advanced architecture solution, such as utilizing [Subgraph].
+
+Figuring out which type of provider to use can be a little confusing at first. As with everything blockchain, the landscape changes rapidly, and search results often return out-of-date information.
+
+
+New onchain devs sometimes get the impression that there are free options for connecting their apps to the blockchain. Unfortunately, this is not really true. Blockchain data is still 1's and 0's, fetched by computation and served to the internet via servers.
+
+It costs money to run these, and you will eventually need to pay for the service.
+
+
+
+You'll encounter providers divided into three general categories: Public Providers, Wallet Providers, and Vendor Providers
+
+### Public Providers
+
+Many tutorials and guides, including the getting started guide for [wagmi], use a _Public Provider_ as the default to get you up and running. Public means that they're open, permissionless, and free, so the guides will also usually warn you that you need to add another provider if you don't want to run into rate limiting. Listen to these warnings! The rate-limits of public providers are severe, and you'll start getting limited very quickly.
+
+In wagmi, a public client is automatically included in the default config. This client is just a wrapper setting up a [JSON RPC] provider using the `chain` and `rpcUrls` listed in Viem's directory of chain information. You can view the [data for Base Sepolia here].
+
+Most chains will list this information in their docs as well. For example, on the network information pages for [Base] and [Optimism]. If you wanted, you could manually set up a `jsonRpcProvider` in wagmi using this information.
+
+### Wallet Providers
+
+Many wallets, including Coinbase Wallet and MetaMask, inject an Ethereum provider into the browser, as defined in [EIP-1193]. The injected provider is accessible via `window.ethereum`.
+
+Under the hood, these are also just JSON RPC providers. Similar to public providers, they are rate-limited.
+
+Older tutorials for early libraries tended to suggest using this method for getting started, so you'll probably encounter references to it. However, it's fallen out of favor, and you'll want to use the public provider for your initial connection experiments.
+
+### Vendor Providers
+
+A growing number of vendors provide access to blockchain nodes as a service. Visiting the landing pages for [QuickNode], [Alchemy], or [Coinbase Developer Platform (CDP)] can be a little confusing. Each of these vendors provides a wide variety of services, SDKs, and information.
+
+Luckily, you can skip most of this if you're just trying to get your frontend connected to your smart contracts. You'll just need to sign up for an account, and get an endpoint, or a key, and configure your app to connect to the provider(s) you choose.
+
+It is worth digging in to get a better understanding of how these providers charge you for their services. The table below summarizes some of the more important API methods, and how you are charged for them by each of the above providers.
+
+Note that the information below may change, and varies by network. Each provider also has different incentives, discounts, and fees for each level of product. They also have different allowances for calls per second, protocols, and number of endpoints. Please check the source to confirm!
+
+| | [Alchemy Costs] | [QuickNode Costs] | [CDP Costs] |
+| :-------------- | :--------------- | :---------------- | :----------------- |
+| Free Tier / Mo. | 3M compute units | 50M credits | 500M billing units |
+| Mid Tier / Mo. | 1.5B CUs @ $199 | 3B credits @ $299 | Coming soon |
+| eth_blocknumber | 10 | 20 | 30 |
+| eth_call | 26 | 20 | 30 |
+| eth_getlogs | 75 | 20 | 100 |
+| eth_getbalance | 19 | 20 | 30 |
+
+To give you an idea of usage amounts, a single wagmi `useContractRead` hook set to watch for changes on a single `view` via a TanStack query and `useBlockNumber` will call `eth_blocknumber` and `eth_call` one time each, every 4 seconds.
+
+## Connecting to the Blockchain
+
+[RainbowKit] is a popular library that works with [wagmi] to make it easy to connect, disconnect, and change between multiple wallets. It's batteries-included out of the box, and allows for a great deal of customization of the list of wallets and connect/disconnect button.
+
+You'll be using RainbowKit's [quick start] to scaffold a new project for this tutorial. Note that at the time of writing, it does **not** use the Next.js app router. See [Building an Onchain App] if you wish to set this up instead.
+
+
+The script doesn't allow you to use `.` to create a project in the root of the folder you run it from, so you'll want to run it from your `src` directory, or wherever you keep your project folders.
+
+It will create a folder with the project name you give, and create the files inside.
+
+
+
+Open up a terminal and run:
+
+```bash
+yarn create @rainbow-me/rainbowkit
+```
+
+Give your project a name, and wait for the script to build it. It will take a minute or two.
+
+
+If you get an error because you are on the wrong version of node, change to the correct version then **delete everything** and run the script again.
+
+
+
+### Scaffolded App
+
+Open your new project in the editor of your choice, and open `pages/_app.tsx`. Here, you'll find a familiar Next.js app wrapped in [context providers] for the TanStack QueryProvider, RainbowKit, and wagmi.
+
+```tsx
+function MyApp({ Component, pageProps }: AppProps) {
+ return (
+
+
+
+
+
+
+
+ );
+}
+```
+
+Note that these providers are using React's context feature to pass the blockchain providers and configuration into your app. It can be confusing to have the word _provider_ meaning two different things in the same file, or even the same line of code!
+
+Before you can do anything else, you need to obtain a _WalletConnect_ `projectId`.
+
+Open up the [WalletConnect] homepage, and create an account, and/or sign in using the method of your choice.
+
+Click the `Create` button in the upper right of the `Projects` tab.
+
+
+
+Enter a name for your project, select the `App` option, and click `Create`.
+
+
+
+Copy the _Project ID_ from the project information page, and paste it in as the `projectId` in `getDefaultWallets`.
+
+```tsx
+const { connectors } = getDefaultWallets({
+ appName: 'RainbowKit App',
+ projectId: 'YOUR_PROJECT_ID',
+ chains,
+});
+```
+
+
+Remember, anything you put on the frontend is public! That includes this id, even if you use environment variables to better manage this type of data. Next.js reminds you of the risk, by requiring you to prepend `NEXT_PUBLIC_` to any environment variables that can be read by the browser.
+
+Before you deploy, make sure you configure the rest of the items in the control panel to ensure only your site can use this id.
+
+
+
+### Public Provider
+
+By default, the setup script will configure your app to use the built-in public provider, and connect to a number of popular chains. To simply matters, remove all but `mainnet` and `base`.
+
+```tsx
+const config = getDefaultConfig({
+ appName: 'RainbowKit App',
+ projectId: 'YOUR_APP_ID_HERE',
+ chains: [mainnet, base],
+ ssr: true,
+});
+```
+
+Open the terminal and start the app with:
+
+```bash
+yarn run dev
+```
+
+Click the `Connect Wallet` button, select your wallet from the modal, approve the connection, and you should see your network, token balance, and address or ENS name at the top of the screen. Select your wallet from the modal.
+
+
+
+You've connected with the Public Provider!
+
+
+
+### QuickNode
+
+To select your provider(s), you'll use [`createConfig`] instead of `getDefaultConfig`. The [`transports`] property allows you to configure how you wish to connect with multiple networks. If you need more than one connector for a given network, you can use [`fallbacks`].
+
+First, set up using [QuickNode] as your provider. Replace your import of the default config from RainbowKit with `createConfig` and `http` from wagmi:
+
+```tsx
+import { createConfig, http, WagmiProvider } from 'wagmi';
+// ...Chains import
+import { RainbowKitProvider } from '@rainbow-me/rainbowkit';
+```
+
+You'll need an RPC URL, so open up [QuickNode]'s site and sign up for an account if you need to. The free tier will be adequate for now, you may need to scroll down to see it. Once you're in, click `Endpoints` on the left side, then click `+ Create Endpoint`.
+
+On the next screen, you'll be asked to select a chain. Each endpoint only works for one. Select `Base`, click `Continue`.
+
+
+
+For now, pick `Base Mainnet`, but you'll probably want to delete this endpoint and create a new one for Sepolia when you start building. The free tier only allows you to have one at a time.
+
+If you haven't already picked a tier, you'll be asked to do so, then you'll be taken to the endpoints page, which will display your endpoints for HTTP and WSS.
+
+
+As with your WalletConnect Id, these endpoints will be visible on the frontend. Be sure to configure the allowlist!
+
+
+
+Use this endpoint to add an `http` `transport` to your config:
+
+```tsx
+const config = createConfig({
+ chains: [mainnet, base],
+ ssr: true,
+ transports: {
+ [base.id]: http('YOUR PROJECT URL'),
+ [mainnet.id]: http('TODO'),
+ },
+});
+```
+
+Now, the app will use your QuickNode endpoint for the Base network. Note that you don't need an app name or WalletConnect Id, because you are no longer using WalletConnect.
+
+To test this out, switch networks a few times. You'll know it's working if you see your balance when Base is the selected network. You haven't added mainnet, so you'll get an error in the console and no balance when you switch to that.
+
+### Alchemy
+
+[Alchemy] is [no longer baked into wagmi], but it still works the same as any other RPC provider. As with QuickNode, you'll need an account and a key. Create an account and/or sign in, navigate to the `Apps` section in the left sidebar, and click `Create new app`.
+
+
+
+Select Base Mainnet, and give your app a name.
+
+
+Once again, remember to configure the [allowlist] when you publish your app, as you'll be exposing your key to the world!
+
+
+
+On the dashboard for your new app, click the `API key` button, and copy the **HTTPS** link to the clipboard. Replace your todo with this link:
+
+```tsx
+const config = createConfig({
+ chains: [mainnet, base],
+ ssr: true,
+ transports: {
+ [base.id]: http('YOUR PROJECT URL'),
+ [mainnet.id]: http('ALCHEMY HTTP URL'),
+ },
+});
+```
+
+As before, you can confirm the Alchemy Provider is working by running the app and changing the network. You should now no longer get an error and should be able to see your balance for Ethereum mainnet.
+
+## Conclusion
+
+In this tutorial, you've learned how Providers supply blockchain connection as a service, eliminating the need for developers to run and maintain their own nodes. You also learned how to connect your app to the blockchain using several different providers, including the public provider(s).
+
+[Base Learn]: https://docs.base.org/learn/welcome
+[Next.js]: https://nextjs.org/
+[RainbowKit]: https://rainbowkit.com/
+[wagmi]: https://wagmi.sh/
+[viem]: https://viem.sh/
+[wagmi]: https://wagmi.sh
+[quick start]: https://www.rainbowkit.com/docs/installation
+[context providers]: https://react.dev/learn/passing-data-deeply-with-context
+[WalletConnect]: https://cloud.reown.com/
+[JSON RPC provider]: https://wagmi.sh/react/providers/jsonRpc
+[Alchemy]: https://www.alchemy.com/
+[QuickNode]: https://www.quicknode.com/
+[allowlist]: https://docs.alchemy.com/docs/how-to-add-allowlists-to-your-apps-for-enhanced-security
+[smart contract development]: https://base.org/learn
+[Subgraph]: https://thegraph.com/docs/en/developing/creating-a-subgraph/
+[data for Base Sepolia here]: https://github.com/wagmi-dev/viem/blob/main/src/chains/definitions/baseSepolia.ts
+[Base]: https://docs.base.org/chain/network-information
+[Optimism]: https://docs.optimism.io/chain/networks
+[EIP-1193]: https://eips.ethereum.org/EIPS/eip-1193
+[QuickNode]: https://www.quicknode.com/
+[Alchemy Costs]: https://docs.alchemy.com/reference/compute-unit-costs
+[QuickNode Costs]: https://www.quicknode.com/api-credits/base
+[Coinbase Developer Platform (CDP)]: https://portal.cdp.coinbase.com/
+[CDP Costs]: https://portal.cdp.coinbase.com/products/base
+[smart contract development]: https://base.org/camp
+[run your own node]: https://docs.base.org/guides/run-a-base-node
+[React Context API]: https://react.dev/learn/passing-data-deeply-with-context
+[Building an Onchain App]: https://docs.base.org/learn/frontend-setup/building-an-onchain-app
+[`createConfig`]: https://wagmi.sh/react/api/createConfig
+[`transports`]: https://wagmi.sh/react/api/transports
+[`fallback`]: https://wagmi.sh/core/api/transports/fallback
+[no longer baked into wagmi]: https://wagmi.sh/react/guides/migrate-from-v1-to-v2#removed-wagmi-providers-entrypoints
+
diff --git a/_pages/cookbook/client-side-development/viem.mdx b/_pages/cookbook/client-side-development/viem.mdx
new file mode 100644
index 00000000..46420341
--- /dev/null
+++ b/_pages/cookbook/client-side-development/viem.mdx
@@ -0,0 +1,112 @@
+---
+title: viem
+slug: /tools/viem
+description: Documentation for using Viem, a TypeScript interface for EVM-compatible blockchains. This page covers installation, setup, and various functionalities such as reading and writing blockchain data and interacting with smart contracts on Base.
+---
+
+# viem
+
+
+Viem is currently only available on Base Sepolia testnet.
+
+
+
+[viem](https://viem.sh/) a TypeScript interface for Ethereum that provides low-level stateless primitives for interacting with Ethereum.
+
+You can use viem to interact with smart contracts deployed on Base.
+
+## Install
+
+To install viem run the following command:
+
+```bash
+npm install --save viem
+```
+
+## Setup
+
+Before you can start using viem, you need to setup a [Client](https://viem.sh/docs/clients/intro.html) with a desired [Transport](https://viem.sh/docs/clients/intro.html) and [Chain](https://viem.sh/docs/chains/introduction).
+
+```javascript
+import { createPublicClient, http } from 'viem';
+import { base } from 'viem/chains';
+
+const client = createPublicClient({
+ chain: base,
+ transport: http(),
+});
+```
+
+
+To use Base, you must specify `base` as the chain when creating a Client.
+
+To use Base Sepolia (testnet), replace `base` with `baseSepolia`.
+
+
+
+## Reading data from the blockchain
+
+Once you have created a client, you can use it to read and access data from Base using [Public Actions](https://viem.sh/docs/actions/public/introduction.html)
+
+Public Actions are client methods that map one-to-one with a "public" Ethereum RPC method (`eth_blockNumber`, `eth_getBalance`, etc.)
+
+For example, you can use the `getBlockNumber` client method to get the latest block:
+
+```javascript
+const blockNumber = await client.getBlockNumber();
+```
+
+## Writing data to the blockchain
+
+In order to write data to Base, you need to create a Wallet client (`createWalletClient`) and specify an [`Account`](https://ethereum.org/en/developers/docs/accounts/) to use.
+
+```javascript
+import { createWalletClient, custom } from 'viem'
+import { base } from 'viem/chains'
+
+//highlight-start
+const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' })
+//highlight-end
+
+const client = createWalletClient({
+ //highlight-next-line
+ account,
+ chain: base,
+ transport: custom(window.ethereum)
+})
+
+client.sendTransaction({ ... })
+```
+
+
+In addition to making a JSON-RPC request (`eth_requestAccounts`) to get an Account, viem provides various helper methods for creating an `Account`, including: [`privateKeyToAccount`](https://viem.sh/docs/accounts/privateKey.html), [`mnemonicToAccount`](https://viem.sh/docs/accounts/mnemonic.html), and [`hdKeyToAccount`](https://viem.sh/docs/accounts/hd.html).
+
+To use Base Sepolia (testnet), replace `base` with `baseSepolia`.
+
+
+
+## Interacting with smart contracts
+
+You can use viem to interact with a smart contract on Base by creating a `Contract` instance using [`getContract`](https://viem.sh/docs/contract/getContract.html) and passing it the contract ABI, contract address, and [Public](https://viem.sh/docs/clients/public.html) and/or [Wallet](https://viem.sh/docs/clients/wallet.html) Client:
+
+```javascript
+import { getContract } from 'viem';
+import { wagmiAbi } from './abi';
+import { publicClient } from './client';
+
+// 1. Create contract instance
+const contract = getContract({
+ address: 'CONTRACT_ADDRESS',
+ abi: wagmiAbi,
+ publicClient,
+});
+
+// 2. Call contract methods, listen to events, etc.
+const result = await contract.read.totalSupply();
+```
+
+
+`CONTRACT_ADDRESS` is the address of the deployed contract.
+
+
+
diff --git a/_pages/cookbook/client-side-development/web3.mdx b/_pages/cookbook/client-side-development/web3.mdx
new file mode 100644
index 00000000..84278142
--- /dev/null
+++ b/_pages/cookbook/client-side-development/web3.mdx
@@ -0,0 +1,112 @@
+---
+title: web3.js
+description: Documentation for using web3.js, a JavaScript library for interacting with EVM-compatible blockchains. This page covers installation, setup, connecting to the Base network and interacting with smart contracts.
+---
+
+# web3.js
+
+[web3.js](https://web3js.org/) is a JavaScript library that allows developers to interact with EVM-compatible blockchain networks.
+
+You can use web3.js to interact with smart contracts deployed on the Base network.
+
+## Install
+
+To install web3.js run the following command:
+
+```bash
+npm install web3
+```
+
+## Setup
+
+Before you can start using web3.js, you need to import it into your project.
+
+Add the following line of code to the top of your file to import web3.js:
+
+```javascript
+//web3.js v1
+const Web3 = require('web3');
+
+//web3.js v4
+const { Web3 } = require('web3');
+```
+
+## Connecting to Base
+
+You can connect to Base by instantiating a new web3.js `Web3` object with a RPC URL of the Base network:
+
+```javascript
+const { Web3 } = require('web3');
+
+const web3 = new Web3('https://mainnet.base.org');
+```
+
+
+To alternatively connect to Base Sepolia (testnet), change the above URL from `https://mainnet.base.org` to `https://sepolia.base.org`.
+
+
+
+## Accessing data
+
+Once you have created a provider, you can use it to read data from the Base network.
+
+For example, you can use the `getBlockNumber` method to get the latest block:
+
+```javascript
+async function getLatestBlock(address) {
+ const latestBlock = await web3.eth.getBlockNumber();
+ console.log(latestBlock.toString());
+}
+```
+
+## Deploying contracts
+
+Before you can deploy a contract to the Base network using web3.js, you must first create an account.
+
+You can create an account by using `web3.eth.accounts`:
+
+```javascript
+const privateKey = "PRIVATE_KEY";
+const account = web3.eth.accounts.privateKeyToAccount(privateKey);
+```
+
+
+`PRIVATE_KEY` is the private key of the wallet to use when creating the account.
+
+
+
+## Interacting with smart contracts
+
+You can use web3.js to interact with a smart contract on Base by instantiating a `Contract` object using the ABI and address of a deployed contract:
+
+```javascript
+const abi = [
+... // ABI of deployed contract
+];
+
+const contractAddress = "CONTRACT_ADDRESS"
+
+const contract = new web3.eth.Contract(abi, contractAddress);
+```
+
+Once you have created a `Contract` object, you can use it to call desired methods on the smart contract:
+
+```javascript
+async function setValue(value) {
+ // write query
+ const tx = await contract.methods.set(value).send();
+ console.log(tx.transactionHash);
+}
+
+async function getValue() {
+ // read query
+ const value = await contract.methods.get().call();
+ console.log(value.toString());
+}
+```
+
+
+For more information on deploying contracts on Base, see [Deploying a Smart Contract](../smart-contract-development/hardhat/deploy-with-hardhat).
+
+
+
diff --git a/_pages/cookbook/cross-chain/bridge-tokens-with-layerzero.mdx b/_pages/cookbook/cross-chain/bridge-tokens-with-layerzero.mdx
new file mode 100644
index 00000000..4ede6628
--- /dev/null
+++ b/_pages/cookbook/cross-chain/bridge-tokens-with-layerzero.mdx
@@ -0,0 +1,632 @@
+---
+title: Sending messages from Base to other chains using LayerZero V2
+description: A tutorial that teaches how to use LayerZero V2 to perform cross-chain messaging from Base Goerli testnet to Optimism Goerli testnet.
+authors:
+ - taycaldwell
+---
+
+# Sending messages from Base to other chains using LayerZero V2
+
+This tutorial will guide you through the process of sending cross-chain message data from a Base smart contract to another smart contract on a different chain using LayerZero V2.
+
+## Objectives
+
+By the end of this tutorial you should be able to do the following:
+
+- Set up a smart contract project for Base using Foundry
+- Install the LayerZero smart contracts as a dependency
+- Use LayerZero to send messages and from smart contracts on Base to smart contracts on different chains
+- Deploy and test your smart contracts on Base testnet
+
+## Prerequisites
+
+### Foundry
+
+This tutorial requires you to have Foundry installed.
+
+- From the command-line (terminal), run: `curl -L https://foundry.paradigm.xyz | bash`
+- Then run `foundryup`, to install the latest (nightly) build of Foundry
+
+For more information, see the Foundry Book [installation guide](https://book.getfoundry.sh/getting-started/installation).
+
+### Coinbase Wallet
+
+In order to deploy a smart contract, you will first need a wallet. You can create a wallet by downloading the Coinbase Wallet browser extension.
+
+- Download [Coinbase Wallet](https://chrome.google.com/webstore/detail/coinbase-wallet-extension/hnfanknocfeofbddgcijnmhnfnkdnaad?hl=en)
+
+### Wallet funds
+
+To complete this tutorial, you will need to fund a wallet with ETH on Base Goerli and Optimism Goerli.
+
+The ETH is required for covering gas fees associated with deploying smart contracts to each network.
+
+- To fund your wallet with ETH on Base Goerli, visit a faucet listed on the [Base Faucets](/chain/network-faucets) page.
+- To fund your wallet with ETH on Optimism Goerli, visit a faucet listed on the [Optimism Faucets](https://docs.optimism.io/builders/tools/faucets) page.
+
+## What is LayerZero?
+
+LayerZero is an interoperability protocol that allows developers to build applications (and tokens) that can connect to multiple blockchains. LayerZero defines these types of applications as "omnichain" applications.
+
+The LayerZero protocol is made up of immutable on-chain [Endpoints](https://docs.layerzero.network/explore/layerzero-endpoint), a configurable [Security Stack](https://docs.layerzero.network/explore/decentralized-verifier-networks), and a permissionless set of [Executors](https://docs.layerzero.network/explore/executors) that transfer messages between chains.
+
+### High-level concepts
+
+#### Endpoints
+
+Endpoints are immutable LayerZero smart contracts that implement a standardized interface for your own smart contracts to use and in order to manage security configurations and send and receive messages.
+
+#### Security Stack (DVNs)
+
+The [Security Stack](https://docs.layerzero.network/explore/decentralized-verifier-networks) is a configurable set of required and optional Decentralized Verifier Networks (DVNs). The DVNs are used to verify message payloads to ensure integrity of your application's messages.
+
+#### Executors
+
+[Executors](https://docs.layerzero.network/explore/executors) are responsible for initiating message delivery. They will automatically execute the `lzReceive` function of the endpoint on the destination chain once a message has been verified by the Security Stack.
+
+## Creating a project
+
+Before you begin, you need to set up your smart contract development environment by creating a Foundry project.
+
+To create a new Foundry project, first create a new directory:
+
+```bash
+mkdir myproject
+```
+
+Then run:
+
+```bash
+cd myproject
+forge init
+```
+
+This will create a Foundry project with the following basic layout:
+
+```bash
+.
+├── foundry.toml
+├── script
+├── src
+└── test
+```
+
+
+You can delete the `src/Counter.sol`, `test/Counter.t.sol`, and `script/Counter.s.sol` boilerplate files that were generated with the project, as you will not be needing them.
+
+
+
+## Installing the LayerZero smart contracts
+
+To use LayerZero within your Foundry project, you need to install the LayerZero smart contracts and their dependencies using `forge install`.
+
+To install LayerZero smart contracts and their dependencies, run the following commands:
+
+```bash
+forge install GNSPS/solidity-bytes-utils --no-commit
+forge install OpenZeppelin/openzeppelin-contracts@v4.9.4 --no-commit
+forge install LayerZero-Labs/LayerZero-v2 --no-commit
+```
+
+Once installed, update your `foundry.toml` file by appending the following lines:
+
+```bash
+remappings = [
+ '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts',
+ 'solidity-bytes-utils/=lib/solidity-bytes-utils',
+ '@layerzerolabs/lz-evm-oapp-v2/=lib/LayerZero-v2/oapp',
+ '@layerzerolabs/lz-evm-protocol-v2/=lib/LayerZero-v2/protocol',
+ '@layerzerolabs/lz-evm-messagelib-v2/=lib/LayerZero-v2/messagelib',
+]
+
+```
+
+## Getting started with LayerZero
+
+LayerZero provides a smart contract standard called [OApp](https://docs.layerzero.network/contracts/oapp) that is intended for omnichain messaging and configuration.
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { OAppSender } from "./OAppSender.sol";
+import { OAppReceiver, Origin } from "./OAppReceiver.sol";
+import { OAppCore } from "./OAppCore.sol";
+
+abstract contract OApp is OAppSender, OAppReceiver {
+ constructor(address _endpoint) OAppCore(_endpoint, msg.sender) {}
+
+ function oAppVersion() public pure virtual returns (uint64 senderVersion, uint64 receiverVersion) {
+ senderVersion = SENDER_VERSION;
+ receiverVersion = RECEIVER_VERSION;
+ }
+}
+```
+
+
+You can view the source code for this contract on [GitHub](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oapp/OApp.sol).
+
+
+
+To get started using LayerZero, developers simply need to inherit from the [OApp](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oapp/OApp.sol) contract, and implement the following two inherited functions:
+
+- `_lzSend`: A function used to send an omnichain message
+- `_lzReceive`: A function used to receive an omnichain message
+
+In this tutorial, you will be implementing the [OApp](https://docs.layerzero.network/contracts/oapp) standard into your own project to add the capability to send messages from a smart contract on Base to a smart contract on Optimism.
+
+
+An extension of the [OApp](https://docs.layerzero.network/contracts/oapp) contract standard known as [OFT](https://docs.layerzero.network/contracts/oft) is also available for supporting omnichain fungible token transfers.
+
+
+
+
+For more information on transferring tokens across chains using LayerZero, visit the [LayerZero documentation](https://docs.layerzero.network/contracts/oft).
+
+
+
+## Writing the smart contract
+
+To get started, create a new Solidity smart contract file in your project's `src/` directory named `ExampleContract.sol`, and add the following content:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { OApp, Origin, MessagingFee } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol";
+
+contract ExampleContract is OApp {
+ constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) {}
+}
+```
+
+The code snippet above defines a new smart contract named `ExampleContract` that extends the `OApp` contract standard.
+
+The contract's constructor expects two arguments:
+
+- `_endpoint`: The [LayerZero Endpoint](https://docs.layerzero.network/explore/layerzero-endpoint) `address` for the chain the smart contract is deployed to.
+- `_owner`: The `address` of the owner of the smart contract.
+
+
+[LayerZero Endpoints](https://docs.layerzero.network/explore/layerzero-endpoint) are smart contracts that expose an interface for OApp contracts to manage security configurations and send and receive messages via the LayerZero protocol.
+
+
+
+### Implementing message sending (`_lzSend`)
+
+To send messages to another chain, your smart contract must call the `_lzSend` function inherited from the [OApp](https://docs.layerzero.network/contracts/oapp) contract.
+
+Add a new custom function named `sendMessage` to your smart contract that has the following content:
+
+```solidity
+/// @notice Sends a message from the source chain to the destination chain.
+/// @param _dstEid The endpoint ID of the destination chain.
+/// @param _message The message to be sent.
+/// @param _options The message execution options (e.g. gas to use on destination).
+function sendMessage(uint32 _dstEid, string memory _message, bytes calldata _options) external payable {
+ bytes memory _payload = abi.encode(_message); // Encode the message as bytes
+ _lzSend(
+ _dstEid,
+ _payload,
+ _options,
+ MessagingFee(msg.value, 0), // Fee for the message (nativeFee, lzTokenFee)
+ payable(msg.sender) // The refund address in case the send call reverts
+ );
+}
+```
+
+The `sendMessage` function above calls the inherited `_lzSend` function, while passing in the following expected data:
+
+| Name | Type | Description |
+| :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `_dstEid` | `uint32` | The [endpoint ID](https://docs.layerzero.network/contracts/endpoint-addresses) of the destination chain to send the message to. |
+| `_payload` | `bytes` | The message (encoded) to send. |
+| `_options` | `bytes` | [Additional options](https://docs.layerzero.network/contracts/options) when sending the message, such as how much gas should be used when receiving the message. |
+| `_fee` | [`MessagingFee`](https://github.com/LayerZero-Labs/LayerZero-v2/blob/c3213200dfe8fabbf7d92c685590d34e6e70da43/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol#L24) | The calculated fee for sending the message. |
+| `_refundAddress` | `address` | The `address` that will receive any excess fee values sent to the endpoint in case the `_lzSend` execution reverts. |
+
+### Implementing gas fee estimation (`_quote`)
+
+As shown in the table provided in the last section, the `_lzSend` function expects an estimated gas [fee](https://github.com/LayerZero-Labs/LayerZero-v2/blob/c3213200dfe8fabbf7d92c685590d34e6e70da43/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol#L24) to be provided when sending a message (`_fee`).
+
+Therefore, sending a message using the `sendMessage` function of your contract, you first need to estimate the associated gas fees.
+
+There are multiple fees incurred when sending a message across chains using LayerZero, including: paying for gas on the source chain, fees paid to DVNs validating the message, and gas on the destination chain. Luckily, LayerZero bundles all of these fees together into a single fee to be paid by the `msg.sender`, and LayerZero Endpoints expose a `_quote` function to estimate this fee.
+
+Add a new function to your `ExampleContract` contract called `estimateFee` that calls the `_quote` function, as shown below:
+
+```solidity
+/// @notice Estimates the gas associated with sending a message.
+/// @param _dstEid The endpoint ID of the destination chain.
+/// @param _message The message to be sent.
+/// @param _options The message execution options (e.g. gas to use on destination).
+/// @return nativeFee Estimated gas fee in native gas.
+/// @return lzTokenFee Estimated gas fee in ZRO token.
+function estimateFee(
+ uint32 _dstEid,
+ string memory _message,
+ bytes calldata _options
+) public view returns (uint256 nativeFee, uint256 lzTokenFee) {
+ bytes memory _payload = abi.encode(_message);
+ MessagingFee memory fee = _quote(_dstEid, _payload, _options, false);
+ return (fee.nativeFee, fee.lzTokenFee);
+}
+```
+
+The `estimateFee` function above calls the inherited `_quote` function, while passing in the following expected data:
+
+| Name | Type | Description |
+| :-------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `_dstEid` | `uint32` | The [endpoint ID](https://docs.layerzero.network/contracts/endpoint-addresses) of the destination chain the message will be sent to. |
+| `_payload` | `bytes` | The message (encoded) that will be sent. |
+| `_options` | `bytes` | [Additional options](https://docs.layerzero.network/contracts/options) when sending the message, such as how much gas should be used when receiving the message. |
+| `_payInLzToken` | `bool` | Boolean flag for which token to use when returning the fee (native or ZRO token). |
+
+
+Your contract's `estimateFee` function should always be called immediately before calling `sendMessage` to accurately estimate associated gas fees.
+
+
+
+### Implementing message receiving (`_lzReceive`)
+
+To receive messages on the destination chain, your smart contract must override the `_lzReceive` function inherited from the [OApp](https://docs.layerzero.network/contracts/oapp) contract.
+
+Add the following code snippet to your `ExampleContract` contract to override the `_lzReceive` function:
+
+```solidity
+/// @notice Entry point for receiving messages.
+/// @param _origin The origin information containing the source endpoint and sender address.
+/// - srcEid: The source chain endpoint ID.
+/// - sender: The sender address on the src chain.
+/// - nonce: The nonce of the message.
+/// @param _guid The unique identifier for the received LayerZero message.
+/// @param _message The payload of the received message.
+/// @param _executor The address of the executor for the received message.
+/// @param _extraData Additional arbitrary data provided by the corresponding executor.
+function _lzReceive(
+ Origin calldata _origin,
+ bytes32 _guid,
+ bytes calldata payload,
+ address _executor,
+ bytes calldata _extraData
+ ) internal override {
+ data = abi.decode(payload, (string));
+ // other logic
+}
+```
+
+The overridden `_lzReceive` function receives the following arguments when receiving a message:
+
+| Name | Type | Description |
+| :------------ | :-------- | :-------------------------------------------------------------------------------------------------------------------- |
+| `_origin` | `Origin` | The origin information containing the source endpoint and sender address. |
+| `_guid` | `bytes32` | The unique identifier for the received LayerZero message. |
+| `payload` | `bytes` | The payload of the received message (encoded). |
+| `_executor` | `address` | The `address` of the [Executor](https://docs.layerzero.network/explore/executors) for the received message. |
+| `_extraData ` | `bytes` | Additional arbitrary data provided by the corresponding [Executor](https://docs.layerzero.network/explore/executors). |
+
+Note that the overridden method decodes the message payload, and stores the string into a variable named `data` that you can read from later to fetch the latest message.
+
+Add the `data` field as a member variable to your contract:
+
+```solidity
+contract ExampleContract is OApp {
+
+ // highlight-next-line
+ string public data;
+
+ constructor(address _endpoint) OApp(_endpoint, msg.sender) {}
+}
+```
+
+
+Overriding the `_lzReceive` function allows you to provide any custom logic you wish when receiving messages, including making a call back to the source chain by invoking `_lzSend`. Visit the LayerZero [Message Design Patterns](https://docs.layerzero.network/contracts/message-design-patterns) for common messaging flows.
+
+
+
+### Final code
+
+Once you complete all of the steps above, your contract should look like this:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { OApp, Origin, MessagingFee } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol";
+
+contract ExampleContract is OApp {
+
+ string public data;
+
+ constructor(address _endpoint) OApp(_endpoint, msg.sender) {}
+
+ /// @notice Sends a message from the source chain to the destination chain.
+ /// @param _dstEid The endpoint ID of the destination chain.
+ /// @param _message The message to be sent.
+ /// @param _options The message execution options (e.g. gas to use on destination).
+ function sendMessage(uint32 _dstEid, string memory _message, bytes calldata _options) external payable {
+ bytes memory _payload = abi.encode(_message); // Encode the message as bytes
+ _lzSend(
+ _dstEid,
+ _payload,
+ _options,
+ MessagingFee(msg.value, 0), // Fee for the message (nativeFee, lzTokenFee)
+ payable(msg.sender) // The refund address in case the send call reverts
+ );
+ }
+
+ /// @notice Estimates the gas associated with sending a message.
+ /// @param _dstEid The endpoint ID of the destination chain.
+ /// @param _message The message to be sent.
+ /// @param _options The message execution options (e.g. gas to use on destination).
+ /// @return nativeFee Estimated gas fee in native gas.
+ /// @return lzTokenFee Estimated gas fee in ZRO token.
+ function estimateFee(
+ uint32 _dstEid,
+ string memory _message,
+ bytes calldata _options
+ ) public view returns (uint256 nativeFee, uint256 lzTokenFee) {
+ bytes memory _payload = abi.encode(_message);
+ MessagingFee memory fee = _quote(_dstEid, _payload, _options, false);
+ return (fee.nativeFee, fee.lzTokenFee);
+ }
+
+ /// @notice Entry point for receiving messages.
+ /// @param _origin The origin information containing the source endpoint and sender address.
+ /// - srcEid: The source chain endpoint ID.
+ /// - sender: The sender address on the src chain.
+ /// - nonce: The nonce of the message.
+ /// @param _guid The unique identifier for the received LayerZero message.
+ /// @param _message The payload of the received message.
+ /// @param _executor The address of the executor for the received message.
+ /// @param _extraData Additional arbitrary data provided by the corresponding executor.
+ function _lzReceive(
+ Origin calldata _origin,
+ bytes32 _guid,
+ bytes calldata payload,
+ address _executor,
+ bytes calldata _extraData
+ ) internal override {
+ data = abi.decode(payload, (string));
+ }
+}
+```
+
+## Compiling the smart contract
+
+Compile the smart contract to ensure it builds without any errors.
+
+To compile your smart contract, run:
+
+```bash
+forge build
+```
+
+## Deploying the smart contract
+
+### Setting up your wallet as the deployer
+
+Before you can deploy your smart contract to various chains you will need to set up a wallet to be used as the deployer.
+
+To do so, you can use the [`cast wallet import`](https://book.getfoundry.sh/reference/cast/cast-wallet-import) command to import the private key of the wallet into Foundry's securely encrypted keystore:
+
+```bash
+cast wallet import deployer --interactive
+```
+
+After running the command above, you will be prompted to enter your private key, as well as a password for signing transactions.
+
+
+For instructions on how to get your private key from Coinbase Wallet, visit the [Coinbase Wallet documentation](https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings#show-private-key). **It is critical that you do NOT commit this to a public repo**.
+
+
+
+To confirm that the wallet was imported as the `deployer` account in your Foundry project, run:
+
+```bash
+cast wallet list
+```
+
+### Setting up environment variables
+
+To setup your environment, create an `.env` file in the home directory of your project, and add the RPC URLs and [LayerZero Endpoint](https://docs.layerzero.network/contracts/endpoint-addresses) information for both Base Goerli and Optimism Goerli testnets:
+
+```bash
+BASE_GOERLI_RPC="https://goerli.base.org"
+BASE_GOERLI_LZ_ENDPOINT=0x464570adA09869d8741132183721B4f0769a0287
+BASE_GOERLI_LZ_ENDPOINT_ID=40184
+
+OPTIMISM_GOERLI_RPC="https://goerli.optimism.io"
+OPTIMISM_GOERLI_LZ_ENDPOINT=0x464570adA09869d8741132183721B4f0769a0287
+OPTIMISM_GOERLI_LZ_ENDPOINT_ID=40132
+```
+
+Once the `.env` file has been created, run the following command to load the environment variables in the current command line session:
+
+```
+source .env
+```
+
+With your contract compiled and environment setup, you are now ready to deploy the smart contract to different networks.
+
+### Deploying the smart contract to Base Goerli
+
+To deploy a smart contract using Foundry, you can use the `forge create` command. The command requires you to specify the smart contract you want to deploy, an RPC URL of the network you want to deploy to, and the account you want to deploy with.
+
+
+Your wallet must be funded with ETH on the Base Goerli and Optimism Goerli to cover the gas fees associated with the smart contract deployment. Otherwise, the deployment will fail.
+
+To get testnet ETH, see the [prerequisites](#prerequisites).
+
+
+
+To deploy the `ExampleContract` smart contract to the Base Goerli testnet, run the following command:
+
+```bash
+forge create ./src/ExampleContract.sol:ExampleContract --rpc-url $BASE_GOERLI_RPC --constructor-args $BASE_GOERLI_LZ_ENDPOINT --account deployer
+```
+
+When prompted, enter the password that you set earlier, when you imported your wallet's private key.
+
+After running the command above, the contract will be deployed on the Base Goerli test network. You can view the deployment status and contract by using a [block explorer](/chain/block-explorers).
+
+### Deploying the smart contract to Optimism Goerli
+
+To deploy the `ExampleContract` smart contract to the Optimism Goerli testnet, run the following command:
+
+```bash
+forge create ./src/ExampleContract.sol:ExampleContract --rpc-url $OPTIMISM_GOERLI_RPC --constructor-args $OPTIMISM_GOERLI_LZ_ENDPOINT --account deployer
+```
+
+When prompted, enter the password that you set earlier, when you imported your wallet's private key.
+
+After running the command above, the contract will be deployed on the Optimism Goerli test network. You can view the deployment status and contract by using the [OP Goerli block explorer](https://goerli-optimism.etherscan.io/).
+
+## Opening the messaging channels
+
+Once your contract has been deployed to Base Goerli and Optimism Goerli, you will need to open the messaging channels between the two contracts so that they can send and receive messages from one another. This is done by calling the `setPeer` function on the contract.
+
+The `setPeer` function expects the following arguments:
+
+| Name | Type | Description |
+| :------ | :-------- | :------------------------------------------------------------------------------------------------------- |
+| `_eid` | `uint32` | The [endpoint ID](https://docs.layerzero.network/contracts/endpoint-addresses) of the destination chain. |
+| `_peer` | `bytes32` | The contract address of the OApp contract on the destination chain. |
+
+### Setting the peers
+
+Foundry provides the `cast` command-line tool that can be used to interact with deployed smart contracts and call their functions.
+
+To set the peer of your `ExampleContract` contracts, you can use `cast` to call the `setPeer` function while providing the [endpoint ID](https://docs.layerzero.network/contracts/endpoint-addresses) and address (in bytes) of the deployed contract on the respective destination chain.
+
+To set the peer of the Base Goerli contract to the Optimism Goerli contract, run the following command:
+
+```bash
+cast send --rpc-url $BASE_GOERLI_RPC "setPeer(uint32, bytes32)" $OPTIMISM_GOERLI_LZ_ENDPOINT_ID --account deployer
+```
+
+
+Replace `` with the contract address of your deployed `ExampleContract` contract on Base Goerli, and`` with the contract address (as bytes) of your deployed `ExampleContract` contract on Optimism Goerli before running the provided `cast` command.
+
+
+
+To set the peer of the Optimism Goerli contract to the Base Goerli contract, run the following command:
+
+```bash
+cast send --rpc-url $OPTIMISM_GOERLI_RPC "setPeer(uint32, bytes32)" $BASE_GOERLI_LZ_ENDPOINT_ID --account deployer
+```
+
+
+Replace `` with the contract address of your deployed `ExampleContract` contract on Optimism Goerli, and`` with the contract address (as bytes) of your deployed `ExampleContract` contract on Base Goerli before running the provided `cast` command.
+
+
+
+## Sending messages
+
+Once peers have been set on each contract, they are now able to send and receive messages from one another.
+
+Sending a message using the newly created `ExampleContract` contract can be done in three steps:
+
+1. Build [message options](https://docs.layerzero.network/contracts/options) to specify logic associated with the message transaction
+2. Call the `estimateFee` function to estimate the gas fee for sending a message
+3. Call the `sendMessage` function to send a message
+
+### Building message options
+
+The `estimateFee` and `sendMessage` custom functions of the `ExampleContract` contract both require a [message options](https://docs.layerzero.network/contracts/options) (`_options`) argument to be provided.
+
+Message options allow you to specify arbitrary logic as part of the message transaction, such as the gas amount the [Executor](https://docs.layerzero.network/explore/executors) pays for message delivery, the order of message execution, or dropping an amount of gas to a destination address.
+
+LayerZero provides a [Solidity](https://github.com/LayerZero-Labs/LayerZero-v2/blob/ccfd0d38f83ca8103b14ab9ca77f32e0419510ff/oapp/contracts/oapp/libs/OptionsBuilder.sol#L12) library and [TypeScript SDK](https://docs.layerzero.network/contracts/options) for building these message options.
+
+As an example, below is a Foundry script that uses OptionsBuilder from the Solidity library to generate message options (as `bytes`) that set the gas amount that the Executor will pay upon message delivery to `200000` wei:
+
+```solidity
+pragma solidity ^0.8.0;
+
+import {Script, console2} from "forge-std/Script.sol";
+import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol";
+
+contract OptionsScript is Script {
+ using OptionsBuilder for bytes;
+
+ function setUp() public {}
+
+ function run() public {
+ bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0);
+ console2.logBytes(options);
+ }
+}
+```
+
+The output of this script results in:
+
+```bash
+0x00030100110100000000000000000000000000030d40
+```
+
+For this tutorial, rather than building and generating your own message options, you can use the bytes output provided above.
+
+
+Covering all of the different message options in detail is out of scope for this tutorial. If you are interested in learning more about the different message options and how to build them, visit the [LayerZero developer documentation](https://docs.layerzero.network/contracts/options).
+
+
+
+### Estimating the gas fee
+
+Before you can send a message from your contract on Base Goerli, you need to estimate the fee associated with sending the message. You can use the `cast` command to call the `estimateFee()` function of the `ExampleContract` contract.
+
+To estimate the gas fee for sending a message from Base Goerli to Optimism Goerli, run the following command:
+
+```bash
+cast send --rpc-url $BASE_GOERLI_RPC "estimateFee(uint32, string, bytes)" $OPTIMISM_GOERLI_LZ_ENDPOINT_ID "Hello World" 0x00030100110100000000000000000000000000030d40 --account deployer
+```
+
+
+Replace `` with the contract address of your deployed `ExampleContract` contract on Base Goerli before running the provided `cast` command.
+
+
+
+The command above calls `estimateFee(uint32, string, bytes, bool)`, while providing the required arguments, including: the endpoint ID of the destination chain, the text to send, and the message options (generated in the last section).
+
+### Sending the message
+
+Once you have fetched the estimated gas for sending your message, you can now call `sendMessage` and provide the value returned as the `msg.value`.
+
+For example, to send a message from Base Goerli to Optimism Goerli with an estimated gas fee, run the following command:
+
+```bash
+cast send --rpc-url $BASE_GOERLI_RPC --value "sendMessage(uint32, string, bytes)" $OPTIMISM_GOERLI_LZ_ENDPOINT_ID "Hello World" 0x00030100110100000000000000000000000000030d40 --account deployer
+```
+
+
+Replace `` with the contract address of your deployed `ExampleContract` contract on Base Goerli, and `` with the gas estimate (in wei) returned by the call to estimateFee, before running the provided `cast` command.
+
+
+
+You can view the status of your cross-chain transaction on [LayerZero Scan](https://layerzeroscan.com/).
+
+### Receiving the message
+
+Once the message has been sent and received on the destination chain, the \_Receive function will be called on the `ExampleContract` and the message payload will be stored in the contract's public `data` variable.
+
+You can use the `cast` command to read the latest message received by the `ExampleContract` stored in the `data` variable.
+
+To read the latest received message data that was sent to Optimism Goerli from Base Goerli, run the following command:
+
+```bash
+cast send --rpc-url $OPTIMISM_GOERLI_RPC "data" --account deployer
+```
+
+The returned data should match the message text payload you sent in your message.
+
+You can view the status of your cross-chain transaction on [LayerZero Scan](https://layerzeroscan.com/).
+
+## Conclusion
+
+Congratulations! You have successfully learned how to perform cross-chain messaging between Base and other chains (i.e. Optimism) using LayerZero V2.
+
+To learn more about cross-chain messaging and LayerZero V2, check out the following resources:
+
+- [LayerZero V2](https://docs.layerzero.network)
+
diff --git a/_pages/cookbook/cross-chain/send-messages-and-tokens-from-base-chainlink.mdx b/_pages/cookbook/cross-chain/send-messages-and-tokens-from-base-chainlink.mdx
new file mode 100644
index 00000000..b6331f72
--- /dev/null
+++ b/_pages/cookbook/cross-chain/send-messages-and-tokens-from-base-chainlink.mdx
@@ -0,0 +1,612 @@
+---
+title: Sending messages and tokens from Base to other chains using Chainlink CCIP
+description: A tutorial that teaches how to use Chainlink CCIP to perform cross-chain messaging and token transfers from Base Goerli testnet to Optimism Goerli testnet.
+authors:
+ - taycaldwell
+tags: ['cross-chain']
+difficulty: intermediate
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+# Sending messages and tokens from Base to other chains using Chainlink CCIP
+
+This tutorial will guide you through the process of sending messages and tokens from a Base smart contract to another smart contract on a different chain using Chainlink's Cross-chain Interoperability Protocol (CCIP).
+
+## Objectives
+
+By the end of this tutorial you should be able to do the following:
+
+- Set up a smart contract project for Base using Foundry
+- Install Chainlink CCIP as a dependency
+- Use Chainlink CCIP within your smart contract to send messages and/or tokens to contracts on other different chains
+- Deploy and test your smart contracts on Base testnet
+
+
+Chainlink CCIP is in an "Early Access" development stage, meaning some of the functionality described within this tutorial is under development and may change in later versions.
+
+
+
+## Prerequisites
+
+### Foundry
+
+This tutorial requires you to have Foundry installed.
+
+- From the command-line (terminal), run: `curl -L https://foundry.paradigm.xyz | bash`
+- Then run `foundryup`, to install the latest (nightly) build of Foundry
+
+For more information, see the Foundry Book [installation guide](https://book.getfoundry.sh/getting-started/installation).
+
+### Coinbase Wallet
+
+In order to deploy a smart contract, you will first need a wallet. You can create a wallet by downloading the Coinbase Wallet browser extension.
+
+- Download [Coinbase Wallet](https://chrome.google.com/webstore/detail/coinbase-wallet-extension/hnfanknocfeofbddgcijnmhnfnkdnaad?hl=en)
+
+### Wallet funds
+
+For this tutorial you will need to fund your wallet with both ETH and LINK on Base Goerli and Optimism Goerli.
+
+The ETH is required for covering gas fees associated with deploying smart contracts to the blockchain, and the LINK token is required to pay for associated fees when using CCIP.
+
+- To fund your wallet with ETH on Base Goerli, visit a faucet listed on the [Base Faucets](https://docs.base.org/tools/network-faucets) page.
+- To fund your wallet with ETH on Optimism Goerli, visit a faucet listed on the [Optimism Faucets](https://docs.optimism.io/builders/tools/faucets) page.
+- To fund your wallet with LINK, visit the [Chainlink Faucet](https://faucets.chain.link/base-testnet).
+
+
+If you are interested in building on Mainnet, you will need to [apply for Chainlink CCIP mainnet access](https://chainlinkcommunity.typeform.com/ccip-form?#ref_id=ccip_docs).
+
+
+
+---
+
+## What is Chainlink CCIP?
+
+Chainlink CCIP (Cross-chain Interoperability Protocol) provides a solution for sending message data and transferring tokens across different chains.
+
+The primary way for users to interface with Chainlink CCIP is through smart contracts known as [Routers](https://docs.chain.link/ccip/architecture#router). A Router contract is responsible for initiating cross-chain interactions.
+
+Users can interact with [Routers](https://docs.chain.link/ccip/architecture#router) to perform the following cross-chain capabilities:
+
+| Capability | Description | Supported receivers |
+| :--------------------------- | :------------------------------------------------------------------------------------------- | :---------------------- |
+| Arbitrary messaging | Send arbitrary (encoded) data from one chain to another. | Smart contracts only |
+| Token transfers | Send tokens from one chain to another. | Smart contracts or EOAs |
+| Programmable token transfers | Send tokens and arbitrary (encoded) data from one chain to another, in a single transaction. | Smart contracts only |
+
+
+Externally owned accounts (EOAs) on EVM blockchains are unable to receive message data, because of this, only smart contracts are supported as receivers when sending arbitrary messages or programmable token transfers. Any attempt to send a programmable token transfer (data and tokens) to an EOA, will result in only the tokens being received.
+
+
+
+### High-level concepts
+
+Although [Routers](https://docs.chain.link/ccip/architecture#router) are the primary interface users will interact with when using CCIP, this section will cover what happens after instructions for a cross-chain interaction are sent to a Router.
+
+#### OnRamps
+
+Once a Router receives an instruction for a cross-chain interaction, it passes it on to another contract known as an [OnRamp](https://docs.chain.link/ccip/architecture#onramp). OnRamps are responsible for a variety of tasks, including: verifying message size and gas limits, preserving the sequencing of messages, managing any fee payments, and interacting with the [token pool](https://docs.chain.link/ccip/architecture#token-pools) to `lock` or `burn` tokens if a token transfer is being made.
+
+#### OffRamps
+
+The destination chain will have a contract known as an [OffRamp](https://docs.chain.link/ccip/architecture#offramp). OffRamps are responsible for a variety of tasks, including: ensuring the authenticity of a message, making sure each transaction is only executed once, and transmitting received messages to the Router contract on the destination chain.
+
+#### Token pools
+
+A [token pool](https://docs.chain.link/ccip/architecture#token-pools) is an abstraction layer over ERC-20 tokens that facilitates OnRamp and OffRamp token-related operations. They are configured to use either a `Lock and Unlock` or `Burn and Mint` mechanism, depending on the type of token.
+
+For example, because blockchain-native gas tokens (i.e. ETH, MATIC, AVAX) can only be minted on their native chains, a `Lock and Mint` mechanism must be used. This mechanism locks the token at the source chain, and mints a synthetic asset on the destination chain.
+
+In contrast, tokens that can be minted on multiple chains (i.e. USDC, USDT, FRAX, etc.), token pools can use a `Burn and Mint` mechanism, where the token is burnt on the source chain and minted on the destination chain.
+
+#### Risk Management Network
+
+Between instructions for a cross-chain interaction making its way from an OnRamp on the source chain to an OffRamp on the destination chain, it will pass through the [Risk Management Network](https://docs.chain.link/ccip/concepts#risk-management-network).
+
+The Risk Management Network is a secondary validation service built using a variety of offchain and onchain components, with the responsibilities of monitoring all chains against abnormal activities.
+
+
+A deep-dive on the technical details of each of these components is too much to cover in this tutorial, but if interested you can learn more by visiting the [Chainlink documentation](https://docs.chain.link/ccip/architecture).
+
+
+
+## Creating a project
+
+Before you begin, you need to set up your smart contract development environment. You can setup a development environment using tools like [Hardhat](../smart-contract-development/hardhat/deploy-with-hardhat) or [Foundry](../smart-contract-development/foundry/deploy-with-foundry). For this tutorial you will use Foundry.
+
+To create a new Foundry project, first create a new directory:
+
+```bash
+mkdir myproject
+```
+
+Then run:
+
+```bash
+cd myproject
+forge init
+```
+
+This will create a Foundry project with the following basic layout:
+
+```bash
+.
+├── foundry.toml
+├── script
+├── src
+└── test
+```
+
+
+You can delete the `src/Counter.sol`, `test/Counter.t.sol`, and `script/Counter.s.sol` boilerplate files that were generated with the project, as you will not be needing them.
+
+
+
+## Installing Chainlink smart contracts
+
+To use Chainlink CCIP within your Foundry project, you need to install Chainlink CCIP smart contracts as a project dependency using `forge install`.
+
+To install Chainlink CCIP smart contracts, run:
+
+```bash
+forge install smartcontractkit/ccip --no-commit
+```
+
+Once installed, update your `foundry.toml` file by appending the following line:
+
+```bash
+remappings = ['@chainlink/contracts-ccip/=lib/ccip/contracts']
+```
+
+## Writing the smart contracts
+
+The most basic use case for Chainlink CCIP is to send data and/or tokens between smart contracts on different blockchains.
+
+To accomplish this, in this tutorial, you will need to create two separate smart contracts:
+
+- `Sender` contract: A smart contract that interacts with CCIP to send data and tokens.
+- `Receiver` contract: A smart contract that interacts with CCIP to receive data and tokens.
+
+### Creating a Sender contract
+
+The code snippet below is for a basic smart contract that uses CCIP to send data:
+
+```solidity
+pragma solidity ^0.8.0;
+
+import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
+import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
+import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
+import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol";
+
+contract Sender is OwnerIsCreator {
+
+ IRouterClient private router;
+ IERC20 private linkToken;
+
+ /// @notice Initializes the contract with the router and LINK token address.
+ /// @param _router The address of the router contract.
+ /// @param _link The address of the link contract.
+ constructor(address _router, address _link) {
+ router = IRouterClient(_router);
+ linkToken = IERC20(_link);
+ }
+
+ /// @notice Sends data to receiver on the destination chain.
+ /// @param destinationChainSelector The identifier (aka selector) for the destination blockchain.
+ /// @param receiver The address of the recipient on the destination blockchain.
+ /// @param text The string text to be sent.
+ /// @return messageId The ID of the message that was sent.
+ function sendMessage(
+ uint64 destinationChainSelector,
+ address receiver,
+ string calldata text
+ ) external onlyOwner returns (bytes32 messageId) {
+ Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
+ receiver: abi.encode(receiver), // Encode receiver address
+ data: abi.encode(text), // Encode text to send
+ tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array indicating no tokens are being sent
+ extraArgs: Client._argsToBytes(
+ Client.EVMExtraArgsV1({gasLimit: 200_000}) // Set gas limit
+ ),
+ feeToken: address(linkToken) // Set the LINK as the feeToken address
+ });
+
+ // Get the fee required to send the message
+ uint256 fees = router.getFee(
+ destinationChainSelector,
+ message
+ );
+
+ // Revert if contract does not have enough LINK tokens for sending a message
+ require(linkToken.balanceOf(address(this)) > fees, "Not enough LINK balance");
+
+ // Approve the Router to transfer LINK tokens on contract's behalf in order to pay for fees in LINK
+ linkToken.approve(address(router), fees);
+
+ // Send the message through the router
+ messageId = router.ccipSend(destinationChainSelector, message);
+
+ // Return the messageId
+ return messageId;
+ }
+}
+```
+
+Create a new file under your project's `src/` directory named `Sender.sol` and copy the code above into the file.
+
+#### Code walkthrough
+
+The sections below provide a detailed explanation for the code for the `Sender` contract provided above.
+
+##### Initializing the contract
+
+In order to send data using CCIP, the `Sender` contract will need access to the following dependencies:
+
+1. **The `Router` contract**: This contract serves as the primary interface when using CCIP to send and receive messages and tokens.
+2. **The fee token contract**: This contract serves as the contract for the token that will be used to pay fees when sending messages and tokens. For this tutorial, the contract address for LINK token is used.
+
+The `Router` contract address and LINK token address are passed in as parameters to the contract's constructor and stored as member variables for later for sending messages and paying any associated fees.
+
+```solidity
+contract Sender is OwnerIsCreator {
+
+ IRouterClient private router;
+ IERC20 private linkToken;
+
+ /// @notice Initializes the contract with the router and LINK token address.
+ /// @param _router The address of the router contract.
+ /// @param _link The address of the link contract.
+ constructor(address _router, address _link) {
+ router = IRouterClient(_router);
+ linkToken = IERC20(_link);
+ }
+```
+
+The `Router` contract provides two important methods that can be used when sending messages using CCIP:
+
+- `getFee`: Given a chain selector and message, returns the fee amount required to send the message.
+- `ccipSend`: Given a chain selector and message, sends the message through the router and returns an associated message ID.
+
+The next section describes how these methods are utilized to send a message to another chain.
+
+##### Sending a message
+
+The `Sender` contract defines a custom method named `sendMessage` that utilizes the methods described above in order to:
+
+1. Construct a message using the `EVM2AnyMessage` method provided by the `Client` CCIP library, using the following data:
+ 1. `receiver`: The receiver contract address (encoded).
+ 1. `data`: The text data to send with the message (encoded).
+ 1. `tokenAmounts`: The amount of tokens to send with the message. For sending just an arbitrary message this field is defined as an empty array (`new Client.EVMTokenAmount[](0)`), indicating that no tokens will be sent.
+ 1. `extraArgs`: Extra arguments associated with the message, such as `gasLimit`.
+ 1. `feeToken`: The `address` of the token to be used for paying fees.
+1. Get the fees required to send the message using the `getFee` method provided by the `Router` contract.
+1. Check that the contract holds an adequate amount of tokens to cover the fee. If not, revert the transaction.
+1. Approve the `Router` contract to transfer tokens on the `Sender` contracts behalf in order to cover the fees.
+1. Send the message to a specified chain using the `Router` contract's `ccipSend` method.
+1. Return a unique ID associated with the sent message.
+
+```solidity
+/// @param receiver The address of the recipient on the destination blockchain.
+/// @param text The string text to be sent.
+/// @return messageId The ID of the message that was sent.
+function sendMessage(
+ uint64 destinationChainSelector,
+ address receiver,
+ string calldata text
+) external onlyOwner returns (bytes32 messageId) {
+ Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
+ receiver: abi.encode(receiver), // Encode receiver address
+ data: abi.encode(text), // Encode text to send
+ tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array indicating no tokens are being sent
+ extraArgs: Client._argsToBytes(
+ Client.EVMExtraArgsV1({gasLimit: 200_000}) // Set gas limit
+ ),
+ feeToken: address(linkToken) // Set the LINK as the feeToken address
+ });
+
+ // Get the fee required to send the message
+ uint256 fees = router.getFee(
+ destinationChainSelector,
+ message
+ );
+
+ // Revert if contract does not have enough LINK tokens for sending a message
+ require(linkToken.balanceOf(address(this)) > fees, "Not enough LINK balance");
+
+ // Approve the Router to transfer LINK tokens on contract's behalf in order to pay for fees in LINK
+ linkToken.approve(address(router), fees);
+ // Send the message through the router
+ messageId = router.ccipSend(destinationChainSelector, message);
+
+ // Return the messageId
+ return messageId;
+}
+```
+
+### Creating a Receiver contract
+
+The code snippet below is for a basic smart contract that uses CCIP to receive data:
+
+```solidity
+pragma solidity ^0.8.0;
+
+import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
+import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
+
+contract Receiver is CCIPReceiver {
+
+ bytes32 private _messageId;
+ string private _text;
+
+ /// @notice Constructor - Initializes the contract with the router address.
+ /// @param router The address of the router contract.
+ constructor(address router) CCIPReceiver(router) {}
+
+ /// @notice Handle a received message
+ /// @param message The cross-chain message being received.
+ function _ccipReceive(
+ Client.Any2EVMMessage memory message
+ ) internal override {
+ _messageId = message.messageId; // Store the messageId
+ _text = abi.decode(message.data, (string)); // Decode and store the message text
+ }
+
+ /// @notice Gets the last received message.
+ /// @return messageId The ID of the last received message.
+ /// @return text The last received text.
+ function getMessage()
+ external
+ view
+ returns (bytes32 messageId, string memory text)
+ {
+ return (_messageId, _text);
+ }
+}
+```
+
+Create a new file under your project's `src/` directory named `Receiver.sol` and copy the code above into the file.
+
+#### Code walkthrough
+
+The sections below provide a detailed explanation for the code for the `Receiver` contract provided above.
+
+##### Initializing the contract
+
+In order to receive data using CCIP, the `Receiver` contract will need to extend to the`CCIPReceiver` interface. Extending this interface allows the `Receiver` contract to initialize the contract with the router address from the constructor, as seen below:
+
+```solidity
+import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
+
+contract Receiver is CCIPReceiver {
+
+ /// @notice Constructor - Initializes the contract with the router address.
+ /// @param router The address of the router contract.
+ constructor(address router) CCIPReceiver(router) {}
+}
+```
+
+##### Receiving a message
+
+Extending the `CCIPReceiver` interface also allows the `Receiver` contract to override the `_ccipReceive` handler method for when a message is received and define custom logic.
+
+```solidity
+/// @notice Handle a received message
+/// @param message The cross-chain message being received.
+function _ccipReceive(
+ Client.Any2EVMMessage memory message
+) internal override {
+ // Add custom logic here
+}
+```
+
+The `Receiver` contract in this tutorial provides custom logic that stores the `messageId` and `text` (decoded) as member variables.
+
+```solidity
+contract Receiver is CCIPReceiver {
+
+ bytes32 private _messageId;
+ string private _text;
+
+ /// @notice Constructor - Initializes the contract with the router address.
+ /// @param router The address of the router contract.
+ constructor(address router) CCIPReceiver(router) {}
+
+ /// @notice Handle a received message
+ /// @param message The cross-chain message being received.
+ function _ccipReceive(
+ Client.Any2EVMMessage memory message
+ ) internal override {
+ _messageId = message.messageId; // Store the messageId
+ _text = abi.decode(message.data, (string)); // Decode and store the message text
+ }
+}
+```
+
+##### Retrieving a message
+
+The `Receiver` contract defines a custom method named `getMessage` that returns the details of the last received message `_messageId` and `_text`. This method can be called to fetch the message data details after the `_ccipReceive` receives a new message.
+
+```solidity
+/// @notice Gets the last received message.
+/// @return messageId The ID of the last received message.
+/// @return text The last received text.
+function getMessage()
+ external
+ view
+ returns (bytes32 messageId, string memory text)
+{
+ return (_messageId, _text);
+}
+```
+
+## Compiling the smart contracts
+
+To compile your smart contracts, run:
+
+```bash
+forge build
+```
+
+## Deploying the smart contract
+
+### Setting up your wallet as the deployer
+
+Before you can deploy your smart contract to the Base network, you will need to set up a wallet to be used as the deployer.
+
+To do so, you can use the [`cast wallet import`](https://book.getfoundry.sh/reference/cast/cast-wallet-import) command to import the private key of the wallet into Foundry's securely encrypted keystore:
+
+```bash
+cast wallet import deployer --interactive
+```
+
+After running the command above, you will be prompted to enter your private key, as well as a password for signing transactions.
+
+
+For instructions on how to get your private key from Coinbase Wallet, visit the [Coinbase Wallet documentation](https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings#show-private-key). **It is critical that you do NOT commit this to a public repo**.
+
+
+
+To confirm that the wallet was imported as the `deployer` account in your Foundry project, run:
+
+```bash
+cast wallet list
+```
+
+### Setting up environment variables
+
+To setup your environment, create an `.env` file in the home directory of your project, and add the RPC URLs, [CCIP chain selectors](https://docs.chain.link/ccip/supported-networks/v1_2_0/testnet), [CCIP router addresses](https://docs.chain.link/ccip/supported-networks/v1_2_0/testnet), and [LINK token addresses](https://docs.chain.link/resources/link-token-contracts) for both Base Goerli and Optimism Goerli testnets:
+
+```bash
+BASE_GOERLI_RPC="https://goerli.base.org"
+OPTIMISM_GOERLI_RPC="https://goerli.optimism.io"
+
+BASE_GOERLI_CHAIN_SELECTOR=5790810961207155433
+OPTIMISM_GOERLI_CHAIN_SELECTOR=2664363617261496610
+
+BASE_GOERLI_ROUTER_ADDRESS="0x80AF2F44ed0469018922c9F483dc5A909862fdc2"
+OPTIMISM_GOERLI_ROUTER_ADDRESS="0xcc5a0B910D9E9504A7561934bed294c51285a78D"
+
+BASE_GOERLI_LINK_ADDRESS="0x6D0F8D488B669aa9BA2D0f0b7B75a88bf5051CD3"
+OPTIMISM_GOERLI_LINK_ADDRESS="0xdc2CC710e42857672E7907CF474a69B63B93089f"
+```
+
+Once the `.env` file has been created, run the following command to load the environment variables in the current command line session:
+
+```bash
+source .env
+```
+
+### Deploying the smart contracts
+
+With your contracts compiled and environment setup, you are ready to deploy the smart contracts.
+
+To deploy a smart contract using Foundry, you can use the `forge create` command. The command requires you to specify the smart contract you want to deploy, an RPC URL of the network you want to deploy to, and the account you want to deploy with.
+
+
+Your wallet must be funded with ETH on the Base Goerli and Optimism Goerli to cover the gas fees associated with the smart contract deployment. Otherwise, the deployment will fail.
+
+To get testnet ETH for Base Goerli and Optimism Goerli, see the [prerequisites](#prerequisites).
+
+
+
+#### Deploying the Sender contract to Base Goerli
+
+To deploy the `Sender` smart contract to the Base Goerli testnet, run the following command:
+
+```bash
+forge create ./src/Sender.sol:Sender --rpc-url $BASE_GOERLI_RPC --constructor-args $BASE_GOERLI_ROUTER_ADDRESS $BASE_GOERLI_LINK_ADDRESS --account deployer
+```
+
+When prompted, enter the password that you set earlier, when you imported your wallet's private key.
+
+After running the command above, the contract will be deployed on the Base Goerli test network. You can view the deployment status and contract by using a [block explorer]: /docs/chain/block-explorers.
+
+#### Deploying the Receiver contract to Optimism Goerli
+
+To deploy the `Receiver` smart contract to the Optimism Goerli testnet, run the following command:
+
+```bash
+forge create ./src/Receiver.sol:Receiver --rpc-url $OPTIMISM_GOERLI_RPC --constructor-args $OPTIMISM_GOERLI_ROUTER_ADDRESS --account deployer
+```
+
+When prompted, enter the password that you set earlier, when you imported your wallet's private key.
+
+After running the command above, the contract will be deployed on the Optimism Goerli test network. You can view the deployment status and contract by using the [OP Goerli block explorer](https://goerli-optimism.etherscan.io/).
+
+### Funding your smart contracts
+
+In order to pay for the fees associated with sending messages, the `Sender` contract will need to hold a balance of LINK tokens.
+
+Fund your contract directly from your wallet, or by running the following `cast` command:
+
+```bash
+cast send $BASE_GOERLI_LINK_ADDRESS --rpc-url $BASE_GOERLI_RPC "transfer(address,uint256)" 5 --account deployer
+```
+
+The above command sends `5` LINK tokens on Base Goerli testnet to the `Sender` contract.
+
+
+Replace `` with the contract address of your deployed `Sender` contract before running the provided `cast` command.
+
+
+
+## Interacting with the smart contract
+
+Foundry provides the `cast` command-line tool that can be used to interact with deployed smart contracts and call their functions.
+
+### Sending data
+
+The `cast` command can be used to call the `sendMessage(uint64, address, string)` function on the `Sender` contract deployed to Base Goerli in order to send message data to the `Receiver` contract on Optimism Goerli.
+
+To call the `sendMessage(uint64, address, string)` function of the `Sender` smart contract, run:
+
+```bash
+cast send --rpc-url $BASE_GOERLI_RPC "sendMessage(uint64, address, string)" $OPTIMISM_GOERLI_CHAIN_SELECTOR "Based" --account deployer
+```
+
+The command above calls the `sendMessage(uint64, address, string)` to send a message. The parameters passed in to the method include: The chain selector to the destination chain (Optimism Goerli), the `Receiver` contract address, and the text data to be included in the message (`Based`).
+
+
+Replace `` and `` with the contract addresses of your deployed `Sender` and `Receiver` contracts respectively before running the provided `cast` command.
+
+
+
+After running the command, a unique `messageId` should be returned.
+
+Once the transaction has been finalized, it will take a few minutes for CCIP to deliver the data to Optimism Goerli and call the `ccipReceive` function on the `Receiver` contract.
+
+
+You can use the [CCIP explorer](https://ccip.chain.link/) to see the status of the CCIP transaction.
+
+
+
+### Receiving data
+
+The `cast` command can also be used to call the `getMessage()` function on the `Receiver` contract deployed to Optimism Goerli in order to read the received message data.
+
+To call the `getMessage()` function of the `Receiver` smart contract, run:
+
+```bash
+cast send --rpc-url $OPTIMISM_GOERLI_RPC "getMessage()" --account deployer
+```
+
+
+Replace `` with the contract addresses of your deployed `Receiver` contract before running the provided `cast` command.
+
+
+
+After running the command, the `messageId` and `text` of the last received message should be returned.
+
+If the transaction fails, ensure the status of your `ccipSend` transaction has been finalized. You can using the [CCIP explorer](https://ccip.chain.link/).
+
+## Conclusion
+
+Congratulations! You have successfully learned how to perform cross-chain messaging on Base using Chainlink CCIP.
+
+To learn more about cross-chain messaging and Chainlink CCIP, check out the following resources:
+
+- [Cross-chain](https://docs.base.org/docs/tools/cross-chain)
+- [Chainlink CCIP](https://docs.chain.link/ccip)
+
diff --git a/_pages/cookbook/nfts/complex-onchain-nfts.mdx b/_pages/cookbook/nfts/complex-onchain-nfts.mdx
new file mode 100644
index 00000000..c8767cf0
--- /dev/null
+++ b/_pages/cookbook/nfts/complex-onchain-nfts.mdx
@@ -0,0 +1,862 @@
+---
+title: 'Complex Onchain NFTs'
+slug: /complex-onchain-nfts
+description: A tutorial that teaches how to make complex nfts that are procedurally generated and have onchain metadata and images.
+author: briandoyle81
+
+---
+
+# Complex Onchain NFTs
+
+Many NFTs are dependent on offchain metadata and images. Some use immutable storage locations, such as [IPFS]. Others use traditional web locations, and many of these allow the owner of the contract to modify the URL returned by a contract when a site or user attempts to retrieve the location of the token art and metadata. This power isn't inherently bad, because we probably want someone to be able to fix the contract if the storage location goes down. However, it does introduce a requirement to trust the contract owner.
+
+Although challenging, it is possible to write a smart contract that contains all the necessary logic and data to generate json metadata and SVG images, entirely onchain. It **will** be expensive to deploy, but will be as cheap as simpler contracts to mint!
+
+In this tutorial, we'll show you how to do this to create your own fully-onchain art project, similar to our [sample project].
+
+## Objectives
+
+By the end of this tutorial you should be able to:
+
+- Programmatically generate and return json metadata for ERC-721 tokens
+- Deterministically construct unique SVG art in a smart contract
+- Generate deterministic, pseudorandom numbers
+
+## Prerequisites
+
+### ERC-721 Tokens
+
+This tutorial assumes that you are able to write, test, and deploy your own ERC-721 tokens using the Solidity programming language. If you need to learn that first, check out our content in [Base Learn] or the sections specific to [ERC-721 Tokens]!
+
+### Vector Art
+
+You'll need some familiarity with the SVG art format and a basic level of ability to edit and manipulate vector art. If you don't have this, find an artist friend and collaborate!
+
+## Creating the Art Assets
+
+To start, you'll need to build a few vector art assets and mock up an example of what your NFT might look like. Later, you'll cut these up and format them in a way that your smart contract will use to assemble unique NFTs for each minter.
+
+The mockup needs to have all of the elements you plan to have in the NFT, and you should be able to manually move things around or change colors to make it so that you can create the range of variation you want. For example:
+
+- A gradient sky in which the colors are randomized
+- One of three styles of sun, always in the same spot in the upper right corner
+- One to five clouds placed randomly in the upper half of the canvas
+- A wide mountain ridge that will always be in the middle, but slide side to side to show a different part for each NFT
+- An ocean in the foreground that is always the same
+
+If you are an artist, or are working with one, you can use the vector drawing tool of your choice to assemble your mockup. If not, you can use a number of stock art or AI tool options to assist you. If you do, make sure you understand any relevant laws or terms of service!
+
+You can also work from ours: [Sample Art]
+
+Either way, you should end up with something similar to this:
+
+
+
+## The Art of Making it Fit
+
+You'll notice that the SVG is probably way too big to be placed in a smart contract. The example is 103 KB, so you'll have to be clever to make this work.
+
+You'll accomplish this task by splitting each element out of the mockup and deploying them into separate smart contracts. To do so, individually export each element, and make sure that the exported pieces are no bigger than about 15 KB. That way, you'll have enough space to fit each piece within the 24KiB limit for compiled bytecode.
+
+If you're working with the sample, you'll end up with individual SVGs for:
+
+- Sun 1: 9 KB
+- Sun 2: 9 KB
+- Sun 3: 9 KB
+- Ocean: 17 KB
+- Mountain: 14 KB
+- Cloud: 6 KB
+- Sky: 802 bytes
+
+If you don't have the tools to do this, you can find these files here: [Sample Art]
+
+## Contract Architecture
+
+You'll need to build and deploy a number of contracts for this project. They'll be organized in this architecture:
+
+
+
+Deploying this many contracts will have a cost associated with it, but once they're deployed, this contract will cost the same as any other NFT contract. Remember, `pure` and `view` functions called outside the blockchain don't cost any gas. This means that you can use multiple contracts to assemble a relatively large graphic without additional costs!
+
+## Building the Contracts
+
+Create a new project using the toolkit of your choice, and add a contract called `LandSeaSkyNFT`. Import OpenZeppelin's ERC-721, inherit from it, and set it up with the constructor, a mint function, and a counter to keep track of the token ID:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.20;
+
+import "hardhat/console.sol";
+import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+
+contract LandSeaSkyNFT is ERC721 {
+ uint public counter;
+
+ constructor() ERC721("Land, Sea, and Sky", "LSS") {}
+
+ function mint() public {
+ counter++;
+ _safeMint(msg.sender, counter);
+ }
+}
+```
+
+### Overriding the `_baseURI()` Function
+
+Normally, you'd override `_baseURI()` with the base URL for the location you select to keep your NFT metadata. This could be a website, IPFS folder, or many other possible locations.
+
+Since this contract will be generating the .json file directly, instead set it to indicate this to the browser:
+
+```solidity
+function _baseURI() internal pure override returns (string memory) {
+ return "data:application/json;base64,";
+}
+```
+
+### Importing the Base64 Library
+
+As indicated above, you'll be returning the json metadata in [Base64] format. OpenZeppelin has a utility contract to do this. You'll also need the `Strings` library. Go ahead and import them:
+
+```solidity
+import "@openzeppelin/contracts/utils/Base64.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+```
+
+
+Base64 allows the transport of binary data over the web in a reliable way. It is not a compression algorithm, and actually increased the data size by a 4/3 ratio.
+
+
+
+### Planning the override for the `tokenURI()` Function
+
+Next, set up your `tokenURI` function override. You'll need to write some other contracts to make this work, but you can write most of the code and stub out a plan for the rest to:
+
+- Check and ensure the token ID exists
+- Compile the json metadata for the token, including:
+ - The `"name"` of the NFT
+ - A `"description"`
+ - The `"image"`
+- Base64 encode the above, combine it with the `_baseURI` and return it.
+
+```solidity
+function tokenURI(uint _tokenId) public view override returns (string memory) {
+ if(_tokenId > counter) {
+ revert InvalidTokenId(_tokenId); // Don't forget to add the error above!
+ }
+
+ string memory json = Base64.encode(
+ bytes(
+ string(
+ abi.encodePacked(
+ '{"name": "Land, Sea, and Sky #: ',
+ Strings.toString(_tokenId),
+ '", "description": "Land, Sea, and Sky is a collection of generative art pieces stored entirely onchain.", "image": "data:image/SVG+xml;base64,',
+ "TODO: Build the SVG with the token ID as the seed",
+ '"}'
+ )
+ )
+ )
+ );
+
+ return string(abi.encodePacked(_baseURI(), json));
+}
+```
+
+
+Getting the quotes and commas correct when the json is broken apart like this is challenging. When debugging, look here first!
+
+
+
+### Test Your Progress
+
+Test your function by writing a simple test to mint an NFT, then call and log the output of the `tokenURI` function. You should get something similar to:
+
+```text
+string: data:application/json;base64,eyJuYW1lIjogIkxhbmQsIFNlYSwgYW5kIFNreSAjMSIsICJkZXNjcmlwdGlvbiI6ICJMYW5kLCBTZWEsIGFuZCBTa3kgaXMgYSBjb2xsZWN0aW9uIG9mIGdlbmVyYXRpdmUgYXJ0IHBpZWNlcyBzdG9yZWQgZW50aXJlbHkgb25jaGFpbi4iLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxUT0RPOiBCdWlsZCB0aGUgU1ZHIHdpdGggdGhlIHRva2VuIElEIGFzIHRoZSBzZWVkIn0=
+```
+
+To see if it worked, you'll need to use a manual method to decode the base64 data; everything after the comma:
+
+```text
+eyJuYW1lIjogIkxhbmQsIFNlYSwgYW5kIFNreSAjMSIsICJkZXNjcmlwdGlvbiI6ICJMYW5kLCBTZWEsIGFuZCBTa3kgaXMgYSBjb2xsZWN0aW9uIG9mIGdlbmVyYXRpdmUgYXJ0IHBpZWNlcyBzdG9yZWQgZW50aXJlbHkgb25jaGFpbi4iLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxUT0RPOiBCdWlsZCB0aGUgU1ZHIHdpdGggdGhlIHRva2VuIElEIGFzIHRoZSBzZWVkIn0=
+```
+
+You can use the terminal: `echo -n '' | base64 --decode`
+
+Do so, and you'll get:
+
+```text
+{"name": "Land, Sea, and Sky #: 1", "description": "Land, Sea, and Sky is a collection of generative art pieces stored entirely onchain.", "image": "data:image/SVG+xml;base64,TODO: Build the SVG with the token ID as the seed"}
+```
+
+## Building the SVG
+
+Next, you need to build logic to compile a real, working SVG from the pieces you've saved. You'll also need to add some variation based on the ID of the NFT.
+
+### SVG Renderer Contract
+
+Add a new file and contract called `SVGRenderer`. It doesn't need a constructor, but it will need the `Strings` library:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.20;
+
+import "hardhat/console.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+
+contract SVGRenderer {
+
+}
+```
+
+Open the exemplar SVG in a code editor, and using it as an example, build out a function that uses `abi.encodePacked` to build everything from the SVG **except** the actual art. That's much too big for one contract, so add stubs instead.
+
+Depending on the tool you used to make the SVG, there may be unneeded extras you can remove from these lines. You also **don't** need the items in `` or ``. You'll take advantage of the flexibility of the format to include those in the pieces returned by the supporting contract.
+
+```solidity
+function render(uint _tokenId) public view returns (string memory) {
+ return string(
+ abi.encodePacked(
+ "",
+ // TODO: Add the clouds,
+ // TODO: Add the sun,
+ // TODO: Add the land,
+ // TODO: Add the sea,
+ // TODO: Add the background,
+ " "
+ )
+ );
+}
+```
+
+### Rendering the Sea
+
+The sea element of this NFT will be the same for all NFTs, so it makes sense to write that contract first. Create it called, `SeaRenderer`, with a function called `render`. The `` element is the root of the different pieces of the SVG, so add that and a stub for the rest.
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.20;
+
+import "hardhat/console.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+
+contract SeaRenderer {
+ function render() public pure returns (string memory) {
+ return // TODO: Render the sea
+ }
+}
+```
+
+The next part is tricky, and a little messy. You'll need to combine parts of the individually exported SVG that has the sea art and all of its properties with the position data for this part of the art from the exemplar SVG. You'll then need to flatten it to a single line, and add it as a string constant.
+
+Start by opening the Ocean SVG. Change the viewBox to `viewBox="0 0 1024 1024"`. Move the `` and `` tag inside of the `` tag. Open the SVG in the browser to make sure it hasn't broken.
+
+Next, delete the `id` and `data-name` from the top level `` and experiment with the `transform="translate(20,2.5)"` property to move the art back down to the bottom of the viewport.
+
+With the sample art, `` should work.
+
+The last edits you need to make are **critical** - do a find/replace to change all of the `cls-1` and similar classnames, to `cls-land-1`! Otherwise, the classes will override one another and nothing will be the right color. Also find all instances of `linear-gradient` and do the same.
+
+**Make sure** you change both the definitions, and where they're called!
+
+Finally, use the tool of your choice to minify **only** the outermost `` tag and its contents. This will flatten the code to a single line and remove extra empty character spaces. Doing so makes it easier to add to your contract, and makes the data smaller. Add it as a constant string to `SeaRenderer.sol`:
+
+```solidity
+string constant SVG = ' ';
+```
+
+You may need to do a find/replace and ensure you're using only one type of quote in the SVG.
+
+Replace your `TODO` with the constant.
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.20;
+
+import "hardhat/console.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+
+string constant SVG = ;
+
+contract SeaRenderer {
+ function render() public pure returns (string memory) {
+ return SVG;
+ }
+}
+```
+
+Test this function independently and make sure that if you paste the content inside a set of `` tags, it renders as expected!
+
+### Calling SeaRenderer
+
+Return to `SVGRenderer.sol`. Add an `interface` for the `SeaRenderer`. All of your renderer contracts will have a function called `render` that either takes a `uint _tokenId`, or no arguments, and returns a string. Because of this, you can use a single interface for all the render contracts:
+
+```solidity
+interface ISVGPartRenderer {
+ function render() external pure returns (string memory);
+ function render(uint _tokenId) external pure returns (string memory);
+}
+```
+
+Add an instance inside the `SVGRenderer` contract for the `SeaRenderer`, and a constructor that takes an address for the `SeaRenderer`:
+
+```solidity
+constructor(address _seaRenderer) {
+ seaRenderer = ISVGPartRenderer(_seaRenderer);
+}
+```
+
+Replace `// TODO: Add the sea,` with a call to your external function.
+
+```solidity
+function render(uint _tokenId) public view returns (string memory) {
+ return string(
+ abi.encodePacked(
+ "",
+ // TODO: Add the clouds,
+ // TODO: Add the sun,
+ // TODO: Add the land,
+ seaRenderer.render(),
+ // TODO: Add the background,
+ " "
+ )
+ );
+}
+```
+
+## Finishing a First Pass
+
+Return to your `LandSeaSkyNFT` contract and add an `interface` for the `SVGRenderer`.
+
+```solidity
+interface ISVGRenderer {
+ function render(uint _tokenId) external view returns (string memory);
+}
+```
+
+Add an instance of it and update the `constructor` to set it:
+
+```
+ISVGRenderer SVGRenderer;
+
+constructor(address _SVGRenderer) ERC721("Land, Sea, and Sky", "LSS") {
+ SVGRenderer = ISVGRenderer(_SVGRenderer);
+}
+```
+
+
+For testing purposes, it may be easier if you add functions to allow you to change these addresses after deployment. But the whole point of all this work is to make immutable, onchain NFTs, so be sure to delete them before you do your real deployment!
+
+
+
+Finally, swap your `TODO` with a line to `Base64.encode` a call to the renderer:
+
+```solidity
+string memory json = Base64.encode(
+ bytes(
+ string(
+ abi.encodePacked(
+ '{"name": "Land, Sea, and Sky #: ',
+ Strings.toString(tokenId),
+ '", "description": "Land, Sea, and Sky is a collection of generative art pieces stored entirely onchain.", "image": "data:image/SVG+xml;base64,',
+ Base64.encode(bytes(SVGRenderer.render(tokenId))),
+ '"}'
+ )
+ )
+ )
+);
+```
+
+### Test Deploy
+
+Now is a good time to deploy to testnet and see if this first pass is working as expected. If you're using [Hardhat and Hardhat Deploy], you can use this script:
+
+```tsx
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+import { DeployFunction } from 'hardhat-deploy/types';
+
+const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
+ const { deployments, getNamedAccounts } = hre;
+ const { deploy } = deployments;
+ const { deployer } = await getNamedAccounts();
+
+ const SeaRenderer = await deploy('SeaRenderer', {
+ from: deployer,
+ });
+
+ const SVGRenderer = await deploy('SVGRenderer', {
+ from: deployer,
+ args: [SeaRenderer.address],
+ });
+
+ const LandSeaSkyNFT = await deploy('LandSeaSkyNFT', {
+ from: deployer,
+ args: [SVGRenderer.address],
+ });
+
+ await hre.run('verify:verify', {
+ address: LandSeaSkyNFT.address,
+ constructorArguments: [SVGRenderer.address],
+ contract: 'contracts/LandSeaSkyNFT.sol:LandSeaSkyNFT',
+ });
+};
+export default func;
+```
+
+Deploy the contracts to Base Sepolia and verify them, or at least verify `LandSeaSkyNFT` (the above script will do this).
+
+Open the contract in [Basescan], connect with your wallet, and mint some NFTs.
+
+**Wait a few minutes**, then open the [testnet version of Opensea] and look up your contract. It may take several minutes to show up, but when it does, if everything is working you'll see NFTs with the ocean part of the art! Neat!
+
+
+
+## Adding the Sky Renderer
+
+Great work! Much of the hardest part is done. All you need to do now is add a renderer for each of the other elements, with the twist that you'll be doing customization inside the SVGs themselves. You'll have to do a little surgery!
+
+### Preparing the SVG
+
+Open the sky SVG in both an editor, and the browser. As with for the sea, the first step is to change the viewport to 1024x1024, move the `` and `
+
+
+
+
+
+
+
+
+
+
+```
+
+You have some design choices to make here. You could use deterministically chosen, but essentially random colors to make up the gradient. However, doing so will lead to the vast majority of NFTs having truly bizarre colors that don't look nice and don't look like they belong with the rest of the art.
+
+No one wants their sky to be a mix of cyan, teal, maroon, and brown!
+
+It might be better to add a gentle modification to the existing gradient colors and range. Start by minifying the top level `` group and contents, then create **two** constant strings, one for everything before the first `` element, and one for everything after the last `` element.
+
+Neither string should have the ``s. You'll make those next.
+
+### Building the Renderer Contract
+
+Add a new file and contract called `SkyRenderer`. Add your strings:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.20;
+
+import "hardhat/console.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+
+string constant START = ' ';
+string constant END = ' ';
+
+contract SkyRenderer {
+ function render(uint _tokenId) public pure returns (string memory) {
+ return //TODO;
+ }
+}
+```
+
+Next, add constants for the existing `offset` and `stop-color` properties:
+
+```solidity
+string constant OFFSET1 = ".12";
+string constant OFFSET2 = ".34";
+string constant OFFSET3 = ".68";
+string constant OFFSET4 = ".96";
+string constant COLOR1 = "#c391b4";
+string constant COLOR2 = "#ce9f9b";
+string constant COLOR3 = "#dfd061";
+string constant COLOR4 = "#e3f9f7";
+```
+
+Now, stub out your return for the `render` function. It will use the built in method of using `abi.encode` and casting to `string` to combine all the parts and return them.
+
+```solidity
+function render(uint _tokenId) public pure returns (string memory) {
+ return string(
+ abi.encodePacked(
+ START,
+ // TODO stop 1,
+ // TODO stop 2,
+ // TODO stop 3,
+ // TODO stop 4,
+ END
+ )
+ );
+}
+```
+
+Do the same for a function to `buildStop`:
+
+```solidity
+function _buildStop(
+ string memory _offset,
+ string memory _color,
+ uint _tokenId,
+ uint _stopNumber
+ ) internal pure returns (string memory) {
+ return string(
+ abi.encodePacked(
+ ' '
+ )
+ );
+ }
+```
+
+Now, you just need to figure out how to modify the properties based on the `_tokenId`. It needs to be "random" in the sense that every NFT should be different, but it has to be deterministic, so that you get the same art every time you load the image.
+
+First, **subtract 10** from the values and convert them to `uints` **without** decimals in each of your stop constants, and reduce the last to `80`:
+
+```solidity
+uint constant OFFSET1 = 2;
+uint constant OFFSET2 = 24;
+uint constant OFFSET3 = 58;
+uint constant OFFSET4 = 80;
+```
+
+You'll also need to update the parameter in `_buildStop`.
+
+Add a function to `_buildOffsetValue`. This will pick an integer between 0 and 20 for each offset, and add it to the modified offsets you just made. The result will be a change of + or 1 10 for each value (with the last being slightly different to keep it in range):
+
+```solidity
+function _buildOffsetValue(
+ uint _offset,
+ uint _tokenId,
+ uint _stopNumber
+ ) internal pure returns (string memory) {
+ bytes32 hash = keccak256(abi.encodePacked(_offset, _tokenId, _stopNumber));
+ uint rand = uint(hash);
+ uint change = rand % 20; // Produces a number between 0 and 19
+ if(change >= 10) {
+ return string(
+ abi.encodePacked(
+ '.',
+ Strings.toString(_offset + change)
+ )
+ );
+ } else {
+ return string(
+ abi.encodePacked(
+ '.',
+ '0', // 9 is .09, not .9
+ Strings.toString(_offset + change)
+ )
+ );
+ }
+}
+```
+
+This function uses hashing to create a pseudo-random number with the token id and stop as seeds, guaranteeing a consistent value, unique for each token and each stop within that token. It takes advantage of the way the offset property is interpreted - in this case, `".12+.20" == ".32"`.
+
+Finally, update your `render` function to call `_buildStop`:
+
+```solidity
+function render(uint _tokenId) public pure returns (string memory) {
+ return string(
+ abi.encodePacked(
+ START,
+ _buildStop(OFFSET1, COLOR1, _tokenId, 1),
+ _buildStop(OFFSET2, COLOR2, _tokenId, 2),
+ _buildStop(OFFSET3, COLOR3, _tokenId, 3),
+ _buildStop(OFFSET4, COLOR4, _tokenId, 4),
+ END
+ )
+ );
+}
+```
+
+### Incorporating the Sky Renderer
+
+Return to `SVGRenderer.sol` and add an instance of `ISVGPartRenderer` for the skyRenderer. Add an argument to the `constructor` and initialize it, then call the `render` function in place of your `TODO` for the background.
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.20;
+
+import "hardhat/console.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+
+interface ISVGPartRenderer {
+ function render() external pure returns (string memory);
+ function render(uint _tokenId) external pure returns (string memory);
+}
+
+contract SVGRenderer {
+
+ ISVGPartRenderer seaRenderer;
+ ISVGPartRenderer skyRenderer;
+
+ constructor(address _seaRenderer, address _skyRenderer) {
+ seaRenderer = ISVGPartRenderer(_seaRenderer);
+ skyRenderer = ISVGPartRenderer(_skyRenderer);
+ }
+
+ function render(uint _tokenId) public view returns (string memory) {
+ return string(
+ abi.encodePacked(
+ "",
+ // TODO: Add the clouds,
+ // TODO: Add the sun,
+ // TODO: Add the land,
+ skyRenderer.render(_tokenId),
+ seaRenderer.render(),
+ " "
+ )
+ );
+ }
+}
+```
+
+Update your deploy script, then deploy and test as before.
+
+```tsx
+const SkyRenderer = await deploy('SkyRenderer', {
+ from: deployer,
+});
+
+const SVGRenderer = await deploy('SVGRenderer', {
+ from: deployer,
+ args: [SeaRenderer.address, SkyRenderer.address],
+});
+```
+
+Test as before. Your NFTs now have the sky!
+
+## Adding the LandRenderer
+
+Next up is the mountain part of the SVG. For this, you'll change the horizontal translation left to right to show a different part of the mountains for each NFT.
+
+### Preparing the SVG
+
+Open the mountain SVG in both your browser and the editor. Once again, set the `viewBox` to 1024x1024 and move the `` and `` inside the top-level group (``).
+
+Find transform/translate values that first put the mountains so that they are at the bottom, and the left-most portion is shown, then the right-most. `transform="translate(-150,350)"` and `transform="translate(-800,350)"` are about right.
+
+Don't forget to add `-land` to the classnames!
+
+### Writing the Contract
+
+Add a file and stub for the `LandRenderer`:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.20;
+
+import "hardhat/console.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+
+contract LandRenderer {
+ function render(uint _tokenId) public pure returns (string memory) {
+ return string(
+ abi.encodePacked(
+ '',
+ END
+ )
+ );
+ }
+}
+```
+
+Minify the top-level `` element, and add a constant with everything after the opening `` tag. Use similar techniques as before to generate an offset based on the token id, then build the SVG. You'll end up with something like this:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.20;
+
+import "hardhat/console.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+
+string constant END = "";
+
+contract LandRenderer {
+ function render(uint _tokenId) public pure returns (string memory) {
+ return string(
+ abi.encodePacked(
+ '',
+ END
+ )
+ );
+ }
+
+ function _buildOffset(uint _tokenId) internal pure returns (string memory) {
+ bytes32 hash = keccak256(abi.encodePacked(_tokenId));
+ uint rand = uint(hash);
+ uint xOffset = (rand % 650) + 150; // Produces a number between 150 and 799
+ return string(abi.encodePacked("-", Strings.toString(xOffset)));
+ }
+}
+```
+
+### Incorporating the LandRenderer
+
+Update `SVGRenderer`:
+
+```solidity
+ISVGPartRenderer seaRenderer;
+ISVGPartRenderer skyRenderer;
+ISVGPartRenderer landRenderer;
+
+constructor(address _seaRenderer, address _skyRenderer, address _landRenderer) {
+ seaRenderer = ISVGPartRenderer(_seaRenderer);
+ skyRenderer = ISVGPartRenderer(_skyRenderer);
+ landRenderer = ISVGPartRenderer(_landRenderer);
+}
+
+function render(uint _tokenId) public view returns (string memory) {
+ return string(
+ abi.encodePacked(
+ "",
+ skyRenderer.render(_tokenId),
+ landRenderer.render(_tokenId),
+ seaRenderer.render(),
+ " "
+ )
+ );
+}
+```
+
+And the deploy script:
+
+```tsx
+const LandRenderer = await deploy('LandRenderer', {
+ from: deployer,
+});
+
+const SVGRenderer = await deploy('SVGRenderer', {
+ from: deployer,
+ args: [SeaRenderer.address, SkyRenderer.address, LandRenderer.address],
+});
+```
+
+Test as before. It's starting to look really nice!
+
+
+
+## Adding the Sun Renderer
+
+The sun renderer will use similar techniques as those you've already incorporated. The sun will be in the same place for all NFTs. Variation will come from each one having only one of the three suns shown in the exemplar art file.
+
+### Preparing the SVGs
+
+For each of the three sun SVGs:
+
+- Change the `viewBox` to 1024x1024
+- Move the `` and `` into the first group
+- Find the correct translation to put the sun in the upper right
+ - 750, 100 should work with the sample art
+- Add `-sun` to the classnames
+
+### Writing the Contracts
+
+The tricky part here is that you can't fit all the suns into one contract. They're too big! Instead, split them into three, similar to the original ocean renderer. For example:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.20;
+
+import "hardhat/console.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+
+string constant SVG = ''
+
+contract SunRenderer1 {
+ function render() public pure returns (string memory) {
+ return SVG;
+ }
+}
+```
+
+### Incorporating the SunRenderer
+
+Add the three `SunRenderer`s as you have the other rendering contracts. You'll have to incorporate this one a little differently. Add a function that picks which `SunRenderer` to call, based on the NFT id.
+
+```solidity
+function pickSunRenderer(uint _tokenId) public view returns (ISVGPartRenderer) {
+ bytes32 hash = keccak256(abi.encodePacked(_tokenId));
+ uint rand = uint(hash);
+ uint sun = rand % 3;
+ if(sun == 0) {
+ return sunRenderer1;
+ } else if(sun == 1) {
+ return sunRenderer2;
+ } else {
+ return sunRenderer3;
+ }
+ }
+```
+
+Make sure to put it after `skyRenderer` in the main `render` function!
+
+```solidity
+function render(uint _tokenId) public view returns (string memory) {
+ return string(
+ abi.encodePacked(
+ "",
+ skyRenderer.render(_tokenId),
+ pickSunRenderer(_tokenId).render(),
+ landRenderer.render(_tokenId),
+ seaRenderer.render(),
+ " "
+ )
+ );
+}
+```
+
+Test it out. You've now got one of three suns in the sky for each NFT!
+
+## Adding the Cloud Renderer
+
+On your own, try adding the clouds. The clouds renderer should:
+
+- Randomly select between one and seven clouds
+- Place those clouds randomly on the top half of the canvas
+- Be on the layer on top of the sun and sky, but below the sea and mountains
+
+## Conclusion
+
+In this tutorial, you've learned how to take advantage of the fact that offchain calls to your smart contracts don't use gas to create a significantly complex system to render complicated and unique NFTs, with the metadata and art existing entirely onchain. You've also explored some techniques for creating deterministic, pseudorandom numbers, and how to use them to add variation to your art. You've dealt with some of the many caveats and quirks of programmatically combining SVG files. Finally, you've practiced building multiple contracts that interact with one another.
+
+[Base Learn]: https://base.org/learn
+[ERC-721 Tokens]: https://docs.base.org/learn/erc-721-token/erc-721-standard-video
+[IPFS]: https://ipfs.tech/
+[Base64]: https://en.wikipedia.org/wiki/Base64
+[Hardhat and Hardhat Deploy]: https://docs.base.org/learn/hardhat-deploy/hardhat-deploy-sbs
+[testnet version of Opensea]: https://testnets.opensea.io/
+[sample project]: https://github.com/base-org/land-sea-and-sky
+[Sample Art]: https://github.com/base-org/land-sea-and-sky/tree/master/Final_SVGs
+[Basescan]: https://sepolia.basescan.org/
+
diff --git a/docs/pages/cookbook/nfts/dynamic-nfts.mdx b/_pages/cookbook/nfts/dynamic-nfts.mdx
similarity index 100%
rename from docs/pages/cookbook/nfts/dynamic-nfts.mdx
rename to _pages/cookbook/nfts/dynamic-nfts.mdx
diff --git a/_pages/cookbook/nfts/signature-mint.mdx b/_pages/cookbook/nfts/signature-mint.mdx
new file mode 100644
index 00000000..3a686304
--- /dev/null
+++ b/_pages/cookbook/nfts/signature-mint.mdx
@@ -0,0 +1,379 @@
+---
+title: 'Signature Mint NFT'
+slug: /signature-mint-nft
+description: A tutorial that teaches how to create a signature mint, in which minters pay their own gas, but must first be given a valid signed authorization.
+author: briandoyle81
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+# Signature Mint NFT
+
+A _signature mint_ is a term for an NFT mint in which the recipient of the NFT pays for their own gas to receive the NFT, but may only do so if they possess a correct message signed by the owner or authorized address of the mint contract. Reasons for doing this include allowing fiat payment of minting fees, allowing holders of an NFT on one chain to mint that NFT on an unrelated chain, or gating the mint to users who meet other specific offchain requirements.
+
+Signature mints are not particularly complex, but they remain challenging to implement. Because they make use of both hashing and cryptography, there are no partially-correct states - either everything is exactly right and the mint works, or **something** is wrong **somewhere** and it doesn't.
+
+Combined with the rapid changes in Solidity, library contracts, and frontend libraries, troubleshooting errors is particularly difficult.
+
+## Objectives
+
+By the end of this tutorial you should be able to:
+
+- Cryptographically sign a message with a wallet
+- Validate a signed message in a smart contract
+- Implement a signature ERC-721 mint
+
+## Prerequisites
+
+### ERC-721 Tokens
+
+This tutorial assumes that you are able to write, test, and deploy your own ERC-721 tokens using the Solidity programming language. If you need to learn that first, check out our content in [Base Learn] or the sections specific to [ERC-721 Tokens]!
+
+### Vercel
+
+You'll need to be comfortable deploying your app to [Vercel], or using another solution on your own. Check out our tutorial on [deploying with Vercel] if you need a refresher!
+
+## Building the Contract
+
+Start by setting up an [OpenZeppelin ERC-721] contract. Set up variables and use the constructor to assign:
+
+- A name for the collection
+- Symbol for the collection
+- Description
+- IPFS Hash for the NFT art (assuming the same art for each NFT)
+- A counter to keep track of which NFT id is next
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity 0.8.24;
+
+import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+
+contract SoulboundSignatureMint is ERC721, Ownable {
+ string public nameString;
+ string public description;
+ string public tokenArtIPFSId;
+ uint public counter;
+
+ constructor(
+ string memory _nameString,
+ string memory _symbol,
+ string memory _tokenArtIPFSId,
+ string memory _description
+ ) ERC721(_nameString, _symbol) Ownable(msg.sender) {
+ nameString = _nameString;
+ description = _description;
+ tokenArtIPFSId = _tokenArtIPFSId;
+ }
+}
+```
+
+You're also using `Ownable` to assign an owner to this contract. You could instead just save an address for the authorized signer if you aren't going to add any functionality only the owner can invoke.
+
+### Public Mint Function
+
+For the `public`-facing mint function, create a function called `mintTo` that accepts an `address` for the `_recipient`.
+
+
+A common pattern used to be to simply mint the token and give it to `msg.sender`. This practice is falling out of favor. Allowing the recipient to be different than the sender gives greater flexibility. Doing so is also necessary to assign the right NFT owner in the event the user is using a smart contract wallet, paymaster, or other form of account abstraction.
+
+
+
+```solidity
+function mintTo(address _recipient, bytes memory _signature) public {
+ counter++;
+ _safeMint(msg.sender, counter);
+}
+```
+
+### Onchain Metadata
+
+Rather than pointing to a `json` file on the traditional internet, you can put your metadata directly in the contract. To do so, first import some helper libraries:
+
+```solidity
+import "@openzeppelin/contracts/utils/Base64.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+```
+
+Next, `override` the functions for `_baseURI` and `tokenURI` to return base 64 encoded json metadata with the information you supplied in the constructor:
+
+```solidity
+function _baseURI() internal pure override returns (string memory) {
+ return "data:application/json;base64,";
+}
+
+function tokenURI(uint _tokenId) public view override returns (string memory) {
+ if(_tokenId > counter) {
+ revert InvalidTokenId(_tokenId);
+ }
+
+ string memory json = Base64.encode(
+ bytes(
+ string(
+ abi.encodePacked(
+ '{"name": "',
+ nameString,
+ ' #: ',
+ Strings.toString(_tokenId),
+ '","description": "',
+ description,
+ '", "image": "ipfs://',
+ tokenArtIPFSId,
+ '"}'
+ )
+ )
+ )
+ );
+
+ return string(abi.encodePacked(_baseURI(), json));
+}
+```
+
+**Be very careful** setting up the single and double quotes above and be sure to test this function to make sure the result is valid json metadata. An error here will break the NFT and it won't show up correctly in wallets or marketplaces!
+
+### Preventing Transfers
+
+_Soulbound_ is a video-game term that means that an item is permanently attached to the receiver - it can **not** be transferred. It's up to you if this restriction fits your design goals. We use it often because our NFTs are intended to be fun mementos or markers of personal accomplishment and not something that will ever have monetary value. Preventing trading reduces speculation and farming on something we did for fun!
+
+To prevent transfers other than the initial mint, you can `override` the `_update` function.
+
+
+Previously, this was done with the `_beforeTransfer` function. Current versions of OpenZeppelin's ERC-721 implementation have replaced that function with `_update`.
+
+
+
+```solidity
+/**
+ * Disallow transfers (Soulbound NFT)
+ */
+/**
+ * @dev Internal function to handle token transfers.
+ * Restricts the transfer of Soulbound tokens.
+ */
+function _update(address to, uint256 tokenId, address auth)
+ internal
+ override(ERC721)
+ returns (address)
+{
+ address from = _ownerOf(tokenId);
+ if (from != address(0) && to != address(0)) {
+ revert SoulboundToken();
+ }
+
+ return super._update(to, tokenId, auth);
+}
+```
+
+### Deploy and Test
+
+Before getting into the complexity of validating a cryptographic signature, it's a good idea to validate your contract and make sure it is working as expected. You'll need to pin an image to IPFS and get a hash for it to use in your metadata. You can use a service like [Pinata] to help with that.
+
+### Adding Signature Validation
+
+To validate the signature that you'll later create in your backend, you'll use a pair of cryptography utilities from OpenZeppelin:
+
+```solidity
+import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
+import "@openzeppelin/contracts/utils/cryptography/MessageHashUtils.sol";
+```
+
+```solidity
+using ECDSA for bytes32;
+using MessageHashUtils for bytes32;
+```
+
+These utilities abstract away most of the complexity involved in working with messages adhering to the [ERC-191] and [EIP-712] specifications. Importantly, they work with the message format that prefixes `"\x19Ethereum Signed Message:\n32"` to the message. You **must** also do this when creating the signed message!
+
+Add a function to `validateSignature`:
+
+```solidity
+function validateSignature(address _recipient, bytes memory _signature) public view returns (bool) {
+ bytes32 messageHash = keccak256(abi.encodePacked(_recipient));
+ bytes32 ethSignedMessageHash = messageHash.toEthSignedMessageHash();
+ address signer = ethSignedMessageHash.recover(_signature);
+
+ return signer == owner();
+}
+```
+
+The way the verification works is a little obtuse, particularly given that you haven't created the `_signature` yet. The function has two inputs:
+
+- The message or variables in the signed message
+ - Here, this is the `address` of the `_recipient`
+ - The `_signature`, or signed message provided by the user claiming they have been given permission to mint the NFT
+
+First, the function recreates the hash of the data to be signed. If you were including other variables, you'd include them here as well. Next, `messageHash.toEthSignedMessageHash` prepends the bytes representation of `"\x19Ethereum Signed Message:\n32"` to the message, then hashes the result.
+
+Finally, calling `recover` with `ethSignedMessageHash` and the `_signature` attempts to recover the signing `address` from the `_signature` using the **independently constructed** message data.
+
+If the recovered address matches the expected address, in this case, the contract owner, then the provided `_signature` is valid. If the addresses do not match, then the `_signature` is not valid.
+
+Update your `mintTo` function to make use of the validation:
+
+```solidity
+function mintTo(address _recipient, bytes memory _signature) public {
+ if(!validateSignature(_recipient, _signature)) {
+ revert InvalidSignature();
+ }
+
+ counter++;
+ _safeMint(msg.sender, counter);
+}
+```
+
+
+Nothing in the above validation method prevents a user, or a third party, from obtaining a valid, signed message from a previous transaction and reusing it for a **new** transaction. In this case, it doesn't matter because signature re-use would only allow minting more soulbound NFTs for the address within the signature.
+
+Other design requirements should use a nonce as a part of the signed data to prevent signature reuse.
+
+
+
+## Signing the Message
+
+If you're using [Hardhat] with [viem], you can write tests to verify the signing and validation mechanisms are working. Otherwise, there isn't a point, as success is dependent on the exact and specific way and order signing happens. If you're using a different toolkit to write your smart contracts, continue in your backend directly.
+
+If you're using a different library, you'll need to do research to figure out how to **exactly** reproduce the steps below.
+
+### Setting Up the Test
+
+Add a new test file and fill out a skeleton to deploy your contract and run a test:
+
+```tsx
+import { loadFixture } from '@nomicfoundation/hardhat-toolbox-viem/network-helpers';
+import { expect } from 'chai';
+import hre from 'hardhat';
+
+describe('Test', function () {
+ // We define a fixture to reuse the same setup in every test.
+ // We use loadFixture to run this setup once, snapshot that state,
+ // and reset Hardhat Network to that snapshot in every test.
+ async function deploySignatureFixture() {
+ // Contracts are deployed using the first signer/account by default
+ const [owner, signer0, signer1] = await hre.viem.getWalletClients();
+
+ const soulboundSignatureMint = await hre.viem.deployContract('SoulboundSignatureMint', [
+ 'Cool NFT Name', // Name
+ 'CNFT', // Symbol
+ 'QmRsQCyTEALYnHvBupFcs2ofzeeswEEEGN...', // IPFS Hash
+ 'This is a cool NFT!', // Description
+ ]);
+
+ const publicClient = await hre.viem.getPublicClient();
+
+ return {
+ soulboundSignatureMint,
+ owner,
+ signer1,
+ publicClient,
+ };
+ }
+
+ describe('Mint', function () {
+ it('Should validate the signed message', async function () {
+ const { soulboundSignatureMint, owner, signer0, signer1 } = await loadFixture(
+ deploySignatureFixture,
+ );
+
+ const ownerAddress = await owner.account.address;
+ const signer1Address = await signer1.account.address;
+
+ // TODO...
+
+ // Signer 1 calls the mintTo function with the signature
+ expect(await soulboundSignatureMint.write.mintTo([signer1Address, signature])).to.be.ok;
+ });
+ });
+});
+```
+
+You can use the example in the documentation for [signMessage] in the [viem] wallet client to get started, but it will **not** work as expected.
+
+```tsx
+// BAD CODE EXAMPLE DO NOT USE!
+const signature = await owner.signMessage({
+ message: signer1Address,
+});
+```
+
+Try it, and it will fail. Add a log to your contract and you'll see that the recovered `signer` address is random, rather than the first address in the list of default Hardhat accounts.
+
+The reason for this is that while `signMessage` does follow the previously mentioned standards, prepends `"\x19Ethereum Signed Message:\n32"` to the message, and correctly signs it, it does **not** prepare the data to be signed in exactly the same way as the smart contract converts the `address` to `bytes32`.
+
+To fix this, first import some helper functions from [viem]:
+
+```tsx
+import { keccak256, encodePacked, toBytes } from 'viem';
+```
+
+Then `encodePacked` and `keccak256` hash your variables and turn them into `bytes`, just like you did in the contract in `validateSignature`:
+
+```tsx
+const message = keccak256(encodePacked(['address'], [signer1Address]));
+const messageBytes = toBytes(message);
+```
+
+Finally, call the wallet `signMessage` function with the newly assembled `messageBytes`. You'll need to mark the data representation as `raw`:
+
+```tsx
+const signature = await owner.signMessage({
+ message: { raw: messageBytes },
+});
+```
+
+Test again, and it will pass!
+
+## Signing from the Backend
+
+It's up to you to determine the conditions that you're willing to sign a message. Once those conditions are met, you can use a similar process to load a wallet from your private key and sign the message on any TypeScript backend:
+
+```tsx
+const authorizedAccount = privateKeyToAccount(COINBASE_WALLET_KEY as `0x${string}`);
+
+const authorizedClient = createWalletClient({
+ account: authorizedAccount,
+ chain: base,
+ transport: http(), // Leave empty for local account
+});
+
+// Align signed message with OpenZeppelin/Solidity
+
+const messageToSign = keccak256(encodePacked(['address'], [userAddress as `0x${string}`]));
+const messageBytes = getBytes(messageToSign);
+
+// Create an Ethereum Signed Message with the user's address
+const signedMessage = await authorizedClient.signMessage({
+ message: { raw: messageBytes },
+});
+console.log('User address:', userAddress);
+console.log('Signed message:', signedMessage);
+
+const data = encodeFunctionData({
+ abi: mintContractData.abi,
+ functionName: 'mintTo',
+ args: [userAddress, signedMessage],
+});
+```
+
+
+`privateKeyToAccount` expects that your key starts with `0x`. You may need to manually add that depending on the tool you exported it from.
+
+
+
+## Conclusion
+
+In this tutorial, you've learned how to create a signature mint, which allows you to set conditions on a backend before a user is allowed to mint your NFT. You've learned the detailed and specific steps needed to align [viem]'s method of signing messages with [OpenZeppelin]'s method of verifying them. Finally, you've learned a few of the risks and considerations needed in designing this type of mint.
+
+[Base Learn]: https://base.org/learn
+[ERC-721 Tokens]: https://docs.base.org/base-learn/docs/erc-721-token/erc-721-standard-video
+[Vercel]: https://vercel.com
+[deploying with Vercel]: /tutorials/farcaster-frames-deploy-to-vercel
+[OpenZeppelin ERC-721]: https://docs.openzeppelin.com/contracts/2.x/api/token/erc721
+[Pinata]: https://www.pinata.cloud/
+[ERC-191]: https://eips.ethereum.org/EIPS/eip-191
+[EIP-712]: https://eips.ethereum.org/EIPS/eip-712
+[Hardhat]: https://hardhat.org/
+[viem]: https://viem.sh/
+[signMessage]: https://viem.sh/docs/actions/wallet/signMessage.html
+[OpenZeppelin]: https://www.openzeppelin.com/
+
diff --git a/_pages/cookbook/nfts/simple-onchain-nfts.mdx b/_pages/cookbook/nfts/simple-onchain-nfts.mdx
new file mode 100644
index 00000000..33507b67
--- /dev/null
+++ b/_pages/cookbook/nfts/simple-onchain-nfts.mdx
@@ -0,0 +1,1044 @@
+---
+title: 'Simple Onchain NFTs'
+slug: /simple-onchain-nfts
+description: A tutorial that teaches how to make simple nfts that are procedurally generated and have onchain metadata and images.
+author: briandoyle81
+---
+
+# Simple Onchain NFTs
+
+Many NFTs are dependent on offchain metadata and images. Some use immutable storage locations, such as [IPFS]. Others use traditional web locations, and many of these allow the owner of the contract to modify the URL returned by a contract when a site or user attempts to retrieve the location of the token art and metadata. This power isn't inherently bad, because we probably want someone to be able to fix the contract if the storage location goes down. However, it does introduce a requirement to trust the contract owner.
+
+In this tutorial, we'll show you how to do this to create a simple NFT that is fully onchain. This contract is used in our tutorials for [Thirdweb and Unreal - NFT Items] and the [Coinbase Smart Wallet].
+
+The result of this tutorial is used in other tutorials. [Below], you can find the complete contract and ABI. Feel free to use it if you're working on one of those and don't want to get sidetracked.
+
+## Objectives
+
+By the end of this tutorial you should be able to:
+
+- Programmatically generate and return json metadata for ERC-721 tokens
+- Deterministically construct unique SVG art in a smart contract
+- Generate deterministic, pseudorandom numbers
+
+## Prerequisites
+
+### ERC-721 Tokens
+
+This tutorial assumes that you are able to write, test, and deploy your own ERC-721 tokens using the Solidity programming language. If you need to learn that first, check out our content in [Base Camp] or the sections specific to [ERC-721 Tokens]!
+
+### Vector Art
+
+You'll need some familiarity with the SVG art format and a basic level of ability to edit and manipulate vector art. If you don't have this, find an artist friend and collaborate!
+
+## Building the Contract
+
+Start by setting up an [OpenZeppelin ERC-721] contract. You'll need to set up a `mintTo` function that accepts the address that should receive the NFT.
+
+```solidity filename="RandomColorNFT.sol"
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.24;
+
+import "hardhat/console.sol";
+import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+
+contract RandomColorNFT is ERC721 {
+ uint public counter;
+
+ constructor() ERC721("RandomColorNFT", "RCNFT") {
+ }
+
+ function mintTo(address _to) public {
+ counter++;
+ _safeMint(_to, counter);
+ }
+}
+```
+
+
+With the Smart Wallet, `msg.sender` is the users custodial address - where you want to send the NFT. This is not always the case with account abstraction. In some other implementations, `msg.sender` is the smart contract address, even if the user signs in with an EOA. Regardless, it's becoming a common practice to pass the address you want the NFT to go to explicitly.
+
+
+
+### Onchain Metadata
+
+Rather than pointing to a `json` file on the traditional internet, you can put your metadata directly in the contract. To do so, first import some helper libraries:
+
+```solidity
+import "@openzeppelin/contracts/utils/Base64.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+```
+
+Next, `override` the functions for `_baseURI` and `tokenURI` to return base 64 encoded json metadata with the appropriate information:
+
+```solidity
+function _baseURI() internal pure override returns (string memory) {
+ return "data:application/json;base64,";
+}
+
+function tokenURI(uint _tokenId) public view override returns (string memory) {
+ if(_tokenId > counter) {
+ revert InvalidTokenId(_tokenId);
+ }
+
+ string memory json = Base64.encode(
+ bytes(
+ string(
+ abi.encodePacked(
+ '{"name": "',
+ name(),
+ ' #: ',
+ Strings.toString(_tokenId),
+ '","description": "Random colors are pretty or boring!", "image": "data:image/svg+xml;base64,',
+ // TODO...,
+ '"}'
+ )
+ )
+ )
+ );
+
+ return string(abi.encodePacked(_baseURI(), json));
+}
+```
+
+**Be very careful** setting up the single and double quotes above and be sure to test this function to make sure the result is valid json metadata. An error here will break the NFT and it won't show up correctly in wallets or marketplaces!
+
+### Onchain SVG Image
+
+For this NFT, the art will consist of a simple onchain SVG containing a square with a pseudo-randomly chosen color. Check out our tutorial on [Building Onchain NFTs] if you want to try something more complicated.
+
+Start by scaffolding out a `render` function:
+
+```solidity
+function render(uint _tokenId) public view returns (string memory) {
+ return string(
+ abi.encodePacked(
+ "",
+ // TODO: add a rectangle with a random color fill
+ " "
+ )
+ );
+}
+```
+
+Rectangles in SVG images are created with the [rect] element. To cover the whole background, you can set the width and height to the size of the `viewbox`. Although not listed directly in the MDN page for rectangles, you can add a `fill` property to add a fill color to any SVG element. You can use color names, or hex codes for colors:
+
+```html
+
+```
+
+### Generating a Random Color
+
+Instead of a fixed color, your design calls for a unique color for each NFT. Add a function to generate this:
+
+```solidity filename="RandomColorNFT.sol"
+// Function to generate a random color hex code
+function generateRandomColor() public view returns (string memory) {
+ // Generate a pseudo-random number using block.prevrandao
+ uint256 randomNum = uint256(keccak256(abi.encodePacked(block.prevrandao, block.timestamp, msg.sender)));
+
+ // Extract RGB components from the random number
+ bytes memory colorBytes = new bytes(3);
+ colorBytes[0] = bytes1(uint8(randomNum >> 16));
+ colorBytes[1] = bytes1(uint8(randomNum >> 8));
+ colorBytes[2] = bytes1(uint8(randomNum));
+
+ // Convert RGB components to hex string
+ string memory colorHex = string(abi.encodePacked(
+ "#",
+ toHexDigit(uint8(colorBytes[0]) >> 4),
+ toHexDigit(uint8(colorBytes[0]) & 0x0f),
+ toHexDigit(uint8(colorBytes[1]) >> 4),
+ toHexDigit(uint8(colorBytes[1]) & 0x0f),
+ toHexDigit(uint8(colorBytes[2]) >> 4),
+ toHexDigit(uint8(colorBytes[2]) & 0x0f)
+ ));
+
+ return colorHex;
+}
+
+// Helper function to convert a uint8 to a hex character
+function toHexDigit(uint8 d) internal pure returns (bytes1) {
+ if (d < 10) {
+ return bytes1(uint8(bytes1('0')) + d);
+ } else {
+ return bytes1(uint8(bytes1('a')) + d - 10);
+ }
+}
+```
+
+
+Remember, randomness generated using onchain information is not fully secure. A determined attacker could manipulate a block to compromise your contract. That being said, `prevrandao` is a passable solution for anything not involving a large amount of money.
+
+
+
+### Saving the Color to the NFT
+
+You'll need to generate this color with the function, then save it in a way that it can be retrieved when the `tokenURI` function is called. Add a mapping to store this relationship:
+
+```solidity
+mapping (uint => string) public tokenIdToColor;
+```
+
+Then set the color when the token is minted:
+
+```solidity
+function mintTo(address _to) public {
+ counter++;
+ _safeMint(_to, counter);
+ tokenIdToColor[counter] = generateRandomColor();
+}
+```
+
+### Finishing the `tokenURI` Function
+
+Update your `render` function to generate the SVG.
+
+```solidity
+function render(uint _tokenId) public view returns (string memory) {
+ return string(
+ abi.encodePacked(
+ "",
+ " ",
+ " "
+ )
+ );
+}
+```
+
+Then update your `tokenURI` function to use it, and return the SVG as base64 encoded data:
+
+```solidity
+function tokenURI(uint _tokenId) public view override returns (string memory) {
+ if(_tokenId > counter) {
+ revert InvalidTokenId(_tokenId);
+ }
+
+ string memory json = Base64.encode(
+ bytes(
+ string(
+ abi.encodePacked(
+ '{"name": "',
+ name(),
+ ' #: ',
+ Strings.toString(_tokenId),
+ '","description": "Random colors are pretty or boring!", "image": "data:image/svg+xml;base64,',
+ Base64.encode(bytes(render(_tokenId))),
+ '"}'
+ )
+ )
+ )
+ );
+
+ return string(abi.encodePacked(_baseURI(), json));
+}
+```
+
+### List of NFTs Owned
+
+Most ERC-721 implementations don't contain an on-contract method to retrieve a list of **all** the NFTs owned by a single address. The reason for this is that it costs extra gas go manage this list, and the information can be retrieved by using read-only services that analyze blockchain data.
+
+However, gas prices are getting lower, and adding this data to your contract will reduce your dependency on third-party APIs.
+
+To track ownership in-contract, first import `EnumerableSet` from OpenZeppelin:
+
+```solidity
+import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
+```
+
+Then enable it for `uint` sets and add a mapping to relate `addresses` to token ids.
+
+```solidity
+// Inside the RandomColorNFT contract
+using EnumerableSet for EnumerableSet.UintSet;
+
+mapping (address => EnumerableSet.UintSet) tokensOwned;
+```
+
+Finally, utilize the `_update` function to handle changes of ownership, including minting:
+
+```solidity
+function _update(address to, uint256 tokenId, address auth) internal override(ERC721) returns(address) {
+ // Only remove the token if it is not being minted
+ if (tokenId != counter){
+ tokensOwned[auth].remove(tokenId);
+ }
+ tokensOwned[to].add(tokenId);
+
+ return super._update(to, tokenId, auth);
+}
+```
+
+Now that you have a list of NFTs owned by an address, you can add a function to retrieve all of them. While you're at it, add the json metadata for each token. Doing so lets you get the complete list of NFTs **and** their metadata for just one RPC call!
+
+```solidity
+function getNFftsOwned(address owner) public view returns (TokenAndMetatdata[] memory) {
+ TokenAndMetatdata[] memory tokens = new TokenAndMetatdata[](tokensOwned[owner].length());
+ for (uint i = 0; i < tokensOwned[owner].length(); i++) {
+ uint tokenId = tokensOwned[owner].at(i);
+ tokens[i] = TokenAndMetatdata(tokenId, tokenURI(tokenId));
+ }
+ return tokens;
+}
+```
+
+### Testing
+
+Write some local tests, then [deploy] and test your contract. It can be very tricky to get all the commas, brackets, and single, and double quotes all lined up properly. The surest way to make sure it is working is to check the collection on [Testnet Opensea] or similar.
+
+Remember, it can take a few minutes for them to register and add the collection. If the metadata or image don't show up correctly, use [Sepolia Basescan] to pull the `tokenURI` and an online or console base64 decoder to decode and check the json metadata and SVG image.
+
+
+
+## Conclusion
+
+In this lesson, you learned how to make a simple NFT that is entirely onchain. You generated an SVG with a random color, and set up the JSON metadata for your NFT -- entirely onchain! Next, check out our tutorial for [Complex Onchain NFTs]!
+
+## Random Color NFT Contract
+
+```solidity filename="RandomColorNFT.sol"
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.24;
+
+import "hardhat/console.sol";
+import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+import "@openzeppelin/contracts/utils/Base64.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
+
+contract RandomColorNFT is ERC721 {
+ using EnumerableSet for EnumerableSet.UintSet;
+
+ mapping (address => EnumerableSet.UintSet) tokensOwned;
+
+ uint public counter;
+
+ mapping (uint => string) public tokenIdToColor;
+
+ error InvalidTokenId(uint tokenId);
+ error OnlyOwner(address);
+
+ constructor() ERC721("RandomColorNFT", "RCNFT") {
+ }
+
+ function mintTo(address _to) public {
+ counter++;
+ _safeMint(_to, counter);
+ tokenIdToColor[counter] = generateRandomColor();
+ }
+
+ struct TokenAndMetatdata {
+ uint tokenId;
+ string metadata;
+ }
+
+ function getNftsOwned(address owner) public view returns (TokenAndMetatdata[] memory) {
+ TokenAndMetatdata[] memory tokens = new TokenAndMetatdata[](tokensOwned[owner].length());
+ for (uint i = 0; i < tokensOwned[owner].length(); i++) {
+ uint tokenId = tokensOwned[owner].at(i);
+ tokens[i] = TokenAndMetatdata(tokenId, tokenURI(tokenId));
+ }
+ return tokens;
+ }
+
+ function shuffleColor(uint _tokenId) public {
+ if(_tokenId > counter) {
+ revert InvalidTokenId(_tokenId);
+ }
+ if(ownerOf(_tokenId) != msg.sender) {
+ revert OnlyOwner(msg.sender);
+ }
+ tokenIdToColor[_tokenId] = generateRandomColor();
+ }
+
+ function _update(address to, uint256 tokenId, address auth) internal override(ERC721) returns(address) {
+ // Only remove the token if it is not being minted
+ if (tokenId != counter){
+ tokensOwned[auth].remove(tokenId);
+ }
+ tokensOwned[to].add(tokenId);
+
+ return super._update(to, tokenId, auth);
+ }
+
+ function _baseURI() internal pure override returns (string memory) {
+ return "data:application/json;base64,";
+ }
+
+ function tokenURI(uint _tokenId) public view override returns (string memory) {
+ if(_tokenId > counter) {
+ revert InvalidTokenId(_tokenId);
+ }
+
+ string memory json = Base64.encode(
+ bytes(
+ string(
+ abi.encodePacked(
+ '{"name": "',
+ name(),
+ ' #: ',
+ Strings.toString(_tokenId),
+ '","description": "Random colors are pretty or boring!", "image": "data:image/svg+xml;base64,',
+ Base64.encode(bytes(render(_tokenId))),
+ '"}'
+ )
+ )
+ )
+ );
+
+ return string(abi.encodePacked(_baseURI(), json));
+ }
+
+ function render(uint _tokenId) public view returns (string memory) {
+ return string(
+ abi.encodePacked(
+ "",
+ " ",
+ " "
+ )
+ );
+ }
+
+ // Function to generate a random color hex code
+ function generateRandomColor() public view returns (string memory) {
+ // Generate a pseudo-random number using block.prevrandao
+ uint256 randomNum = uint256(keccak256(abi.encodePacked(block.prevrandao, block.timestamp, msg.sender)));
+
+ // Extract RGB components from the random number
+ bytes memory colorBytes = new bytes(3);
+ colorBytes[0] = bytes1(uint8(randomNum >> 16));
+ colorBytes[1] = bytes1(uint8(randomNum >> 8));
+ colorBytes[2] = bytes1(uint8(randomNum));
+
+ // Convert RGB components to hex string
+ string memory colorHex = string(abi.encodePacked(
+ "#",
+ toHexDigit(uint8(colorBytes[0]) >> 4),
+ toHexDigit(uint8(colorBytes[0]) & 0x0f),
+ toHexDigit(uint8(colorBytes[1]) >> 4),
+ toHexDigit(uint8(colorBytes[1]) & 0x0f),
+ toHexDigit(uint8(colorBytes[2]) >> 4),
+ toHexDigit(uint8(colorBytes[2]) & 0x0f)
+ ));
+
+ return colorHex;
+ }
+
+ // Helper function to convert a uint8 to a hex character
+ function toHexDigit(uint8 d) internal pure returns (bytes1) {
+ if (d < 10) {
+ return bytes1(uint8(bytes1('0')) + d);
+ } else {
+ return bytes1(uint8(bytes1('a')) + d - 10);
+ }
+ }
+}
+```
+
+```json
+{
+ "address": "0x59c35beE5eAdeEDDc2c34d419459243bD70AFD72",
+ "abi": [
+ {
+ "inputs": [],
+ "stateMutability": "nonpayable",
+ "type": "constructor"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ },
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ }
+ ],
+ "name": "ERC721IncorrectOwner",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "operator",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "ERC721InsufficientApproval",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "approver",
+ "type": "address"
+ }
+ ],
+ "name": "ERC721InvalidApprover",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "operator",
+ "type": "address"
+ }
+ ],
+ "name": "ERC721InvalidOperator",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ }
+ ],
+ "name": "ERC721InvalidOwner",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "receiver",
+ "type": "address"
+ }
+ ],
+ "name": "ERC721InvalidReceiver",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "sender",
+ "type": "address"
+ }
+ ],
+ "name": "ERC721InvalidSender",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "ERC721NonexistentToken",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "InvalidTokenId",
+ "type": "error"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "name": "OnlyOwner",
+ "type": "error"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "approved",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "Approval",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "operator",
+ "type": "address"
+ },
+ {
+ "indexed": false,
+ "internalType": "bool",
+ "name": "approved",
+ "type": "bool"
+ }
+ ],
+ "name": "ApprovalForAll",
+ "type": "event"
+ },
+ {
+ "anonymous": false,
+ "inputs": [
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "indexed": true,
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "Transfer",
+ "type": "event"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "approve",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ }
+ ],
+ "name": "balanceOf",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "counter",
+ "outputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "generateRandomColor",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "getApproved",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ }
+ ],
+ "name": "getNFftsOwned",
+ "outputs": [
+ {
+ "components": [
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ },
+ {
+ "internalType": "string",
+ "name": "metadata",
+ "type": "string"
+ }
+ ],
+ "internalType": "struct RandomColorNFT.TokenAndMetatdata[]",
+ "name": "",
+ "type": "tuple[]"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "owner",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "operator",
+ "type": "address"
+ }
+ ],
+ "name": "isApprovedForAll",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "_to",
+ "type": "address"
+ }
+ ],
+ "name": "mintTo",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "name",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "ownerOf",
+ "outputs": [
+ {
+ "internalType": "address",
+ "name": "",
+ "type": "address"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "_tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "render",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "safeTransferFrom",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ },
+ {
+ "internalType": "bytes",
+ "name": "data",
+ "type": "bytes"
+ }
+ ],
+ "name": "safeTransferFrom",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "operator",
+ "type": "address"
+ },
+ {
+ "internalType": "bool",
+ "name": "approved",
+ "type": "bool"
+ }
+ ],
+ "name": "setApprovalForAll",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "_tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "shuffleColor",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "bytes4",
+ "name": "interfaceId",
+ "type": "bytes4"
+ }
+ ],
+ "name": "supportsInterface",
+ "outputs": [
+ {
+ "internalType": "bool",
+ "name": "",
+ "type": "bool"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [],
+ "name": "symbol",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "",
+ "type": "uint256"
+ }
+ ],
+ "name": "tokenIdToColor",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "uint256",
+ "name": "_tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "tokenURI",
+ "outputs": [
+ {
+ "internalType": "string",
+ "name": "",
+ "type": "string"
+ }
+ ],
+ "stateMutability": "view",
+ "type": "function"
+ },
+ {
+ "inputs": [
+ {
+ "internalType": "address",
+ "name": "from",
+ "type": "address"
+ },
+ {
+ "internalType": "address",
+ "name": "to",
+ "type": "address"
+ },
+ {
+ "internalType": "uint256",
+ "name": "tokenId",
+ "type": "uint256"
+ }
+ ],
+ "name": "transferFrom",
+ "outputs": [],
+ "stateMutability": "nonpayable",
+ "type": "function"
+ }
+ ]
+}
+```
+
+[Base Camp]: https://base.org.camp
+[ERC-721 Tokens]: https://docs.base.org/base-camp/docs/erc-721-token/erc-721-standard-video
+[IPFS]: https://ipfs.tech/
+[Base64]: https://en.wikipedia.org/wiki/Base64
+[Hardhat and Hardhat Deploy]: https://docs.base.org/base-camp/docs/hardhat-deploy/hardhat-deploy-sbs
+[testnet version of Opensea]: https://testnets.opensea.io/
+[sample project]: https://github.com/base-org/land-sea-and-sky
+[Sample Art]: https://github.com/base-org/land-sea-and-sky/tree/master/Final_SVGs
+[Basescan]: https://sepolia.basescan.org/
+[Thirdweb and Unreal - NFT Items]: ./thirdweb-unreal-nft-items
+[Coinbase Smart Wallet]: ./coinbase-smart-wallet
+[Below]: #random-color-nft-contract
+[Complex Onchain NFTs]: ./complex-onchain-nfts
+
diff --git a/_pages/cookbook/nfts/thirdweb-unreal-nft-items.mdx b/_pages/cookbook/nfts/thirdweb-unreal-nft-items.mdx
new file mode 100644
index 00000000..9a2c32f2
--- /dev/null
+++ b/_pages/cookbook/nfts/thirdweb-unreal-nft-items.mdx
@@ -0,0 +1,631 @@
+---
+title: 'Thirdweb and Unreal - NFT Items'
+slug: /thirdweb-unreal-nft-items
+description: Learn how to use NFTs as in-game items using Thirdweb and Unreal.
+author: briandoyle81
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+# Thirdweb and Unreal - NFT Items
+
+[thirdweb] provides a number of contracts and tools for building onchain. Their [Gaming SDK] enables seamless onboarding, cross-platform support, and provides many other features. It's compatible with [Unreal Engine] and can be used to enable onchain elements in your games.
+
+In this tutorial, you'll learn how to add NFT item usage on top of the demo game you build in their [Unreal Engine Quickstart]. Specifically, you'll use an NFT collection of random colors to change the color of the player's race car.
+
+
+
+## Objectives
+
+By the end of this tutorial you should be able to:
+
+- Pull a user's NFTs into an Unreal Engine Game
+- Apply elements from the NFT to game entities
+- Award NFTs to players for game accomplishments or actions
+
+## Prerequisites
+
+### ERC-721 Tokens
+
+This tutorial assumes that you are able to write, test, and deploy your own ERC-721 tokens using the Solidity programming language. If you need to learn that first, check out our content in [Base Learn] or the sections specific to [ERC-721 Tokens]!
+
+### Unreal Engine
+
+This tutorial will cover everything you need to know to accomplish the learning objectives, but it won't teach you how to make a game. You'll need to take further steps to learn the [Unreal Engine] on your own. You'll also need to have Visual Studio or Visual Studio Code set up to edit and compile Unreal files.
+
+### Onchain Apps
+
+The tutorial assumes you're comfortable with the basics of deploying an app and connecting it to a smart contract. If you're still learning this part, check out our tutorials in [Base Learn] for [Building an Onchain App].
+
+## Reviewing the Contract
+
+In our tutorial for building a [Simple Onchain NFTs], you can find an example of an ERC-721 NFT contract. It's an extension of the [OpenZeppelin ERC-721] implementation. When a user mints, they're granted an NFT with a random color. The metadata is fully onchain, as is the svg image. The image is a simple 1024\*1024 `rect`, with a `fill` of the randomly generated color.
+
+If the user dislikes the color, they may shuffle it and the NFT will change to a new randomly-selected color.
+
+These NFTs are not restricted for trading. The contract includes a utility function, `getNftsOwned`, which will return an array containing the `tokenId` and base64-encoded metadata string for all tokens currently owned by the provided `address`.
+
+## Getting Started with Unreal
+
+Continue below for some tips on completing the [Unreal Engine Quickstart] tutorial provided by thirdweb. This tutorial will guide you through installing the Unreal Engine and setting up the major components of the thirdweb [Gaming SDK].
+
+It will also guide you through setting up the website and backend that are needed to support the game integration. The client in their example uses Next.js, and the server is built with Node and Express.
+
+### Setting up the Engine
+
+First, you need to set up the [Engine]. For testing, you can [run it locally] with a [Docker] container.
+
+If you need to, install or update [Docker] and [Postgres].
+
+Start Postgres:
+
+```shell
+docker run -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres
+```
+
+Make sure Docker desktop is running.
+
+Create a [thirdweb API key]. Allow `localhost:3000` and `localhost:8000` when creating your api key. When you deploy, you'll need to update the allowed domains.
+
+The command to launch the engine itself is complicated and has many parameters. You'll want to create a file and run it from that. Create `thirdweb-engine.sh` in a convenient location and add:
+
+```shell
+docker run \
+ -e ENCRYPTION_PASSWORD="" \
+ -e THIRDWEB_API_SECRET_KEY="" \
+ -e ADMIN_WALLET_ADDRESS="" \
+ -e POSTGRES_CONNECTION_URL="postgresql://postgres:postgres@host.docker.internal:5432/postgres?sslmode=disable" \
+ -e ENABLE_HTTPS=true \
+ -p 3005:3005 \
+ --pull=always \
+ --cpus="0.5" \
+ thirdweb/engine:latest
+```
+
+Enter your `THIRDWEB_API_SECRET_KEY` and the wallet address you sign into thirdweb with as the `ADMIN_WALLET_ADDRESS`. You can see a full list of [environment variables] in the docs, but shouldn't need to set any others now.
+
+Give your script permission to run with `chmod +x ./thirdweb-engine.sh` then run it with `./thirdweb-engine.sh
+`.
+
+It will take awhile to spin up, and you can ignore the warning about the `Chain Indexer Listener not started...`.
+
+Once the engine is running, navigate to `https://localhost:3005/json`. Click through the warning that the connection is not secure. Doing so allows your browser to connect to your engine instance.
+
+
+We do not have an official browser recommendation, but during our testing, Chrome worked and Brave did not with the engine setup and configuration.
+
+
+
+Navigate to the [thirdweb engine dashboard], and click the `Import` button. Enter a name and the local address for your engine instance:
+
+
+
+Next, you must add your wallet to the engine instance. Open up the instance in the dashboard, then click the `Import` button next to `Backend Wallets`. Enter your secret key for the wallet.
+
+
+Remember, the wallet key gives full access to all assets within any wallet. Use separate wallets for development and individual production tasks. Don't hold or fund a production wallet with any assets other than the minimum amount necessary for the task it is accomplishing.
+
+
+
+**Be sure to fund** this wallet with Base Sepolia ETH. It will be paying the gas for transactions.
+
+
+The key to your wallet is stored in ephemeral memory in the engine itself. You'll need to re-add it whenever you restart the engine.
+
+
+
+## Setting up the Client and Server
+
+Clone the [engine-express] repo. CD into the `client` and `server` repos and run `yarn` to install dependencies, then CD back to root and run `yarn` again.
+
+In both the `client` and `server` folders, copy or rename the `.env.example` files as `.env`.
+
+### Client
+
+In the client `.env`:
+
+- Set `NEXT_PUBLIC_THIRDWEB_CLIENT_ID` to the **Client ID** matching your [thirdweb API key]
+- You don't need to change the `NEXT_PUBLIC_BACKEND_URL`
+- Set the `NEXT_PUBLIC_THIRDWEB_AUTH_DOMAIN` as `localhost`
+
+In the server `.env`:
+
+- Don't change the `THIRDWEB_ENGINE_URL`. (It **is** supposed to be `https`)
+- Set the `THIRDWEB_ENGINE_BACKEND_WALLET` to the same as you used in the engine setup
+- Set the `THIRDWEB_AUTH_DOMAIN` as `localhost`
+- Set the `THIRDWEB_API_SECRET_KEY` to the **Secret Key** matching your [thirdweb API key]
+- Set the `THIRDWEB_AUTH_PRIVATE_KEY` to the private key matching your backend engine wallet
+
+Open `client/components/ThirdwebProvider.tsx`. Update the `activeChain` to Base Sepolia.
+
+```tsx
+import { BaseSepoliaTestnet } from '@thirdweb-dev/chains';
+
+// This is the chainId your dApp will work on.
+const activeChain = BaseSepoliaTestnet;
+```
+
+### Server
+
+Open `server/src/controllers/engineController.ts`. Update the `const`s at the beginning to load from environment variables:
+
+```tsx
+const ENGINE_URL = process.env.THIRDWEB_ENGINE_URL;
+const BACKEND_WALLET = process.env.THIRDWEB_ENGINE_BACKEND_WALLET;
+const ERC20_CONTRACT = process.env.ERC20_CONTRACT;
+const CHAIN = process.env.CHAIN;
+```
+
+You'll need to deploy your own version of the [Token Drop] contract. Click `Deploy Now`, then enter the name, symbol, and image of your choosing.
+
+**Select `Base Sepolia Testnet` as the Network / Chain**.
+
+You can leave the `Recipient Address` as your connected address, and you don't need to do an advanced configuration.
+
+Click `Deploy Now`, and approve the transactions to deploy the contract and add it to your dashboard.
+
+Next, click the `Claim Conditions` tab on the left side nav. Then click the `+ Add Phase` button and select `Public`. Review the options, but don't change any of them for this demo. Click `Save Phases`.
+
+
+If later in the tutorial, you get an error when you attempt to claim a token, but not an error every four seconds failing to read the balance, it's because you missed this step.
+
+
+
+Copy the address from the dashboard:
+
+
+
+Return to the `.env` for your server, and add:
+
+```env
+ERC20_CONTRACT=0x... # Your Address
+CHAIN=84532 # Base Sepolia
+```
+
+Run the client and server with `yarn client` and `yarn server`. Navigate to `localhost:3000`, create a user, and link a wallet.
+
+## Setting Up the Game
+
+Clone the thirdweb [Unreal Demo], and open it with the Unreal Editor. Do so by clicking the `Recent Projects` tab in the upper left, then `Browse`, in the lower right.
+
+
+
+Open the folder cloned from the repo and select `unreal_demo.uproject`. You may need to convert the project to the current version of Unreal. Click the `Open a copy` button.
+
+When the scene loads, double-click `Scene_Game` in the upper-right corner.
+
+
+
+Before you can play, you need to do some config. Scroll down in the `Outliner` until you find `ThirdWebManager`. Click the `Open Thirdweb Manager` button to open the file in your editor.
+
+
+
+Then, click the green play button at the top of the viewport.
+
+
+
+Log in using the credentials you created on the website, and play the game for a minute or two. If you get a 404, check that your engine, client, and server are all still running.
+
+
+The demo does not actually have a database connected for users. You'll need to recreate your user each time you restart the server. For production, you'll need to swap this out with an actual database.
+
+
+
+If you get an error 500 `"No configured wallet found with address 0xABCD...."`, it's because you didn't add your wallet in the [thirdweb engine dashboard].
+
+Otherwise, the game should run, and you should receive an ERC20 NFT every time you collect one of the orange orbs on the race track.
+
+## Adding the Color Changer
+
+Your next goal is to make it so that your players can use their Random Color NFTs as skins on the race car. You'll need to deploy the [contract provided below], set it up to be accessed via the server and engine, and finally, enable the colors from the NFTs to be used to change the color of the car.
+
+### Deploying the Contract
+
+You'll use thirdweb's platform to deploy this contract as well. Open a new folder in your editor and run:
+
+```shell
+npx thirdweb create contract
+```
+
+Then:
+
+- Name the project - `random-color-nft`, or `.` if you run the script from the folder where you want the project
+- Select your preference of `Forge` or `Hardhat`
+- Name the NFT contract - `RandomColorNFT`
+- Select `Empty Contract`
+
+Open `contracts/Contract.sol` and replace the contents with the [contract provided below].
+
+You'll need to import or install the OpenZeppelin contracts. You may also need to update the config for the development environment you're using to `0.8.24`.
+
+Run `yarn build`.
+
+Select `y` to install the thirdweb package and wait for the script to complete.
+
+Run `yarn deploy`.
+
+If you haven't linked your device to your thirdweb account, the browser will open to a page asking you to make the connection. Do so now.
+
+After the script runs for a moment, it will open the thirdweb dashboard with the deployment UI open. Select `Base Sepolia Testnet` as your network, then click the `Deploy Now` button. Sign the transaction and wait for the contract to deploy.
+
+### Adding the Contract to the Server
+
+Copy the address for the contract to the clipboard and return to `thirdweb-engine-express`. Open `server/.env` and add:
+
+```env
+RANDOM_COLOR_NFT_CONTRACT=
+```
+
+Open `server/src/controllers/engineController.ts` and add it there as well:
+
+```tsx
+const RANDOM_COLOR_NFT_CONTRACT = process.env.RANDOM_COLOR_NFT_CONTRACT;
+```
+
+Now, using `claimERC20` as a template, add a function to `claimRandomColorNFT`. It's identical, except the `url`, `body`, and error message are:
+
+```tsx
+// Other code...
+const url = `${ENGINE_URL}/contract/${CHAIN}/${RANDOM_COLOR_NFT_CONTRACT}/write`;
+// Other code
+const body = {
+ functionName: 'mintTo',
+ args: [user.ethAddress],
+};
+// Other code
+res.status(400).json({ message: 'Error claiming RandomColorNFT' });
+```
+
+
+A better practice for production would be to make a more generalized function that can handle multiple requests to your contracts. We're skipping that for now to avoid needing to refactor the existing collectibles in the game.
+
+
+
+Next, you need to add a route for this function. Open `server/src/routes/engineRoutes.ts`. Import `claimRandomColorNFT` and add a route for it:
+
+```tsx
+router.post('/claim-random-color-nft', claimRandomColorNFT);
+```
+
+### Collecting the NFT from the Game
+
+Return to the Unreal Editor and open `ThirdwebManager.cpp`:
+
+.
+
+Similarly to what you did in the server, use the existing `PerformClaim()` as a template to add a function for `PerformNFTClaim()`. The only thing different is the name of the function and the URL:
+
+```c++
+HttpRequest->SetURL(this->ServerUrl + "/engine/claim-random-color-nft");
+```
+
+
+Again, it would be better practice to generalize this function, but you can skip that for now to avoid needing to update all the collectibles.
+
+
+
+Next, you need to let the editor know about this new function. Open `Source/unreal_demo/Public/ThirdwebManager.h`. Add your new function under the one for `PerformClaim();`
+
+```c++
+// Function to perform the NFT claim operation
+UFUNCTION(BlueprintCallable, Category = "Thirdweb")
+void PerformNFTClaim();
+```
+
+**Build your project**
+
+Once it's done compiling, return UnrealEditor. In the `Outliner`, open the folder for `Collectibles` and click `Edit Collectible`. In the new window, click `File->Save As...` and save a copy as `CollectibleNFT`.
+
+Open the `Content Drawer` at the bottom, search for `CollectibleNFT`, and drag one into the scene. Find it in the `Outliner` and click `Edit Collectible NFT`.
+
+Find the `Perform Claim` function call and replace it with `Perform NFT Claim`. **Note** that the `Target` is passed from `Get Actor of Class`.
+
+
+
+You'll want to be able to tell this collectible apart, so click on the mesh for `Collectible` on the left side in the `Component` tree, then on the `Details` panel on the right, find the `Materials` section and change it to `MI_Solid_Blue`.
+
+Click the icons at the top to `Compile` and save your asset.
+
+From the content drawer, drag your asset into the viewport.
+
+You should now see a blue orb floating where you placed it.
+
+Make sure the orb is low enough to drive through, then run the game. Collect the orb, then verify on a block explorer that you received the NFT.
+
+### Tinting the Car
+
+In the content browser, open `All>Content>Vehicles>SportsCar>Materials`. Right-click in an empty spot and select `Material>Material Parameter Collection`. Name yours `NFT_MPS`. Open the collection, click the `+` to add an item to `Vector Parameters` and create the color of your choosing. Bright red is a good option to make your change very visible.
+
+Right-click in an empty spot again and select `Create Basic Asset>Material`. Name your new material `M_NFT_Color`. Open it by double-clicking.
+
+Right-click on the graph and add a `Collection Parameter` node. In the `Details` panel on the left, select your `NFT_MPS` collection and pick the first vector for `Parameter Name`
+
+Connect the output to the `Base Color` of `M_NFT_Color`, then save and close the editor.
+
+Again in the content browser, right-click on the `M_NFT_Color` asset and select `Create Material Instance`. Name the instance `MI_NFT_Color`.
+
+Navigate to the sports car mesh located in `VehicleTemplate>Blueprints>SportsCar` and double-click to open the `SportsCar_pawn`. Select the `Mesh` from the `Components` tree and you should see the car in the editor.
+
+On the right side, change the `Element 2` material to `MI_NFT_Color`. The car is now bright red. Radical! Take your newly red car for a spin.
+
+### Fetching the NFT Colors
+
+Return to `engine-express` and open `engineController.ts`. Add a function to `getNFTColors` that uses the `read` endpoint to call the `getNFTsOwned` function.
+
+```tsx
+export const getNFTColors = async (req: Request, res: Response) => {
+ const { authToken } = req.body;
+ if (!authToken || !userTokens[authToken]) {
+ return res.status(400).json({ message: 'Invalid auth token' });
+ }
+ const user = userTokens[authToken];
+ try {
+ const url = `${ENGINE_URL}/contract/${CHAIN}/${RANDOM_COLOR_NFT_CONTRACT}/read?functionName=getNftsOwned&args=${user.ethAddress}`;
+ const headers = {
+ 'x-backend-wallet-address': BACKEND_WALLET,
+ Authorization: `Bearer ${process.env.THIRDWEB_API_SECRET_KEY}`,
+ };
+
+ const response = await axiosInstance.get(url, { headers: headers });
+
+ // TODO: Extract the color from the image
+
+ // TODO: Replace response
+ res.json(response.data);
+ } catch (error) {
+ console.error(error);
+ res.status(400).json({ message: 'Error getting NFT data' });
+ }
+};
+```
+
+You'll also need to add this function to `engineRoutes.ts`:
+
+```tsx
+router.post('/get-nft-colors', getNFTColors);
+```
+
+Return to `engineController.ts`.
+
+Because Unreal doesn't support SVGs, you'll need to extract the color from your NFT metadata, and pass that to use in the material you created. Start by adding a type for the response, and for the JSON metadata:
+
+```tsx
+type NFTData = {
+ tokenId: bigint;
+ metadata: string;
+};
+
+type JSONMetadata = {
+ name: string;
+ description: string;
+ image: string;
+};
+```
+
+You'll also need helper functions to decode the base64 encoded metadata and SVG, then get the color from the SVG.
+
+```tsx
+function getJsonMetadata(nft: NFTData) {
+ const base64String = nft.metadata.split(',')[1];
+ const jsonString = atob(base64String);
+ return JSON.parse(jsonString) as JSONMetadata;
+}
+
+function getColorFromBase64StringSVG(base64String: string) {
+ const base64Data = base64String.split(',')[1];
+ const svgString = atob(base64Data);
+ const color = svgString.match(/fill=['"](#[0-9a-fA-F]{6})['"]/);
+ return color ? color[1] : '#000000';
+}
+```
+
+Use these to extract an array of colors and return it:
+
+```tsx
+const nfts = response.data.result.map((item: any) => {
+ return {
+ tokenId: item[0],
+ metadata: item[1],
+ };
+});
+
+const metadata = nfts.map((nft: NFTData) => getJsonMetadata(nft));
+const colors = metadata.map((m: JSONMetadata) => getColorFromBase64StringSVG(m.image));
+
+res.json(colors);
+// Delete res.json(response.data);
+```
+
+
+To test with Postman or similar, comment out the check for a valid `authToken` and hardcode in an address that you know has NFTs.
+
+
+
+### Getting the Colors into the Game
+
+Return to the game in your code editor, and open `ThirdwebManager.cpp` and `ThirdwebManager.h`. As before, add a function to call and endpoint on your server. This time to retrieve the array of colors. You'll need to do a little more for this one to set an in-game variable for the colors.
+
+First, you'll need to add a new multicast delegate type to handle the response in `ThirdwebManager.h`:
+
+```c++
+// ThirdwebManager.h
+DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnNFTColorsResponse, bool, bWasSuccessful, const TArray &, ResponseArray);
+```
+
+And expose it to the editor:
+
+```c++
+// ThirdwebManager.h
+// This delegate is triggered in C++, and Blueprints can bind to it.
+UPROPERTY(BlueprintAssignable, Category = "Thirdweb", meta = (DisplayName = "OnNFTColorsResponse"))
+FOnNFTColorsResponse OnNFTColorsResponse:
+```
+
+Then, add the function to `ThirdwebManager.cpp`. It's similar, but instead hits the endpoint for the NFT color array and uses the response you just created. It also expects the response to be an array of strings instead of searching for a property called `result`:
+
+```c++
+// ThirdwebManager.cpp
+void AThirdwebManager::GetNFTColors()
+{
+ TSharedRef HttpRequest = FHttpModule::Get().CreateRequest();
+ HttpRequest->SetURL(this->ServerUrl + "/engine/get-nft-colors"); // The endpoint to get the NFT colors
+ HttpRequest->SetVerb("POST");
+ HttpRequest->SetHeader(TEXT("Content-Type"), TEXT("application/json"));
+
+ TSharedPtr JsonObject = MakeShareable(new FJsonObject);
+ JsonObject->SetStringField("authToken", AuthToken);
+
+ FString OutputString;
+ TSharedRef> Writer = TJsonWriterFactory<>::Create(&OutputString);
+ FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
+
+ UE_LOG(LogTemp, Warning, TEXT("OutputString: %s"), *OutputString);
+
+ HttpRequest->SetContentAsString(OutputString);
+
+ HttpRequest->OnProcessRequestComplete().BindLambda([this](FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
+ {
+ if (bWasSuccessful && Response.IsValid())
+ {
+ int32 StatusCode = Response->GetResponseCode();
+ if (StatusCode == 200)
+ {
+ TArray> JsonArray;
+ TSharedRef> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString());
+ if (FJsonSerializer::Deserialize(Reader, JsonArray) && JsonArray.Num() > 0)
+ {
+ TArray ResponseArray;
+ for (const TSharedPtr& Value : JsonArray)
+ {
+ FString StringValue;
+ if (Value->TryGetString(StringValue))
+ {
+ ResponseArray.Add(StringValue);
+ }
+ }
+ this->OnNFTColorsResponse.Broadcast(true, ResponseArray);
+ UE_LOG(LogTemp, Warning, TEXT("Get NFT Color response: %s"), *Response->GetContentAsString());
+ return;
+ }
+ this->OnNFTColorsResponse.Broadcast(false, TArray());
+ }
+ else
+ {
+ FString ErrorMsg = FString::Printf(TEXT("HTTP Error: %d, Response: %s"), StatusCode, *(Response->GetContentAsString()));
+ TArray ErrorArray;
+ ErrorArray.Add(ErrorMsg);
+ this->OnNFTColorsResponse.Broadcast(false, ErrorArray);
+ UE_LOG(LogTemp, Warning, TEXT("ErrorMsg: %s"), *ErrorMsg);
+ }
+ }
+ else
+ {
+ TArray ErrorArray;
+ ErrorArray.Add(TEXT("Failed to connect to the server."));
+ this->OnNFTColorsResponse.Broadcast(false, ErrorArray);
+ UE_LOG(LogTemp, Warning, TEXT("Failed to connect to the server."));
+ } });
+
+ HttpRequest->ProcessRequest();
+}
+```
+
+Finally, expose this function to the editor.
+
+```c++
+// ThirdwebManager.h
+// Function to perform the get NFT Colors operation
+UFUNCTION(BlueprintCallable, Category = "Thirdweb")
+void GetNFTColors();
+```
+
+Compile and reload the project in the editor.
+
+In the content browser, find and open `Content>_Thirdweb>Blueprints>Canvas_HUD`.
+
+Under the text field for `Tokens`, drag a new `Text` widget in. Set the name at the top to `Label_Colors` and check `Is Variable`. Change the `Content` to `Colors`. If you put it on the right side, move the `Anchor` to the upper right corner.
+
+In the upper right, click the `Graph` tab. Add a `Sequence` node to split the flow after `Get Actor Of Class`. Following the same pattern as the flow that gets the balance response, add one that gets the NFT colors.
+
+Create the `Bind Event to OnNFTColorsResponse` node first, then create the `Custom Event` node from dragging from `Event`.
+
+For now, simply grab the last color in the array and set it in the HUD. To get it, drag off the `Response Array` in `OnNFTColorResponseReceived` and add a `Last Index` node. Drag again from the `Response Array` and add a `Get (Ref)` node. Connect the output of `Last Index` to the `Input` of `Get`. From there, drag from the output of `Get` and add a `To Text (String)` node.
+
+Drag out of the exec (white) connector of `OnNFTColorResponseReceived` and add a `Branch` and connect `Was Successful` to the `Condition`. For the `True` state, drag and add a `SetText (Text)`. Right click and add a reference to `Label Colors` and drag it to the `Target` of `SetText`. Connect the `Return Value` of `To Text (String)` to `In Text`.
+
+Finally, drag off `Bind Event to OnNFTColorsResponse` and add a `Set Timer by Function Name` node. Connect the `Return Value` of `Get Actor Of Class` to `Object`. Set the `Function Name` to `GetNFTColors` and the `Time` to `2.0`.
+
+You should end up with something like this:
+
+
+
+Compile the blueprint then run the game. You should see that last color in the array in the HUD, and you should see the full list printed in the console every two seconds.
+
+
+If you have an error in your `GetNFTColors` function that prevents `.Broadcast` from being called, nothing in the NFT Colors branch of this blueprint will run, including printing to the console.
+
+
+
+### Changing the Color of the Car
+
+Now that you have the colors, you can use them to change the color of your car! For now, you can just set the car to the last color, but on your own you'll want to add a UI widget to allow the player to pick their color.
+
+
+If you really wanted to get fancy, you could modify the contract to emit an `event` containing the color of the newly-minted NFT, extract that from the receipt, and optimistically make it available to the player a few seconds earlier.
+
+
+
+Unreal doesn't use hex colors, so you'll need to convert your hex string to a linear color and save it in the `Material Parameter Collection` you created earlier.
+
+Converting the hex code with a blueprint is very complicated. Luckily, Unreal has a helpful community that has created many utilities, including a [conversion function].
+
+
+Copying and pasting code for a game engine isn't quite as dangerous as copying and pasting unknown smart contract code, but you're working at the intersection of these worlds. Be sure to review and make sure you understand anything you find online.
+
+
+
+In the content browser, add a `Blueprints>Blueprint Function Library` called `ColorUtils`. In it, add a function called `HexStringToColor`.
+
+Copy the code from the community site and paste it into the function. Connect the `Hex String to Color` node to the `SET` node attached to `Make Array`, then from `SET` to the `Return Node`.
+
+Compile, and you'll get an error. Find and right-click on the error in the `Hex Code` node, then select `Create local variable`. Recompile and the error will resolve.
+
+You also need to input the string you want converted. Select the `Hex String to Color` node and click the `+` button by `Inputs` located in the panel on the right. Name it `hexString` and give it a `string` type. `Hex String` will now appear as a value in the `Hex String to Color` node. Connect it to the `Source String` input in the `Replace` node.
+
+Compile one last time, then save and close `ColorUtils`.
+
+Return to `Canvas_HUD` and open the `Graph`. Drag out of the `SetText` node that adds the color to the HUD and add a `Hex String to Color` node. The function expects alpha values in the hex code. To add this connect a second output of the string array `GET` to an `Append` function and append `ff` in the `B` input. Connect the `Return Value` to the `Hex String` input in `Hex String to Color`.
+
+Finally, add a `Set Vector Parameter Value`. Select `NFT_MPS` for the collection and `Vector` for the `Parameter Name`. Connect the `Liner Color` output of `Hex String to Color` to the `Parameter Value` input.
+
+
+
+Compile, save, and close `Canvas_HUD`. Run the game. Your car will start red, but after the response from the server, it will turn the color of your last NFT! Drive and collect the NFT collectible, and it will change colors!
+
+## Conclusion
+
+In this tutorial, you've learned how to set up Thirdweb's engine and use it to connect an Unreal Engine game to Base. You've also learned how to use their platform to deploy and manager your contracts. Finally, you've learned how to build game elements to allow players to collect new NFTs and use them to personalize their game items.
+
+[Base Learn]: https://docs.base.org/learn/welcome
+[ERC-721 Tokens]: https://docs.base.org/learn/erc-721-token/erc-721-standard-video
+[OpenZeppelin ERC-721]: https://docs.openzeppelin.com/contracts/2.x/api/token/erc721
+[OpenZeppelin]: https://www.openzeppelin.com/
+[Unreal Engine]: https://www.unrealengine.com/en-US
+[thirdweb]: https://thirdweb.com/
+[Gaming SDK]: https://portal.thirdweb.com/solutions/gaming/overview
+[Unreal Engine Quickstart]: https://portal.thirdweb.com/solutions/gaming/unreal-engine/quickstart
+[contract provided below]: #random-color-nft-contract
+[Engine]: https://github.com/thirdweb-dev/engine
+[run it locally]: https://portal.thirdweb.com/engine/self-host
+[Docker]: https://www.docker.com/
+[Postgres]: https://www.postgresql.org/
+[thirdweb API key]: https://thirdweb.com/dashboard/settings/api-keys
+[environment variables]: https://portal.thirdweb.com/engine/self-host#environment-variables
+[engine-express]: https://github.com/thirdweb-example/engine-express
+[Token Drop]: https://thirdweb.com/thirdweb.eth/DropERC20
+[Unreal Demo]: https://github.com/thirdweb-example/unreal_demo
+[thirdweb engine dashboard]: https://thirdweb.com/dashboard/engine
+[wallet best practices]: https://portal.thirdweb.com/engine/features/backend-wallets#best-practices
+[conversion function]: https://blueprintue.com/blueprint/vm4ujcqe/
+[Simple Onchain NFTs]: /tutorials/simple-onchain-nfts
+
diff --git a/_pages/cookbook/smart-contract-development/foundry/deploy-with-foundry.mdx b/_pages/cookbook/smart-contract-development/foundry/deploy-with-foundry.mdx
new file mode 100644
index 00000000..29cacee4
--- /dev/null
+++ b/_pages/cookbook/smart-contract-development/foundry/deploy-with-foundry.mdx
@@ -0,0 +1,314 @@
+---
+title: Deploying a smart contract using Foundry
+slug: /deploy-with-foundry
+description: "A tutorial that teaches how to deploy a smart contract on the Base test network using Foundry. Includes instructions for setting up the environment, compiling, and deploying the smart contract."
+author: neodaoist
+---
+
+# Deploying a smart contract using Foundry
+
+This article will provide an overview of the [Foundry](https://book.getfoundry.sh/) development toolchain, and show you how to deploy a contract to **Base Sepolia** testnet.
+
+Foundry is a powerful suite of tools to develop, test, and debug your smart contracts. It comprises several individual tools:
+
+- `forge`: the main workhorse of Foundry — for developing, testing, compiling, and deploying smart contracts
+- `cast`: a command-line tool for performing Ethereum RPC calls (e.g., interacting with contracts, sending transactions, and getting onchain data)
+- `anvil`: a local testnet node, for testing contract behavior from a frontend or over RPC
+- `chisel`: a Solidity [REPL](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop), for trying out Solidity snippets on a local or forked network
+
+Foundry offers extremely fast feedback loops (due to the under-the-hood Rust implementation) and less context switching — because you'll be writing your contracts, tests, and deployment scripts **All** in Solidity!
+
+
+For production / mainnet deployments the steps below in this tutorial will be almost identical, however, you'll want to ensure that you've configured `Base` (mainnet) as the network rather than `Base Sepolia` (testnet).
+
+
+
+## Objectives
+
+By the end of this tutorial, you should be able to do the following:
+
+- Setup Foundry for Base
+- Create an NFT smart contract for Base
+- Compile a smart contract for Base (using `forge`)
+- Deploy a smart contract to Base (also with `forge`)
+- Interact with a smart contract deployed on Base (using `cast`)
+
+## Prerequisites
+
+### Foundry
+
+This tutorial requires you have Foundry installed.
+
+- From the command-line (terminal), run: `curl -L https://foundry.paradigm.xyz | bash`
+- Then run `foundryup`, to install the latest (nightly) build of Foundry
+
+For more information, see the Foundry Book [installation guide](https://book.getfoundry.sh/getting-started/installation).
+
+### Coinbase Wallet
+
+In order to deploy a smart contract, you will first need a web3 wallet. You can create a wallet by downloading the Coinbase Wallet browser extension.
+
+- Download [Coinbase Wallet](https://chrome.google.com/webstore/detail/coinbase-wallet-extension/hnfanknocfeofbddgcijnmhnfnkdnaad?hl=en)
+
+### Wallet funds
+
+Deploying contracts to the blockchain requires a gas fee. Therefore, you will need to fund your wallet with ETH to cover those gas fees.
+
+For this tutorial, you will be deploying a contract to the Base Sepolia test network. You can fund your wallet with Base Sepolia ETH using one of the faucets listed on the Base [Network Faucets](https://docs.base.org/chain/network-faucets) page.
+
+## Creating a project
+
+Before you can begin deploying smart contracts to Base, you need to set up your development environment by creating a Foundry project.
+
+To create a new Foundry project, first create a new directory:
+
+```bash
+mkdir myproject
+```
+
+Then run:
+
+```bash
+cd myproject
+forge init
+```
+
+This will create a Foundry project, which has the following basic layout:
+
+```bash
+.
+├── foundry.toml
+├── script
+ │ └── Counter.s.sol
+├── src
+ │ └── Counter.sol
+└── test
+ └── Counter.t.sol
+```
+
+## Compiling the smart contract
+
+Below is a simple NFT smart contract ([ERC-721](https://eips.ethereum.org/EIPS/eip-721)) written in the Solidity programming language:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.23;
+
+import "openzeppelin-contracts/contracts/token/ERC721/ERC721.sol";
+
+contract NFT is ERC721 {
+ uint256 public currentTokenId;
+
+ constructor() ERC721("NFT Name", "NFT") {}
+
+ function mint(address recipient) public payable returns (uint256) {
+ uint256 newItemId = ++currentTokenId;
+ _safeMint(recipient, newItemId);
+ return newItemId;
+ }
+}
+```
+
+The Solidity code above defines a smart contract named `NFT`. The code uses the `ERC721` interface provided by the [OpenZeppelin Contracts library](https://docs.openzeppelin.com/contracts/5.x/) to create an NFT smart contract. OpenZeppelin allows developers to leverage battle-tested smart contract implementations that adhere to official ERC standards.
+
+To add the OpenZeppelin Contracts library to your project, run:
+
+```bash
+forge install openzeppelin/openzeppelin-contracts
+```
+
+In your project, delete the `src/Counter.sol` contract that was generated with the project and add the above code in a new file called `src/NFT.sol`. (You can also delete the `test/Counter.t.sol` and `script/Counter.s.sol` files, but you should add your own tests ASAP!).
+
+To compile our basic NFT contract using Foundry, run:
+
+```bash
+forge build
+```
+
+## Configuring Foundry with Base
+
+Next, you will configure your Foundry project to deploy smart contracts to the Base network. First you'll store your private key in an encrypted keystore, then you'll add Base as a network.
+
+### Storing your private key
+
+The following command will import your private key to Foundry's secure keystore. You will be prompted to enter your private key, as well as a password for signing transactions:
+
+```bash
+cast wallet import deployer --interactive
+```
+
+
+For instructions on how to get your private key from Coinbase Wallet, visit the [Coinbase Wallet documentation](https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings#show-private-key). **It is critical that you do NOT commit this to a public repo**.
+
+
+
+Run this command to confirm that the 'deployer' account is setup in Foundry:
+
+```bash
+cast wallet list
+```
+
+### Adding Base as a network
+
+When verifying a contract with BaseScan, you need an API key. You can get your BaseScan API key from [here](https://basescan.org/myapikey) after you sign up for an account.
+
+
+Although they're made by the same folks, Etherscan API keys will **not** work on BaseScan!
+
+
+
+Now create a `.env` file in the home directory of your project to add the Base network and an API key for verifying your contract on BaseScan:
+
+```
+BASE_MAINNET_RPC="https://mainnet.base.org"
+BASE_SEPOLIA_RPC="https://sepolia.base.org"
+ETHERSCAN_API_KEY=""
+```
+
+Note that even though you're using BaseScan as your block explorer, Foundry expects the API key to be defined as `ETHERSCAN_API_KEY`.
+
+### Loading environment variables
+
+Now that you've created the above `.env` file, run the following command to load the environment variables in the current command line session:
+
+```bash
+source .env
+```
+
+## Deploying the smart contract
+
+With your contract compiled and your environment configured, you are ready to deploy to the Base Sepolia test network!
+
+Today, you'll use the `forge create` command, which is a straightforward way to deploy a single contract at a time. In the future, you may want to look into [`forge script`](https://book.getfoundry.sh/guides/scripting-with-solidity), which enables scripting onchain transactions and deploying more complex smart contract projects.
+
+You'll need testnet ETH in your wallet. See the [prerequisites](#prerequisites) if you haven't done that yet. Otherwise, the deployment attempt will fail.
+
+To deploy the contract to the Base Sepolia test network, run the following command. You will be prompted to enter the password that you set earlier, when you imported your private key:
+
+```bash
+forge create ./src/NFT.sol:NFT --rpc-url $BASE_SEPOLIA_RPC --account deployer
+```
+
+The contract will be deployed on the Base Sepolia test network. You can view the deployment status and contract by using a [block explorer](/chain/block-explorers) and searching for the address returned by your deploy script. If you've deployed an exact copy of the NFT contract above, it will already be verified and you'll be able to read and write to the contract using the web interface.
+
+
+If you'd like to deploy to mainnet, you'll modify the command like so:
+
+```bash
+forge create ./src/NFT.sol:NFT --rpc-url $BASE_MAINNET_RPC --account deployer
+```
+
+
+
+Regardless of the network you're deploying to, if you're deploying a new or modified contract, you'll need to verify it.
+
+## Verifying the Smart Contract
+
+In web3, it's considered best practice to verify your contracts so that users and other developers can inspect the source code, and be sure that it matches the deployed bytecode on the blockchain.
+
+Further, if you want to allow others to interact with your contract using the block explorer, it first needs to be verified. The above contract has already been verified, so you should be able to view your version on a block explorer already, but we'll still walk through how to verify a contract on Base Sepolia testnet.
+
+
+Remember, you need an API key from BaseScan to verify your contracts. You can get your API key from [the BaseScan site](https://basescan.org/myapikey) after you sign up for an account.
+
+
+
+Grab the deployed address and run:
+
+```bash
+forge verify-contract ./src/NFT.sol:NFT --chain 84532 --watch
+```
+
+You should see an output similar to:
+
+```
+Start verifying contract `0x71bfCe1172A66c1c25A50b49156FAe45EB56E009` deployed on base-sepolia
+
+Submitting verification for [src/NFT.sol:NFT] 0x71bfCe1172A66c1c25A50b49156FAe45EB56E009.
+Submitted contract for verification:
+ Response: `OK`
+ GUID: `3i9rmtmtyyzkqpfvy7pcxj1wtgqyuybvscnq8d7ywfuskss1s7`
+ URL:
+ https://sepolia.basescan.org/address/0x71bfce1172a66c1c25a50b49156fae45eb56e009
+Contract verification status:
+Response: `NOTOK`
+Details: `Pending in queue`
+Contract verification status:
+Response: `OK`
+Details: `Pass - Verified`
+Contract successfully verified
+```
+
+Search for your contract on [BaseScan](https://sepolia.basescan.org/) to confirm it is verified.
+
+
+You can't re-verify a contract identical to one that has already been verified. If you attempt to do so, such as verifying the above contract, you'll get an error similar to:
+
+```text
+Start verifying contract `0x71bfCe1172A66c1c25A50b49156FAe45EB56E009` deployed on base-sepolia
+
+Contract [src/NFT.sol:NFT] "0x71bfCe1172A66c1c25A50b49156FAe45EB56E009" is already verified. Skipping verification.
+```
+
+
+
+## Interacting with the Smart Contract
+
+If you verified on BaseScan, you can use the `Read Contract` and `Write Contract` sections under the `Contract` tab to interact with the deployed contract. To use `Write Contract`, you'll need to connect your wallet first, by clicking the `Connect to Web3` button (sometimes this can be a little finicky, and you'll need to click `Connect` twice before it shows your wallet is successfully connected).
+
+To practice using the `cast` command-line tool which Foundry provides, you'll perform a call without publishing a transaction (a read), then sign and publish a transaction (a write).
+
+### Performing a call
+
+A key component of the Foundry toolkit, `cast` enables us to interact with contracts, send transactions, and get onchain data using Ethereum RPC calls. First you will perform a call from your account, without publishing a transaction.
+
+From the command-line, run:
+
+```bash
+cast call --rpc-url $BASE_SEPOLIA_RPC "balanceOf(address)"
+```
+
+You should receive `0x0000000000000000000000000000000000000000000000000000000000000000` in response, which equals `0` in hexadecimal. And that makes sense — while you've deployed the NFT contract, no NFTs have been minted yet and therefore your account's balance is zero.
+
+### Signing and publishing a transaction
+
+Now, sign and publish a transaction, calling the `mint(address)` function on the NFT contract you just deployed.
+
+Run the following command:
+
+```bash
+cast send --rpc-url=$BASE_SEPOLIA_RPC "mint(address)" --account deployer
+```
+
+
+Note that in this `cast send` command, you had to include your private key, but this is not required for `cast call`, because that's for calling view-only contract functions and therefore you don't need to sign anything.
+
+
+
+If successful, Foundry will respond with information about the transaction, including the `blockNumber`, `gasUsed`, and `transactionHash`.
+
+Finally, let's confirm that you did indeed mint yourself one NFT. If you run the first `cast call` command again, you should see that your balance increased from 0 to 1:
+
+```bash
+cast call --rpc-url $BASE_SEPOLIA_RPC "balanceOf(address)"
+```
+
+And the response: `0x0000000000000000000000000000000000000000000000000000000000000001` (`1` in hex) — congratulations, you deployed a contract and minted an NFT with Foundry!
+
+## Conclusion
+
+Phew, that was a lot! You learned how to setup a project, deploy to Base, and interact with our smart contract using Foundry. The process is the same for real networks, just more expensive — and of course, you'll want to invest time and effort testing your contracts, to reduce the likelihood of user-impacting bugs before deploying.
+
+For all things Foundry, check out the [Foundry book](https://book.getfoundry.sh/), or head to the official Telegram [dev chat](https://t.me/foundry_rs) or [support chat](https://t.me/foundry_support).
+
+{/*-- Add reference style links here. These do not render on the page. --*/}
+
+[`sepolia.basescan.org`]: https://sepolia.basescan.org/
+[`basescan.org`]: https://basescan.org/
+[coinbase]: https://www.coinbase.com/wallet
+[MetaMask]: https://metamask.io/
+[coinbase settings]: https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings
+[etherscan]: https://etherscan.io/
+[faucets on the web]: https://coinbase.com/faucets
+[Foundry]: https://book.getfoundry.sh/
+
diff --git a/_pages/cookbook/smart-contract-development/foundry/generate-random-numbers-contracts.mdx b/_pages/cookbook/smart-contract-development/foundry/generate-random-numbers-contracts.mdx
new file mode 100644
index 00000000..57a50ad5
--- /dev/null
+++ b/_pages/cookbook/smart-contract-development/foundry/generate-random-numbers-contracts.mdx
@@ -0,0 +1,408 @@
+---
+title: Generating random numbers contracts using Supra dVRF
+slug: /oracles-supra-vrf
+description: A tutorial that teaches how to use Supra dVRF to serve random numbers using an onchain randomness generation mechanism directly within your smart contracts on the Base testnet.
+author: taycaldwell
+keywords: [
+ Oracle
+ Oracles,
+ Supra,
+ Supra VRF,
+ Supra dVRF,
+ VRF,
+ verifiable random function,
+ verifiable random functions,
+ random numbers,
+ rng,
+ random number generator,
+ random numbers in smart contracts,
+ random numbers on Base,
+ smart contract,
+ Base blockchain,
+ Base network,
+ Base testnet,
+ Base test network,
+ app development,
+ dapp development,
+ build a dapp on Base,
+ build on Base,
+ ]
+tags: ['oracles', 'vrf']
+difficulty: intermediate
+displayed_sidebar: null
+---
+
+This tutorial will guide you through the process of creating a smart contract on Base that utilizes Supra dVRF to serve random numbers using an onchain randomness generation mechanism directly within your smart contracts.
+
+
+
+## Objectives
+
+By the end of this tutorial you should be able to do the following:
+
+- Set up a smart contract project for Base using Foundry
+- Install the Supra dVRF as a dependency
+- Use Supra dVRF within your smart contract
+- Deploy and test your smart contracts on Base
+
+
+
+## Prerequisites
+
+### Foundry
+
+This tutorial requires you to have Foundry installed.
+
+- From the command-line (terminal), run: `curl -L https://foundry.paradigm.xyz | bash`
+- Then run `foundryup`, to install the latest (nightly) build of Foundry
+
+For more information, see the Foundry Book [installation guide](https://book.getfoundry.sh/getting-started/installation).
+
+### Coinbase Wallet
+
+In order to deploy a smart contract, you will first need a wallet. You can create a wallet by downloading the Coinbase Wallet browser extension.
+
+- Download [Coinbase Wallet](https://chrome.google.com/webstore/detail/coinbase-wallet-extension/hnfanknocfeofbddgcijnmhnfnkdnaad?hl=en)
+
+### Wallet funds
+
+Deploying contracts to the blockchain requires a gas fee. Therefore, you will need to fund your wallet with ETH to cover those gas fees.
+
+For this tutorial, you will be deploying a contract to the Base Sepolia test network. You can fund your wallet with Base Sepolia ETH using one of the faucets listed on the Base [Network Faucets](https://docs.base.org/chain/network-faucets) page.
+
+### Supra wallet registration
+
+
+Supra dVRF V2 requires subscription to the service with a customer controlled wallet address to act as the main reference.
+
+Therefore you must register your wallet with the Supra team if you plan to consume Supra dVRF V2 within your smart contracts.
+
+Please refer to the [Supra documentation](https://docs.supra.com/oracles/dvrf/vrf-subscription-model) for the latest steps on how to register your wallet for their service.
+
+
+
+
+
+## What is a Verifiable Random Function (VRF)?
+
+A Verifiable Random Function (VRF) provides a solution for generating random outcomes in a manner that is both decentralized and verifiably recorded onchain. VRFs are crucial for applications where randomness integrity is paramount, such as in gaming or prize drawings.
+
+Supra dVRF provides a decentralized VRF that ensures that the outcomes are not only effectively random but also responsive, scalable, and easily verifiable, thereby addressing the unique needs of onchain applications for trustworthy and transparent randomness.
+
+
+
+## Creating a project
+
+Before you can begin writing smart contracts for Base, you need to set up your development environment by creating a Foundry project.
+
+To create a new Foundry project, first create a new directory:
+
+```bash
+mkdir myproject
+```
+
+Then run:
+
+```bash
+cd myproject
+forge init
+```
+
+This will create a Foundry project, which has the following basic layout:
+
+```bash
+.
+├── foundry.toml
+├── script
+ │ └── Counter.s.sol
+├── src
+ │ └── Counter.sol
+└── test
+ └── Counter.t.sol
+```
+
+
+
+## Writing the Smart Contract
+
+Once your Foundry project has been created, you can now start writing a smart contract.
+
+The Solidity code below defines a basic contract named `RNGContract`. The smart contract's constructor takes in a single `address` and assigns it to a member variable named `supraAddr`. This address corresponds to the [contract address](https://docs.supra.com/oracles/data-feeds/pull-oracle/networks) of the Supra Router Contract that will be used to generate random numbers. The contract address of the Supra Router Contract on Base Sepolia testnet is `0x99a021029EBC90020B193e111Ae2726264a111A2`.
+
+The contract also assigns the contract deployer (`msg.sender`) to a member variable named `supraClientAddress`. This should be the client wallet address that is registered and whitelisted to use Supra VRF (see: [Prerequisites](#prerequisites)).
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+contract RNGContract {
+ address supraAddr;
+ address supraClientAddress;
+
+ constructor(address supraSC) {
+ supraAddr = supraSC;
+ supraClientAddress = msg.sender;
+ }
+}
+```
+
+In your project, add the code provided above to a new file named `src/ExampleContract.sol`, and delete the `src/Counter.sol` contract that was generated with the project. (you can also delete the `test/Counter.t.sol` and `script/Counter.s.sol` files).
+
+The following sections will guide you step-by-step on how to update your contract to generate random numbers using the Supra Router contract.
+
+### Adding the Supra Router Contract interface
+
+In order to help your contract (the requester contract) interact with the Supra Router contract and understand what methods it can call, you will need to add the following interface to your contract file.
+
+```solidity
+interface ISupraRouter {
+ function generateRequest(string memory _functionSig, uint8 _rngCount, uint256 _numConfirmations, uint256 _clientSeed, address _clientWalletAddress) external returns(uint256);
+ function generateRequest(string memory _functionSig, uint8 _rngCount, uint256 _numConfirmations, address _clientWalletAddress) external returns(uint256);
+}
+```
+
+The `ISupraRouter` interface defines a `generateRequest` function. This function is used to create a request for random numbers. The `generateRequest` function is defined twice, because one of the definitions allows for an optional `_clientSeed` (defaults to `0`) for additional unpredictability.
+
+
+Alternatively, you can add the `ISupraRouter` interface in a separate interface file and inherit the interface in your contract.
+
+
+
+### Adding a request function
+
+Once you have defined the `ISupraRouter`, you are ready to add the logic to your smart contract for requesting random numbers.
+
+For Supra dVRF, adding logic for requesting random numbers requires two functions:
+
+- A request function
+- A callback function
+
+The request function is a custom function defined by the developer. There are no requirements when it comes to the signature of the request function.
+
+The following code is an example of a request function named `rng` that requests random numbers using the Supra Router Contract. Add this function to your smart contract:
+
+```solidity
+function rng() external returns (uint256) {
+ // Amount of random numbers to request
+ uint8 rngCount = 5;
+ // Amount of confirmations before the request is considered complete/final
+ uint256 numConfirmations = 1;
+ uint256 nonce = ISupraRouter(supraAddr).generateRequest(
+ "requestCallback(uint256,uint256[])",
+ rngCount,
+ numConfirmations,
+ supraClientAddress
+ );
+ return nonce;
+ // store nonce if necessary (e.g., in a hashmap)
+ // this can be used to track parameters related to the request in a lookup table
+ // these can be accessed inside the callback since the response from supra will include the nonce
+}
+```
+
+The `rng` function above requests `5` random numbers (defined by `rngCount`), and waits `1` confirmation (defined by `numConfirmations`) before considering the result to be final. It makes this request by calling the `generateRequest` function of the Supra Router contract, while providing a callback function with the signature `requestCallback(uint256,uint256[])`.
+
+### Adding a callback function
+
+As seen in the previous section, the `generateRequest` method of the Supra Router contract expects a signature for a callback function. This callback function must be of the form: `uint256 nonce, uint256[] calldata rngList`, and must include validation code, such that only the Supra Router contract can call the function.
+
+To do this, add the following callback function (`requestCallback`) to your smart contract:
+
+```solidity
+function requestCallback(uint256 _nonce ,uint256[] _rngList) external {
+ require(msg.sender == supraAddr, "Only the Supra Router can call this function.");
+ uint8 i = 0;
+ uint256[] memory x = new uint256[](rngList.length);
+ rngForNonce[nonce] = x;
+ for(i=0; i uint256[] ) rngForNonce;
+```
+
+### Adding a function to view the result
+
+To fetch resulting random numbers based on their associated `nonce`, you add a third function:
+
+```solidity
+function viewRngForNonce(uint256 nonce) external view returns (uint256[] memory) {
+ return rngForNonce[nonce];
+}
+```
+
+### Final smart contract code
+
+After following all the steps above, your smart contract code should look like the following:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+interface ISupraRouter {
+ function generateRequest(string memory _functionSig, uint8 _rngCount, uint256 _numConfirmations, uint256 _clientSeed, address _clientWalletAddress) external returns (uint256);
+ function generateRequest(string memory _functionSig, uint8 _rngCount, uint256 _numConfirmations, address _clientWalletAddress) external returns (uint256);
+}
+
+contract RNGContract {
+ address supraAddr;
+ address supraClientAddress;
+
+ mapping (uint256 => uint256[]) rngForNonce;
+
+ constructor(address supraSC) {
+ supraAddr = supraSC;
+ supraClientAddress = msg.sender;
+ }
+
+ function rng() external returns (uint256) {
+ // Amount of random numbers to request
+ uint8 rngCount = 5;
+ // Amount of confirmations before the request is considered complete/final
+ uint256 numConfirmations = 1;
+ uint256 nonce = ISupraRouter(supraAddr).generateRequest(
+ "requestCallback(uint256,uint256[])",
+ rngCount,
+ numConfirmations,
+ supraClientAddress
+ );
+ return nonce;
+ }
+
+ function requestCallback(uint256 _nonce, uint256[] memory _rngList) external {
+ require(msg.sender == supraAddr, "Only the Supra Router can call this function.");
+ uint8 i = 0;
+ uint256[] memory x = new uint256[](_rngList.length);
+ rngForNonce[_nonce] = x;
+ for (i = 0; i < _rngList.length; i++) {
+ rngForNonce[_nonce][i] = _rngList[i] % 100;
+ }
+ }
+
+ function viewRngForNonce(uint256 nonce) external view returns (uint256[] memory) {
+ return rngForNonce[nonce];
+ }
+}
+```
+
+
+You must whitelist this smart contract under the wallet address you registered with Supra, and deposit funds to be paid for the gas fees associated with transactions for your callback function.
+
+Follow the [guidance steps](https://supraoracles.com/docs/vrf/v2-guide#step-1-create-the-supra-router-contract-interface-1) provided by Supra for whitelisting your contract and depositing funds.
+
+If you have not yet registered your wallet with Supra, see the [Prerequisites](#prerequisites) section.
+
+
+
+
+
+## Compiling the Smart Contract
+
+To compile your smart contract code, run:
+
+```bash
+forge build
+```
+
+
+
+## Deploying the smart contract
+
+### Setting up your wallet as the deployer
+
+Before you can deploy your smart contract to the Base network, you will need to set up a wallet to be used as the deployer.
+
+To do so, you can use the [`cast wallet import`](https://book.getfoundry.sh/reference/cast/cast-wallet-import) command to import the private key of the wallet into Foundry's securely encrypted keystore:
+
+```bash
+cast wallet import deployer --interactive
+```
+
+After running the command above, you will be prompted to enter your private key, as well as a password for signing transactions.
+
+
+For instructions on how to get your private key from Coinbase Wallet, visit the [Coinbase Wallet documentation](https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings#show-private-key).
+
+**It is critical that you do NOT commit this to a public repo**.
+
+
+
+To confirm that the wallet was imported as the `deployer` account in your Foundry project, run:
+
+```bash
+cast wallet list
+```
+
+### Setting up environment variables for Base Sepolia
+
+To setup your environment for deploying to the Base network, create an `.env` file in the home directory of your project, and add the RPC URL for the Base Sepolia testnet, as well as the Supra Router contract address for Base Sepolia testnet:
+
+```
+BASE_SEPOLIA_RPC="https://sepolia.base.org"
+ISUPRA_ROUTER_ADDRESS=0x99a021029EBC90020B193e111Ae2726264a111A2
+```
+
+Once the `.env` file has been created, run the following command to load the environment variables in the current command line session:
+
+```bash
+source .env
+```
+
+### Deploying the smart contract to Base Sepolia
+
+With your contract compiled and environment setup, you are ready to deploy the smart contract to the Base Sepolia Testnet!
+
+For deploying a single smart contract using Foundry, you can use the `forge create` command. The command requires you to specify the smart contract you want to deploy, an RPC URL of the network you want to deploy to, and the account you want to deploy with.
+
+To deploy the `RNGContract` smart contract to the Base Sepolia test network, run the following command:
+
+```bash
+forge create ./src/RNGContract.sol:RNGContract --rpc-url $BASE_SEPOLIA_RPC --constructor-args $ISUPRA_ROUTER_ADDRESS --account deployer
+```
+
+When prompted, enter the password that you set earlier, when you imported your wallet’s private key.
+
+
+Your wallet must be funded with ETH on the Base Sepolia Testnet to cover the gas fees associated with the smart contract deployment. Otherwise, the deployment will fail.
+
+To get testnet ETH for Base Sepolia, see the [prerequisites](#prerequisites).
+
+
+
+After running the command above, the contract will be deployed on the Base Sepolia test network. You can view the deployment status and contract by using a [block explorer](/chain/block-explorers).
+
+
+
+## Interacting with the Smart Contract
+
+Foundry provides the `cast` command-line tool that can be used to interact with the smart contract that was deployed and call the `getLatestPrice()` function to fetch the latest price of ETH.
+
+To call the `getLatestPrice()` function of the smart contract, run:
+
+```bash
+cast call --rpc-url $BASE_SEPOLIA_RPC "rng()"
+```
+
+You should receive a `nonce` value.
+
+You can use this `nonce` value to call the `viewRngForNonce(uint256)` function to get the resulting list of randomly generated numbers:
+
+```bash
+cast call --rpc-url $BASE_SEPOLIA_RPC "viewRngForNonce(uint256)"
+```
+
+
+
+## Conclusion
+
+Congratulations! You have successfully deployed and interacted with a smart contract that generates a list of random numbers using Supra dVRF on the Base blockchain network.
+
+To learn more about VRF and using Supra dVRF to generate random numbers within your smart contracts on Base, check out the following resources:
+
+- [Oracles](https://docs.base.org/tools/oracles)
+- [Supra dVRF - Developer Guide V2](https://supraoracles.com/docs/vrf/v2-guide)
diff --git a/docs/pages/cookbook/smart-contract-development/foundry/setup-with-base.mdx b/_pages/cookbook/smart-contract-development/foundry/setup-with-base.mdx
similarity index 100%
rename from docs/pages/cookbook/smart-contract-development/foundry/setup-with-base.mdx
rename to _pages/cookbook/smart-contract-development/foundry/setup-with-base.mdx
diff --git a/docs/pages/cookbook/smart-contract-development/foundry/testing-smart-contracts.mdx b/_pages/cookbook/smart-contract-development/foundry/testing-smart-contracts.mdx
similarity index 100%
rename from docs/pages/cookbook/smart-contract-development/foundry/testing-smart-contracts.mdx
rename to _pages/cookbook/smart-contract-development/foundry/testing-smart-contracts.mdx
diff --git a/_pages/cookbook/smart-contract-development/foundry/verify-contract-with-basescan.mdx b/_pages/cookbook/smart-contract-development/foundry/verify-contract-with-basescan.mdx
new file mode 100644
index 00000000..e8e2fb8b
--- /dev/null
+++ b/_pages/cookbook/smart-contract-development/foundry/verify-contract-with-basescan.mdx
@@ -0,0 +1,296 @@
+---
+title: 'Verify a Smart Contract using Basescan API'
+slug: /verify-smart-contract-using-basescan
+description: A tutorial that teaches how to verify a smart contract using Basescan APIs.
+author: hughescoin
+---
+
+[Basescan] is a block explorer specifically designed for [Base], offering developers a way to interact with and verify the smart contracts deployed on Base. Smart contract verification is a critical step in ensuring the transparency and security of onchain applications, as it allows others to review and validate the source code of deployed contracts. There are multiple ways to verify contracts and by the end of this tutorial you will learn how to verify a contract using the [Solidity] single file verification method using the [Basescan API].
+
+
+
+## Objectives
+
+By the end of this tutorial, you should be able to:
+
+- Deploy a smart contract using [Foundry]
+- Interact with the [Basescan API] to verify your deployed contract
+- Obtain and configure a (free) Base RPC Node from [Coinbase Developer Platform (CDP)](https://portal.cdp.coinbase.com/products/node)
+
+
+
+## Prerequisites
+
+**Familiarity with smart contract development and the Solidity programming language**
+
+Solidity is the primary programming language for writing smart contracts on Ethereum and Ethereum-compatible blockchains like Base. You should be comfortable with writing, compiling, and deploying basic smart contracts using Solidity. If not, check out [Base Learn].
+
+**Basic understanding of Foundry for Ethereum development**
+
+Foundry is a fast and portable toolkit for Ethereum application development. It simplifies the process of deploying, testing, and interacting with smart contracts. This tutorial assumes you have experience using Foundry to compile and [deploy smart contracts].
+
+**Access to a Coinbase Developer Platform (CDP) account**
+
+The [Coinbase Developer Platform] provides access to tools and services necessary for blockchain development, including RPC nodes for different networks. You'll need to sign up for a CDP account to obtain a [Base RPC Node], which will be essential for deploying and interacting with your smart contracts on the Base blockchain.
+
+**Node + Basic API requests**
+
+## Jump Right In
+
+For this tutorial, you will deploy a simple contract that is included in the Foundry quickstart. To do so, ensure that you have Foundry installed.
+
+If you don't have Foundry install it:
+
+```bash
+curl -L https://foundry.paradigm.xyz | bash
+```
+
+Once installed, create a Foundry project:
+
+```bash
+forge init verify_contracts
+```
+
+then change into the newly made directory:
+
+```bash
+cd verify_contracts
+```
+
+You should have a folder structure similar to this:
+
+```bash
+├── lib
+├── script
+├── src
+└── test
+```
+
+The `src` folder will contain a `Counter.sol` file which will serve as the contract you want to deploy.
+
+
+**You will need ETH on Base to deploy**
+
+You (the deployer wallet) will need some ETH in order to broadcast the transaction to the Base network. Fortunately, transactions are usually < 1 cent on Base mainnet.
+
+If using a [Coinbase Wallet] use the "Buy" button to onramp crypto from your Coinbase account.
+
+
+
+You will need a private key of the wallet that you want to deploy the smart contract from. Obtain it and store it as an env variable in your terminal.
+
+Once you have the private key to the wallet of your choice, open your terminal and store it in an environment variable:
+
+```bash
+export PRIVATE_KEY=""
+```
+
+To deploy our contract you will need an RPC URL to a Base node in order to broadcast our transactions to the network. [CDP] provides us with a free node for interacting with Base mainnet and testnet.
+
+Obtain an rpc url from the [Node product] and store the url as an environment variable similar to the private key in the previous step.
+
+
+
+Then store it as an environment variable in your terminal:
+
+```bash
+export BASE_RPC_URL="your_base_rpc_url"
+```
+
+It's deployment time! Deploy the `Counter.sol` contract using `forge create --rpc-url $BASE_RPC_URL --private-key $PRIVATE_KEY src/Counter.sol:Counter`
+
+Once deployed, it should return something like this:
+
+```bash
+[⠊] Compiling...
+[⠢] Compiling 1 files with Solc 0.8.26
+[⠆] Solc 0.8.26 finished in 1.23s
+Compiler run successful!
+Deployer: 0xLo69e5523D33FBDbF133E81C91639e9d3C6cb369
+Deployed to: 0xEF5fe818Cb814E5c8277C5F12B57106B4EC3DdaA
+Transaction hash: 0xb191f9679a1fee253cf430ac09a6838f6806cfb2a250757fef407880f5546836
+```
+
+Congrats! You've now deployed a contract to Base. The output of the deployment command contains a contract address (e.g `Deployed to: 0xEF5fe818Cb814E5c8277C5F12B57106B4EC3DdaA`). Copy this address as you will need it in the next step.
+
+### Verify the contract
+
+You will now interact with the [Basescan API]. For this, you need API Keys. Create an account using an email or [login to Basescan].
+
+After signing in, navigate to your [Basescan account] then select `API Keys` on the left navigation bar.
+
+
+
+From the [API Key page], click the blue "Add" button to create a new API Key then copy your `API Key Token`
+
+
+
+Save this to your clipboard for the next step.
+
+Create a `.js` file to create a function to that will call the Basescan's contract verification endpoint.
+
+In your terminal create a new file: `touch verifyContractBasescan.js` then open this file in your IDE of choice.
+
+At the top of the file create a variable that contains the `Counter.sol` that was created from your foundry project.
+
+```javascript
+const sourceCode = `pragma solidity ^0.8.13;
+contract Counter {
+ uint256 public number;
+ function setNumber(uint256 newNumber) public {
+ number = newNumber;
+}
+ function increment() public {
+ number++;
+ }
+}`;
+```
+
+Create an `async` function to call the basescan api. Basescan offers a few endpoints to interact with their api with the base url being: `https://api.basescan.org/api`
+
+To verify a contract you will use the `verifysourcecode` route, with the `contract` module, and your `apiKey` as query parameters.
+
+
+**Unsure what data to input?**
+
+In every foundry project you will have a `.json` file that contains the contracts metadata and ABI. For this particular project, this information is located in the `/verify_contracts/out/Counter.sol/Counter.json`
+
+Under the `Metadata` object you will find the compiler version under `evmversion`
+
+
+
+Putting everything together, our function will look like this:
+
+```
+async function verifySourceCode() {
+ const url = 'https://api.basescan.org/api';
+
+ const params = new URLSearchParams({
+ module: 'contract',
+ action: 'verifysourcecode',
+ apikey: 'DK8M329VYXDSKTF633ABTK3SAEZ2U9P8FK', //remove hardcode
+ });
+
+ const data = new URLSearchParams({
+ chainId: '8453',
+ codeformat: 'solidity-single-file',
+ sourceCode: sourceCode,
+ contractaddress: '0x8aB096ea9886ACe363f81068d2439033F67F62E4',
+ contractname: 'Counter',
+ compilerversion: 'v0.8.26+commit.8a97fa7a',
+ optimizationUsed: 0,
+ evmversion: 'paris',
+ });
+ }
+```
+
+Now add a `try-catch` block to send the request and log any errors to the console.
+
+Your final file should look something like this:
+
+```javascript
+const sourceCode = `pragma solidity ^0.8.13;
+
+contract Counter {
+ uint256 public number;
+
+ function setNumber(uint256 newNumber) public {
+ number = newNumber;
+ }
+
+ function increment() public {
+ number++;
+ }
+}`;
+
+async function verifySourceCode() {
+ const url = 'https://api.basescan.org/api';
+
+ const params = new URLSearchParams({
+ module: 'contract',
+ action: 'verifysourcecode',
+ apikey: 'YOUR_API_KEY',
+ });
+
+ const data = new URLSearchParams({
+ chainId: '8453',
+ codeformat: 'solidity-single-file',
+ sourceCode: sourceCode,
+ contractaddress: '0x8aB096ea9886ACe363f81068d2439033F67F62E4',
+ contractname: 'Counter',
+ compilerversion: 'v0.8.26+commit.8a97fa7a',
+ optimizationUsed: 0,
+ evmversion: 'paris',
+ });
+
+ try {
+ const response = await fetch(`${url}?${params}`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: data,
+ });
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+
+ const result = await response.json();
+ console.log(result);
+ return result;
+ } catch (error) {
+ console.error('Error:', error);
+ throw error;
+ }
+}
+
+verifySourceCode().catch((error) => console.error('Unhandled error:', error));
+```
+
+Save your file and then run `node verifyContractBasescan.js` in your terminal
+
+If successful, your terminal will output JSON text with three properties `status`, `message` and `result` like below:
+
+```bash
+{
+ status: '1',
+ message: 'OK',
+ result: 'cqjzzvppgswqw5adq4v6iq4xkmf519pj1higvcxsdiwcvwxemd'
+}
+```
+
+Result is the GUID and is a unique identifier for checking the status of your contracts verification.
+
+To verify the contract, let's create a curl request with the following parameters
+
+```bash
+curl "https://api.basescan.org/api?module=contract&action=checkverifystatus&guid=cqjzzvppgswqw5adq4v6iq4xkmf519pj1higvcxsdiwcvwxemd&apikey=DK8M329VYXDSKTF633ABTK3SAEZ2U9P8FK"
+```
+
+Run the command and you will see a that the contract should already be verified based on the `result` field
+
+```json
+{ "status": "0", "message": "NOTOK", "result": "Already Verified" }
+```
+
+## Conclusion
+
+Congratulations! You’ve successfully deployed and verified a smart contract using the Basescan API. Now, your users don’t have to rely solely on your word—they can verify the contract’s functionality through the code itself.
+
+
+
+[Coinbase Developer Platform]: https://portal.cdp.coinbase.com/
+[Base RPC Node]: https://portal.cdp.coinbase.com/products/node
+[CDP]: https://portal.cdp.coinbase.com/
+[Base]: https://base.org/
+[Basescan]: https://basescan.org/
+[Solidity]: https://soliditylang.org/
+[Basescan account]: https://basescan.org/myaccount/
+[API Key page]: https://basescan.org/myapikey/
+[Basescan API]: https://docs.basescan.org/
+[login to Basescan]: https://basescan.org/login/
+[Node product]: https://portal.cdp.coinbase.com/products/node/
+[deploy smart contracts]: https://docs.base.org/tutorials/deploy-with-foundry/
+[Base Learn]: https://docs.base.org/learn/welcome/
+[Foundry]: https://book.getfoundry.sh/getting-started/installation
diff --git a/docs/pages/cookbook/smart-contract-development/hardhat/analyzing-test-coverage.mdx b/_pages/cookbook/smart-contract-development/hardhat/analyzing-test-coverage.mdx
similarity index 100%
rename from docs/pages/cookbook/smart-contract-development/hardhat/analyzing-test-coverage.mdx
rename to _pages/cookbook/smart-contract-development/hardhat/analyzing-test-coverage.mdx
diff --git a/docs/pages/cookbook/smart-contract-development/hardhat/debugging-smart-contracts.mdx b/_pages/cookbook/smart-contract-development/hardhat/debugging-smart-contracts.mdx
similarity index 100%
rename from docs/pages/cookbook/smart-contract-development/hardhat/debugging-smart-contracts.mdx
rename to _pages/cookbook/smart-contract-development/hardhat/debugging-smart-contracts.mdx
diff --git a/_pages/cookbook/smart-contract-development/hardhat/deploy-with-hardhat.mdx b/_pages/cookbook/smart-contract-development/hardhat/deploy-with-hardhat.mdx
new file mode 100644
index 00000000..d05bb514
--- /dev/null
+++ b/_pages/cookbook/smart-contract-development/hardhat/deploy-with-hardhat.mdx
@@ -0,0 +1,364 @@
+---
+title: Deploying a smart contract using Hardhat
+slug: /deploy-with-hardhat
+description: "A tutorial that teaches how to deploy a smart contract on the Base test network using Hardhat. Includes instructions for setting up the environment, compiling, and deploying the smart contract."
+author: taycaldwell
+---
+
+This section will guide you through deploying an NFT smart contract (ERC-721) on the Base test network using [Hardhat](https://hardhat.org/).
+
+Hardhat is a developer tool that provides a simple way to deploy, test, and debug smart contracts.
+
+
+
+## Objectives
+
+By the end of this tutorial, you should be able to do the following:
+
+- Setup Hardhat for Base
+- Create an NFT smart contract for Base
+- Compile a smart contract for Base
+- Deploy a smart contract to Base
+- Interact with a smart contract deployed on Base
+
+
+
+## Prerequisites
+
+### Node v18+
+
+This tutorial requires you have Node version 18+ installed.
+
+- Download [Node v18+](https://nodejs.org/en/download/)
+
+If you are using `nvm` to manage your node versions, you can just run `nvm install 18`.
+
+### Coinbase Wallet
+
+In order to deploy a smart contract, you will first need a web3 wallet. You can create a wallet by downloading the Coinbase Wallet browser extension.
+
+- Download [Coinbase Wallet](https://chrome.google.com/webstore/detail/coinbase-wallet-extension/hnfanknocfeofbddgcijnmhnfnkdnaad?hl=en)
+
+### Wallet funds
+
+Deploying contracts to the blockchain requires a gas fee. Therefore, you will need to fund your wallet with ETH to cover those gas fees.
+
+For this tutorial, you will be deploying a contract to the Base Sepolia test network. You can fund your wallet with Base Sepolia ETH using one of the faucets listed on the Base [Network Faucets](https://docs.base.org/chain/network-faucets) page.
+
+
+
+## Creating a project
+
+Before you can begin deploying smart contracts to Base, you need to set up your development environment by creating a Node.js project.
+
+To create a new Node.js project, run:
+
+```bash
+npm init --y
+```
+
+Next, you will need to install Hardhat and create a new Hardhat project
+
+To install Hardhat, run:
+
+```bash
+npm install --save-dev hardhat
+```
+
+To create a new Hardhat project, run:
+
+```bash
+npx hardhat init
+```
+
+Select `Create a TypeScript project` then press _enter_ to confirm the project root.
+
+Select `y` for both adding a `.gitignore` and loading the sample project. It will take a moment for the project setup process to complete.
+
+
+
+## Configuring Hardhat with Base
+
+In order to deploy smart contracts to the Base network, you will need to configure your Hardhat project and add the Base network.
+
+To configure Hardhat to use Base, add Base as a network to your project's `hardhat.config.ts` file:
+
+```tsx
+import { HardhatUserConfig } from 'hardhat/config';
+import '@nomicfoundation/hardhat-toolbox';
+
+require('dotenv').config();
+
+const config: HardhatUserConfig = {
+ solidity: {
+ version: '0.8.23',
+ },
+ networks: {
+ // for mainnet
+ 'base-mainnet': {
+ url: 'https://mainnet.base.org',
+ accounts: [process.env.WALLET_KEY as string],
+ gasPrice: 1000000000,
+ },
+ // for testnet
+ 'base-sepolia': {
+ url: 'https://sepolia.base.org',
+ accounts: [process.env.WALLET_KEY as string],
+ gasPrice: 1000000000,
+ },
+ // for local dev environment
+ 'base-local': {
+ url: 'http://localhost:8545',
+ accounts: [process.env.WALLET_KEY as string],
+ gasPrice: 1000000000,
+ },
+ },
+ defaultNetwork: 'hardhat',
+};
+
+export default config;
+```
+
+### Install Hardhat toolbox
+
+The above configuration uses the `@nomicfoundation/hardhat-toolbox` plugin to bundle all the commonly used packages and Hardhat plugins recommended to start developing with Hardhat.
+
+To install `@nomicfoundation/hardhat-toolbox`, run:
+
+```bash
+npm install --save-dev @nomicfoundation/hardhat-toolbox
+```
+
+### Loading environment variables
+
+The above configuration also uses [dotenv](https://www.npmjs.com/package/dotenv) to load the `WALLET_KEY` environment variable from a `.env` file to `process.env.WALLET_KEY`. You should use a similar method to avoid hardcoding your private keys within your source code.
+
+To install `dotenv`, run:
+
+```bash
+npm install --save-dev dotenv
+```
+
+Once you have `dotenv` installed, you can create a `.env` file with the following content:
+
+```
+WALLET_KEY=""
+```
+
+Substituting `` with the private key for your wallet.
+
+
+`WALLET_KEY` is the private key of the wallet to use when deploying a contract. For instructions on how to get your private key from Coinbase Wallet, visit the [Coinbase Wallet documentation](https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings#show-private-key). **It is critical that you do NOT commit this to a public repo**
+
+
+
+### Local Networks
+
+You can run the Base network locally, and deploy using it. If this is what you are looking to do, see the [repo containing the relevant Docker builds](https://github.com/base-org/node).
+
+It will take a **very** long time for your node to sync with the network. If you get errors that the `nonce has already been used` when trying to deploy, you aren't synced yet.
+
+For quick testing, such as if you want to add unit tests to the below NFT contract, you may wish to leave the `defaultNetwork` as `'hardhat'`.
+
+
+
+## Compiling the smart contract
+
+Below is a simple NFT smart contract (ERC-721) written in the Solidity programming language:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.23;
+
+import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+
+contract NFT is ERC721 {
+ uint256 public currentTokenId;
+
+ constructor() ERC721("NFT Name", "NFT") {}
+
+ function mint(address recipient) public payable returns (uint256) {
+ uint256 newItemId = ++currentTokenId;
+ _safeMint(recipient, newItemId);
+ return newItemId;
+ }
+}
+```
+
+The Solidity code above defines a smart contract named `NFT`. The code uses the `ERC721` interface provided by the [OpenZeppelin Contracts library](https://docs.openzeppelin.com/contracts/5.x/) to create an NFT smart contract. OpenZeppelin allows developers to leverage battle-tested smart contract implementations that adhere to official ERC standards.
+
+To add the OpenZeppelin Contracts library to your project, run:
+
+```bash
+npm install --save @openzeppelin/contracts
+```
+
+In your project, delete the `contracts/Lock.sol` contract that was generated with the project and add the above code in a new file called `contracts/NFT.sol`. (You can also delete the `test/Lock.ts` test file, but you should add your own tests ASAP!).
+
+To compile the contract using Hardhat, run:
+
+```bash
+npx hardhat compile
+```
+
+
+
+## Deploying the smart contract
+
+Once your contract has been successfully compiled, you can deploy the contract to the Base Sepolia test network.
+
+To deploy the contract to the Base Sepolia test network, you'll need to modify the `scripts/deploy.ts` in your project:
+
+```tsx
+import { ethers } from 'hardhat';
+
+async function main() {
+ const nft = await ethers.deployContract('NFT');
+
+ await nft.waitForDeployment();
+
+ console.log('NFT Contract Deployed at ' + nft.target);
+}
+
+// We recommend this pattern to be able to use async/await everywhere
+// and properly handle errors.
+main().catch((error) => {
+ console.error(error);
+ process.exitCode = 1;
+});
+```
+
+You'll also need testnet ETH in your wallet. See the [prerequisites](#prerequisites) if you haven't done that yet. Otherwise, the deployment attempt will fail.
+
+Finally, run:
+
+```bash
+npx hardhat run scripts/deploy.ts --network base-sepolia
+```
+
+The contract will be deployed on the Base Sepolia test network. You can view the deployment status and contract by using a [block explorer](/chain/block-explorers) and searching for the address returned by your deploy script. If you've deployed an exact copy of the NFT contract above, it will already be verified and you'll be able to read and write to the contract using the web interface.
+
+
+If you'd like to deploy to mainnet, you'll modify the command like so:
+
+```bash
+npx hardhat run scripts/deploy.ts --network base-mainnet
+```
+
+
+
+Regardless of the network you're deploying to, if you're deploying a new or modified contract, you'll need to verify it first.
+
+
+
+## Verifying the Smart Contract
+
+If you want to interact with your contract on the block explorer, you, or someone, needs to verify it first. The above contract has already been verified, so you should be able to view your version on a block explorer already. For the remainder of this tutorial, we'll walk through how to verify your contract on Base Sepolia testnet.
+
+In `hardhat.config.ts`, configure Base Sepolia as a custom network. Add the following to your `HardhatUserConfig`:
+
+
+```tsx
+etherscan: {
+ apiKey: {
+ "base-sepolia": "PLACEHOLDER_STRING"
+ },
+ customChains: [
+ {
+ network: "base-sepolia",
+ chainId: 84532,
+ urls: {
+ apiURL: "https://api-sepolia.basescan.org/api",
+ browserURL: "https://sepolia.basescan.org"
+ }
+ }
+ ]
+ },
+```
+
+
+You can get your Basescan API key from [basescan.org](https://basescan.org/myapikey) when you sign up for an account.
+
+
+
+{/* */}
+
+
+```tsx
+// Hardhat expects etherscan here, even if you're using Blockscout.
+etherscan: {
+ apiKey: {
+ "base-sepolia": process.env.BLOCKSCOUT_KEY as string
+ },
+ customChains: [
+ {
+ network: "base-sepolia",
+ chainId: 84532,
+ urls: {
+ apiURL: "https://base-sepolia.blockscout.com/api",
+ browserURL: "https://base-sepolia.blockscout.com"
+ }
+ }
+ ]
+ },
+```
+
+
+You can get your Blockscout API key from [here](https://base-sepolia.blockscout.com/account/api_key) after you sign up for an account.
+
+
+
+
+
+Now, you can verify your contract. Grab the deployed address and run:
+
+```bash
+npx hardhat verify --network base-sepolia
+```
+
+You should see an output similar to:
+
+
+```
+Nothing to compile
+No need to generate any newer typings.
+Successfully submitted source code for contract
+contracts/NFT.sol:NFT at 0x6527E5052de5521fE370AE5ec0aFCC6cD5a221de
+for verification on the block explorer. Waiting for verification result...
+
+Successfully verified contract NFT on Etherscan.
+```
+
+
+
+
+```
+Nothing to compile
+No need to generate any newer typings.
+Successfully submitted source code for contract
+contracts/NFT.sol:NFT at 0x6527E5052de5521fE370AE5ec0aFCC6cD5a221de
+for verification on the block explorer. Waiting for verification result...
+
+Successfully verified contract NFT on Etherscan.
+```
+
+
+
+
+You can't re-verify a contract identical to one that has already been verified. If you attempt to do so, such as verifying the above contract, you'll get an error similar to:
+
+```text
+Error in plugin @nomiclabs/hardhat-etherscan: The API responded with an unexpected message.
+Contract verification may have succeeded and should be checked manually.
+Message: Already Verified
+```
+
+
+
+Search for your contract on [Blockscout](https://base-sepolia.blockscout.com/) or [Basescan](https://sepolia.basescan.org/) to confirm it is verified.
+
+## Interacting with the Smart Contract
+
+If you verified on Basescan, you can use the `Read Contract` and `Write Contract` tabs to interact with the deployed contract. You'll need to connect your wallet first, by clicking the Connect button.
+
+
diff --git a/docs/pages/cookbook/smart-contract-development/hardhat/optimizing-gas-usage.md b/_pages/cookbook/smart-contract-development/hardhat/optimizing-gas-usage.mdx
similarity index 100%
rename from docs/pages/cookbook/smart-contract-development/hardhat/optimizing-gas-usage.md
rename to _pages/cookbook/smart-contract-development/hardhat/optimizing-gas-usage.mdx
diff --git a/docs/pages/cookbook/smart-contract-development/hardhat/reducing-contract-size.md b/_pages/cookbook/smart-contract-development/hardhat/reducing-contract-size.mdx
similarity index 100%
rename from docs/pages/cookbook/smart-contract-development/hardhat/reducing-contract-size.md
rename to _pages/cookbook/smart-contract-development/hardhat/reducing-contract-size.mdx
diff --git a/docs/pages/cookbook/smart-contract-development/remix/deploy-with-remix.mdx b/_pages/cookbook/smart-contract-development/remix/deploy-with-remix.mdx
similarity index 98%
rename from docs/pages/cookbook/smart-contract-development/remix/deploy-with-remix.mdx
rename to _pages/cookbook/smart-contract-development/remix/deploy-with-remix.mdx
index a82474c4..1b31eed1 100644
--- a/docs/pages/cookbook/smart-contract-development/remix/deploy-with-remix.mdx
+++ b/_pages/cookbook/smart-contract-development/remix/deploy-with-remix.mdx
@@ -5,6 +5,8 @@ description: "A tutorial that teaches how to deploy a smart contract on the Base
author: briandoyle81
---
+import { Danger } from "/snippets/danger.mdx";
+
# Deploying a smart contract using Remix
[Remix] is an online IDE that you can use to rapidly develop and deploy smart contracts. If you're new to smart contracts, it's a great tool that lets you jump right in without needing to configure a local editor or struggle through environment configuration issues before getting started.
@@ -13,11 +15,10 @@ Remix contains a simulation of a blockchain that you can use to rapidly deploy a
In this article, we'll give you an overview of Remix, and show you how to deploy a contract to **Base Sepolia** testnet.
-:::info
-
+
For production / mainnet deployments the steps below in this tutorial will be almost identical, however, you'll want to ensure that you've selected `Base` (mainnet) as the network rather than `Base Sepolia` (testnet).
+
-:::
If you're already familiar with Remix, you probably want to jump down to [here].
@@ -73,15 +74,14 @@ The _Deploy & Run Transactions_ plugin is what you'll use to deploy your contrac
Fix any errors you introduced to `1_Storage.sol` and click the orange `Deploy` button. You'll see your contract appear below as _STORAGE AT \_.
-:::caution
-
+
There are a couple gotchas that can be very confusing with deploying contracts in Remix.
First, every time you hit the Deploy button, a new copy of your contract is deployed, but the previous deployments remain. Unless you are comparing or debugging between different versions of a contract, or deploying multiple contracts at once, you should click the `Trash` button to erase old deployments before deploying again.
Second, if your code will not compile, **clicking the deploy button will not generate an error!** Instead, the last compiled version will be deployed. Visually check and confirm that there are no errors indicated by a number in a red circle on top of the Compiler plugin.
+
-:::
## Prepare for Deployment
@@ -91,13 +91,12 @@ Testnets operate in a similar, **but not exactly the same** manner as the main n
If you already have a wallet set up **exclusively for development**, you can skip to the next section. Otherwise, now is the time to jump in!
-:::danger
-
+
It is very dangerous to use a wallet with valuable assets for development. You could easily write code with a bug that transfers the wrong amount of the wrong token to the wrong address. Transactions cannot be reversed once sent!
Be safe and use separate wallets for separate purposes.
+
-:::
First, add the [Coinbase] or [MetaMask] wallet to your browser, and [set up] a new wallet. As a developer, you need to be doubly careful about the security of your wallet! Many apps grant special powers to the wallet address that is the owner of the contract, such as allowing the withdrawal of all the Ether that customers have paid to the contract, or changing critical settings.
@@ -151,21 +150,19 @@ Click the orange _Deploy_ button. Because it costs gas to deploy a contract, you

-:::danger
-
+
Always carefully review all transactions, confirming the transaction cost, assets transferred, and network. As a developer, you'll get used to approving transactions regularly. Do the best you can to avoid getting into the habit of clicking _Confirm_ without reviewing the transaction carefully. If you feel pressured to _Confirm_ before you run out of time, it is almost certainly a scam.
+
-:::
After you click the _Confirm_ button, return to Remix and wait for the transaction to deploy. Copy its address and navigate to [`sepolia.basescan.org`]. **Note:** If you deployed to mainnet, you'll navigate to [`basescan.org`] instead.
### Verify the Contract
-:::info
-
+
You don't need to verify the contract if you've deployed one identical to a contract that has already been verified.
+
-:::
You can interact with your deployed contract using Remix, the same as before, but it's also possible to interact with it through [BaseScan]. Paste your address in the search field to find it.
diff --git a/docs/pages/cookbook/smart-contract-development/tenderly/deploy-with-tenderly.mdx b/_pages/cookbook/smart-contract-development/tenderly/deploy-with-tenderly.mdx
similarity index 99%
rename from docs/pages/cookbook/smart-contract-development/tenderly/deploy-with-tenderly.mdx
rename to _pages/cookbook/smart-contract-development/tenderly/deploy-with-tenderly.mdx
index 3ac19c24..4aea7617 100644
--- a/docs/pages/cookbook/smart-contract-development/tenderly/deploy-with-tenderly.mdx
+++ b/_pages/cookbook/smart-contract-development/tenderly/deploy-with-tenderly.mdx
@@ -52,15 +52,17 @@ Follow these steps to set up your DevNet template for Base Network:

-:::info
+
If needed, check out the Tenderly documentation for more information on [setting up a Devnet template](https://docs.tenderly.co/devnets/intro-to-devnets#basic-devnet-usage).
-:::
+
+
4. Click **Spawn DevNet,** and that's about it - you've got your own private replica of Base Network.
-:::info
+
If needed, check out the Tenderly documentation for more alternative [methods to spawn a DevNet](https://docs.tenderly.co/devnets/advanced/automated-devnet-spawning-bash-and-javascript).
-:::
+
+
### 4. Customize your DevNet environment
@@ -289,9 +291,10 @@ In addition, we can **change the state of the contract** before simulating a tra
6. Click **Add**.
7. Click **Simulate**.
-:::info
+
Note that this has overridden the existing state with a new default greeting. This functionality allows you to run transaction simulations under custom conditions.
-:::
+
+
## Use DevNets in Continuous Integration (CI)
diff --git a/docs/pages/cookbook/smart-contract-development/thirdweb/build-with-thirdweb.mdx b/_pages/cookbook/smart-contract-development/thirdweb/build-with-thirdweb.mdx
similarity index 99%
rename from docs/pages/cookbook/smart-contract-development/thirdweb/build-with-thirdweb.mdx
rename to _pages/cookbook/smart-contract-development/thirdweb/build-with-thirdweb.mdx
index 17bc78e2..1f224b9d 100644
--- a/docs/pages/cookbook/smart-contract-development/thirdweb/build-with-thirdweb.mdx
+++ b/_pages/cookbook/smart-contract-development/thirdweb/build-with-thirdweb.mdx
@@ -51,11 +51,10 @@ Follow these steps to set up your NFT collection:

-:::info
-
+
For production / mainnet deployments select `Base` (mainnet) as the network rather than `Base Sepolia`.
+
-:::
Post-deployment, you can manage your smart contract via the [thirdweb dashboard](https://thirdweb.com/dashboard/contracts).
@@ -118,11 +117,10 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
The above code imports and uses `BaseSepoliaTestnet` to be the `activeChain`.
-:::info
-
+
For production / mainnet deployments, update the information above so that the `chain` variable is `base` (step ii), the `blockExplorer` is `https://basescan.org` (step iii), and update both instances of `BaseSepoliaTestnet` to `Base` in the example javascript code.
+
-:::
## Running the Application
diff --git a/docs/pages/cookbook/smart-contract-development/thirdweb/deploy-with-thirdweb.mdx b/_pages/cookbook/smart-contract-development/thirdweb/deploy-with-thirdweb.mdx
similarity index 99%
rename from docs/pages/cookbook/smart-contract-development/thirdweb/deploy-with-thirdweb.mdx
rename to _pages/cookbook/smart-contract-development/thirdweb/deploy-with-thirdweb.mdx
index f4a95dcd..8422579e 100644
--- a/docs/pages/cookbook/smart-contract-development/thirdweb/deploy-with-thirdweb.mdx
+++ b/_pages/cookbook/smart-contract-development/thirdweb/deploy-with-thirdweb.mdx
@@ -105,11 +105,10 @@ From the dashboard, you will need to first enter the values for our contract's c
Finally, select the Base Sepolia test network as the [network](https://blog.thirdweb.com/guides/which-network-should-you-use/) you want to deploy to, and click **Deploy Now**.
-:::info
-
+
For production / mainnet deployments select `Base` (mainnet) as the network rather than `Base Sepolia`.
+
-:::
Once your contract is deployed, you'll be redirected to a [dashboard](https://thirdweb.com/dashboard) for managing your contract.
diff --git a/docs/pages/cookbook/smart-contract-development/thirdweb/thirdweb-cli.mdx b/_pages/cookbook/smart-contract-development/thirdweb/thirdweb-cli.mdx
similarity index 97%
rename from docs/pages/cookbook/smart-contract-development/thirdweb/thirdweb-cli.mdx
rename to _pages/cookbook/smart-contract-development/thirdweb/thirdweb-cli.mdx
index ea31626b..5b6001de 100644
--- a/docs/pages/cookbook/smart-contract-development/thirdweb/thirdweb-cli.mdx
+++ b/_pages/cookbook/smart-contract-development/thirdweb/thirdweb-cli.mdx
@@ -21,8 +21,7 @@ Create a new project with thirdweb installed and configured:
npx thirdweb create
```
-:::info
-
+
When you create a project for smart contracts or web3 apps there are various configurable options.
**For contracts, some options are:**
@@ -36,8 +35,8 @@ When you create a project for smart contracts or web3 apps there are various con
- Front end applications using Next, CRA or Vite
- Backend applications using Node.js or Express.js
- Choice of TypeScript or JavaScript variants
+
-:::
## Deploying a smart contract
@@ -47,17 +46,15 @@ When you create a project for smart contracts or web3 apps there are various con
npx thirdweb deploy
```
-:::info
-
+
To deploy to the Base network, after running `npx thirdweb deploy`, visit the provided dashboard URL and select Base from the Network dropdown.
+
-:::
-
-:::info
+
For a complete guide on using the thirdweb CLI to create and deploy contracts on Base, see [Deploy a smart contract on Base testnet](https://blog.thirdweb.com/guides/how-to-deploy-a-smart-contract-to-base-network-testnet-coinbase-l2/).
+
-:::
## Publishing a smart contract
diff --git a/docs/pages/cookbook/smart-contract-development/thirdweb/thirdweb-sdk.mdx b/_pages/cookbook/smart-contract-development/thirdweb/thirdweb-sdk.mdx
similarity index 99%
rename from docs/pages/cookbook/smart-contract-development/thirdweb/thirdweb-sdk.mdx
rename to _pages/cookbook/smart-contract-development/thirdweb/thirdweb-sdk.mdx
index cf70f54f..2c30eeff 100644
--- a/docs/pages/cookbook/smart-contract-development/thirdweb/thirdweb-sdk.mdx
+++ b/_pages/cookbook/smart-contract-development/thirdweb/thirdweb-sdk.mdx
@@ -36,8 +36,7 @@ const sdk = new ThirdwebSDK(Base);
const contract = await sdk.getContract('0x0000000000000000000000000000000000000000');
```
-:::info
-
+
The code snippet above uses the [React SDK](https://portal.thirdweb.com/react). The thirdweb SDKs are also available in [React Native](https://portal.thirdweb.com/react-native), [TypeScript](https://portal.thirdweb.com/typescript), [Python](https://portal.thirdweb.com/python), [Go](https://portal.thirdweb.com/go), and [Unity](https://portal.thirdweb.com/unity).
If alternatively you'd like to initialize the SDK with Base Sepolia (testnet), use the following code instead:
@@ -49,18 +48,17 @@ import { ThirdwebSDK } from '@thirdweb-dev/sdk/evm';
const sdk = new ThirdwebSDK(BaseSepoliaTestnet);
const contract = await sdk.getContract('0x0000000000000000000000000000000000000000');
```
+
-:::
## Interacting with smart contracts
Once you initialize the SDK and connect to a smart contract deployed to Base, you can start calling functions on it using the SDK.
-:::info
-
+
Any interaction you make with a smart contract will be made from the connected wallet automatically.
+
-:::
### Using contract extension functions
diff --git a/docs/pages/cookbook/token-gating/gate-irl-events-with-nouns.mdx b/_pages/cookbook/token-gating/gate-irl-events-with-nouns.mdx
similarity index 99%
rename from docs/pages/cookbook/token-gating/gate-irl-events-with-nouns.mdx
rename to _pages/cookbook/token-gating/gate-irl-events-with-nouns.mdx
index 723c10e6..e0a9b061 100644
--- a/docs/pages/cookbook/token-gating/gate-irl-events-with-nouns.mdx
+++ b/_pages/cookbook/token-gating/gate-irl-events-with-nouns.mdx
@@ -5,6 +5,8 @@ description: Learn how to gate entry to an IRL event for members of a Nounish DA
author: briandoyle81
---
+import { Danger } from "/snippets/danger.mdx";
+
# Gate IRL Events with Nouns
You've probably seen people in onchain communities use ⌐◨-◨ in their profile names. These are called _Nouns Goggles_, or _Noggles_. They're an ASCII representation of the glasses found on every procedurally generated [Nouns] NFT avatar. The [Nouns Auction] makes one new Noun available for auction every single day - forever!
@@ -42,11 +44,10 @@ You can use the contracts in the [Nouns Monorepo] to deploy your own DAO, but th
Navigate to the [Testnet Builder DAO] site and connect your wallet.
-:::danger
-
+
The name of this site is a little misleading. It will treat the DAOs you create here as test DAOs, but it deploys contracts on Base Mainnet, **not** Base Sepolia. Luckily, gas is inexpensive enough that this isn't a terrible price.
+
-:::
Click the green circle in the upper right, and select `Create a DAO`
@@ -66,11 +67,10 @@ For testing, you'll want the `Auction Reserve Price` as low as possible, .0001 E
Set `Veto Power` and initial `Token Allocation` as you see fit. To speed up testing, you may wish to grant several addresses here.
-:::caution
-
+
The app gets confused if you change addresses from the browser extension wallet, copy them from another window, or another location.
+
-:::
### Artwork
diff --git a/_pages/cookbook/use-case-guides/cast-actions.mdx b/_pages/cookbook/use-case-guides/cast-actions.mdx
new file mode 100644
index 00000000..424d8a63
--- /dev/null
+++ b/_pages/cookbook/use-case-guides/cast-actions.mdx
@@ -0,0 +1,197 @@
+---
+title: 'Farcaster Cast Actions: Create a Simple Cast Action'
+slug: /farcaster-cast-actions-simple
+description: A tutorial that teaches how to make a simple Farcaster cast action.
+author: briandoyle81
+---
+
+# Farcaster Cast Actions: Create a Simple Cast Action
+
+[Cast Actions] are a new feature of [Farcaster] that enable you to create actions that users can install on their Farcaster client and then click a button on any cast to trigger the action. In this tutorial, you'll learn how to build a simple cast action with [OnchainKit] that returns a message to the Farcaster client.
+
+
+Cast Actions are brand new and tools for building them are evolving quickly. Check the [Cast Actions] docs and OnchainKit [changelog]!
+
+
+
+## Objectives
+
+By the end of this tutorial you should be able to:
+
+- Create a Farcaster cast action that returns a message when triggered by a user
+- Identify the location to add additional functionality to the cast
+- Enable users to install the cast action
+
+## Prerequisites
+
+### Vercel
+
+You'll need to be comfortable deploying your app to [Vercel], or using another solution on your own. If you need a refresher, check out our tutorial on [deploying with Vercel]!
+
+### Farcaster
+
+You must have a [Farcaster] account with a connected wallet. Check out the [Base channel] to stay in the loop when we release tutorials like this!
+
+## Setup
+
+Create a copy of [a-frame-in-100-lines]. It's a template, so you can easily add a new copy by clicking the green button at the top of the page. If you're working off of an existing copy, check to make sure you have the most up-to-date version of [OnchainKit]!
+
+Run `yarn install`.
+
+Cast actions need to be deployed to a server to work, so connect your repo to [Vercel] with CI/CD. Remember that while doing so does make it much easier to test and redeploy, it also makes it easier to break a live cast action!
+
+Open `app/config.ts` and update `NEXT_PUBLIC_URL` to your new deployment.
+
+## Creating A Simple Cast Action
+
+Cast actions work similarly to the way [Frames] call your api endpoint. As a result, you can quickly adapt the code of an endpoint expecting a frame to instead manage a cast action.
+
+Add a new folder in `api` called `action` with a file called `route.ts` to create a new route in your app. Import dependencies from OnchainKit and Next.js:
+
+```tsx
+import { FrameRequest, getFrameMessage } from '@coinbase/onchainkit/frame';
+import { NextRequest, NextResponse } from 'next/server';
+```
+
+You won't be returning a frame, so you don't need `getFrameHtmlResponse`.
+
+Stub out your `POST` handler and response function:
+
+```tsx
+async function getResponse(req: NextRequest): Promise {
+ // TODO
+}
+
+export async function POST(req: NextRequest): Promise {
+ console.log('POST');
+ return getResponse(req);
+}
+
+export async function GET(req: NextRequest): Promise {
+ console.log('GET');
+ return getResponse(req);
+}
+
+export const dynamic = 'force-dynamic';
+```
+
+Parse and validate the request exactly the same as you would a frame in the top of `getResponse`:
+
+```tsx
+const body: FrameRequest = await req.json();
+const { isValid, message } = await getFrameMessage(body, { neynarApiKey: 'NEYNAR_ONCHAIN_KIT' });
+
+if (!isValid) {
+ return new NextResponse('Message not valid', { status: 500 });
+}
+```
+
+Instead of returning a frame, you'll return a json `NextResponse` with a string `message` and a 200 status:
+
+```tsx
+return NextResponse.json({ message: 'Hello from the frame route' }, { status: 200 });
+```
+
+### Testing the Cast Action
+
+Commit your work to trigger a deploy in [Vercel]. Once it's done, you can test your cast action with the [Cast Actions Playground].
+
+The above link has an example embedded in it. Try it out, then replace the `Post URL` with your own. Click `Submit Action` and you will see the hello message from your route.
+
+## Performing a Task in the Cast Action
+
+Before sending the response, you can do whatever you want to give your cast action functionality. Possibilities include:
+
+- Extracting the cast hash or caster FID from the request and using it for something
+- Reading or writing from a database or smart contract
+- Anything else you'd use an API call to do on behalf of a user
+
+For now, you can add a simple addition to let the user know what time it is.
+
+Modify your `return`:
+
+```tsx
+// Get the current date and time
+const now = new Date();
+const date = now.toLocaleDateString();
+const time = now.toLocaleTimeString();
+
+// Format them as a string
+const dateTime = `${date} ${time} UTC`;
+
+return NextResponse.json({ message: dateTime }, { status: 200 });
+```
+
+## Installing the Cast Action
+
+To install a cast action, you need to pick an [Octicon] from the list at the bottom of the [Cast Actions] doc. You also need to decide on a name for your action. Using those, you create a deeplink to Warpcast that will prompt the user to install your action. Currently, this replaces their existing action, but the capacity for multiple will be added soon.
+
+For example:
+
+```text
+https://warpcast.com/~/add-cast-action?actionType=post&name=Current+Time&icon=clock&postUrl=https%3A%2F%2Ftest-cast-actions.vercel.app%2Fapi%2Faction
+```
+
+The [Cast Actions Playground] has a nice tool to build this link for you!
+
+### From a Web Page
+
+To enable your users to install your action from a web page, simply include the link. Update the root of your site to do so:
+
+```tsx
+export default function Page() {
+ return (
+ <>
+
+ >
+ );
+}
+```
+
+
+
+### From a Frame
+
+Finally, update the frame on your root page to also have the install link:
+
+```tsx
+const frameMetadata = getFrameMetadata({
+ buttons: [
+ {
+ action: 'link',
+ label: 'Add Current Time Action',
+ target:
+ 'https://warpcast.com/~/add-cast-action?actionType=post&name=Current+Time&icon=clock&postUrl=https%3A%2F%2Ftest-cast-actions.vercel.app%2Fapi%2Faction',
+ },
+ ],
+ image: {
+ src: `${NEXT_PUBLIC_URL}/park-3.png`,
+ aspectRatio: '1:1',
+ },
+});
+```
+
+## Conclusion
+
+In this tutorial, you created a [Farcaster] [Cast Action] that allows your users to get the current date and time from a cast. You also learned where and how you can add further functionality to your action. Finally, you created a link and a frame to allow your users to install your cast action.
+
+Watch the [Base Channel] to stay up-to-date on new developments!
+
+[Octicon]: https://primer.style/foundations/icons/
+[Cast Actions Playground]: https://warpcast.com/~/developers/cast-actions?icon=home&name=Random+City+Name&postUrl=https%3A%2F%2F660b36e4ccda4cbc75dc8ec2.mockapi.io%2Fcity
+[Cast Action]: https://warpcast.notion.site/Spec-Farcaster-Actions-84d5a85d479a43139ea883f6823d8caa
+[Farcaster]: https://www.farcaster.xyz/
+[a-frame-in-100-lines]: https://github.com/Zizzamia/a-frame-in-100-lines
+[OnchainKit]: https://onchainkit.xyz/?utm_source=basedocs&utm_medium=tutorials&campaign=farcaster-cast-actions-simple
+[Vercel]: https://vercel.com
+[Frame Validator]: https://warpcast.com/~/developers/frames
+[Base channel]: https://warpcast.com/~/channel/base
+[deploying with Vercel]: /tutorials/farcaster-frames-deploy-to-vercel
+[Frame]: https://docs.farcaster.xyz/learn/what-is-farcaster/frames
+[Frames]: https://docs.farcaster.xyz/learn/what-is-farcaster/frames
+[Cast Actions]: https://warpcast.notion.site/Spec-Farcaster-Actions-84d5a85d479a43139ea883f6823d8caa
+
diff --git a/docs/pages/cookbook/use-case-guides/commerce/build-an-ecommerce-app.mdx b/_pages/cookbook/use-case-guides/commerce/build-an-ecommerce-app.mdx
similarity index 99%
rename from docs/pages/cookbook/use-case-guides/commerce/build-an-ecommerce-app.mdx
rename to _pages/cookbook/use-case-guides/commerce/build-an-ecommerce-app.mdx
index cb8b9c5f..56fa0950 100644
--- a/docs/pages/cookbook/use-case-guides/commerce/build-an-ecommerce-app.mdx
+++ b/_pages/cookbook/use-case-guides/commerce/build-an-ecommerce-app.mdx
@@ -148,10 +148,12 @@ For visual appeal, add an image of your product to the `/public` folder. This im
```
-:::tip[use conditional rendering]
+
+**use conditional rendering**
When setting up the payment component, it's important to implement conditional rendering. This ensures that the payment button only appears once the user's wallet is connected. This approach provides a smoother user experience and prevents potential errors from attempting to initiate a payment before a wallet is available.
-:::
+
+
Finally, configure the Pay component within your JSX. Wrap the `PayButton` and `PayStatus` components inside the `Pay` component, passing your `productId` as a prop to the `Pay` component. Set the `coinbaseBranded` prop on the `PayButton` to true for consistent branding. This setup creates a complete payment flow, allowing users to initiate a payment and view its status all within your application.
diff --git a/docs/pages/cookbook/use-case-guides/commerce/deploy-a-shopify-storefront.mdx b/_pages/cookbook/use-case-guides/commerce/deploy-a-shopify-storefront.mdx
similarity index 97%
rename from docs/pages/cookbook/use-case-guides/commerce/deploy-a-shopify-storefront.mdx
rename to _pages/cookbook/use-case-guides/commerce/deploy-a-shopify-storefront.mdx
index c3bac0a1..ff56cfc9 100644
--- a/docs/pages/cookbook/use-case-guides/commerce/deploy-a-shopify-storefront.mdx
+++ b/_pages/cookbook/use-case-guides/commerce/deploy-a-shopify-storefront.mdx
@@ -67,11 +67,12 @@ Click `Install` and you should be redirected to Coinbase Commerce with a prompt
This tutorial will guide you through the steps to create a new Hydrogen storefront for your Shopify store. This will allow you to showcase the products you already have in your Shopify account. Make sure you've already created your products by following the [Shopify products guide](https://help.shopify.com/en/manual/online-sales-channels/shop/products-and-collections).
-:::warning[Shopify Basic Plan Required]
+
+**Shopify Basic Plan Required**
To access the Hydrogen and Oxygen APIs, Shopify requires users to have at least a Basic Plan. The following steps will not work without this plan configured.
+
-:::
To get started, clone a Shopify demo store using the Hydrogen framework. This will give us a quick setup to work with.
@@ -92,11 +93,12 @@ Next, open a new terminal. You'll need to link your Hydrogen project to your Sho
npx shopify hydrogen link
```
-:::info[Install Hydrogen sales channel]
+
+**Install Hydrogen sales channel**
You will need to create access tokens for your own Shopify store. This is done by installing the [Hydrogen sales channel](https://apps.shopify.com/hydrogen?shpxid=4c8ddf03-1A48-4F61-D565-FB8DC4E5A4A0), which includes built-in support for Oxygen, Shopify's global edge hosting platform.
+
-:::
The Shopify quickstart comes with a Mock Shop as a template. To ensure your storefront is configured with your products, update the project environment variables.
@@ -106,13 +108,14 @@ The following code pulls the necessary settings from your Shopify account into t
npx shopify hydrogen env pull
```
-:::note[Not seeing your products?]
+
+**Not seeing your products?**
Your Shopify store should have products.
Visit [Shopify products guide](https://help.shopify.com/en/manual/online-sales-channels/shop/products-and-collections) for more details.
+
-:::
Now, verify that everything is set up correctly and your site is running. Start the development server again:
@@ -137,11 +140,12 @@ You've successfully created and deployed your Hydrogen storefront.
Visit your new storefront and add an item to your cart. Proceed to view your cart and then proceed to checkout. On the payment screen, you should see Coinbase Commerce automatically appear as an additional payment method alongside your existing payment options.
-:::info[Not seeing a crypto payment option?]
+
+**Not seeing a crypto payment option?**
Remember to link "activate" your Coinbase Commerce plugin.
+
-:::
## Conclusion
diff --git a/docs/pages/cookbook/use-case-guides/create-email-campaigns.mdx b/_pages/cookbook/use-case-guides/create-email-campaigns.mdx
similarity index 99%
rename from docs/pages/cookbook/use-case-guides/create-email-campaigns.mdx
rename to _pages/cookbook/use-case-guides/create-email-campaigns.mdx
index 47000d46..3acff5b0 100644
--- a/docs/pages/cookbook/use-case-guides/create-email-campaigns.mdx
+++ b/_pages/cookbook/use-case-guides/create-email-campaigns.mdx
@@ -41,11 +41,12 @@ This project will house your integration settings and project-specific credentia
You will now set up your development environment.
-:::tip[Integrating Resend to an existing project?]
+
+**Integrating Resend to an existing project?**
If you’re planning to integrate Resend into an existing project, feel free to skip ahead to the backend section where we’ll create custom API routes for interacting with Resend.
+
-:::
To begin, you’ll need to fork the [OnchainKit App template] from GitHub by clicking the green `Use this template` button. This template provides a solid foundation for building onchain applications and will be used as the base for our demo.
@@ -74,11 +75,12 @@ The OnchainKit template uses [Bun] as the package manager. If you don’t have B
bun curl -fsSL | bash
```
-:::tip[Is Bun Installed?]
+
+**Is Bun Installed?**
After installation, you may need to restart your terminal or run source ~/.bashrc (or ~/.zshrc) to ensure Bun is recognized as a command.
+
-:::
Next, install the Resend package to handle email campaign functionality within your app:
@@ -105,11 +107,10 @@ RESEND_API_KEY="YOUR_RESEND_API_KEY"
RESEND_AUDIENCE_ID="YOUR_RESEND_AUDIENCE_ID"
```
-:::note
-
+
Make sure to replace the placeholder values (YOUR_COINBASE_API_KEY, etc.) with your actual keys.
+
-:::
## Deploy template to Vercel
@@ -125,11 +126,12 @@ In the next step, Vercel will prompt you to import a Git repository. Click on Im
This step connects your GitHub (or other Git provider) account with Vercel, allowing Vercel to pull the code from your repository.
-:::note[Private Repos]
+
+**Private Repos**
If your project is private, you’ll see an option to Configure GitHub App. Click this button to give Vercel the necessary permissions to access your private repository. Follow the prompts to complete the authorization process.
+
-:::
After Vercel has access to your repository, you’ll be guided through the final deployment steps. Vercel will automatically detect the settings for your project, but you may want to double-check that everything is correct, such as the project name and deployment settings.
diff --git a/docs/pages/cookbook/use-case-guides/creator/convert-farcaster-frame-to-open-frame.mdx b/_pages/cookbook/use-case-guides/creator/convert-farcaster-frame-to-open-frame.mdx
similarity index 98%
rename from docs/pages/cookbook/use-case-guides/creator/convert-farcaster-frame-to-open-frame.mdx
rename to _pages/cookbook/use-case-guides/creator/convert-farcaster-frame-to-open-frame.mdx
index ad9ecdb7..b1c11937 100644
--- a/docs/pages/cookbook/use-case-guides/creator/convert-farcaster-frame-to-open-frame.mdx
+++ b/_pages/cookbook/use-case-guides/creator/convert-farcaster-frame-to-open-frame.mdx
@@ -24,12 +24,14 @@ To convert your Farcaster Frame to an Open Frame, we need to make several change
3. Adjust button configurations
4. Remove Farcaster-specific validations
-:::tip[Your Frame can support Farcaster and Open Frames]
+
+**Your Frame can support Farcaster and Open Frames**
If you want to have support for Open Frames and Farcaster Frames, create a new route that is specific to the protocol you want.
For example `src/app/frame-fc` is the route for farcaster frames and `src/app/frame-of` will render frames using the open frame spec.
-:::
+
+
Let's go through each of these changes step by step.
@@ -130,11 +132,10 @@ if (!text) {
// Process the request...
```
-:::note
-
+
When converting from a Farcaster Frame to an Open Frame, the message validation process changes significantly. Farcaster Frames use [signed messages] to ensure authenticity, while Open Frames don't require this validation. You should be aware of this trade-off and implement additional security measures if needed.
+
-:::
## Adjust the Button Configurations
@@ -270,13 +271,15 @@ Repeat this process for any of the additional routes you may want to convert fro
Before redeploying your Frame check how the routes differ using the [Frame Debugger]. The frame debugger should be used to validate that your frame follows the Openframe protocol spec.
-:::tip[use `Anonymous`]
+
+**use `Anonymous`**
+
Frame Dugger allows you to select the protocol you wish to debug.
For Open Frame, use the `anonymous (openframes)` option

+
-:::
### Before:
diff --git a/docs/pages/cookbook/use-case-guides/creator/nft-minting-with-zora.mdx b/_pages/cookbook/use-case-guides/creator/nft-minting-with-zora.mdx
similarity index 99%
rename from docs/pages/cookbook/use-case-guides/creator/nft-minting-with-zora.mdx
rename to _pages/cookbook/use-case-guides/creator/nft-minting-with-zora.mdx
index 75799cb3..d5139c00 100644
--- a/docs/pages/cookbook/use-case-guides/creator/nft-minting-with-zora.mdx
+++ b/_pages/cookbook/use-case-guides/creator/nft-minting-with-zora.mdx
@@ -92,11 +92,10 @@ You can also delete the files for the components you removed. You still need `On
### Creating the Premint from the App
-:::info
-
+
Gas on Base is currently inexpensive enough that we're doing this tutorial on the live network! Zora supports Base Sepolia as well, so feel free to use that instead.
+
-:::
Open `src/app/wagmi.ts` and convert the references of `baseSepolia` to `base`.
diff --git a/_pages/cookbook/use-case-guides/deploy-to-vercel.mdx b/_pages/cookbook/use-case-guides/deploy-to-vercel.mdx
new file mode 100644
index 00000000..bb60a484
--- /dev/null
+++ b/_pages/cookbook/use-case-guides/deploy-to-vercel.mdx
@@ -0,0 +1,156 @@
+---
+title: 'Farcaster Frames: Deploying to Vercel'
+slug: /farcaster-frames-deploy-to-vercel
+description: A tutorial that teaches how to deploy a Farcaster Frame using Vercel.
+author: briandoyle81
+---
+
+# Farcaster Frames: Deploying to Vercel
+
+To share your [Frames] on [Farcaster], you must first deploy them to the web. Farcaster reads the metadata from your site to build the Frame as it initially appears to the user. In this tutorial, you'll learn how to deploy the frame in the [OnchainKit] example - [a-frame-in-100-lines].
+
+## Objectives
+
+By the end of this tutorial, you should be able to:
+
+- Deploy a [Farcaster] Frame with [Vercel]
+- Use CI/CD to automatically deploy to Vercel when you update your `main` or `master` branch
+- Find and review logs and error messages for your deployed Frame
+
+## Prerequisites
+
+### Onchain App Development
+
+You'll need to be comfortable building onchain apps.
+
+### GitHub
+
+You'll need a [GitHub] account and should be comfortable pushing code to it. If you're using another solution for managing your code, you should be able to adapt this tutorial to your use case.
+
+### Farcaster
+
+You must have a [Farcaster] account with a connected wallet. Check out the [Base channel] to stay in the loop when we release tutorials like this!
+
+## Setup and Testing the Template
+
+Start by creating a new repo using [a-frame-in-100-lines] as a template.
+
+Run `yarn install`, then `yarn dev`.
+
+In the browser, open `http://localhost:3000/`. All you'll see is a heading with _Zizzamia.xyz_. This is expected!
+
+Open `app/page.tsx`. Here, you'll find the initial setup of the metadata that Farcaster reads to create the frame, as well as the simple page you just viewed. Change the `` to be your name.
+
+```tsx
+export default function Page() {
+ return (
+ <>
+ YOUR NAME HERE
+ >
+ );
+}
+```
+
+Change the `label` of the first button in `buttons` to be something you'll recognize as well. This can be your name, your pet's, or anything you like!
+
+```tsx
+buttons: [
+ {
+ label: 'YOUR NAME HERE',
+ },
+ {
+ action: 'tx',
+ label: 'Send Base Sepolia',
+ target: `${NEXT_PUBLIC_URL}/api/tx`,
+ postUrl: `${NEXT_PUBLIC_URL}/api/tx-success`,
+ },
+],
+```
+
+## Setting up Vercel
+
+Navigate to [Vercel]. Create an account if you need to, then sign in. You'll want to use your social login from [GitHub], GitLab, or BitBucket.
+
+You should see something like this:
+
+
+
+Click the `Install` button to install the Vercel app in your Github organization. You'll need to select the appropriate choice for your organization between `All repositories` and `Only selected repositories`.
+
+All is more convenient, but gives a (well respected) third party more access than is required. For this tutorial, we're assuming that you've chosen to go with minimum necessary access. Click the `Select repositories` dropdown, and pick the repo for your Frame.
+
+Click `Install`.
+
+You should see the `Import Git Repository` screen:
+
+
+
+
+If you've come back to Vercel after a few months, it may be unclear how to add more repos. To do so, use the `Add New...` dropdown on the projects page to get to the screen above, then click `Adjust GitHub App Permissions ->`. Scroll down in the popup window to find your list of repos to add.
+
+
+
+
+
+Click the button, then configure your project. The [a-frame-in-100-lines] example uses `Next.js`, which is made by the same people as Vercel, so the default settings are fine. Pick a new name if you'd like, then click `Deploy`.
+
+
+You won't have been the first person to name a project `a-frame-in-100-lines`, so Vercel will adjust the name for you for the file path, if you gave your template copy the same name. Don't be confused when your changes don't show up at `a-frame-in-100-lines.vercel.app`, that one isn't yours!
+
+
+
+After the deploy completes, click `Continue to Dashboard`.
+
+On this screen, you'll see:
+
+- A _Deployment_ link to this specific deployment. These persist, so you can always go back to compare before and after if you need to
+- A _Domains_ link that goes to the main domain for your project. This will always link to your most recent deploy
+- The _Status_ of your deploy, indicating, success, failure, or in process for each build
+- A preview of the front page of your site
+
+There are also a number of useful tabs. You should eventually check them all out, but for now inspect:
+
+- _Project_: You are here
+- _Deployments_: A list of the current, and all prior deployments. You'll use this tab to monitor new deployments as they build, or to revert to an old one if you need to
+- _Logs_: Any `console.log` or `console.error` logs you put in your code will appear here. Note that there may be a few seconds delay
+- _Settings_: Among other things, this is where you can set your environment variables. If it works locally but not deployed, this is a good place to review!
+
+## CI/CD with Vercel
+
+By default, Vercel sets up CI/CD, so whenever you push a change to your main branch, it will automatically redeploy!
+
+Make a trivial change to your repo, commit it, and push it. When you do so, you'll see a new build in the _Deployments_ tab:
+
+
+
+If it fails, you'll get a fairly comprehensive log explaining what happened. Otherwise, it will automatically deploy!
+
+## Manual Deploys
+
+If you want, you can disable CI/CD and set up manual deploys. One convenient way to do this is to set up a [one click deploy button].
+
+## Testing the Cast
+
+Open the [Frame Validator] and paste in your link. Viewing a frame from the validator will re-cache it for **future** casts, but it **will not** change any existing casts of the frame.
+
+Click `Load`. You should see:
+
+
+
+Except it will have the text that you edited!
+
+## Conclusion
+
+In this tutorial, you learned how to deploy a frame with [Vercel] and take advantage of CI/CD.
+
+[OnchainKit]: https://onchainkit.xyz/?utm_source=basedocs&utm_medium=tutorials&campaign=farcaster-frames-deploy-to-vercel
+[Base Learn]: https://docs.base.org/learn/welcome
+[Farcaster]: https://www.farcaster.xyz/
+[a-frame-in-100-lines]: https://github.com/Zizzamia/a-frame-in-100-lines
+[Vercel]: https://vercel.com
+[Frame Validator]: https://warpcast.com/~/developers/frames
+[Base channel]: https://warpcast.com/~/channel/base
+[Frames]: https://warpcast.notion.site/Farcaster-Frames-4bd47fe97dc74a42a48d3a234636d8c5
+[GitHub]: https://github.com
+[one click deploy button]: https://vercel.com/blog/deploy-button
+
diff --git a/_pages/cookbook/use-case-guides/finance/access-real-time-asset-data-pyth-price-feeds.mdx b/_pages/cookbook/use-case-guides/finance/access-real-time-asset-data-pyth-price-feeds.mdx
new file mode 100644
index 00000000..89b038eb
--- /dev/null
+++ b/_pages/cookbook/use-case-guides/finance/access-real-time-asset-data-pyth-price-feeds.mdx
@@ -0,0 +1,248 @@
+---
+title: Accessing real-time asset data using Pyth Price Feeds
+slug: /oracles-pyth-price-feeds
+description: A tutorial that teaches how to use Pyth Price Feeds to access real-time asset data, directly from your smart contracts on the Base testnet.
+author: taycaldwell
+---
+
+# Accessing real-time asset data using Pyth Price Feeds
+
+This tutorial will guide you through the process of creating a smart contract on Base that utilizes Pyth Network oracles to consume a price feed.
+
+## Objectives
+
+By the end of this tutorial you should be able to do the following:
+
+- Set up a smart contract project for Base using Foundry
+- Install the Pyth smart contracts
+- Consume a Pyth Network price feed within your smart contract
+- Deploy and test your smart contracts on Base
+
+## Prerequisites
+
+### Foundry
+
+This tutorial requires you to have Foundry installed.
+
+- From the command-line (terminal), run: `curl -L https://foundry.paradigm.xyz | bash`
+- Then run `foundryup`, to install the latest (nightly) build of Foundry
+
+For more information, see the Foundry Book [installation guide](https://book.getfoundry.sh/getting-started/installation).
+
+### Coinbase Wallet
+
+In order to deploy a smart contract, you will first need a wallet. You can create a wallet by downloading the Coinbase Wallet browser extension.
+
+- Download [Coinbase Wallet](https://chrome.google.com/webstore/detail/coinbase-wallet-extension/hnfanknocfeofbddgcijnmhnfnkdnaad?hl=en)
+
+### Wallet funds
+
+Deploying contracts to the blockchain requires a gas fee. Therefore, you will need to fund your wallet with ETH to cover those gas fees.
+
+For this tutorial, you will be deploying a contract to the Base Sepolia test network. You can fund your wallet with Base Sepolia ETH using one of the faucets listed on the Base [Network Faucets](https://docs.base.org/chain/network-faucets) page.
+
+## What is Pyth Network?
+
+**Pyth Network** focuses on ultra-low latency and real-time data, making it suitable for financial applications that require sub-second updates. Pyth's design emphasizes performance, and it is designed to provide data for a range of traditional and DeFi assets.
+
+## Creating a project
+
+Before you can begin writing smart contracts for Base and consuming Pyth price feeds, you need to set up your development environment by creating a Foundry project.
+
+To create a new Foundry project, first create a new directory:
+
+```bash
+mkdir myproject
+```
+
+Then run:
+
+```bash
+cd myproject
+forge init
+```
+
+This will create a Foundry project, which has the following basic layout:
+
+```bash
+.
+├── foundry.toml
+├── script
+ │ └── Counter.s.sol
+├── src
+ │ └── Counter.sol
+└── test
+ └── Counter.t.sol
+```
+
+## Installing Pyth smart contracts
+
+To use Pyth price feeds within your project, you need to install Pyth oracle contracts as a project dependency using `forge install`.
+
+To install Pyth oracle contracts, run:
+
+```bash
+forge install pyth-network/pyth-sdk-solidity@v2.2.0 --no-git --no-commit
+```
+
+Once installed, update your `foundry.toml` file by appending the following line:
+
+```bash
+remappings = ['@pythnetwork/pyth-sdk-solidity/=lib/pyth-sdk-solidity']
+```
+
+## Writing and compiling the Smart Contract
+
+Once your project has been created and dependencies have been installed, you can now start writing a smart contract.
+
+The Solidity code below defines a smart contract named `ExampleContract`. The code uses the `IPyth` interface from the [Pyth Solidity SDK](https://github.com/pyth-network/pyth-crosschain/tree/main/target_chains/ethereum/sdk/solidity).
+
+An instance of`IPyth` is defined within the contract that provides functions for consuming Pyth price feeds. The constructor for the `IPyth` interface expects a contract address to be provided. This address provided in the code example below (`0xA2aa501b19aff244D90cc15a4Cf739D2725B5729`) corresponds to the Pyth contract address for the Base Sepolia testnet.
+
+
+Pyth also supports other EVM networks, such as Base Mainnet. For a list of all network contract addresses, visit the [Pyth documentation](https://docs.pyth.network/documentation/pythnet-price-feeds/evm).
+
+
+
+The contract also contains a function named `getLatestPrice`. This function takes a provided `priceUpdateData` that is used to get updated price data, and returns the price given a `priceId` of a price feed. The smart contract provided below uses a `priceId` of `0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace`, which corresponds to the price feed for `ETH / USD`.
+
+
+Pyth provides a number of price feeds. For a list of available price feeds, visit the [Pyth documentation](https://pyth.network/developers/price-feed-ids#pyth-evm-stable).
+
+
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "@pythnetwork/pyth-sdk-solidity/IPyth.sol";
+import "@pythnetwork/pyth-sdk-solidity/PythStructs.sol";
+
+contract ExampleContract {
+ IPyth pyth;
+
+ /**
+ * Network: Base Sepolia (testnet)
+ * Address: 0xA2aa501b19aff244D90cc15a4Cf739D2725B5729
+ */
+ constructor() {
+ pyth = IPyth(0xA2aa501b19aff244D90cc15a4Cf739D2725B5729);
+ }
+
+ function getLatestPrice(
+ bytes[] calldata priceUpdateData
+ ) public payable returns (PythStructs.Price memory) {
+ // Update the prices to the latest available values and pay the required fee for it. The `priceUpdateData` data
+ // should be retrieved from our off-chain Price Service API using the `pyth-evm-js` package.
+ // See section "How Pyth Works on EVM Chains" below for more information.
+ uint fee = pyth.getUpdateFee(priceUpdateData);
+ pyth.updatePriceFeeds{ value: fee }(priceUpdateData);
+
+ bytes32 priceID = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace;
+ // Read the current value of priceID, aborting the transaction if the price has not been updated recently.
+ // Every chain has a default recency threshold which can be retrieved by calling the getValidTimePeriod() function on the contract.
+ // Please see IPyth.sol for variants of this function that support configurable recency thresholds and other useful features.
+ return pyth.getPrice(priceID);
+ }
+}
+```
+
+In your project, add the code provided above to a new file named `src/ExampleContract.sol` and delete the `src/Counter.sol` contract that was generated with the project (You can also delete the `test/Counter.t.sol` and `script/Counter.s.sol` files).
+
+To compile the new smart contract, run:
+
+```bash
+forge build
+```
+
+## Deploying the smart contract
+
+### Setting up your wallet as the deployer
+
+Before you can deploy your smart contract to the Base network, you will need to set up a wallet to be used as the deployer.
+
+To do so, you can use the [`cast wallet import`](https://book.getfoundry.sh/reference/cast/cast-wallet-import) command to import the private key of the wallet into Foundry's securely encrypted keystore:
+
+```bash
+cast wallet import deployer --interactive
+```
+
+After running the command above, you will be prompted to enter your private key, as well as a password for signing transactions.
+
+
+For instructions on how to get your private key from Coinbase Wallet, visit the [Coinbase Wallet documentation](https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings#show-private-key).
+
+**It is critical that you do NOT commit this to a public repo**.
+
+
+
+To confirm that the wallet was imported as the `deployer` account in your Foundry project, run:
+
+```bash
+cast wallet list
+```
+
+### Setting up environment variables for Base Sepolia
+
+To setup your environment for deploying to the Base network, create an `.env` file in the home directory of your project, and add the RPC URL for the Base Sepolia testnet:
+
+```
+BASE_SEPOLIA_RPC="https://sepolia.base.org"
+```
+
+Once the `.env` file has been created, run the following command to load the environment variables in the current command line session:
+
+```bash
+source .env
+```
+
+### Deploying the smart contract to Base Sepolia
+
+With your contract compiled and environment setup, you are ready to deploy the smart contract to the Base Sepolia Testnet!
+
+For deploying a single smart contract using Foundry, you can use the `forge create` command. The command requires you to specify the smart contract you want to deploy, an RPC URL of the network you want to deploy to, and the account you want to deploy with.
+
+To deploy the `ExampleContract` smart contract to the Base Sepolia test network, run the following command:
+
+```bash
+forge create ./src/ExampleContract.sol:ExampleContract --rpc-url $BASE_SEPOLIA_RPC --account deployer
+```
+
+When prompted, enter the password that you set earlier, when you imported your wallet's private key.
+
+
+Your wallet must be funded with ETH on the Base Sepolia Testnet to cover the gas fees associated with the smart contract deployment. Otherwise, the deployment will fail.
+
+To get testnet ETH for Base Sepolia, see the [prerequisites](#prerequisites).
+
+
+
+After running the command above, the contract will be deployed on the Base Sepolia test network. You can view the deployment status and contract by using a [block explorer](/chain/block-explorers).
+
+## Interacting with the Smart Contract
+
+The `getLatestPrice(bytes[])` function of the deployed contract takes a `priceUpdateData` argument that is used to get the latest price. This data can be fetched using the Hermes web service. Hermes allows users to easily query for recent price updates via a REST API. Make a curl request to fetch the `priceUpdateData` the `priceId`, `0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace`:
+
+```
+curl https://hermes.pyth.network/api/latest_vaas?ids[]=0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace
+```
+
+Once you have the `priceUpdateData`, you can use Foundry's `cast` command-line tool to interact with the smart contract and call the `getLatestPrice(bytes[])` function to fetch the latest price of ETH.
+
+To call the `getLatestPrice(bytes[])` function of the smart contract, run the following command, replacing `` with the address of your deployed contract, and `` with the `priceUpdateData` returned by the Hermes endpoint:
+
+```bash
+cast call --rpc-url $BASE_SEPOLIA_RPC "getLatestPrice(bytes[])"
+```
+
+You should receive the latest `ETH / USD` price in hexadecimal form.
+
+## Conclusion
+
+Congratulations! You have successfully deployed and interacted with a smart contract that consumes a Pyth Network oracle to access a real-time price feed on Base.
+
+To learn more about Oracles and using Pyth Network price feeds within your smart contracts on Base, check out the following resources:
+
+- [Oracles](https://docs.base.org/chain/oracles)
+- [Pyth Network Price Feeds](https://docs.pyth.network/documentation/pythnet-price-feeds/evm)
+
diff --git a/_pages/cookbook/use-case-guides/finance/access-real-world-data-chainlink.mdx b/_pages/cookbook/use-case-guides/finance/access-real-world-data-chainlink.mdx
new file mode 100644
index 00000000..63fa89e6
--- /dev/null
+++ b/_pages/cookbook/use-case-guides/finance/access-real-world-data-chainlink.mdx
@@ -0,0 +1,232 @@
+---
+title: Accessing real-world data using Chainlink Data Feeds
+slug: /oracles-chainlink-price-feeds
+description: A tutorial that teaches how to use Chainlink Data Feeds to access real-world data, such as asset prices, directly from your smart contracts on the Base testnet.
+author: taycaldwell
+---
+
+# Accessing real-world data using Chainlink Data Feeds
+
+This tutorial will guide you through the process of creating a smart contract on Base that utilizes Chainlink Data Feeds to access real-world data, such as asset prices, directly from your smart contracts.
+
+## Objectives
+
+By the end of this tutorial you should be able to do the following:
+
+- Set up a smart contract project for Base using Foundry
+- Install the Chainlink smart contracts
+- Consume a Chainlink price data feed within your smart contract
+- Deploy and test your smart contracts on Base
+
+## Prerequisites
+
+### Foundry
+
+This tutorial requires you to have Foundry installed.
+
+- From the command-line (terminal), run: `curl -L https://foundry.paradigm.xyz | bash`
+- Then run `foundryup`, to install the latest (nightly) build of Foundry
+
+For more information, see the Foundry Book [installation guide](https://book.getfoundry.sh/getting-started/installation).
+
+### Coinbase Wallet
+
+In order to deploy a smart contract, you will first need a wallet. You can create a wallet by downloading the Coinbase Wallet browser extension.
+
+- Download [Coinbase Wallet](https://chrome.google.com/webstore/detail/coinbase-wallet-extension/hnfanknocfeofbddgcijnmhnfnkdnaad?hl=en)
+
+### Wallet funds
+
+Deploying contracts to the blockchain requires a gas fee. Therefore, you will need to fund your wallet with ETH to cover those gas fees.
+
+For this tutorial, you will be deploying a contract to the Base Goerli test network. You can fund your wallet with Base Goerli ETH using one of the faucets listed on the Base [Network Faucets](https://docs.base.org/chain/network-faucets) page.
+
+## What are Chainlink Data Feeds?
+
+Accurate price data is essential in DeFi applications. However, blockchain networks lack the capability to directly fetch external real-world data, leading to the "[Oracle Problem](https://chain.link/education-hub/oracle-problem)".
+
+Chainlink Data Feeds offer a solution to this problem by serving as a secure middleware layer that bridges the gap between real-world asset prices and onchain smart contracts.
+
+## Creating a project
+
+Before you can begin writing smart contracts for Base and consuming Chainlink data feeds, you need to set up your development environment by creating a Foundry project.
+
+To create a new Foundry project, first create a new directory:
+
+```bash
+mkdir myproject
+```
+
+Then run:
+
+```bash
+cd myproject
+forge init
+```
+
+This will create a Foundry project, which has the following basic layout:
+
+```bash
+.
+├── foundry.toml
+├── script
+ │ └── Counter.s.sol
+├── src
+ │ └── Counter.sol
+└── test
+ └── Counter.t.sol
+```
+
+## Installing Chainlink smart contracts
+
+To use Chainlink's data feeds within your project, you need to install Chainlink smart contracts as a project dependency using `forge install`.
+
+To install Chainlink smart contracts, run:
+
+```bash
+forge install smartcontractkit/chainlink --no-commit
+```
+
+Once installed, update your `foundry.toml` file by appending the following line:
+
+```bash
+remappings = ['@chainlink/contracts/=lib/chainlink/contracts']
+```
+
+## Writing and compiling the Smart Contract
+
+Once your project has been created and dependencies have been installed, you can now start writing a smart contract.
+
+The Solidity code below defines a smart contract named `DataConsumerV3`. The code uses the `AggregatorV3Interface` interface from the [Chainlink contracts library](https://docs.chain.link/data-feeds/api-reference#aggregatorv3interface) to provide access to price feed data.
+
+The smart contract passes an address to `AggregatorV3Interface`. This address (`0xcD2A119bD1F7DF95d706DE6F2057fDD45A0503E2`) corresponds to the `ETH/USD` price feed on the Base Goerli network.
+
+
+Chainlink provides a number of price feeds for Base. For a list of available price feeds on Base, visit the [Chainlink documentation](https://docs.chain.link/data-feeds/price-feeds/addresses/?network=base&page=1).
+
+
+
+```solidity
+ // SPDX-License-Identifier: MIT
+ pragma solidity ^0.8.0;
+
+ import "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
+
+ contract DataConsumerV3 {
+ AggregatorV3Interface internal priceFeed;
+
+ /**
+ * Network: Base Goerli
+ * Aggregator: ETH/USD
+ * Address: 0xcD2A119bD1F7DF95d706DE6F2057fDD45A0503E2
+ */
+ constructor() {
+ priceFeed = AggregatorV3Interface(0xcD2A119bD1F7DF95d706DE6F2057fDD45A0503E2);
+ }
+
+ function getLatestPrice() public view returns (int) {
+ (
+ /* uint80 roundID */,
+ int price,
+ /* uint startedAt */,
+ /* uint timeStamp */,
+ /* uint80 answeredInRound */
+ ) = priceFeed.latestRoundData();
+ return price;
+ }
+ }
+```
+
+In your project, add the code provided above to a new file named `src/DataConsumerV3.sol`, and delete the `src/Counter.sol` contract that was generated with the project. (you can also delete the `test/Counter.t.sol` and `script/Counter.s.sol` files).
+
+To compile the new smart contract, run:
+
+```bash
+forge build
+```
+
+## Deploying the smart contract
+
+### Setting up your wallet as the deployer
+
+Before you can deploy your smart contract to the Base network, you will need to set up a wallet to be used as the deployer.
+
+To do so, you can use the [`cast wallet import`](https://book.getfoundry.sh/reference/cast/cast-wallet-import) command to import the private key of the wallet into Foundry's securely encrypted keystore:
+
+```bash
+cast wallet import deployer --interactive
+```
+
+After running the command above, you will be prompted to enter your private key, as well as a password for signing transactions.
+
+
+For instructions on how to get your private key from Coinbase Wallet, visit the [Coinbase Wallet documentation](https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings#show-private-key).
+
+**It is critical that you do NOT commit this to a public repo**.
+
+
+
+To confirm that the wallet was imported as the `deployer` account in your Foundry project, run:
+
+```bash
+cast wallet list
+```
+
+### Setting up environment variables for Base Goerli
+
+To setup your environment for deploying to the Base network, create an `.env` file in the home directory of your project, and add the RPC URL for the Base Goerli testnet:
+
+```
+BASE_GOERLI_RPC="https://goerli.base.org"
+```
+
+Once the `.env` file has been created, run the following command to load the environment variables in the current command line session:
+
+```bash
+source .env
+```
+
+### Deploying the smart contract to Base Goerli
+
+With your contract compiled and environment setup, you are ready to deploy the smart contract to the Base Goerli Testnet!
+
+For deploying a single smart contract using Foundry, you can use the `forge create` command. The command requires you to specify the smart contract you want to deploy, an RPC URL of the network you want to deploy to, and the account you want to deploy with.
+
+To deploy the `DataConsumerV3` smart contract to the Base Goerli test network, run the following command:
+
+```bash
+forge create ./src/DataConsumerV3.sol:DataConsumerV3 --rpc-url $BASE_GOERLI_RPC --account deployer
+```
+
+When prompted, enter the password that you set earlier, when you imported your wallet's private key.
+
+
+Your wallet must be funded with ETH on the Base Goerli Testnet to cover the gas fees associated with the smart contract deployment. Otherwise, the deployment will fail.
+
+To get testnet ETH for Base Goerli, see the [prerequisites](#prerequisites).
+
+
+
+After running the command above, the contract will be deployed on the Base Goerli test network. You can view the deployment status and contract by using a [block explorer](/chain/block-explorers).
+
+## Interacting with the Smart Contract
+
+Foundry provides the `cast` command-line tool that can be used to interact with the smart contract that was deployed and call the `getLatestPrice()` function to fetch the latest price of ETH.
+
+To call the `getLatestPrice()` function of the smart contract, run:
+
+```bash
+cast call --rpc-url $BASE_GOERLI_RPC "getLatestPrice()"
+```
+
+You should receive the latest `ETH / USD` price in hexadecimal form.
+
+## Conclusion
+
+Congratulations! You have successfully deployed and interacted with a smart contract that consumes a Chainlink price feed on the Base blockchain network.
+
+To learn more about Oracles and using Chainlink to access real-world data within your smart contracts on Base, check out the following resources:
+
+- [Oracles](https://docs.base.org/chain/oracles)
+- [Chainlink Data Feeds on Base](https://docs.chain.link/data-feeds/price-feeds/addresses?network=base&page=1&search=#networks)
+
diff --git a/_pages/cookbook/use-case-guides/finance/build-a-smart-wallet-funding-app.mdx b/_pages/cookbook/use-case-guides/finance/build-a-smart-wallet-funding-app.mdx
new file mode 100644
index 00000000..5a748ce2
--- /dev/null
+++ b/_pages/cookbook/use-case-guides/finance/build-a-smart-wallet-funding-app.mdx
@@ -0,0 +1,164 @@
+---
+title: Add an In-App Onramp with OnchainKit
+description: Learn how to create a app that detects if a smart wallet has ETH and prompts users to add funds if needed.
+authors: [hughescoin]
+---
+
+# Add an In-App Onramp with OnchainKit
+
+In this tutorial, you'll learn how to build an onchain app that checks a user's wallet balance and either allows them to mint an NFT or prompts them to add funds. We'll use the OnchainKit App Template as a starting point.
+
+## Objectives
+
+By the end of this tutorial you should be able to:
+
+- Set up a project using the Onchain Kit App Template
+- Configure the app for to onboard users easily using [Smart Wallets]
+- Implement balance checking and conditional rendering
+- Use the Fund component to allow users to add funds to their wallet
+
+## Prerequisites
+
+### React and TypeScript
+
+You should be familiar with React and TypeScript. If you're new to these technologies, consider reviewing their [official documentation] first.
+
+### OnchainKit
+
+This tutorial uses Coinbase's Onchain Kit. Familiarity with its basic concepts will be helpful.
+
+### Access to the Coinbase Developer Platform
+
+You'll need to set up an account on with [Coinbase Developer Platform (CDP) Account](https://www.coinbase.com/cloud). The CDP provides various tools and services for blockchain development, including access to API endpoints and other resources that will be instrumental in your project. Once you've created your account, you'll be ready to move forward with integrating these services into your application.
+
+
+**CDP Configurations**
+
+If you see a "something went wrong" error message when navigating to pay.coinbase.com, make sure you have "enforce secure initialization" disabled on the [Onramp config page] in Coinbase Developer Platform Dashboard.
+
+
+
+
+
+## Setting up the Project
+
+To get started, clone the Onchain Kit App Template by running
+
+```bash
+git clone git@github.com:coinbase/onchain-app-template.git
+```
+
+in your terminal, then navigate into the project directory with:
+
+```bash
+cd onchain-app-template
+```
+
+Next, install the necessary dependencies by executing `bun install` followed by `bun install viem`.
+
+After setting up the project, you'll need to configure your environment variables. Create a `.env` file in the root directory of your project and add the following line: `NEXT_PUBLIC_WC_PROJECT_ID=your_project_id_here`. Remember to replace 'your_project_id_here' with your actual project ID. Additionally, don't forget to configure your apiKey in the `src/app/components/OnchainProviders.tsx` file.
+
+## Configuring for Smart Wallets
+
+To make the app work only with smart wallets, modify `src/wagmi.ts`:
+
+```typescript
+// Inside the useWagmiConfig() function, before the useMemo() hook
+coinbaseWallet.preference = 'smartWalletOnly';
+```
+
+## Implementing Balance Checking
+
+Now well implement a check on the user's wallet to see if they have enough funds. Before we implement this check, let's create a helper function that grabs the user's Ethereum balance using [viem]. To do so, create a `utils.ts` file in the `src` directory that creates a client connected to Base and fetches the user's ETH balance:
+
+```typescript
+import { createPublicClient, http } from 'viem';
+import { base } from 'viem/chains';
+import type { GetBalanceParameters } from 'viem';
+
+const publicClient = createPublicClient({
+ transport: http(),
+ chain: base,
+});
+
+export async function getBalance(address: GetBalanceParameters) {
+ const balance = publicClient.getBalance(address);
+ return balance;
+}
+```
+
+Next, import the `getBalance()` function into your main component file (e.g., `src/app/page.tsx`). You will want to add a few [react hooks] to fetch the balance and store it as a state variable. Add the following lines of code to your `page.tsx` file:
+
+```typescript
+import { useState, useEffect } from 'react';
+import { getBalance } from '../utils';
+import { FundButton } from '@coinbase/onchainkit/fund';
+
+// Inside your component
+const [walletBalance, setWalletBalance] = useState('');
+
+useEffect(() => {
+ async function fetchBalance() {
+ if (address) {
+ const balance = await getBalance({ address });
+ setWalletBalance(String(balance));
+ }
+ }
+ fetchBalance();
+}, [address]);
+```
+
+## Implementing Conditional Rendering
+
+Now that we know the user's balance, we can then have them mint an NFT or prompt them to fund their wallet if they do not have enough ETH.
+
+The end state is to show their balance along with the appropriate call to actions like so:
+
+
+
+Update your component's return statement with the following code:
+
+```typescript
+return (
+
+ {/* ... other sections ... */}
+
+
+
+
+ Your Wallet has {walletBalance} ETH
+
+
+
+ {address ? (
+ parseFloat(walletBalance) > 0 ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ )}
+
+
+
+);
+```
+
+Sweet! Now our conditional rendering is in full force. If a user clicks on the `+ Add funds to transact` button they will be given three options for topping up their smart wallet:
+
+
+
+## Conclusion
+
+Congratulations! You've built a app that checks a user's smart wallet balance and provides appropriate options based on their funds.
+This app can serve as a foundation for more complex onchain applications that require users to have funded smart wallets.
+
+[Onchain Kit]: https://github.com/coinbase/onchainkit
+[Viem]: https://viem.sh/
+[Smart Wallets]: https://keys.coinbase.com/onboarding
+[viem]: https://viem.sh/docs/introduction
+[react hooks]: https://react.dev/reference/react/hooks
+[Onramp config page]: https://portal.cdp.coinbase.com/products/onramp
+[official documentation]: https://react.dev/
+
diff --git a/_pages/cookbook/use-case-guides/gating-and-redirects.mdx b/_pages/cookbook/use-case-guides/gating-and-redirects.mdx
new file mode 100644
index 00000000..5d188cb0
--- /dev/null
+++ b/_pages/cookbook/use-case-guides/gating-and-redirects.mdx
@@ -0,0 +1,250 @@
+---
+title: 'Farcaster Frames: Gating content and creating redirects'
+slug: /farcaster-frames-gating-and-redirects
+description: A tutorial that teaches how to create Frames with more advanced behaviors such as gating content based on a user's follows, likes, or recasts, and creating redirect buttons.
+author: briandoyle81
+---
+
+# Farcaster Frames: Gating content and creating redirects
+
+You can build complex and exciting [Frames] on [Farcaster] that meet the community's expectation for new and exciting things to do. [OnchainKit], and our [a-frame-in-100-lines] demo give you a number of tools to build complex interactions within a frame.
+
+## Objectives
+
+By the end of this tutorial, you should be able to:
+
+- Build a [Farcaster] Frame that gates content behind a requirement that the user follows, recasts, or likes you or your frame
+- Create frames that use the text field and extract it from the message
+- Construct redirect buttons in Frames
+- Create multiple paths within a Frame based on the button clicked by a user
+
+## Prerequisites
+
+### Onchain App Development
+
+You'll need to be comfortable building onchain apps.
+
+### Vercel
+
+You'll need to be comfortable deploying your app to [Vercel], or using another solution on your own. Check out our tutorial on [deploying with Vercel] if you need a refresher!
+
+### Farcaster
+
+You must have a [Farcaster] account with a connected wallet. Check out the [Base channel] to stay in the loop when we release tutorials like this!
+
+### Frames
+
+You should be comfortable with the basics of creating Farcaster [Frames]. If you aren't, check out our tutorial on [NFT Minting Frame].
+
+## Setting Up a Copy of A Frame in 100 Lines
+
+Create a new repo using [a-frame-in-100-lines] as a template.
+
+Add your new repo to Vercel and deploy. If you need a refresher, check out [deploying with Vercel]. Open the [Frame Validator] and test the current version of the template.
+
+
+
+The demo has examples of most of the features available for your frames.
+
+### Buttons and Images
+
+The core functionality of a frame is an image and at least 1 button. Buttons can do a number of things, including requesting a new frame to replace the current one, opening a transaction for the user to consider approving, or opening a link to a website.
+
+### Text Entry
+
+Optionally, frames can include a text entry field. Try it out! When you click the `Story Time!` button, the text will be preserved, and will appear in the button on the next frame.
+
+
+
+The text input field is created by adding the `input` property to `getFrameMetadata`.
+
+```tsx
+const frameMetadata = getFrameMetadata({
+ buttons: [
+ {
+ label: 'Story time',
+ },
+ {
+ action: 'tx',
+ label: 'Send Base Sepolia',
+ target: `${NEXT_PUBLIC_URL}/api/tx`,
+ postUrl: `${NEXT_PUBLIC_URL}/api/tx-success`,
+ },
+ ],
+ image: {
+ src: `${NEXT_PUBLIC_URL}/park-3.png`,
+ aspectRatio: '1:1',
+ },
+ input: {
+ text: 'Tell me a story',
+ },
+ postUrl: `${NEXT_PUBLIC_URL}/api/frame`,
+});
+```
+
+When the user enters the text, it gets included in the frame message. You can see how it is retrieved in `api/frame/route.ts`. The `getFrameMessageBody` function extracts the frame message from the request, and validates it. The returned `message` contains a number of useful properties that can be seen where it is defined in [OnchainKit], in `src/frame/types.ts`:
+
+```tsx
+export interface FrameValidationData {
+ address: string | null; // The connected wallet address of the interacting user.
+ button: number; // Number of the button clicked
+ following: boolean; // Indicates if the viewer clicking the frame follows the cast author
+ input: string; // Text input from the viewer typing in the frame
+ interactor: {
+ fid: number; // Viewer Farcaster ID
+ custody_address: string; // Viewer custody address
+ verified_accounts: string[]; // Viewer account addresses
+ verified_addresses: {
+ eth_addresses: string[] | null;
+ sol_addresses: string[] | null;
+ };
+ };
+ liked: boolean; // Indicates if the viewer clicking the frame liked the cast
+ raw: NeynarFrameValidationInternalModel;
+ recasted: boolean; // Indicates if the viewer clicking the frame recasted the cast
+ state: {
+ serialized: string; // Serialized state (e.g. JSON) passed to the frame server
+ };
+ transaction: {
+ hash: string;
+ } | null;
+ valid: boolean; // Indicates if the frame is valid
+}
+```
+
+The demo doesn't make use of the `input` property, but it does extract the text:
+
+```tsx
+//api/frames/route.ts
+
+// Extract the input:
+const text = message.input || '';
+```
+
+### Link Button
+
+You can now add outbound links to buttons. To do this with [OnchainKit], simply create a button with an `action` property of `link`, and a `target` of the desired url:
+
+```tsx
+{
+ action: 'link',
+ label: 'Link to Google',
+ target: 'https://www.google.com',
+},
+```
+
+### Redirect Button
+
+You can also do a redirect with a button. The example has one on the second frame - `Dog pictures ↗`:
+
+```tsx
+{
+ action: 'post_redirect',
+ label: 'Dog pictures',
+},
+```
+
+The way it works is a little tricky.
+
+Clicking this button hits the `postUrl`, with the requirement that you return a `redirect` response with a status of 302, and a link. You can see that in `route.ts`:
+
+```tsx
+if (message?.button === 3) {
+ return NextResponse.redirect(
+ 'https://www.google.com/search?q=cute+dog+pictures&tbm=isch&source=lnms',
+ { status: 302 },
+ );
+}
+```
+
+Try changing it to your favorite link!
+
+## Creating Gates
+
+The `message`, shown above, has properties that make it easy to design interactions based on whether or not the user interacting with the frame has liked or recast your post, or if they follow the original caster.
+
+!!!caution
+
+The community is evolving quickly and many people are fatigued with "Like, follow, recast" spam in their feeds. These are useful tools, but some consideration should be given for designing an experience for the current culture of the community you are trying to reach.
+
+!!!
+
+### Like Gate
+
+To require the user to "like" the cast before seeing images of puppies, simply modify the conditional for the redirect to include that check:
+
+```tsx
+if (message?.button === 3 && message?.liked) {
+ return NextResponse.redirect(
+ 'https://www.google.com/search?q=cute+dog+pictures&tbm=isch&source=lnms',
+ { status: 302 },
+ );
+}
+```
+
+If they haven't, return a version of the original frame with a new message in the third button. In doing so, you've created a loop with a behavior condition for the user to exit:
+
+```tsx
+if (message?.button === 3 && message.liked) {
+ return NextResponse.redirect(
+ 'https://www.google.com/search?q=cute+dog+pictures&tbm=isch&source=lnms',
+ { status: 302 },
+ );
+} else if (message?.button === 3) {
+ return new NextResponse(
+ getFrameHtmlResponse({
+ buttons: [
+ {
+ label: `State: ${state?.page || 0}`,
+ },
+ {
+ action: 'link',
+ label: 'OnchainKit',
+ target: 'https://onchainkit.xyz',
+ },
+ {
+ action: 'post_redirect',
+ label: 'Like to see doggos!',
+ },
+ ],
+ image: {
+ src: `${NEXT_PUBLIC_URL}/park-1.png`,
+ },
+ postUrl: `${NEXT_PUBLIC_URL}/api/frame`,
+ state: {
+ page: state?.page + 1,
+ time: new Date().toISOString(),
+ },
+ }),
+ );
+}
+```
+
+Note that we're returning a `getFrameHtmlResponse` here, **not** a `getFrameMetadata`. That's only used for the initial frame created from a page!
+
+### Follow and Recast Gates
+
+On your own, try changing the "like" gate to require the user to follow you, recast, or all three!
+
+
+**Hint:** Take a close look at the `FrameValidationData` properties. [OnchainKit] makes this easy!
+
+
+
+## Conclusion
+
+In this tutorial, you learned how to use the main features of Frames - text input, link buttons, and redirects. You also learned how to use new features in [OnchainKit] to require your users to perform certain actions to unlock features in your Frame. Finally, you learned how to create a loop in your Frame's behavior, which can be used to create very complicated Frames!
+
+[Base Learn]: https://docs.base.org/base-learn/docs/welcome
+[Farcaster]: https://www.farcaster.xyz/
+[a-frame-in-100-lines]: https://github.com/Zizzamia/a-frame-in-100-lines
+[OnchainKit]: https://onchainkit.xyz/?utm_source=basedocs&utm_medium=tutorials&campaign=farcaster-frames-gating-and-redirects
+[Vercel]: https://vercel.com
+[Frame Validator]: https://warpcast.com/~/developers/frames
+[Base channel]: https://warpcast.com/~/channel/base
+[deploying with Vercel]: /tutorials/farcaster-frames-deploy-to-vercel
+[NFT Minting Frame]: /tutorials/farcaster-frames-nft-minting
+[Frames]: https://warpcast.notion.site/Farcaster-Frames-4bd47fe97dc74a42a48d3a234636d8c5
+[viem]: https://viem.sh/
+[Basescan]: https://basescan.org/
+
diff --git a/_pages/cookbook/use-case-guides/hyperframes.mdx b/_pages/cookbook/use-case-guides/hyperframes.mdx
new file mode 100644
index 00000000..4b3b9b18
--- /dev/null
+++ b/_pages/cookbook/use-case-guides/hyperframes.mdx
@@ -0,0 +1,407 @@
+---
+title: 'Farcaster Frames: Building HyperFrames'
+slug: /farcaster-frames-hyperframes
+description: A tutorial that teaches how to make cross-linked HyperFrames in an organized manner.
+author: briandoyle81
+---
+
+# Farcaster Frames: Building HyperFrames
+
+[Frames] on [Farcaster] are getting more complex. Developers are now building interactions that require a handful, or even dozens of frames in response to various user states, inputs, and actions. HyperFrames are a system to organize navigation for large numbers of frames, using [OnchainKit]. In this tutorial, we'll use making the navigation for an [old-school adventure game] fully in a frame. You can use this same technique for many other intents with your Frames, such as games, stores, customized mints, etc.
+
+## Objectives
+
+By the end of this tutorial, you should be able to:
+
+- Build HyperFrames - connected [Farcaster] Frames that manage significant numbers of cross-linked frames in an organized fashion
+- Add management of conditional states to the navigation system
+- Use frame state to pass information from one frame to another
+
+## Prerequisites
+
+### Vercel
+
+You'll need to be comfortable deploying your app to [Vercel], or using another solution on your own. Check out our tutorial on [deploying with Vercel] if you need a refresher!
+
+### Farcaster
+
+You must have a [Farcaster] account with a connected wallet. Check out the [Base channel] to stay in the loop when we release tutorials like this!
+
+### Frames
+
+You should be comfortable with the basics of creating Farcaster [Frames]. If you aren't, check out our tutorial on [NFT Minting Frame].
+
+## Getting Started
+
+This tutorial assumes you're using [a-frame-in-100-lines] as the base for your project. It's a very lightweight template, so with minor modifications, you can adapt this technique to any project.
+
+You'll also need to create or find a handful of images to use for each frame. AI tools are wonderful for this type of prototyping, or you can right-click and save the images from the [old-school adventure game].
+
+## Creating the First Frame
+
+Open `app/page.tsx`. Modify the `getFrameMetadata` for the first frame to match the frame in the example.
+
+
+
+```tsx
+const frameMetadata = getFrameMetadata({
+ buttons: [
+ {
+ label: 'Road',
+ },
+ {
+ label: 'Woods',
+ },
+ {
+ label: 'Cave',
+ },
+ {
+ action: 'link',
+ label: 'TODO',
+ target: 'https://www.google.com',
+ },
+ ],
+ image: {
+ src: `${NEXT_PUBLIC_URL}/frame-1-forest.webp`,
+ aspectRatio: '1:1',
+ },
+ postUrl: `${NEXT_PUBLIC_URL}/api/frame`,
+});
+```
+
+
+Per the [Frames] specification, `state` is not an allowed property on the first frame.
+
+
+
+Configure the rest of the metadata as you see fit. Remember, this won't show up in your frame, but it will appear if someone links your site to another platform that uses the standard Open Graph metadata.
+
+```tsx
+export const metadata: Metadata = {
+ title: 'HyperFrames!',
+ description: 'Time is a flat circle.',
+ openGraph: {
+ title: 'HyperFrames!',
+ description: 'Time is a flat circle.',
+ images: [`${NEXT_PUBLIC_URL}/frame-1-forest.webp`],
+ },
+ other: {
+ ...frameMetadata,
+ },
+};
+```
+
+## Setting up the Route
+
+The route you'll construct is similar to the example in [`app/api/route.ts`] of the 100-lines example. It will use [OnchainKit] to retrieve and validate the message from the frame. In doing so, it will collect:
+
+- The user's address
+- The button clicked by the user to get here
+- Text, if the previous frame had a text box
+- Any `state` data included in the sending frame
+
+Stub out the route, but remove the conditionals and the existing response:
+
+```tsx
+import { FrameRequest, getFrameMessage, getFrameHtmlResponse } from '@coinbase/onchainkit';
+import { NextRequest, NextResponse } from 'next/server';
+
+async function getResponse(req: NextRequest): Promise {
+ let accountAddress: string | undefined = '';
+ let text: string | undefined = '';
+
+ const body: FrameRequest = await req.json();
+ const { isValid, message } = await getFrameMessage(body, { neynarApiKey: 'NEYNAR_ONCHAIN_KIT' });
+
+ if (isValid) {
+ accountAddress = message.interactor.verified_accounts[0];
+ } else {
+ return new NextResponse('Message not valid', { status: 500 });
+ }
+
+ return new NextResponse();
+ // TODO: Return a frame
+}
+
+export async function POST(req: NextRequest): Promise {
+ return getResponse(req);
+}
+
+export const dynamic = 'force-dynamic';
+```
+
+### Identifying the Sending Frame
+
+To identify which frame sent the request to the endpoint, you can extract the `state` sent with the frame data in the request. The first frame **will not** have this, so you'll have to make an assumption there.
+
+After the first frame, if the `state` property is present, it will appear in the message as `state`, but it will need to be decoded.
+
+```tsx
+let state = { frame: 'start' };
+
+try {
+ state = JSON.parse(decodeURIComponent(message.state?.serialized));
+} catch (e) {
+ // Note that this error will always be triggered by the first frame
+ console.error(e);
+}
+```
+
+## HyperFrames
+
+Add a file called `hyperframes.ts` to the `app` folder. Import:
+
+```tsx
+import { getFrameHtmlResponse } from '@coinbase/onchainkit';
+import { NEXT_PUBLIC_URL } from './config';
+```
+
+Next, add an interface for the `HyperFrame`:
+
+```tsx
+export type HyperFrame = {
+ frame: string;
+ 1: string | ((text: string) => string) | (() => string);
+ 2?: string | ((text: string) => string) | (() => string);
+ 3?: string | ((text: string) => string) | (() => string);
+ 4?: string | ((text: string) => string) | (() => string);
+};
+```
+
+The interface contains properties to track the frame itself, and the identifier for which frame each button is mapped to. These are all strings, or functions that return strings, because you'll use string identifiers for your frames, and because the frame returned by `getFrameHtmlResponse` is formatted as a string.
+
+For the buttons, you can now use:
+
+- A string identifying the frame the button should lead to
+- A function that returns a string that also takes a string argument. Use this to pass in the user's address, or the `text` from the frame
+- Any function with no arguments that returns a frame name as a string
+
+If you need to, update the type here to handle more complex cases.
+
+Next, add a `Record` to store your collection of HyperFrames, and a function to add frames to the `Record`:
+
+```tsx
+const frames: Record = {};
+
+export function addHyperFrame(label: string, frame: HyperFrame) {
+ frames[label] = frame;
+}
+```
+
+Finally, add a function to retrieve and return a HyperFrame by its `label` and handle the case of an error.
+
+```tsx
+export function getHyperFrame(frame: string, text: string, button: number) {
+ const currentFrame = frames[frame];
+ const nextFrameIdOrFunction = currentFrame[button as keyof HyperFrame];
+
+ let nextFrameId: string;
+ if (typeof nextFrameIdOrFunction === 'function') {
+ nextFrameId = nextFrameIdOrFunction(text);
+ } else {
+ nextFrameId = nextFrameIdOrFunction as string;
+ }
+
+ if (!frames[nextFrameId]) {
+ throw new Error(`Frame not found: ${nextFrameId}`);
+ }
+
+ return frames[nextFrameId].frame;
+}
+```
+
+## Adding HyperFrames
+
+You can put the HyperFrames wherever you want and import them into your route. For the sake of simplicity, this demo will simply include them at the top of the route file. Import
+
+```tsx
+import { addHyperFrame, getHyperFrame } from '../../../../hyperframes';
+```
+
+To store the HyperFrames, add them to the `Record` type with `addHyperFrame`. To create the frames themselves, use `getFrameHtmlResponse` to build the frame, and add the names of the frames you have created, or will create, to the appropriate button.
+
+Add the name of each frame as that frame's `state` as well.
+
+```tsx
+addHyperFrame('start', {
+ frame: getFrameHtmlResponse({
+ buttons: [
+ {
+ label: 'Road',
+ },
+ {
+ label: 'Woods',
+ },
+ {
+ label: 'Cave',
+ },
+ {
+ action: 'link',
+ label: 'TODO',
+ target: 'https://www.google.com',
+ },
+ ],
+ image: {
+ src: `${NEXT_PUBLIC_URL}/frame-1-forest.webp`,
+ aspectRatio: '1:1',
+ },
+ state: { frame: 'start' },
+ postUrl: `${NEXT_PUBLIC_URL}/api/frame`,
+ }),
+ 1: 'road',
+ 2: 'woods-bear',
+ 3: 'cave-1',
+});
+
+addHyperFrame('road', {
+ frame: getFrameHtmlResponse({
+ buttons: [
+ {
+ label: 'Go Back',
+ },
+ {
+ label: 'Shack',
+ },
+ {
+ label: 'Forward',
+ },
+ ],
+ image: {
+ src: `${NEXT_PUBLIC_URL}/road.png`,
+ aspectRatio: '1:1',
+ },
+ postUrl: `${NEXT_PUBLIC_URL}/api/frame`,
+ }),
+ 1: 'start',
+ 2: 'shack',
+ 3: 'desert-road',
+});
+```
+
+Above, you've created two HyperFrames. The first, has three buttons, mapped to the frames named `road`, `woods-bear`, and `cave-1`. Only the first will work, because you haven't built the other HyperFrames, or error handling.
+
+The second, also has three buttons, mapped to frames as well. Only `start` is implemented as of yet.
+
+## Calling `getHyperFrame`
+
+Return to `route.ts`. To avoid TypeScript errors, you'll need to implement at least a minimum of error handling for the event that the `frame` query parameter or `button` are not present:
+
+```tsx
+if (!frame) {
+ return new NextResponse('Frame not found', { status: 404 });
+}
+
+// There should always be a button number
+if (!message?.button) {
+ return new NextResponse('Button not found', { status: 404 });
+}
+```
+
+Then, simply import and call `getHyperFrame` as the `NextResponse`:
+
+```tsx
+return new NextResponse(getHyperFrame(frame as string, text || '', message?.button));
+```
+
+Deploy, test with the [Frame Validator], and debug!
+
+## Adding Conditionals
+
+It's not very interesting for everyone to be able to explore without restriction, so add a lock with a password! To do so, add HyperFrame that contains a function to see if the `text` contains the correct password.
+
+```tsx
+addHyperFrame('shack', {
+ frame: getFrameHtmlResponse({
+ buttons: [
+ {
+ label: 'Go Back',
+ },
+ {
+ label: 'Door',
+ },
+ {
+ label: 'Testing',
+ },
+ ],
+ image: {
+ src: `${NEXT_PUBLIC_URL}/shack.png`,
+ aspectRatio: '1:1',
+ },
+ input: {
+ text: 'What is the password?',
+ },
+ state: { frame: 'shack' },
+ postUrl: `${NEXT_PUBLIC_URL}/api/frame`,
+ }),
+ 1: 'road',
+ 2: (text: string) => {
+ return text === 'All your Base are belong to you' ? 'key' : 'shack-bad-password';
+ },
+});
+```
+
+For the event that the user enters the wrong password, simply add a nearly identical frame asking them to try again. If they enter the correct password, take them to a new room via a different HyperFrame:
+
+```tsx
+addHyperFrame('shack-bad-password', {
+ frame: getFrameHtmlResponse({
+ buttons: [
+ {
+ label: 'Go Back',
+ },
+ {
+ label: 'Door',
+ },
+ ],
+ image: {
+ src: `${NEXT_PUBLIC_URL}/shack.png`,
+ aspectRatio: '1:1',
+ },
+ input: {
+ text: 'Try again. What is the password?',
+ },
+ state: { frame: 'shack-bad-password' },
+ postUrl: `${NEXT_PUBLIC_URL}/api/frame`,
+ }),
+ 1: 'road',
+ 2: (text: string) => {
+ return text === 'All your Base are belong to you' ? 'key' : 'shack-bad-password';
+ },
+});
+
+addHyperFrame('key', {
+ frame: getFrameHtmlResponse({
+ buttons: [
+ {
+ label: 'Go Back',
+ },
+ {
+ label: 'TODO',
+ },
+ ],
+ image: {
+ src: `${NEXT_PUBLIC_URL}/key.png`,
+ aspectRatio: '1:1',
+ },
+ state: { frame: 'key' },
+ postUrl: `${NEXT_PUBLIC_URL}/api/frame`,
+ }),
+ 1: 'shack',
+});
+```
+
+## Conclusion
+
+In this tutorial, you learned how to implement a system of HyperFrames - frames that are easily cross-linkable. You also learned how to add variety and depth to this system by adding conditionals for the button linking one frame to another. Finally, you learned how to use the `state` property to pass information between frames.
+
+[Farcaster]: https://www.farcaster.xyz/
+[a-frame-in-100-lines]: https://github.com/Zizzamia/a-frame-in-100-lines
+[OnchainKit]: https://onchainkit.xyz/?utm_source=basedocs&utm_medium=tutorials&campaign=farcaster-frames-hyperframes
+[Vercel]: https://vercel.com
+[Frame Validator]: https://warpcast.com/~/developers/frames
+[deploying with Vercel]: /tutorials/farcaster-frames-deploy-to-vercel
+[Frames]: https://docs.farcaster.xyz/learn/what-is-farcaster/frames
+[NFT Minting Frame]: /tutorials/farcaster-frames-nft-minting
+[old-school adventure game]: https://warpcast.com/briandoyle81/0x108f1cdb
+[`app/api/route.ts`]: https://github.com/Zizzamia/a-frame-in-100-lines/blob/main/app/api/frame/route.ts
+
diff --git a/docs/pages/cookbook/use-case-guides/nft-minting.mdx b/_pages/cookbook/use-case-guides/nft-minting.mdx
similarity index 99%
rename from docs/pages/cookbook/use-case-guides/nft-minting.mdx
rename to _pages/cookbook/use-case-guides/nft-minting.mdx
index 11532ead..46b43c53 100644
--- a/docs/pages/cookbook/use-case-guides/nft-minting.mdx
+++ b/_pages/cookbook/use-case-guides/nft-minting.mdx
@@ -6,6 +6,8 @@ authors:
- briandoyle81
---
+import { Danger } from "/snippets/danger.mdx";
+
# Farcaster Frames: Building an NFT airdrop Frame
[Frames] have taken the [Farcaster] world by storm. They allow you to place a small onchain app inside of a cast! In this tutorial, we'll show you how to build your first frame using [OnchainKit], and our [a-frame-in-100-lines] template.
@@ -88,19 +90,17 @@ function mintTo(address _recipient) public onlyOwner {
}
```
-:::info
-
+
You should also use a `mintTo` function that accepts an `address _recipient` if you are doing a mint transaction button. Doing so helps Farcaster more easily scan your transaction for safety.
+
-:::
You'll also want to keep track of addresses that have already minted, to prevent a few spammers from claiming all the NFTs!
-:::danger
-
+
Make sure you added `.env.local` to ``.gitignore`! **If you don't do this you are going to expose your key and lose your wallet!**
+
-:::
Create `.env.local` and add:
@@ -362,11 +362,10 @@ const url = new URL(req.url);
You'll use the BaseScan api to find the token information. Add your api key to your `.env` if you didn't already.
-:::caution
-
+
Note that BaseScan and Etherscan use different keys!
+
-:::
There isn't an sdk to install.
@@ -560,9 +559,10 @@ return new NextResponse(img, {
});
```
-:::caution
+
SVG images aren't working in Frames on mobile clients. Stay tuned on the Base channel for a fix! Or add a library here to return the image as a `.png` instead of a `.svg`.
-:::
+
+
**Remember to update your envars in Vercel**, redeploy, and test. You'll now see your NFT in the frame!
diff --git a/docs/pages/cookbook/use-case-guides/no-code-minting.mdx b/_pages/cookbook/use-case-guides/no-code-minting.mdx
similarity index 100%
rename from docs/pages/cookbook/use-case-guides/no-code-minting.mdx
rename to _pages/cookbook/use-case-guides/no-code-minting.mdx
diff --git a/docs/pages/cookbook/use-case-guides/transactions.mdx b/_pages/cookbook/use-case-guides/transactions.mdx
similarity index 99%
rename from docs/pages/cookbook/use-case-guides/transactions.mdx
rename to _pages/cookbook/use-case-guides/transactions.mdx
index 7e60c323..730d0694 100644
--- a/docs/pages/cookbook/use-case-guides/transactions.mdx
+++ b/_pages/cookbook/use-case-guides/transactions.mdx
@@ -224,11 +224,10 @@ Finally, return the transaction as a `NextResponse`:
return NextResponse.json(txData);
```
-:::info
-
+
If you find Warpcast errors or spins forever after receiving your transaction data, it can be handy to simulate the transaction on your service first. It makes debugging much easier and will rule out any errors in forming the transaction arguments. To learn how, check out viem's documentation on [Simulating Contract Interactions](https://viem.sh/docs/contract/simulateContract#simulatecontract).
+
-:::
### Setting Up the After Transaction Endpoint
@@ -282,11 +281,10 @@ export async function POST(req: NextRequest): Promise {
export const dynamic = 'force-dynamic';
```
-:::info
-
+
In certain applications you might want to monitor the status of the transaction at this point. It's possible it fails, takes a while, and/or you may want to do another operation _only after_ it has been confirmed. To do so you can make use of `message.transaction.hash` and build a frame flow that checks the status of the transaction by fetching the [transaction receipt](https://viem.sh/docs/actions/public/getTransactionReceipt#gettransactionreceipt).
+
-:::
## Conclusion
diff --git a/docs/pages/cookie-policy.mdx b/_pages/cookie-policy.mdx
similarity index 100%
rename from docs/pages/cookie-policy.mdx
rename to _pages/cookie-policy.mdx
diff --git a/docs/pages/docs.mdx b/_pages/docs.mdx
similarity index 100%
rename from docs/pages/docs.mdx
rename to _pages/docs.mdx
diff --git a/_pages/feedback.mdx b/_pages/feedback.mdx
new file mode 100644
index 00000000..203638e8
--- /dev/null
+++ b/_pages/feedback.mdx
@@ -0,0 +1,4 @@
+---
+title: "Feedback\n\nComing Soon"
+---
+
diff --git a/docs/pages/identity/basenames/basenames-faq.mdx b/_pages/identity/basenames/basenames-faq.mdx
similarity index 99%
rename from docs/pages/identity/basenames/basenames-faq.mdx
rename to _pages/identity/basenames/basenames-faq.mdx
index 8f0e6f42..082509cf 100644
--- a/docs/pages/identity/basenames/basenames-faq.mdx
+++ b/_pages/identity/basenames/basenames-faq.mdx
@@ -72,9 +72,10 @@ You can set your Basename as your primary name through Profile Management. Setti
You can transfer your Basename to another address through Profile Management:
-:::warning
+
**Make sure to use the Basenames UI to send Basenames properly, sends on platforms like OpenSea will only transfer the NFT.**
-:::
+
+
Transfer token ownership - transfers ownership of the Basename token and associated permissions.
Transfer management - transfers ability to manage and update profile records.
@@ -90,11 +91,10 @@ Step by step:
* Paste the ENS or address of the wallet you want to transfer the basename to
* Proceed to sign all four transactions to properly update the basename address, ownership, and profile records. The last transaction will be sending the NFT.
-:::info
-
+
**For the new owner to use the basename they will need to confirm by setting it as their [primary name](#9-how-do-i-set-my-basename-as-my-primary-name-for-my-address).**
+
-:::
### 11. What happens if I forget to renew my Basename?
diff --git a/docs/pages/identity/basenames/basenames-onchainkit-tutorial.mdx b/_pages/identity/basenames/basenames-onchainkit-tutorial.mdx
similarity index 98%
rename from docs/pages/identity/basenames/basenames-onchainkit-tutorial.mdx
rename to _pages/identity/basenames/basenames-onchainkit-tutorial.mdx
index 87a4598d..8bd0ace2 100644
--- a/docs/pages/identity/basenames/basenames-onchainkit-tutorial.mdx
+++ b/_pages/identity/basenames/basenames-onchainkit-tutorial.mdx
@@ -91,9 +91,12 @@ This configuration sets up the wagmi project to connect to the Base and BaseSepo
Now we’ll create a component to display the Basenames associated with an address.
-:::tip[Use Base as your chain]
+
+**Use Base as your chain**
+
Ensure Chain is Set to Base Be sure to set the `chain={base}` parameter; otherwise, it will default to ENS (Ethereum Name Service).
-:::
+
+
```typescript filename="src/components/basename.tsx"
'use client';
diff --git a/docs/pages/identity/basenames/basenames-wagmi-tutorial.mdx b/_pages/identity/basenames/basenames-wagmi-tutorial.mdx
similarity index 99%
rename from docs/pages/identity/basenames/basenames-wagmi-tutorial.mdx
rename to _pages/identity/basenames/basenames-wagmi-tutorial.mdx
index 0d11a03d..583ed431 100644
--- a/docs/pages/identity/basenames/basenames-wagmi-tutorial.mdx
+++ b/_pages/identity/basenames/basenames-wagmi-tutorial.mdx
@@ -43,11 +43,10 @@ export default [
] as const;
```
-:::tip
-
+
You will need to replace the placeholder comment with the actual ABI. Here is the link to the full [L2ResolverAbi].
+
-:::
To interact with the Base blockchain, you will need to update the wagmi configuration. This will allow your project to connect to the Base network and use its features.
@@ -86,11 +85,14 @@ Next, we'll create a new directory to house the functions that will resolve and
In your project folder, create the apis directory and add a basenames.tsx file:
-:::note[What's happening in the code?]
+
+**What's happening in the code?**
+
`convertReverseNodeToBytes()`: This function is creating the reverse node so we can look up a name given an address. Each address gets its own reverse record in our registry that's created in a deterministic way.
You can see the implementation of `convertReverseNodeToBytes()` in the [OnchainKit repo]
`BasenameTextRecordKeys`: Metadata (e.g., github, twitter, etc.) are stored as text records so we can look them up based on the enum key.
-:::
+
+
```typescript filename="src/apis/basenames.tsx"
import {
@@ -127,9 +129,10 @@ export async function getBasename(address: Address) {
This code provides the foundation for resolving Basenames using the Base network.
-:::tip
+
You can find the complete implementation in the full [basenames.tsx] file.
-:::
+
+
Now that the necessary functions are in place, you can implement the Basenames functionality in your app. For this example, we'll modify the `page.tsx` file to display Basename information on the server and client side.
diff --git a/docs/pages/identity/index.mdx b/_pages/identity/index.mdx
similarity index 100%
rename from docs/pages/identity/index.mdx
rename to _pages/identity/index.mdx
diff --git a/docs/pages/identity/mobile-wallet-protocol/batch.mdx b/_pages/identity/mobile-wallet-protocol/batch.mdx
similarity index 98%
rename from docs/pages/identity/mobile-wallet-protocol/batch.mdx
rename to _pages/identity/mobile-wallet-protocol/batch.mdx
index 5b39f8b1..1558f05c 100644
--- a/docs/pages/identity/mobile-wallet-protocol/batch.mdx
+++ b/_pages/identity/mobile-wallet-protocol/batch.mdx
@@ -1,4 +1,7 @@
-# Batch requests
+---
+title: "Batch requests"
+---
+
To improve UX by minimizing app switches,
MWP allows client apps to make requests with multiple actions at once.
diff --git a/docs/pages/identity/mobile-wallet-protocol/encryption.mdx b/_pages/identity/mobile-wallet-protocol/encryption.mdx
similarity index 99%
rename from docs/pages/identity/mobile-wallet-protocol/encryption.mdx
rename to _pages/identity/mobile-wallet-protocol/encryption.mdx
index 6beb216e..992ce832 100644
--- a/docs/pages/identity/mobile-wallet-protocol/encryption.mdx
+++ b/_pages/identity/mobile-wallet-protocol/encryption.mdx
@@ -1,4 +1,7 @@
-# Encryption
+---
+title: "Encryption"
+---
+
MWP uses deep links for direct communication between peers.
diff --git a/docs/pages/identity/mobile-wallet-protocol/handshake.mdx b/_pages/identity/mobile-wallet-protocol/handshake.mdx
similarity index 98%
rename from docs/pages/identity/mobile-wallet-protocol/handshake.mdx
rename to _pages/identity/mobile-wallet-protocol/handshake.mdx
index 83331d33..a6bfa14a 100644
--- a/docs/pages/identity/mobile-wallet-protocol/handshake.mdx
+++ b/_pages/identity/mobile-wallet-protocol/handshake.mdx
@@ -1,4 +1,7 @@
-# Handshake
+---
+title: "Handshake"
+---
+
For key exchange, client apps make `handshake` calls to ask wallets to generate and share a key to encrypt subsequent messages on the session.
diff --git a/docs/pages/identity/mobile-wallet-protocol/messages-example.mdx b/_pages/identity/mobile-wallet-protocol/messages-example.mdx
similarity index 100%
rename from docs/pages/identity/mobile-wallet-protocol/messages-example.mdx
rename to _pages/identity/mobile-wallet-protocol/messages-example.mdx
diff --git a/docs/pages/identity/mobile-wallet-protocol/messages-request.mdx b/_pages/identity/mobile-wallet-protocol/messages-request.mdx
similarity index 96%
rename from docs/pages/identity/mobile-wallet-protocol/messages-request.mdx
rename to _pages/identity/mobile-wallet-protocol/messages-request.mdx
index 27b43f1b..614524bb 100644
--- a/docs/pages/identity/mobile-wallet-protocol/messages-request.mdx
+++ b/_pages/identity/mobile-wallet-protocol/messages-request.mdx
@@ -1,4 +1,7 @@
-# Request content
+---
+title: "Request content"
+---
+
Request message has `request` as its `content`, which contains following:
diff --git a/docs/pages/identity/mobile-wallet-protocol/messages-response.mdx b/_pages/identity/mobile-wallet-protocol/messages-response.mdx
similarity index 97%
rename from docs/pages/identity/mobile-wallet-protocol/messages-response.mdx
rename to _pages/identity/mobile-wallet-protocol/messages-response.mdx
index 4de52dc8..d27f2389 100644
--- a/docs/pages/identity/mobile-wallet-protocol/messages-response.mdx
+++ b/_pages/identity/mobile-wallet-protocol/messages-response.mdx
@@ -1,4 +1,7 @@
-# Response content
+---
+title: "Response content"
+---
+
Response content can be either `response` or `failure`.
diff --git a/docs/pages/identity/mobile-wallet-protocol/messages.mdx b/_pages/identity/mobile-wallet-protocol/messages.mdx
similarity index 98%
rename from docs/pages/identity/mobile-wallet-protocol/messages.mdx
rename to _pages/identity/mobile-wallet-protocol/messages.mdx
index 2613ce60..c01abdfe 100644
--- a/docs/pages/identity/mobile-wallet-protocol/messages.mdx
+++ b/_pages/identity/mobile-wallet-protocol/messages.mdx
@@ -1,4 +1,7 @@
-# Messages
+---
+title: "Messages"
+---
+
Communications between client and server (wallet host) in MWP are through exchanging discrete stateless messages.
diff --git a/docs/pages/identity/mobile-wallet-protocol/multi-chain.mdx b/_pages/identity/mobile-wallet-protocol/multi-chain.mdx
similarity index 92%
rename from docs/pages/identity/mobile-wallet-protocol/multi-chain.mdx
rename to _pages/identity/mobile-wallet-protocol/multi-chain.mdx
index 9265e759..2b2845bf 100644
--- a/docs/pages/identity/mobile-wallet-protocol/multi-chain.mdx
+++ b/_pages/identity/mobile-wallet-protocol/multi-chain.mdx
@@ -1,4 +1,7 @@
-# Multi-chain support
+---
+title: "Multi-chain support"
+---
+
MWP is chain-agnostic.
As long as the wallet supports the chain and is able to process the requested actions, apps can communicate using MWP.
diff --git a/_pages/identity/mobile-wallet-protocol/network.mdx b/_pages/identity/mobile-wallet-protocol/network.mdx
new file mode 100644
index 00000000..9b45670c
--- /dev/null
+++ b/_pages/identity/mobile-wallet-protocol/network.mdx
@@ -0,0 +1,8 @@
+---
+title: "Transport layer"
+---
+
+
+Deep linking through universal links on iOS.
+Intent + app links on android.
+
diff --git a/docs/pages/identity/mobile-wallet-protocol/spec-overview.mdx b/_pages/identity/mobile-wallet-protocol/spec-overview.mdx
similarity index 100%
rename from docs/pages/identity/mobile-wallet-protocol/spec-overview.mdx
rename to _pages/identity/mobile-wallet-protocol/spec-overview.mdx
diff --git a/docs/pages/identity/mobile-wallet-protocol/verification.mdx b/_pages/identity/mobile-wallet-protocol/verification.mdx
similarity index 96%
rename from docs/pages/identity/mobile-wallet-protocol/verification.mdx
rename to _pages/identity/mobile-wallet-protocol/verification.mdx
index 60aae1d7..72fab2a7 100644
--- a/docs/pages/identity/mobile-wallet-protocol/verification.mdx
+++ b/_pages/identity/mobile-wallet-protocol/verification.mdx
@@ -1,4 +1,7 @@
-# Verification
+---
+title: "Verification"
+---
+
Decentralized verification of participating apps’ authenticity using [.well-known](https://en.wikipedia.org/wiki/Well-known_URI) data without centralized registry
- [apple-app-site-association](https://developer.apple.com/documentation/xcode/supporting-associated-domains)
diff --git a/_pages/identity/smart-wallet/concepts/base-gasless-campaign.mdx b/_pages/identity/smart-wallet/concepts/base-gasless-campaign.mdx
new file mode 100644
index 00000000..79e9765f
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/base-gasless-campaign.mdx
@@ -0,0 +1,13 @@
+---
+title: "Base Gasless Campaign"
+---
+
+
+Base is offering gas credits to help developers make the most of
+Smart Wallet's [paymaster (sponsored transactions)](/identity/smart-wallet/concepts/features/optional/gas-free-transactions) features.
+
+| Partner Tier | Base Gas Credit Incentive | Requirements | Actions |
+| ------------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| 1 | $15k | - Support Coinbase Smart Wallet - Onboard to Coinbase Paymaster - Preferred placement in your UI (ie "Create Wallet" button) | 1. Bump your Coinbase SDK to add Coinbase Smart Wallet to your app, or bump to latest version of any [supporting wallet library](/identity/smart-wallet/concepts/usage-details/wallet-library-support). 2. Sign in / up for [Coinbase Developer Platform](https://www.coinbase.com/developer-platform) (takes less than 2 minutes). No KYC needed - just email and phone. 3. Check out the Paymaster product where the Base Mainnet Paymaster is enabled by default. Set and change your gas policy at any time. 4. Complete [this form](https://docs.google.com/forms/d/1yPnBFW0bVUNLUN_w3ctCqYM9sjdIQO3Typ53KXlsS5g/viewform?edit_requested=true) Credits will land within 1 week of completion |
+| 2 | $10k | - Support Coinbase Smart Wallet - Onboard to Coinbase Paymaster | 1. Bump your Coinbase SDK to add Coinbase Smart Wallet to your app, or bump to latest version of any [supporting wallet library](/identity/smart-wallet/concepts/usage-details/wallet-library-support). 2. Sign in / up for [Coinbase Developer Platform](https://www.coinbase.com/developer-platform) (takes less than 2 minutes). No KYC needed - just email and phone. 3. Check out the Paymaster product where the Base Mainnet Paymaster is enabled by default. Set and change your gas policy at any time. 4. Complete [this form](https://docs.google.com/forms/d/1yPnBFW0bVUNLUN_w3ctCqYM9sjdIQO3Typ53KXlsS5g/viewform?edit_requested=true) Credits will land within 1 week of completion |
+| Bonus | $1k | | Create a demo of your Coinbase Smart wallet integration, post on social (Warpcast and/or X) and tag Coinbase Wallet and/or Base |
diff --git a/_pages/identity/smart-wallet/concepts/features/built-in/MagicSpend.mdx b/_pages/identity/smart-wallet/concepts/features/built-in/MagicSpend.mdx
new file mode 100644
index 00000000..40f736e8
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/features/built-in/MagicSpend.mdx
@@ -0,0 +1,24 @@
+---
+title: "MagicSpend"
+---
+
+
+MagicSpend enables Smart Wallet users to spend their Coinbase balances directly onchain. Users can connect their Coinbase account during the transaction flow, eliminating the need for manual onramps or transfers.
+
+## Benefits for Your App
+- Remove funding friction for new users
+- Tap into Coinbase's massive user base
+- Enable instant transactions without waiting for onramps
+
+## Supported Assets and Networks
+- Assets: ETH
+- Networks: Base
+
+## Best Practices
+Some apps check user's onchain balance and block certain interactions if the user
+has insufficient funds. This is a problem for MagicSpend users, who can access
+their Coinbase funds during the transaction flow.
+
+Apps can detect whether the connected address may have other funds accessible
+via `auxiliaryFunds` capability, which can be discovered via a [`wallet_getCapabilities` RPC call](https://eip5792.xyz/reference/getCapabilities).
+If a user has `auxiliaryFunds`, apps should not block interactions based on onchain balance.
diff --git a/_pages/identity/smart-wallet/concepts/features/built-in/networks.mdx b/_pages/identity/smart-wallet/concepts/features/built-in/networks.mdx
new file mode 100644
index 00000000..4d51a723
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/features/built-in/networks.mdx
@@ -0,0 +1,27 @@
+---
+title: "Networks"
+---
+
+;
+
+
+The Smart Wallet contracts can be [permissionlessly deployed](https://github.com/coinbase/smart-wallet/tree/main?tab=readme-ov-file#deployments) on any EVM-compatible network.
+Our SDK and client support the following networks.
+
+## Mainnet Networks
+
+- Base
+- Arbitrum
+- Optimism
+- Zora
+- Polygon
+- BNB
+- Avalanche
+- Lordchain
+- Ethereum mainnet (not recommended due to costs)
+
+## Testnet Networks
+
+- Sepolia
+- Base Sepolia
+- Optimism Sepolia
diff --git a/_pages/identity/smart-wallet/concepts/features/built-in/passkeys.mdx b/_pages/identity/smart-wallet/concepts/features/built-in/passkeys.mdx
new file mode 100644
index 00000000..4cf3ea5b
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/features/built-in/passkeys.mdx
@@ -0,0 +1,24 @@
+## Passkeys
+
+Passkeys enable instant account creation and seamless authentication for Smart Wallet users, dramatically simplifying onboarding. By leveraging FIDO2-compliant authentication, passkeys eliminate seed phrases while providing enterprise-grade security for both wallet access and onchain ownership.
+
+During account creation, a cryptographic key pair is generated and the private key is securely stored on the user's device, while the public key is registered onchain as an owner of the user's Smart Wallet. The passkey serves two core functions:
+
+1. **Authentication**: Replaces passwords for wallet access.
+2. **Transaction Signing**: Functions as the signing key for onchain transactions, replacing private keys and seed phrases.
+
+### Cross-Device Support
+
+Passkeys leverage platform authenticator APIs for cross-device synchronization through:
+- iCloud Keychain (Apple devices)
+- Google Password Manager
+- 1Password
+- Any WebAuthn-compatible password manager
+
+This enables seamless multi-device support without exposing the underlying cryptographic material.
+
+
+ **Account Recovery Considerations**
+
+ Without access to their passkey or a configured recovery key, users will permanently lose wallet access.
+
diff --git a/_pages/identity/smart-wallet/concepts/features/built-in/recovery-keys.mdx b/_pages/identity/smart-wallet/concepts/features/built-in/recovery-keys.mdx
new file mode 100644
index 00000000..5d83c504
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/features/built-in/recovery-keys.mdx
@@ -0,0 +1,22 @@
+---
+title: "Recovery Keys"
+---
+
+;
+
+
+Recovery keys provide a fallback authentication and signing mechanism for Smart Wallets when passkey access is lost.
+
+## Implementation
+A recovery key is a standard Ethereum private key. When generated, its corresponding Ethereum address is registered onchain as an owner of the Smart Wallet. This enables two key capabilities:
+
+1. In the event a user loses their passkey, the recovery key can be used to add new passkey owners to the Smart Wallet through the [wallet recovery flow](https://help.coinbase.com/en/wallet/getting-started/smart-wallet-recovery).
+2. The recovery key can be used to sign transactions as an owner of the Smart Wallet without the use of our website (though specialized wallet software is needed to properly construct the Smart Wallet transactions).
+
+## Technical Details
+- The recovery key has equivalent permissions to passkey owners at the contract level.
+- Recovery keys use standard ECDSA signatures, making them compatible with existing Ethereum tooling.
+- Multiple recovery keys can be registered to a single Smart Wallet.
+- Recovery keys can be added or removed by any existing owner of the Smart Wallet.
+
+
diff --git a/_pages/identity/smart-wallet/concepts/features/built-in/single-sign-on.mdx b/_pages/identity/smart-wallet/concepts/features/built-in/single-sign-on.mdx
new file mode 100644
index 00000000..4161ec77
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/features/built-in/single-sign-on.mdx
@@ -0,0 +1,12 @@
+---
+title: "Single Sign On"
+---
+
+
+Smart Wallet is a single sign on for onchain apps. Users bring the same account, identity, and assets across apps.
+
+## How it works
+
+1. Smart Wallet relies on [passkeys](/identity/smart-wallet/concepts/features/built-in/passkeys), stored on users' devices, which can be used on our website (keys.coinbase.com) and [mobile app](https://www.coinbase.com/wallet).
+2. Our SDK, which developers integrate into their apps, uses keys.coinbase.com popups to allow users to see requests and sign with their passkey.
+3. The SDK and the popup use cross domain messaging to share information back to the app.
diff --git a/_pages/identity/smart-wallet/concepts/features/optional/batch-operations.mdx b/_pages/identity/smart-wallet/concepts/features/optional/batch-operations.mdx
new file mode 100644
index 00000000..bfe7d546
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/features/optional/batch-operations.mdx
@@ -0,0 +1,11 @@
+---
+title: "Batch Operations"
+---
+
+
+Smart Wallet supports batch operations, which allow developers to perform multiple operations in a single transaction.
+This is useful for reducing the number of transactions required to perform a complex operation, such as a swap or a multi-step transaction.
+
+## Technical Details
+
+- Batch operations are facilitated via the [wallet_sendCalls](https://www.eip5792.xyz/reference/sendCalls) RPC.
\ No newline at end of file
diff --git a/_pages/identity/smart-wallet/concepts/features/optional/custom-gas-tokens.mdx b/_pages/identity/smart-wallet/concepts/features/optional/custom-gas-tokens.mdx
new file mode 100644
index 00000000..f0bf7d2b
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/features/optional/custom-gas-tokens.mdx
@@ -0,0 +1,12 @@
+---
+title: "ERC20 Paymasters"
+---
+
+
+Smart Wallet enables users to pay for gas in ERC20 tokens! This enables users to use app-specific tokens as payment for gas.
+
+
+## Technical Details
+
+- We recommend the [Coinbase Developer Platform](https://www.coinbase.com/developer-platform) paymaster, as it is [fully set up to work with Smart Wallet ERC-20 token gas payments](https://docs.base.org/tutorials/enable-erc20-gas-payments/).
+- Developers pay gas for their users in ETH and choose an exchange rate at which to receive an ERC-20 token as payment.
\ No newline at end of file
diff --git a/_pages/identity/smart-wallet/concepts/features/optional/gas-free-transactions.mdx b/_pages/identity/smart-wallet/concepts/features/optional/gas-free-transactions.mdx
new file mode 100644
index 00000000..b15b0ba7
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/features/optional/gas-free-transactions.mdx
@@ -0,0 +1,11 @@
+---
+title: "Gas-free Transactions"
+---
+
+
+Smart Wallet enables apps to pay for users' transaction gas fees, allowing free transactions or removing the need for users to hold native tokens.
+
+## Technical Details
+- Sponsored transactions are facilitated onchain via [ERC-4337](https://eips.ethereum.org/EIPS/eip-4337) compliant paymasters.
+- Apps must have a [ERC-7677](https://eips.ethereum.org/EIPS/eip-7677) compliant paymaster service.
+- Apps can share this paymaster to Smart Wallet via the ERC-7677 `paymasterService` capability on a `wallet_sendCalls` request.
\ No newline at end of file
diff --git a/_pages/identity/smart-wallet/concepts/features/optional/spend-permissions.mdx b/_pages/identity/smart-wallet/concepts/features/optional/spend-permissions.mdx
new file mode 100644
index 00000000..b7334880
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/features/optional/spend-permissions.mdx
@@ -0,0 +1,71 @@
+---
+title: "Spend Permissions"
+---
+
+
+Spend Permissions enable third-party signers to spend assets (native and ERC-20 tokens) from users' wallets. Once granted, spend permissions
+allow you to move your users' assets without any further signatures, unlocking use cases like subscriptions & trading bots. The reduced signing friction
+can also be leveraged to enhance UX for high-frequency use cases such as onchain games.
+
+Differences between Spend Permissions and ERC-20 permits:
+
+- Spend Permissions **supports all ERC-20 assets**, whereas permits only support ERC-20s that implement the permit standard
+- Spend Permissions **enable spending native tokens**
+- Spend Permissions offers granular **controls for recurrence**
+- The logic that controls asset movements is **implemented in the user's wallet**, not the asset contract
+
+## The `SpendPermission` details
+
+A spend permission is defined by the following parameters
+
+```solidity
+struct SpendPermission {
+ address account;
+ address spender;
+ address token;
+ uint160 allowance;
+ uint48 period;
+ uint48 start;
+ uint48 end;
+ uint256 salt;
+ bytes extraData;
+}
+```
+
+Spend Permissions are managed by a single manager contract, the `SpendPermissionManager`, which tracks the approval/revocation
+statuses of all permissions and enforces accurate spending limits and accounting.
+
+## Approving
+
+A user approves a spend permission by signing an [ERC-712](https://eips.ethereum.org/EIPS/eip-712) typed object that contains
+the spend permission properties. This signature and the corresponding spend permission details are then submitted to
+`SpendPermissionManager.approveWithSignature` to approve the spend permission onchain.
+
+## Revoking
+
+Users can revoke permissions at any time by calling `SpendPermissionManager.revoke`. Revocations are onchain calls that can be
+batched similar to other ERC-4337 transactions.
+
+Spenders can call `SpendPermissionManager.revokeAsSpender` to revoke their own permissions without requiring user interaction.
+
+Once a spend permission has been revoked, it can never be re-approved. Therefore, if a user wants to re-authorize a revoked spend permission,
+the spender will need to generate a new spend permission that has a unique hash from the original spend permission.
+If the parameters of the new spend permission are identical to the revoked permission, the `salt` field of the permission can be used to generate a unique hash.
+
+## Cycle accounting
+
+Spend Permissions offers granular controls for recurrence (e.g. 10 USDC / month).
+As the third-party signer spends user assets, the `SpendPermissionManager` contract tracks cumulative spend and enforces the per-period
+allowance. If there are multiple periods defined during the valid lifespan of the spend permission, the cumulative usage resets to 0
+at the beginning of the next period, allowing the spender to once again spend up to the allowance.
+
+This behavior is parameterized by the `start`, `end`, `period` and `allowance` properties of the permission.
+
+Note that spend permissions can be used for non-recurring allowances, either indefinite or with expiry, by setting
+a period duration that spans the entire range between start and end.
+
+A comprehensive example of spend permission accounting can be found [here](https://github.com/coinbase/spend-permissions/blob/main/docs/SpendPermissionAccounting.md).
+
+## Additional resources
+
+Contract sourcecode, diagrams, and additional documentation can be found in the open-source [contracts repository](https://github.com/coinbase/spend-permissions).
diff --git a/_pages/identity/smart-wallet/concepts/features/optional/sub-accounts.mdx b/_pages/identity/smart-wallet/concepts/features/optional/sub-accounts.mdx
new file mode 100644
index 00000000..d6190a06
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/features/optional/sub-accounts.mdx
@@ -0,0 +1,33 @@
+---
+title: "Sub Accounts"
+---
+
+import SubAccount from '@/components/smart-wallet/SubAccount';
+
+
+Sub Accounts are hierarchical, app-specific accounts that extend from a user's primary Smart Wallet. Each Sub Account operates as a distinct account that can:
+
+- Execute transactions and sign messages
+- Maintain separate asset management
+- Be fully controlled by both the universal Smart Wallet and authorized application logic
+
+## Give it a try!
+
+
+
+## Technical Details
+
+- Smart Wallet Ownership: The user's Smart Wallet acts as an owner of the Sub Account, allowing it to manage assets on the Sub Account and make calls from it.
+- App-Signer Agnostic: Sub Accounts are designed to be agnostic to whatever signing software an app wants to use: whether in-browser CryptoKey or server signers from teams like Privy or Turnkey.
+- When an app requests a Sub Account creation and the user approves, all future signing and transaction requests will use the Sub Account.
+
+Refer to the [Implementation Guide](/identity/smart-wallet/guides/sub-accounts) or the [Technical Reference](/identity/smart-wallet/technical-reference/sdk/sub-account-reference) for more details on how to create and manage Sub Accounts.
+
+
+**Development**
+
+Sub Accounts are currently only available in the Coinbase Smart Wallet development environment.
+
+If you would like to use Sub Accounts with Coinbase Smart Wallet in our production environment, please [reach out](https://discord.com/invite/buildonbase) to the Base team.
+
+
diff --git a/_pages/identity/smart-wallet/concepts/usage-details/gas-usage.mdx b/_pages/identity/smart-wallet/concepts/usage-details/gas-usage.mdx
new file mode 100644
index 00000000..2a9d7ab6
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/usage-details/gas-usage.mdx
@@ -0,0 +1,21 @@
+---
+title: "Gas Usage"
+---
+
+
+Smart Wallets use more gas for transactions than traditional Ethereum accounts. On L2 networks, the cost difference to the user is a matter of cents.
+The gas difference is due to the additional overhead required for:
+
+1. **ERC-4337 Bundling**
+2. **Smart Contract Operations**, including one time deployment of the Smart Wallet contract
+3. **Signature Verification**
+
+## Gas Usage Breakdown
+
+Here's a rough comparison of gas usage per account:
+
+| Operation Type | Traditional Ethereum Account | Smart Wallet |
+|---------------|------------|--------------|
+| Native Token Transfer | ~21,000 gas | ~100,000 gas |
+| ERC-20 Token Transfer | ~65,000 gas | ~150,000 gas |
+| First Deployment | N/A | ~300,000+ gas |
\ No newline at end of file
diff --git a/_pages/identity/smart-wallet/concepts/usage-details/popups.mdx b/_pages/identity/smart-wallet/concepts/usage-details/popups.mdx
new file mode 100644
index 00000000..2bb616ac
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/usage-details/popups.mdx
@@ -0,0 +1,37 @@
+---
+title: "Popup Tips"
+---
+
+
+## Overview
+When a Smart Wallet is connected and Coinbase Wallet SDK receives a request, it opens
+[keys.coinbase.com](https://keys.coinbase.com/) in a popup window and passes the request to the popup for handling.
+Keep the following points in mind when working with the Smart Wallet popup.
+
+## Default blocking behavior
+- Most modern browsers block all popups by default, unless they are triggered by a click.
+- If a popup is blocked the browser shows a notification to the user, allowing them to manage popup settings.
+
+### What to do about it
+- Ensure there is no additional logic between the button click and the request to open the Smart Wallet popup,
+as browsers might perceive the request as programmatically initiated.
+- If logic is unavoidable, keep it minimal and test thoroughly in all supported browsers.
+
+## `Cross-Origin-Opener-Policy`
+If the Smart Wallet popup opens and displays an error or infinite spinner, it may be due to the dapp's `Cross-Origin-Opener-Policy`. Be sure to use a directive that allows the Smart Wallet popup to function.
+
+- ✅ Allows Smart Wallet popup to function
+ - `unsafe-none` (default)
+ - `same-origin-allow-popups` (recommended)
+- ❌ Breaks Smart Wallet popup
+ - `same-origin`
+
+For more detailed information refer to the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy).
+
+
+## Smart Wallet popup 'linger' behavior
+- Sometimes a dapp may programmatically make a followup request based on the response to a previous request.
+Normally, browsers block these programmatic requests to open popups.
+- To address this, after the Smart Wallet popup responds to a request, it will linger for 200ms to listen for another incoming request before closing.
+ - If a request is received *during* this 200ms window, it will be received and handled within the same popup window.
+ - If a request is received *after* the 200ms window and the popup has closed, opening the Smart Wallet popup will be blocked by the browser.
\ No newline at end of file
diff --git a/docs/pages/identity/smart-wallet/concepts/usage-details/self-calls.mdx b/_pages/identity/smart-wallet/concepts/usage-details/self-calls.mdx
similarity index 87%
rename from docs/pages/identity/smart-wallet/concepts/usage-details/self-calls.mdx
rename to _pages/identity/smart-wallet/concepts/usage-details/self-calls.mdx
index 75e4af1d..ddef4cdb 100644
--- a/docs/pages/identity/smart-wallet/concepts/usage-details/self-calls.mdx
+++ b/_pages/identity/smart-wallet/concepts/usage-details/self-calls.mdx
@@ -1,4 +1,7 @@
-# Self Calls
+---
+title: "Self Calls"
+---
+
For security reasons, we do not allow 3rd party apps to make calls to a user's own Smart Wallet address.
This could allow apps to change owners, upgrade the user's account, or cause other issues.
\ No newline at end of file
diff --git a/_pages/identity/smart-wallet/concepts/usage-details/signature-verification.mdx b/_pages/identity/smart-wallet/concepts/usage-details/signature-verification.mdx
new file mode 100644
index 00000000..cb692fed
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/usage-details/signature-verification.mdx
@@ -0,0 +1,44 @@
+---
+title: "Signature Verification"
+---
+
+
+There are important details to verifying smart contract wallet signatures. The smart contract itself
+cannot produce a signature. Instead, the contract has a function
+```solidity
+function isValidSignature(bytes32 _hash, bytes memory _signature) returns (bytes4 magicValue);
+```
+which can be called to determine if signature should be considered valid (defined in [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271)).
+
+In the case of Smart Wallet, a signature is considered valid if it was signed by a current signer (aka "owner") of the Smart Wallet.
+For example, a user can sign a message with their passkey, and when `isValidSignature` is called on their Smart Wallet,
+it would return that the signature is valid because their passkey is an owner.
+
+There is also an additional complexity: users receive their Smart Wallet address immediately upon passkey registration,
+and are able to begin signing for their Smart Wallet,
+but their Smart Wallet is not deployed on a chain until the first transaction on that chain.
+
+[ERC-6492](https://eips.ethereum.org/EIPS/eip-6492) describes
+> With the rising popularity of account abstraction, we often find that the best user experience
+for contract wallets is to defer contract deployment until the first user transaction,
+therefore not burdening the user with an additional deploy step before they can use their account.
+However, at the same time, many dApps expect signatures, not only for interactions, but also just for logging in.
+
+So the challenge is, how do we verify signatures in a way that works for both deployed and undeployed Smart Wallets?
+ERC-6492 has a solution for this, which Smart Wallet has adopted.
+We won't go unto all the details here, read the ERC linked above, if you're looking for that.
+Below we cover the minimum work needed to support on and off chain signature validation for Smart Wallet.
+
+## Offchain
+For purely offchain signature verification––such as "Sign-In With Ethereum"––ensure you are using
+a ERC-6492-compliant signature verification library.
+We recommend Viem's [`verifyMessage`](https://viem.sh/docs/actions/public/verifyMessage#verifymessage) ([example](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/components/SignMessage.tsx#L28))
+and [verifyTypedData](https://viem.sh/docs/actions/public/verifyTypedData) ([example](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/components/TypedSign.tsx#L46)).
+
+
+## Onchain
+For signatures that will be used onchain, such as with [Permit2](https://github.com/Uniswap/permit2)
+or [Seaport](https://github.com/ProjectOpenSea/seaport) developers will need to inspect the signature offchain
+and remove unneeded ERC-6492 data, if it is present.
+We recommend using the [parseErc6492Signature](https://viem.sh/docs/utilities/parseErc6492Signature#parseerc6492signature)
+util from Viem.
\ No newline at end of file
diff --git a/_pages/identity/smart-wallet/concepts/usage-details/simulations.mdx b/_pages/identity/smart-wallet/concepts/usage-details/simulations.mdx
new file mode 100644
index 00000000..0776dc4e
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/usage-details/simulations.mdx
@@ -0,0 +1,14 @@
+---
+title: "Transaction Simulation Data"
+---
+
+There is a hidden feature which enables you to easily copy transaction simulation request and response data which can then be pasted it in a text editor to inspect.
+
+## Instructions
+- Click the area defined in red **_five times_**, then paste the copied data in a text editor.
+
+
+
+ 
+
+
\ No newline at end of file
diff --git a/_pages/identity/smart-wallet/concepts/usage-details/wallet-library-support.mdx b/_pages/identity/smart-wallet/concepts/usage-details/wallet-library-support.mdx
new file mode 100644
index 00000000..253ce0c2
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/usage-details/wallet-library-support.mdx
@@ -0,0 +1,16 @@
+---
+title: "Wallet Library Support"
+---
+
+
+Below are some popular wallet libraries and what we know of their plans for day 1 support for Smart Wallet.
+
+| Name | Support |
+| ---------------------------------------------------------------------------------- | ------- |
+| [Dynamic](https://docs.dynamic.xyz/wallets/advanced-wallets/coinbase-smart-wallet) | ✅ |
+| [Privy](https://docs.privy.io/guide/react/recipes/misc/coinbase-smart-wallets) | ✅ |
+| [ThirdWeb](http://portal.thirdweb.com/connect) | ✅ |
+| [ConnectKit](https://docs.family.co/connectkit) | ✅ |
+| [Web3Modal](https://docs.reown.com/web3modal/react/smart-accounts) | ✅ |
+| [Web3-Onboard](https://www.blocknative.com/coinbase-wallet-integration) | ✅ |
+| [RainbowKit](https://www.rainbowkit.com/) | ✅ |
diff --git a/_pages/identity/smart-wallet/concepts/what-is-smart-wallet.mdx b/_pages/identity/smart-wallet/concepts/what-is-smart-wallet.mdx
new file mode 100644
index 00000000..ca6cdaa2
--- /dev/null
+++ b/_pages/identity/smart-wallet/concepts/what-is-smart-wallet.mdx
@@ -0,0 +1,19 @@
+---
+title: "What is Smart Wallet?"
+---
+
+
+Smart Wallet is a multi-chain self-custodial cryptocurrency wallet.
+It enables users to create an onchain account in seconds
+with no app or extension required, thanks to its reliance on [Passkeys](https://passkeys.com).
+It is often described as a more secure and user-friendly way to interact with onchain applications.
+
+## What can you do with Smart Wallet?
+
+Smart Wallet combines the best features of a wallet and an app specific account:
+
+- [Use MagicSpend](/identity/smart-wallet/concepts/features/built-in/MagicSpend): Use Coinbase balances onchain;
+- [Get Free Sponsored Transactions](/identity/smart-wallet/concepts/base-gasless-campaign): Use Paymaster to sponsor transactions and get qualified for up to $15k in gas credits;
+- [Support Sub Accounts](/identity/smart-wallet/guides/sub-accounts): Improve user experience with embedded accounts controlled by their Smart Wallet,
+ eliminating the need for users to sign a transaction for every onchain action.
+- [Support Spend Permissions](/identity/smart-wallet/guides/spend-permissions): Enable third-party signers to spend user assets
diff --git a/_pages/identity/smart-wallet/contribute/contribute-to-smart-wallet-docs.mdx b/_pages/identity/smart-wallet/contribute/contribute-to-smart-wallet-docs.mdx
new file mode 100644
index 00000000..a40f51db
--- /dev/null
+++ b/_pages/identity/smart-wallet/contribute/contribute-to-smart-wallet-docs.mdx
@@ -0,0 +1,177 @@
+---
+title: "Contribute to the Smart Wallet Docs"
+---
+
+This guide is intended for all contributors who are adding new features, content, or making updates to the Smart Wallet documentation.
+Following these guidelines ensures consistency and maintains
+the documentation structure, making it easier for developers to find information.
+
+
+**Why Documentation is Important**
+
+Good documentation significantly accelerates developer adoption. Focus on creating content that helps developers understand and implement Smart Wallet features efficiently, while maintaining the documentation's structural integrity.
+
+
+
+## Documentation Structure Guidelines
+
+### Core Principle: Maintain Existing Structure
+
+The Smart Wallet documentation is organized into the following main sections:
+
+1. **Quickstart**
+2. **Concepts**
+3. **Guides**
+4. **Examples**
+5. **Technical Reference**
+6. **Contribute**
+
+
+ **Do not create new top-level sections**
+
+ All new content must fit within these existing sections.
+
+
+
+### Section Purpose and Content Placement
+
+When adding new content, determine the appropriate section based on the following criteria:
+
+#### Quickstart
+
+- End-to-end guides for getting developers up and running quickly
+- Should remain focused and concise
+- Update only when there are fundamental changes to the initial setup process
+
+#### Concepts
+
+- Explanatory content about Smart Wallet components, architecture, and design philosophy
+- Add new concept documentation when introducing fundamentally new capabilities or paradigms
+- Subsections include:
+ - Intro
+ - Built-in Features
+ - Optional Features
+ - Additional conceptual topics as needed
+
+#### Guides
+
+- Step-by-step tutorials for specific tasks and implementation scenarios
+- **All guides should live under the main "Guides" section**
+- Avoid creating deep hierarchical subsections unless absolutely necessary
+- Try to keep the number of subsections to a minimum, preferably only one page per guide
+- Although not required, guides should preferably have an accompanying GitHub repository
+- Name guides clearly with action-oriented titles (e.g., "Implementing Multi-Signature Verification" rather than "Multi-Sig Guide")
+
+#### Examples
+
+- Complete, working code examples demonstrating real-world use cases
+- Each example should be self-contained and fully functional
+- Each example should have an accompanying GitHub repository
+- Include comments explaining key implementation details
+- Examples should cover all of the built-in and optional features of the Smart Wallet
+
+#### Technical Reference
+
+- Comprehensive technical documentation of APIs, methods, components, and configurations
+- Structured reference material rather than tutorial content
+- Include parameter descriptions, return values, and usage examples
+- All technical specifications for new features go here, not in separate sections
+
+#### Contribute
+
+- Information for contributors to the Smart Wallet project
+- Update when contribution processes change
+
+
+**Avoiding Subsection Proliferation**
+- **For Guides**: Keep all guides at the same level under the Guides section
+- **For Technical Reference**: Organize by component or feature, not by use case
+- When tempted to add a new subsection, consider if the content could be reorganized to fit existing sections
+- Use cross-referencing between related content rather than creating new organizational structures
+
+
+
+## Documentation Style Guidelines
+
+### Writing Style
+
+1. **Be concise**: Use simple, direct language. Avoid unnecessary words.
+2. **Consistency**: Maintain consistent terminology throughout documentation.
+3. **Persona-focused**: Think about the persona of the reader and write accordingly.
+4. **Happy Path**: Focus on the happy path, but don't forget to mention the alternative paths.
+5. [**AI-friendly**](#ai-friendly-writing-tips): Write in a way that is easy for AI to understand and follow.
+
+
+**Make sure to review any AI generated content**
+
+If you use AI to generate content:
+
+- Make sure to review it carefully before submission.
+- Make sure that the content follows the guidelines in this document.
+- Make sure that the content is easy for AI to understand and follow.
+
+
+
+### AI-friendly Writing Tips
+
+- Make sure you use explicit language in your file names, headings, and content.
+- Make active linking references to the relevant guides and examples.
+- Use bulleted lists for steps or options.
+- Explicitely name and reference the libraries you are using.
+- Use code blocks to highlight code.
+- Use semantic urls that make sense even without context. Avoid abbreviations.
+
+
+ **Think like a Large Language Model**
+
+When writing documentation, think about how a Large Language Model would understand the content.
+
+You should continuously ask yourself:
+
+- "Would a Large Language Model be able to understand this content?"
+- "Would a Large Language Model be able to follow this content?"
+- "Would a Large Language Model be able to use this content?"
+
+If you can't answer yes to all of these questions, you need to rewrite the content.
+
+
+
+### Formatting
+
+1. **Markdown usage**:
+
+ - Use proper heading hierarchy (# for main titles, ## for section headings, etc.)
+ - Use code blocks with language specification (```javascript)
+ - Use tables for parameter references
+ - Use bulleted lists for steps or options
+
+2. **Code examples**:
+ - Include complete, working code examples
+ - Comment code thoroughly
+ - Follow the project's coding style guide
+
+### Abbreviations and Terminology
+
+1. **First reference**: The first time you use an abbreviation or technical term, spell it out followed by the abbreviation in parentheses. Example: "Account Abstraction (AA)"
+2. **Consistency**: Use the same term for the same concept throughout the documentation
+3. **Technical Reference**: Keep the guides and examples to a minimal size. Put the comprehensive technical details in the Technical Reference section.
+
+## Review Checklist Before Submission
+
+- [ ] Content fits within existing structure
+- [ ] No new top-level sections created
+- [ ] Minimal subsection creation
+- [ ] Consistent terminology used throughout
+- [ ] Abbreviations properly introduced
+- [ ] Code examples are complete and functional
+- [ ] Cross-references to related documentation added
+- [ ] Documentation follows style guidelines
+- [ ] Documentation is written in a way that is easy for AI to understand and follow
+
+## Submission Process
+
+1. Create a documentation Pull Request (PR) to the [repository](https://github.com/base/web) with your changes
+2. Ensure your PR includes updates to all relevant sections and respects the instructions in this guide
+3. Request review from the documentation team
+4. Address feedback and make necessary revisions
+5. Once approved, the PR will be merged and published
diff --git a/_pages/identity/smart-wallet/contribute/security-and-bug-bounty.mdx b/_pages/identity/smart-wallet/contribute/security-and-bug-bounty.mdx
new file mode 100644
index 00000000..114946ed
--- /dev/null
+++ b/_pages/identity/smart-wallet/contribute/security-and-bug-bounty.mdx
@@ -0,0 +1,18 @@
+---
+title: "Security and Bug Bounty"
+---
+
+
+## Security Audits
+
+Smart Wallet has undergone multiple security audits. You can find the full list of our audits in the main Smart Wallet repository.
+
+[Smart Wallet audits](https://github.com/coinbase/smart-wallet/tree/main/audits)
+
+## Bug Bounty Program
+
+The Coinbase/Base Bug Bounty program is a crowdsourced initiative that rewards security researchers
+for responsibly reporting vulnerabilities in Base's smart contracts and infrastructure.
+
+To report a bug, please follow the instructions in
+the [HackerOne Bug Bounty Program](https://hackerone.com/coinbase?type=team).
diff --git a/_pages/identity/smart-wallet/examples/coin-a-joke-app.mdx b/_pages/identity/smart-wallet/examples/coin-a-joke-app.mdx
new file mode 100644
index 00000000..58f23de8
--- /dev/null
+++ b/_pages/identity/smart-wallet/examples/coin-a-joke-app.mdx
@@ -0,0 +1,210 @@
+---
+title: "Coin Your Bangers: Turn Your Jokes into Coins"
+---
+
+import { GithubRepoCard } from "/snippets/GithubRepoCard.mdx"
+
+
+In this guide we illustrate how to use Smart Wallet and Zora's content coin SDK to create an engaging consumer application with great UX.
+
+Memecoins and culturecoins have been catalysts of the onchain economy, allowing people to rally around concepts that are fun and engaging with a potential upside.
+
+Memecoins are great for long term community and culture building onchain.
+Contentcoins are an emergent category of coins focused on valuing individual pieces of content and providing monetization for creators.
+
+
+
+
A Table showing the difference between memecoins and contentcoins
+
+
+Apps like [Zora](https://zora.co/) or [Flaunch](https://flaunch.gg/) illustrate the power of contentcoins.
+They create a new category of apps that we may call "contentcoin apps".
+
+## Overview
+
+This example explains "Coin Your Bangers", a full onchain app that lets users turn their jokes into coins on Base.
+The app uses OpenAI to generate coin metadata and the [Zora SDK](https://docs.zora.co/coins/sdk/create-coin) to coin the content.
+From the user's perspective, the process is simple:
+
+1. Input a joke or funny content
+2. Generate coin metadata using AI
+3. Deploy a new Zora Coin on Base
+4. Get transaction confirmation and links
+5. Start earning referal fees from your the app
+6. Start earning trading fees from your Coin
+
+The full codebase of this app is available on Github:
+
+
+
+## Why use Smart Wallet to build your contentcoin app?
+
+Onboarding users to end-consumer onchain apps has always been a challenge, mainly
+due to the need for a wallet. In the case of web apps, this has always come in the form of a
+new browser extension to install or a new mobile app to download.
+In order to solve this, Smart Wallet was created.
+
+Smart Wallet is a new way to universally sign in into any onchain app.
+This example shows how Smart Wallet can seamlessly onboard users without the need for an installation.
+If you are building an app for creators and end-consumers, it is important to meet them where they are.
+Smart Wallet allows you to onboard them with passkeys, a simple pop-up, and no new extension to install.
+
+VIDEO
+
+
+**Does Zora use Smart Wallet?**
+
+Yes, Zora integrates Smart Wallet smart contracts directly into their protocol.
+This allows users to sign in to the app and create contentcoins without any additional steps.
+
+You can see the share of Zora from Smart Wallet transactions on the [Dune Dashboard](https://dune.com/wilsoncusack/coinbase-smart-wallet-kpis).
+
+
+
+## Run the app
+
+1. Open a terminal and run the following commands:
+
+```bash [Terminal]
+# Clone the repository
+git clone https://github.com/base/demos.git
+
+# Navigate to the app
+cd demos/smart-wallet/coin-your-joke
+
+# Install dependencies
+pnpm install
+
+# Copy the environment variables
+cp .env.example .env
+```
+
+2. Open the repository in your favorite editor (eg. Cursor, VSCode, etc.) and edit the `.env` file with your own values as specified in the [README](https://github.com/base/demos/blob/master/smart-wallet/coin-your-joke/README.md).
+
+```
+ENV=local or prod
+NEXT_PUBLIC_URL=your_deployment_url (http://localhost:3000 for local development)
+OPENAI_API_KEY=your_openai_api_key
+NEXT_TELEMETRY_DISABLED=1
+```
+
+3. Run the app
+
+```bash [Terminal]
+pnpm dev
+```
+
+## Key Components
+
+### Wagmi Config (`wagmi.ts`)
+
+The wagmi config is used to set up the Smart Wallet client.
+
+```ts
+import { http, cookieStorage, createConfig, createStorage } from "wagmi";
+import { base, baseSepolia } from "wagmi/chains";
+import { coinbaseWallet } from "wagmi/connectors";
+
+export const cbWalletConnector = coinbaseWallet({
+ appName: "Smart Wallet Zora Coiner",
+ preference: "smartWalletOnly",
+});
+
+export function getConfig() {
+ return createConfig({
+ chains: [base, baseSepolia],
+ connectors: [cbWalletConnector],
+ // ... other config options
+ });
+}
+
+// ... other wagmi config options
+```
+
+### CoinButton Component (`CoinButton.tsx`)
+
+The deployment button component handles the actual token creation:
+
+```tsx
+export function CoinButton({
+ name,
+ symbol,
+ uri,
+ initialPurchaseWei = BigInt(0),
+ onSuccess,
+ onError,
+ className
+}: CreateCoinArgs) {
+ // ... implementation
+}
+```
+
+Key features:
+
+- Using Zora's [`createCoin`](https://docs.zora.co/coins/sdk/create-coin) function to create a new coin
+- Using Smart Wallet and Wagmi to submit the transaction
+- Loading states and error handling
+- Transaction status tracking
+
+### Metadata Generation API
+
+The API endpoint uses OpenAI to generate creative coin metadata:
+
+```typescript
+// Example API structure
+async function generateMetadata(joke: string) {
+ // 1. Process the joke using OpenAI
+ // 2. Generate coin name and symbol
+ // 3. Create metadata JSON
+ // 4. Return coin parameters
+}
+```
+
+Features:
+
+- OpenAI integration for creative naming
+- Metadata JSON generation
+- URI creation for token metadata
+- Error handling and validation
+
+## Technical Stack
+
+- **Frontend**: Next.js 13+ with App Router
+- **Styling**: Tailwind CSS
+- **Onchain tools**:
+ - Smart Wallet for Sign In
+ - Wagmi for contract interactions
+ - Zora SDK for coining content
+- **AI**: OpenAI API for metadata generation
+- **Network**: Base
+
+## Deployment
+
+You can deploy your app to Vercel by following the instructions [here](https://vercel.com/docs/deployments).
+
+## Conclusion
+
+This example demonstrates how to build a full-featured onchain application that combines:
+
+- Smart Wallet for Sign In
+- AI capabilities
+- Coin creation with [Zora's SDK](https://docs.zora.co/coins/sdk/create-coin)
+- Wagmi for contract interactions
+- Error handling
+- Transaction management
+
+The modular architecture makes it easy to extend or modify for other use cases beyond joke tokenization.
diff --git a/_pages/identity/smart-wallet/guides/batch-transactions.mdx b/_pages/identity/smart-wallet/guides/batch-transactions.mdx
new file mode 100644
index 00000000..59fe52b4
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/batch-transactions.mdx
@@ -0,0 +1,183 @@
+---
+title: "Batch Transactions"
+---
+
+
+With Smart Wallet, you can send multiple onchain calls in a single transaction. Doing so improves the UX of multi-step interactions by reducing them to a single click. A common example of where you might want to leverage batch transactions is an ERC-20 `approve` followed by a swap.
+
+You can submit batch transactions by using new `wallet_sendCalls` RPC, defined [here](https://eip5792.xyz/reference/sendCalls).
+
+## Using Wagmi
+
+
+The `useWriteContracts` and `useCapabilities` hooks used below rely on new wallet RPC and are not yet supported in most wallets.
+It is recommended to have a fallback function if your app supports wallets other than Smart Wallet.
+
+
+
+
+
+ Smart Wallet will submit multiple calls as part of a single transaction. However, if your app supports other wallets, and you want to check that multiple calls will be submitted atomically (in a single transaction), check the wallet's capabilities.
+
+ ```ts twoslash [App.tsx]
+ // @filename: App.tsx
+ import React from 'react'
+ // ---cut---
+ import { useCapabilities } from 'wagmi/experimental'
+
+ function App() {
+ const { data: capabilities } = useCapabilities() // [!code hl]
+ // @log: {
+ // @log: 84532: {
+ // @log: atomicBatch: {
+ // @log: supported: true,
+ // @log: },
+ // @log: }
+ // @log: }
+
+ return
+ }
+ ```
+
+ The `useCapabilities` method will return, per chain, the capabilities that the connected wallet supports. If the connected wallet supports atomic batching, it will return an `atomicBatch` capability with a `supported` field equal to `true` for each chain it supports atomic batching on.
+
+
+
+ If you have your smart contract ABIs, the easiest way to send multiple calls is to use the Wagmi `useWriteContracts` hook.
+
+ ```ts twoslash [App.tsx]
+ // @filename: App.tsx
+ import React from 'react'
+ // ---cut---
+ import { useAccount } from 'wagmi'
+ import { useWriteContracts } from 'wagmi/experimental'
+
+ const abi = [
+ {
+ stateMutability: 'nonpayable',
+ type: 'function',
+ inputs: [{ name: 'to', type: 'address' }],
+ name: 'safeMint',
+ outputs: [],
+ }
+ ] as const
+
+ function App() {
+ const account = useAccount()
+ const { writeContracts } = useWriteContracts() // [!code hl]
+
+ const handleMint = () => {
+ writeContracts({ // [!code hl]
+ contracts: [ // [!code hl]
+ { // [!code hl]
+ address: "0x119Ea671030FBf79AB93b436D2E20af6ea469a19", // [!code hl]
+ abi, // [!code hl]
+ functionName: "safeMint", // [!code hl]
+ args: [account.address], // [!code hl]
+ }, // [!code hl]
+ { // [!code hl]
+ address: "0x119Ea671030FBf79AB93b436D2E20af6ea469a19", // [!code hl]
+ abi, // [!code hl]
+ functionName: "safeMint", // [!code hl]
+ args: [account.address], // [!code hl]
+ } // [!code hl]
+ ], // [!code hl]
+ }) // [!code hl]
+ }
+
+ return (
+
+ Mint
+
+ )
+ }
+ ```
+
+
+
+ The `useWriteContracts` hook returns an object with a `data` field. This `data` is a call bundle identifier. Use the Wagmi `useCallsStatus` hook with this identifier to check on the status of your calls.
+
+ This will return a `PENDING` or `CONFIRMED` status along with a subset of a transaction receipt.
+
+ ```ts twoslash [App.tsx]
+ // @errors: 2352 2367
+ // @filename: App.tsx
+ import React from 'react'
+ // ---cut---
+ import { useAccount } from 'wagmi'
+ import { useWriteContracts, useCallsStatus } from 'wagmi/experimental'
+
+ const abi = [
+ {
+ stateMutability: 'nonpayable',
+ type: 'function',
+ inputs: [{ name: 'to', type: 'address' }],
+ name: 'safeMint',
+ outputs: [],
+ }
+ ] as const
+
+ function App() {
+ const account = useAccount()
+ const { data: id, writeContracts } = useWriteContracts()
+ const { data: callsStatus } = useCallsStatus({ // [!code hl]
+ id: id as string, // [!code hl]
+ query: { // [!code hl]
+ enabled: !!id, // [!code hl]
+ // Poll every second until the calls are confirmed // [!code hl]
+ refetchInterval: (data) => // [!code hl]
+ data.state.data?.status === "CONFIRMED" ? false : 1000, // [!code hl]
+ }, // [!code hl]
+ }); // [!code hl]
+ // @log: {
+ // @log: status: 'CONFIRMED',
+ // @log: receipts: [
+ // @log: {
+ // @log: logs: [
+ // @log: {
+ // @log: address: '0x...',
+ // @log: topics: [
+ // @log: '0x...'
+ // @log: ],
+ // @log: data: '0x...'
+ // @log: },
+ // @log: ],
+ // @log: status: 'success',
+ // @log: blockHash: '0x...',
+ // @log: blockNumber: 122414523n,
+ // @log: gasUsed: 390000n,
+ // @log: transactionHash: '0x...'
+ // @log: }
+ // @log: ]
+ // @log: }
+
+ const handleMint = () => {
+ writeContracts({
+ contracts: [
+ {
+ address: "0x...",
+ abi,
+ functionName: "safeMint",
+ args: [account.address],
+ },
+ {
+ address: "0x...",
+ abi,
+ functionName: "safeMint",
+ args: [account.address],
+ }
+ ],
+ })
+ }
+
+ return (
+
+
Mint
+ {callsStatus &&
Status: {callsStatus.status}
}
+
+ )
+ }
+ ```
+
+
+
diff --git a/_pages/identity/smart-wallet/guides/components/create-wallet-button.mdx b/_pages/identity/smart-wallet/guides/components/create-wallet-button.mdx
new file mode 100644
index 00000000..9f018b57
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/components/create-wallet-button.mdx
@@ -0,0 +1,529 @@
+---
+title: "Create Wallet Button"
+---
+
+## Simplify user onboarding with Smart Wallet
+
+For the best onboarding experience, we recommend adding a highly visible 'Create' or 'Create Wallet' button to your dapp's homepage.
+
+
+ 
+
+
+Adding a 'Create Wallet' button to a page streamlines the onboarding experience for a new user and gets them ready to use your dapp in a few seconds.
+
+
+We're offering additional incentives to dapps who implement this new approach. By integrating a create wallet button on the front page of your app, you are eligible for:
+
+- Increased volume of gas credits via our Cloud paymaster
+- Comarketing and promotional opportunities with Coinbase
+- Featured placement in our dapp listings on wallet.coinbase.com
+
+For more information, please [fill out the
+form](https://docs.google.com/forms/d/e/1FAIpQLSdmTnSjbI-wkkf2TzmuSEv3iYzkbZBsY08xPdK-54J0Ipt4PA/viewform)
+on [coinbase.com/smart-wallet](http://coinbase.com/smart-wallet) so our team can get
+in touch with you.
+
+
+
+## Implementation
+
+**We recommend two paths for implementation:**
+
+1. Match our branded Create Wallet button
+1. Match your own apps look and feel in the Create button
+
+Example:
+
+
+
+ 
+
+

+
+
+
+
+### With Wagmi
+
+To implement the Create Wallet button with Wagmi, all you need to do is call `connect`.
+
+
+```tsx twoslash [BlueCreateWalletButton.tsx] filename="BlueCreateWalletButton.tsx"
+import React, { useCallback } from 'react';
+import { useConnect } from 'wagmi';
+import { CoinbaseWalletLogo } from './CoinbaseWalletLogo';
+
+const buttonStyles = {
+ background: 'transparent',
+ border: '1px solid transparent',
+ boxSizing: 'border-box',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ width: 200,
+ fontFamily: 'Arial, sans-serif',
+ fontWeight: 'bold',
+ fontSize: 18,
+ backgroundColor: '#0052FF',
+ paddingLeft: 15,
+ paddingRight: 30,
+ borderRadius: 10,
+};
+
+export function BlueCreateWalletButton() {
+ const { connectors, connect, data } = useConnect();
+
+ const createWallet = useCallback(() => {
+ const coinbaseWalletConnector = connectors.find(
+ (connector) => connector.id === 'coinbaseWalletSDK'
+ );
+ if (coinbaseWalletConnector) {
+ connect({ connector: coinbaseWalletConnector });
+ }
+ }, [connectors, connect]);
+ return (
+
+
+ Create Wallet
+
+ );
+}
+```
+
+```tsx twoslash [BlackCreateWalletButton.tsx] filename="BlackCreateWalletButton.tsx"
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { useConnect } from 'wagmi';
+import { CoinbaseWalletLogo } from './CoinbaseWalletLogo';
+
+const GRADIENT_BORDER_WIDTH = 2;
+
+const buttonStyles = {
+ background: 'transparent',
+ border: '1px solid transparent',
+ boxSizing: 'border-box',
+};
+
+const contentWrapperStyle = {
+ position: 'relative',
+};
+
+function Gradient({ children, style, isAnimationDisabled = false }) {
+ const [isAnimating, setIsAnimating] = useState(false);
+ const gradientStyle = useMemo(() => {
+ const rotate = isAnimating ? '720deg' : '0deg';
+ return {
+ transform: `rotate(${rotate})`,
+ transition: isAnimating
+ ? 'transform 2s cubic-bezier(0.27, 0, 0.24, 0.99)'
+ : 'none',
+ ...style,
+ };
+ }, [isAnimating, style]);
+
+ const handleMouseEnter = useCallback(() => {
+ if (isAnimationDisabled || isAnimating) return;
+ setIsAnimating(true);
+ }, [isAnimationDisabled, isAnimating, setIsAnimating]);
+
+ useEffect(() => {
+ if (!isAnimating) return;
+ const animationTimeout = setTimeout(() => {
+ setIsAnimating(false);
+ }, 2000);
+ return () => {
+ clearTimeout(animationTimeout);
+ };
+ }, [isAnimating]);
+
+ return (
+
+ );
+}
+
+export function BlackCreateWalletButton({ height = 66, width = 200 }) {
+ const { connectors, connect } = useConnect();
+
+ const minButtonHeight = 48;
+ const minButtonWidth = 200;
+ const buttonHeight = Math.max(minButtonHeight, height);
+ const buttonWidth = Math.max(minButtonWidth, width);
+ const gradientDiameter = Math.max(buttonHeight, buttonWidth);
+ const styles = useMemo(
+ () => ({
+ gradientContainer: {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'black',
+ borderRadius: buttonHeight / 2,
+ height: buttonHeight,
+ width: buttonWidth,
+ boxSizing: 'border-box',
+ overflow: 'hidden',
+ },
+ gradient: {
+ background:
+ 'conic-gradient(from 180deg, #45E1E5 0deg, #0052FF 86.4deg, #B82EA4 165.6deg, #FF9533 255.6deg, #7FD057 320.4deg, #45E1E5 360deg)',
+ position: 'absolute',
+ top: -buttonHeight - GRADIENT_BORDER_WIDTH,
+ left: -GRADIENT_BORDER_WIDTH,
+ width: gradientDiameter,
+ height: gradientDiameter,
+ },
+ buttonBody: {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ boxSizing: 'border-box',
+ backgroundColor: '#000000',
+ height: buttonHeight - GRADIENT_BORDER_WIDTH * 2,
+ width: buttonWidth - GRADIENT_BORDER_WIDTH * 2,
+ fontFamily: 'Arial, sans-serif',
+ fontWeight: 'bold',
+ fontSize: 18,
+ borderRadius: buttonHeight / 2,
+ position: 'relative',
+ paddingRight: 10,
+ },
+ }),
+ [buttonHeight, buttonWidth, gradientDiameter]
+ );
+
+ const createWallet = useCallback(() => {
+ const coinbaseWalletConnector = connectors.find(
+ (connector) => connector.id === 'coinbaseWalletSDK'
+ );
+ if (coinbaseWalletConnector) {
+ connect({ connector: coinbaseWalletConnector });
+ }
+ }, [connectors, connect]);
+
+ return (
+
+
+
+
+
+ Create Wallet
+
+
+
+
+ );
+}
+```
+
+```tsx twoslash [CoinbaseWalletLogo.tsx] filename="CoinbaseWalletLogo.tsx"
+import React from 'react';
+
+const defaultContainerStyles = {
+ paddingTop: 2,
+};
+
+export function CoinbaseWalletLogo({
+ size = 26,
+ containerStyles = defaultContainerStyles,
+}) {
+ return (
+
+ );
+}
+```
+
+```ts twoslash [wagmi.ts]
+// @noErrors: 2554
+import { http, createConfig } from 'wagmi';
+import { baseSepolia } from 'wagmi/chains';
+import { coinbaseWallet } from 'wagmi/connectors';
+
+export const config = createConfig({
+ chains: [baseSepolia],
+ connectors: [
+ coinbaseWallet({
+ appName: 'Create Wagmi',
+ preference: 'smartWalletOnly',
+ }),
+ ],
+ transports: {
+ [baseSepolia.id]: http(),
+ },
+});
+
+declare module 'wagmi' {
+ interface Register {
+ config: typeof config;
+ }
+}
+```
+
+
+#### Notes
+
+- For more detail, view the [`useConnect` documentation](https://wagmi.sh/react/api/hooks/useConnect).
+- Upon successful connection, account information can be accessed via [data](https://wagmi.sh/react/api/hooks/useConnect#data)
+ returned from `useConnect`, or via [`useAccount`](https://wagmi.sh/react/api/hooks/useAccount).
+
+### With CoinbaseWalletSDK only
+
+To implement the Create Wallet button using only the SDK, all you need to do is make an `eth_requestAccounts` request.
+
+
+```tsx twoslash [BlueCreateWalletButton.tsx] filename="BlueCreateWalletButton.tsx"
+import React, { useCallback } from 'react';
+import { createCoinbaseWalletSDK } from '@coinbase/wallet-sdk';
+import { CoinbaseWalletLogo } from './CoinbaseWalletLogo';
+
+const buttonStyles = {
+ background: 'transparent',
+ border: '1px solid transparent',
+ boxSizing: 'border-box',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ width: 200,
+ fontFamily: 'Arial, sans-serif',
+ fontWeight: 'bold',
+ fontSize: 18,
+ backgroundColor: '#0052FF',
+ paddingLeft: 15,
+ paddingRight: 30,
+ borderRadius: 10,
+};
+
+const sdk = new createCoinbaseWalletSDK({
+ appName: 'My Dapp',
+ appLogoUrl: 'https://example.com/logo.png',
+ appChainIds: [84532],
+});
+
+const provider = sdk.getProvider();
+
+export function BlueCreateWalletButton({ handleSuccess, handleError }) {
+ const createWallet = useCallback(async () => {
+ try {
+ const [address] = await provider.request({
+ method: 'eth_requestAccounts',
+ });
+ handleSuccess(address);
+ } catch (error) {
+ handleError(error);
+ }
+ }, [handleSuccess, handleError]);
+
+ return (
+
+
+ Create Wallet
+
+ );
+}
+```
+
+```tsx twoslash [BlackCreateWalletButton.tsx] filename="BlackCreateWalletButton.tsx"
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { createCoinbaseWalletSDK } from '@coinbase/wallet-sdk';
+import { CoinbaseWalletLogo } from './CoinbaseWalletLogo';
+
+const GRADIENT_BORDER_WIDTH = 2;
+
+const buttonStyles = {
+ background: 'transparent',
+ border: '1px solid transparent',
+ boxSizing: 'border-box',
+};
+
+const contentWrapperStyle = {
+ position: 'relative',
+};
+
+function Gradient({ children, style, isAnimationDisabled = false }) {
+ const [isAnimating, setIsAnimating] = useState(false);
+ const gradientStyle = useMemo(() => {
+ const rotate = isAnimating ? '720deg' : '0deg';
+ return {
+ transform: `rotate(${rotate})`,
+ transition: isAnimating
+ ? 'transform 2s cubic-bezier(0.27, 0, 0.24, 0.99)'
+ : 'none',
+ ...style,
+ };
+ }, [isAnimating, style]);
+
+ const handleMouseEnter = useCallback(() => {
+ if (isAnimationDisabled || isAnimating) return;
+ setIsAnimating(true);
+ }, [isAnimationDisabled, isAnimating, setIsAnimating]);
+
+ useEffect(() => {
+ if (!isAnimating) return;
+ const animationTimeout = setTimeout(() => {
+ setIsAnimating(false);
+ }, 2000);
+ return () => {
+ clearTimeout(animationTimeout);
+ };
+ }, [isAnimating]);
+
+ return (
+
+ );
+}
+
+const sdk = new createCoinbaseWalletSDK({
+ appName: 'My Dapp With Vanilla SDK',
+ appLogoUrl: 'https://example.com/logo.png',
+ appChainIds: [84532],
+});
+
+const provider = sdk.getProvider();
+
+export function BlackCreateWalletButton({ height = 66, width = 200 }) {
+ const minButtonHeight = 48;
+ const minButtonWidth = 200;
+ const buttonHeight = Math.max(minButtonHeight, height);
+ const buttonWidth = Math.max(minButtonWidth, width);
+ const gradientDiameter = Math.max(buttonHeight, buttonWidth);
+ const styles = useMemo(
+ () => ({
+ gradientContainer: {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ backgroundColor: 'black',
+ borderRadius: buttonHeight / 2,
+ height: buttonHeight,
+ width: buttonWidth,
+ boxSizing: 'border-box',
+ overflow: 'hidden',
+ },
+ gradient: {
+ background:
+ 'conic-gradient(from 180deg, #45E1E5 0deg, #0052FF 86.4deg, #B82EA4 165.6deg, #FF9533 255.6deg, #7FD057 320.4deg, #45E1E5 360deg)',
+ position: 'absolute',
+ top: -buttonHeight - GRADIENT_BORDER_WIDTH,
+ left: -GRADIENT_BORDER_WIDTH,
+ width: gradientDiameter,
+ height: gradientDiameter,
+ },
+ buttonBody: {
+ display: 'flex',
+ justifyContent: 'center',
+ alignItems: 'center',
+ boxSizing: 'border-box',
+ backgroundColor: '#000000',
+ height: buttonHeight - GRADIENT_BORDER_WIDTH * 2,
+ width: buttonWidth - GRADIENT_BORDER_WIDTH * 2,
+ fontFamily: 'Arial, sans-serif',
+ fontWeight: 'bold',
+ fontSize: 18,
+ borderRadius: buttonHeight / 2,
+ position: 'relative',
+ paddingRight: 10,
+ },
+ }),
+ [buttonHeight, buttonWidth, gradientDiameter]
+ );
+
+ const createWallet = useCallback(async () => {
+ try {
+ const [address] = await provider.request({
+ method: 'eth_requestAccounts',
+ });
+ handleSuccess(address);
+ } catch (error) {
+ handleError(error);
+ }
+ }, [handleSuccess, handleError]);
+
+ return (
+
+
+
+
+
+ Create Wallet
+
+
+
+
+ );
+}
+```
+
+```tsx twoslash [CoinbaseWalletLogo.tsx] filename="CoinbaseWalletLogo.tsx"
+import React from 'react';
+
+const defaultContainerStyles = {
+ paddingTop: 2,
+};
+
+export function CoinbaseWalletLogo({
+ size = 26,
+ containerStyles = defaultContainerStyles,
+}) {
+ return (
+
+ );
+}
+```
+
+```ts twoslash [wagmi.ts]
+// @noErrors: 2554
+import { http, createConfig } from 'wagmi';
+import { baseSepolia } from 'wagmi/chains';
+import { coinbaseWallet } from 'wagmi/connectors';
+
+export const config = createConfig({
+ chains: [baseSepolia],
+ connectors: [
+ coinbaseWallet({
+ appName: 'Create Wagmi',
+ preference: 'smartWalletOnly',
+ }),
+ ],
+ transports: {
+ [baseSepolia.id]: http(),
+ },
+});
+
+declare module 'wagmi' {
+ interface Register {
+ config: typeof config;
+ }
+}
+```
+
+
diff --git a/_pages/identity/smart-wallet/guides/erc20-paymasters.mdx b/_pages/identity/smart-wallet/guides/erc20-paymasters.mdx
new file mode 100644
index 00000000..0b9188a4
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/erc20-paymasters.mdx
@@ -0,0 +1,188 @@
+---
+title: "ERC20 Paymasters"
+---
+
+
+Smart Wallet enables users to pay for gas in ERC20 tokens!
+Tokens can be accepted for payment by passed in app paymasters in addition to a set of universally supported tokens, such as USDC (this set to be expanded soon).
+
+This guide outlines how to set up your own app paymaster which will accept your token as payment.
+
+### Choose a paymaster service provider
+
+As a prerequisite, you'll need to obtain a paymaster service URL from a paymaster service provider. ERC20 paymasters have additional requirements that will be outlined below.
+
+We recommend the [Coinbase Developer Platform](https://www.coinbase.com/developer-platform) paymaster as it is fully set up to work with Smart Wallet ERC20 token gas payments out of the box. CDP is also offering up to $15k in gas credits as part of the [Base Gasless Campaign](/identity/smart-wallet/concepts/base-gasless-campaign).
+
+Otherwise if using a different paymaster provider, it must conform to the specification outlined in [ERC20 Compatible Paymasters](#erc20-compatible-paymasters) to correctly work with Smart Wallet.
+
+### App setup for custom token
+
+Once you have a paymaster that is compatible with ERC20 gas payments on Smart Wallet, you are only responsible for including the approvals to the paymaster for your token. It is recommended to periodically top up the allowance once they hit some threshold.
+
+```js
+
+const tokenDecimals = 6
+const minTokenThreshold = 1 * 10 ** tokenDecimals // $1
+const tokenApprovalTopUp = 20 * 10 ** tokenDecimals // $20
+const tokenAddress = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
+const nftContractAddress = "0x66519FCAee1Ed65bc9e0aCc25cCD900668D3eD49"
+const paymasterAddress = "0x2FAEB0760D4230Ef2aC21496Bb4F0b47D634FD4c"
+
+const mintTo = {
+ abi: abi,
+ functionName: "mintTo",
+ to: nftContractAddress,
+ args: [account.address, 1],
+};
+
+calls = [mintTo]
+
+// Checks for allowance
+const allowance = await client.readContract({
+ abi: parseAbi(["function allowance(address owner, address spender) returns (uint256)"]),
+ address: tokenAddress,
+ functionName: "allowance",
+ args: [account.address, paymasterAddress],
+})
+
+if (allowance < minTokenThreshold) {
+ // include approval for $20 in calls so that the paymaster will be able to move the token to accept payment
+ calls.push({
+ abi: ["function approve(address,uint)"],
+ functionName: "approve",
+ to: nftContractAddress,
+ args: [paymasterAddress, tokenApprovalTopUp],
+ })
+}
+```
+
+That is it! Smart Wallet will handle the rest as long as it is compatible as outlined below.
+
+### ERC20 Compatible Paymasters
+
+Coinbase Developer Platform is compatible out of the box and we will be working with other teams to include support soon!
+
+The paymaster must handle the `pm_getPaymasterStubData` and `pm_getPaymasterData` JSON-RPC requests specified by ERC-7677 in addition to `pm_getAcceptedPaymentTokens`. We step through each request and response below.
+
+#### pm_getPaymasterStubData and pm_getPaymasterData
+
+1. The paymaster must use the specified ERC20 for payment if specified in the 7677 context field under `erc20`.
+2. Upon rejection / failure the paymaster should return a `data` field in the JSONRPC response which could be used to approve the paymaster and includes:
+
+- `acceptedTokens` array which is a struct including the token address
+- `paymasterAddress` field which is the paymaster address which will perform the token transfers.
+
+3. Upon success the paymaster must return a `tokenPayment` field in the result. This includes:
+
+- `tokenAddress` address of the token used for payment
+- `maxFee` the maximum fee to show in the UI
+- `decimals` decimals to use in the UI
+- `name` name of the token
+
+Smart wallet will simulate the transaction to ensure success and accurate information.
+
+##### Request
+
+This is a standard V0.6 Entrypoint request example with the additional context for the specified token to be used.
+
+```json
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "method": "pm_getPaymasterData",
+ "params": [
+ {
+ "sender": "0xe62B4aD6A7c079F47D77a9b939D5DC67A0dcdC2B",
+ "nonce": "0x4e",
+ "initCode": "0x",
+ "callData": "0xb61d27f60000000000000000000000007746371e8df1d7099a84c20ed72e3335fb016b23000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000",
+ "callGasLimit": "0x113e10",
+ "verificationGasLimit": "0x113e10",
+ "preVerificationGas": "0x113e10",
+ "maxFeePerGas": "0x113e10",
+ "maxPriorityFeePerGas": "0x113e10",
+ "paymasterAndData": "0x",
+ "signature": "0x5ee079a5dec73fe39c1ce323955fb1158fc1b9a6b2ddbec104cd5cfec740fa5531584f098b0ca95331b6e316bd76091e3ab75a7bc17c12488664d27caf19197e1c"
+ },
+ "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789",
+ "0x2105",
+ {
+ "erc20": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
+ }
+ ]
+}
+```
+
+##### Response
+
+Successful response:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "result": {
+ "paymasterAndData": "0x2faeb0760d4230ef2ac21496bb4f0b47d634fd4c0000670fdc98000000000000494b3b6e1d074fbca920212019837860000100833589fcd6edb6e08f4c7c32d4f71b54bda029137746371e8df1d7099a84c20ed72e3335fb016b23000000000000000000000000000000000000000000000000000000009b75458400000000697841102cd520d4e0171a58dadc3e6086111a49a90826cb0ad25579f25f1652081f68c17d8652387a33bf8880dc44ecf95be4213e786566d755baa6299f477b0bb21c",
+ "tokenPayment": {
+ "name": "USDC",
+ "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
+ "maxFee": "0xa7c8",
+ "decimals": 6
+ }
+ }
+}
+```
+
+Rejection response:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "error": {
+ "code": -32002,
+ "message": "request denied - no sponsorship and address can not pay with accepted token",
+ "data": {
+ "acceptedTokens": [
+ {
+ "name": "USDC",
+ "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
+ }
+ ]
+ }
+ }
+}
+```
+
+#### pm_getAcceptedPaymentTokens
+
+`pm_getAcceptedPaymentTokens` returns an array of tokens the paymaster will accept for payment.
+The request contains the entrypoint and the chain id with optional context.
+
+##### Request
+
+```json
+{
+ "jsonrpc": "2.0", "id": 1,
+ "method": "pm_getAcceptedPaymentTokens",
+ "params": [ "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789", "0x2105", {}]
+}
+```
+
+##### Response
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "result": {
+ "acceptedTokens": [
+ {
+ "name": "USDC",
+ "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
+ }
+ ]
+ }
+}
+```
diff --git a/_pages/identity/smart-wallet/guides/magic-spend.mdx b/_pages/identity/smart-wallet/guides/magic-spend.mdx
new file mode 100644
index 00000000..5dca3edb
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/magic-spend.mdx
@@ -0,0 +1,36 @@
+---
+title: "MagicSpend"
+---
+
+
+With MagicSpend, Smart Wallet users can use their Coinbase balances onchain. This means users can easily start using onchain apps without needing to onramp funds into their wallet.
+
+This also means that apps might not have all the balance information typically available to them by reading onchain data. Smart Wallet indicates that this is the case by responding to [`wallet_getCapabilities` RPC calls](https://eip5792.xyz/reference/getCapabilities) with the `auxiliaryFunds` capability for each chain Smart Wallet users can use their Coinbase balances on.
+
+If your app supports Smart Wallet, it should not assume it knows the full balances available to a user if the `auxiliaryFunds` capability is present on a given chain.
+
+## Using Wagmi
+
+```ts twoslash [App.tsx]
+// @filename: App.tsx
+import React from 'react'
+// ---cut---
+import { useCapabilities } from 'wagmi/experimental'
+
+function App() {
+ const { data: capabilities } = useCapabilities()
+// @log: {
+// @log: 84532: {
+// @log: auxiliaryFunds: {
+// @log: supported: true,
+// @log: },
+// @log: }
+// @log: }
+
+ return
+}
+```
+
+If your app supports Smart Wallet and sees that the `auxiliaryFunds` capability is supported on a given chain,
+it means that a user might have funds available for use onchain on Coinbase.
+As a result, your app should not block user actions on the basis of balance checks.
\ No newline at end of file
diff --git a/_pages/identity/smart-wallet/guides/paymasters.mdx b/_pages/identity/smart-wallet/guides/paymasters.mdx
new file mode 100644
index 00000000..52d8a8aa
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/paymasters.mdx
@@ -0,0 +1,262 @@
+---
+title: "Build with Paymasters (Sponsored Transactions)"
+---
+
+One of the biggest UX enhancements unlocked by Smart Wallet is the ability for app developers to sponsor their users' transactions.
+If your app supports Smart Wallet, you can start sponsoring your users'
+transactions by using [standardized Paymaster service communication](https://erc7677.xyz) enabled by [new wallet RPC methods](https://eip5792.xyz).
+
+This guide is specific to using Smart Wallet, you can find our more about using Paymasters with Base in
+the [Base Go Gasless page](https://docs.base.org/use-cases/go-gasless).
+
+The code below is also in our [Wagmi Smart Wallet template](https://github.com/wilsoncusack/wagmi-scw/).
+
+## Skip ahead and clone the template
+
+If you are looking for a complete example, you can skip the steps below and clone the [Wagmi Smart Wallet template](https://github.com/wilsoncusack/wagmi-scw/). Simply open a terminal and run:
+
+```bash
+git clone https://github.com/wilsoncusack/wagmi-scw.git
+```
+
+Follow the instructions in the README to install the dependencies and run the app.
+
+## Add Support in your Next.js app (using Wagmi/Viem)
+
+
+
+ As a prerequisite, you'll need to obtain a Paymaster service URL from a Paymaster service provider.
+
+ We'll use [Coinbase Developer Platform](https://www.coinbase.com/developer-platform) as a Paymaster service provider,
+ currently offering up to $15k in gas credits as part of the [Base Gasless Campaign](/identity/smart-wallet/concepts/base-gasless-campaign).
+
+
+ **ERC-7677-Compliant Paymaster Providers**
+
+ If you choose to use a different Paymaster service provider, ensure they are [ERC-7677-compliant](https://www.erc7677.xyz/ecosystem/Paymasters).
+
+
+
+ Once you have signed up for [Coinbase Developer Platform](https://www.coinbase.com/developer-platform), you get your Paymaster service URL by navigating to **Onchain Tools > Paymaster** as shown below:
+
+
+
+
How to get your Paymaster service URL
+
+
+
+ **Should you create a proxy for your Paymaster service?**
+
+ As you can see in the Paymaster transaction [component](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/components/TransactWithPaymaster.tsx), we use a proxy to protect
+ the Paymaster service URL to prevent it from being exposed/leaked on a frontend client
+
+ For local development, you can use the same URL for the Paymaster service and the proxy.
+
+ We also created a [minimalist proxy API](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/app/api/Paymaster/route.ts)
+ which you can use as the `PaymasterServiceUrl` in the [`TransactWithPaymaster` component](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/components/TransactWithPaymaster.tsx).
+
+
+
+ Once you have your Paymaster service URL, you can proceed to setting up your contracts allowlist.
+ This is a list of contracts and function calls that you want to be sponsored by the Paymaster.
+
+
+
+
How to set your Paymaster contracts allowlist
+
+
+ Congrats! You've set up your Paymaster service and contracts allowlist.
+ It's time to create Wagmi and Viem clients.
+
+
+ **You can also choose to create custom advanced policies !**
+
+ For example, in the [template](https://github.com/wilsoncusack/wagmi-scw), we create a `willSponsor` function in [`utils.ts`](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/app/api/utils.ts) to add some extra validation if you need
+ more control over the policy enforcement.
+
+ `willSponsor` is most likely not needed if you are using [Coinbase Developer Platform](https://www.coinbase.com/developer-platform) as it has built-in policy enforcement features,
+ but know that this is still possible if you need it.
+
+
+
+
+
+ This section shows you how to create Wagmi and Viem clients to interact with Smart Wallet using
+ the Paymaster service you set up in the previous steps.
+
+
+ ```ts twoslash [wagmi.ts] filename="wagmi.ts"
+ // @noErrors
+ import { http, createConfig } from "wagmi";
+ import { baseSepolia } from "wagmi/chains";
+ import { coinbaseWallet } from "wagmi/connectors";
+
+ export const cbWalletConnector = coinbaseWallet({
+ appName: "Wagmi Smart Wallet",
+ preference: "smartWalletOnly",
+ });
+
+ export const config = createConfig({
+ chains: [baseSepolia],
+ // turn off injected provider discovery
+ multiInjectedProviderDiscovery: false,
+ connectors: [cbWalletConnector],
+ ssr: true,
+ transports: {
+ [baseSepolia.id]: http(),
+ },
+ });
+ ```
+
+ ```ts twoslash [app/config.ts] filename="app/config.ts"
+ // @noErrors
+ import { createClient, createPublicClient, http } from "viem";
+ import { baseSepolia } from "viem/chains";
+ import { entryPoint06Address, createPaymasterClient, createBundlerClient } from "viem/account-abstraction";
+
+ export const client = createPublicClient({
+ chain: baseSepolia,
+ transport: http(),
+ });
+
+ const PaymasterService = process.env.PAYMASTER_SERVICE_URL!;
+
+ export const PaymasterClient = createPaymasterClient({
+ transport: http(PaymasterService),
+ });
+
+ export const bundlerClient = createBundlerClient({
+ chain: baseSepolia,
+ Paymaster: PaymasterClient,
+ transport: http(PaymasterService),
+ })
+ ```
+
+
+
+
+ Once you have your Paymaster service set up, you can now pass its URL along to Wagmi's `useWriteContracts` hook.
+
+
+ **Pass in the proxy URL**
+
+ If you set up a proxy in your app's backend as recommended in step (2) above, you'll want to pass in the proxy URL you created.
+
+
+
+
+ ```ts twoslash [page.tsx] filename="page.tsx"
+ // @noErrors
+ import { useAccount } from "wagmi";
+ import { useCapabilities, useWriteContracts } from "wagmi/experimental";
+ import { useMemo, useState } from "react";
+ import { CallStatus } from "./CallStatus";
+ import { myNFTABI, myNFTAddress } from "./myNFT";
+
+ export function App() {
+ const account = useAccount();
+ const [id, setId] = useState(undefined);
+ const { writeContracts } = useWriteContracts({
+ mutation: { onSuccess: (id) => setId(id) },
+ });
+ const { data: availableCapabilities } = useCapabilities({
+ account: account.address,
+ });
+ const capabilities = useMemo(() => {
+ if (!availableCapabilities || !account.chainId) return {};
+ const capabilitiesForChain = availableCapabilities[account.chainId];
+ if (
+ capabilitiesForChain["PaymasterService"] &&
+ capabilitiesForChain["PaymasterService"].supported
+ ) {
+ return {
+ const PaymasterServiceUrl = process.env.NEXT_PUBLIC_PAYMASTER_PROXY_SERVER_URL
+ PaymasterService: {
+ url: PaymasterServiceUrl // You can also use the minimalist proxy we created: `${document.location.origin}/api/Paymaster`
+ },
+ };
+ }
+ return {};
+ }, [availableCapabilities, account.chainId]);
+
+ return (
+
+
Transact With Paymaster
+
{JSON.stringify(capabilities)}
+
+ {
+ writeContracts({
+ contracts: [
+ {
+ address: myNFTAddress,
+ abi: myNFTABI,
+ functionName: "safeMint",
+ args: [account.address],
+ },
+ ],
+ capabilities,
+ });
+ }}
+ >
+ Mint
+
+ {id && }
+
+
+ );
+ }
+ ```
+
+ ```ts twoslash [myNFT.ts] filename="myNFT.ts"
+ // @noErrors
+ export const myNFTABI = [
+ {
+ stateMutability: "nonpayable",
+ type: "function",
+ inputs: [{ name: "to", type: "address" }],
+ name: "safeMint",
+ outputs: [],
+ },
+ ] as const;
+
+ export const myNFTAddress = "0x119Ea671030FBf79AB93b436D2E20af6ea469a19";
+
+ ```
+
+
+
+ **About The Hooks Used Above**
+
+ The `useWriteContracts` and `useCapabilities` hooks used below rely on new wallet RPC and are not yet supported in most wallets.
+ It is recommended to have a fallback function if your app supports wallets other than Smart Wallet.
+
+
+
+
+ **How to find this code in the repository?**
+
+ The code above is a simplified version of the code in the
+ [template](https://github.com/wilsoncusack/wagmi-scw/).
+
+ In the template, we create a [`TransactWithPaymaster`](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/components/TransactWithPaymaster.tsx) component that uses the `useWriteContracts` hook to send a transaction with a Paymaster.
+
+ The [`TransactWithPaymaster`](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/components/TransactWithPaymaster.tsx) component is used in the [`page.tsx`](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/app/page.tsx) file.
+
+
+
+ That's it! Smart Wallet will handle the rest. If your Paymaster service is able to sponsor the transaction,
+ in the UI Smart Wallet will indicate to your user that the transaction is sponsored.
+
+
+
diff --git a/_pages/identity/smart-wallet/guides/signature-verification.mdx b/_pages/identity/smart-wallet/guides/signature-verification.mdx
new file mode 100644
index 00000000..33184434
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/signature-verification.mdx
@@ -0,0 +1,90 @@
+---
+title: "Signature Verification"
+---
+
+
+There are important details to verifying smart contract wallet signatures. The smart contract itself
+cannot produce a signature. Instead, the contract has a function
+```solidity
+function isValidSignature(bytes32 _hash, bytes memory _signature) returns (bytes4 magicValue);
+```
+which can be called to determine if signature should be considered valid (defined in [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271)).
+
+In the case of Smart Wallet, a signature is considered valid if it was signed by a current signer (aka "owner") of the Smart Wallet.
+For example, a user can sign a message with their passkey, and when `isValidSignature` is called on their Smart Wallet,
+it would return that the signature is valid because their passkey is an owner.
+
+There is also an additional complexity: users receive their Smart Wallet address immediately upon passkey registration,
+and are able to begin signing for their Smart Wallet,
+but their Smart Wallet is not deployed on a chain until the first transaction on that chain.
+
+[ERC-6492](https://eips.ethereum.org/EIPS/eip-6492) describes
+> With the rising popularity of account abstraction, we often find that the best user experience
+for contract wallets is to defer contract deployment until the first user transaction,
+therefore not burdening the user with an additional deploy step before they can use their account.
+However, at the same time, many dApps expect signatures, not only for interactions, but also just for logging in.
+
+So the challenge is, how do we verify signatures in a way that works for both deployed and undeployed Smart Wallets?
+ERC-6492 has a solution for this, which Smart Wallet has adopted.
+We won't go unto all the details here, read the ERC linked above, if you're looking for that.
+Below we cover the minimum work needed to support on and off chain signature validation for Smart Wallet.
+
+## Offchain
+For purely offchain signature verification––such as "Sign-In With Ethereum"––ensure you are using
+a ERC-6492-compliant signature verification library.
+We recommend Viem's [`verifyMessage`](https://viem.sh/docs/actions/public/verifyMessage#verifymessage) ([example](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/components/SignMessage.tsx#L28))
+and [verifyTypedData](https://viem.sh/docs/actions/public/verifyTypedData) ([example](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/components/TypedSign.tsx#L46)).
+
+See our [Sign-In with Ethereum guide](../guides/siwe) for a detailed example.
+
+## Onchain
+For signatures that will be used onchain, such as with [Permit2](https://github.com/Uniswap/permit2)
+or [Seaport](https://github.com/ProjectOpenSea/seaport) developers will need to inspect the signature offchain
+and remove unneeded ERC-6492 data, if it is present.
+We recommend using the [parseErc6492Signature](https://viem.sh/docs/utilities/parseErc6492Signature#parseerc6492signature)
+util from Viem. Abbreviated example below. See full example [here](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/components/Permit2.tsx).
+
+```ts [example.tsx]
+import { useMemo, useState } from "react";
+import type { Hex } from "viem";
+import { useAccount, useReadContract, useSignTypedData } from "wagmi";
+import { useWriteContract } from "wagmi";
+import { parseErc6492Signature } from "viem/experimental";
+
+export function App() {
+ const { writeContract } = useWriteContract();
+ const [signature, setSignature] = useState(undefined)
+ const { signTypedData } = useSignTypedData({
+ mutation: { onSuccess: (sig) => setSignature(sig) },
+ });
+
+ // parse signature, in case encoded with extra ERC-6492 data
+ const parsedSignature = useMemo(() => {
+ if (!signature) return
+
+ return parseErc6492Signature(signature).signature
+ }, [signature])
+
+ return(
+ <>
+ signTypedData(...)}>
+ Sign
+
+ {signature && (
+ {
+ writeContract({
+ address: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
+ abi: permit2Abi,
+ functionName: "permit",
+ args: [..., parsedSignature],
+ });
+ }}
+ >
+ Submit Onchain
+
+ )}
+ >
+ )
+}
+```
diff --git a/_pages/identity/smart-wallet/guides/signing-and-verifying-messages.mdx b/_pages/identity/smart-wallet/guides/signing-and-verifying-messages.mdx
new file mode 100644
index 00000000..dde537b1
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/signing-and-verifying-messages.mdx
@@ -0,0 +1,349 @@
+---
+title: "Sign Messages Using Smart Wallet"
+---
+
+Smart contract wallets introduce a few differences in how messages are signed compared to traditional Externally Owned Accounts (EOAs). This guide explains how to properly implement message signing using Smart Wallet, covering both standard messages and typed data signatures, as well as some edge cases.
+
+## Introduction
+
+Before walking through the details of how to sign and verify messages using Smart Wallet, it's important to understand some of the use cases of signing messages with wallets, as well as the key differences between EOAs and smart contracts when it comes to signing messages.
+
+### Use Cases for Wallet Signatures
+
+Blockchain-based apps use wallet signatures for two main categories:
+
+1. **Signatures for offchain verification**: Used for authenticating users in onchain apps (e.g., Sign-In with Ethereum) to avoid spoofing. The signature is not used for any onchain action.
+
+2. **Signatures for onchain verification**: Used for signing onchain permissions (e.g., [Permit2](https://github.com/Uniswap/permit2)) or batching transactions. The signature is usually stored for future transactions.
+
+### Smart Contract Wallet Differences
+
+Smart contract wallets handle signatures differently from EOAs in several ways:
+
+- The contract itself doesn't produce signatures - instead, the owner (e.g., passkey) signs messages
+- Verification happens through the `isValidSignature` function defined in [EIP-1271](https://eips.ethereum.org/EIPS/eip-1271)
+- Smart contract wallet addresses are often deterministic, allowing signature support before deployment via [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492)
+
+## Signing Offchain Messages using Wagmi/Viem
+
+### Prerequisites
+
+Before implementing message signing, ensure:
+
+- Your project can use Wagmi/Viem
+- You're signing an offchain message
+- Your Smart Wallet can be deployed or undeployed (methods are ERC-6492 compatible)
+
+
+If your implementation is more complicated and is not covered by the above assumptions, please refer to the [Handling Advanced Cases section](#handling-advanced-cases) below.
+
+
+
+### Signing a Simple Message (Sign-In with Ethereum)
+
+The following example demonstrates how to implement basic message signing using a Smart Wallet.
+It is a typical Sign-In with Ethereum (SIWE) implementation as detailed in [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361):
+
+
+SignMessage.tsx: 👉 Click to expand/collapse
+
+```tsx [SignMessage.tsx]
+import { useCallback, useEffect, useMemo, useState } from "react";
+import type { Hex } from "viem";
+import { useAccount, usePublicClient, useSignMessage } from "wagmi";
+import { SiweMessage } from "siwe";
+
+export function SignMessage() {
+ const account = useAccount();
+ const client = usePublicClient();
+ const [signature, setSignature] = useState(undefined);
+ const { signMessage } = useSignMessage({
+ mutation: { onSuccess: (sig) => setSignature(sig) },
+ });
+ const message = useMemo(() => {
+ return new SiweMessage({
+ domain: document.location.host,
+ address: account.address,
+ chainId: account.chainId,
+ uri: document.location.origin,
+ version: "1",
+ statement: "Smart Wallet SIWE Example",
+ nonce: "12345678",
+ });
+ }, []);
+
+ const [valid, setValid] = useState(undefined);
+
+ const checkValid = useCallback(async () => {
+ if (!signature || !account.address || !client) return;
+
+ client
+ .verifyMessage({
+ address: account.address,
+ message: message.prepareMessage(),
+ signature,
+ })
+ .then((v) => setValid(v));
+ }, [signature, account]);
+
+ useEffect(() => {
+ checkValid();
+ }, [signature, account]);
+
+ return (
+
+
Sign Message (Sign In with Ethereum)
+
signMessage({ message: message.prepareMessage() })}
+ >
+ Sign
+
+
{}
+ {signature &&
Signature: {signature}
}
+ {valid != undefined &&
Is valid: {valid.toString()}
}
+
+ );
+}
+```
+
+
+
+To run this example:
+
+1. Clone the repo: `git clone https://github.com/wilsoncusack/wagmi-scw/`
+2. Install bun: `curl -fsSL https://bun.sh/install | bash`
+3. Install packages: `bun i`
+4. Run next app: `bun run dev`
+
+
+The example above is a typical Sign-In with Ethereum (SIWE) implementation as detailed in [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361).
+
+
+### Signing Typed Data (EIP-712)
+
+For structured data signing, implement the following:
+
+
+TypedSign.tsx: 👉 Click to expand/collapse
+
+```tsx [TypedSign.tsx]
+import { useCallback, useEffect, useState } from "react";
+import type { Address, Hex } from "viem";
+import { useAccount, usePublicClient, useSignTypedData } from "wagmi";
+
+export const domain = {
+ name: "Ether Mail",
+ version: "1",
+ chainId: 1,
+ verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
+} as const;
+
+export const types = {
+ Person: [
+ { name: "name", type: "string" },
+ { name: "wallet", type: "address" },
+ ],
+ Mail: [
+ { name: "from", type: "Person" },
+ { name: "to", type: "Person" },
+ { name: "contents", type: "string" },
+ ],
+} as const;
+
+export function TypedSign() {
+ const account = useAccount();
+ const client = usePublicClient();
+ const [signature, setSignature] = useState(undefined);
+ const { signTypedData } = useSignTypedData({
+ mutation: { onSuccess: (sig) => setSignature(sig) },
+ });
+ const message = {
+ from: {
+ name: "Cow",
+ wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826" as Address,
+ },
+ to: {
+ name: "Bob",
+ wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB" as Address,
+ },
+ contents: "Hello, Bob!",
+ };
+
+ const [valid, setValid] = useState(undefined);
+
+ const checkValid = useCallback(async () => {
+ if (!signature || !account.address) return;
+
+ client
+ .verifyTypedData({
+ address: account.address,
+ types,
+ domain,
+ primaryType: "Mail",
+ message,
+ signature,
+ })
+ .then((v) => setValid(v));
+ }, [signature, account]);
+
+ useEffect(() => {
+ checkValid();
+ }, [signature, account]);
+
+ return (
+
+
Sign Typed Data
+
+ signTypedData({ domain, types, message, primaryType: "Mail" })
+ }
+ >
+ Sign
+
+
{}
+ {signature &&
Signature: {signature}
}
+ {valid != undefined &&
Is valid: {valid.toString()}
}
+
+ );
+}
+```
+
+
+
+Key points about typed data signatures:
+
+- Uses wagmi's `useSignTypedData` hook for structured data signing
+- Defines domain and types for EIP-712 typed data
+- Verifies the signature using the public client
+- Provides user feedback on signature validity
+
+## Handling Advanced Cases
+
+### Onchain Signatures
+
+If you are looking to handle onchain signatures (eg. [Permit2](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/components/Permit2.tsx)), you can sign them in the same way as above.
+However, you should be careful when looking to validate the signatures:
+
+- ERC-6492-compatible signatures contain other elements that are not useful for onchain signatures (magicBytes, create2Factory, factoryCalldata). In order to understand the complete logic of how ERC-6492-compatible signatures work,
+ please refer to the ["Verifier Side" section of the EIP](https://eips.ethereum.org/EIPS/eip-6492#verifier-side).
+- Use Viem's [`parseErc6492Signature`](https://viem.sh/docs/utilities/parseErc6492Signature#parseerc6492signature) utility to parse these elements
+- For non-Viem implementations, see alternative approaches below
+
+
+There is an example implementation of Permit2 using Wagmi in the [wagmi-scw repository](https://github.com/wilsoncusack/wagmi-scw/blob/main/src/components/Permit2.tsx).
+
+
+
+### Alternative Frameworks
+
+To aid in the verification of smart account signatures, ERC-6492 includes a singleton contract that can validate ERC-6492 signatures.
+This singleton contract is called [`UniversalSigValidator`](https://eips.ethereum.org/EIPS/eip-6492#reference-implementation).
+
+If you are using a different framework other than Viem, or you find it impossible to use Viem, you can do one of the following:
+
+1. Deploy [`UniversalSigValidator`](https://eips.ethereum.org/EIPS/eip-6492#reference-implementation) and call the view function `isValidSig`. It accepts a signer,
+ hash, and signature and returns a boolean of whether the signature is valid or not.
+ This method may revert if the underlying calls revert.
+2. If you would like to avoid deploying the contract, the ERC-6492 contract has a ValidateSigOffchain
+ helper contract that allows you to validate a signature in one eth_call without deploying the smart account. Below is a reference implementation in ethers for this second case.
+
+```typescript [ethers-example.ts]
+const isValidSignature = '0x01' === await provider.call({
+ data: ethers.utils.concat([
+ validateSigOffchainBytecode,
+ (new ethers.utils.AbiCoder()).encode(['address', 'bytes32', 'bytes'], [signer, hash, signature])
+ ])
+})
+```
+
+### Server-side Verification
+
+You can handle server-side verification using NextJS edge functions such as shown [here](https://github.com/youssefea/ethden2025-sign-tx-csw/blob/main/src/app/api/verify/route.ts):
+
+
+route.ts: 👉 Click to expand/collapse
+
+```typescript [route.ts]
+import { NextRequest, NextResponse } from 'next/server';
+import { createPublicClient, http } from 'viem';
+import { baseSepolia, base } from 'viem/chains';
+
+export async function POST(request: NextRequest) {
+ try {
+ const { address, message, signature } = await request.json();
+ const CHAIN = process.env.NODE_ENV === 'production' ? base : baseSepolia
+
+ const publicClient = createPublicClient({
+ chain: CHAIN,
+ transport: http(),
+ });
+
+
+ const valid = await publicClient.verifyMessage({
+ address: address,
+ message: message,
+ signature: signature,
+ });
+ console.log("valid", valid);
+
+ if (valid){
+ return NextResponse.json({
+ success: true,
+ message: 'Signature verified',
+ address: address
+ });
+ } else {
+ return NextResponse.json(
+ { success: false, message: 'Invalid signature' },
+ { status: 400 }
+ );
+ }
+ } catch (error) {
+ console.error('Error verifying signature:', error);
+ return NextResponse.json(
+ { success: false, message: 'Invalid signature' },
+ { status: 400 }
+ );
+ }
+}
+```
+
+
+
+
+Storing signatures safely requires advanced security guarantees. Ensure your database cannot be tampered with.
+
+
diff --git a/_pages/identity/smart-wallet/guides/siwe.mdx b/_pages/identity/smart-wallet/guides/siwe.mdx
new file mode 100644
index 00000000..be659d09
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/siwe.mdx
@@ -0,0 +1,202 @@
+---
+title: "Using Smart Wallet with Sign-In with Ethereum"
+---
+
+This guide covers creating a new Sign-In with Ethereum template project that uses Smart Wallet.
+
+
+ This simple implementation is for demo purposes only and is not meant to be an example of a
+ production app. A production app should: 1. Generate a random nonce for each message on the
+ backend. 2. Check the nonce and verify the message signature on the backend as specified in
+ [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361). 3. Invalidate nonces on logout to prevent
+ replay attacks through session duplication.
+
+
+
+
+ Follow the [Wagmi guide](/identity/smart-wallet/quickstart/nextjs-project) to set up a template project and connect a Smart Wallet.
+
+
+
+ ```bash [pnpm]
+ pnpm install siwe
+ ```
+
+ ```bash [bun]
+ bun install siwe
+ ```
+
+
+
+
+ ```tsx twoslash [src/SignInWithEthereum.tsx]
+ import React from 'react';
+
+ export function SignInWithEthereum() {
+ return (
+
+
SIWE Example
+
+ );
+ }
+ ```
+
+ ```tsx [src/App.tsx]
+ import React from 'react'
+ import { useAccount, useConnect, useDisconnect } from 'wagmi'
+ import { SignInWithEthereum } from './SignInWithEthereum'; // [!code focus]
+
+ function App() {
+ const account = useAccount()
+ const { connector, connect, status, error } = useConnect()
+ const { disconnect } = useDisconnect()
+
+ return (
+ <>
+
+
Account
+
+ status: {account.status}
+
+ addresses: {JSON.stringify(account.addresses)}
+
+ chainId: {account.chainId}
+
+
disconnect()}>
+ Disconnect
+
+
+
+
Connect
+ {connectors.map((connector) => (
+
connect({ connector })}
+ type="button"
+ >
+ {connector.name}
+
+ ))}
+
{status}
+
{error?.message}
+
+
+ >
+ )
+ }
+
+ export default App
+
+ ```
+
+
+
+ Wagmi's `signMessage` function will open the Smart Wallet popup to sign the message. The signature is stored in the component's state.
+
+ ```tsx twoslash [src/SignInWithEthereum.tsx]
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
+ import { useAccount, usePublicClient, useSignMessage } from 'wagmi';
+ import { SiweMessage } from 'siwe';
+ import type { Hex } from 'viem';
+
+ export function SignInWithEthereum() {
+ const [signature, setSignature] = useState(undefined);
+ const { signMessage } = useSignMessage({ mutation: { onSuccess: (sig) => setSignature(sig) } });
+ const account = useAccount();
+
+ const siweMessage = useMemo(() => {
+ return new SiweMessage({
+ domain: document.location.host,
+ address: account.address,
+ chainId: account.chainId,
+ uri: document.location.origin,
+ version: '1',
+ statement: 'Smart Wallet SIWE Example',
+ nonce: '12345678', // replace with nonce generated by your backend
+ });
+ }, []);
+
+ const promptToSign = () => {
+ signMessage({ message: siweMessage.prepareMessage() });
+ };
+
+ return (
+
+
SIWE Example
+
Sign In with Ethereum
+ {signature &&
Signature: {signature}
}
+
+ );
+ }
+ ```
+
+
+
+
+ For Smart Wallet, [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) should be used to verify the SIWE message signature.
+
+
+ This simple example does not check the nonce during verification, all production implementations
+ should. Furthermore, nonces should be invalidated on logout to prevent replay attacks through
+ session duplication (e.g. store expired nonce and make sure they can't be used again). In
+ production apps, SIWE message verification is generally handled on the backend.
+
+
+ ```tsx twoslash [src/SignInWithEthereum.tsx]
+ // @noErrors: 2339
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
+ import type { Hex } from 'viem';
+ import { useAccount, usePublicClient, useSignMessage } from 'wagmi';
+ import { SiweMessage } from 'siwe';
+ export function SignInWithEthereum() {
+ const [signature, setSignature] = useState(undefined);
+ const [valid, setValid] = useState(undefined); // [!code focus]
+ const client = usePublicClient(); // [!code focus]
+ const { signMessage } = useSignMessage({ mutation: { onSuccess: (sig) => setSignature(sig) } });
+ const account = useAccount();
+
+ const message = useMemo(() => {
+ return new SiweMessage({
+ domain: document.location.host,
+ address: account.address,
+ chainId: account.chainId,
+ uri: document.location.origin,
+ version: '1',
+ statement: 'Smart Wallet SIWE Example',
+ nonce: '12345678', // replace with nonce generated by your backend
+ });
+ }, []);
+
+ const checkValid = useCallback(async () => { // [!code focus]
+ if (!signature || !account.address || !client) return; // [!code focus]
+ const isValid = await client.verifyMessage({ // [!code focus]
+ // [!code focus]
+ address: account.address, // [!code focus]
+ message: message.prepareMessage(), // [!code focus]
+ signature, // [!code focus]
+ }); // [!code focus]
+ setValid(isValid); // [!code focus]
+ }, [signature, account]); // [!code focus]
+
+ useEffect(() => { // [!code focus]
+ checkValid(); // [!code focus]
+ }, [signature, account]); // [!code focus]
+
+ const promptToSign = () => {
+ signMessage({ message: message.prepareMessage() });
+ };
+ return (
+
+
SIWE Example
+
Sign In with Ethereum
+ {signature &&
Signature: {signature}
}
+ {valid !== undefined &&
Is valid: {valid.toString()}
}{/* // [!code focus] */}
+
+ );
+ }
+ ```
+
+
+ Visit your local server and click "Sign In with Ethereum"
+
+
diff --git a/_pages/identity/smart-wallet/guides/spend-permissions.mdx b/_pages/identity/smart-wallet/guides/spend-permissions.mdx
new file mode 100644
index 00000000..68e3ca01
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/spend-permissions.mdx
@@ -0,0 +1,619 @@
+---
+title: "Build with Spend Permissions"
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+## Overview
+
+Spend Permissions enable third-party signers to spend assets (native and ERC-20 tokens) from a user's Smart Wallet.
+Once granted, Spend Permissions
+allow developers to move users'
+assets without any further signatures, unlocking use cases like subscriptions & trading bots.
+
+The following is a technical guide to build with Spend Permissions. If you want to learn more about high level details of this feature, check out the [Spend Permissions feature page](/identity/smart-wallet/concepts/features/optional/spend-permissions).
+
+## Let's build!
+
+This guide will walk you through a simple example of building an app that leverages Spend Permissions using
+[OnchainKit](https://onchainkit.xyz/), [Viem](https://viem.sh/) and [Wagmi](https://wagmi.sh/).
+
+The repository for the complete example of this demo can be found [here](https://github.com/ilikesymmetry/spend-permissions-quickstart).
+A hosted version of the demo can be found [here](https://subscribe-onchain.vercel.app/).
+Smart contract deployment addresses for `SpendPermissionManager.sol` can be found [here](https://github.com/coinbase/spend-permissions).
+
+
+
+ Set up a boilerplate React/Next app by running the following command and following the instructions. Don't worry about getting a Coinbase
+ Developer Platform API Key, you don't need one for this example. When prompted to use Coinbase Smart Wallet select "yes".
+
+ ```bash
+ npm create onchain@latest
+ ```
+
+ This will generate an app that is ready to run and contains a wallet connection button that users can use
+ to connect their smart wallet to the application.
+
+ From here, we'll modify the app to assemble, sign, approve and use a spend permission to spend our users' funds!
+
+
+ Add the following variables to your `.env`:
+
+ ```
+ SPENDER_PRIVATE_KEY=
+ NEXT_PUBLIC_SPENDER_ADDRESS=
+ ```
+
+
+ Always secure your private keys appropriately! We insecurely use an
+ environment variable in this demo for simplicity.
+
+
+ Our spender will need to sign transactions from our app, so we'll create a wallet (private key and address) for our spender.
+ If you have [Foundry](https://book.getfoundry.sh/) installed, you can generate a new wallet via `cast wallet new`. If you already have a
+ development keypair you can use that too. Assign the private key and address to
+ `SPENDER_PRIVATE_KEY` and `NEXT_PUBLIC_SPENDER_ADDRESS`, respectively.
+
+
+ Our client is what our app will use to communicate with the blockchain.
+
+ Create a sibling directory to `app` called `lib` and add the following `spender.ts` file to create your spender client.
+
+ ```ts [lib/spender. ts]
+ import { createPublicClient, createWalletClient, Hex, http } from "viem";
+ import { baseSepolia } from "viem/chains";
+ import { privateKeyToAccount } from "viem/accounts";
+
+ export async function getPublicClient() {
+ const client = createPublicClient({
+ chain: baseSepolia,
+ transport: http(),
+ });
+ return client;
+ }
+
+ export async function getSpenderWalletClient() {
+ const spenderAccount = privateKeyToAccount(
+ process.env.SPENDER_PRIVATE_KEY! as Hex
+ );
+
+ const spenderWallet = await createWalletClient({
+ account: spenderAccount,
+ chain: baseSepolia,
+ transport: http(),
+ });
+ return spenderWallet;
+ }
+ ```
+
+
+
+ In `app/providers.tsx`, update your configuration based on your environment:
+
+ - For testnets:
+ - Set `keysUrl: "https://keys-dev.coinbase.com/connect"`
+ - Replace all instances of `base` with `baseSepolia` (including the import)
+ - For mainnets:
+ - Leave `keysUrl` undefined (defaults to keys.coinbase.com)
+ - Keep the default `base` chain from the template
+
+ Your config in `app/providers.tsx` should look like this for testnet:
+
+ ```ts
+ const config = createConfig({
+ chains: [baseSepolia],
+ connectors: [
+ coinbaseWallet({
+ appName: process.env.NEXT_PUBLIC_ONCHAINKIT_PROJECT_NAME,
+ preference: process.env.NEXT_PUBLIC_ONCHAINKIT_WALLET_CONFIG as
+ | "smartWalletOnly"
+ | "all",
+ // @ts-ignore
+ keysUrl: "https://keys-dev.coinbase.com/connect"
+ }),
+ ],
+ storage: createStorage({
+ storage: cookieStorage,
+ }),
+ ssr: true,
+ transports: {
+ [baseSepolia.id]: http(),
+ },
+ });
+ ```
+
+
+ Spend permissions are managed by a singleton contract called the `SpendPermissionManager`. We'll add some
+ configuration so our client knows how to interact with this contract.
+
+ Inside your `/lib` directory, create a new subdirectory called `/abi`. This is where we'll store information about
+ [smart contract interfaces](https://docs.soliditylang.org/en/latest/abi-spec.html) and addresses.
+
+ Add a new file called `SpendPermissionManager.ts` and copy and paste the code from [this file](https://github.com/ilikesymmetry/spend-permissions-quickstart/blob/main/lib/abi/SpendPermissionManager.ts).
+
+
+ [Here's an example](https://docs.basescan.org/api-endpoints/contracts) of
+ finding the ABI for any verified contract on Basescan.
+
+
+
+ Let's create a button that will prompt a user to subscribe to our services by authorizing
+ a spend permission for our app to spend their assets.
+
+ Create a subdirectory inside `/app` called `/components` and paste the following code into a new file called `Subscribe.tsx`.
+
+ We'll walk through what's happening here in subsequent steps.
+
+ ```tsx showLineNumbers
+ "use client";
+ import { cn, color, pressable, text } from "@coinbase/onchainkit/theme";
+ import { useEffect, useState } from "react";
+ import {
+ useAccount,
+ useChainId,
+ useConnect,
+ useConnectors,
+ useSignTypedData,
+ } from "wagmi";
+ import { Address, Hex, parseUnits } from "viem";
+ import { useQuery } from "@tanstack/react-query";
+ import { spendPermissionManagerAddress } from "@/lib/abi/SpendPermissionManager";
+
+ export default function Subscribe() {
+ const [isDisabled, setIsDisabled] = useState(false);
+ const [signature, setSignature] = useState();
+ const [transactions, setTransactions] = useState([]);
+ const [spendPermission, setSpendPermission] = useState();
+
+ const { signTypedDataAsync } = useSignTypedData();
+ const account = useAccount();
+ const chainId = useChainId();
+ const { connectAsync } = useConnect();
+ const connectors = useConnectors();
+
+ const { data, error, isLoading, refetch } = useQuery({
+ queryKey: ["collectSubscription"],
+ queryFn: handleCollectSubscription,
+ refetchOnWindowFocus: false,
+ enabled: !!signature,
+ });
+
+ async function handleSubmit() {
+ setIsDisabled(true);
+ let accountAddress = account?.address;
+ if (!accountAddress) {
+ try {
+ const requestAccounts = await connectAsync({
+ connector: connectors[0],
+ });
+ accountAddress = requestAccounts.accounts[0];
+ } catch {
+ return;
+ }
+ }
+
+ const spendPermission = {
+ account: accountAddress, // User wallet address
+ spender: process.env.NEXT_PUBLIC_SPENDER_ADDRESS! as Address, // Spender smart contract wallet address
+ token: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" as Address, // ETH (https://eips.ethereum.org/EIPS/eip-7528)
+ allowance: parseUnits("10", 18),
+ period: 86400, // seconds in a day
+ start: 0, // unix timestamp
+ end: 281474976710655, // max uint48
+ salt: BigInt(0),
+ extraData: "0x" as Hex,
+ };
+
+ try {
+ const signature = await signTypedDataAsync({
+ domain: {
+ name: "Spend Permission Manager",
+ version: "1",
+ chainId: chainId,
+ verifyingContract: spendPermissionManagerAddress,
+ },
+ types: {
+ SpendPermission: [
+ { name: "account", type: "address" },
+ { name: "spender", type: "address" },
+ { name: "token", type: "address" },
+ { name: "allowance", type: "uint160" },
+ { name: "period", type: "uint48" },
+ { name: "start", type: "uint48" },
+ { name: "end", type: "uint48" },
+ { name: "salt", type: "uint256" },
+ { name: "extraData", type: "bytes" },
+ ],
+ },
+ primaryType: "SpendPermission",
+ message: spendPermission,
+ });
+ setSpendPermission(spendPermission);
+ setSignature(signature);
+ } catch (e) {
+ console.error(e);
+ }
+ setIsDisabled(false);
+ }
+
+ async function handleCollectSubscription() {
+ setIsDisabled(true);
+ let data;
+ try {
+ const replacer = (key: string, value: any) => {
+ if (typeof value === "bigint") {
+ return value.toString();
+ }
+ return value;
+ };
+ const response = await fetch("/collect", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(
+ {
+ spendPermission,
+ signature,
+ dummyData: Math.ceil(Math.random() * 100),
+ },
+ replacer
+ ),
+ });
+ if (!response.ok) {
+ throw new Error("Network response was not ok");
+ }
+ data = await response.json();
+ } catch (e) {
+ console.error(e);
+ }
+ setIsDisabled(false);
+ return data;
+ }
+
+ useEffect(() => {
+ if (!data) return;
+ setTransactions([data?.transactionHash, ...transactions]);
+ }, [data]);
+
+ return (
+
+ {!signature ? (
+
+
+
+ Subscribe
+
+
+
+ ) : (
+
+
+ refetch()}
+ type="button"
+ disabled={isDisabled}
+ data-testid="collectSubscriptionButton_Button"
+ >
+
+ Collect Subscription
+
+
+
+
+
Subscription Payments
+
+
+
+ )}
+
+ );
+ }
+ ```
+
+ Also be sure to add this new `Subscribe` button component to the top level component in `page.tsx`.
+ You can delete lines 77-127 and put your button there.
+
+ ```tsx [page.tsx]
+ ...
+
+
+
+ ...
+ ```
+
+
+
+ A `SpendPermission` is the struct that defines the parameters of the permission.
+ See the solidity struct [here](https://github.com/coinbase/spend-permissions/blob/main/src/SpendPermissionManager.sol#L18-L38).
+
+ You can see the spend permission object being defined in lines 49-59 of our `Subscribe` component:
+
+ ```tsx [Subscribe.tsx]
+
+ export default function Subscribe() {
+ ...
+
+ async function handleSubmit() {
+ setIsDisabled(true);
+ let accountAddress = account?.address;
+ if (!accountAddress) {
+ try {
+ const requestAccounts = await connectAsync({
+ connector: connectors[0],
+ });
+ accountAddress = requestAccounts.accounts[0];
+ } catch {
+ return;
+ }
+ }
+
+ // Define a `SpendPermission` to request from the user // [!code focus]
+ const spendPermission = { // [!code focus]
+ account: accountAddress, // User wallet address // [!code focus]
+ spender: process.env.NEXT_PUBLIC_SPENDER_ADDRESS! as Address, // Spender smart contract wallet address // [!code focus]
+ token: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" as Address, // ETH (https://eips.ethereum.org/EIPS/eip-7528) // [!code focus]
+ allowance: parseUnits("10", 18), // [!code focus]
+ period: 86400, // seconds // [!code focus]
+ start: 0, // unix time, seconds // [!code focus]
+ end: 281474976710655, // max uint48 // [!code focus]
+ salt: BigInt(0), // [!code focus]
+ extraData: "0x" as Hex, // [!code focus]
+ }; // [!code focus]
+ }
+ ...
+
+ return (
+
+ ...
+
+ );
+ }
+ ```
+
+
+ As part of our button handler `handleSubmit`, in lines 61-91 of our subscribe component we call [`signTypedDataAsync`](https://wagmi.sh/react/api/hooks/useSignTypedData),
+ a Wagmi hook that will prompt our user to create a signature from their wallet across the details of the spend permission.
+
+
+ SpendPermissions use [ERC-712](https://eips.ethereum.org/EIPS/eip-712)
+ signatures.
+
+
+ ```tsx [Subscribe.tsx]
+
+ export default function Subscribe() {
+ ...
+
+ async function handleSubmit() {
+ ...
+
+ // Obtain signature over `SpendPermission` from user // [!code focus]
+ try {
+ const signature = await signTypedDataAsync({ // [!code focus]
+ domain: { // [!code focus]
+ name: "Spend Permission Manager", // [!code focus]
+ version: "1", // [!code focus]
+ chainId: chainId, // [!code focus]
+ verifyingContract: spendPermissionManagerAddress, // [!code focus]
+ }, // [!code focus]
+ types: { // [!code focus]
+ SpendPermission: [ // [!code focus]
+ { name: "account", type: "address" }, // [!code focus]
+ { name: "spender", type: "address" }, // [!code focus]
+ { name: "token", type: "address" }, // [!code focus]
+ { name: "allowance", type: "uint160" }, // [!code focus]
+ { name: "period", type: "uint48" }, // [!code focus]
+ { name: "start", type: "uint48" }, // [!code focus]
+ { name: "end", type: "uint48" }, // [!code focus]
+ { name: "salt", type: "uint256" }, // [!code focus]
+ { name: "extraData", type: "bytes" }, // [!code focus]
+ ], // [!code focus]
+ }, // [!code focus]
+ primaryType: "SpendPermission", // [!code focus]
+ message: spendPermission, // [!code focus]
+ }); // [!code focus]
+ setSpendPermission(spendPermission); // [!code focus]
+ setSignature(signature); // [!code focus]
+ } catch (e) {
+ console.error(e)
+ }
+ setIsDisabled(false);
+ }
+
+ ...
+
+ return (
+
+ ...
+
+ );
+ }
+ ```
+
+
+
+ Now that we have a signature from the user, we can approve the permission onchain by submitting the
+ signature and the permission details to `approveWithSignature` on the `SpendPermissionManager` contract.
+
+ Our `handleCollectSubscription` function that's defined in our `Subscribe` will pass this signature and data to our
+ backend, so the spender client we created earlier can handle our onchain calls.
+
+ ```ts [Subscribe.tsx]
+ // We send the permission details and the user signature to our backend route
+ async function handleCollectSubscription() {
+ setIsDisabled(true);
+ let data;
+ try {
+ const replacer = (key: string, value: any) => {
+ if (typeof value === "bigint") {
+ return value.toString();
+ }
+ return value;
+ };
+ const response = await fetch("/collect", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify(
+ {
+ spendPermission,
+ signature,
+ dummyData: Math.ceil(Math.random() * 100),
+ },
+ replacer
+ ),
+ });
+ if (!response.ok) {
+ throw new Error("Network response was not ok");
+ }
+ data = await response.json();
+ } catch (e) {
+ console.error(e);
+ }
+ setIsDisabled(false);
+ return data;
+ }
+ ```
+
+ But wait, we don't have any backend routes set up yet! Let's define what should happen when we want to
+ approve and use our spend permission.
+
+ Create a new subdirectory under `/app` called `collect`. Create a new file there called `route.tsx` and paste the following
+ code:
+
+ ```ts [route.tsx]
+ import { NextRequest, NextResponse } from "next/server";
+ import { getPublicClient, getSpenderWalletClient } from "../../lib/spender";
+ import {
+ spendPermissionManagerAbi,
+ spendPermissionManagerAddress,
+ } from "../../lib/abi/SpendPermissionManager";
+
+ export async function POST(request: NextRequest) {
+ const spenderBundlerClient = await getSpenderWalletClient();
+ const publicClient = await getPublicClient();
+ try {
+ const body = await request.json();
+ const { spendPermission, signature } = body;
+
+ const approvalTxnHash = await spenderBundlerClient.writeContract({
+ address: spendPermissionManagerAddress,
+ abi: spendPermissionManagerAbi,
+ functionName: "approveWithSignature",
+ args: [spendPermission, signature],
+ });
+
+ const approvalReceipt = await publicClient.waitForTransactionReceipt({
+ hash: approvalTxnHash,
+ });
+
+ const spendTxnHash = await spenderBundlerClient.writeContract({
+ address: spendPermissionManagerAddress,
+ abi: spendPermissionManagerAbi,
+ functionName: "spend",
+ args: [spendPermission, "1"],
+ });
+
+ const spendReceipt = await publicClient.waitForTransactionReceipt({
+ hash: spendTxnHash,
+ });
+
+ return NextResponse.json({
+ status: spendReceipt.status ? "success" : "failure",
+ transactionHash: spendReceipt.transactionHash,
+ transactionUrl: `https://sepolia.basescan.org/tx/${spendReceipt.transactionHash}`,
+ });
+ } catch (error) {
+ console.error(error);
+ return NextResponse.json({}, { status: 500 });
+ }
+ }
+ ```
+
+ This code is using our spender client to do two things:
+
+ 1. calls `approveWithSignature` to approve the spend permission
+ 2. calls `spend` to make use of our allowance and spend our user's funds
+
+
+
+ Run your app locally with `npm run dev` and visit `localhost:3000`.
+
+ When you click the "Subscribe" button you should be prompted to create or connect your Smart Wallet.
+
+ You can create a new Smart Wallet via the popup. Note that you'll need a little ETH in this wallet to fund the
+ deployment of your account. If you don't have any testnet ETH, try this [Coinbase faucet](https://portal.cdp.coinbase.com/products/faucet).
+
+ Note that we'll need a little bit of base sepolia ETH in both wallet addresses (the "user" wallet and the "app" wallet).
+ In a more involved implementation you would use a paymaster to eliminate this requirement.
+ For now, If you don't have any base sepolia ETH, try this [Coinbase faucet](https://portal.cdp.coinbase.com/products/faucet).
+
+ Once your wallet is created and both wallets are funded, return to the app and click "Subscribe", then sign the prompt to allow the spend permission.
+
+ Once you've subscribed, you should see a spend transaction hash show up on screen after a few seconds.
+ You can prompt subsequent spends by clicking the "Collect Subscription" button. Click the transactions to check them out on Etherscan!
+
+ We've made it! 🎉
+
+ Our app successfully
+
+ - prompts the user to connect their Coinbase Smart Wallet to our app
+ - assembles a spend permission representing our recurring spending needs as an app
+ - retrieves a signature from the user authorizing this spend permission
+ - approves the spend permission onchain
+ - uses this permission to retrieve user assets within our allowance
+
+
+
+
diff --git a/_pages/identity/smart-wallet/guides/sub-accounts/index.mdx b/_pages/identity/smart-wallet/guides/sub-accounts/index.mdx
new file mode 100644
index 00000000..9db3dd09
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/sub-accounts/index.mdx
@@ -0,0 +1,50 @@
+---
+title: 'Creating Sub Accounts with Smart Wallet'
+description: 'Learn how to create and manage Sub Accounts using Smart Wallet'
+---
+
+import { GithubRepoCard } from "/snippets/GithubRepoCard.mdx"
+
+# Build with Sub Accounts
+
+## Overview
+
+Smart Wallet's self-custodial design requires a user passkey prompt for each wallet interaction that the user is presented with, such as a transaction or message signing. While this helps ensure the user is aware and required to approve of every interaction with their wallet, this also impacts user experience when using applications that may require frequent wallet interactions.
+
+To support using Smart Wallet with user experiences that require more developer control over the wallet interactions, we've built Sub Accounts in conjunction with [ERC-7895](https://eip.tools/eip/7895), a new wallet RPC for creating hierarchical relationships between wallet accounts.
+
+Sub Accounts allow you to provision wallet accounts that are directly embedded in your application for your users. You can control when a Sub Account is created for your user, and can interact with them just as you would with another wallet via the wallet provider, or other popular web3 libraries like OnchainKit, wagmi, viem, etc.
+
+These Sub Accounts are linked to the end user's Smart wallet through an onchain relationship. When combined with our [Spend Permission feature](/identity/smart-wallet/guides/spend-permissions), this creates a powerful foundation for provisioning and funding app accounts, securely, while giving you, the developer, ample control over building the user experience that makes the most sense for your application.
+
+If you would like to see a live demo of Sub Accounts in action, check out our [Sub Accounts Demo](https://sub-account-demo.com).
+
+## What You'll Build
+
+In this guide, we'll set up a basic React app using NextJS and Wagmi that:
+
+- Lets users log in with their Smart Wallet
+- Creates a Sub Account scoped to the app with the a Spend Permission included in the configuration
+- Uses the Sub Account to perform popup-less transactions
+
+### Skip ahead
+
+If you want to skip ahead and just get the final code, you can find it here:
+
+
+
+## Guide Sections
+
+1. [Project Setup](/identity/smart-wallet/guides/sub-accounts/setup) - Learn how to set up a NextJS project with Wagmi and configure Smart Wallet Sub Accounts. This guide covers:
+
+ - Creating a new NextJS+Wagmi project
+ - Configuring Smart Wallet with development environment settings
+ - Setting up spend permissions for native ETH
+ - Creating a basic wallet connection interface
+
+2. [Using Sub Accounts](/identity/smart-wallet/guides/sub-accounts/using-sub-accounts) - Explore how to interact with Sub Accounts in your application. This guide demonstrates:
+
+ - Signing messages with Sub Accounts using `useSignMessage`
+ - Sending transactions with spend limits using `useSendTransaction`
+ - Building a complete interface for wallet connection and transactions
+ - Understanding transaction limitations based on spend permissions
diff --git a/_pages/identity/smart-wallet/guides/sub-accounts/setup.mdx b/_pages/identity/smart-wallet/guides/sub-accounts/setup.mdx
new file mode 100644
index 00000000..4c2c866b
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/sub-accounts/setup.mdx
@@ -0,0 +1,229 @@
+---
+title: 'Project Setup'
+description: 'Set up a NextJS project with Smart Wallet integration'
+---
+
+import { GithubRepoCard } from "/snippets/GithubRepoCard.mdx"
+
+
+# Project Setup
+
+In this section, we'll set up a NextJS project using [Wagmi](https://wagmi.sh/) hooks and configure it to work with Smart Wallet.
+
+## What you'll achieve
+
+By the end of this guide, you will:
+
+- Set up a NextJS+Wagmi project and configure it to work with Smart Wallet
+- Create a Wagmi config to use Sub Accounts in conjunction with Spend Permissions
+
+### Skip ahead
+
+If you want to skip ahead and just get the final code, you can find it here:
+
+
+
+## Creating a New Project
+
+We'll be using a [Wagmi template](https://wagmi.sh/react/getting-started) to bootstrap this project.
+
+First, create a new Wagmi project:
+
+```bash [Terminal]
+npm create wagmi@latest my-app
+```
+
+Select the following options:
+
+```bash [Terminal]
+✔ Select a framework: › React
+✔ Select a variant: › Next
+```
+
+Navigate into the project directory:
+
+```bash [Terminal]
+cd my-app
+```
+
+Let's override the default `@coinbase/wallet-sdk` version with the latest canary version:
+
+```bash [Terminal]
+npm pkg set overrides.@coinbase/wallet-sdk=canary
+```
+
+
+**Override before installing**
+
+Sub Accounts are currently only available in the Smart Wallet development environment (Canary).
+Make sure to override the `@coinbase/wallet-sdk` version BEFORE installing the packages.
+
+
+
+Install packages
+
+```bash [Terminal]
+npm install
+```
+
+## Setting up the Wagmi config
+
+After running these commands, it is time to set up the Wagmi config.
+
+```ts [wagmi.ts]
+import { http, cookieStorage, createConfig, createStorage } from "wagmi";
+import { baseSepolia, base } from "wagmi/chains";
+import { coinbaseWallet } from "wagmi/connectors";
+import { parseEther, toHex } from "viem";
+
+export function getConfig() {
+ return createConfig({
+ chains: [baseSepolia, base],
+ connectors: [
+ coinbaseWallet({
+ appName: "My Sub Account Demo",
+ preference: {
+ keysUrl: "https://keys-dev.coinbase.com/connect",
+ options: "smartWalletOnly",
+ },
+ subAccounts: {
+ enableAutoSubAccounts: true,
+ defaultSpendLimits: {
+ 84532: [ // Base Sepolia Chain ID
+ {
+ token: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",
+ allowance: toHex(parseEther('0.01')), // 0.01 ETH
+ period: 86400, // 24h
+ },
+ ],
+ },
+ },
+ }),
+ ],
+ storage: createStorage({
+ storage: cookieStorage,
+ }),
+ ssr: true,
+ transports: {
+ [baseSepolia.id]: http(),
+ [base.id]: http(),
+ },
+ });
+}
+
+declare module "wagmi" {
+ interface Register {
+ config: ReturnType;
+ }
+}
+```
+
+Let's break down the key preference parameters:
+
+- `keysUrl`: Points to the development environment for Smart Wallet testing
+- `options: 'smartWalletOnly'`: Ensures only Smart Wallet mode is used
+- `enableAutoSubAccounts: true`: When set to true, automatically creates a Sub Account at connection
+- `defaultSpendLimits`: Configures Spend Limits for Sub Account for a network (eg. Base Sepolia `84532`), including:
+
+ - Token address (In this case, `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` represents the native ETH)
+ - Allowance WEI amount (in Hex)
+ - Time period for the allowance (in seconds, e.g., 86400 for 24 hours)
+
+
+**About `keysUrl`**
+
+Sub Accounts are currently only available in the Smart Wallet development environment.
+To use this environment, you need to set the `keysUrl` to `https://keys-dev.coinbase.com/connect` in your configuration.
+
+
+
+## Creating the Home Page
+
+Update your main page (`app/page.tsx`) to include wallet connection functionality:
+
+```tsx [app/page.tsx]
+'use client'
+
+import { parseEther } from 'viem'
+import { useAccount, useConnect, useDisconnect, useSendTransaction } from 'wagmi'
+
+function App() {
+ const account = useAccount()
+ const { connectors, connect, status, error } = useConnect()
+ const { disconnect } = useDisconnect()
+ return (
+ <>
+
+
Account
+
+
+ Status: {account.status}
+
+ Sub Account Address: {JSON.stringify(account.addresses)}
+
+ Chain ID: {account.chainId}
+
+
+ {account.status === 'connected' && (
+
disconnect()}>
+ Disconnect
+
+ )}
+
+
+
+
Connect
+ {connectors
+ .filter((connector) => connector.name === 'Coinbase Wallet')
+ .map((connector) => (
+
connect({ connector })}
+ type="button"
+ >
+ Sign in with Smart Wallet
+
+ ))}
+
{status}
+
{error?.message}
+
+ >
+ )
+}
+
+export default App
+
+```
+
+## Testing the Setup
+
+1. Start your development server:
+
+```bash [Terminal]
+npm run dev
+```
+
+2. Open your browser and navigate to `http://localhost:3000`.
+3. Click the "Sign in with Smart Wallet" button to connect your Smart Wallet.
+4. You should be prompted to create a Sub Account as shown below.
+
+
+
+
Sub Account Creation Pop-up
+
+
+Now that you have the basic setup complete, you're ready to start using Sub Accounts! Continue to the next section to learn how to send transactions using your Sub Account.
+
+## Next Steps
+
+In the next section, we'll cover [Using Sub Accounts](/identity/smart-wallet/guides/sub-accounts/using-sub-accounts) - how to sign messages and send transactions with your Sub Account.
+
+## Additional Resources
+
+- You can take a look at the final code in the [Sub Account Starter Template Demo](https://github.com/base/demos/smart-wallet/sub-accounts-demo)
+- You can reach out to us on the #smart-wallet channel on [Discord](https://discord.com/invite/cdp) if you have any questions or feedback
diff --git a/_pages/identity/smart-wallet/guides/sub-accounts/using-sub-accounts.mdx b/_pages/identity/smart-wallet/guides/sub-accounts/using-sub-accounts.mdx
new file mode 100644
index 00000000..99a62c9e
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/sub-accounts/using-sub-accounts.mdx
@@ -0,0 +1,197 @@
+---
+title: 'Using Sub Accounts'
+description: 'Learn how to sign messages and send transactions with your Sub Account'
+---
+
+import { GithubRepoCard } from "/snippets/GithubRepoCard.mdx"
+
+# Using Sub Accounts
+
+Once you've [set up your Wagmi config and created your Smart Wallet connection](/identity/smart-wallet/guides/sub-accounts/setup), you can use it to perform various operations like signing messages and sending transactions.
+In this section, we'll cover how to send a simple transaction using your Sub Account.
+
+## What you'll achieve
+
+By the end of this guide, you will:
+
+- Know how to sign a message with your user's Sub Account
+- Know how to send a transaction with your user's Sub Account
+
+
+
+
Sub Account Demo
+
+
+### Skip ahead
+
+If you want to skip ahead and just get the final code, you can find it here:
+
+
+
+## Sign a Message
+
+To sign a message with a Sub Account, we'll use Wagmi's `useSignMessage` hook. Here's how to implement message signing:
+
+```tsx [app/page.tsx]
+import { useSignMessage } from 'wagmi'
+
+function App() {
+ const { signMessage, data: signData } = useSignMessage()
+
+ return (
+
+
Sign Message
+
signMessage({ message: 'Hello World' })}
+ >
+ Sign Message
+
+
{signData}
+
+ )
+}
+```
+
+The signed message data will be available in the `signData` variable after the user approves the signature request.
+
+## Send a Transaction
+
+To send transactions with a Sub Account, we'll use Wagmi's `useSendTransaction` hook. Remember that transactions will be limited by the spend permissions we configured in the [setup](/identity/smart-wallet/guides/sub-accounts/setup):
+
+```tsx [app/page.tsx]
+import { parseEther } from 'viem'
+import { useSendTransaction } from 'wagmi'
+
+function App() {
+ const { sendTransactionAsync, data } = useSendTransaction()
+
+ return (
+
+
Send Transaction
+
sendTransactionAsync({
+ to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',
+ value: parseEther('0.001'),
+ })}
+ >
+ Send Transaction
+
+
{data && "Transaction sent successfully! 🎉"}
+
{data}
+
+ )
+}
+```
+
+The transaction parameters include:
+
+- `to`: The recipient address
+- `value`: The amount of ETH to send (converted from ETH to Wei using `parseEther`)
+
+The transaction data will be available in the `data` variable after it succeeds on the network.
+
+Remember that transactions will only succeed if they:
+
+1. Don't exceed the allowance specified in `spendPermissionConfig`
+2. Use the permitted token (in this case, native ETH)
+3. Fall within the configured time period
+
+## Complete Example
+
+Here's a complete example combining account connection, message signing, and transaction sending:
+
+```tsx [app/page.tsx]
+'use client'
+
+import { parseEther } from 'viem'
+import { useAccount, useConnect, useDisconnect, useSendTransaction, useSignMessage } from 'wagmi'
+
+function App() {
+ const account = useAccount()
+ const { connectors, connect, status, error } = useConnect()
+ const { disconnect } = useDisconnect()
+ const { sendTransactionAsync, data } = useSendTransaction()
+ const { signMessage, data: signData } = useSignMessage()
+ return (
+ <>
+
+
Account
+
+
+ Status: {account.status}
+
+ Sub Account Address: {JSON.stringify(account.addresses)}
+
+ ChainId: {account.chainId}
+
+
+ {account.status === 'connected' && (
+
disconnect()}>
+ Disconnect
+
+ )}
+
+
+
+
Connect
+ {connectors
+ .filter((connector) => connector.name === 'Coinbase Wallet')
+ .map((connector) => (
+
connect({ connector })}
+ type="button"
+ >
+ Sign in with Smart Wallet
+
+ ))}
+
{status}
+
{error?.message}
+
Send Transaction
+
sendTransactionAsync({
+ to: '0xd8da6bf26964af9d7eed9e03e53415d37aa96045',
+ value: parseEther('0.001'),
+ })}>
+ Send Transaction
+
+
{data && "Transaction sent successfully! 🎉"}
+
{data}
+
+
Sign Message
+
signMessage({ message: 'Hello World' })}
+ >
+ Sign Message
+
+
{signData}
+
+ >
+ )
+}
+
+export default App
+
+```
+
+This example provides a complete interface for users to:
+
+1. Connect their Smart Wallet (Sign in with Smart Wallet button)
+2. View their Sub Account address
+3. Sign messages
+4. Send transactions
+5. Disconnect their wallet
+
+All transactions will be automatically limited by the spend permissions configured in your [Wagmi setup](/identity/smart-wallet/guides/sub-accounts/setup).
+
+## Additional Resources
+
+- You can take a look at the final code in the [Sub Account Starter Template Demo](https://github.com/base/demos/tree/master/smart-wallet/sub-accounts-demo)
+- You can reach out to us on the #smart-wallet channel on [Discord](https://discord.com/invite/cdp) if you have any questions or feedback
diff --git a/_pages/identity/smart-wallet/guides/tips/inspect-txn-simulation.mdx b/_pages/identity/smart-wallet/guides/tips/inspect-txn-simulation.mdx
new file mode 100644
index 00000000..742cccab
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/tips/inspect-txn-simulation.mdx
@@ -0,0 +1,14 @@
+---
+title: "Transaction Simulation Data"
+---
+
+There is a hidden feature which enables you to easily copy transaction simulation request and response data which can then be pasted it in a text editor to inspect.
+
+## Instructions
+- Click the area defined in red **_five times_**, then paste the copied data in a text editor.
+
+
+
+ 
+
+
\ No newline at end of file
diff --git a/_pages/identity/smart-wallet/guides/tips/popup-tips.mdx b/_pages/identity/smart-wallet/guides/tips/popup-tips.mdx
new file mode 100644
index 00000000..2bb616ac
--- /dev/null
+++ b/_pages/identity/smart-wallet/guides/tips/popup-tips.mdx
@@ -0,0 +1,37 @@
+---
+title: "Popup Tips"
+---
+
+
+## Overview
+When a Smart Wallet is connected and Coinbase Wallet SDK receives a request, it opens
+[keys.coinbase.com](https://keys.coinbase.com/) in a popup window and passes the request to the popup for handling.
+Keep the following points in mind when working with the Smart Wallet popup.
+
+## Default blocking behavior
+- Most modern browsers block all popups by default, unless they are triggered by a click.
+- If a popup is blocked the browser shows a notification to the user, allowing them to manage popup settings.
+
+### What to do about it
+- Ensure there is no additional logic between the button click and the request to open the Smart Wallet popup,
+as browsers might perceive the request as programmatically initiated.
+- If logic is unavoidable, keep it minimal and test thoroughly in all supported browsers.
+
+## `Cross-Origin-Opener-Policy`
+If the Smart Wallet popup opens and displays an error or infinite spinner, it may be due to the dapp's `Cross-Origin-Opener-Policy`. Be sure to use a directive that allows the Smart Wallet popup to function.
+
+- ✅ Allows Smart Wallet popup to function
+ - `unsafe-none` (default)
+ - `same-origin-allow-popups` (recommended)
+- ❌ Breaks Smart Wallet popup
+ - `same-origin`
+
+For more detailed information refer to the [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy).
+
+
+## Smart Wallet popup 'linger' behavior
+- Sometimes a dapp may programmatically make a followup request based on the response to a previous request.
+Normally, browsers block these programmatic requests to open popups.
+- To address this, after the Smart Wallet popup responds to a request, it will linger for 200ms to listen for another incoming request before closing.
+ - If a request is received *during* this 200ms window, it will be received and handled within the same popup window.
+ - If a request is received *after* the 200ms window and the popup has closed, opening the Smart Wallet popup will be blocked by the browser.
\ No newline at end of file
diff --git a/_pages/identity/smart-wallet/quickstart/ai-tools-available-for-devs.mdx b/_pages/identity/smart-wallet/quickstart/ai-tools-available-for-devs.mdx
new file mode 100644
index 00000000..7c81d197
--- /dev/null
+++ b/_pages/identity/smart-wallet/quickstart/ai-tools-available-for-devs.mdx
@@ -0,0 +1,34 @@
+---
+title: "AI Tools for Smart Wallet Developers"
+---
+
+import SmartWalletAITools from '@/components/SmartWalletAITools';
+
+
+Smart Wallet has a number of AI tools available for builders and developers.
+We keep expanding the list of tools and features, so please check back soon for updates.
+
+
+
+## Base Builder MCP
+
+This repository is an [Model Context Protocol](https://modelcontextprotocol.io/introduction) server destined for Base Builders.
+It contains a list of tools that you can give your AI coding assistant to help it build with Smart Wallet
+
+In particular, it allows your AI coding assistant to efficiently find the right guides that are relevant to the code you are writing.
+
+[Base Builder MCP](https://github.com/base/base-builder-mcp)
+
+## LLMs.txt File
+
+This is a simple text file that contains the full context of our documentation for your LLMs.
+It is a convenient and useful tool for your AI coding assistant to help it build with Smart Wallet.
+
+[LLMs.txt File](https://docs.base.org/identity/smart-wallet/llms.txt)
+
+## Agent Kit
+
+This is a tool that allows you to build your AI agent using embedded Wallet APIs.
+It is a great starting point for your AI agent projects with Smart Wallet.
+
+[Agent Kit](https://docs.cdp.coinbase.com/agentkit/docs/welcome)
diff --git a/_pages/identity/smart-wallet/quickstart/index.mdx b/_pages/identity/smart-wallet/quickstart/index.mdx
new file mode 100644
index 00000000..3734d9dd
--- /dev/null
+++ b/_pages/identity/smart-wallet/quickstart/index.mdx
@@ -0,0 +1,27 @@
+---
+title: "Get Started with Smart Wallet"
+---
+
+import SmartWalletQuickstartOptions from '@/components/SmartWalletQuickstartOptions';
+
+
+Smart Wallet is a multi-chain self-custodial cryptocurrency wallet. It enables users to create an account in seconds
+with no app or extension required, thanks to its reliance on [Passkeys](https://passkeys.com).
+
+This simple guide provides three options for getting started with Smart Wallet:
+
+1. [Quick Demo using OnchainKit (5 mins)](/identity/smart-wallet/quickstart/quick-demo) - The fastest way to see Smart Wallet in action
+2. [Add to Existing Next.js Project (15 mins)](/identity/smart-wallet/quickstart/nextjs-project) - Step-by-step integration with an existing Next.js application using Wagmi
+3. [Add to Existing React Native Project](/identity/smart-wallet/quickstart/react-native-project) - A detailed guide for adding Smart Wallet to an existing React Native project
+
+
+
+## Explore More Features
+
+After implementing either option, you can:
+
+- [Use MagicSpend](/identity/smart-wallet/concepts/features/built-in/MagicSpend): Use Coinbase balances onchain;
+- [Get Free Sponsored Transactions](/identity/smart-wallet/concepts/base-gasless-campaign): Use Paymaster to sponsor transactions and get qualified for up to $15k in gas credits;
+- [Support Sub Accounts](/identity/smart-wallet/guides/sub-accounts): Improve user experience with embedded accounts controlled by their Smart Wallet,
+ eliminating the need for users to sign a transaction for every onchain action.
+- [Support Spend Permissions](/identity/smart-wallet/guides/spend-permissions): Enable third-party signers to spend user assets
diff --git a/_pages/identity/smart-wallet/quickstart/nextjs-project.mdx b/_pages/identity/smart-wallet/quickstart/nextjs-project.mdx
new file mode 100644
index 00000000..e9e24fe4
--- /dev/null
+++ b/_pages/identity/smart-wallet/quickstart/nextjs-project.mdx
@@ -0,0 +1,210 @@
+---
+title: "Add to Existing Next.js Project (with Wagmi)"
+---
+
+This option guides you through adding Smart Wallet to an existing
+[Next.js](https://nextjs.org/) application using [Wagmi](https://wagmi.sh/).
+
+## Step 1: Install Dependencies
+
+Let's start by navigating to your project directory and installing the dependencies:
+
+
+```bash [npm]
+npm install @coinbase/wallet-sdk wagmi viem @tanstack/react-query
+```
+
+```bash [pnpm]
+pnpm add @coinbase/wallet-sdk wagmi viem @tanstack/react-query
+```
+
+```bash [yarn]
+yarn add @coinbase/wallet-sdk wagmi viem @tanstack/react-query
+```
+
+```bash [bun]
+bun add @coinbase/wallet-sdk wagmi viem @tanstack/react-query
+```
+
+
+## Step 2: Create Wagmi Config
+
+If your project does not have a Wagmi Config, create a file called `wagmi.ts` in the root directory of your project:
+
+```typescript
+import { http, createConfig } from "wagmi";
+import { baseSepolia } from "wagmi/chains";
+import { coinbaseWallet } from "wagmi/connectors";
+
+export const cbWalletConnector = coinbaseWallet({
+ appName: "Wagmi Smart Wallet",
+ preference: "smartWalletOnly",
+});
+
+export const config = createConfig({
+ chains: [baseSepolia],
+ // turn off injected provider discovery
+ multiInjectedProviderDiscovery: false,
+ connectors: [cbWalletConnector],
+ ssr: true,
+ transports: {
+ [baseSepolia.id]: http(),
+ },
+});
+
+declare module "wagmi" {
+ interface Register {
+ config: typeof config;
+ }
+}
+```
+
+
+The wagmi config is modified to include the Smart Wallet connector `cbWalletConnector`.
+
+
+## Step 3: Create Providers Component
+
+Create a file called `providers.tsx` in your `app/` directory:
+
+```tsx
+"use client";
+
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { useState, type ReactNode } from "react";
+import { WagmiProvider } from "wagmi";
+
+import { config } from "@/wagmi";
+
+export function Providers(props: { children: ReactNode }) {
+ const [queryClient] = useState(() => new QueryClient());
+
+ return (
+
+
+ {props.children}
+
+
+ );
+}
+```
+
+## Step 4: Add Providers to Root Layout
+
+Update your root layout file (`app/layout.tsx`):
+
+```tsx
+import "./globals.css";
+import type { Metadata } from "next";
+import type { ReactNode } from "react";
+
+import { Providers } from "./providers";
+
+export const metadata: Metadata = {
+ title: "Smart Wallet App",
+ description: "Smart Wallet Next.js integration",
+};
+
+export default function RootLayout(props: { children: ReactNode }) {
+ return (
+
+
+ {props.children}
+
+
+ );
+}
+```
+
+## Step 5: Create `ConnectAndSIWE` Component
+
+Create a component for wallet connection and Sign-In With Ethereum (SIWE):
+
+```tsx
+import { useCallback, useEffect, useState } from "react";
+import type { Hex } from "viem";
+import { useAccount, useConnect, usePublicClient, useSignMessage } from "wagmi";
+import { SiweMessage } from "siwe";
+import { cbWalletConnector } from "@/wagmi";
+
+export function ConnectAndSIWE() {
+ const { connect } = useConnect({
+ mutation: {
+ onSuccess: (data) => {
+ const address = data.accounts[0];
+ const chainId = data.chainId;
+ const m = new SiweMessage({
+ domain: document.location.host,
+ address,
+ chainId,
+ uri: document.location.origin,
+ version: "1",
+ statement: "Smart Wallet SIWE Example",
+ nonce: "12345678",
+ });
+ setMessage(m);
+ signMessage({ message: m.prepareMessage() });
+ },
+ },
+ });
+ const account = useAccount();
+ const client = usePublicClient();
+ const [signature, setSignature] = useState(undefined);
+ const { signMessage } = useSignMessage({
+ mutation: { onSuccess: (sig) => setSignature(sig) },
+ });
+ const [message, setMessage] = useState(undefined);
+
+ const [valid, setValid] = useState(undefined);
+
+ const checkValid = useCallback(async () => {
+ if (!signature || !account.address || !client || !message) return;
+
+ client
+ .verifyMessage({
+ address: account.address,
+ message: message.prepareMessage(),
+ signature,
+ })
+ .then((v) => setValid(v));
+ }, [signature, account]);
+
+ useEffect(() => {
+ checkValid();
+ }, [signature, account]);
+
+ useEffect(() => {});
+
+ return (
+
+
connect({ connector: cbWalletConnector })}>
+ Connect + SIWE
+
+
{}
+ {valid != undefined &&
Is valid: {valid.toString()}
}
+
+ );
+}
+```
+
+## Step 6: Use the Component in a Page
+
+Add the component to `app/page.tsx`:
+
+```tsx
+import { ConnectAndSIWE } from '../components/ConnectAndSIWE'
+
+export default function Home() {
+ return (
+
+ Smart Wallet Integration
+
+
+ )
+}
+```
+
+
+Congratulations! You just integrated Smart Wallet in your app.
+You can now [Explore More Features](/identity/smart-wallet/quickstart#explore-more-features).
+
diff --git a/_pages/identity/smart-wallet/quickstart/quick-demo.mdx b/_pages/identity/smart-wallet/quickstart/quick-demo.mdx
new file mode 100644
index 00000000..fd40602b
--- /dev/null
+++ b/_pages/identity/smart-wallet/quickstart/quick-demo.mdx
@@ -0,0 +1,113 @@
+---
+title: "Quick Demo using OnchainKit (5 mins)"
+---
+
+This option uses the OnchainKit [Wallet component](/builderkits/onchainkit/wallet/wallet). It is perfect for quickly experiencing Smart Wallet functionality with minimal setup.
+
+
+**What is OnchainKit?**
+
+[OnchainKit](https://www.base.org/builders/onchainkit)
+is a suite of components allowing you to build
+your onchain app fast and quick. It is a
+very popular solution to build on Base. You can learn more about it [here](/builderkits/onchainkit/getting-started).
+
+
+
+## Step 1: Clone the Template Repository
+
+Let's start by cloning the [repository](https://github.com/coinbase/onchain-app-template). Open a terminal and execute the following:
+
+```bash
+git clone https://github.com/coinbase/onchain-app-template
+cd onchain-app-template
+```
+
+## Step 2: Set Up Environment Variables
+
+Before running your project, you need to set up your environment variables. Follow these steps:
+
+### Get a ReOwn Project ID
+
+1. Sign up to [ReOwn Cloud](https://www.reown.com/cloud)
+2. Click on Create, and go through the steps (screenshot below):
+ - Name your app (eg. OnchainKit-Template)
+ - Select a product - Choose WalletKit
+ - Select a platform - Choose JavaScript
+
+
+
+
Screenshot of the steps to get your ReOwn Project ID
+
+
+### Get a Coinbase Developer Platform (CDP) Client API Key
+
+1. Sign up to [Coinbase Developer Platform (CDP)](https://portal.cdp.coinbase.com/)
+2. Create a new project and copy the [Client API Key](https://portal.cdp.coinbase.com/projects/api-keys/client-key) (screenshot below)
+
+
+
+
Coinbase Developer Platform Client API Key
+
+
+### Create your .env file
+
+Now that you have obtained your ReOwn Project ID and CDP API Key, it is time to create the .env file:
+
+1. Copy `.env.local.default` from to the repository root and name it `.env`
+2. Leave `NEXT_PUBLIC_GOOGLE_ANALYTICS_ID` and `NEXT_PUBLIC_ENVIRONMENT` as they are
+3. Replace `NEXT_PUBLIC_CDP_API_KEY` and `NEXT_PUBLIC_WC_PROJECT_ID` with their respective values obtained from the previous steps
+
+Congratulations! We are ready to run the application.
+
+## Step 3: Install Dependencies and Run
+
+Open your terminal, make sure you are at the directory `/onchain-app-template` and execute the following:
+
+```bash
+# Install packages
+bun i
+
+# Run Next app
+bun run dev
+```
+
+
+**Make sure Bun is installed**
+
+This example uses [Bun](https://bun.sh/) as a preferred package manager. If you run into an error because you don't have Bun installed you can run the following command to install it:
+
+```bash
+curl -fsSL https://bun.sh/install | bash
+```
+
+
+
+Your app will be available at http://localhost:3000
+
+## Step 4: Explore Smart Wallet Features
+
+The template includes:
+
+- Pre-integrated Wallet component
+- OnchainKit components for blockchain interactions
+- Basic authentication flow
+- Transaction handling (NFT Mint)
+
+This setup is ideal for:
+
+- Beginners exploring onchain apps and Smart Wallet functionality
+- Developers evaluating Smart Wallet capabilities
+- Quick prototyping
+
+If you are looking for a more in depth understanding of Smart Wallet, jump ahead to the [Explore More Features](/identity/smart-wallet/quickstart#explore-more-features) section.
diff --git a/_pages/identity/smart-wallet/quickstart/react-native-project.mdx b/_pages/identity/smart-wallet/quickstart/react-native-project.mdx
new file mode 100644
index 00000000..f702b846
--- /dev/null
+++ b/_pages/identity/smart-wallet/quickstart/react-native-project.mdx
@@ -0,0 +1,206 @@
+---
+title: "Add to Existing React Native App"
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+This guide helps you add support for Smart Wallet into a React Native app
+by integrating the
+[Mobile Wallet Protocol Client](https://www.npmjs.com/package/@mobile-wallet-protocol/client).
+
+
+ This doc is updated for Mobile Wallet Protocol Client `v1.0.0`
+
+
+
+ **Deep Link Handling**
+
+ Breaking change in v1.0.0: Universal Links and App Links requirements are
+ removed in favor of custom schemes (e.g. `myapp://`).
+
+
+## Before You Start
+
+This guide walks you through adding support for Smart Wallet into an existing React Native app or starter project.
+
+If you prefer to skip ahead and start with a working example, navigate to the [repository](https://github.com/MobileWalletProtocol/smart-wallet-expo-example.git) or run the following command to clone it locally:
+
+```bash
+git clone https://github.com/MobileWalletProtocol/smart-wallet-expo-example.git
+```
+
+If you are looking to integrate Smart Wallet into an existing React Native app or starter project, follow the instructions below.
+
+## Step 1: Install Mobile Wallet Protocol Client
+
+Add the latest version of [Mobile Wallet Protocol Client](https://mobilewalletprotocol.github.io/wallet-mobile-sdk/) to your project.
+
+
+```zsh [npm]
+npm i @mobile-wallet-protocol/client@latest
+```
+
+```zsh [yarn]
+yarn add @mobile-wallet-protocol/client@latest
+```
+
+
+## Step 2: Add Polyfills
+
+### Install peer dependencies
+
+The Mobile Wallet Protocol Client library requires the [Expo WebBrowser](https://docs.expo.dev/versions/latest/sdk/webbrowser/) and [Async Storage](https://react-native-async-storage.github.io/async-storage/docs/install) packages to be installed.
+Follow the instructions on the respective pages for any additional setup.
+
+
+```zsh [npm]
+npm i expo expo-web-browser @react-native-async-storage/async-storage
+```
+
+```zsh [yarn]
+yarn add expo expo-web-browser @react-native-async-storage/async-storage
+```
+
+
+### Polyfills
+
+Mobile Wallet Protocol Client requires `crypto.randomUUID`, `crypto.getRandomValues`, and `URL` to be polyfilled globally since they are not available in the React Native environment.
+
+Below is an example of how to polyfill these functions in your app using the [expo-crypto](https://docs.expo.dev/versions/latest/sdk/crypto/) and [expo-standard-web-crypto](https://github.com/expo/expo/tree/master/packages/expo-standard-web-crypto/) packages.
+
+
+```zsh [npm]
+npm i expo-crypto expo-standard-web-crypto react-native-url-polyfill
+```
+
+```zsh [yarn]
+yarn add expo-crypto expo-standard-web-crypto react-native-url-polyfill
+```
+
+
+
+```js [polyfills.js]
+import "react-native-url-polyfill/auto";
+import { polyfillWebCrypto } from "expo-standard-web-crypto";
+import { randomUUID } from "expo-crypto";
+
+polyfillWebCrypto();
+crypto.randomUUID = randomUUID;
+```
+
+```tsx [App.tsx]
+import "./polyfills"; // import before @mobile-wallet-protocol/client
+
+import { CoinbaseWalletSDK } from "@mobile-wallet-protocol/client";
+
+/// ...
+```
+
+
+## Step 3: Usage
+
+Mobile Wallet Protocol Client provides 2 interfaces for mobile app to interact with the Smart Wallet, an EIP-1193 compliant provider interface and a wagmi connector.
+
+
+ If your app is using wallet aggregator, go straight to [**Option 2: Wagmi
+ Connector**](#option-2-wagmi-connector) for 1-line integration.
+
+
+### Option 1: EIP-1193 Provider
+
+
+ The `app` prefix in SDK config params is removed in v1.0.0.
+
+
+Create a new `EIP1193Provider` instance, which is EIP-1193 compliant.
+
+```tsx [App.tsx]
+import { EIP1193Provider } from "@mobile-wallet-protocol/client";
+
+// Step 1. Initialize provider with your dapp's metadata and target wallet
+const metadata = {
+ name: "My App Name",
+ customScheme: "myapp://", // only custom scheme (e.g. `myapp://`) is supported in v1.0.0
+ chainIds: [8453],
+ logoUrl: "https://example.com/logo.png",
+};
+const provider = new EIP1193Provider({
+ metadata,
+ wallet: Wallets.CoinbaseSmartWallet,
+});
+
+// ...
+
+// 2. Use the provider
+const addresses = await provider.request({ method: "eth_requestAccounts" });
+const signedData = await provider.request({
+ method: "personal_sign",
+ params: ["0x48656c6c6f20776f726c6421", addresses[0]],
+});
+```
+
+### Option 2: Wagmi Connector
+
+Add the latest version of Mobile Wallet Protocol wagmi-connectors to your project.
+
+
+```zsh [npm]
+npm i @mobile-wallet-protocol/wagmi-connectors@latest
+```
+
+```zsh [yarn]
+yarn add @mobile-wallet-protocol/wagmi-connectors@latest
+```
+
+
+Simply import the `createConnectorFromWallet` function and pass in the wallet you want to use to wagmi config.
+
+```ts [config.ts]
+import {
+ createConnectorFromWallet,
+ Wallets,
+} from "@mobile-wallet-protocol/wagmi-connectors";
+
+const metadata = {
+ name: "My App Name",
+ customScheme: "myapp://", // only custom scheme (e.g. `myapp://`) is supported in v1.0.0
+ chainIds: [8453],
+ logoUrl: "https://example.com/logo.png",
+};
+
+export const config = createConfig({
+ chains: [base],
+ connectors: [
+ createConnectorFromWallet({
+ metadata,
+ wallet: Wallets.CoinbaseSmartWallet,
+ }),
+ ],
+ transports: {
+ [base.id]: http(),
+ },
+});
+```
+
+Then you can use wagmi's react interface to interact with the Smart Wallet.
+
+```tsx [App.tsx]
+import { useConnect } from "wagmi";
+
+// ...
+
+const { connect, connectors } = useConnect();
+
+return (
+ {
+ connect({ connector: connectors[0] });
+ }}
+ />
+);
+```
+
+## Give feedback!
+
+Send us feedback on the [Coinbase Developer Platform](https://discord.com/invite/cdp/) Discord or create a new issue on the [MobileWalletProtocol/react-native-client](https://github.com/MobileWalletProtocol/react-native-client/issues) repository.
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/Overview.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/Overview.mdx
new file mode 100644
index 00000000..843763c5
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/Overview.mdx
@@ -0,0 +1,103 @@
+---
+title: "Overview"
+---
+
+
+The request method allows apps to make Ethereum RPC requests to the wallet.
+
+## Specification
+
+```typescript
+interface RequestArguments {
+readonly method: string;
+readonly params?: readonly unknown[] | object;
+}
+
+interface ProviderRpcError extends Error {
+code: number;
+data?: unknown;
+}
+
+interface CoinbaseWalletProvider {
+/\*\*
+
+- @param {RequestArguments} args request arguments.
+- @returns A promise that resolves with the result.
+- @throws {ProviderRpcError} incase of error.
+- @fires CoinbaseWalletProvider#connect When the provider successfully connects.
+ \*/
+ request: (args: RequestArguments) => Promise
+ }
+```
+
+## Example
+
+
+```typescript [example.ts]
+
+import {provider} from "./setup";
+
+const addresses = await provider.request({method: 'eth_requestAccounts'});
+const txHash = await provider.request({
+method: 'eth_sendTransaction',
+params: [{from: addresses[0], to: addresses[0], value: 1}]
+}
+);
+```
+
+```typescript [setup.ts]
+
+import { CoinbaseWalletSDK } from '@coinbase/wallet-sdk'
+
+const baseSepoliaChainId = 84532;
+
+export const sdk = new CoinbaseWalletSDK({
+ appName: 'My App Name',
+ appChainIds: [baseSepoliaChainId]
+});
+
+const provider = sdk.makeWeb3Provider();
+```
+
+
+## Request Handling
+
+Requests are handled in one of three ways
+
+Sent to the Wallet application (Wallet mobile app, extension, or popup window).
+Handled locally by the SDK.
+Passed onto default RPC provider for the given chain, if it exists.
+
+### 1. Sent to the Wallet application
+
+The following RPC requests are sent to the Wallet application:
+
+- eth_ecRecover
+- personal_sign
+- personal_ecRecover
+- eth_signTransaction
+- eth_sendTransaction
+- eth_signTypedData_v1
+- eth_signTypedData_v3
+- eth_signTypedData_v4
+- eth_signTypedData
+- wallet_addEthereumChain
+- wallet_watchAsset
+- wallet_sendCalls
+- wallet_showCallsStatus
+
+### 2. Handled Locally by the SDK
+
+The following requests are handled locally by the SDK, with no external calls.
+
+- eth_requestAccounts
+- eth_accounts
+- eth_coinbase
+- net_version
+- eth_chainId
+- wallet_getCapabilities
+- wallet_switchEthereumChain
+
+```
+
+```
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_accounts.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_accounts.mdx
new file mode 100644
index 00000000..e09fd4e9
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_accounts.mdx
@@ -0,0 +1,58 @@
+---
+title: "eth_accounts"
+---
+
+
+Defined in [EIP-1474](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1474.md)
+
+> Returns a list of addresses owned by the connected wallet. Unlike `eth_requestAccounts`, this method returns an empty array if no accounts are available or if the user has not authorized any accounts to the caller. This method does not prompt the user to approve connection.
+
+## Parameters
+
+None. This method does not accept any parameters.
+
+## Returns
+
+`Array`
+
+An array of Ethereum addresses (hexadecimal strings), which the connected user controls. The array will typically contain a single address, which is the currently selected account in the wallet. If the wallet is not connected or no accounts are authorized, this method returns an empty array.
+
+## Example
+
+Request:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "method": "eth_accounts",
+ "params": []
+}
+```
+
+Response:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "result": ["0xabc123..."]
+}
+```
+
+If no accounts are connected:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "result": []
+}
+```
+
+## Errors
+
+| Code | Message | Description |
+| ---- | ------------------------------ | ------------------------------------------------------- |
+| 4100 | Requested method not supported | The provider does not support the `eth_accounts` method |
+| 4900 | Disconnected | The provider is disconnected from the wallet |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_blockNumber.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_blockNumber.mdx
new file mode 100644
index 00000000..02a3f9ed
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_blockNumber.mdx
@@ -0,0 +1,22 @@
+---
+title: "eth_blockNumber"
+---
+
+
+> Returns the number of the most recent block.
+
+## Returns
+
+`string`
+
+A hexadecimal string representing the integer of the current block number the client is on.
+
+### Example
+
+```
+"0x4b7" // 1207
+```
+
+## Errors
+
+This method does not typically return errors.
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_chainId.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_chainId.mdx
new file mode 100644
index 00000000..44855f0a
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_chainId.mdx
@@ -0,0 +1,26 @@
+---
+title: "eth_chainId"
+---
+
+
+Defined in [EIP-695](https://eips.ethereum.org/EIPS/eip-695)
+
+> Returns the currently configured chain ID, a value used in replay-protected transaction signing as introduced by [EIP-155](https://eips.ethereum.org/EIPS/eip-155).
+
+## Returns
+
+`string`
+
+A hexadecimal string representation of the integer chain ID.
+
+### Example
+
+```
+"0x1" // Ethereum Mainnet
+"0x14a34" // Base Sepolia (84532)
+"0x14a33" // Base Mainnet (84531)
+```
+
+## Errors
+
+This method does not typically return errors.
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_coinbase.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_coinbase.mdx
new file mode 100644
index 00000000..dc2dde6f
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_coinbase.mdx
@@ -0,0 +1,16 @@
+---
+title: "eth_coinbase"
+---
+
+
+Returns the current coinbase address, which is the account the wallet will use as the default sender.
+
+## Returns
+
+`string`
+
+The current Ethereum address that is set as the coinbase (default sender).
+
+## Errors
+
+This method does not typically return errors.
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_estimateGas.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_estimateGas.mdx
new file mode 100644
index 00000000..f3ace6a0
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_estimateGas.mdx
@@ -0,0 +1,33 @@
+---
+title: "eth_estimateGas"
+---
+
+
+> Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.
+
+## Parameters
+
+```typescript
+interface TransactionParams {
+ from?: string; // The address the transaction is sent from
+ to?: string; // The address the transaction is directed to
+ gas?: string; // Hexadecimal string of the gas provided for the transaction execution
+ gasPrice?: string; // Hexadecimal string of the gasPrice used for each paid gas
+ value?: string; // Hexadecimal string of the value sent with this transaction
+ data?: string; // The compiled code of a contract or the hash of the invoked method signature and encoded parameters
+}
+```
+
+## Returns
+
+`string`
+
+A hexadecimal string indicating the estimated gas required for the transaction to complete. This is not the exact gas amount that will be used by the transaction when it executes, but should be used as a gas limit when sending the transaction.
+
+## Errors
+
+| Code | Message |
+| ------ | ------------------------------ |
+| -32000 | Transaction execution error |
+| -32602 | Invalid transaction parameters |
+| -32603 | Internal error |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_feeHistory.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_feeHistory.mdx
new file mode 100644
index 00000000..e8765b56
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_feeHistory.mdx
@@ -0,0 +1,71 @@
+---
+title: "eth_feeHistory"
+---
+
+
+> Returns a collection of historical gas information from which you can recompute gas costs.
+
+## Parameters
+
+1. `string` - Number of blocks in the requested range (in hexadecimal)
+2. `string` - Highest block number in the requested range (in hexadecimal) or "latest"
+3. `array` - Percentile values to sample from each block's effective priority fees
+
+### Example
+
+```json
+["0x5", "latest", [20, 70]]
+```
+
+## Returns
+
+`object`
+
+Returns an object with fee history data:
+
+```typescript
+interface FeeHistory {
+ oldestBlock: string; // Lowest block number in the range
+ baseFeePerGas: Array; // Array of block base fees per gas
+ gasUsedRatio: Array; // Array of block gas utilization ratios
+ reward?: Array>; // Array of effective priority fee per gas data points for each block
+}
+```
+
+### Example
+
+```json
+{
+ "oldestBlock": "0x1",
+ "baseFeePerGas": [
+ "0x3b9aca00",
+ "0x3ba1f3e2",
+ "0x3a6db1e6"
+ ],
+ "gasUsedRatio": [
+ 0.5265,
+ 0.4858,
+ 0.6124
+ ],
+ "reward": [
+ [
+ "0x3b9aca00",
+ "0x3b9aca00"
+ ],
+ [
+ "0x3ba1f3e2",
+ "0x3b9aca00"
+ ],
+ [
+ "0x3a6db1e6",
+ "0x3b9aca00"
+ ]
+ ]
+}
+```
+
+## Errors
+
+| Code | Message |
+| ------ | ------------------ |
+| -32602 | Invalid parameters |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_gasPrice.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_gasPrice.mdx
new file mode 100644
index 00000000..b40fcaa8
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_gasPrice.mdx
@@ -0,0 +1,22 @@
+---
+title: "eth_gasPrice"
+---
+
+
+> Returns the current price per gas in wei.
+
+## Returns
+
+`string`
+
+A hexadecimal string representing the integer value of the current gas price in wei.
+
+### Example
+
+```
+"0x09184e72a000" // 10000000000000
+```
+
+## Errors
+
+This method does not typically return errors.
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBalance.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBalance.mdx
new file mode 100644
index 00000000..8249c3b3
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBalance.mdx
@@ -0,0 +1,35 @@
+---
+title: "eth_getBalance"
+---
+
+
+> Returns the balance of the account of given address.
+
+## Parameters
+
+1. `string` - The address to check for balance.
+2. `string` - Integer block number, or the string "latest", "earliest" or "pending".
+
+### Example
+
+```json
+["0x407d73d8a49eeb85d32cf465507dd71d507100c1", "latest"]
+```
+
+## Returns
+
+`string`
+
+A hexadecimal string representing the current balance in wei.
+
+### Example
+
+```
+"0x0234c8a3397aab58" // 158972490234375000
+```
+
+## Errors
+
+| Code | Message |
+| ------ | ---------------------------------- |
+| -32602 | Invalid address or block parameter |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockByHash.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockByHash.mdx
new file mode 100644
index 00000000..8a0ea2e1
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockByHash.mdx
@@ -0,0 +1,59 @@
+---
+title: "eth_getBlockByHash"
+---
+
+
+Defined in the [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/)
+
+> Returns information about a block by block hash.
+
+## Parameters
+
+```ts
+type GetBlockByHashParams = [string, boolean]
+```
+
+1. `string` - Hash of a block
+2. `boolean` - If true, returns the full transaction objects; if false, only the hashes of the transactions
+
+### Example
+
+```json
+["0x1b4", true]
+```
+
+## Returns
+
+`object` or `null`
+
+Returns a block object, or `null` if no block was found:
+
+```typescript
+interface Block {
+ number: string; // The block number
+ hash: string; // Hash of the block
+ parentHash: string; // Hash of the parent block
+ nonce: string; // Hash of the generated proof-of-work
+ sha3Uncles: string; // SHA3 of the uncles data in the block
+ logsBloom: string; // The bloom filter for the logs of the block
+ transactionsRoot: string; // The root of the transaction trie of the block
+ stateRoot: string; // The root of the final state trie of the block
+ receiptsRoot: string; // The root of the receipts trie of the block
+ miner: string; // The address of the beneficiary to whom the mining rewards were given
+ difficulty: string; // Integer of the difficulty for this block
+ totalDifficulty: string; // Integer of the total difficulty of the chain until this block
+ extraData: string; // The "extra data" field of this block
+ size: string; // Integer the size of this block in bytes
+ gasLimit: string; // The maximum gas allowed in this block
+ gasUsed: string; // The total used gas by all transactions in this block
+ timestamp: string; // The unix timestamp for when the block was collated
+ transactions: Array; // Array of transaction objects, or 32-byte transaction hashes depending on the second parameter
+ uncles: Array; // Array of uncle hashes
+}
+```
+
+## Errors
+
+| Code | Message |
+| ------ | ------------------ |
+| -32602 | Invalid parameters |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockByNumber.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockByNumber.mdx
new file mode 100644
index 00000000..261b21e9
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockByNumber.mdx
@@ -0,0 +1,57 @@
+---
+title: "eth_getBlockByNumber"
+---
+
+
+> Returns information about a block by block number.
+
+## Parameters
+
+1. `string` - Integer block number, or the string "latest", "earliest" or "pending"
+2. `boolean` - If true, returns the full transaction objects; if false, only the hashes of the transactions
+
+### Example
+
+```json
+["0x1b4", true]
+```
+
+## Returns
+
+`object` or `null`
+
+Returns a block object, or `null` if no block was found:
+
+```typescript
+interface Block {
+ number: string; // The block number
+ hash: string; // Hash of the block
+ parentHash: string; // Hash of the parent block
+ nonce: string; // Hash of the generated proof-of-work
+ sha3Uncles: string; // SHA3 of the uncles data in the block
+ logsBloom: string; // The bloom filter for the logs of the block
+ transactionsRoot: string; // The root of the transaction trie of the block
+ stateRoot: string; // The root of the final state trie of the block
+ receiptsRoot: string; // The root of the receipts trie of the block
+ miner: string; // The address of the beneficiary to whom the mining rewards were given
+ difficulty: string; // Integer of the difficulty for this block
+ totalDifficulty: string; // Integer of the total difficulty of the chain until this block
+ extraData: string; // The "extra data" field of this block
+ size: string; // Integer the size of this block in bytes
+ gasLimit: string; // The maximum gas allowed in this block
+ gasUsed: string; // The total used gas by all transactions in this block
+ timestamp: string; // The unix timestamp for when the block was collated
+ transactions: Array; // Array of transaction objects, or 32-byte transaction hashes depending on the second parameter
+ uncles: Array; // Array of uncle hashes
+}
+```
+
+## Errors
+
+| Code | Message |
+| ------ | ------------------ |
+| -32602 | Invalid parameters |
+
+```
+
+```
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockTransactionCountByHash.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockTransactionCountByHash.mdx
new file mode 100644
index 00000000..b0269462
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockTransactionCountByHash.mdx
@@ -0,0 +1,38 @@
+---
+title: "eth_getBlockTransactionCountByHash"
+---
+
+
+Defined in the [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/)
+
+> Returns the number of transactions in a block from a block matching the given block hash.
+
+## Parameters
+
+```ts
+type GetBlockTransactionCountByHashParams = [string]
+```
+
+An array containing a single string: the hash of the block.
+
+## Returns
+
+`string`
+
+The number of transactions in the specified block, encoded as a hexadecimal.
+
+## Example
+
+```
+"0x10" // 16 transactions in the block
+```
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------ |
+| 4200 | Unsupported method |
+
+```
+
+```
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockTransactionCountByNumber.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockTransactionCountByNumber.mdx
new file mode 100644
index 00000000..6b53bf78
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockTransactionCountByNumber.mdx
@@ -0,0 +1,36 @@
+---
+title: "eth_getBlockTransactionCountByNumber"
+---
+
+
+Defined in the [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/)
+
+> Returns the number of transactions in a block matching the given block number.
+
+## Parameters
+
+```ts
+type GetBlockTransactionCountByNumberParams = [string]
+```
+
+An array containing a single string: the block number (as a hexadecimal) or tag.
+
+Valid tags: `"earliest"`, `"latest"`, `"pending"`, `"safe"`, `"finalized"`
+
+## Returns
+
+`string`
+
+The number of transactions in the specified block, encoded as a hexadecimal.
+
+## Example
+
+```
+"0x10" // 16 transactions in the block
+```
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------ |
+| 4200 | Unsupported method |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getCode.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getCode.mdx
new file mode 100644
index 00000000..d74f413a
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getCode.mdx
@@ -0,0 +1,35 @@
+---
+title: "eth_getCode"
+---
+
+
+> Returns the bytecode at a given address.
+
+## Parameters
+
+1. `string` - The address to get the code from
+2. `string` - Integer block number, or the string "latest", "earliest" or "pending"
+
+### Example
+
+```json
+["0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b", "0x2"]
+```
+
+## Returns
+
+`string`
+
+The bytecode at the given address. Returns "0x" if the account has no bytecode.
+
+### Example
+
+```
+"0x606060405260043610610062576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680635fd8c710146100675780637c68f6ab146100905780638da5cb5b146100c9578063dc10753b1461011e575b600080fd5b341561007257600080fd5b61007a610145565b6040518082815260200191505060405180910390f35b341561009b57600080fd5b6100b3600480803560ff169060200190919050506101e9565b6040518082815260200191505060405180910390f35b34156100d457600080fd5b6100dc610290565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b341561012957600080fd5b610143600480803560001916906020019091905050610297565b005b6000600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561065d57600080fd5b600060149054906101000a900460ff16156101ae57600080fd5b60011515600060159054906101000a900460ff1615151415156101d057600080fd5b680c7d713b49da0000009050600060149054906101000a900460ff16156101e657806000819055505b80905090565b60006101f36102b8565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561024f57600080fd5b600060149054906101000a900460ff161561026957600080fd5b60011515600060159054906101000a900460ff16151514151561028b57600080fd5b819050919050565b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b80600019166001816000191690919091019190915550565b6000806000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561031757600080fd5b600060149054906101000a900460ff161561033157600080fd5b60011515600060159054906101000a900460ff16151514151561035357600080fd5b6000600554111561070657600554600454111561071857600454600554111561072a576000600554111561073c5760006004541115610751576000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905080915050905600a165627a"
+```
+
+## Errors
+
+| Code | Message |
+| ------ | ------------------ |
+| -32602 | Invalid parameters |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getLogs.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getLogs.mdx
new file mode 100644
index 00000000..7e4588d8
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getLogs.mdx
@@ -0,0 +1,61 @@
+---
+title: "eth_getLogs"
+---
+
+
+> Returns an array of all logs matching the given filter object.
+
+## Parameters
+
+1. `object` - The filter object:
+ - `fromBlock` (optional) - `string`: Integer block number, or "latest", "earliest" or "pending"
+ - `toBlock` (optional) - `string`: Integer block number, or "latest", "earliest" or "pending"
+ - `address` (optional) - `string` or `array`: Contract address or array of addresses from which logs should originate
+ - `topics` (optional) - `array`: Array of 32-byte DATA topics. Topics are order-dependent
+ - `blockHash` (optional) - `string`: Hash of the block to get logs from (overrides fromBlock/toBlock)
+
+### Example
+
+```json
+[{
+ "fromBlock": "0x1",
+ "toBlock": "0x2",
+ "address": "0x8888f1f195afa192cfee860698584c030f4c9db1",
+ "topics": [
+ "0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
+ null,
+ [
+ "0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
+ "0x0000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebccc"
+ ]
+ ]
+}]
+```
+
+## Returns
+
+`array`
+
+An array of log objects, each containing:
+
+```typescript
+interface Log {
+ removed: boolean; // Whether the log was removed due to a chain reorganization
+ logIndex: string; // Integer of the log index position in the block
+ transactionIndex: string; // Integer of the transaction's index position the log was created from
+ transactionHash: string; // Hash of the transaction this log was created from
+ blockHash: string; // Hash of the block where this log was in
+ blockNumber: string; // The block number where this log was in
+ address: string; // Address from which this log originated
+ data: string; // Contains non-indexed parameters of the log
+ topics: Array; // Array of up to 4 32-byte topics, topic[0] is the event signature
+}
+```
+
+## Errors
+
+| Code | Message |
+| ------ | ---------------------- |
+| -32602 | Invalid parameters |
+| -32005 | Filter not found |
+| -32000 | Log response too large |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getProof.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getProof.mdx
new file mode 100644
index 00000000..be5550d9
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getProof.mdx
@@ -0,0 +1,70 @@
+---
+title: "eth_getProof"
+---
+
+
+Defined in [EIP-1186](https://eips.ethereum.org/EIPS/eip-1186)
+
+> Returns the account and storage values of the specified account including the Merkle-proof.
+
+## Parameters
+
+```ts
+type GetProofParams = [string, string[], string]
+```
+
+1. The address of the account
+2. Array of storage-keys which should be proofed and included
+3. Block number or tag (as hexadecimal string)
+
+## Returns
+
+```ts
+interface ProofResult {
+ /**
+ * The address of the account being proved.
+ */
+ address: string
+ /**
+ * The balance of the account.
+ */
+ balance: string
+ /**
+ * The hash of the code of the account.
+ */
+ codeHash: string
+ /**
+ * The nonce of the account.
+ */
+ nonce: string
+ /**
+ * The storage hash of the account.
+ */
+ storageHash: string
+ /**
+ * Array of storage entries as requested.
+ */
+ storageProof: StorageProofEntry[]
+}
+
+interface StorageProofEntry {
+ /**
+ * The storage key.
+ */
+ key: string
+ /**
+ * The storage value.
+ */
+ value: string
+ /**
+ * Array of rlp-serialized MerkleTree-Nodes, starting with the stateRoot-Node.
+ */
+ proof: string[]
+}
+```
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------ |
+| 4200 | Unsupported method |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getStorageAt.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getStorageAt.mdx
new file mode 100644
index 00000000..e9d3c694
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getStorageAt.mdx
@@ -0,0 +1,42 @@
+---
+title: "eth_getStorageAt"
+---
+
+
+Defined in the [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/)
+
+> Returns the value from a storage position at a given address.
+
+## Parameters
+
+```ts
+type GetStorageAtParams = [string, string, string]
+```
+
+1. The address of the storage
+2. Integer of the position in the storage (hexadecimal)
+3. Block number or tag (as hexadecimal string)
+
+Valid tags: `"earliest"`, `"latest"`, `"pending"`, `"safe"`, `"finalized"`
+
+## Returns
+
+`string`
+
+The value at this storage position.
+
+## Example
+
+```
+"0x0000000000000000000000000000000000000000000000000000000000000004"
+```
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------ |
+| 4200 | Unsupported method |
+
+```
+
+```
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionByBlockHashAndIndex.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionByBlockHashAndIndex.mdx
new file mode 100644
index 00000000..5c9a94bf
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionByBlockHashAndIndex.mdx
@@ -0,0 +1,50 @@
+---
+title: "eth_getTransactionByBlockHashAndIndex"
+---
+
+
+Defined in the [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/)
+
+> Returns information about a transaction by block hash and transaction index position.
+
+## Parameters
+
+```ts
+type GetTransactionByBlockHashAndIndexParams = [string, string]
+```
+
+1. Hash of a block
+2. Integer of the transaction index position (hexadecimal)
+
+## Returns
+
+```ts
+interface Transaction {
+ hash: string // Hash of the transaction
+ nonce: string // The number of transactions made by the sender prior to this one
+ blockHash: string | null // Hash of the block where this transaction was in. null if pending
+ blockNumber: string | null // Block number where this transaction was in. null if pending
+ transactionIndex: string | null // Integer of the transaction's index position in the block. null if pending
+ from: string // Address of the sender
+ to: string | null // Address of the receiver. null when it's a contract creation transaction
+ value: string // Value transferred in wei
+ gasPrice: string // Gas price provided by the sender in wei
+ gas: string // Gas provided by the sender
+ input: string // The data sent along with the transaction
+ v: string // ECDSA recovery id
+ r: string // ECDSA signature r
+ s: string // ECDSA signature s
+}
+```
+
+Returns `null` when no transaction was found.
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------ |
+| 4200 | Unsupported method |
+
+```
+
+```
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionByBlockNumberAndIndex.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionByBlockNumberAndIndex.mdx
new file mode 100644
index 00000000..f5dc6e24
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionByBlockNumberAndIndex.mdx
@@ -0,0 +1,52 @@
+---
+title: "eth_getTransactionByBlockNumberAndIndex"
+---
+
+
+Defined in the [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/)
+
+> Returns information about a transaction by block number and transaction index position.
+
+## Parameters
+
+```ts
+type GetTransactionByBlockNumberAndIndexParams = [string, string]
+```
+
+1. Block number or tag (as hexadecimal string)
+2. Integer of the transaction index position (hexadecimal)
+
+Valid tags: `"earliest"`, `"latest"`, `"pending"`, `"safe"`, `"finalized"`
+
+## Returns
+
+```ts
+interface Transaction {
+ hash: string // Hash of the transaction
+ nonce: string // The number of transactions made by the sender prior to this one
+ blockHash: string | null // Hash of the block where this transaction was in. null if pending
+ blockNumber: string | null // Block number where this transaction was in. null if pending
+ transactionIndex: string | null // Integer of the transaction's index position in the block. null if pending
+ from: string // Address of the sender
+ to: string | null // Address of the receiver. null when it's a contract creation transaction
+ value: string // Value transferred in wei
+ gasPrice: string // Gas price provided by the sender in wei
+ gas: string // Gas provided by the sender
+ input: string // The data sent along with the transaction
+ v: string // ECDSA recovery id
+ r: string // ECDSA signature r
+ s: string // ECDSA signature s
+}
+```
+
+Returns `null` when no transaction was found.
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------ |
+| 4200 | Unsupported method |
+
+```
+
+```
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionByHash.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionByHash.mdx
new file mode 100644
index 00000000..101f5015
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionByHash.mdx
@@ -0,0 +1,47 @@
+---
+title: "eth_getTransactionByHash"
+---
+
+
+> Returns information about a transaction by transaction hash.
+
+## Parameters
+
+1. `string` - The transaction hash
+
+### Example
+
+```json
+["0x88df016429689c079f3b2f6ad39fa052532c56795b733da78a91ebe6a713944b"]
+```
+
+## Returns
+
+`object` or `null`
+
+Returns a transaction object, or `null` if no transaction was found:
+
+```typescript
+interface Transaction {
+ blockHash: string | null; // Hash of the block. null when pending
+ blockNumber: string | null; // Number of the block. null when pending
+ from: string; // Address of the sender
+ gas: string; // Gas provided by the sender
+ gasPrice: string; // Gas price provided by the sender
+ hash: string; // Hash of the transaction
+ input: string; // The data sent along with the transaction
+ nonce: string; // Number of transactions from the sender prior to this one
+ to: string | null; // Address of the receiver. null when creating a contract
+ transactionIndex: string | null; // Integer of the transaction's index position in the block. null when pending
+ value: string; // Value transferred in wei
+ v: string; // ECDSA recovery ID
+ r: string; // ECDSA signature r
+ s: string; // ECDSA signature s
+}
+```
+
+## Errors
+
+| Code | Message |
+| ------ | ----------------- |
+| -32602 | Invalid parameter |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionCount.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionCount.mdx
new file mode 100644
index 00000000..7315d247
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionCount.mdx
@@ -0,0 +1,35 @@
+---
+title: "eth_getTransactionCount"
+---
+
+
+> Returns the number of transactions sent from an address.
+
+## Parameters
+
+1. `string` - The address to get the transaction count for
+2. `string` - Integer block number, or the string "latest", "earliest" or "pending"
+
+### Example
+
+```json
+["0xc94770007dda54cF92009BFF0dE90c06F603a09f", "latest"]
+```
+
+## Returns
+
+`string`
+
+A hexadecimal string representing the number of transactions sent from this address.
+
+### Example
+
+```
+"0x1" // 1
+```
+
+## Errors
+
+| Code | Message |
+| ------ | ---------------------------------- |
+| -32602 | Invalid address or block parameter |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionReceipt.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionReceipt.mdx
new file mode 100644
index 00000000..82de800f
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionReceipt.mdx
@@ -0,0 +1,57 @@
+---
+title: "eth_getTransactionReceipt"
+---
+
+
+> Returns the receipt of a transaction by transaction hash.
+
+## Parameters
+
+1. `string` - The transaction hash
+
+### Example
+
+```json
+["0xb903239f8543d04b5dc1ba6579132b143087c68db1b2168786408fcbce568238"]
+```
+
+## Returns
+
+`object` or `null`
+
+Returns a transaction receipt object, or `null` if no receipt was found:
+
+```typescript
+interface TransactionReceipt {
+ transactionHash: string; // Hash of the transaction
+ transactionIndex: string; // Integer of the transaction's index position in the block
+ blockHash: string; // Hash of the block where this transaction was in
+ blockNumber: string; // Block number where this transaction was in
+ from: string; // Address of the sender
+ to: string | null; // Address of the receiver. null when it's a contract creation transaction
+ cumulativeGasUsed: string; // The total amount of gas used in the block up to and including this transaction
+ gasUsed: string; // The amount of gas used by this specific transaction
+ contractAddress: string | null; // The contract address created, if the transaction was a contract creation, otherwise null
+ logs: Array; // Array of log objects, which this transaction generated
+ logsBloom: string; // Bloom filter for light clients to quickly retrieve related logs
+ status: string; // Either '0x1' (success) or '0x0' (failure)
+}
+
+interface Log {
+ removed: boolean; // Whether the log was removed due to a chain reorganization
+ logIndex: string; // Integer of the log index position in the block
+ transactionIndex: string; // Integer of the transaction's index position the log was created from
+ transactionHash: string; // Hash of the transaction this log was created from
+ blockHash: string; // Hash of the block where this log was in
+ blockNumber: string; // The block number where this log was in
+ address: string; // Address from which this log originated
+ data: string; // Contains non-indexed parameters of the log
+ topics: Array; // Array of up to 4 32-byte topics, topic[0] is the event signature
+}
+```
+
+## Errors
+
+| Code | Message |
+| ------ | ----------------- |
+| -32602 | Invalid parameter |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getUncleCountByBlockHash.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getUncleCountByBlockHash.mdx
new file mode 100644
index 00000000..a66f9707
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getUncleCountByBlockHash.mdx
@@ -0,0 +1,38 @@
+---
+title: "eth_getUncleCountByBlockHash"
+---
+
+
+Defined in the [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/)
+
+> Returns the number of uncles in a block from a block matching the given block hash.
+
+## Parameters
+
+```ts
+type GetUncleCountByBlockHashParams = [string]
+```
+
+An array containing a single string: the hash of the block.
+
+## Returns
+
+`string`
+
+The number of uncles in the specified block, encoded as a hexadecimal.
+
+## Example
+
+```
+"0x1" // 1 uncle
+```
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------ |
+| 4200 | Unsupported method |
+
+```
+
+```
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getUncleCountByBlockNumber.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getUncleCountByBlockNumber.mdx
new file mode 100644
index 00000000..40d4b9ba
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getUncleCountByBlockNumber.mdx
@@ -0,0 +1,40 @@
+---
+title: "eth_getUncleCountByBlockNumber"
+---
+
+
+Defined in the [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/)
+
+> Returns the number of uncles in a block from a block matching the given block number.
+
+## Parameters
+
+```ts
+type GetUncleCountByBlockNumberParams = [string]
+```
+
+An array containing a single string: the block number (as a hexadecimal) or tag.
+
+Valid tags: `"earliest"`, `"latest"`, `"pending"`, `"safe"`, `"finalized"`
+
+## Returns
+
+`string`
+
+The number of uncles in the specified block, encoded as a hexadecimal.
+
+## Example
+
+```
+"0x1" // 1 uncle
+```
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------ |
+| 4200 | Unsupported method |
+
+```
+
+```
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_requestAccounts.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_requestAccounts.mdx
new file mode 100644
index 00000000..c0c14601
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_requestAccounts.mdx
@@ -0,0 +1,49 @@
+---
+title: "eth_requestAccounts"
+---
+
+
+Defined in [EIP-1102](https://eips.ethereum.org/EIPS/eip-1102)
+
+> Requests that the user provides an Ethereum address to be identified by. This method is used to request the user's accounts and trigger wallet connection. Calling this method may trigger a user interface that allows the user to approve or reject account access for the dApp.
+
+## Parameters
+
+None. This method does not accept any parameters.
+
+## Returns
+
+`Array`
+
+An array of Ethereum addresses (hexadecimal strings), which the connected user controls. The array will typically contain a single address, which is the currently selected account in the wallet.
+
+## Example
+
+Request:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "method": "eth_requestAccounts",
+ "params": []
+}
+```
+
+Response:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "result": ["0xabc123..."]
+}
+```
+
+## Errors
+
+| Code | Message | Description |
+| ---- | ------------------------------ | ----------------------------------------------------------- |
+| 4001 | User denied connection request | The user rejected the request to connect with your dApp |
+| 4100 | Unauthorized | The requested method and/or account has not been authorized |
+| 4900 | Disconnected | The provider is disconnected from the wallet |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_sendRawTransaction.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_sendRawTransaction.mdx
new file mode 100644
index 00000000..aa5ae378
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_sendRawTransaction.mdx
@@ -0,0 +1,37 @@
+---
+title: "eth_sendRawTransaction"
+---
+
+
+> Submits a pre-signed transaction for broadcast to the Ethereum network.
+
+## Parameters
+
+1. `string` - The signed transaction data
+
+### Example
+
+```json
+["0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"]
+```
+
+## Returns
+
+`string`
+
+A 32-byte hex string containing the transaction hash.
+
+### Example
+
+```
+"0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331"
+```
+
+## Errors
+
+| Code | Message |
+| ------ | -------------------- |
+| -32000 | Invalid transaction |
+| -32003 | Transaction rejected |
+| -32010 | Gas price too low |
+| -32603 | Internal error |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_sendTransaction.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_sendTransaction.mdx
new file mode 100644
index 00000000..3223bb81
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_sendTransaction.mdx
@@ -0,0 +1,40 @@
+---
+title: "eth_sendTransaction"
+---
+
+
+Defined in [EIP-1474](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1474.md)
+
+> Creates, signs, and sends a new transaction to the network.
+
+## Parameters
+
+`Array`
+
+An array containing a single transaction object with the following fields:
+
+| Field | Type | Description |
+| -------------------- | ------ | --------------------------------------------------------------------- |
+| from | string | The address the transaction is sent from |
+| to | string | (Optional) The address the transaction is directed to |
+| gas | string | (Optional) Integer of the gas provided for the transaction |
+| gasPrice | string | (Optional) Integer of the gas price in wei |
+| value | string | (Optional) Integer of the value sent with this transaction |
+| data | string | (Optional) Hash of the method signature and encoded parameters |
+| nonce | string | (Optional) Integer of a nonce used to prevent transaction replay |
+| maxFeePerGas | string | (Optional) The maximum fee per gas for EIP-1559 transactions |
+| maxPriorityFeePerGas | string | (Optional) The maximum priority fee per gas for EIP-1559 transactions |
+
+## Returns
+
+`string`
+
+A transaction hash.
+
+## Errors
+
+| Code | Message |
+| ---- | --------------------------------- |
+| 4001 | User denied transaction signature |
+| 4100 | Requested method not supported |
+| 4200 | Wallet not connected |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_signTypedData_v4.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_signTypedData_v4.mdx
new file mode 100644
index 00000000..77bc036b
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_signTypedData_v4.mdx
@@ -0,0 +1,195 @@
+---
+title: "eth_signTypedData_v4"
+---
+
+
+Defined in [EIP-712](https://eips.ethereum.org/EIPS/eip-712)
+
+> Signs EIP-712 typed data. The signing account must be unlocked. This method allows applications to create more user-friendly signing requests by providing structured data with type information, domain separation, and a robust hashing mechanism for the data. This is the most up-to-date version of the EIP-712 signing method.
+
+## Parameters
+
+```ts
+type SignTypedDataV4Params = [string, TypedData]
+
+interface TypedData {
+ /**
+ * The typed data domain
+ */
+ domain: TypedDataDomain
+ /**
+ * The primary type of the message
+ */
+ primaryType: string
+ /**
+ * Type definitions for all types used in the message
+ */
+ types: Record
+ /**
+ * The message to be signed
+ */
+ message: Record
+}
+
+interface TypedDataDomain {
+ /**
+ * The name of the domain
+ */
+ name?: string
+ /**
+ * The version of the domain
+ */
+ version?: string
+ /**
+ * The chain ID of the domain
+ */
+ chainId?: number | string
+ /**
+ * The verifying contract address
+ */
+ verifyingContract?: string
+ /**
+ * A salt used to disambiguate the domain
+ */
+ salt?: string
+}
+
+interface TypedDataField {
+ /**
+ * The name of the field
+ */
+ name: string
+ /**
+ * The type of the field
+ */
+ type: string
+}
+```
+
+## Returns
+
+`string`
+
+A signature string in hexadecimal format. The signature conforms to the standard Ethereum signature format: a 65-byte array represented as a hexadecimal string with `0x` prefix. The signature can be broken down into:
+
+- `r`: The first 32 bytes
+- `s`: The second 32 bytes
+- `v`: The final 1 byte (recovery ID)
+
+## Example
+
+Request:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "method": "eth_signTypedData_v4",
+ "params": [
+ "0x8eeC87D46108b8A3763Dda3A24A0D3e038C6996F",
+ {
+ "domain": {
+ "name": "My Dapp",
+ "version": "1",
+ "chainId": 1,
+ "verifyingContract": "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC"
+ },
+ "message": {
+ "from": {
+ "name": "Alice",
+ "wallets": [
+ "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
+ "0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"
+ ]
+ },
+ "to": {
+ "name": "Bob",
+ "wallets": [
+ "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
+ "0xB0BdaBea57B0BDABeA57b0bdABEA57b0BDabEa57"
+ ]
+ },
+ "contents": "Hello, Bob!"
+ },
+ "primaryType": "Mail",
+ "types": {
+ "EIP712Domain": [
+ { "name": "name", "type": "string" },
+ { "name": "version", "type": "string" },
+ { "name": "chainId", "type": "uint256" },
+ { "name": "verifyingContract", "type": "address" }
+ ],
+ "Mail": [
+ { "name": "from", "type": "Person" },
+ { "name": "to", "type": "Person" },
+ { "name": "contents", "type": "string" }
+ ],
+ "Person": [
+ { "name": "name", "type": "string" },
+ { "name": "wallets", "type": "address[]" }
+ ]
+ }
+ }
+ ]
+}
+```
+
+Response:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "result": "0x4355c47d63924e8a72e509b65029052eb6c299d53a04e167c5775fd466751c9d07299936d304c153f6443dfa05f40ff007d72911b6f72307f996231605b915621c"
+}
+```
+
+## Usage Example
+
+```js
+// Request to sign typed data
+const signature = await ethereum.request({
+ method: 'eth_signTypedData_v4',
+ params: [
+ '0x8eeC87D46108b8A3763Dda3A24A0D3e038C6996F', // The signer's address
+ JSON.stringify({
+ domain: {
+ name: 'My Dapp',
+ version: '1',
+ chainId: 1,
+ verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC'
+ },
+ message: {
+ // The data to sign
+ value: '100000000000000000', // 0.1 ETH in wei
+ recipient: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB',
+ timestamp: 1647032970
+ },
+ primaryType: 'Transaction',
+ types: {
+ EIP712Domain: [
+ { name: 'name', type: 'string' },
+ { name: 'version', type: 'string' },
+ { name: 'chainId', type: 'uint256' },
+ { name: 'verifyingContract', type: 'address' }
+ ],
+ Transaction: [
+ { name: 'value', type: 'string' },
+ { name: 'recipient', type: 'address' },
+ { name: 'timestamp', type: 'uint256' }
+ ]
+ }
+ })
+ ]
+});
+console.log('Signature:', signature);
+```
+
+## Errors
+
+| Code | Message | Description |
+| ---- | ------------------------------ | --------------------------------------------------------------- |
+| 4001 | User denied signature request | The user rejected the request to sign the data |
+| 4100 | Requested method not supported | The provider does not support the `eth_signTypedData_v4` method |
+| 4200 | Wallet not connected | The wallet is not connected to the dApp |
+| 4300 | Invalid parameters | The parameters provided are invalid |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/personal_sign.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/personal_sign.mdx
new file mode 100644
index 00000000..5e8c7d6d
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/personal_sign.mdx
@@ -0,0 +1,32 @@
+---
+title: "personal_sign"
+---
+
+
+Defined in [EIP-191](https://eips.ethereum.org/EIPS/eip-191)
+
+> Signs data using a specific account. This method calculates an Ethereum specific signature with: `sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message)))`.
+
+## Parameters
+
+`Array`
+
+An array containing two elements:
+
+1. The data to sign, in hexadecimal format
+2. The address of the account that should sign the data
+
+## Returns
+
+`string`
+
+A signature string in hexadecimal format.
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------------------ |
+| 4001 | User denied signature request |
+| 4100 | Requested method not supported |
+| 4200 | Wallet not connected |
+| 4300 | Invalid parameters |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/request-overview.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/request-overview.mdx
new file mode 100644
index 00000000..3758fac3
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/request-overview.mdx
@@ -0,0 +1,96 @@
+---
+title: "Overview"
+---
+
+
+The `request` method allows apps to make make Ethereum RPC requests to the wallet.
+
+## Specification
+
+```ts twoslash
+interface RequestArguments {
+ readonly method: string;
+ readonly params?: readonly unknown[] | object;
+}
+
+interface ProviderRpcError extends Error {
+ code: number;
+ data?: unknown;
+}
+
+interface CoinbaseWalletProvider {
+ /**
+ * @param {RequestArguments} args request arguments.
+ * @returns A promise that resolves with the result.
+ * @throws {ProviderRpcError} incase of error.
+ * @fires CoinbaseWalletProvider#connect When the provider successfully connects.
+ */
+ request: (args: RequestArguments) => Promise
+}
+```
+
+### Example
+
+
+```ts twoslash [example.ts]
+import {provider} from "./setup";
+
+const addresses = await provider.request({method: 'eth_requestAccounts'});
+const txHash = await provider.request({
+ method: 'eth_sendTransaction',
+ params: [{from: addresses[0], to: addresses[0], value: 1}]
+ }
+);
+```
+
+```ts twoslash [setup.ts] filename="setup.ts"
+import { CoinbaseWalletSDK } from '@coinbase/wallet-sdk'
+
+const baseSepoliaChainId = 84532;
+
+export const sdk = new CoinbaseWalletSDK({
+ appName: 'My App Name',
+ appChainIds: [baseSepoliaChainId]
+});
+
+const provider = sdk.makeWeb3Provider();
+```
+
+
+## Request Handling
+
+Requests are handled in one of three ways
+
+1. Sent to the Wallet application (Wallet mobile app, extension, or popup window).
+2. Handled locally by the SDK.
+3. Passed onto default RPC provider for the given chain, if it exists.
+
+### 1. Sent to the Wallet application
+
+The following RPC requests are sent to the Wallet application:
+
+- eth_ecRecover
+- personal_sign
+- personal_ecRecover
+- eth_signTransaction
+- eth_sendTransaction
+- eth_signTypedData_v1
+- eth_signTypedData_v3
+- eth_signTypedData_v4
+- eth_signTypedData
+- wallet_addEthereumChain
+- wallet_watchAsset
+- wallet_sendCalls
+- wallet_showCallsStatus
+
+### 2. Handled Locally by the SDK
+
+The following requests are handled locally by the SDK, with no external calls.
+
+- eth_requestAccounts
+- eth_accounts
+- eth_coinbase
+- net_version
+- eth_chainId
+- wallet_getCapabilities
+- wallet_switchEthereumChain
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/sdk-overview.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/sdk-overview.mdx
new file mode 100644
index 00000000..66030f0f
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/sdk-overview.mdx
@@ -0,0 +1,14 @@
+---
+title: "Overview"
+---
+
+
+## Introduction
+
+CoinbaseWalletProvider is an [JavaScript Ethereum provider](https://eips.ethereum.org/EIPS/eip-1193).
+It allows JavaScript applications to make Ethereum RPC requests, via its `request` method.
+These requests will be handled in one of three ways
+
+1. Sent to the Wallet (Wallet mobile app, extension, or popup window).
+2. Handled locally
+3. Passed onto default RPC provider for the given chain, if it exists.
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_addEthereumChain.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_addEthereumChain.mdx
new file mode 100644
index 00000000..d1b5694b
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_addEthereumChain.mdx
@@ -0,0 +1,48 @@
+---
+title: "wallet_addEthereumChain"
+---
+
+
+Defined in [EIP-3085](https://eips.ethereum.org/EIPS/eip-3085)
+
+> Requests that the wallet adds the specified chain to the wallet.
+
+## Parameters
+
+`Array`
+
+An array containing a single object with the following fields:
+
+| Field | Type | Description |
+| ----------------- | --------------- | -------------------------------------------------- |
+| chainId | string | The chain ID as a 0x-prefixed hexadecimal string |
+| chainName | string | The name of the chain to display to the user |
+| nativeCurrency | NativeCurrency | Information about the chain's native currency |
+| rpcUrls | `Array` | Array of RPC endpoint URLs for the chain |
+| blockExplorerUrls | `Array` | (Optional) Array of block explorer URLs |
+| iconUrls | `Array` | (Optional) Array of icon URLs for the chain's icon |
+
+The `NativeCurrency` object should contain:
+
+| Field | Type | Description |
+| -------- | ------ | -------------------------------------------------------- |
+| name | string | The name of the native currency |
+| symbol | string | The symbol of the native currency (2-6 characters) |
+| decimals | number | The number of decimals of the native currency (e.g., 18) |
+
+## Returns
+
+`null`
+
+Returns null if the request was successful.
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------------------ |
+| 4001 | User rejected the request |
+| 4100 | Requested method not supported |
+| 4200 | Wallet not connected |
+| 4300 | Invalid parameters |
+| 4901 | Chain already added |
+| 4902 | Chain couldn't be added |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_addSubAccount.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_addSubAccount.mdx
new file mode 100644
index 00000000..0c57bc4e
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_addSubAccount.mdx
@@ -0,0 +1,60 @@
+---
+title: "wallet_addSubAccount"
+---
+
+
+Experimental RPC Defined in [EIP-7895](https://eip.tools/eip/7895)
+
+> Requests that the wallet adds the specified sub-account to the wallet.
+
+## Parameters
+
+`Array`
+
+```ts
+type AccountCreate = {
+ type: 'create';
+ keys: {
+ type: 'address' | 'p256' | 'webcrypto-p256' | 'webauthn-p256';
+ key: `0x${string}`;
+ }[];
+};
+
+type AccountDeployed = {
+ type: 'deployed';
+ address: Address;
+};
+
+type AccountUndeployed = {
+ type: 'undeployed';
+ address?: Address;
+ factory?: Address;
+ factoryData?: Hex;
+ chainId?: Hex;
+};
+
+type AddSubAccountParameter = {
+ version: '1';
+ account: AddSubAccountAccount;
+};
+```
+
+## Returns
+
+```ts
+type AddSubAccountResponse = {
+ address: Address;
+ chainId?: Hex;
+ factory?: Address;
+ factoryData?: Hex;
+};
+```
+
+Returns null if the request was successful.
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------------------ |
+| 4001 | User rejected the request |
+| 4100 | Requested method not supported |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_connect.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_connect.mdx
new file mode 100644
index 00000000..965328a4
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_connect.mdx
@@ -0,0 +1,97 @@
+---
+title: "wallet_connect"
+---
+
+
+Experimental RPC defined in [ERC-7846](https://github.com/ethereum/ERCs/pull/779)
+
+> Requests that the wallet connects to the specified provider with an emphasis on extensibility.
+
+## Parameters
+
+`Array`
+
+```ts
+type SignInWithEthereumCapabilityRequest = {
+ nonce: string;
+ chainId: string; // EIP-155 hex-encoded
+ version?: string;
+ scheme?: string;
+ domain?: string;
+ uri?: string;
+ statement?: string;
+ issuedAt?: string;
+ expirationTime?: string;
+ notBefore?: string;
+ requestId?: string;
+ resources?: string[];
+};
+
+type SpendPermissionsCapabilityRequest = {
+ token: `0x${string}`;
+ allowance: string;
+ period: number;
+ salt?: `0x${string}`;
+ extraData?: `0x${string}`;
+};
+
+type AddSubAccountCapabilityRequest = {
+ // See wallet_addSubAccount
+ account: AddSubAccountAccount;
+};
+
+type ConnectParameter = {
+ version: string;
+ // Optional capabilities to request (e.g. Sign In With Ethereum).
+ capabilities?: {
+ addSubAccount?: AddSubAccountCapabilityRequest;
+ getSubAccounts?: boolean;
+ spendPermissions?: SpendPermissionsCapabilityRequest;
+ signInWithEthereum?: SignInWithEthereumCapabilityRequest;
+ };
+};
+
+```
+
+## Returns
+
+```ts
+type SignInWithEthereumCapabilityResponse = {
+ message: string;
+ signature: `0x${string}`;
+};
+
+type SpendPermissionsCapabilityResponse = {
+ signature: `0x${string}`;
+};
+
+type AddSubAccountCapabilityResponse = {
+ address: `0x${string}`;
+ chainId?: `0x${string}`;
+ factory?: `0x${string}`;
+ factoryData?: `0x${string}`;
+};
+
+type ConnectResponse = {
+ accounts: {
+ // Address of the connected account.
+ address: `0x${string}`;
+ // Capabilities granted that is associated with this account.
+ capabilities?: {
+ addSubAccount?: AddSubAccountCapabilityResponse | SerializedEthereumRpcError;
+ getSubAccounts?: AddSubAccountCapabilityResponse[];
+ spendPermissions?: SpendPermissionsCapabilityResponse | SerializedEthereumRpcError;
+ signInWithEthereum?: SignInWithEthereumCapabilityResponse | SerializedEthereumRpcError;
+ };
+ }[];
+};
+```
+
+Returns null if the request was successful.
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------------------ |
+| 4001 | User rejected the request |
+| 4100 | Requested method not supported |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_switchEthereumChain.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_switchEthereumChain.mdx
new file mode 100644
index 00000000..6ff76e4b
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_switchEthereumChain.mdx
@@ -0,0 +1,85 @@
+---
+title: "wallet_switchEthereumChain"
+---
+
+
+Defined in [EIP-3326](https://github.com/ethereum/EIPs/pull/3326)
+
+> Requests that the wallet switches to the specified chain. This method is used to request that the user changes the connected chain in their wallet. If the requested chain is not available in the wallet, this method may return a 4902 error, which can be used as a signal to call `wallet_addEthereumChain`.
+
+## Parameters
+
+```ts
+interface SwitchEthereumChainParameter {
+ /**
+ * The chain ID as a 0x-prefixed hexadecimal string
+ */
+ chainId: string
+}
+
+type SwitchEthereumChainParams = [SwitchEthereumChainParameter]
+```
+
+## Returns
+
+`null`
+
+Returns null if the request was successful, indicating that the wallet has switched to the requested chain.
+
+## Example
+
+Request:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "method": "wallet_switchEthereumChain",
+ "params": [{ "chainId": "0x1" }]
+}
+```
+
+Response:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "result": null
+}
+```
+
+## Handling Chain Not Found
+
+If the requested chain is not available in the wallet, the method will return a 4902 error. In this case, you may want to prompt the user to add the chain using `wallet_addEthereumChain`.
+
+```js
+try {
+ await ethereum.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId: '0x89' }], // Polygon
+ });
+} catch (error) {
+ // This error code indicates that the chain has not been added to the wallet
+ if (error.code === 4902) {
+ await ethereum.request({
+ method: 'wallet_addEthereumChain',
+ params: [{
+ chainId: '0x89',
+ chainName: 'Polygon Mainnet',
+ // ...other chain parameters
+ }],
+ });
+ }
+}
+```
+
+## Errors
+
+| Code | Message | Description |
+| ---- | ------------------------------ | --------------------------------------------------------------------- |
+| 4001 | User rejected the request | The user rejected the request to switch networks |
+| 4100 | Requested method not supported | The provider does not support the `wallet_switchEthereumChain` method |
+| 4200 | Wallet not connected | The wallet is not connected to the dApp |
+| 4300 | Invalid parameters | The parameters provided are invalid |
+| 4902 | Unrecognized chain ID | The chain ID is not recognized by the wallet |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_watchAsset.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_watchAsset.mdx
new file mode 100644
index 00000000..43682386
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_watchAsset.mdx
@@ -0,0 +1,112 @@
+---
+title: "wallet_watchAsset"
+---
+
+
+Defined in [EIP-747](https://eips.ethereum.org/EIPS/eip-747)
+
+> Allows suggesting a token to be added to a user's wallet. This method is used to request that the user tracks a specific token in their wallet. The wallet may prompt the user to approve the addition of the token and may reject the request if the token is already being tracked or if the parameters are invalid.
+
+## Parameters
+
+```ts
+interface WatchAssetParams {
+ /**
+ * The type of asset to watch
+ * Currently only 'ERC20' is supported
+ */
+ type: string
+ /**
+ * The options for the asset being watched
+ */
+ options: {
+ /**
+ * The token contract address
+ */
+ address: string
+ /**
+ * A ticker symbol or shorthand, up to 11 characters
+ */
+ symbol: string
+ /**
+ * The number of token decimals
+ */
+ decimals: number
+ /**
+ * A string URL of the token logo
+ */
+ image?: string
+ }
+}
+
+type WatchAssetParamsType = [WatchAssetParams]
+```
+
+## Returns
+
+`boolean`
+
+Returns `true` if the token was added successfully (the user approved the request and the token was successfully added to their wallet), `false` otherwise (the request was rejected or failed).
+
+## Example
+
+Request:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "method": "wallet_watchAsset",
+ "params": {
+ "type": "ERC20",
+ "options": {
+ "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
+ "symbol": "USDT",
+ "decimals": 6,
+ "image": "https://static.alchemyapi.io/images/assets/825.png"
+ }
+ }
+}
+```
+
+Response:
+
+```json
+{
+ "id": 1,
+ "jsonrpc": "2.0",
+ "result": true
+}
+```
+
+## Usage Example
+
+```js
+// Request to watch a token
+const wasAdded = await ethereum.request({
+ method: 'wallet_watchAsset',
+ params: {
+ type: 'ERC20',
+ options: {
+ address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
+ symbol: 'USDT',
+ decimals: 6,
+ image: 'https://static.alchemyapi.io/images/assets/825.png'
+ }
+ }
+});
+
+if (wasAdded) {
+ console.log('Token was added successfully');
+} else {
+ console.log('Token was not added');
+}
+```
+
+## Errors
+
+| Code | Message | Description |
+| ---- | ----------------------------- | ---------------------------------------------------------------- |
+| 4001 | User rejected the request | The user rejected the request to add the token |
+| 4200 | Unsupported asset type | The specified asset type is not supported (only ERC20 currently) |
+| 4100 | Missing or invalid parameters | The parameters provided are missing required fields or invalid |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/web3_clientVersion.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/web3_clientVersion.mdx
new file mode 100644
index 00000000..561b690b
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/web3_clientVersion.mdx
@@ -0,0 +1,26 @@
+---
+title: "web3_clientVersion"
+---
+
+
+Defined in the [Ethereum JSON-RPC Specification](https://ethereum.org/en/developers/docs/apis/json-rpc/)
+
+> Returns the current client version.
+
+## Returns
+
+`string`
+
+The current client version as a string.
+
+## Example
+
+```
+"MetaMask/v10.8.1"
+```
+
+## Errors
+
+| Code | Message |
+| ---- | ------------------ |
+| 4200 | Unsupported method |
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/index.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/index.mdx
new file mode 100644
index 00000000..812bcd5a
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/index.mdx
@@ -0,0 +1,112 @@
+---
+title: "Coinbase Wallet SDK"
+---
+
+
+The Coinbase Wallet SDK is a JavaScript library that allows you to connect to the Coinbase Wallet Provider.
+
+## Installation
+
+
+```bash [npm]
+npm i @coinbase/wallet-sdk
+```
+
+```bash [pnpm]
+pnpm i @coinbase/wallet-sdk
+```
+
+```bash [yarn]
+yarn add @coinbase/wallet-sdk
+```
+
+```bash [bun]
+bun i @coinbase/wallet-sdk
+```
+
+
+## makeWeb3Provider
+
+Creates a new `CoinbaseWalletProvider` instance using a `CoinbaseWalletSDK` instance.
+
+### Usage
+
+
+```ts twoslash [provider.ts]
+import {sdk} from "./setup";
+
+// Create provider
+const provider = sdk.makeWeb3Provider({options: 'smartWalletOnly'});
+// Use provider
+const addresses = provider.request({method: 'eth_requestAccounts'});
+```
+
+```ts twoslash [setup.ts] filename="setup.ts"
+import { CoinbaseWalletSDK } from '@coinbase/wallet-sdk'
+
+export const sdk = new CoinbaseWalletSDK({
+ appName: 'My App Name',
+ appChainIds: [8453]
+});
+```
+
+
+### Returns
+
+A new `CoinbaseWalletProvider` instance, which is an [Ethereum Javascript provider](https://eips.ethereum.org/EIPS/eip-1193) provider.
+
+### Parameters
+
+#### options (optional)
+
+- Type: `'all' | 'smartWalletOnly' | 'eoaOnly'`
+
+Determines which connection options users will see. Defaults to `all`.
+
+##### `all`
+
+Users will see Smart Wallet and mobile app connection options.
+
+
+If a user is using a browser with Coinbase Wallet Extension installed, they will be taken straight to the
+Coinbase Wallet Extension and not see any choice.
+
+
+
+##### `smartWalletOnly`
+
+With this option, users will only see an option to create a Smart Wallet or sign into their Smart Wallet.
+
+##### `eoaOnly`
+
+Users will only see the mobile app connection option. If the user is on mobile, they will be taken directly
+to the Coinbase Wallet app. If the user is using a browser with Coinbase Wallet Extension, they will be taken
+directly to the Extension.
+
+#### attribution (optional)
+
+- Type: `Attribution`
+
+This option only applies to Coinbase Smart Wallet. When a valid data suffix is supplied, it is appended to the initCode and executeBatch calldata.
+
+```ts twoslash
+type Attribution = {
+ auto: boolean;
+ dataSuffix?: never;
+} | {
+ auto?: never;
+ dataSuffix: `0x${string}`;
+}
+```
+
+##### `auto` (optional)
+
+- Type: `boolean`
+
+If auto is true, the Smart Wallet will generate a 16 byte hex string from the apps origin.
+
+##### `dataSuffix` (optional)
+
+- Type: `0x${string}`
+
+Smart Wallet expects a 16 byte hex string. If the data suffix is not a 16 byte hex string, the Smart Wallet will ignore the property.
diff --git a/_pages/identity/smart-wallet/technical-reference/sdk/sub-account-reference.mdx b/_pages/identity/smart-wallet/technical-reference/sdk/sub-account-reference.mdx
new file mode 100644
index 00000000..543484e2
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/sdk/sub-account-reference.mdx
@@ -0,0 +1,177 @@
+---
+title: "Sub Accounts"
+---
+
+
+## Overview
+
+The Coinbase Wallet SDK now supports Sub Accounts, allowing apps to create and manage additional smart contract accounts for users.
+Sub Accounts can have multiple owners and operate as smart contract wallets with enhanced security and flexibility.
+
+
+**Sub Account scope**
+
+Sub Accounts are currently scoped to an application's fully qualified domain name (FQDN).
+This means that a user has one sub account for your application,
+and it cannot be used on other applications on separate domains.
+
+
+
+## Getting Started
+
+### Installation
+
+```bash
+npm install @coinbase/wallet-sdk@canary
+```
+
+### Basic Setup with Sub Account Support
+
+```typescript
+import { createCoinbaseWalletSDK, getCryptoKeyAccount } from '@coinbase/wallet-sdk';
+
+// Initialize the SDK with Sub Account support
+const sdk = createCoinbaseWalletSDK({
+ appName: 'My Dapp',
+ appLogoUrl: 'https://example.com/logo.png',
+ // Set up Sub Account support with a signer function
+ toSubAccountSigner: async () => {
+ // Return a signer that can be used to authenticate operations
+ return await getCryptoKeyAccount();
+ },
+});
+
+// Get the provider for regular wallet operations
+const provider = sdk.getProvider();
+```
+
+## Creating a Sub Account
+
+```typescript
+// Create a new Sub Account with WebAuthn keys
+const account = await sdk.subaccount.create({
+ type: 'create',
+ keys: [
+ {
+ type: 'webauthn-p256',
+ // The public key of the owner, this can be retrieved from the account object
+ key: '0x7da44d4bc972affd138c619a211ef0afe0926b813fec67d15587cf8625b2bf185f5044ae96640a63b32aa1eb6f8f993006bbd26292b81cb07a0672302c69a866',
+ },
+ ],
+});
+
+console.log('Sub account created:', account.address);
+```
+
+## Getting a Sub Account
+
+```typescript
+// Get the current Sub Account if it exists, otherwise connect to one
+const account = await sdk.subaccount.get();
+console.log('Sub account address:', account.address);
+```
+
+## Adding an Owner to a Sub Account
+
+You can add owners to a Sub Account by providing either an address or a public key.
+
+```typescript
+// Add an owner using an address
+await sdk.subaccount.addOwner({
+ chainId: 84532, // Base Sepolia
+ address: '0xE3cA9Cc9378143a26b9d4692Ca3722dc45910a15',
+});
+
+// Or add an owner using a public key
+const { account } = await getCryptoKeyAccount();
+await sdk.subaccount.addOwner({
+ chainId: 84532, // Base Sepolia
+ publicKey: account.publicKey,
+});
+```
+
+## Using a Sub Account for Transactions
+
+Once a Sub Account is created, you can use it to sign and send transactions:
+
+```typescript
+// Connect to the Sub Account
+const account = await sdk.subaccount.get();
+
+// Use the provider to send transactions from the Sub Account
+const provider = sdk.getProvider();
+const signature = await provider.request({
+ method: 'personal_sign',
+ params: ['0x48656c6c6f2c20776f726c6421', account.address],
+});
+```
+
+## API Reference
+
+### SDK Configuration
+
+```typescript
+createCoinbaseWalletSDK({
+ appName: string,
+ appLogoUrl: string,
+ toSubAccountSigner: () => Promise<{ account: { publicKey: string; address?: string } }>,
+});
+```
+
+### Sub Account Methods
+
+#### `sdk.subaccount.create(account)`
+
+Creates a new Sub Account.
+
+Parameters:
+
+- `account`: An object with details about the account to create
+ - `type`: 'create' | 'deployed' | 'undeployed'
+ - `keys`: Array of key objects (for 'create' type)
+ - `type`: 'address' | 'p256' | 'webcrypto-p256' | 'webauthn-p256'
+ - `key`: Hex string of the public key
+
+Returns: `Promise`
+
+#### `sdk.subaccount.get()`
+
+Gets the current Sub Account, or connects to one if none exists.
+
+Returns: `Promise`
+
+#### `sdk.subaccount.addOwner(options)`
+
+Adds an owner to a Sub Account.
+
+Parameters:
+
+- `options`: Object with details about the owner to add
+ - `chainId`: Chain ID (number)
+ - `address`: Address of the owner (optional)
+ - `publicKey`: Public key of the owner (optional)
+
+Returns: `Promise` - Response from the smart contract call
+
+#### `sdk.subaccount.setSigner(toSubAccountSigner)`
+
+Sets the signer function for Sub Account operations.
+
+Parameters:
+
+- `toSubAccountSigner`: Function that returns a Promise resolving to a signer object
+
+## Technical Details
+
+Sub accounts are implemented as smart contract wallets that support multiple owners. The SDK provides a wrapper around the low-level RPC methods used to interact with these accounts:
+
+- `wallet_addSubAccount`: Creates a new Sub Account
+- `wallet_connect`: Connects to an existing Sub Account
+- `wallet_sendCalls`: Sends transactions from a Sub Account
+
+Each Sub Account can have multiple owners identified by either their address or public key. When performing operations with a Sub Account, the appropriate owner is used to sign the transaction.
+
+## Requirements
+
+- Coinbase Wallet SDK v4.4.0-canary or higher
+- An app connected to an EVM-compatible chain
diff --git a/_pages/identity/smart-wallet/technical-reference/spend-permissions/coinbase-fetchpermissions.mdx b/_pages/identity/smart-wallet/technical-reference/spend-permissions/coinbase-fetchpermissions.mdx
new file mode 100644
index 00000000..fa6cf6d4
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/spend-permissions/coinbase-fetchpermissions.mdx
@@ -0,0 +1,115 @@
+---
+title: "Coinbase Wallet API - Spend Permissions"
+---
+
+
+The `coinbase_fetchPermissions` RPC method retrieves permissions associated with a specific account, chain, and spender. This method excludes permissions that have expired or been revoked.
+
+## Overview
+
+**RPC Method:** `coinbase_fetchPermissions`
+
+**Endpoint:** `https://rpc.wallet.coinbase.com`
+
+This RPC method allows you to query active spend permissions for an account on a specific chain and spender.
+
+## Example Request
+
+Below is an example of how to use the `coinbase_fetchPermissions` method with a cURL request:
+
+```bash
+curl --location 'https://rpc.wallet.coinbase.com' \
+--header 'Content-Type: application/json' \
+--data '{
+ "jsonrpc": "2.0",
+ "method": "coinbase_fetchPermissions",
+ "params": [
+ {
+ "account": "0xfB2adc8629FC9F54e243377ffcECEb437a42934C",
+ "chainId": "0x14A34",
+ "spender": "0x2a83b0e4462449660b6e7567b2c81ac6d04d877d"
+ }
+ ],
+ "id": 1
+}'
+```
+
+## Request Schema
+
+```typescript
+type FetchPermissionsRequest = {
+ chainId: string; // hex, uint256
+ account: string; // address
+ spender: string; // address
+ pageOptions?: {
+ pageSize: number; // number of items requested, defaults to 50
+ cursor: string; // identifier for where the page should start
+ };
+};
+```
+
+### Parameters
+
+- **`chainId`**: The ID of the blockchain, in hexadecimal format.
+- **`account`**: The address of the account whose permissions are being queried.
+- **`spender`**: The entity granted with the permission to spend the `account`'s funds.
+- **`pageOptions`** _(optional)_:
+ - **`pageSize`**: The number of permissions to fetch in a single request (default: 50).
+ - **`cursor`**: A unique identifier to start fetching from a specific page.
+
+## Response Schema
+
+```typescript
+type FetchPermissionsResult = {
+ permissions: FetchPermissionsResultItem[];
+ pageDescription: {
+ pageSize: number; // number of items returned
+ nextCursor: string; // identifier for where the next page should start
+ };
+};
+
+type FetchPermissionsResultItem = {
+ createdAt: number; // UTC timestamp for when the permission was granted
+ permissionHash: string; // hex
+ signature: string; // hex
+ permission: {
+ account: string; // address
+ spender: string; // address
+ token: string; // address
+ allowance: string; // base 10 numeric string
+ period: number; // unix seconds
+ start: number; // unix seconds
+ end: number; // unix seconds
+ salt: string; // base 10 numeric string
+ extraData: string; // hex
+ };
+};
+```
+
+### Fields
+
+- **`permissions`**: An array of permission objects.
+- **`pageDescription`**:
+ - **`pageSize`**: Number of permissions returned in this response.
+ - **`nextCursor`**: The cursor to be used for the next page of results.
+
+### Permission Object
+
+- **`createdAt`**: The UTC timestamp when the permission was granted.
+- **`permissionHash`**: A unique hash representing the permission.
+- **`signature`**: The cryptographic signature for the permission.
+- **`permission`**:
+ - **`account`**: The address of the account granting the permission.
+ - **`spender`**: The address of the spender receiving the permission.
+ - **`token`**: The address of the token involved.
+ - **`allowance`**: The amount allowed, represented as a base 10 numeric string.
+ - **`period`**: Duration of the permission in Unix seconds.
+ - **`start`**: Start time of the permission in Unix seconds.
+ - **`end`**: End time of the permission in Unix seconds.
+ - **`salt`**: A unique salt value as a base 10 numeric string.
+ - **`extraData`**: Additional data in hexadecimal format.
+
+## Notes
+
+- Ensure the `chainId`, `account`, and `spender` parameters are correctly formatted and valid for the blockchain you are querying.
+- This RPC method only returns active permissions.
diff --git a/_pages/identity/smart-wallet/technical-reference/spend-permissions/spendpermissionmanager.mdx b/_pages/identity/smart-wallet/technical-reference/spend-permissions/spendpermissionmanager.mdx
new file mode 100644
index 00000000..37cc2c99
--- /dev/null
+++ b/_pages/identity/smart-wallet/technical-reference/spend-permissions/spendpermissionmanager.mdx
@@ -0,0 +1,155 @@
+---
+title: "`SpendPermissionManager.sol` smart contract"
+---
+
+;
+
+
+The open-source contracts repository is [here](https://github.com/coinbase/spend-permissions).
+
+### Structs
+
+#### `SpendPermission`
+
+Defines the complete parameters of a spend permission.
+
+
+ The fields of the `SpendPermission` structure must be strictly ordered as
+ defined below.
+
+
+| Field | Type | Description |
+| ----------- | --------- | ------------------------------------------------------------------------------------------ |
+| `account` | `address` | Smart account this spend permission is valid for. |
+| `spender` | `address` | Entity that can spend `account`'s tokens. |
+| `token` | `address` | Token address (ERC-7528 native token address or ERC-20 contract). |
+| `allowance` | `uint160` | Maximum allowed value to spend within each `period`. |
+| `period` | `uint48` | Time duration for resetting used `allowance` on a recurring basis (seconds). |
+| `start` | `uint48` | Timestamp this spend permission is valid starting at (unix seconds). |
+| `end` | `uint48` | Timestamp this spend permission is valid until (unix seconds). |
+| `salt` | `uint256` | An arbitrary salt to differentiate unique spend permissions with otherwise identical data. |
+| `extraData` | `bytes` | Arbitrary data to include in the permission. |
+
+#### `PeriodSpend`
+
+Describes the cumulative spend for the current active period.
+
+| Field | Type | Description |
+| ------- | --------- | ---------------------------------------- |
+| `start` | `uint48` | Start time of the period (unix seconds). |
+| `end` | `uint48` | End time of the period (unix seconds). |
+| `spend` | `uint160` | Accumulated spend amount for period. |
+
+---
+
+### Contract functions
+
+#### `approve`
+
+Approve a spend permission via a direct call from the `account`. Only callable by the `account` specified in the spend permission.
+
+```solidity
+function approve(SpendPermission calldata spendPermission) external;
+```
+
+---
+
+#### `approveWithSignature`
+
+Approve a spend permission via a signature from the `account` owner. Compatible with [ERC-6492](https://eips.ethereum.org/EIPS/eip-6492) signatures for automatic account creation if needed.
+
+```solidity
+function approveWithSignature(SpendPermission calldata spendPermission, bytes calldata signature) external;
+```
+
+---
+
+#### `spend`
+
+Spend tokens using a spend permission, transferring them from the `account` to the `spender`. Only callable by the `spender` specified in the permission.
+
+```solidity
+function spend(SpendPermission memory spendPermission, uint160 value) external;
+```
+
+---
+
+#### `revoke`
+
+Revoke a spend permission, permanently disabling its use. Only callable by the `account` specified in the spend permission.
+
+```solidity
+function revoke(SpendPermission calldata spendPermission) external;
+```
+
+---
+
+#### `revokeAsSpender`
+
+Revoke a spend permission, permanently disabling its use. Only callable by the `spender` specified in the spend permission.
+
+```solidity
+function revokeAsSpender(SpendPermission calldata spendPermission) external;
+```
+
+---
+
+#### `getHash`
+
+Generate a hash of a `SpendPermission` struct for signing, in accordance with [EIP-712](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md).
+
+```solidity
+function getHash(SpendPermission memory spendPermission) public view returns (bytes32);
+```
+
+---
+
+#### `isApproved`
+
+Check if a spend permission is approved, regardless of whether the current time is within the valid time range of the permission.
+
+```solidity
+function isApproved(SpendPermission memory spendPermission) public view returns (bool);
+```
+
+---
+
+#### `isRevoked`
+
+Check if a spend permission is revoked, regardless of whether the current time is within the valid time range of the permission.
+
+```solidity
+function isRevoked(SpendPermission memory spendPermission) public view returns (bool);
+```
+
+---
+
+#### `isValid`
+
+Check if a spend permission is approved and not revoked, regardless of whether the current time is within the valid time range of the permission.
+
+```solidity
+function isValid(SpendPermission memory spendPermission) public view returns (bool);
+```
+
+---
+
+#### `getLastUpdatedPeriod`
+
+Retrieve the `start`, `end`, and accumulated `spend` for the last updated period of a spend permission.
+
+```solidity
+function getLastUpdatedPeriod(SpendPermission memory spendPermission) public view returns (PeriodSpend memory);
+```
+
+---
+
+#### `getCurrentPeriod`
+
+Retrieve the `start`, `end`, and accumulated `spend` for the current period of a spend permission.
+Reverts if the current time is outside the valid time range of the permission, but does not validate whether the
+spend permission has been approved or revoked.
+
+```solidity
+function getCurrentPeriod(SpendPermission memory spendPermission) public view returns (PeriodSpend memory);
+```
diff --git a/docs/pages/identity/wallet-sdk/adding-tokens-to-coinbase-wallet.mdx b/_pages/identity/wallet-sdk/adding-tokens-to-coinbase-wallet.mdx
similarity index 98%
rename from docs/pages/identity/wallet-sdk/adding-tokens-to-coinbase-wallet.mdx
rename to _pages/identity/wallet-sdk/adding-tokens-to-coinbase-wallet.mdx
index 6435bb0e..7c7ba527 100644
--- a/docs/pages/identity/wallet-sdk/adding-tokens-to-coinbase-wallet.mdx
+++ b/_pages/identity/wallet-sdk/adding-tokens-to-coinbase-wallet.mdx
@@ -12,9 +12,12 @@ Coinbase Wallet makes any ERC-20 token instantly available for swapping seconds
Follow the instructions below to ensure your token logo, asset name, and other metadata also appear on Coinbase Wallet.
-:::info[Disclaimer]
+
+**Disclaimer**
+
Base does not endorse any specific token that is deployed on mainnet and made available for swapping.
-:::
+
+
---
@@ -61,7 +64,10 @@ How to get your custom link:
**Step 2:** Click the share button
-:::info[Disclaimer]
+
+**Disclaimer**
+
New assets with low liquidity may result in failed swaps or may result in a user receiving less of the destination token due to slippage. An important responsibility of the token creator is to communicate to the community these risks.
-:::
+
+
diff --git a/docs/pages/identity/wallet-sdk/android-api-reference.mdx b/_pages/identity/wallet-sdk/android-api-reference.mdx
similarity index 99%
rename from docs/pages/identity/wallet-sdk/android-api-reference.mdx
rename to _pages/identity/wallet-sdk/android-api-reference.mdx
index 898c5420..02bf5afb 100644
--- a/docs/pages/identity/wallet-sdk/android-api-reference.mdx
+++ b/_pages/identity/wallet-sdk/android-api-reference.mdx
@@ -33,8 +33,7 @@ None.
#### Example
-:::code-group
-
+
```kotlin [Kotlin]
val requestAccounts = Web3JsonRPC.RequestAccounts().action()
```
@@ -42,6 +41,5 @@ val requestAccounts = Web3JsonRPC.RequestAccounts().action()
```java [Java]
Action requestAccounts = new Web3JsonRPC.RequestAccounts().action(false);
```
-
-:::
+
diff --git a/docs/pages/identity/wallet-sdk/android-establishing-a-connection.mdx b/_pages/identity/wallet-sdk/android-establishing-a-connection.mdx
similarity index 98%
rename from docs/pages/identity/wallet-sdk/android-establishing-a-connection.mdx
rename to _pages/identity/wallet-sdk/android-establishing-a-connection.mdx
index aec4ee58..364d9090 100644
--- a/docs/pages/identity/wallet-sdk/android-establishing-a-connection.mdx
+++ b/_pages/identity/wallet-sdk/android-establishing-a-connection.mdx
@@ -6,8 +6,7 @@ slug: 'android-establishing-a-connection'
A connection to Coinbase Wallet can be initiated by calling the `initiateHandshake` function provided by the SDK. The function also takes in an optional `initialActions` parameter which apps can use to take certain actions along with the initial handshake request.
-:::code-group
-
+
```kotlin [Kotlin]
val requestAccount = Web3JsonRPC.RequestAccounts().action()
val handShakeActions = listOf(requestAccount)
@@ -47,8 +46,7 @@ client.initiateHandshake(
}
);
```
-
-:::
+
An example handshake request is provided in the [sample application](https://github.com/MobileWalletProtocol/wallet-mobile-sdk/blob/main/android/example/src/main/java/com/coinbase/android/beta/MainActivity.kt).
diff --git a/docs/pages/identity/wallet-sdk/android-install.mdx b/_pages/identity/wallet-sdk/android-install.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/android-install.mdx
rename to _pages/identity/wallet-sdk/android-install.mdx
diff --git a/docs/pages/identity/wallet-sdk/android-making-requests.mdx b/_pages/identity/wallet-sdk/android-making-requests.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/android-making-requests.mdx
rename to _pages/identity/wallet-sdk/android-making-requests.mdx
diff --git a/docs/pages/identity/wallet-sdk/android-setup.mdx b/_pages/identity/wallet-sdk/android-setup.mdx
similarity index 97%
rename from docs/pages/identity/wallet-sdk/android-setup.mdx
rename to _pages/identity/wallet-sdk/android-setup.mdx
index e2889e9e..c5b46159 100644
--- a/docs/pages/identity/wallet-sdk/android-setup.mdx
+++ b/_pages/identity/wallet-sdk/android-setup.mdx
@@ -14,8 +14,7 @@ In order for your app to interact with Coinbase Wallet, you must add a [queries
Before the SDK can be used, it needs to be configured with an App Link to your application. This callback URL will be used by the Coinbase Wallet application to navigate back to your application.
-:::code-group
-
+
```kotlin [Kotlin]
CoinbaseWalletSDK(
appContext = applicationContext,
@@ -34,13 +33,11 @@ new CoinbaseWalletSDK(
}
);
```
-
-:::
+
When your application receives a response from Coinbase Wallet via App Links, this URL needs to be handed off to the SDK via the `handleResponse` function.
-:::code-group
-
+
```kotlin [Kotlin]
launcher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
val uri = result.data?.data ?: return@registerForActivityResult
@@ -65,8 +62,7 @@ protected void onActivityResult(int requestCode, int resultCode, @Nullable Inten
client.handleResponse(url);
}
```
-
-:::
+
An example is provided in our [sample application](https://github.com/coinbase/wallet-mobile-sdk/blob/master/android/example/src/main/java/com/coinbase/android/beta/ui/MainActivity.kt#L42).
diff --git a/docs/pages/identity/wallet-sdk/developer-settings.mdx b/_pages/identity/wallet-sdk/developer-settings.mdx
similarity index 97%
rename from docs/pages/identity/wallet-sdk/developer-settings.mdx
rename to _pages/identity/wallet-sdk/developer-settings.mdx
index 7e4a2c95..40a2b211 100644
--- a/docs/pages/identity/wallet-sdk/developer-settings.mdx
+++ b/_pages/identity/wallet-sdk/developer-settings.mdx
@@ -16,27 +16,28 @@ Enable and disable test networks.
To see a list of supported test networks, see [Whitelisted Networks](./whitelisted-networks.mdx).
-:::info
+
Testnets are enabled by default for the Coinbase Wallet browser extension. When enabled, Testnet assets display separately from Mainnet assets in a dedicated Testnets tab.
-:::
+
+
## Show private key
View the private key associated with your wallet.
-:::info
+
If `Show Private Key` is disabled, you are connected to the extension through your mobile app or your hardware wallet.
To export your private key and get full functionality, first sign out of the extension and import your seed phrase into Coinbase Wallet Extension.
-:::
+
-
-:::caution
+
+
Do not share your private key with anyone, even Coinbase. Anyone with your private key can steal your funds.
+
-:::
## Export custom networks
diff --git a/docs/pages/identity/wallet-sdk/disconnecting-links.mdx b/_pages/identity/wallet-sdk/disconnecting-links.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/disconnecting-links.mdx
rename to _pages/identity/wallet-sdk/disconnecting-links.mdx
diff --git a/docs/pages/identity/wallet-sdk/environments.mdx b/_pages/identity/wallet-sdk/environments.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/environments.mdx
rename to _pages/identity/wallet-sdk/environments.mdx
diff --git a/docs/pages/identity/wallet-sdk/errors.mdx b/_pages/identity/wallet-sdk/errors.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/errors.mdx
rename to _pages/identity/wallet-sdk/errors.mdx
diff --git a/_pages/identity/wallet-sdk/existing-welcome-page-features.mdx b/_pages/identity/wallet-sdk/existing-welcome-page-features.mdx
new file mode 100644
index 00000000..323269b5
--- /dev/null
+++ b/_pages/identity/wallet-sdk/existing-welcome-page-features.mdx
@@ -0,0 +1,5 @@
+---
+title: "Existing Welcome Page Features"
+---
+
+
diff --git a/docs/pages/identity/wallet-sdk/faq.mdx b/_pages/identity/wallet-sdk/faq.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/faq.mdx
rename to _pages/identity/wallet-sdk/faq.mdx
diff --git a/docs/pages/identity/wallet-sdk/getting-eth-accounts.mdx b/_pages/identity/wallet-sdk/getting-eth-accounts.mdx
similarity index 99%
rename from docs/pages/identity/wallet-sdk/getting-eth-accounts.mdx
rename to _pages/identity/wallet-sdk/getting-eth-accounts.mdx
index 17ca831b..29a646ab 100644
--- a/docs/pages/identity/wallet-sdk/getting-eth-accounts.mdx
+++ b/_pages/identity/wallet-sdk/getting-eth-accounts.mdx
@@ -31,10 +31,10 @@ ethereum.enable().then((accounts: string[]) => {
Once the user obtains authorization, the Web3 object (`web3`) and the Web3 Provider (`ethereum`) are ready to be used.
-:::info
-
+
If you were using `ethereum.on("accountsChanged")`, remove it and obtain addresses with EIP-1102 callbacks instead. It was removed to improve compatibility with the latest `web3.js`.
-:::
+
+
**Next steps:**
diff --git a/docs/pages/identity/wallet-sdk/injected-provider-guidance.mdx b/_pages/identity/wallet-sdk/injected-provider-guidance.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/injected-provider-guidance.mdx
rename to _pages/identity/wallet-sdk/injected-provider-guidance.mdx
diff --git a/docs/pages/identity/wallet-sdk/injected-provider.mdx b/_pages/identity/wallet-sdk/injected-provider.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/injected-provider.mdx
rename to _pages/identity/wallet-sdk/injected-provider.mdx
diff --git a/docs/pages/identity/wallet-sdk/installing.mdx b/_pages/identity/wallet-sdk/installing.mdx
similarity index 99%
rename from docs/pages/identity/wallet-sdk/installing.mdx
rename to _pages/identity/wallet-sdk/installing.mdx
index 9b830055..745593bd 100644
--- a/docs/pages/identity/wallet-sdk/installing.mdx
+++ b/_pages/identity/wallet-sdk/installing.mdx
@@ -6,9 +6,10 @@ slug: 'installing'
This page explains how to install and upgrade Coinbase Wallet SDK .
-:::caution
+
The installation package for **Coinbase Wallet SDK** (formerly WalletLink) is now named `@coinbase/wallet-sdk`.
-:::
+
+
## Installing Wallet SDK
diff --git a/docs/pages/identity/wallet-sdk/ios-api-reference.mdx b/_pages/identity/wallet-sdk/ios-api-reference.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/ios-api-reference.mdx
rename to _pages/identity/wallet-sdk/ios-api-reference.mdx
diff --git a/docs/pages/identity/wallet-sdk/ios-establishing-a-connection.mdx b/_pages/identity/wallet-sdk/ios-establishing-a-connection.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/ios-establishing-a-connection.mdx
rename to _pages/identity/wallet-sdk/ios-establishing-a-connection.mdx
diff --git a/docs/pages/identity/wallet-sdk/ios-install.mdx b/_pages/identity/wallet-sdk/ios-install.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/ios-install.mdx
rename to _pages/identity/wallet-sdk/ios-install.mdx
diff --git a/docs/pages/identity/wallet-sdk/ios-making-requests.mdx b/_pages/identity/wallet-sdk/ios-making-requests.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/ios-making-requests.mdx
rename to _pages/identity/wallet-sdk/ios-making-requests.mdx
diff --git a/docs/pages/identity/wallet-sdk/ios-setup.mdx b/_pages/identity/wallet-sdk/ios-setup.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/ios-setup.mdx
rename to _pages/identity/wallet-sdk/ios-setup.mdx
diff --git a/docs/pages/identity/wallet-sdk/message-payment-xmtp-content-type.mdx b/_pages/identity/wallet-sdk/message-payment-xmtp-content-type.mdx
similarity index 99%
rename from docs/pages/identity/wallet-sdk/message-payment-xmtp-content-type.mdx
rename to _pages/identity/wallet-sdk/message-payment-xmtp-content-type.mdx
index 1a0dc2fc..8de20a6c 100644
--- a/docs/pages/identity/wallet-sdk/message-payment-xmtp-content-type.mdx
+++ b/_pages/identity/wallet-sdk/message-payment-xmtp-content-type.mdx
@@ -50,9 +50,10 @@ A regular payment message contains details of standard cryptocurrency transactio
| `toAddress` | The recipient's cryptocurrency wallet address. |
| `transactionHash` | A unique identifier for the transaction. |
-:::info
+
The `transactionHash` can be used to fetch additional details and verify that the transaction has succeeded.
-:::
+
+
## Coinbase Sponsored Payments
diff --git a/_pages/identity/wallet-sdk/mobile-dapp-integration-via-deep-linking.mdx b/_pages/identity/wallet-sdk/mobile-dapp-integration-via-deep-linking.mdx
new file mode 100644
index 00000000..ec066e9c
--- /dev/null
+++ b/_pages/identity/wallet-sdk/mobile-dapp-integration-via-deep-linking.mdx
@@ -0,0 +1,5 @@
+---
+title: "Mobile DApp Integration via Deep Linking"
+---
+
+
diff --git a/docs/pages/identity/wallet-sdk/mobile-sdk-overview.mdx b/_pages/identity/wallet-sdk/mobile-sdk-overview.mdx
similarity index 98%
rename from docs/pages/identity/wallet-sdk/mobile-sdk-overview.mdx
rename to _pages/identity/wallet-sdk/mobile-sdk-overview.mdx
index 343175c3..08b3d6e9 100644
--- a/docs/pages/identity/wallet-sdk/mobile-sdk-overview.mdx
+++ b/_pages/identity/wallet-sdk/mobile-sdk-overview.mdx
@@ -6,9 +6,10 @@ slug: 'mobile-sdk-overview'
[Coinbase Wallet Mobile SDK](https://github.com/MobileWalletProtocol/wallet-mobile-sdk) is an open source SDK that allows you to connect your native mobile applications to millions of Coinbase Wallet users.
-:::info
+
Coinbase Wallet Mobile SDK does not work with [Smart Wallet](https://www.smartwallet.dev/).
-:::
+
+
## Platforms
diff --git a/docs/pages/identity/wallet-sdk/sample-applications.mdx b/_pages/identity/wallet-sdk/sample-applications.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/sample-applications.mdx
rename to _pages/identity/wallet-sdk/sample-applications.mdx
diff --git a/_pages/identity/wallet-sdk/setup.mdx b/_pages/identity/wallet-sdk/setup.mdx
new file mode 100644
index 00000000..69d99df0
--- /dev/null
+++ b/_pages/identity/wallet-sdk/setup.mdx
@@ -0,0 +1,98 @@
+---
+title: 'Setup'
+sidebar_label: 'Setup'
+slug: 'initializing'
+---
+
+This page explains how to integrate Coinbase Wallet SDK as the default provider for your app using web3.js . You can follow a similar pattern if you are using ethers.js .
+
+## Setting up Coinbase Wallet SDK
+
+
+The code snippets have been updated to reflect the rebranding of Coinbase Wallet SDK from its previous name of WalletLink.
+
+
+
+
+Instructions are in TypeScript. The usage is the same in JavaScript, except for the occasional TypeScript type annotation such as `string[]` or `as any`.
+
+
+
+### Prerequisites
+
+- A Typescript project set up locally, created with `yarn create react-app my-app --template typescript` or similar
+- web3.js installed using `npm install web3` or similar
+
+### Initializing
+
+In your **App.tsx** file, add the following code to initialize Coinbase Wallet SDK and a Web3 object:
+
+```typescript
+// TypeScript
+import CoinbaseWalletSDK from '@coinbase/wallet-sdk';
+import Web3 from 'web3';
+
+const APP_NAME = 'My Awesome App';
+const APP_LOGO_URL = 'https://example.com/logo.png';
+const APP_SUPPORTED_CHAIN_IDS = [8453, 84532];
+
+// Initialize Coinbase Wallet SDK
+export const coinbaseWallet = new CoinbaseWalletSDK({
+ appName: APP_NAME,
+ appLogoUrl: APP_LOGO_URL,
+ chainIds: APP_SUPPORTED_CHAIN_IDS,
+});
+
+// Initialize a Web3 Provider object
+export const ethereum = coinbaseWallet.makeWeb3Provider();
+
+// Initialize a Web3 object
+export const web3 = new Web3(ethereum as any);
+```
+
+Coinbase Wallet SDK uses an rpcUrl provided by Coinbase Wallet clients regardless of the rpcUrl passed into `makeWeb3Provider` for whitelisted networks. Wallet SDK needs an rpcUrl to be provided by the app as a fallback.
+
+**Next steps:**
+
+- [Switching or Adding EVM Chains](./switching-chains.mdx)
+- [Getting Ethereum Accounts](./getting-eth-accounts.mdx)
+
+## Troubleshooting
+
+
+ I run into the following error: Module not found: Error: Can't resolve <'assert'/'url/'/...>
+
+Due to the removal of default polyfills in webpack5, you must install the following utilities:
+
+```bash
+yarn add assert
+yarn add url
+yarn add os-browserify
+yarn add https-browserify
+yarn add stream-http
+yarn add stream-browserify
+yarn add crypto-browserify
+```
+
+Then, add the following code snippet to your _webpack.config.js_:
+
+```javascript
+resolve: {
+ fallback: {
+ fs: false,
+ 'util': require.resolve('assert/'),
+ 'url': require.resolve('url/'),
+ 'os': require.resolve("os-browserify/browser"),
+ 'https': require.resolve("https-browserify"),
+ 'http': require.resolve("stream-http"),
+ 'stream': require.resolve("stream-browserify"),
+ 'crypto': require.resolve("crypto-browserify")
+ },
+}
+```
+
+If you are using an application built on `create-react-app` locally, you must run `npm run eject` to be able to customize your webpack configuration.
+
+
+
+
diff --git a/_pages/identity/wallet-sdk/smart-wallet.mdx b/_pages/identity/wallet-sdk/smart-wallet.mdx
new file mode 100644
index 00000000..33caaa76
--- /dev/null
+++ b/_pages/identity/wallet-sdk/smart-wallet.mdx
@@ -0,0 +1,6 @@
+---
+title: "Smart Wallet"
+---
+
+
+// ... existing code ...
diff --git a/docs/pages/identity/wallet-sdk/solana-adapter-guide.mdx b/_pages/identity/wallet-sdk/solana-adapter-guide.mdx
similarity index 99%
rename from docs/pages/identity/wallet-sdk/solana-adapter-guide.mdx
rename to _pages/identity/wallet-sdk/solana-adapter-guide.mdx
index 1bcf6e88..060fb517 100644
--- a/docs/pages/identity/wallet-sdk/solana-adapter-guide.mdx
+++ b/_pages/identity/wallet-sdk/solana-adapter-guide.mdx
@@ -18,9 +18,10 @@ To set up a new React app, run:
npx create-react-app
```
-:::info
+
For additional front-end framework support, visit the Solana wallet adapter [documentation](https://github.com/solana-labs/wallet-adapter#solanawallet-adapter) to find more [starter projects](https://github.com/solana-labs/wallet-adapter#starter-projects) and [packages](https://github.com/solana-labs/wallet-adapter#starter-projects).
-:::
+
+
## Install
@@ -76,11 +77,10 @@ export default function WalletAdapter() {
}
```
-:::info
-
+
For additional UI framework support, such as [Material UI](https://mui.com/) and [Ant Design](https://ant.design/), visit the Solana wallet adapter [documentation](https://github.com/solana-labs/wallet-adapter#ui-components).
+
-:::
## Step 3: Configure wallets
@@ -140,9 +140,10 @@ export default function WalletAdapter() {
```
-:::info
+
You can also specify the `autoconnect` attribute on the `WalletProvider` component to automatically attempt to reconnect a user wallet upon a page refresh.
-:::
+
+
## Step 4: Configure network connection
diff --git a/docs/pages/identity/wallet-sdk/solana-connecting-accounts.mdx b/_pages/identity/wallet-sdk/solana-connecting-accounts.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/solana-connecting-accounts.mdx
rename to _pages/identity/wallet-sdk/solana-connecting-accounts.mdx
diff --git a/docs/pages/identity/wallet-sdk/solana-overview.mdx b/_pages/identity/wallet-sdk/solana-overview.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/solana-overview.mdx
rename to _pages/identity/wallet-sdk/solana-overview.mdx
diff --git a/docs/pages/identity/wallet-sdk/solana-provider-api.mdx b/_pages/identity/wallet-sdk/solana-provider-api.mdx
similarity index 98%
rename from docs/pages/identity/wallet-sdk/solana-provider-api.mdx
rename to _pages/identity/wallet-sdk/solana-provider-api.mdx
index ab13f88a..6077b640 100644
--- a/docs/pages/identity/wallet-sdk/solana-provider-api.mdx
+++ b/_pages/identity/wallet-sdk/solana-provider-api.mdx
@@ -35,11 +35,10 @@ If successful, sets the `isConnected` property to `true`, and sets the `publicKe
Disconnects the user’s wallet.
If successful, sets the `isConnected` property to `false`, and sets the `publicKey` property to `null`.
-:::info
-
+
This method does not disconnect the app from the wallet under **Settings > Dapp Connections**.
+
-:::
### sendTransaction
#### `sendTransaction(transaction: Transaction, options?: SendOptions): Promise<{ signature: string }>`
@@ -47,11 +46,10 @@ This method does not disconnect the app from the wallet under **Settings > Dapp
Sends a transaction.
Returns a Promise for an object containing the signature.
-:::info
-
+
[SendOptions](https://solana-labs.github.io/solana-web3.js/modules.html#SendOptions) can be specified as a optional second argument.
+
-:::
#### Parameters
| Name | Type | Description |
@@ -80,11 +78,10 @@ Returns a Promise for the list of signed transactions.
Signs and sends a transaction.
Returns a Promise for an object containing the signature.
-:::info
-
+
[SendOptions](https://solana-labs.github.io/solana-web3.js/modules.html#SendOptions) can be specified as a optional second argument.
+
-:::
#### Parameters
| Name | Type | Description |
diff --git a/docs/pages/identity/wallet-sdk/solana-provider.mdx b/_pages/identity/wallet-sdk/solana-provider.mdx
similarity index 97%
rename from docs/pages/identity/wallet-sdk/solana-provider.mdx
rename to _pages/identity/wallet-sdk/solana-provider.mdx
index 184f8c7d..11bd50fa 100644
--- a/docs/pages/identity/wallet-sdk/solana-provider.mdx
+++ b/_pages/identity/wallet-sdk/solana-provider.mdx
@@ -18,7 +18,8 @@ const getProvider = () => {
};
```
-:::info
+
If a user does have Coinbase Wallet installed and no provider is found, we recommend redirecting the user to download Coinbase Wallet.
-:::
+
+
diff --git a/docs/pages/identity/wallet-sdk/solana-sending-transactions.mdx b/_pages/identity/wallet-sdk/solana-sending-transactions.mdx
similarity index 99%
rename from docs/pages/identity/wallet-sdk/solana-sending-transactions.mdx
rename to _pages/identity/wallet-sdk/solana-sending-transactions.mdx
index 0428e7cc..9c294eea 100644
--- a/docs/pages/identity/wallet-sdk/solana-sending-transactions.mdx
+++ b/_pages/identity/wallet-sdk/solana-sending-transactions.mdx
@@ -41,9 +41,10 @@ transaction.add(
```
-:::info
+
You can learn more about creating transactions on Solana by visiting the [Solana documentation](https://docs.solana.com/developing/clients/javascript-api#creating-and-sending-transactions).
-:::
+
+
## Signing a transaction
diff --git a/docs/pages/identity/wallet-sdk/solana-signing-messages.mdx b/_pages/identity/wallet-sdk/solana-signing-messages.mdx
similarity index 98%
rename from docs/pages/identity/wallet-sdk/solana-signing-messages.mdx
rename to _pages/identity/wallet-sdk/solana-signing-messages.mdx
index a0ad084f..c8e86998 100644
--- a/docs/pages/identity/wallet-sdk/solana-signing-messages.mdx
+++ b/_pages/identity/wallet-sdk/solana-signing-messages.mdx
@@ -16,9 +16,10 @@ const { signature } = await window.coinbaseSolana.signMessage(encodedMessage);
If the user successfully signs the message, the call returns a Promise for an object with the signature.
-:::info
+
The message passed to the `signMessage` method must be a UTF-8 encoded string as a [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array).
-:::
+
+
## Verifying a message
diff --git a/docs/pages/identity/wallet-sdk/solana-wallet-adapter.mdx b/_pages/identity/wallet-sdk/solana-wallet-adapter.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/solana-wallet-adapter.mdx
rename to _pages/identity/wallet-sdk/solana-wallet-adapter.mdx
diff --git a/_pages/identity/wallet-sdk/supported-environments.mdx b/_pages/identity/wallet-sdk/supported-environments.mdx
new file mode 100644
index 00000000..aa3f8768
--- /dev/null
+++ b/_pages/identity/wallet-sdk/supported-environments.mdx
@@ -0,0 +1,5 @@
+---
+title: "Supported Environments"
+---
+
+
diff --git a/docs/pages/identity/wallet-sdk/sw-rainbowkit.mdx b/_pages/identity/wallet-sdk/sw-rainbowkit.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/sw-rainbowkit.mdx
rename to _pages/identity/wallet-sdk/sw-rainbowkit.mdx
diff --git a/docs/pages/identity/wallet-sdk/sw-setup.mdx b/_pages/identity/wallet-sdk/sw-setup.mdx
similarity index 94%
rename from docs/pages/identity/wallet-sdk/sw-setup.mdx
rename to _pages/identity/wallet-sdk/sw-setup.mdx
index 656af3db..783039f2 100644
--- a/docs/pages/identity/wallet-sdk/sw-setup.mdx
+++ b/_pages/identity/wallet-sdk/sw-setup.mdx
@@ -4,6 +4,7 @@ sidebar_label: "Smart Wallet"
slug: "sw-setup"
---
-:::info
+
Our Smart Wallet documentation has moved! Visit the new [Smart Wallet Documentation](https://www.smartwallet.dev/) or join the [Smart Wallet Discord Channel](https://discord.gg/invite/cdp).
-:::
+
+
diff --git a/docs/pages/identity/wallet-sdk/switching-chains.mdx b/_pages/identity/wallet-sdk/switching-chains.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/switching-chains.mdx
rename to _pages/identity/wallet-sdk/switching-chains.mdx
diff --git a/docs/pages/identity/wallet-sdk/tracking-assets.mdx b/_pages/identity/wallet-sdk/tracking-assets.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/tracking-assets.mdx
rename to _pages/identity/wallet-sdk/tracking-assets.mdx
diff --git a/_pages/identity/wallet-sdk/ux-guidelines.mdx b/_pages/identity/wallet-sdk/ux-guidelines.mdx
new file mode 100644
index 00000000..1ff107cb
--- /dev/null
+++ b/_pages/identity/wallet-sdk/ux-guidelines.mdx
@@ -0,0 +1,6 @@
+---
+title: "UX Guidelines"
+---
+
+
+// ... existing code ...
diff --git a/docs/pages/identity/wallet-sdk/ux-tips.mdx b/_pages/identity/wallet-sdk/ux-tips.mdx
similarity index 99%
rename from docs/pages/identity/wallet-sdk/ux-tips.mdx
rename to _pages/identity/wallet-sdk/ux-tips.mdx
index cc77cc51..d7dc725b 100644
--- a/docs/pages/identity/wallet-sdk/ux-tips.mdx
+++ b/_pages/identity/wallet-sdk/ux-tips.mdx
@@ -4,9 +4,10 @@ sidebar_label: "UX Guidelines"
slug: "ux-tips"
---
-:::tip
+
Coinbase Wallet SDK, or Wallet SDK, has been fully rebranded from its former name of WalletLink.
-:::
+
+
## Mobile UX guidance
diff --git a/docs/pages/identity/wallet-sdk/wallet-features.mdx b/_pages/identity/wallet-sdk/wallet-features.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/wallet-features.mdx
rename to _pages/identity/wallet-sdk/wallet-features.mdx
diff --git a/_pages/identity/wallet-sdk/wallet-mobile-sdk.mdx b/_pages/identity/wallet-sdk/wallet-mobile-sdk.mdx
new file mode 100644
index 00000000..c7b16195
--- /dev/null
+++ b/_pages/identity/wallet-sdk/wallet-mobile-sdk.mdx
@@ -0,0 +1,5 @@
+---
+title: "Wallet Mobile SDK"
+---
+
+
diff --git a/_pages/identity/wallet-sdk/wallet-sdk-existing-section.mdx b/_pages/identity/wallet-sdk/wallet-sdk-existing-section.mdx
new file mode 100644
index 00000000..8988f00e
--- /dev/null
+++ b/_pages/identity/wallet-sdk/wallet-sdk-existing-section.mdx
@@ -0,0 +1,5 @@
+---
+title: "Wallet SDK Existing Section"
+---
+
+
diff --git a/_pages/identity/wallet-sdk/wallet-sdk-mobile-sdk.mdx b/_pages/identity/wallet-sdk/wallet-sdk-mobile-sdk.mdx
new file mode 100644
index 00000000..0cdf1385
--- /dev/null
+++ b/_pages/identity/wallet-sdk/wallet-sdk-mobile-sdk.mdx
@@ -0,0 +1,5 @@
+---
+title: "Wallet SDK Mobile SDK"
+---
+
+
diff --git a/_pages/identity/wallet-sdk/welcome.mdx b/_pages/identity/wallet-sdk/welcome.mdx
new file mode 100644
index 00000000..f7f2b143
--- /dev/null
+++ b/_pages/identity/wallet-sdk/welcome.mdx
@@ -0,0 +1,43 @@
+---
+title: "Welcome"
+sidebar_label: "Welcome"
+slug: "welcome"
+---
+
+
+**dapps are apps**
+
+Dapps are now called apps! If you still see references to "dapps", please forgive us while we clean things up.
+
+
+Welcome to the Coinbase Wallet developer documentation. Coinbase Wallet is a self-custody crypto wallet, available as a browser extension and a mobile app on Android and iOS.
+
+## Coinbase Wallet features
+
+Coinbase Wallet works with all EVM-compatible L1/L2 networks, supports NFTs, and is multi-platform (browser extension, iOS and Android mobile apps).
+
+Learn more about Coinbase Wallet's [features](./wallet-features.mdx).
+
+## Get started
+
+You can integrate Coinbase Wallet into both web applications and native mobile applications.
+
+### Web applications
+
+There are two ways to integrate Coinbase Wallet into your web applications:
+
+1. [Injected provider](./injected-provider.mdx): The top-level web3 provider injected by the Coinbase Wallet browser extension.
+2. [Coinbase Wallet SDK](https://www.smartwallet.dev/sdk/install): An open source SDK which allows you to connect your applications to millions of Coinbase Wallet users.
+3. Third-party libraries: The **most recommended** way to integrate. Open source libraries which allow you to easily add multi-wallet support within your web application (i.e. [wagmi](https://www.smartwallet.dev/guides/create-app/using-wagmi))
+
+### Mobile applications
+
+You can integrate Coinbase Wallet into your native mobile application via the [Coinbase Wallet Mobile SDK](./mobile-sdk-overview.mdx).
+
+The SDK is available for the following platforms:
+
+- [iOS](./ios-install.mdx)
+- [Android](./android-install.mdx)
+- [React Native](https://www.npmjs.com/package/@coinbase/wallet-mobile-sdk)
+- [Flutter](https://pub.dev/packages/coinbase_wallet_sdk)
+
diff --git a/docs/pages/identity/wallet-sdk/whitelisted-networks.mdx b/_pages/identity/wallet-sdk/whitelisted-networks.mdx
similarity index 100%
rename from docs/pages/identity/wallet-sdk/whitelisted-networks.mdx
rename to _pages/identity/wallet-sdk/whitelisted-networks.mdx
diff --git a/_pages/identity/wallet-sdk/xmtp.mdx b/_pages/identity/wallet-sdk/xmtp.mdx
new file mode 100644
index 00000000..f0022bf3
--- /dev/null
+++ b/_pages/identity/wallet-sdk/xmtp.mdx
@@ -0,0 +1,5 @@
+---
+title: "XMTP"
+---
+
+
diff --git a/_pages/index.mdx b/_pages/index.mdx
new file mode 100644
index 00000000..47a28a5b
--- /dev/null
+++ b/_pages/index.mdx
@@ -0,0 +1,136 @@
+---
+content:
+ layout: landing
+ sidebar: true
+ width: 100%
+ horizontalPadding: 40px
+ verticalPadding: 0px
+---
+
+
+
+import { BrowseUseCaseCard } from '/snippets/BrowseUseCaseCard';
+import { BrowseCard } from '/snippets/BrowseCard';
+import { onchainKitSvg } from '@/components/svg/onchainKitSvg';
+import { smartWalletSvg } from '@/components/svg/smartWalletSvg';
+import { agentKitSvg } from '@/components/svg/agentKitSvg';
+import { verificationsSvg } from '@/components/svg/verificationsSvg';
+import { miniKitSvg } from '@/components/svg/miniKitSvg';
+import { onboardingSvg } from '@/components/svg/onboardingSvg';
+import { agentSvg } from '@/components/svg/agentSvg';
+import { paymentsSvg } from '@/components/svg/paymentsSvg';
+import { depositSvg } from '@/components/svg/depositSvg';
+import { gaslessSvg } from '@/components/svg/gaslessSvg';
+import { socialSvg } from '@/components/svg/socialSvg';
+import { paymasterSvg } from '@/components/svg/paymasterSvg';
+
+
+
+
+
+
Base Documentation
+
+ Base is a secure, low-cost, builder-friendly Ethereum L2 built to bring the next billion users onchain.
+
+ Base is incubated within Coinbase and plans to progressively decentralize in the years ahead. We believe that
+ decentralization is critical to creating an open, global cryptocurrency that is accessible to everyone.
+
+
+
+ {/* Browse by Use Cases Section */}
+
+
Browse by use cases
+
+
+
+
+
+
+
+
+
+
+ {/* Browse by Tools Section */}
+
+
Browse by tools
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/pages/learn/address-and-payable/address-and-payable.mdx b/_pages/learn/address-and-payable/address-and-payable.mdx
similarity index 100%
rename from docs/pages/learn/address-and-payable/address-and-payable.mdx
rename to _pages/learn/address-and-payable/address-and-payable.mdx
diff --git a/_pages/learn/advanced-functions/function-modifiers-vid.mdx b/_pages/learn/advanced-functions/function-modifiers-vid.mdx
new file mode 100644
index 00000000..10616e52
--- /dev/null
+++ b/_pages/learn/advanced-functions/function-modifiers-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Function Modifiers
+description: Use modifiers to control how functions work.
+hide_table_of_contents: false
+---
+
+# Function Modifiers
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/advanced-functions/function-modifiers.mdx b/_pages/learn/advanced-functions/function-modifiers.mdx
new file mode 100644
index 00000000..87aee2bc
--- /dev/null
+++ b/_pages/learn/advanced-functions/function-modifiers.mdx
@@ -0,0 +1,169 @@
+---
+title: Function Modifiers
+description: Build custom function modifiers to efficiently modify functionality.
+hide_table_of_contents: false
+---
+
+# Function Modifiers
+
+Function modifiers allow you to efficiently change the behavior of functions. In some ways, it's similar to inheritance, but there are restrictions, particularly in variable scope.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Use modifiers to efficiently add functionality to multiple functions
+
+---
+
+## Adding a Simple OnlyOwner Modifier
+
+By default, `public` functions can be called by **anyone**, without restriction. Often this is desirable. You want any user to be able to see what NFTs are for sale on your platform, sign up for a service, or read various items stored in state.
+
+However, there will be many functions you **don't** want any user to be able to do, such as setting the fee for using the app, or withdrawing all funds in the contract! A common pattern to protect these functions is to use `modifier`s to make sure that only the owner can call these functions.
+
+
+For a production app, you'll want to use a more robust implementation of `onlyOwner`, such as the [one provided by OpenZeppelin].
+
+
+
+### Adding an Owner
+
+The address of the deployer of a contract is **not** included as an accessible property. To make it available, add it as a state variable and assign `msg.sender` in the `constructor`.
+
+
+
+Reveal code
+
+```solidity
+contract Modifiers {
+ address owner;
+
+ constructor () {
+ owner = msg.sender;
+ }
+}
+```
+
+
+
+
+
+### Creating an `onlyOwner` Modifier
+
+[Modifiers] are very similar to functions and are declared with the `modifier` keyword. The modifier can run any Solidity code, including functions, and is allowed to modify state. Modifiers must have a special `_` character, which serves as a placeholder for where the code contained within the modified function will run.
+
+Create a simple `onlyOwner` modifier, which returns an `error` of `NotOwner` with the sending address if the sender is not the owner.
+
+
+
+Reveal code
+
+```solidity
+error NotOwner(address _msgSender);
+```
+
+```solidity
+modifier onlyOwner {
+ if (msg.sender != owner) {
+ revert NotOwner(msg.sender);
+ }
+ _;
+}
+```
+
+
+
+
+
+Test your `modifier` by adding a function that uses it:
+
+
+
+Reveal code
+
+```solidity
+function iOwnThis() public view onlyOwner returns (string memory) {
+ return "You own this!";
+}
+```
+
+
+
+
+
+To test, deploy your contract and call the `iOwnThis` function. You should see the message "You own this!".
+
+Next, switch the _Account_, and try the function again. You should see an error in the console:
+
+```text
+call to Modifiers.iOwnThis errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Error provided by the contract:
+NotOwner
+Parameters:
+{
+ "_msgSender": {
+ "value": "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"
+ }
+}
+Debug the transaction to get more information.
+```
+
+
+Always verify the output of a function call in the console. The result that appears under the button for the function is convenient, but it does **not** clear or change if a subsequent call reverts.
+
+
+
+---
+
+## Modifiers and Variables
+
+Modifiers can have parameters, which essentially work the same as in functions. These parameters can be independent values, or they can overlap with the arguments provided to a function call.
+
+### Modifiers with Parameters
+
+Modifier parameters can be the arguments provided to the functions they modify. You can perform calculations and trigger errors based on these values.
+
+```solidity
+error NotEven(uint number);
+
+modifier onlyEven(uint _number) {
+ if(_number % 2 != 0) {
+ revert NotEven(_number);
+ }
+ _;
+}
+
+function halver(uint _number) public pure onlyEven(_number) returns (uint) {
+ return _number / 2;
+}
+```
+
+### Independent Scope
+
+While `modifiers` are used to modify functions and can share inputs, they have separate scopes. The following example will **not** work:
+
+```solidity
+// Bad code example, does not work
+modifier doubler(uint _number) {
+ _number *= 2;
+ _;
+}
+
+function modifierDoubler(uint _number) public pure doubler(_number) returns (uint) {
+ return _number; // Returns the original number, NOT number * 2
+}
+```
+
+---
+
+## Conclusion
+
+Function `modifier`s are an efficient and reusable way to add checks, trigger errors, and control function execution. In this lesson, you've seen examples of how they can be used to abort execution under certain conditions. You've also learned that they have separate scopes and cannot be used to modify variables within the function they modify.
+
+[one provided by OpenZeppelin]: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
diff --git a/_pages/learn/advanced-functions/function-visibility-vid.mdx b/_pages/learn/advanced-functions/function-visibility-vid.mdx
new file mode 100644
index 00000000..7af2d842
--- /dev/null
+++ b/_pages/learn/advanced-functions/function-visibility-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Function Visibility
+description: Learn how to control the visibility of functions.
+hide_table_of_contents: false
+---
+
+# Function Visibility
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/advanced-functions/function-visibility.mdx b/_pages/learn/advanced-functions/function-visibility.mdx
new file mode 100644
index 00000000..42f4f0cd
--- /dev/null
+++ b/_pages/learn/advanced-functions/function-visibility.mdx
@@ -0,0 +1,131 @@
+---
+title: Function Visibility and State Mutability
+description: A quick reference for all your function declaring needs.
+hide_table_of_contents: false
+---
+
+# Function Visibility and State Mutability
+
+You've seen much of this before, but this document outlines and highlights the options for _function visibility_ and _state mutability_ all in one document.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Categorize functions as public, private, internal, or external based on their usage
+- Describe how pure and view functions are different than functions that modify storage
+
+---
+
+## Function Visibility
+
+There are four types of [visibility] for functions in Solidity: `external`, `public`, `internal`, and `private`. These labels represent a further division of the _public_ and _private_ labels you might use in another language.
+
+### External
+
+Functions with `external` visibility are **only** callable from other contracts and cannot be called within their own contract. You may see older references stating you should use `external` over `public` because it forces the function to use `calldata`. This is no longer correct, because both function visibilities can now use `calldata` and `memory` for parameters. However, using `calldata` for either will cost less gas.
+
+```solidity
+contract Foo {
+ constructor() {
+ // Bad code example, will not work
+ uint bar = foo(3);
+ // ... other code
+ }
+
+ function foo(uint _number) external pure returns (uint) {
+ return _number*2;
+ }
+}
+```
+
+### Public
+
+Public functions work the same as external, except they may also be called within the contract that contains them.
+
+```solidity
+contract Foo {
+ constructor() {
+ // Public functions may be called within the contract
+ uint bar = foo(3);
+ // ... other code
+ }
+
+ function foo(uint _number) public pure returns (uint) {
+ return _number*2;
+ }
+}
+```
+
+### Private and Internal
+
+Functions visible as `private` and `internal` operate nearly identically. Beyond writing hygienic code, these have a very important effect. Because they are not a part of the contract's ABI, you can use `mapping`s and `storage` variable references as parameters.
+
+The difference is that `private` functions can't be called from derived contracts. You'll learn more about that when we cover inheritance.
+
+Some developers prepend an underscore to `private` and `internal` functions.
+
+```solidity
+function _foo(uint _number) private returns (uint) {
+ return _number*2;
+}
+```
+
+
+All data on a blockchain is public. Don't mistake hiding visibility while coding for hiding information from the world!
+
+
+
+---
+
+## Function State Mutability
+
+State mutability labels are relatively unique to Solidity. They determine how a function can interact with state, which has a substantial impact on gas costs.
+
+### Pure
+
+`pure` functions promise to neither read nor write state. They're usually used for helper functions that support other functionality.
+
+```solidity
+function abs(int x) public pure returns (int) {
+ return x >= 0 ? x : -x;
+}
+```
+
+`pure` functions can be called from outside the blockchain without using gas, if they are also `public` or `external`.
+
+### View
+
+`view` functions access state, but don't modify it. You've used these for tasks such as returning all the values in an array.
+
+```solidity
+function getArr() public view returns (uint[] memory) {
+ return arr;
+}
+```
+
+`view` functions can be called from outside the blockchain without using gas, if they are also `public` or `external`.
+
+### Unlabeled Functions
+
+Functions that are not labeled `view` or `pure` can modify state and the compiler will generate a warning if they do not.
+
+```solidity
+function addToArr(uint _number) public {
+ arr.push(_number);
+}
+```
+
+They can have any visibility and will always cost gas when called.
+
+---
+
+## Conclusion
+
+The visibility and mutability keywords in Solidity help you organize your code and alert other developers to the properties of each of your functions. Use them to keep your code organized and readable.
+
+---
+
+[visibility]: https://docs.soliditylang.org/en/v0.8.17/contracts.html?highlight=pure#function-visibility
diff --git a/_pages/learn/arrays/array-storage-layout-vid.mdx b/_pages/learn/arrays/array-storage-layout-vid.mdx
new file mode 100644
index 00000000..77d6665c
--- /dev/null
+++ b/_pages/learn/arrays/array-storage-layout-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Array Storage Layout
+description: Learn how arrays are kept in storage.
+hide_table_of_contents: false
+---
+
+# Array Storage Layout
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/arrays/arrays-exercise.mdx b/_pages/learn/arrays/arrays-exercise.mdx
new file mode 100644
index 00000000..1967e8b5
--- /dev/null
+++ b/_pages/learn/arrays/arrays-exercise.mdx
@@ -0,0 +1,89 @@
+---
+title: Arrays Exercise
+description: Exercise - Demonstrate your knowledge of arrays.
+hide_table_of_contents: false
+---
+
+# Arrays Exercise
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Review the contract in the starter snippet called `ArraysExercise`. It contains an array called `numbers` that is initialized with the numbers 1–10. Copy and paste this into your file.
+
+```solidity
+contract ArraysExercise {
+ uint[] public numbers = [1,2,3,4,5,6,7,8,9,10];
+}
+```
+
+Add the following functions:
+
+### Return a Complete Array
+
+The compiler automatically adds a getter for individual elements in the array, but it does not automatically provide functionality to retrieve the entire array.
+
+Write a function called `getNumbers` that returns the entire `numbers` array.
+
+### Reset Numbers
+
+Write a `public` function called `resetNumbers` that resets the `numbers` array to its initial value, holding the numbers from 1-10.
+
+
+We'll award the pin for any solution that works, but one that **doesn't** use `.push()` is more gas-efficient!
+
+
+
+
+Remember, _anyone_ can call a `public` function! You'll learn how to protect functionality in another lesson.
+
+
+
+### Append to an Existing Array
+
+Write a function called `appendToNumbers` that takes a `uint[] calldata` array called `_toAppend`, and adds that array to the `storage` array called `numbers`, already present in the starter.
+
+### Timestamp Saving
+
+At the contract level, add an `address` array called `senders` and a `uint` array called `timestamps`.
+
+Write a function called `saveTimestamp` that takes a `uint` called `_unixTimestamp` as an argument. When called, it should add the address of the caller to the end of `senders` and the `_unixTimestamp` to `timestamps`.
+
+
+You'll need to research on your own to discover the correct _Special Variables and Functions_ that can help you with this challenge!
+
+
+
+### Timestamp Filtering
+
+Write a function called `afterY2K` that takes no arguments. When called, it should return two arrays.
+
+The first should return all timestamps that are more recent than January 1, 2000, 12:00am. To save you a click, the Unix timestamp for this date and time is `946702800`.
+
+The second should return a list of `senders` addresses corresponding to those timestamps.
+
+### Resets
+
+Add `public` functions called `resetSenders` and `resetTimestamps` that reset those storage variables.
+
+---
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
diff --git a/_pages/learn/arrays/arrays-in-solidity-vid.mdx b/_pages/learn/arrays/arrays-in-solidity-vid.mdx
new file mode 100644
index 00000000..ac651d90
--- /dev/null
+++ b/_pages/learn/arrays/arrays-in-solidity-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Arrays
+description: Learn about the unique properties of arrays in Solidity.
+hide_table_of_contents: false
+---
+
+# Arrays
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/arrays/arrays-in-solidity.mdx b/_pages/learn/arrays/arrays-in-solidity.mdx
similarity index 100%
rename from docs/pages/learn/arrays/arrays-in-solidity.mdx
rename to _pages/learn/arrays/arrays-in-solidity.mdx
diff --git a/_pages/learn/arrays/filtering-an-array-sbs.mdx b/_pages/learn/arrays/filtering-an-array-sbs.mdx
new file mode 100644
index 00000000..bd2ab54e
--- /dev/null
+++ b/_pages/learn/arrays/filtering-an-array-sbs.mdx
@@ -0,0 +1,252 @@
+---
+title: Filtering an Array
+description: Explore techniques to filter an array.
+hide_table_of_contents: false
+---
+
+# Filtering an Array
+
+In this exercise, you'll explore two different solutions for filtering an array in Solidity. By doing so, you'll gain a better understanding of the constraints present while working with arrays, and have the chance to learn and compare the gas costs of different approaches.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Write a function that can return a filtered subset of an array
+
+---
+
+## First Pass Solution
+
+### Setup
+
+Create a new workspace in Remix and add a file called `ArrayDemo.sol` containing a `contract` called `ArrayDemo`. Initialize an array containing the numbers from 1 to 10. Add a stub for a function called `getEvenNumbers` that returns a `uint[] memory`.
+
+```solidity
+contract ArrayDemo {
+ uint[] public numbers = [1,2,3,4,5,6,7,8,9,10];
+
+ function getEvenNumbers() external view returns(uint[] memory) {
+ // TODO
+ }
+}
+```
+
+
+You don't have to declare the size of the memory array to be returned. You usually don't want to either, unless the results will always be the same, known size.
+
+
+
+### Finding the Number of Even Numbers
+
+We need to initialize a `memory` array to hold the results, but to do so, we need to know how big to make the array. Don't be tempted to count the number of evens in `numbers`, as what happens if we modify it later?
+
+The simple and obvious solution is to simply iterate through `numbers` and count how many even numbers are present. You could add that functionality in `getEvenNumbers()`, but it might be useful elsewhere, so a better practice would be to separate these concerns into another function.
+
+Go ahead and write it on your own. It needs to:
+
+- Instantiate a `uint` to hold the results
+- Iterate through all values in `numbers` and increment that number if the value is even
+- Return the result
+
+You should end up with something like:
+
+
+
+Reveal code
+
+```solidity
+function _countEvenNumbers() internal view returns(uint) {
+ uint result = 0;
+
+ for(uint i = 0; i < numbers.length; i++) {
+ if(numbers[i] % 2 == 0) {
+ result++;
+ }
+ }
+
+ return result;
+}
+```
+
+
+
+
+
+The `_` in front of the function name is a practice used by some developers, in Solidity and in other languages, to indicate visually that this function is intended for internal use only.
+
+### Returning Only Even Numbers
+
+Now that we have a method to find out how big the return array needs to be in `getEvenNumbers()`, we can simply loop through `numbers`, and add the even numbers to the array to be returned.
+
+Finish the function on your own. It needs to:
+
+- Determine the number of results and instantiate an array that size
+- Loop through the `numbers` array and if a given number is even, add it to the next unused index in the results array
+
+You should end up with something like:
+
+
+
+Reveal code
+
+```solidity
+
+function getEvenNumbers() external view returns(uint[] memory) {
+ uint resultsLength = _countEvenNumbers();
+ uint[] memory results = new uint[](resultsLength);
+ uint cursor = 0;
+
+ for(uint i = 0; i < numbers.length; i++) {
+ if(numbers[i] % 2 == 0) {
+ results[cursor] = numbers[i];
+ cursor++;
+ }
+ }
+
+ return results;
+}
+
+```
+
+
+
+
+
+Did you catch the compiler warning about `view`? You aren't modifying state, so you should mark it as such.
+
+### Testing the Function
+
+Deploy your contract and test the function. You should get a return of `[2,4,6,8,10]`. The total gas cost will be about 63,947, depending on if you used the same helper variables, etc.
+
+---
+
+## Optimizing the Function
+
+It does seem inefficient to loop through the same array twice. What if we instead kept track of how many even numbers to expect. That way, we would only need to loop once, thus saving gas! Right?
+
+Only one way to find out.
+
+### Tracking Relevant Data
+
+Add a contract-level variable called `numEven`, and initialize it with **5**, the number of even numbers in the array. Modify `getEvenNumbers()` to use `numEven` instead of the `_countEvenNumbers()` function. It should now look like:
+
+
+
+Reveal code
+
+```solidity
+function getEvenNumbers() external view returns(uint[] memory) {
+ uint resultsLength = numEven; // <- Changed here
+ uint[] memory results = new uint[](resultsLength);
+ uint cursor = 0;
+
+ for(uint i = 0; i < numbers.length; i++) {
+ if(numbers[i] % 2 == 0) {
+ results[cursor] = numbers[i];
+ cursor++;
+ }
+ }
+
+ return results;
+}
+```
+
+
+
+
+
+Redeploy and test again. Success, the function now only costs about 57,484 gas to run! Except there is a catch. Remember, it's going to cost about 5000 gas to update `numEven` **each time** the array adds an even number.
+
+### A More Realistic Accounting
+
+As we considered above, in a real-world example, we wouldn't declare the array up front, it would be modified over time. A slightly more realistic example would be to fill the array with a function.
+
+Change the declaration for `numbers` and `numEven` so that they have their respective default values to begin with.
+
+```solidity
+uint[] public numbers;
+uint numEven;
+```
+
+Add a new function called `debugLoadArray` that takes a `uint` called `_number` as an argument, and fills the array by looping through `_number` times, pushing each number into the array. **For now, _don't_ update `numEven`**.
+
+
+
+Reveal code
+
+```solidity
+function debugLoadArray(uint _number) external {
+ for(uint i = 0; i < _number; i++) {
+ numbers.push(i);
+ }
+}
+```
+
+
+
+
+
+Test out the function by loading in **10** numbers. It costs about 249,610 gas to load the array. Now, add functionality to **also** increment `numEven` when the number added is even. We can't just calculate it, because although the numbers are sequential in the debug function, they might not be in real world use.
+
+
+
+Reveal code
+
+```solidity
+function debugLoadArray(uint _number) external {
+ for(uint i = 0; i < _number; i++) {
+ numbers.push(i);
+ if(i % 2 == 0) {
+ numEven++;
+ }
+ }
+}
+```
+
+
+
+
+
+**Be sure to redeploy** and try again with **10** numbers. This time, the cost was about 275,335 gas. That's almost 26,000 more gas in an effort to save the 5,000 gas needed to run `_countEvenNumbers()`.
+
+### Looking at the Big Picture
+
+What about more? What if there are a thousand numbers in the array? What about a million?
+
+Let's start with 500, any more will break the Remix EVM simulation, and/or would trigger an out of gas error because we're approaching the gas limit for the entire block.
+
+**Comment out** the `if` statement in `debugLoadArray` that checks for even numbers and load 500 numbers. The Remix EVM should be able to handle this, but it might hang up for a moment, or even crash. (You can also do this experiment with 250 numbers instead.)
+
+```solidity
+function debugLoadArray(uint _number) external {
+ for(uint i = 0; i < _number; i++) {
+ numbers.push(i);
+ // if(i % 2 == 0) {
+ // numEven++;
+ //}
+ }
+}
+```
+
+You'll get a result of about 11,323,132 gas to load the array. That's a lot! The target total gas for a single block is 15 million, and the limit is 30 million.
+
+Try again with the code to increment `numEven`. You should get about 11,536,282, or an increase of about 213,150 gas.
+
+Now, test out `getEvenNumbers()` using `numEven` vs. using `_countEvenNumbers()`. With `numEven`, it should cost about 1,578,741 gas to find the even numbers. Using `_countEvenNumbers()`, that cost increases to 1,995,579 gas, an increase of 416,838 gas.
+
+### Which is Better?
+
+As is often the case with code, it depends. You might think that the experiment makes things obvious. Paying 213k gas up front to track `_numEven` results in a savings of over 400k gas when filtering for even numbers. Even better, you might realize that the upfront cost difference will be spread across all of your users over time, making them almost trivial. You also might think that it's possible that the filter function could be called dozens of times for each time 500 numbers are loaded.
+
+These are all valid considerations that you should evaluate as you are developing your code solution to a business problem. One last critical element to consider is that there is only a gas cost to read from the blockchain if it's another contract calling the function. It **doesn't** cost any gas to call `view` or `pure` functions from a front end or app.
+
+If `getEvenNumbers` will never be called by another contract, then using `numEven` might cost more for no benefit!
+
+---
+
+## Conclusion
+
+In this lesson, you've explored a few different approaches to a problem. You've learned how to filter an array, but more importantly, you've learned some of the specific considerations in blockchain development. Finally, you've seen that pushing 500 integers to an array, usually a trivial operation, is very large and very expensive on the EVM.
diff --git a/_pages/learn/arrays/fixed-size-arrays-vid.mdx b/_pages/learn/arrays/fixed-size-arrays-vid.mdx
new file mode 100644
index 00000000..14a16671
--- /dev/null
+++ b/_pages/learn/arrays/fixed-size-arrays-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Fixed-Size Arrays
+description: Learn about fixed-size arrays.
+hide_table_of_contents: false
+---
+
+# Fixed-Size Arrays
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/arrays/writing-arrays-in-solidity-vid.mdx b/_pages/learn/arrays/writing-arrays-in-solidity-vid.mdx
new file mode 100644
index 00000000..59b428a9
--- /dev/null
+++ b/_pages/learn/arrays/writing-arrays-in-solidity-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Writing Arrays
+description: Learn how to write arrays in Solidity.
+hide_table_of_contents: false
+---
+
+# Writing Arrays in Solidity
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/contracts-and-basic-functions/basic-functions-exercise.mdx b/_pages/learn/contracts-and-basic-functions/basic-functions-exercise.mdx
new file mode 100644
index 00000000..bd9f4202
--- /dev/null
+++ b/_pages/learn/contracts-and-basic-functions/basic-functions-exercise.mdx
@@ -0,0 +1,40 @@
+---
+title: 'Basic Functions Exercise'
+description: Exercise - Create and deploy a contract with simple math functions.
+hide_table_of_contents: false
+---
+
+# Basic Functions Exercise
+
+Each module in this course will contain exercises in which you are given a specification for a contract **without** being given specific instructions on how to build the contract. You must use what you've learned to figure out the best solution on your own!
+
+
+Once you've learned how to deploy your contracts to a test network, you'll be given the opportunity to submit your contract address for review by an onchain unit test. If it passes, you'll receive an NFT pin recognizing your accomplishment.
+
+**You'll deploy and submit this contract in the next module.**
+
+
+
+The following exercise asks you to create a contract that adheres to the following stated specifications.
+
+## Contract
+
+Create a contract called `BasicMath`. It should not inherit from any other contracts and does not need a constructor. It should have the following two functions:
+
+### Adder
+
+A function called `adder`. It must:
+
+- Accept two `uint` arguments, called `_a` and `_b`
+- Return a `uint` `sum` and a `bool` `error`
+- If `_a` + `_b` does not overflow, it should return the `sum` and an `error` of `false`
+- If `_a` + `_b` overflows, it should return `0` as the `sum`, and an `error` of `true`
+
+### Subtractor
+
+A function called `subtractor`. It must:
+
+- Accept two `uint` arguments, called `_a` and `_b`
+- Return a `uint` `difference` and a `bool` `error`
+- If `_a` - `_b` does not underflow, it should return the `difference` and an `error` of `false`
+- If `_a` - `_b` underflows, it should return `0` as the `difference`, and an `error` of `true`
diff --git a/_pages/learn/contracts-and-basic-functions/basic-types.mdx b/_pages/learn/contracts-and-basic-functions/basic-types.mdx
new file mode 100644
index 00000000..4702393b
--- /dev/null
+++ b/_pages/learn/contracts-and-basic-functions/basic-types.mdx
@@ -0,0 +1,223 @@
+---
+title: Basic Types
+description: Introduction to basic types in Solidity.
+hide_table_of_contents: false
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+# Basic Types
+
+Solidity contains most of the basic [types] you are used to from other languages, but their properties and usage are often a little different than other languages and are likely much more restrictive. In particular, Solidity is a very **explicit** language and will not allow you to make inferences most of the time.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Categorize basic data types
+- List the major differences between data types in Solidity as compared to other languages
+- Compare and contrast signed and unsigned integers
+
+---
+
+## Common Properties
+
+In Solidity, [types] must always have a value and are never `undefined`, `null`, or `none`. Because of this, each type has a default value. If you declare a variable without assigning a value, it will instead have the default value for that type. This property can lead to some tricky bugs until you get used to it.
+
+```solidity
+uint defaultValue;
+uint explicitValue = 0;
+
+// (defaultValue == explicitValue) <-- true
+```
+
+Types can be cast from one type to another, but not as freely as you may expect. For example, to convert a `uint256` into a `int8`, you need to cast twice:
+
+```solidity
+uint256 first = 1;
+int8 second = int8(int256(first));
+```
+
+
+Overflow/underflow protection (described below), does not provide protection when casting.
+
+```solidity
+uint256 first = 256;
+int8 second = int8(int256(first)); // <- The value stored in second is 0
+```
+
+
+
+## Boolean
+
+[Booleans] can have a value of `true` or `false`. Solidity does not have the concept of _truthy_ or _falsey_, and non-boolean values cannot be cast to bools by design. The short conversation in this [issue] explains why, and explains the philosophy why.
+
+### Logical Operators
+
+Standard logical operators (`!`, `&&`, `||`, `==`, `!=`) apply to booleans. Short-circuiting rules do apply, which can sometimes be used for gas savings since if the first operator in an `&&` is `false` or `||` is `true`, the second will not be evaluated. For example, the following code will execute without an error, despite the divide by zero in the second statement.
+
+```solidity
+// Bad code for example. Do not use.
+uint divisor = 0;
+if(1 < 2 || 1 / divisor > 0) {
+ // Do something...
+}
+```
+
+You cannot use any variant of `>` or `<` with booleans, because they cannot be implicitly or explicitly cast to a type that uses those operators.
+
+---
+
+## Numbers
+
+Solidity has a number of types for signed and unsigned [integers], which are not ignored as much as they are in other languages, due to potential gas-savings when storing smaller numbers. Support for [fixed point numbers] is under development, but is not fully implemented as of version `0.8.17`.
+
+Floating point numbers are not supported and are not likely to be. Floating precision includes an inherent element of ambiguity that doesn't work for explicit environments like blockchains.
+
+### Min, Max, and Overflow
+
+Minimum and maximum values for each type can be accessed with `type().min` and `type().max`. For example, `type(uint).min` is **0**, and `type(uint).max` is equal to **2^256-1**.
+
+An overflow or underflow will cause a transaction to _revert_, unless it occurs in a code block that is marked as [unchecked].
+
+### `uint` vs. `int`
+
+In Solidity, it is common practice to favor `uint` over `int` when it is known that a value will never (or should never) be below zero. This practice helps you write more secure code by requiring you to declare whether or not a given value should be allowed to be negative. Use `uint` for values that should not, such as array indexes, account balances, etc. and `int` for a value that does **need** to be negative.
+
+### Integer Variants
+
+Smaller and larger variants of integers exist in many languages but have fallen out of favor in many instances, in part because memory and storage are relatively cheap. Solidity supports sizes in steps of eight from `uint8` to `uint256`, and the same for `int`.
+
+Smaller sized integers are used to optimize gas usage in storage operations, but there is a cost. The EVM operates with 256 bit words, so operations involving smaller data types must be cast first, which costs gas.
+
+`uint` is an alias for `uint256` and can be considered the default.
+
+### Operators
+
+Comparisons (`<=`, `<`, `==`, `!=`, `>=`, `>`) and arithmetic (`+`, `-`, `*`, `/`, `%`, `**`) operators are present and work as expected. You can also use bit and shift operators.
+
+`uint` and `int` variants can be compared directly, such as `uint8` and `uint256`, but you must cast one value to compare a `uint` to an `int`.
+
+```solidity
+uint first = 1;
+int8 second = 1;
+
+if(first == uint8(second)) {
+ // Do something...
+}
+```
+
+---
+
+## Addresses
+
+The [address] type is a relatively unique type representing a wallet or contract address. It holds a 20-byte value, similar to the one we explored when you deployed your _Hello World_ contract in _Remix_. `address payable` is a variant of `address` that allows you to use the `transfer` and `send` methods. This distinction helps prevent sending Ether, or other tokens, to a contract that is not designed to receive it. If that were to happen, the Ether would be lost.
+
+Addresses are **not** strings and do not need quotes when represented literally, but conversions from `bytes20` and `uint160` are allowed.
+
+```solidity
+address existingWallet = 0xd9145CCE52D386f254917e481eB44e9943F39138;
+```
+
+### Members of Addresses
+
+Addresses contain a number of functions. `balance` returns the balance of an address, and `transfer`, mentioned above, can be used to send `ether`.
+
+```solidity
+function getBalance(address _address) public view returns(uint) {
+ return _address.balance;
+}
+```
+
+Later on, you'll learn about `call`, `delegatecall`, and `staticcall`, which can be used to call functions deployed in other contracts.
+
+---
+
+## Contracts
+
+When you declare a [contract], you are defining a type. This type can be used to instantiate one contract as a local variable inside a second contract, allowing the second to interact with the first.
+
+---
+
+## Byte Arrays and Strings
+
+[Byte arrays] come as both fixed-size and dynamically-sized. They hold a sequence of bytes. Arrays are a little more complicated than in other languages and will be covered in-depth later.
+
+### Strings
+
+Strings are arrays in Solidity, not a type. You cannot concat them with `+`, but as of _0.8.12_, you can use `string.concat(first, second)`. They are limited to printable characters and escaped characters. Casting other data types to `string` is at best tricky, and sometimes impossible.
+
+Generally speaking, you should be deliberate when working with strings inside of a smart contract. Don't be afraid to use them when appropriate, but if possible, craft and display messages on the front end rather than spending gas to assemble them on the back end.
+
+---
+
+## Enums
+
+[Enums] allow you to apply human-readable labels to a list of unsigned integers.
+
+```solidity
+enum Flavors { Vanilla, Chocolate, Strawberry, Coffee }
+
+Flavors chosenFlavor = Flavors.Coffee;
+```
+
+Enums can be explicitly cast to and from `uint`, but not implicitly. They are limited to 256 members.
+
+---
+
+## Constant and Immutable
+
+The [constant and immutable] keywords allow you to declare variables that cannot be changed. Both result in gas savings because the compiler does not need to reserve a storage slot for these values.
+
+As of _0.8.17_, `constant` and `immutable` are not fully implemented. Both are supported on [value types], and `constant` can also be used with strings.
+
+### Constant
+
+Constants can be declared at the file level, or at the contract level. In Solidity, modifiers come after the type declaration. You must initialize a value when declaring a constant. Convention is to use SCREAMING_SNAKE_CASE for constants.
+
+```solidity
+uint constant NUMBER_OF_TEAMS = 10;
+
+contract Cars {
+ uint constant NUMBER_OF_CARS = 20;
+}
+```
+
+At compilation, the compiler replaces every instance of the constant variable with its literal value.
+
+### Immutable
+
+The immutable keyword is used to declare variables that are set once within the constructor, which are then never changed:
+
+```solidity
+contract Season {
+ immutable numberOfRaces;
+
+ constructor(uint _numberOfRaces) {
+ numberOfRaces = _numberOfRaces;
+ }
+}
+```
+
+---
+
+## Conclusion
+
+You've learned the usage and some of the unique quirks of common variable types in Solidity. You've seen how overflow and underflow are handled and how that behavior can be overridden. You've learned why unsigned integers are used more commonly than in other languages, why floats are not present, and have been introduced to some of the quirks of working with strings. Finally, you've been introduced to the address and contract data types.
+
+---
+
+[types]: https://docs.soliditylang.org/en/v0.8.17/types.html
+[Booleans]: https://docs.soliditylang.org/en/v0.8.17/types.html#booleans
+[issue]: https://github.com/ethereum/solidity/issues/1200
+[integers]: https://docs.soliditylang.org/en/v0.8.17/types.html#integers
+[fixed point numbers]: https://docs.soliditylang.org/en/v0.8.17/types.html#fixed-point-numbers
+[unchecked]: https://docs.soliditylang.org/en/v0.8.17/control-structures.html#unchecked
+[address]: https://docs.soliditylang.org/en/v0.8.17/types.html#address
+[contract]: https://docs.soliditylang.org/en/v0.8.17/types.html#contract-types
+[Byte arrays]: https://docs.soliditylang.org/en/v0.8.17/types.html#fixed-size-byte-arrays
+[Enums]: https://docs.soliditylang.org/en/v0.8.17/types.html#enums
+[constant and immutable]: https://docs.soliditylang.org/en/v0.8.17/contracts.html?constant-and-immutable-state-variables#constant-and-immutable-state-variables
+[value types]: https://docs.soliditylang.org/en/v0.8.17/types.html#value-types
diff --git a/docs/pages/learn/contracts-and-basic-functions/hello-world-step-by-step.mdx b/_pages/learn/contracts-and-basic-functions/hello-world-step-by-step.mdx
similarity index 100%
rename from docs/pages/learn/contracts-and-basic-functions/hello-world-step-by-step.mdx
rename to _pages/learn/contracts-and-basic-functions/hello-world-step-by-step.mdx
diff --git a/_pages/learn/contracts-and-basic-functions/intro-to-contracts-vid.mdx b/_pages/learn/contracts-and-basic-functions/intro-to-contracts-vid.mdx
new file mode 100644
index 00000000..af6cff63
--- /dev/null
+++ b/_pages/learn/contracts-and-basic-functions/intro-to-contracts-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Introduction to Contracts
+description: Learn about the core structure of EVM programs.
+hide_table_of_contents: false
+---
+
+# Introduction to Contracts
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/control-structures/control-structures-exercise.mdx b/_pages/learn/control-structures/control-structures-exercise.mdx
new file mode 100644
index 00000000..2b88a536
--- /dev/null
+++ b/_pages/learn/control-structures/control-structures-exercise.mdx
@@ -0,0 +1,54 @@
+---
+title: Control Structures Exercise
+description: Exercise - Demonstrate your knowledge of control structures.
+hide_table_of_contents: false
+---
+
+# Control Structures Exercise
+
+Create a contract that adheres to the following specifications:
+
+---
+
+## Contract
+
+Create a single contract called `ControlStructures`. It should not inherit from any other contracts and does not need a constructor. It should have the following functions:
+
+### Smart Contract FizzBuzz
+
+Create a function called `fizzBuzz` that accepts a `uint` called `_number` and returns a `string memory`. The function should return:
+
+- "Fizz" if the `_number` is divisible by 3
+- "Buzz" if the `_number` is divisible by 5
+- "FizzBuzz" if the `_number` is divisible by 3 and 5
+- "Splat" if none of the above conditions are true
+
+### Do Not Disturb
+
+Create a function called `doNotDisturb` that accepts a `uint` called `_time`, and returns a `string memory`. It should adhere to the following properties:
+
+- If `_time` is greater than or equal to 2400, trigger a `panic`
+- If `_time` is greater than 2200 or less than 800, `revert` with a custom error of `AfterHours`, and include the time provided
+- If `_time` is between `1200` and `1259`, `revert` with a string message "At lunch!"
+- If `_time` is between 800 and 1199, return "Morning!"
+- If `_time` is between 1300 and 1799, return "Afternoon!"
+- If `_time` is between 1800 and 2200, return "Evening!"
+
+---
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
diff --git a/_pages/learn/control-structures/control-structures.mdx b/_pages/learn/control-structures/control-structures.mdx
new file mode 100644
index 00000000..481e69a3
--- /dev/null
+++ b/_pages/learn/control-structures/control-structures.mdx
@@ -0,0 +1,233 @@
+---
+title: Control Structures
+description: Learn how to control code flow in Solidity.
+hide_table_of_contents: false
+---
+
+# Control Structures
+
+Solidity supports many familiar control structures, but these come with additional restrictions and considerations due to the cost of gas and the necessity of setting a maximum amount of gas that can be spent in a given transaction.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Control code flow with `if`, `else`, `while`, and `for`
+- List the unique constraints for control flow in Solidity
+- Utilize `require` to write a function that can only be used when a variable is set to `true`
+- Write a `revert` statement to abort execution of a function in a specific state
+- Utilize `error` to control flow more efficiently than with `require`
+
+---
+
+## Control Structures
+
+Solidity supports the basic conditional and iterative [control structures] found in other curly bracket languages, but it **does not** support more advanced statements such as `switch`, `forEach`, `in`, `of`, etc.
+
+Solidity does support `try`/`catch`, but only for calls to other contracts.
+
+
+[Yul] is an intermediate-level language that can be embedded in Solidity contracts and is documented within the docs for Solidity. Yul **does** contain the `switch` statement, which can confuse search results.
+
+
+
+### Conditional Control Structure Examples
+
+The `if`, `else if`, and `else`, statements work as expected. Curly brackets may be omitted for single-line bodies, but we recommend avoiding this as it is less explicit.
+
+```solidity
+function ConditionalExample(uint _number) external pure returns (string memory) {
+ if(_number == 0) {
+ return "The number is zero.";
+ } else if(_number % 2 == 0) {
+ return "The number is even and greater than zero.";
+ } else {
+ return "The number is odd and is greater than zero.";
+ }
+}
+```
+
+### Iterative Control Structures
+
+The `while`, `for`, and `do`, keywords function the same as in other languages. You can use `continue` to skip the rest of a loop and start the next iteration. `break` will terminate execution of the loop, and you can use `return` to exit the function and return a value at any point.
+
+
+You can use `console.log` by importing `import "hardhat/console.sol";`. Doing so will require you to mark otherwise `pure` contracts as `view`.
+
+
+
+```solidity
+uint times; // Default value is 0!
+for(uint i = 0; i <= times; i++) {
+ console.log(i);
+}
+
+uint timesWithContinue;
+for(uint i = 0; i <= timesWithContinue; i++) {
+ if(i % 2 == 1) {
+ continue;
+ }
+ console.log(i);
+}
+
+uint timesWithBreak;
+for(uint i = 0; i <= timesWithBreak; i++) {
+ // Always stop at 7
+ if(i == 7) {
+ break;
+ }
+ console.log(i);
+}
+
+uint stopAt = 10;
+while(stopAt <= 10) {
+ console.log(i);
+ stopAt++;
+}
+
+uint doFor = 10;
+do {
+ console.log(i);
+ doFor++;
+} while(doFor <= 10);
+```
+
+---
+
+## Error Handling
+
+Solidity contains a set of relatively unique, built-in functions and keywords to handle [errors]. They ensure certain requirements are met, and completely abort all execution of the function and revert any state changes that occurred during function execution. You can use these functions to help protect the security of your contracts and limit their execution.
+
+The approach may seem different than in other environments. If an error occurs partly through a high-stakes transaction such as transferring millions of dollars of tokens, you **do not** want execution to carry on, partially complete, or swallow any errors.
+
+### Revert and Error
+
+The `revert` keyword halts and reverses execution. It must be paired with a custom `error`. Revert should be used to prevent operations that are logically valid, but should not be allowed for business reasons. It is **not** a bug if a `revert` is triggered. Examples where `revert` and `error` would be used to control operations include:
+
+- Allowing only certain senders to access functionality
+- Preventing the withdrawal of a deposit before a certain date
+- Allowing inputs under certain state conditions and denying them under others
+
+Custom `error`s can be declared without parameters, but they are much more useful if you include them:
+
+```solidity
+error OddNumberSubmitted(uint _first, uint _second);
+function onlyAddEvenNumbers(uint _first, uint _second) public pure returns (uint) {
+ if(_first % 2 != 0 || _second % 2 != 0) {
+ revert OddNumberSubmitted(_first, _second);
+ }
+ return _first + _second;
+}
+```
+
+When triggered, the `error` provides the values in the parameters provided. This information is very useful when debugging, and/or to transmit information to the front end to share what has happened with the user:
+
+```text
+call to HelloWorld.onlyAddEvenNumbers errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Error provided by the contract:
+OddNumberSubmitted
+Parameters:
+{
+ "_first": {
+ "value": "1"
+ },
+ "_second": {
+ "value": "2"
+ }
+}
+Debug the transaction to get more information.
+```
+
+You'll also encounter `revert` used as a function, returning a string error. This legacy pattern has been retained to maintain compatibility with older contracts:
+
+```solidity
+function oldRevertAddEvenNumbers(uint _first, uint _second) public pure returns (uint) {
+ if(_first % 2 != 0 || _second % 2 != 0) {
+ // Legacy use of revert, do not use
+ revert("One of the numbers is odd");
+ }
+ return _first + _second;
+}
+```
+
+The error provided is less helpful:
+
+```text
+call to HelloWorld.oldRevertAddEvenNumbers errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+The reason provided by the contract: "One of the numbers is odd".
+Debug the transaction to get more information.
+```
+
+### Require
+
+The `require` function is falling out of favor because it uses more gas than the pattern above. You should still become familiar with it because it is present in innumerable contracts, tutorials, and examples.
+
+`require` takes a logical condition and a string error as arguments. It is more gas efficient to separate logical statements if they are not interdependent. In other words, don't use `&&` or `||` in a `require` if you can avoid it.
+
+For example:
+
+```solidity
+function requireAddEvenNumbers(uint _first, uint _second) public pure returns (uint) {
+ // Legacy pattern, do not use
+ require(_first % 2 == 0, "First number is not even");
+ require(_second % 2 == 0, "Second number is not even");
+
+ return _first + _second;
+}
+```
+
+The output error message will be the first one that fails. If you were to submit `1`, and `3` to this function, the error will only contain the first message:
+
+```test
+call to HelloWorld.requireAddEvenNumbers errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+The reason provided by the contract: "First number is not even".
+Debug the transaction to get more information.
+```
+
+### Assert and Panic
+
+The `assert` keyword throws a `panic` error if triggered. A `panic` is the same type of error that is thrown if you try to divide by zero or access an array out-of-bounds. It is used for testing internal errors and should never be triggered by normal operations, even with flawed input. You have a bug that should be resolved if an assert throws an exception:
+
+```solidity
+function ProcessEvenNumber(uint _validatedInput) public pure {
+ // If assert triggers, input validation has failed. This should never
+ // happen!
+ assert(_validatedInput % 2 == 0);
+ // Do something...
+}
+```
+
+The output here isn't as helpful, so you may wish to use one of the patterns above instead.
+
+```text
+call to HelloWorld.ProcessEvenNumber errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Note: The called function should be payable if you send value and the value you send should be less than your current balance.
+Debug the transaction to get more information.
+```
+
+---
+
+## Conclusion
+
+In this lesson, you've learned how to control code flow with standard conditional and iterative operators. You've also learned about the unique keywords Solidity uses to generate errors and reset changes if one of them has been triggered. You've been exposed to both newer and legacy methods of writing errors, and learned the difference between `assert` and `require`.
+
+
+
+[switch]: https://docs.soliditylang.org/en/v0.8.17/yul.html?#switch
+[yul]: https://docs.soliditylang.org/en/v0.8.17/yul.html
+[control structures]: https://docs.soliditylang.org/en/v0.8.17/control-structures.html
+[errors]: https://docs.soliditylang.org/en/v0.8.17/control-structures.html#error-handling-assert-require-revert-and-exceptions
diff --git a/_pages/learn/control-structures/loops-vid.mdx b/_pages/learn/control-structures/loops-vid.mdx
new file mode 100644
index 00000000..f49457f9
--- /dev/null
+++ b/_pages/learn/control-structures/loops-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Loops
+description: Explore loops in Solidity.
+hide_table_of_contents: false
+---
+
+# Loops
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/control-structures/require-revert-error-vid.mdx b/_pages/learn/control-structures/require-revert-error-vid.mdx
new file mode 100644
index 00000000..3c8ec4c0
--- /dev/null
+++ b/_pages/learn/control-structures/require-revert-error-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Require, Revert, and Error
+description: Handle errors in Solidity.
+hide_table_of_contents: false
+---
+# Require, Revert, and Error
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/control-structures/standard-control-structures-vid.mdx b/_pages/learn/control-structures/standard-control-structures-vid.mdx
new file mode 100644
index 00000000..47357a33
--- /dev/null
+++ b/_pages/learn/control-structures/standard-control-structures-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: If, Else, and Else If
+description: Learn how to control your code.
+hide_table_of_contents: false
+---
+
+# If, Else, and Else If
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/deployment-to-testnet/contract-verification-sbs.mdx b/_pages/learn/deployment-to-testnet/contract-verification-sbs.mdx
new file mode 100644
index 00000000..ff130cd5
--- /dev/null
+++ b/_pages/learn/deployment-to-testnet/contract-verification-sbs.mdx
@@ -0,0 +1,75 @@
+---
+title: Contract Verification
+description: Verify your contract and interact with it.
+hide_table_of_contents: false
+---
+
+# Contract Verification
+Once your contract is deployed, you can verify it using a number of popular services. Doing so will let your users have confidence that your contract does what you claim, and will allow you to interact with it using a similar interface to what you used in Remix.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Verify a contract on the Base Sepolia testnet and interact with it in [BaseScan]
+
+---
+
+### Verify the Contract
+
+Make sure you still have the address of the contract you deployed in the last article copied to the clipboard.
+
+You can interact with your deployed contract using Remix, the same as before, but it's also possible to interact with it through BaseScan. Paste your address in the search field to find it.
+
+On this page, you can review the balance, information about, and all the transactions that have ever occurred with your contract.
+
+Click the _Contract_ tab in the main panel. At the top is a message asking you to _Verify and Publish_ your contract source code.
+
+
+
+Verifying your contract maps the names of your functions and variables to the compiled byte code, which makes it possible to interact with the contract using a human-readable interface.
+
+Click the link. Your contract address is already entered.
+
+Under _Please select Compiler Type_ choose Solidity (Single file)
+
+For _Please Select Compiler Version_ select the version matching the `pragma` at the top of your source file. Our examples are currently using _v0.8.17+commit.8df45f5f_.
+
+For _Please select Open Source License Type_ pick the license that matches what you selected for your contract as the `SPDX-License-Identifier`. Pick _None_ if you followed the Solidity-recommended practice of using `UNLICENSED`.
+
+On the next page, copy and paste your source code in the window. Verify that you are not a robot, and click _Verify and Publish_. You should see a success message.
+
+
+
+Click the linked address to your contract to return to the contract page. You'll now see your code!
+
+
+If you have imports, you'll need to right-click on the name of the file and choose `Flatten`. Submit the newly generated `filename_flattened.sol` for verification.
+
+
+
+### Interact with the Contract
+
+You can now interact with your contract using BaseScan. Click the _Read Contract_ button. Both of your functions will be listed here and can be tested using the web interface.
+
+You won't have anything under _Write Contract_ because this contract doesn't have any functions that save data to state.
+
+---
+
+## Conclusion
+
+With your contracts verified, you can interact with them using online tools and your users can be secure that your code does what you claim.
+
+---
+
+
+
+[`sepolia.basescan.org`]: https://sepolia.basescan.org/
+[coinbase]: https://www.coinbase.com/wallet
+[faucet]: https://docs.base.org/chain/network-faucets
+[set up]:
+[coinbase settings]: https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings
+[BaseScan]: https://sepolia.basescan.org/
+[faucets on the web]: https://coinbase.com/faucets
diff --git a/_pages/learn/deployment-to-testnet/deployment-to-base-sepolia-sbs.mdx b/_pages/learn/deployment-to-testnet/deployment-to-base-sepolia-sbs.mdx
new file mode 100644
index 00000000..fda143a6
--- /dev/null
+++ b/_pages/learn/deployment-to-testnet/deployment-to-base-sepolia-sbs.mdx
@@ -0,0 +1,109 @@
+---
+title: Deployment to Base Sepolia
+description: Deploy your smart contract to a test network.
+hide_table_of_contents: false
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+# Deployment to Base Sepolia
+Remix contains a simulation of a blockchain that you can use to rapidly deploy and test your contracts. This simulation only exists within your browser so you can't share it with others, use external tools, or a front end to interact with it. However, you can also deploy to a variety of testnets from within Remix. Doing so will allow you to share your contract with others, at the cost of making it public.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Deploy a contract to the Base Sepolia testnet and interact with it in [BaseScan]
+
+---
+
+## Prepare for Deployment
+
+Testnets operate in a similar, **but not exactly the same** manner as the main networks they shadow. You need a wallet with the appropriate token to interact with them by deploying a new contract or calling functions in a deployed contract.
+
+### Set Up a Wallet
+
+If you already have a wallet set up **exclusively for development**, you can skip to the next section. Otherwise, now is the time to jump in!
+
+
+It is very dangerous to use a wallet with valuable assets for development. You could easily write code with a bug that transfers the wrong amount of the wrong token to the wrong address. Transactions cannot be reversed once sent!
+
+Be safe and use separate wallets for separate purposes.
+
+
+
+First, add the [Coinbase] or [Metamask] wallet to your browser, and then [set up] a new wallet. As a developer, you need to be doubly careful about the security of your wallet! Many apps grant special powers to the wallet address that is the owner of the contract, such as allowing the withdrawal of all the Ether that customers have paid to the contract or changing critical settings.
+
+Once you've completed the wallet setup, enable developer settings and turn on testnets ([Coinbase Settings], [Metamask Settings]).
+
+### Add Base Sepolia to your Wallet
+
+Use the [faucet] to add Base Sepolia ETH to your wallet. You can also ask Base personnel on Discord or other social media for some!
+
+### Get Testnet Ether
+
+Testnet tokens have no real value, but the supply is not unlimited. You can use a faucet to get a small amount of Sepolia Ether to pay gas fees for testing. Most faucets allow you to ask for a small amount each day, and some won't send you more if your balance is too high.
+
+You can find many faucets by searching, and it's good to keep a few bookmarked because they have a tendency to go down from time to time. Faucet providers are constantly combating bad actors and sometimes need to disable their faucets while doing so.
+
+You can also access the [faucets on the web].
+
+Once you have testnet Base Sepolia Ether, you can view your balance under the _Testnets_ tab in the Coinbase wallet or by selecting the testnet from the network dropdown in Metamask. Sadly, it's not actually worth the amount listed!
+
+
+
+---
+
+## Deploying to Testnet
+
+Once you have testnet Ether, you can deploy your BasicMath contract!
+
+### Selecting the Environment
+
+Open the _Deploy & Run Transactions_ tab. Under _Environment_, select _Injected Provider_. It will list _Coinbase_, _Metamask_, or any other wallet you have activated here.
+
+
+
+If that option is not available, you can add it by choosing `Customize this list...`
+
+
+
+The first time you do this, your wallet will ask you to confirm that you want to connect this app (Remix) to your wallet.
+
+Once you are connected, you'll see the name of the network below the _Environment_ dropdown.
+
+
+
+For Base Sepolia, you should see `Custom (84532) network`. The old network, Goerli, was `84531`. If you don't see the correct network, change the active network in your wallet.
+
+### Deploy the Contract
+
+Click the orange _Deploy_ button. Because it costs gas to deploy a contract, you'll be asked to review and confirm a transaction.
+
+
+
+
+Always carefully review all transactions, confirming the transaction cost, assets transferred, and network. As a developer, you'll get used to approving transactions regularly. Do the best you can to avoid getting into the habit of clicking _Confirm_ without reviewing the transaction carefully. If you feel pressured to _Confirm_ before you run out of time, it is almost certainly a scam.
+
+
+
+After you click the _Confirm_ button, return to Remix and wait for the transaction to deploy. Copy its address and navigate to [`sepolia.basescan.org`].
+
+## Conclusion
+
+You now have the power to put smart contracts on the blockchain! You've only deployed to a test network, but the process for real networks is exactly the same - just more expensive!
+
+---
+
+
+[`sepolia.basescan.org`]: https://sepolia.basescan.org/
+[coinbase]: https://www.coinbase.com/wallet
+[metamask]: https://metamask.io/
+[faucet]: https://docs.base.org/chain/network-faucets
+[set up]:
+[coinbase settings]: https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings
+[Metamask Settings]: https://support.metamask.io/hc/en-us/articles/13946422437147-How-to-view-testnets-in-MetaMask
+[BaseScan]: https://sepolia.basescan.org/
+[faucets on the web]: https://coinbase.com/faucets
diff --git a/_pages/learn/deployment-to-testnet/deployment-to-testnet-exercise.mdx b/_pages/learn/deployment-to-testnet/deployment-to-testnet-exercise.mdx
new file mode 100644
index 00000000..ea4ecb67
--- /dev/null
+++ b/_pages/learn/deployment-to-testnet/deployment-to-testnet-exercise.mdx
@@ -0,0 +1,37 @@
+---
+title: 'Deployment Exercise'
+description: Exercise - Deploy your basic math contract and earn an NFT.
+hide_table_of_contents: false
+---
+
+# Deployment Exercise
+
+You've already built and deployed your [Basic Math] contract for this exercise. Now it's time to submit the address and earn an NFT pin to commemorate your accomplishment!
+
+
+We're currently in beta, so you'll only need to pay testnet funds to submit your contract, but this means you'll be getting a testnet NFT.
+
+Stay tuned for updates!
+
+
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
+
+---
+
+[basic math]: /learn/contracts-and-basic-functions/basic-functions-exercise
diff --git a/_pages/learn/deployment-to-testnet/overview-of-test-networks-vid.mdx b/_pages/learn/deployment-to-testnet/overview-of-test-networks-vid.mdx
new file mode 100644
index 00000000..ea25a654
--- /dev/null
+++ b/_pages/learn/deployment-to-testnet/overview-of-test-networks-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Overview of Test Networks
+description: Learn about test networks.
+hide_table_of_contents: false
+---
+
+# Overview of Test Networks
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/deployment-to-testnet/test-networks.mdx b/_pages/learn/deployment-to-testnet/test-networks.mdx
similarity index 100%
rename from docs/pages/learn/deployment-to-testnet/test-networks.mdx
rename to _pages/learn/deployment-to-testnet/test-networks.mdx
diff --git a/docs/pages/learn/development-tools/overview.mdx b/_pages/learn/development-tools/overview.mdx
similarity index 100%
rename from docs/pages/learn/development-tools/overview.mdx
rename to _pages/learn/development-tools/overview.mdx
diff --git a/_pages/learn/erc-20-token/analyzing-erc-20-vid.mdx b/_pages/learn/erc-20-token/analyzing-erc-20-vid.mdx
new file mode 100644
index 00000000..e2e3a258
--- /dev/null
+++ b/_pages/learn/erc-20-token/analyzing-erc-20-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Analyzing the ERC-20 Standard
+description: Explore the ERC-20 standard.
+hide_table_of_contents: false
+---
+
+# Analyzing the ERC-20 Token
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/erc-20-token/erc-20-exercise.mdx b/_pages/learn/erc-20-token/erc-20-exercise.mdx
new file mode 100644
index 00000000..607add22
--- /dev/null
+++ b/_pages/learn/erc-20-token/erc-20-exercise.mdx
@@ -0,0 +1,122 @@
+---
+title: ERC-20 Tokens Exercise
+description: Exercise - Create your own ERC-20 token!
+hide_table_of_contents: false
+---
+
+# ERC-20 Tokens Exercise
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Create a contract called `WeightedVoting`. Add the following:
+
+- A `maxSupply` of 1,000,000
+- Errors for:
+ - `TokensClaimed`
+ - `AllTokensClaimed`
+ - `NoTokensHeld`
+ - `QuorumTooHigh`, returning the quorum amount proposed
+ - `AlreadyVoted`
+ - `VotingClosed`
+- A struct called `Issue` containing:
+ - An OpenZeppelin Enumerable Set storing addresses called `voters`
+ - A string `issueDesc`
+ - Storage for the number of `votesFor`, `votesAgainst`, `votesAbstain`, `totalVotes`, and `quorum`
+ - Bools storing if the issue is `passed` and `closed`
+
+
+The unit tests require this `struct` to be constructed with the variables in the order above.
+
+
+
+- An array of `Issue`s called `issues`
+- An `enum` for `Vote` containing:
+ - `AGAINST`
+ - `FOR`
+ - `ABSTAIN`
+- Anything else needed to complete the tasks
+
+Add the following functions.
+
+### Constructor
+
+Initialize the ERC-20 token and burn the zeroeth element of `issues`.
+
+### Claim
+
+Add a `public` function called `claim`. When called, so long as a number of tokens equalling the `maximumSupply` have not yet been distributed, any wallet _that has not made a claim previously_ should be able to claim 100 tokens. If a wallet tries to claim a second time, it should revert with `TokensClaimed`.
+
+Once all tokens have been claimed, this function should revert with an error `AllTokensClaimed`.
+
+
+In our simple token, we used `totalSupply` to mint our tokens up front. The ERC20 implementation we're using also tracks `totalSupply`, but does it differently.
+
+Review the docs and code comments to learn how.
+
+
+
+### Create Issue
+
+Implement an `external` function called `createIssue`. It should add a new `Issue` to `issues`, allowing the user to set the description of the issue, and `quorum` - which is how many votes are needed to close the issue.
+
+Only token holders are allowed to create issues, and issues cannot be created that require a `quorum` greater than the current total number of tokens.
+
+This function must return the index of the newly-created issue.
+
+
+One of the unit tests will break if you place your check for `quorum` before the check that the user holds a token. The test compares encoded error names, which are **not** human-readable. If you are getting `-> AssertionError: �s is not equal to �9�` or similar, this is likely the issue.
+
+
+
+### Get Issue
+
+Add an `external` function called `getIssue` that can return all of the data for the issue of the provided `_id`.
+
+`EnumerableSet` has a `mapping` underneath, so it can't be returned outside of the contract. You'll have to figure something else out.
+
+
+**Hint**
+
+The return type for this function should be a `struct` very similar to the one that stores the issues.
+
+
+### Vote
+
+Add a `public` function called `vote` that accepts an `_issueId` and the token holder's vote. The function should revert if the issue is closed, or the wallet has already voted on this issue.
+
+Holders must vote all of their tokens for, against, or abstaining from the issue. This amount should be added to the appropriate member of the issue and the total number of votes collected.
+
+If this vote takes the total number of votes to or above the `quorum` for that vote, then:
+
+- The issue should be set so that `closed` is true
+- If there are **more** votes for than against, set `passed` to `true`
+
+---
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+
+The contract specification contains actions that can only be performed once by a given address. As a result, the unit tests for a passing contract will only be successful the **first** time you test.
+
+**You may need to submit a fresh deployment to pass**
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
diff --git a/docs/pages/learn/erc-20-token/erc-20-standard.mdx b/_pages/learn/erc-20-token/erc-20-standard.mdx
similarity index 100%
rename from docs/pages/learn/erc-20-token/erc-20-standard.mdx
rename to _pages/learn/erc-20-token/erc-20-standard.mdx
diff --git a/_pages/learn/erc-20-token/erc-20-testing-vid.mdx b/_pages/learn/erc-20-token/erc-20-testing-vid.mdx
new file mode 100644
index 00000000..531321f9
--- /dev/null
+++ b/_pages/learn/erc-20-token/erc-20-testing-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: ERC-20 Testing
+description: Test the OpenZeppelin ERC-20 implementation.
+hide_table_of_contents: false
+---
+
+# ERC-20 Testing
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/erc-20-token/erc-20-token-sbs.mdx b/_pages/learn/erc-20-token/erc-20-token-sbs.mdx
new file mode 100644
index 00000000..107101cd
--- /dev/null
+++ b/_pages/learn/erc-20-token/erc-20-token-sbs.mdx
@@ -0,0 +1,144 @@
+---
+title: ERC-20 Implementation
+description: Implement your own ERC-20 token.
+hide_table_of_contents: false
+---
+
+# ERC-20 Implementation
+
+The ERC-20 is a standard that allows for the development of fungible tokens and helps sites and apps, such as exchanges, know how to find and display information about these tokens. You can leverage existing implementations, such as the one by [OpenZeppelin] to develop your own tokens.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Describe OpenZeppelin
+- Import the OpenZeppelin ERC-20 implementation
+- Describe the difference between the ERC-20 standard and OpenZeppelin's ERC20.sol
+- Build and deploy an ERC-20 compliant token
+
+---
+
+## Setting Up the Contract
+
+Create a new Solidity file, add the license and pragma, and import the ERC-20 implementation linked above.
+
+Add a contract called `MyERC20Token` that inherits from `ERC20`.
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.17;
+
+import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol";
+
+contract MyERC20Token is ERC20 {
+
+}
+```
+
+### Adding a Constructor
+
+Review the constructor on line 53 of the [OpenZeppelin] implementation. It requires strings for the name and symbol you wish to use for your token. They're using a slightly different naming convention by putting the `_` after the name of the parameters. Like any other function, you can pass variables of **any** name as long as they're the right type, so feel free to continue adding the `_` in front in your contract's constructor:
+
+```solidity
+constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {
+
+}
+```
+
+
+There is neither a governing body nor built-in programmatic rules preventing you, or anyone else, from using the same name and symbol as an already in-use token. Scammers often take advantage of this fact, and even well-meaning developers can cause confusion by not being careful here.
+
+
+
+That's it. You're done! Deploy and test, and you should see all of the functionality called for by the standard and provided by the OpenZeppelin implementation.
+
+
+
+Do some testing. You'll see that the `totalSupply` and all balances are zero.
+
+By default, the decimal for the token will be 18, which is the most common choice. Remember, there aren't decimal types yet, so 1.0 ETH is really a `uint` holding 1 \* 10\*\*18, or 1000000000000000000.
+
+---
+
+## ERC-20 Further Testing
+
+Line 251 of the [OpenZeppelin] implementation contains a `_mint` function, but it's internal. As a result, you'll need to figure out a minting mechanism and add it via your own contract.
+
+### Minting in the Constructor
+
+One method of using the `_mint` function is to create an initial supply of tokens in the constructor. Add a call to `_mint` that awards 1 full token to the contract creator. Remember, the decimal is 18. Minting literally `1` is creating a tiny speck of dust.
+
+```solidity
+constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {
+ _mint(msg.sender, 1 * 10**18);
+}
+```
+
+Redeploy. Without you needing to do anything, you should find that the `totalSupply` is now 1000000000000000000, as is the `balanceOf` the deploying address.
+
+You can also use this to mint to other users. Go ahead and add the second and third accounts:
+
+
+
+Reveal code
+
+```solidity
+constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {
+ _mint(msg.sender, 1 * 10**18);
+ _mint(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2, 1 * 10**18);
+ _mint(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db, 1 * 10**18);
+}
+```
+
+
+
+
+
+
+**Switch back** to the first account and redeploy. Test to confirm that each account has the appropriate amount of tokens.
+
+### Testing the Transfer Function
+
+Try using the `transfer` function to move tokens around.
+
+What happens if you try to burn a token by sending it to the zero address? Give it a try!
+
+You'll get an error, because protecting from burning is built into the `_transfer` function.
+
+```text
+transact to MyERC20Token.transfer pending ...
+transact to MyERC20Token.transfer errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Reason provided by the contract: "ERC20: transfer to the zero address".
+Debug the transaction to get more information.
+```
+
+### Testing the Transfer From Function
+
+You might have noticed that there's another function called `transferFrom`. What's that for? Check the documentation in the contract to find out!
+
+This function works with the `allowance` function to give the owner of one wallet permission to spend up to a specified amount of tokens owned by another. Exchanges can make use of this to allow a user to post tokens for sale at a given price without needing to take possession of them.
+
+---
+
+## ERC-20 Final Thoughts
+
+The world is still figuring out how to handle all of the new possibilities tokens provide. Old laws are being applied in new ways, and new laws are being written. Different jurisdictions are doing this in unique and sometimes conflicting ways.
+
+You should consult with a lawyer in your jurisdiction before releasing your own tokens.
+
+---
+
+## Conclusion
+
+In this lesson, you've learned how easy it is to create an ERC-20 compliant token by using the OpenZeppelin implementation. You've reviewed at least one method to mint an initial supply of tokens, and that it's up to you to figure out the best way to create your tokens and follow all relevant laws and regulations.
+
+---
+
+[OpenZeppelin]: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol
diff --git a/_pages/learn/erc-20-token/openzeppelin-erc-20-vid.mdx b/_pages/learn/erc-20-token/openzeppelin-erc-20-vid.mdx
new file mode 100644
index 00000000..ffbd1ecb
--- /dev/null
+++ b/_pages/learn/erc-20-token/openzeppelin-erc-20-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: OpenZeppelin ERC-20 Implementation
+description: Review a popular implementation of the ERC-20 standard.
+hide_table_of_contents: false
+---
+
+# OpenZeppelin ERC-20 Implementation
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/erc-721-token/erc-721-exercise.mdx b/_pages/learn/erc-721-token/erc-721-exercise.mdx
new file mode 100644
index 00000000..09958993
--- /dev/null
+++ b/_pages/learn/erc-721-token/erc-721-exercise.mdx
@@ -0,0 +1,84 @@
+---
+title: ERC-721 Tokens Exercise
+description: Exercise - Create your own NFT!
+hide_table_of_contents: false
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+# ERC-721 Tokens Exercise
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Create a contract called `HaikuNFT`. Add the following to the contract:
+
+- A `struct` called `Haiku` to store the `address` of the `author` and `line1`, `line2`, and `line3`
+- A public array to store these `haikus`
+- A public `mapping` to relate `sharedHaikus` from the `address` of the wallet shared with, to the id of the Haiku NFT shared
+- A public `counter` to use as the id and to track and share the total number of Haikus minted
+ - If 10 Haikus have been minted, the counter should be at 11, to serve as the next id
+ - Do **NOT** assign an id of 0 to a haiku
+- Other variables as necessary to complete the task
+
+Add the following functions.
+
+### Constructor
+
+As appropriate.
+
+### Mint Haiku
+
+Add an `external` function called `mintHaiku` that takes in the three lines of the poem. This function should mint an NFT for the minter and save their Haiku.
+
+Haikus must be **unique**! If any line in the Haiku has been used as any line of a previous Haiku, revert with `HaikuNotUnique()`.
+
+You **don't** have to count syllables, but it would be neat if you did! (No promises on whether or not we counted the same as you did)
+
+### Share Haiku
+
+Add a `public` function called `shareHaiku` that allows the owner of a Haiku NFT to share that Haiku with the designated `address` they are sending it `_to`. Doing so should add it to that address's entry in `sharedHaikus`.
+
+If the sender isn't the owner of the Haiku, instead revert with an error of `NotYourHaiku`. Include the id of the Haiku in the error.
+
+
+Remember, everything on the blockchain is public. This sharing functionality can be expanded for features similar to allowing an app user to display the selected shared haiku on their profile.
+
+It does nothing to prevent anyone and everyone from seeing or copy/pasting the haiku!
+
+
+
+### Get Your Shared Haikus
+
+Add a `public` function called `getMySharedHaikus`. When called, it should return an array containing all of the haikus shared with the caller.
+
+If there are no haikus shared with the caller's wallet, it should revert with a custom error of `NoHaikusShared`, with no arguments.
+
+---
+
+
+The contract specification contains actions that can only be performed once by a given address. As a result, the unit tests for a passing contract will only be successful the **first** time you test.
+
+**You may need to submit a fresh deployment to pass**
+
+
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](../deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
diff --git a/_pages/learn/erc-721-token/erc-721-on-opensea-vid.mdx b/_pages/learn/erc-721-token/erc-721-on-opensea-vid.mdx
new file mode 100644
index 00000000..1e69d5f8
--- /dev/null
+++ b/_pages/learn/erc-721-token/erc-721-on-opensea-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: ERC-721 Token On Opensea
+description: Learn how a popular marketplace interprets tokens.
+hide_table_of_contents: false
+---
+
+# ERC-721 Token On Opensea
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/erc-721-token/erc-721-sbs.mdx b/_pages/learn/erc-721-token/erc-721-sbs.mdx
new file mode 100644
index 00000000..120e960a
--- /dev/null
+++ b/_pages/learn/erc-721-token/erc-721-sbs.mdx
@@ -0,0 +1,313 @@
+---
+title: ERC-721 Token
+description: Build your own NFT based on the ERC-721 standard.
+hide_table_of_contents: false
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+# ERC-721 Token
+
+Punks, Apes, and birds of all kinds. You've heard about them, seen them, and may even be lucky enough to own a famous NFT. Or maybe you've just bought into a random collection and aren't sure what to do with your NFT. NFTs aren't really pictures, or anything else specific. They're a method of proving ownership of a digital asset. Anyone can right-click on a picture of a monkey and set it as their profile picture, but only the owner can use it with apps that utilize web3 ownership.
+
+The ERC-721 token standard is the underlying technical specification that not only makes digital ownership possible, it provides a standardized way for marketplaces, galleries, and other sites to know how to interact with these digital items.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Analyze the anatomy of an ERC-721 token
+- Compare and contrast the technical specifications of ERC-20 and ERC-721
+- Review the formal specification for ERC-721
+- Build and deploy an ERC-721 compliant token
+- Use an ERC-721 token to control ownership of another data structure
+
+---
+
+## Implementing the OpenZeppelin ERC-721 Token
+
+JPGs may be all the rage right now but in the future, the selfie you post on social media, a text message you send to your mother, and the +4 battleaxe you wield in your favorite MMO might all be NFTs.
+
+### Import and Setup
+
+Start by opening the [OpenZeppelin] ERC-721 in Github. Copy the link and use it to import the ERC-721 contract. Create your own contract, called `MyERC721`, that inherits from `ERC721Token`. Add a constructor that initializes the `_name` and `_symbol`.
+
+
+
+Reveal code
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.17;
+
+import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol";
+
+contract MyERC721Token is ERC721 {
+ constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {
+
+ }
+}
+```
+
+
+
+
+
+### Minting NFTs
+
+The minting function that is provided by OpenZeppelin, `_safeMint`, is `internal`. To use it to let your customers mint NFTs, you'll need to implement a function in your contract that calls the one in the imported contract.
+
+Before you can do that, you need a way to supply the two parameters needed for `_safeMint`:
+
+- `address to` - the owner of the new NFT
+- `uint256 tokenId` - the ID number for the new NFT
+
+The owner is easy, you can simply use `msg.sender` to grant ownership to the wallet doing the minting.
+
+ID is slightly more challenging. A common practice is to simply assign the total number of NFTs, including the one being minted, as the `tokenId`. Doing so is straightforward, makes it easier to find all of the NFTs within a collection, and helps lean in to the common community perception that lower-number NFTs are better, just like other limited-edition collectibles.
+
+
+Obfuscating certain information, such as customer IDs, is often considered a best practice. Doing so might make it harder for an attacker who has circumvented other security functions from getting access to more data. If `134` is a valid `customer_id`, it is likely that `135` is too. The same can't be said for `bfcb51bd-c04f-42d5-8116-3def754e8c32`.
+
+This practice is not as useful on the blockchain, because all information is public.
+
+
+
+To implement ID generation, simply add a `uint` called `counter` to storage and initialize it as 1, either at declaration or in the constructor.
+
+Now, you can add a function called `redeemNFT` that calls `safeMint` using the `msg.sender` and `counter`, and then increments the `counter`:
+
+
+
+Reveal code
+
+```solidity
+function redeemNFT() external {
+ _safeMint(msg.sender, counter);
+ counter++;
+}
+```
+
+
+
+
+
+
+As a programmer, you've probably gone through great pains to internalize the idea of zero-indexing. Arrays start at 0. The pixel in the top-left corner of your screen is located at 0, 0.
+
+As a result, you need to be very careful when working with Solidity because there isn't the concept of `undefined`, and "deleted" values return to their default value, which is 0 for numbers.
+
+To prevent security risks, you'll need to make sure that you never give an ID or array index of 0 to anything. Otherwise, attempting to delete a value, such as a `struct` member called `authorizedSellerID` might give the wallet address stored at index 0 access to that resource.
+
+
+
+Deploy and test. Be sure to:
+
+- Mint several NFTs
+- Transfer an NFT from one Remix account to another
+- Try to transfer an NFT to `0x0000000000000000000000000000000000000000`
+
+---
+
+## ERC-721 URIs
+
+The ERC-721 standard includes the option to define a [URI] associated with each NFT. These are intended to point to a `json` file following the _ERC721 Metadata JSON Schema_
+
+```json
+{
+ "title": "Asset Metadata",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Identifies the asset to which this NFT represents"
+ },
+ "description": {
+ "type": "string",
+ "description": "Describes the asset to which this NFT represents"
+ },
+ "image": {
+ "type": "string",
+ "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
+ }
+ }
+}
+```
+
+Note that they don't have to. In the OpenZeppelin implementation, the function that returns the `_baseURI` is `virtual` and must be overridden by an inheriting contract.
+
+```
+// OpenZeppelin ERC-721
+/**
+ * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
+ * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
+ * by default, can be overridden in child contracts.
+ */
+function _baseURI() internal view virtual returns (string memory) {
+ return "";
+}
+```
+
+The owner of the contract can therefore choose what the value is and when, how, or if it is changeable. For example, the [Bored Ape Yacht Club] contract has a function allowing the owner to set or change the \_baseURI, changing where the metadata is stored, and potentially what is in it.
+
+```solidity
+// From boredapeyachtclub.sol
+function setBaseURI(string memory baseURI) public onlyOwner {
+ _setBaseURI(baseURI);
+}
+```
+
+The metadata for [BAYC] is [stored on IPFS], but some projects even use centralized, web2 storage options!
+
+### NFT Switcheroo
+
+[Doodles] is another NFT collection that [uses IPFS] to store metadata. Let's modify our contract to swap metadata back and forth from one collection to the other.
+
+Start by saving the IPFS metadata bases as constants, at the contract level. Add an enum to enable selection between these two choices, and an instance of that enum.
+
+
+
+Reveal code
+
+```solidity
+ string constant BAYC = "https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/";
+ string constant DOODLES = "https://ipfs.io/ipfs/QmPMc4tcBsMqLRuCQtPmPe84bpSjrC3Ky7t3JWuHXYB4aS/";
+
+ enum NFTMetadata { BAYC, DOODLES }
+ NFTMetadata nftMetadata = NFTMetadata.BAYC;
+```
+
+
+
+
+
+Finally, add an override of `_baseURI` that returns the appropriate selection based on which collection is active, and a function to swap the URI.
+
+
+
+Reveal code
+
+```solidity
+function _baseURI() internal override view returns(string memory) {
+ if (nftMetadata == NFTMetadata.BAYC) {
+ return BAYC;
+ } else if (nftMetadata == NFTMetadata.DOODLES){
+ return DOODLES;
+ } else {
+ revert("Error...");
+ }
+}
+
+function switchURI() public {
+ // TODO: Limit to contract owner
+ nftMetadata = nftMetadata == NFTMetadata.BAYC ? NFTMetadata.DOODLES : NFTMetadata.BAYC;
+}
+```
+
+
+
+
+
+Deploy, mint some NFTs, and call `tokenURI` to find the information for token number 1. You should get:
+
+```text
+https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/1
+```
+
+This links to the metadata json file for the first Bored Ape:
+
+```json
+{
+ "image": "ipfs://QmPbxeGcXhYQQNgsC6a36dDyYUcHgMLnGKnF8pVFmGsvqi",
+ "attributes": [
+ {
+ "trait_type": "Mouth",
+ "value": "Grin"
+ },
+ {
+ "trait_type": "Clothes",
+ "value": "Vietnam Jacket"
+ },
+ {
+ "trait_type": "Background",
+ "value": "Orange"
+ },
+ {
+ "trait_type": "Eyes",
+ "value": "Blue Beams"
+ },
+ {
+ "trait_type": "Fur",
+ "value": "Robot"
+ }
+ ]
+}
+```
+
+IPFS links don't work natively directly in the browser, but you can see the image here:
+
+https://ipfs.io/ipfs/QmPbxeGcXhYQQNgsC6a36dDyYUcHgMLnGKnF8pVFmGsvqi/
+
+Now, call your `switchURI` function and then call `tokenURI` again for token 1.
+
+Now, you'll get a new link for metadata:
+
+```text
+https://ipfs.io/ipfs/QmPMc4tcBsMqLRuCQtPmPe84bpSjrC3Ky7t3JWuHXYB4aS/1
+```
+
+Which contains the metadata for Doodle 1 instead of BAYC 1:
+
+```json
+{
+ "image": "ipfs://QmTDxnzcvj2p3xBrKcGv1wxoyhAn2yzCQnZZ9LmFjReuH9",
+ "name": "Doodle #1",
+ "description": "A community-driven collectibles project featuring art by Burnt Toast. Doodles come in a joyful range of colors, traits and sizes with a collection size of 10,000. Each Doodle allows its owner to vote for experiences and activations paid for by the Doodles Community Treasury. Burnt Toast is the working alias for Scott Martin, a Canadian\u2013based illustrator, designer, animator and muralist.",
+ "attributes": [
+ {
+ "trait_type": "face",
+ "value": "holographic beard"
+ },
+ {
+ "trait_type": "hair",
+ "value": "white bucket cap"
+ },
+ {
+ "trait_type": "body",
+ "value": "purple sweater with satchel"
+ },
+ {
+ "trait_type": "background",
+ "value": "grey"
+ },
+ {
+ "trait_type": "head",
+ "value": "gradient 2"
+ }
+ ]
+}
+```
+
+Your robot ape is now a person with a rainbow beard!
+
+https://ipfs.io/ipfs/QmTDxnzcvj2p3xBrKcGv1wxoyhAn2yzCQnZZ9LmFjReuH9
+
+---
+
+## Conclusion
+
+In this lesson, you've learned how to use OpenZeppelin's ERC-721 implementation to create your own NFT contract. You've also learned how NFT metadata is stored, and that it is not necessarily immutable.
+
+---
+
+[OpenZeppelin]: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol
+[Coinbase NFT]: https://nft.coinbase.com/
+[URI]: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier
+[stored on IPFS]: https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
+[BAYC]: https://nft.coinbase.com/collection/ethereum/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d
+[CryptoPunks]: https://nft.coinbase.com/collection/ethereum/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb
+[Doodles]: https://nft.coinbase.com/collection/ethereum/0x8a90cab2b38dba80c64b7734e58ee1db38b8992e
+[uses IPFS]: https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
diff --git a/_pages/learn/erc-721-token/erc-721-standard-video.mdx b/_pages/learn/erc-721-token/erc-721-standard-video.mdx
new file mode 100644
index 00000000..d04942ed
--- /dev/null
+++ b/_pages/learn/erc-721-token/erc-721-standard-video.mdx
@@ -0,0 +1,11 @@
+---
+title: ERC-721 Token Standard
+description: Review the formal standard for the ERC-721 Token.
+hide_table_of_contents: false
+---
+
+# ERC-721 Token Standard
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/erc-721-token/erc-721-standard.mdx b/_pages/learn/erc-721-token/erc-721-standard.mdx
similarity index 100%
rename from docs/pages/learn/erc-721-token/erc-721-standard.mdx
rename to _pages/learn/erc-721-token/erc-721-standard.mdx
diff --git a/_pages/learn/erc-721-token/implementing-an-erc-721-vid.mdx b/_pages/learn/erc-721-token/implementing-an-erc-721-vid.mdx
new file mode 100644
index 00000000..a40b6999
--- /dev/null
+++ b/_pages/learn/erc-721-token/implementing-an-erc-721-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Implementing an ERC-721
+description: Deploy your own NFT.
+hide_table_of_contents: false
+---
+
+# Implementing an ERC-721
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/erc-721-token/openzeppelin-erc-721-vid.mdx b/_pages/learn/erc-721-token/openzeppelin-erc-721-vid.mdx
new file mode 100644
index 00000000..0df8fd00
--- /dev/null
+++ b/_pages/learn/erc-721-token/openzeppelin-erc-721-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: OpenZeppelin ERC-721 Implementation
+description: Review the ERC-721 implementation by OpenZeppelin.
+hide_table_of_contents: false
+---
+
+# OpenZeppelin ERC-721 Implementation
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/error-triage/error-triage-exercise-source.sol b/_pages/learn/error-triage/error-triage-exercise-source.sol
similarity index 100%
rename from docs/pages/learn/error-triage/error-triage-exercise-source.sol
rename to _pages/learn/error-triage/error-triage-exercise-source.sol
diff --git a/_pages/learn/error-triage/error-triage-exercise.mdx b/_pages/learn/error-triage/error-triage-exercise.mdx
new file mode 100644
index 00000000..4cf2f672
--- /dev/null
+++ b/_pages/learn/error-triage/error-triage-exercise.mdx
@@ -0,0 +1,94 @@
+---
+title: Error Triage Exercise
+description: Exercise - Demonstrate your debugging skill.
+hide_table_of_contents: false
+---
+
+# Error Triage Exercise
+
+Copy the starter code into a new file in Remix.
+
+Debug the existing functions in the provided contract.
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.17;
+
+contract ErrorTriageExercise {
+ /**
+ * Finds the difference between each uint with it's neighbor (a to b, b to c, etc.)
+ * and returns a uint array with the absolute integer difference of each pairing.
+ */
+ function diffWithNeighbor(
+ uint _a,
+ uint _b,
+ uint _c,
+ uint _d
+ ) public pure returns (uint[] memory) {
+ uint[] memory results = new uint[](3);
+
+ results[0] = _a - _b;
+ results[1] = _b - _c;
+ results[2] = _c - _d;
+
+ return results;
+ }
+
+ /**
+ * Changes the _base by the value of _modifier. Base is always >= 1000. Modifiers can be
+ * between positive and negative 100;
+ */
+ function applyModifier(
+ uint _base,
+ int _modifier
+ ) public pure returns (uint) {
+ return _base + _modifier;
+ }
+
+ /**
+ * Pop the last element from the supplied array, and return the popped
+ * value (unlike the built-in function)
+ */
+ uint[] arr;
+
+ function popWithReturn() public returns (uint) {
+ uint index = arr.length - 1;
+ delete arr[index];
+ return arr[index];
+ }
+
+ // The utility functions below are working as expected
+ function addToArr(uint _num) public {
+ arr.push(_num);
+ }
+
+ function getArr() public view returns (uint[] memory) {
+ return arr;
+ }
+
+ function resetArr() public {
+ delete arr;
+ }
+}
+
+```
+
+---
+
+## Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
diff --git a/_pages/learn/error-triage/error-triage-vid.mdx b/_pages/learn/error-triage/error-triage-vid.mdx
new file mode 100644
index 00000000..e13152d3
--- /dev/null
+++ b/_pages/learn/error-triage/error-triage-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Error Triage
+description: Learn to debug common errors.
+hide_table_of_contents: false
+---
+
+# Error Triage
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/error-triage/error-triage.mdx b/_pages/learn/error-triage/error-triage.mdx
new file mode 100644
index 00000000..bb881d01
--- /dev/null
+++ b/_pages/learn/error-triage/error-triage.mdx
@@ -0,0 +1,462 @@
+---
+title: Error Triage
+description: Learn how to identify and resolve common errors in Solidity.
+hide_table_of_contents: false
+---
+
+# Error Triage
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Debug common solidity errors including transaction reverted, out of gas, stack overflow, value overflow/underflow, index out of range, etc.
+
+---
+
+## Compiler Errors
+
+Compiler errors are manifold but almost always very easy to debug, since the error message usually tells you what is wrong and how to fix it.
+
+### Type Errors
+
+You will get a compiler error if you try to assign a literal to the wrong type.
+
+```solidity
+// Bad code example, do not use
+function compilerTypeError() public pure returns (uint) {
+ uint myNumber = "One";
+ return myNumber;
+}
+```
+
+```text
+from solidity:
+TypeError: Type literal_string "One" is not implicitly convertible to expected type uint256.
+ --> contracts/ErrorTriage.sol:8:9:
+ |
+8 | uint myNumber = "One";
+ | ^^^^^^^^^^^^^^^^^^^^^
+```
+
+Fix by correcting the type or value, as appropriate for your needs:
+
+
+
+Reveal code
+
+
+```solidity
+function compilerTypeErrorFixed() public pure returns (string) {
+ string myNumber = "One";
+ return myNumber;
+}
+```
+
+
+
+
+
+### Conversion Errors
+
+Conversion errors occur when you attempt to _implicitly_ convert one type to another. Solidity only allows this under very narrow circumstances where there is no possibility of ambiguous interpretation of the data.
+
+```solidity
+// Bad code example, do not use
+function compilerConversionError() public pure returns (uint) {
+ int8 first = 1;
+
+ return first;
+}
+```
+
+```text
+from solidity:
+TypeError: Return argument type int8 is not implicitly convertible to expected type (type of first return variable) uint256.
+ --> contracts/ErrorTriage.sol:15:16:
+ |
+15 | return first;
+ | ^^^^^
+```
+
+Fix by explicitly casting as necessary:
+
+
+
+Reveal code
+
+
+```solidity
+function compilerConversionErrorFixed() public pure returns (uint) {
+ int8 first = 1;
+
+ return uint(uint8(first));
+}
+```
+
+
+
+
+
+
+You'll commonly need to use multiple conversions to bridge from one type to another.
+
+
+
+### Operator Errors
+
+You cannot use operators between types as flexibly as you may be used to.
+
+```solidity
+// Bad code example, do not use
+function compilerOperatorError() public pure returns (uint) {
+ int8 first = 1;
+ uint256 second = 2;
+
+ uint sum = first + second;
+
+ return sum;
+}
+```
+
+Operator errors are often paired with a type error.
+
+```text
+from solidity:
+TypeError: Operator + not compatible with types int8 and uint256.
+ --> contracts/ErrorTriage.sol:22:20:
+ |
+22 | uint sum = first + second;
+ | ^^^^^^^^^^^^^^
+
+from solidity:
+TypeError: Type int8 is not implicitly convertible to expected type uint256.
+ --> contracts/ErrorTriage.sol:22:9:
+ |
+22 | uint sum = first + second;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+```
+
+Resolve by explicitly converting to the final type:
+
+
+
+Reveal code
+
+
+```
+function compilerOperatorErrorFixed() public pure returns (uint) {
+ int8 first = 1;
+ uint256 second = 2;
+
+ uint sum = uint(uint8(first)) + second;
+
+ return sum;
+}
+```
+
+
+
+
+
+### Stack Depth Limit
+
+The [EVM stack] has 1024 slots, but only the top 16 slots are accessible. As a result, you can only have fewer than 16 variables in scope at one time.
+
+
+Other items can also use up these slots. You are **not** guaranteed 15 slots, it can be lower.
+
+
+
+```solidity
+// Bad code example, do not use
+function stackDepthLimit() public pure returns (uint) {
+ uint first = 1;
+ uint second = 2;
+ uint third = 3;
+ uint fourth = 4;
+ uint fifth = 5;
+ uint sixth = 6;
+ uint seventh = 7;
+ uint eighth = 8;
+ uint ninth = 9;
+ uint tenth = 10;
+ uint eleventh = 11;
+ uint twelfth = 12;
+ uint thirteenth = 13;
+ uint fourteenth = 14;
+ uint fifteenth = 15;
+ uint sixteenth = 16;
+
+ return first +
+ second +
+ third +
+ fourth +
+ fifth +
+ sixth +
+ seventh +
+ eighth +
+ ninth +
+ tenth +
+ eleventh +
+ twelfth +
+ thirteenth +
+ fourteenth +
+ fifteenth +
+ sixteenth;
+ }
+```
+
+```text
+from solidity:
+CompilerError: Stack too deep. Try compiling with --via-ir (cli) or the equivalent viaIR: true (standard JSON) while enabling the optimizer. Otherwise, try removing local variables.
+ --> contracts/ErrorTriage.sol:92:17:
+ |
+92 | eighth +
+ | ^^^^^^
+```
+
+Resolve this error by breaking up large functions and separating operations into different levels of scope.
+
+
+
+Reveal code
+
+
+```solidity
+function stackDepthLimitFixed() public pure returns (uint) {
+ uint subtotalA;
+ {
+ uint first = 1;
+ uint second = 2;
+ uint third = 3;
+ uint fourth = 4;
+ uint fifth = 5;
+ uint sixth = 6;
+ uint seventh = 7;
+ uint eighth = 8;
+ subtotalA = first +
+ second +
+ third +
+ fourth +
+ fifth +
+ sixth +
+ seventh +
+ eighth;
+ }
+
+ uint subtotalB;
+ {
+ uint ninth = 9;
+ uint tenth = 10;
+ uint eleventh = 11;
+ uint twelfth = 12;
+ uint thirteenth = 13;
+ uint fourteenth = 14;
+ uint fifteenth = 15;
+ uint sixteenth = 16;
+ subtotalB = ninth +
+ tenth +
+ eleventh +
+ twelfth +
+ thirteenth +
+ fourteenth +
+ fifteenth +
+ sixteenth;
+ }
+
+ return subtotalA + subtotalB;
+}
+```
+
+
+
+
+
+---
+
+## Logical Errors
+
+Logical errors occur when your code is syntactically correct, but still results in a data state that is a violation of the rules of the language.
+
+A [panic] occurs when your code tries to do an illegal operation. These return with a very basic error code, which Remix unfortunately hides. However, it makes up for that annoyance by providing a very powerful debugger.
+
+
+The Remix VM doesn't behave exactly the same as true onchain operations, so note that these errors will not behave exactly the same if triggered while testing with Hardhat, or called from a front end.
+
+caution
+
+For each of these examples, copy them into Remix to explore with the debugger on your own.
+
+### Array Index Out-of-Bounds
+
+A panic will be triggered if you try to access an array at an invalid index.
+
+```solidity
+// Bad code example, do not use
+function badGetLastValue() public pure returns (uint) {
+ uint[4] memory arr = [uint(1), 2, 3, 4];
+
+ return arr[arr.length];
+}
+```
+
+Running this function will result in the following error in the console:
+
+```text
+call to ErrorTriage.badGetLastValue errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Note: The called function should be payable if you send value and the value you send should be less than your current balance.
+Debug the transaction to get more information.
+```
+
+Click the _Debug_ button to open the debugger.
+
+
+
+The debugger contains panels with information about variables in storage, memory, what's on the stack, and so on. You can also add breakpoints to lines of code to further help with debugging.
+
+One of the most useful features is the link near the top instructing you to _"Click here to jump where the call reverted."_
+
+Click that link and the debugger will jump to the point of failure, **and highlight the code that caused the panic.** Neat!
+
+
+
+You can find the specific error here, but it's difficult.
+
+Look in the _Memory_ panel. The first item at `0x0` has a hash starting with `0x4e487b71`. This code indicates a panic.
+
+The second item, at `0x20` has the error code of `32` hidden in it, which is for array out-of-bounds.
+
+
+
+It's sometimes better to just review the code first to see if the error is obvious.
+
+```solidity
+function badGetLastValueFixed() public pure returns (uint) {
+ uint[4] memory arr = [uint(1), 2, 3, 4];
+
+ return arr[arr.length-1];
+}
+```
+
+### Out of Gas
+
+The default settings for Remix make it difficult to trigger an out of gas error because the VM will often crash first. For this example, go to the _Deploy & Run Transactions_ tab and reduce the gas limit to **300000**.
+
+If you write code that can have an ambiguous execution time, it becomes very difficult to accurately estimate gas limits.
+
+In this example, each loop has a 1 in 1000 chance of ending.
+
+
+`block.timestamp` can be manipulated. **DO NOT** use this as a source of randomness if any value can be derived from one outcome over another!
+
+
+
+```solidity
+// Bad code example, do not use
+function badRandomLoop() public view returns (uint) {
+ uint seed = 0;
+ // DO NOT USE THIS METHOD FOR RANDOM NUMBERS!!! IT IS EASILY EXPLOITABLE!!!
+ while(uint(keccak256(abi.encodePacked(block.timestamp, seed))) % 1000 != 0) {
+ seed++;
+ // ...do something
+ }
+
+ return seed;
+}
+```
+
+Run this function a few times. Often, it will work just fine. Other times, an error appears:
+
+```text
+call to ErrorTriage.badLoop errored: VM error: out of gas.
+
+out of gas
+ The transaction ran out of gas. Please increase the Gas Limit.
+
+Debug the transaction to get more information.
+```
+
+The error message here is a bit misleading. You do **not** usually want to fix this by increasing the gas limit. If you're getting a gas error because the transaction didn't estimate for enough gas, it's better to refactor for better predictability.
+
+```solidity
+function badRandomLoopFixed() public view returns (uint) {
+ // DO NOT USE THIS METHOD FOR RANDOM NUMBERS!!! IT IS EASILY EXPLOITABLE!!!
+ uint times = uint(keccak256(abi.encodePacked(block.timestamp))) % 1000;
+
+ for(uint i = 0; i <= times; i++) {
+ // ...do something
+ }
+
+ return times;
+}
+```
+
+### Overflow or Underflow
+
+The `uint` type will _panic_ in the event of an overflow or underflow.
+
+```solidity
+function badSubtraction() public pure returns (uint) {
+ uint first = 1;
+ uint second = 2;
+ return first - second;
+}
+```
+
+As before, you can see the panic code and panic type in _memory_.
+
+
+
+In this case, the error type is `11`, for overflow/underflow outside of an `unchecked` block.
+
+Fix by changing your code to handle the expected range of values.
+
+
+
+Reveal code
+
+
+```solidity
+function badSubstractionFixed() public pure returns (int) {
+ int first = 1;
+ int second = 2;
+ return first - second;
+}
+```
+
+
+
+
+
+### Divide by Zero
+
+Divide by zero errors also trigger a panic, with a code of `12`.
+
+```solidity
+function badDivision() public pure returns (uint) {
+ uint first = 1;
+ uint second = 0;
+ return first / second;
+}
+```
+
+
+
+Don't divide by zero.
+
+---
+
+## Conclusion
+
+In this lesson, you reviewed the causes of and solutions for a number of compiler errors and logical errors that you may encounter.
+
+---
+
+[panic]: https://docs.soliditylang.org/en/v0.8.17/control-structures.html?#panic-via-assert-and-error-via-require
+[EVM stack]: https://docs.soliditylang.org/en/v0.8.17/introduction-to-smart-contracts.html#storage-memory-and-the-stack
diff --git a/docs/pages/learn/ethereum-virtual-machine/evm-diagram.mdx b/_pages/learn/ethereum-virtual-machine/evm-diagram.mdx
similarity index 100%
rename from docs/pages/learn/ethereum-virtual-machine/evm-diagram.mdx
rename to _pages/learn/ethereum-virtual-machine/evm-diagram.mdx
diff --git a/docs/pages/learn/etherscan/etherscan-sbs.mdx b/_pages/learn/etherscan/etherscan-sbs.mdx
similarity index 99%
rename from docs/pages/learn/etherscan/etherscan-sbs.mdx
rename to _pages/learn/etherscan/etherscan-sbs.mdx
index cc14c813..93bda321 100644
--- a/docs/pages/learn/etherscan/etherscan-sbs.mdx
+++ b/_pages/learn/etherscan/etherscan-sbs.mdx
@@ -98,11 +98,10 @@ After you connect, the following UI appears:
You can then call the functions you wish to write to.
-:::info
-
+
Be aware that you may need to have real Ethereum in case you want to write to a contract in Ethereum mainnet. Also, any logic that the smart contract defines will be respected. This means that if you try to write to a contract that verifies certain conditions during the transaction (e.g., a function where only the owner of the contract can write information), then you won't be able to execute the transaction if you are not the owner.
+
-:::
## Conclusion
diff --git a/docs/pages/learn/etherscan/etherscan-vid.mdx b/_pages/learn/etherscan/etherscan-vid.mdx
similarity index 79%
rename from docs/pages/learn/etherscan/etherscan-vid.mdx
rename to _pages/learn/etherscan/etherscan-vid.mdx
index 6c3c6458..b06f7964 100644
--- a/docs/pages/learn/etherscan/etherscan-vid.mdx
+++ b/_pages/learn/etherscan/etherscan-vid.mdx
@@ -6,6 +6,6 @@ hide_table_of_contents: false
# Etherscan
-import Video from '@/components/VideoPlayer.jsx'
+import { Video } from '/snippets/VideoPlayer.mdx';
diff --git a/_pages/learn/events/hardhat-events-sbs.mdx b/_pages/learn/events/hardhat-events-sbs.mdx
new file mode 100644
index 00000000..89815714
--- /dev/null
+++ b/_pages/learn/events/hardhat-events-sbs.mdx
@@ -0,0 +1,286 @@
+---
+title: Events
+description: Events in Solidity
+hide_table_of_contents: false
+---
+
+# Events
+
+In this article, you'll learn how events work in Solidity by reviewing some practical examples and common use cases of events.
+
+
+This tutorial has been moved as part of a reorganization! It assumes you are using Hardhat. Everything in this lesson will work with minor adjustments if you are working in Foundry or Remix.
+
+
+
+---
+
+## Objectives
+
+By the end of this lesson, you should be able to:
+
+- Write and trigger an event
+- List common uses of events
+- Understand events vs. smart contract storage
+
+---
+
+## Overview
+
+Understanding how Solidity events work is important in the world of smart contract development. Events provide a powerful way to create event-driven applications on the blockchain. They allow you to notify external parties, such as off-chain applications, user interfaces, and any entity that wants to listen for events of a particular contract.
+
+In this tutorial, you'll learn how to declare, trigger, and utilize events, gaining the knowledge necessary to enhance the functionality and user experience of your decentralized applications.
+
+## What are events?
+
+From the official solidity documentation, [events] are:
+
+> _...an abstraction on top of the EVM’s logging functionality. Applications can subscribe and listen to these events through the RPC interface of an Ethereum client._
+
+> _...when you call them, they cause the arguments to be stored in the transaction’s log – a special data structure in the blockchain. These logs are associated with the address of the contract that emitted them, are incorporated into the blockchain, and stay there as long as a block is accessible (forever as of now, but this might change in the future)._
+
+In other words, events are an abstraction that allow you to store a transaction's log information in the blockchain.
+
+## Your first solidity event
+
+Start by creating a first event in the `Lock.sol` contract that's included by default in Hardhat.
+
+The event is called `Created` and includes the address of the creator and the amount that was sent during the creation of the smart contract. Then, `emit` the event in the constructor:
+
+```solidity
+emit Created(msg.sender, msg.value);
+```
+
+The contract is:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+contract Lock {
+ uint public unlockTime;
+ address payable public owner;
+
+ event Created(address owner, uint amount);
+
+ constructor(uint _unlockTime) payable {
+ require(
+ block.timestamp < _unlockTime,
+ "Unlock time should be in the future"
+ );
+
+ unlockTime = _unlockTime;
+ owner = payable(msg.sender);
+
+ emit Created(msg.sender, msg.value);
+ }
+}
+```
+
+Events can be defined at the file level or as inheritable members of contracts (including interfaces). You can also define the event in an interface as:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+interface ILock {
+ event Created(address owner, uint amount);
+}
+
+contract Lock is ILock {
+ uint public unlockTime;
+ address payable public owner;
+
+ constructor(uint _unlockTime) payable {
+ require(
+ block.timestamp < _unlockTime,
+ "Unlock time should be in the future"
+ );
+
+ unlockTime = _unlockTime;
+ owner = payable(msg.sender);
+
+ emit Created(msg.sender, msg.value);
+ }
+}
+```
+
+You can test the event by simplifying the original test file with the following code:
+
+```solidity
+import {
+ time,
+} from "@nomicfoundation/hardhat-toolbox/network-helpers";
+import { ethers } from "hardhat";
+
+describe("Lock tests", function () {
+ describe("Deployment", function () {
+ it("Should set the right unlockTime", async function () {
+ const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
+ const ONE_GWEI = 1_000_000_000;
+
+ const lockedAmount = ONE_GWEI;
+ const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;
+
+ // Contracts are deployed using the first signer/account by default
+ const [owner] = await ethers.getSigners();
+
+ // But we do it explicit by using the owner signer
+ const LockFactory = await ethers.getContractFactory("Lock", owner);
+ const lock = await LockFactory.deploy(unlockTime, { value: lockedAmount });
+
+ const hash = await lock.deploymentTransaction()?.hash
+ const receipt = await ethers.provider.getTransactionReceipt(hash as string)
+
+ console.log("Sender Address", owner.address)
+ console.log("Receipt.logs", receipt?.logs)
+
+ const defaultDecoder = ethers.AbiCoder.defaultAbiCoder()
+ const decodedData = defaultDecoder.decode(['address', 'uint256'], receipt?.logs[0].data as string)
+ console.log("decodedData", decodedData)
+ });
+ });
+});
+```
+
+Notice that the previous code is logging the sender address and the logs coming from the transaction receipt. You are also decoding the `receipts.logs[0].data` field that contains the information emitted by the event but not in a human-readable way, since it is encoded. For that reason, you can use `AbiCoder` to decode the raw data.
+
+By running `npx hardhat test`, you should be able to see the following:
+
+```solidity
+ Lock tests
+ Deployment
+Sender Address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
+Receipt.logs [
+ Log {
+ provider: HardhatEthersProvider {
+ _hardhatProvider: [LazyInitializationProviderAdapter],
+ _networkName: 'hardhat',
+ _blockListeners: [],
+ _transactionHashListeners: Map(0) {},
+ _eventListeners: []
+ },
+ transactionHash: '0xad4ff104036f23096ea5ed165bff1c3e1bc0f53e375080f84bce4cc108c28cee',
+ blockHash: '0xb2117cfd2aa8493a451670acb0ce14228b06d17bf545cd7efad6791aeac83c05',
+ blockNumber: 1,
+ removed: undefined,
+ address: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
+ data: '0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000003b9aca00',
+ topics: [
+ '0x0ce3610e89a4bb9ec9359763f99110ed52a4abaea0b62028a1637e242ca2768b'
+ ],
+ index: 0,
+ transactionIndex: 0
+ }
+]
+decodedData Result(2) [ '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', 1000000000n ]
+ ✔ Should set the right unlockTime (1008ms)
+```
+
+Notice the value `f39fd6e51aad88f6f4ce6ab8827279cfffb92266` is encoded in the property data, which is the address of the sender.
+
+## Event topics
+
+Another important feature is that events can be indexed by adding the indexed attribute to the event declaration.
+
+For example, if you modify the interface with:
+
+```solidity
+interface ILock {
+ event Created(address indexed owner, uint amount);
+}
+```
+
+Then, if you run `npx hardhat test` again, an error may occur because the decoding assumes that the data field contains an `address` and a `uint256`. But by adding the indexed attribute, you are instructing that the events will be added to a special data structure known as "topics". Topics have some limitations, since the maximum indexed attributes can be up to three parameters and a topic can only hold a single word (32 bytes).
+
+You then need to modify the decoding line in the test file with the following:
+
+```solidity
+const decodedData = defaultDecoder.decode(['uint256'], receipt?.logs[0].data as string)
+```
+
+Then, you should be able to see the receipt as:
+
+```solidity
+ Lock tests
+ Deployment
+Sender Address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
+Receipt.logs [
+ Log {
+ provider: HardhatEthersProvider {
+ _hardhatProvider: [LazyInitializationProviderAdapter],
+ _networkName: 'hardhat',
+ _blockListeners: [],
+ _transactionHashListeners: Map(0) {},
+ _eventListeners: []
+ },
+ transactionHash: '0x0fd52fd72bca26879474d3e512fb812489111a6654473fd288c6e8ec0432e09d',
+ blockHash: '0x138f74df5637315099d31aedf5bf643cf95c2bb7ae923c21fcd7f0075cb55324',
+ blockNumber: 1,
+ removed: undefined,
+ address: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
+ data: '0x000000000000000000000000000000000000000000000000000000003b9aca00',
+ topics: [
+ '0x0ce3610e89a4bb9ec9359763f99110ed52a4abaea0b62028a1637e242ca2768b',
+ '0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266'
+ ],
+ index: 0,
+ transactionIndex: 0
+ }
+]
+decodedData Result(1) [ 1000000000n ]
+ ✔ Should set the right unlockTime (994ms)
+```
+
+Notice the topics property, which now contains the address of the sender: `f39fd6e51aad88f6f4ce6ab8827279cfffb92266`.
+
+## Common uses of events
+
+Solidity events have several common use cases, which are described in the following sections.
+
+### User notifications
+
+Events can be used to notify users or external systems about certain contract actions.
+
+### Logging
+
+Events are primarily used to log significant changes within the contract, providing a transparent and verifiable history of what has occurred.
+
+### Historical state reconstruction
+
+Events can be valuable for recreating the historical state of a contract. By capturing and analyzing emitted event logs, you can reconstruct past states, offering a transparent and auditable history of the contract's actions and changes.
+
+### Debugging and monitoring
+
+Events are essential for debugging and monitoring contract behavior, as they provide a way to observe what's happening on the blockchain.
+
+The ability to use events to recreate historical states provides an important auditing and transparency feature, allowing users and external parties to verify the contract's history and actions. While not a common use, it's a powerful capability that can be particularly useful in certain contexts.
+
+## Events vs. smart contract storage
+
+Although it is possible to rely on events to fully recreate the state of a particular contract, there are a few other options to consider.
+
+Existing services such as [The Graph] allow you to index and create GraphQL endpoints for your smart contracts and generate entities based on custom logic. However, you must pay for that service since you are adding an intermediate layer to your application. This has the following benefits, such as:
+
+- the ability to simply query one particular endpoint to get all the information you need
+- your users will pay less gas costs due to the minimization of storage usage in your contract
+
+But storing all of the information within the smart contract and relying fully on it to access data can create more complexity, since not all of the data is directly query-able. The benefits of this approach include:
+
+- your application requires only the smart contract address to access all of the required data
+- there are fewer dependencies involved, which makes this approach more crypto native in the sense that everything is in the blockchain (but, storing all the data in the blockchain will cause higher gas costs)
+
+As a smart contract developer, you must evaluate which options work best for you.
+
+## Conclusion
+
+In this lesson, you've learned the basics of Solidity events and their importance in Ethereum smart contract development. You now understand how to declare and trigger events, a few of their common use cases, and the difference between events and smart contract storage.
+
+Now that you have a solid grasp of events and their versatile applications, you can leverage them to build more sophisticated and interactive smart contracts that meet your specific needs, all while being mindful of the cost considerations.
+
+---
+
+## See also
+
+[events]: https://docs.soliditylang.org/en/latest/contracts.html#events
+[The Graph]: https://thegraph.com/
diff --git a/docs/pages/learn/exercise-contracts.mdx b/_pages/learn/exercise-contracts.mdx
similarity index 100%
rename from docs/pages/learn/exercise-contracts.mdx
rename to _pages/learn/exercise-contracts.mdx
diff --git a/docs/pages/learn/frontend-setup/building-an-onchain-app.mdx b/_pages/learn/frontend-setup/building-an-onchain-app.mdx
similarity index 98%
rename from docs/pages/learn/frontend-setup/building-an-onchain-app.mdx
rename to _pages/learn/frontend-setup/building-an-onchain-app.mdx
index 984d9832..99ddd07f 100644
--- a/docs/pages/learn/frontend-setup/building-an-onchain-app.mdx
+++ b/_pages/learn/frontend-setup/building-an-onchain-app.mdx
@@ -4,6 +4,8 @@ description: Learn step-by-step how to turn a regular template app into an oncha
hide_table_of_contents: false
---
+import { Danger } from "/snippets/danger.mdx";
+
# Building an Onchain App
While it's convenient and fast to start from a template, the template may not fit your needs. Whether you prefer a different stack, or have already started building the traditional web components of your app, it's common to need to manually add onchain libraries to get your app working.
@@ -39,11 +41,10 @@ This script will accept `.`, if you want to add the project to the root of a fol
- Use App Router?: Yes
- Customize the default import alias?: No
-:::info
-
+
The default Next.js script installs [Tailwind]. [RainbowKit]'s does not.
+
-:::
Run your app with `yarn dev` to make sure it generated correctly.
@@ -57,9 +58,10 @@ Start by installing the dependencies:
npm install @rainbow-me/rainbowkit wagmi viem@2.x @tanstack/react-query
```
-:::info
+
Onchain libraries and packages tend to require very current versions of Node. If you're not already using it, you may want to install [nvm].
-:::
+
+
## Adding Imports, Connectors, Config
@@ -67,11 +69,10 @@ In Next.js with the app router, the root of your app is found in `app/layout.tsx
You'll need to set up your providers in a second file, so that you can add `'use client';` to the top. Doing so forces this code to be run client side, which is necessary since your server won't have access to your users' wallet information.
-:::caution
-
+
You must configure these wrappers in a separate file. It will not work if you try to add them and `'use client';` directly in `layout.tsx`!
+
-:::
Add a new file in the `app` folder called `providers.tsx`.
@@ -90,29 +91,26 @@ import { base, baseSepolia } from 'wagmi/chains';
import { QueryClientProvider, QueryClient } from '@tanstack/react-query';
```
-:::caution
-
+
If you're adapting this guide to a different set of libraries or platforms, you may need to import `styles.css` differently. You'll know this is the case if you get ugly text at the bottom of the page instead of a nice modal when you click the connect button.
+
-:::
### Config
Now, you need to configure the chains, wallet connectors, and providers for your app. You'll use `getDefaultConfig` for now, to get started. See our guide on [Connecting to the Blockchain] for more information on blockchain providers.
-:::info
-
+
To take advantage of a more advanced set of options with [OnchainKit], see our tutorial on how to [Use the Coinbase Smart Wallet and EOAs with OnchainKit]. If you just want to customize the list of wallets in [RainbowKit], see our tutorial for [Coinbase Smart Wallet with RainbowKit].
+
-:::
You'll need a `projectId` from [Wallet Connect Cloud], which you can get for free on their site. Make sure to insert it in the appropriate place.
-:::danger
-
+
Remember, everything on the frontend is public! Be sure to configure the allowlist for your WalletConnect id!
+
-:::
```tsx
const config = getDefaultConfig({
diff --git a/docs/pages/learn/frontend-setup/overview.mdx b/_pages/learn/frontend-setup/overview.mdx
similarity index 100%
rename from docs/pages/learn/frontend-setup/overview.mdx
rename to _pages/learn/frontend-setup/overview.mdx
diff --git a/docs/pages/learn/frontend-setup/wallet-connectors.mdx b/_pages/learn/frontend-setup/wallet-connectors.mdx
similarity index 99%
rename from docs/pages/learn/frontend-setup/wallet-connectors.mdx
rename to _pages/learn/frontend-setup/wallet-connectors.mdx
index 5b3220f6..fa45223f 100644
--- a/docs/pages/learn/frontend-setup/wallet-connectors.mdx
+++ b/_pages/learn/frontend-setup/wallet-connectors.mdx
@@ -41,11 +41,10 @@ If you're just trying to get up and running as quickly as possible, you can use
yarn create @rainbow-me/rainbowkit
```
-:::info
-
+
The script doesn't accept `.` as a project name, so you'll want to run this script in your `src` directory, or wherever you keep your projects. It will create a folder with the same name as your project, and install the project files inside.
+
-:::
Once it's done, simply run the app with:
@@ -55,13 +54,12 @@ yarn run dev
Using the script is fast, but it does mean less choice. In this case, it builds the app on top of [Next.js], which is great if you want to use it, but not helpful if you prefer to work from a different framework, such as [Create React App], or [Remix] (the React framework, not the Solidity IDE). The script also doesn't help you if you want to add an onchain integration to an existing site.
-:::info
-
+
The Rainbowkit template has been updated to wagmi 2.X, but it does **not** use the Next.js app router. You'll need to install it manually if you wish to use the latest patterns.
The [Building an Onchain App] tutorial will show you how to do this!
+
-:::
### Coinbase Smart Wallet
diff --git a/_pages/learn/hardhat-deploy/deployment-vid.mdx b/_pages/learn/hardhat-deploy/deployment-vid.mdx
new file mode 100644
index 00000000..3e3527c5
--- /dev/null
+++ b/_pages/learn/hardhat-deploy/deployment-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Deployment
+description: Configure test networks.
+hide_table_of_contents: false
+---
+
+# Deployment
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/hardhat-deploy/hardhat-deploy-sbs.mdx b/_pages/learn/hardhat-deploy/hardhat-deploy-sbs.mdx
new file mode 100644
index 00000000..82f55fb8
--- /dev/null
+++ b/_pages/learn/hardhat-deploy/hardhat-deploy-sbs.mdx
@@ -0,0 +1,262 @@
+---
+title: Deploying Smart Contracts
+description: Deploy smart contracts with hardhat deploy and hardhat
+hide_table_of_contents: false
+---
+
+# Deploying Smart Contracts
+
+In this article, you'll learn how to deploy smart contracts to multiple Blockchain networks using Hardhat and Hardhat deploy.
+
+---
+
+## Objectives
+
+By the end of this lesson, you should be able to:
+
+- Deploy a smart contract to the Base Sepolia Testnet with hardhat-deploy
+- Deploy a smart contract to the Sepolia Testnet with hardhat-deploy
+- Use BaseScan to view a deployed smart contract
+
+---
+
+## Overview
+
+Hardhat capabilities enable developers to deploy smart contracts easily to any Blockchain by simply creating `tasks` or `scripts`. However, due to the Hardhat architecture that enables its extension by creating plugins, you can rely on existing solutions developed by the community.
+
+[Hardhat deploy](https://github.com/wighawag/hardhat-deploy) is a community-developed plugin that enables the deployment of your smart contracts in a simple way.
+
+## Setting up Hardhat deploy
+
+To install:
+
+1. Run `npm install -D hardhat-deploy`. Then, import hardhat-deploy in `hardhat.config.ts`:
+
+```tsx
+import 'hardhat-deploy';
+```
+
+2. Create a folder called deploy and inside it create a new file called `001_deploy_lock.ts`.
+
+3. Include the following:
+
+```tsx
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+import { DeployFunction } from 'hardhat-deploy/types';
+
+const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
+ // code here
+};
+export default func;
+```
+
+4. Modify the `tsconfig.json` file to look like:
+
+```json
+{
+ "compilerOptions": {
+ "target": "es2020",
+ "module": "commonjs",
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "resolveJsonModule": true
+ },
+ "include": ["./hardhat.config.ts", "./scripts", "./deploy", "./test"]
+}
+```
+
+5. Before implementing the deploy functionality, configure a deployer account in the `hardhat.config.ts` file. Hardhat deployment includes a way to name accounts in the config file.
+
+6. Run the following, which adds an alias to the account 0 of your environment:
+
+```tsx
+const config: HardhatUserConfig = {
+ solidity: '0.8.23',
+ namedAccounts: {
+ deployer: 0,
+ },
+};
+```
+
+7. Implement the deploy function by including the following in the `001_deploy_lock.ts` file:
+
+```tsx
+import { HardhatRuntimeEnvironment } from 'hardhat/types';
+import { DeployFunction } from 'hardhat-deploy/types';
+import { ethers } from 'hardhat';
+
+const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
+ const { deploy } = hre.deployments;
+ // We can now use deployer
+ const { deployer } = await hre.getNamedAccounts();
+
+ // The value we want to lock
+ const VALUE_LOCKED = hre.ethers.parseEther('0.01');
+
+ // The unlock time after deployment
+ const UNLOCK_TIME = 10000;
+
+ // We use ethers to get the current time stamp
+ const blockNumber = await ethers.provider.getBlockNumber();
+ const lastBlockTimeStamp = (await ethers.provider.getBlock(blockNumber))?.timestamp as number;
+
+ // We say we want to deploy our Lock contract using the deployer
+ // account and passing the value and arguments.
+ await deploy('Lock', {
+ from: deployer,
+ args: [lastBlockTimeStamp + UNLOCK_TIME],
+ value: VALUE_LOCKED.toString(),
+ });
+};
+
+export default func;
+
+// This tag will help us in the next section to trigger this deployment file programmatically
+func.tags = ['DeployAll'];
+```
+
+## Testing your deployment
+
+The easiest way to test your deployment is by modifying the test.
+
+Go to `Lock.ts` and include in the imports the following:
+
+```tsx
+import { ethers, deployments } from 'hardhat';
+```
+
+`deployments` will allow you to execute the deployment files from your test.
+
+Change the `before` function to look like the following:
+
+```tsx
+before(async () => {
+ lastBlockTimeStamp = await time.latest();
+
+ const signers = await ethers.getSigners();
+ ownerSigner = signers[0];
+ otherUserSigner = signers[1];
+
+ await deployments.fixture(['DeployAll']);
+ const lockDeployment = await deployments.get('Lock');
+
+ lockInstance = Lock__factory.connect(lockDeployment.address, ownerSigner);
+});
+```
+
+Notice how you execute `deployments.fixture` and pass a tag that matches the one you specified in the deployment file (`001_deploy_lock.ts`).
+
+The deployment file is then executed and you can then reuse that functionality and simply consume the address of the newly-deployed contract by using:
+
+```tsx
+const lockDeployment = await deployments.get('Lock');
+```
+
+Reuse `Lock__factory` but use the connect function and pass the address of the newly-created contract plus a signer. Then, run `npx hardhat test` and you should get the same result:
+
+```
+ Lock
+ ✔ should get the unlockTime value
+ ✔ should have the right ether balance
+ ✔ should have the right owner
+ ✔ shouldn't allow to withdraw before unlock time (51ms)
+ ✔ shouldn't allow to withdraw a non owner
+ ✔ should allow to withdraw an owner
+
+ 6 passing (2s)
+```
+
+## Deploying to a test network
+
+Deploying to a real test network involves configuring the network parameters in the hardhat config file. You need to include parameters such as:
+
+- The JSON RPC URL
+- The account you want to use
+- Real test ether or the native Blockchain token for gas costs
+
+Include the following in the `hardhat.config.ts` file:
+
+```tsx
+const config: HardhatUserConfig = {
+ solidity: '0.8.18',
+ namedAccounts: {
+ deployer: 0,
+ },
+ networks: {
+ base_sepolia: {
+ url: 'https://sepolia.base.org',
+ accounts: {
+ mnemonic: process.env.MNEMONIC ?? '',
+ },
+ },
+ sepolia: {
+ url: `https://eth-sepolia.g.alchemy.com/v2/${process.env.ALCHEMY_SEPOLIA_KEY ?? ''}`,
+ accounts: {
+ mnemonic: process.env.MNEMONIC ?? '',
+ },
+ },
+ },
+};
+```
+
+You've configured 2 networks:
+
+- base_sepolia
+- sepolia
+
+You also need to create a `.env` file with the following variables:
+
+```
+MNEMONIC=""
+ALCHEMY_SEPOLIA_KEY=
+```
+
+In order to ensure the environment variables are loaded, you need to install another package called `dotenv`:
+
+```bash
+npm install -D dotenv
+```
+
+Then, include the following in the `hardhat.config.ts` file:
+
+```tsx
+import dotenv from 'dotenv';
+
+dotenv.config();
+```
+
+Deploy to base with the following command:
+
+```bash
+npx hardhat deploy --network base_sepolia
+```
+
+After you run the command, a deployments folder appears with a newly-created deployment for `base_sepolia`:
+
+
+
+If you want to deploy to another network, change the network name as follows:
+
+```bash
+npx hardhat deploy --network sepolia
+```
+
+
+Be aware that you must have the correct environment variables for the JSON RPC URLs. For example, for Sepolia use `ALCHEMY_SEPOLIA_KEY`.
+
+
+
+## Conclusion
+
+In this lesson, you've learned how to deploy smart contracts using Hardhat and Hardhat-deploy. You have configured hardhat to easily deploy to multiple networks and you created deployment files to abstract this task.
+
+---
+
+## See also
+
+[Solidity Docs](https://docs.soliditylang.org/en/v0.8.17/)
+[Remix Project]: https://remix-project.org/
+[Hardhat]: https://hardhat.org/
+[Hardhat Deploy]: https://github.com/wighawag/hardhat-deploy
diff --git a/_pages/learn/hardhat-deploy/installing-hardhat-deploy-vid.mdx b/_pages/learn/hardhat-deploy/installing-hardhat-deploy-vid.mdx
new file mode 100644
index 00000000..457411cd
--- /dev/null
+++ b/_pages/learn/hardhat-deploy/installing-hardhat-deploy-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Installing Hardhat Deploy
+description: Install a community plugin that makes deployments easier.
+hide_table_of_contents: false
+---
+
+# Installing Hardhat Deploy
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/hardhat-deploy/setup-deploy-script-vid.mdx b/_pages/learn/hardhat-deploy/setup-deploy-script-vid.mdx
new file mode 100644
index 00000000..c770ef48
--- /dev/null
+++ b/_pages/learn/hardhat-deploy/setup-deploy-script-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Setting up the Deploy Script
+description: Prepare a script to deploy your contract.
+hide_table_of_contents: false
+---
+
+# Setting up the Deploy Script
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/hardhat-deploy/test-network-configuration-vid.mdx b/_pages/learn/hardhat-deploy/test-network-configuration-vid.mdx
new file mode 100644
index 00000000..70b3e88e
--- /dev/null
+++ b/_pages/learn/hardhat-deploy/test-network-configuration-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Test Network Configuration
+description: Configure test networks.
+hide_table_of_contents: false
+---
+
+# Test Network Configuration
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/hardhat-deploy/testing-our-deployment-vid.mdx b/_pages/learn/hardhat-deploy/testing-our-deployment-vid.mdx
new file mode 100644
index 00000000..405a0b9f
--- /dev/null
+++ b/_pages/learn/hardhat-deploy/testing-our-deployment-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Testing Our Deployment
+description: Test the newly created deploy script.
+hide_table_of_contents: false
+---
+
+# Testing Our Deployment
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/hardhat-forking/hardhat-forking.mdx b/_pages/learn/hardhat-forking/hardhat-forking.mdx
similarity index 100%
rename from docs/pages/learn/hardhat-forking/hardhat-forking.mdx
rename to _pages/learn/hardhat-forking/hardhat-forking.mdx
diff --git a/_pages/learn/hardhat-forking/mainnet-forking-vid.mdx b/_pages/learn/hardhat-forking/mainnet-forking-vid.mdx
new file mode 100644
index 00000000..493738e9
--- /dev/null
+++ b/_pages/learn/hardhat-forking/mainnet-forking-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Forking Mainnet
+description: Create a copy of the mainnet to run advanced tests.
+hide_table_of_contents: false
+---
+
+# Forking Mainnet
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/hardhat-setup-overview/creating-a-project-vid.mdx b/_pages/learn/hardhat-setup-overview/creating-a-project-vid.mdx
similarity index 79%
rename from docs/pages/learn/hardhat-setup-overview/creating-a-project-vid.mdx
rename to _pages/learn/hardhat-setup-overview/creating-a-project-vid.mdx
index 2c290cea..e52aa960 100644
--- a/docs/pages/learn/hardhat-setup-overview/creating-a-project-vid.mdx
+++ b/_pages/learn/hardhat-setup-overview/creating-a-project-vid.mdx
@@ -6,6 +6,6 @@ hide_table_of_contents: false
# Creating a Project
-import Video from '@/components/VideoPlayer.jsx'
+import { Video } from '/snippets/VideoPlayer.mdx';
diff --git a/docs/pages/learn/hardhat-setup-overview/hardhat-overview-vid.mdx b/_pages/learn/hardhat-setup-overview/hardhat-overview-vid.mdx
similarity index 76%
rename from docs/pages/learn/hardhat-setup-overview/hardhat-overview-vid.mdx
rename to _pages/learn/hardhat-setup-overview/hardhat-overview-vid.mdx
index cc310528..2c0ccd70 100644
--- a/docs/pages/learn/hardhat-setup-overview/hardhat-overview-vid.mdx
+++ b/_pages/learn/hardhat-setup-overview/hardhat-overview-vid.mdx
@@ -6,6 +6,6 @@ hide_table_of_contents: false
# Overview
-import Video from '@/components/VideoPlayer.jsx'
+import { Video } from '/snippets/VideoPlayer.mdx';
diff --git a/docs/pages/learn/hardhat-setup-overview/hardhat-setup-overview-sbs.mdx b/_pages/learn/hardhat-setup-overview/hardhat-setup-overview-sbs.mdx
similarity index 100%
rename from docs/pages/learn/hardhat-setup-overview/hardhat-setup-overview-sbs.mdx
rename to _pages/learn/hardhat-setup-overview/hardhat-setup-overview-sbs.mdx
diff --git a/_pages/learn/hardhat-testing/contract-abi-and-testing-vid.mdx b/_pages/learn/hardhat-testing/contract-abi-and-testing-vid.mdx
new file mode 100644
index 00000000..99199b33
--- /dev/null
+++ b/_pages/learn/hardhat-testing/contract-abi-and-testing-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Contract ABIs and Testing
+description: Learn how the contract ABI is related to writing tests.
+hide_table_of_contents: false
+---
+
+# Contract ABIs and Testing
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/hardhat-testing/hardhat-testing-sbs.mdx b/_pages/learn/hardhat-testing/hardhat-testing-sbs.mdx
similarity index 100%
rename from docs/pages/learn/hardhat-testing/hardhat-testing-sbs.mdx
rename to _pages/learn/hardhat-testing/hardhat-testing-sbs.mdx
diff --git a/_pages/learn/hardhat-testing/testing-overview-vid.mdx b/_pages/learn/hardhat-testing/testing-overview-vid.mdx
new file mode 100644
index 00000000..e3dac2ce
--- /dev/null
+++ b/_pages/learn/hardhat-testing/testing-overview-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Testing Overview
+description: An overview of writing tests in Hardhat.
+hide_table_of_contents: false
+---
+
+# Testing Overview
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/hardhat-testing/writing-tests-vid.mdx b/_pages/learn/hardhat-testing/writing-tests-vid.mdx
new file mode 100644
index 00000000..e0fe433a
--- /dev/null
+++ b/_pages/learn/hardhat-testing/writing-tests-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Writing Tests
+description: An introduction to writing tests.
+hide_table_of_contents: false
+---
+
+# Writing Tests
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/hardhat-tools-and-testing/overview.mdx b/_pages/learn/hardhat-tools-and-testing/overview.mdx
similarity index 100%
rename from docs/pages/learn/hardhat-tools-and-testing/overview.mdx
rename to _pages/learn/hardhat-tools-and-testing/overview.mdx
diff --git a/docs/pages/learn/hardhat-verify/hardhat-verify-sbs.mdx b/_pages/learn/hardhat-verify/hardhat-verify-sbs.mdx
similarity index 100%
rename from docs/pages/learn/hardhat-verify/hardhat-verify-sbs.mdx
rename to _pages/learn/hardhat-verify/hardhat-verify-sbs.mdx
diff --git a/_pages/learn/hardhat-verify/hardhat-verify-vid.mdx b/_pages/learn/hardhat-verify/hardhat-verify-vid.mdx
new file mode 100644
index 00000000..0464866b
--- /dev/null
+++ b/_pages/learn/hardhat-verify/hardhat-verify-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Verifying Smart Contracts
+description: Verify your contracts with Hardhat.
+hide_table_of_contents: false
+---
+
+# Verifying Smart Contracts
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/help-on-discord.mdx b/_pages/learn/help-on-discord.mdx
similarity index 100%
rename from docs/pages/learn/help-on-discord.mdx
rename to _pages/learn/help-on-discord.mdx
diff --git a/_pages/learn/imports/imports-exercise.mdx b/_pages/learn/imports/imports-exercise.mdx
new file mode 100644
index 00000000..f07a061f
--- /dev/null
+++ b/_pages/learn/imports/imports-exercise.mdx
@@ -0,0 +1,87 @@
+---
+title: Imports Exercise
+description: Exercise - Demonstrate your knowledge of imports.
+hide_table_of_contents: false
+---
+
+# Imports Exercise
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Create a contract called `ImportsExercise`. It should `import` a copy of `SillyStringUtils`
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.17;
+
+library SillyStringUtils {
+
+ struct Haiku {
+ string line1;
+ string line2;
+ string line3;
+ }
+
+ function shruggie(string memory _input) internal pure returns (string memory) {
+ return string.concat(_input, unicode" 🤷");
+ }
+}
+```
+
+Add a public instance of `Haiku` called `haiku`.
+
+Add the following two functions.
+
+### Save Haiku
+
+`saveHaiku` should accept three strings and save them as the lines of `haiku`.
+
+### Get Haiku
+
+`getHaiku` should return the haiku as a `Haiku` type.
+
+
+Remember, the compiler will automatically create a getter for `public` `struct`s, but these return each member individually. Create your own getters to return the type.
+
+
+
+### Shruggie Haiku
+
+`shruggieHaiku` should use the library to add 🤷 to the end of `line3`. It must **not** modify the original haiku. It should return the modified `Haiku`.
+
+---
+
+## Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Contract Verification Best Practices
+
+To simplify the verification of your contract on a blockchain explorer like BaseScan.org, consider these two common strategies:
+
+1. **Flattening**: This method involves combining your main contract and all of its imported dependencies into a single file. This makes it easier for explorers to verify the code since they only have to process one file.
+
+2. **Modular Deployment**: Alternatively, you can deploy each imported contract separately and then reference them in your main contract via their deployed addresses. This approach maintains the modularity and readability of your code. Each contract is deployed and verified independently, which can facilitate easier updates and reusability.
+
+3. **Use Desktop Tools**: Forge and Hardhat both have tools to write scripts that both deploy and verify your contracts.
+
+
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
diff --git a/_pages/learn/imports/imports-sbs.mdx b/_pages/learn/imports/imports-sbs.mdx
new file mode 100644
index 00000000..e3472c9e
--- /dev/null
+++ b/_pages/learn/imports/imports-sbs.mdx
@@ -0,0 +1,105 @@
+---
+title: Imports
+description: Learn to import code into your contract.
+hide_table_of_contents: false
+---
+
+# Imports
+
+In this lesson, we'll learn how to import code written by others into your contracts. We'll also explore the [OpenZeppelin] library of smart contracts.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Import and use code from another file
+- Utilize OpenZeppelin contracts within Remix
+
+---
+
+## OpenZeppelin
+
+[OpenZeppelin] has a robust [library] of well-[documented] smart contracts. These include a number of standard-compliant token implementations and a suite of utilities. All the contracts are audited and are therefore safer to use than random code you might find on the internet (you should still do your own audits before releasing to production).
+
+### Docs
+
+The [docs] start with installation instructions, which we'll return to when we switch over to local development. You do **not** need to install anything to use these contracts in Remix.
+
+Find the documentation for the `EnumerableSet` under _Utils_. This library will allow you to create [sets] of `bytes32`, `address`, and `uint256`. Since they're enumerated, you can iterate through them. Neat!
+
+### Implementing the OpenZeppelin EnumerableSet
+
+Create a new file to work in and add the `pragma` and license identifier.
+
+In Remix, you can import libraries directly from GitHub!
+
+```solidity
+import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/structs/EnumerableSet.sol";
+```
+
+You should see `EnumerableSet.sol` pop into your workspace files, nested deeply in a bunch of folders.
+
+### Trying It Out
+
+Add a contract called `SetExploration`. Review the extensive comments within the contract itself.
+
+To use the `EnumerableSet`, you need to use the [`using`] keyword. This directive attaches all of the library methods to the type. Doing so allows you to call the method on the variable with dot notation, and the variable itself will be supplied as the first argument.
+
+Follow the pattern in the example in the comments, but name the variable `visitors`:
+
+```
+using EnumerableSet for EnumerableSet.AddressSet;
+
+EnumerableSet.AddressSet private visitors;
+```
+
+Add a function called `registerVisitor` that makes use of the library's `add` function to add the sender of the message to the `visitors` set.
+
+
+There's also an `_add` function, which is private.
+
+
+
+
+
+Reveal code
+
+```solidity
+function registerVisitor() public {
+ visitors.add(msg.sender);
+}
+```
+
+
+
+
+
+Add another function to return the `numberOfVisitors`. Thanks to `using`, this can cleanly call the `length` function:
+
+
+
+Reveal code
+
+```solidity
+function numberOfVisitors() public view returns (uint) {
+ return visitors.length();
+}
+```
+
+
+---
+
+## Conclusion
+
+In this lesson, you imported a library from [OpenZeppelin] and implemented some of its functions. You also learned how to use the `using` keyword.
+
+---
+
+[OpenZeppelin]: https://www.openzeppelin.com/
+[library]: https://github.com/OpenZeppelin/openzeppelin-contracts
+[documented]: https://docs.openzeppelin.com/contracts/4.x/
+[docs]: https://docs.openzeppelin.com/contracts/4.x/
+[sets]: https://en.wikipedia.org/wiki/Set_(abstract_data_type)
+[`using`]: https://docs.soliditylang.org/en/v0.8.17/contracts.html#using-for
diff --git a/_pages/learn/imports/imports-vid.mdx b/_pages/learn/imports/imports-vid.mdx
new file mode 100644
index 00000000..34fce4e3
--- /dev/null
+++ b/_pages/learn/imports/imports-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Imports
+description: Import libraries and contracts into your own contracts.
+hide_table_of_contents: false
+---
+
+# Imports
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/inheritance/abstract-contracts-sbs.mdx b/_pages/learn/inheritance/abstract-contracts-sbs.mdx
similarity index 100%
rename from docs/pages/learn/inheritance/abstract-contracts-sbs.mdx
rename to _pages/learn/inheritance/abstract-contracts-sbs.mdx
diff --git a/_pages/learn/inheritance/abstract-contracts-vid.mdx b/_pages/learn/inheritance/abstract-contracts-vid.mdx
new file mode 100644
index 00000000..f8d59358
--- /dev/null
+++ b/_pages/learn/inheritance/abstract-contracts-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Abstract Contracts
+description: Create contracts that exist only to be inherited from.
+hide_table_of_contents: false
+---
+
+# Abstract Contracts
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/inheritance/inheritance-exercise.mdx b/_pages/learn/inheritance/inheritance-exercise.mdx
new file mode 100644
index 00000000..f98cd1f0
--- /dev/null
+++ b/_pages/learn/inheritance/inheritance-exercise.mdx
@@ -0,0 +1,120 @@
+---
+title: Inheritance Exercise
+description: Exercise - Demonstrate your knowledge of inheritance.
+hide_table_of_contents: false
+---
+
+# Inheritance Exercise
+
+Create contracts that adhere to the following specifications.
+
+---
+
+## Contracts
+
+### Employee
+
+Create an `abstract` contract called `Employee`. It should have:
+
+- A public variable storing `idNumber`
+- A public variable storing `managerId`
+- A constructor that accepts arguments for and sets both of these variables
+- A `virtual` function called `getAnnualCost` that returns a `uint`
+
+### Salaried
+
+A contract called `Salaried`. It should:
+
+- Inherit from `Employee`
+- Have a public variable for `annualSalary`
+- Implement an `override` function for `getAnnualCost` that returns `annualSalary`
+- An appropriate constructor that performs any setup, including setting `annualSalary`
+
+### Hourly
+
+Implement a contract called `Hourly`. It should:
+
+- Inherit from `Employee`
+- Have a public variable storing `hourlyRate`
+- Include any other necessary setup and implementation
+
+
+The annual cost of an hourly employee is their hourly rate \* 2080 hours.
+
+
+
+### Manager
+
+Implement a contract called `Manager`. It should:
+
+- Have a public array storing employee Ids
+- Include a function called `addReport` that can add id numbers to that array
+- Include a function called `resetReports` that can reset that array to empty
+
+### Salesperson
+
+Implement a contract called `Salesperson` that inherits from `Hourly`.
+
+### Engineering Manager
+
+Implement a contract called `EngineeringManager` that inherits from `Salaried` and `Manager`.
+
+## Deployments
+
+You'll have to do a more complicated set of deployments for this exercise.
+
+Deploy your `Salesperson` and `EngineeringManager` contracts. You don't need to separately deploy the other contracts.
+
+Use the following values:
+
+### Salesperson
+
+- Hourly rate is 20 dollars an hour
+- Id number is 55555
+- Manager Id number is 12345
+
+### Manager
+
+- Annual salary is 200,000
+- Id number is 54321
+- Manager Id is 11111
+
+## Inheritance Submission
+
+Copy the below contract and deploy it using the addresses of your `Salesperson` and `EngineeringManager` contracts.
+
+```solidity
+contract InheritanceSubmission {
+ address public salesPerson;
+ address public engineeringManager;
+
+ constructor(address _salesPerson, address _engineeringManager) {
+ salesPerson = _salesPerson;
+ engineeringManager = _engineeringManager;
+ }
+}
+```
+
+---
+
+## Submit your Contracts and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+
+Submit your address for your copy of the `InheritanceSubmission` contract that contains your other contract addresses.
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
diff --git a/docs/pages/learn/inheritance/inheritance-sbs.mdx b/_pages/learn/inheritance/inheritance-sbs.mdx
similarity index 100%
rename from docs/pages/learn/inheritance/inheritance-sbs.mdx
rename to _pages/learn/inheritance/inheritance-sbs.mdx
diff --git a/_pages/learn/inheritance/inheritance-vid.mdx b/_pages/learn/inheritance/inheritance-vid.mdx
new file mode 100644
index 00000000..2a8270eb
--- /dev/null
+++ b/_pages/learn/inheritance/inheritance-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Inheritance
+description: Create contracts that inherit from other contracts.
+hide_table_of_contents: false
+---
+
+# Inheritance
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/inheritance/multiple-inheritance-vid.mdx b/_pages/learn/inheritance/multiple-inheritance-vid.mdx
new file mode 100644
index 00000000..f8ca1a37
--- /dev/null
+++ b/_pages/learn/inheritance/multiple-inheritance-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Multiple Inheritance
+description: Create contracts that inherit from multiple contracts.
+hide_table_of_contents: false
+---
+
+# Multiple Inheritance
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/inheritance/multiple-inheritance.mdx b/_pages/learn/inheritance/multiple-inheritance.mdx
similarity index 100%
rename from docs/pages/learn/inheritance/multiple-inheritance.mdx
rename to _pages/learn/inheritance/multiple-inheritance.mdx
diff --git a/_pages/learn/interfaces/calling-another-contract-vid.mdx b/_pages/learn/interfaces/calling-another-contract-vid.mdx
new file mode 100644
index 00000000..9e2da311
--- /dev/null
+++ b/_pages/learn/interfaces/calling-another-contract-vid.mdx
@@ -0,0 +1,16 @@
+---
+title: Calling Another Contract
+description: Call the functions in another contract from your own contract.
+hide_table_of_contents: false
+---
+
+# Calling Another Contract
+
+
+This tutorial has been moved as part of a reorganization! It assumes you are using Hardhat. Everything in this lesson will work with minor adjustments if you are working in Foundry or Remix.
+
+
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/interfaces/contract-to-contract-interaction.mdx b/_pages/learn/interfaces/contract-to-contract-interaction.mdx
new file mode 100644
index 00000000..7622b558
--- /dev/null
+++ b/_pages/learn/interfaces/contract-to-contract-interaction.mdx
@@ -0,0 +1,267 @@
+---
+title: 'Contract to Contract Interaction'
+description: Interact with other smart contracts
+hide_table_of_contents: false
+---
+
+# Contract to Contract Interaction
+
+In this article, you'll learn how to interact with other smart contracts using interfaces and the `.call()` function, which allows you to interact with other smart contracts without using an interface.
+
+
+This tutorial has been moved as part of a reorganization! It assumes you are using Hardhat. Everything in this lesson will work with minor adjustments if you are working in Foundry or Remix.
+
+
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Use interfaces to allow a smart contract to call functions in another smart contract
+- Use the `call()` function to interact with another contract without using an interface
+
+---
+
+## Overview
+
+Interacting with external smart contracts is a very common task in the life of a smart contract developer. This includes interacting with contracts that are already deployed to a particular network.
+
+Usually the creators of certain smart contracts document their functionality and expose their functions by providing interfaces that can be used to integrate those particular contracts into your own.
+
+For instance, [Uniswap] provides documentation on how to interact with their smart contracts and also some packages to easily integrate their protocol.
+
+In this example, you interact with the [Uniswap protocol] to create a custom pool for a custom pair of tokens.
+
+Since the Uniswap protocol is already deployed, you will use [Hardhat forking] to test your contract.
+
+You will also use the following two approaches in the example:
+
+- Using interfaces
+- Using the `.call()` function
+
+## Interacting with deployed contracts using interfaces
+
+You must first install the [Uniswap V3 core package] by running:
+
+```bash
+npm install @uniswap/v3-core
+```
+
+This package provides access to the Uniswap interfaces of the Core protocol.
+
+Then, write a custom contract called `PoolCreator` with the following code:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
+
+contract PoolCreator {
+ IUniswapV3Factory public uniswapFactory;
+
+ constructor(address _factoryAddress) {
+ uniswapFactory = IUniswapV3Factory(_factoryAddress);
+ }
+
+ function createPool(
+ address tokenA,
+ address tokenB,
+ uint24 fee
+ ) external returns (address poolAddress) {
+ // Check if a pool with the given tokens and fee already exists
+ poolAddress = uniswapFactory.getPool(tokenA, tokenB, fee);
+ if (poolAddress == address(0)) {
+ // If the pool doesn't exist, create a new one
+ poolAddress = uniswapFactory.createPool(tokenA, tokenB, fee);
+ }
+
+ return poolAddress;
+ }
+}
+```
+
+Notice the following:
+
+- You are importing a `IUniswapV3Factory` interface. The interface contains function declarations that include `getPool` and `createPool`:
+
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity >=0.5.0;
+
+/// @title The interface for the Uniswap V3 Factory
+/// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees
+interface IUniswapV3Factory {
+ // ...
+ // ...other function declarations
+
+ /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist
+ /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order
+ /// @param tokenA The contract address of either token0 or token1
+ /// @param tokenB The contract address of the other token
+ /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
+ /// @return pool The pool address
+ function getPool(
+ address tokenA,
+ address tokenB,
+ uint24 fee
+ ) external view returns (address pool);
+
+ /// @notice Creates a pool for the given two tokens and fee
+ /// @param tokenA One of the two tokens in the desired pool
+ /// @param tokenB The other of the two tokens in the desired pool
+ /// @param fee The desired fee for the pool
+ /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved
+ /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments
+ /// are invalid.
+ /// @return pool The address of the newly created pool
+ function createPool(
+ address tokenA,
+ address tokenB,
+ uint24 fee
+ ) external returns (address pool);
+```
+
+- The constructor receives the address of the pool factory and creates an instance of `IUniswapV3Factory`.
+- The `createPool` function includes a validation to ensure the pool doesn't exist.
+- The `createPool` function creates a new pool.
+
+Then, create a test file called `PoolCreator.test.ts` with the following content:
+
+```tsx
+import { ethers } from 'hardhat';
+import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers';
+
+import { Token, Token__factory, PoolCreator, PoolCreator__factory } from '../typechain-types';
+
+describe('PoolCreator tests', function () {
+ const UNISWAP_FACTORY_ADDRESS = '0x1F98431c8aD98523631AE4a59f267346ea31F984';
+ let tokenA: Token;
+ let tokenB: Token;
+ let poolCreator: PoolCreator;
+ let owner: HardhatEthersSigner;
+
+ before(async () => {
+ const signers = await ethers.getSigners();
+ owner = signers[0];
+ tokenA = await new Token__factory().connect(owner).deploy('TokenA', 'TokenA');
+ tokenB = await new Token__factory().connect(owner).deploy('TokenB', 'TokenB');
+ poolCreator = await new PoolCreator__factory().connect(owner).deploy(UNISWAP_FACTORY_ADDRESS);
+ });
+
+ it('should create a pool', async () => {
+ const contractAddress = await poolCreator.createPool.staticCall(tokenA, tokenB, 500);
+ console.log('Contract Address', contractAddress);
+ await poolCreator.createPool(tokenA, tokenB, 500);
+ });
+});
+```
+
+Notice the following:
+
+- The address `0x1F98431c8aD98523631AE4a59f267346ea31F984` is the address of the Uniswap pool factory deployed to the Ethereum mainnet. This can be verified by looking at the Uniswap documentation that includes the [Deployment addresses of the contracts].
+- You created two tokens, TokenA and TokenB, by using a `Token` contract.
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract Token is ERC20 {
+ constructor(string memory name, string memory symbol) ERC20(name, symbol){
+ _mint(msg.sender, 1000 ether);
+ }
+}
+```
+
+Finally, run `npx hardhat test` and you should get a result similar to the following:
+
+```
+PoolCreator tests
+Contract Address 0xa76662f79A5bC06e459d0a841190C7a4e093b04d
+ ✔ should create a pool (1284ms)
+
+ 1 passing (5s)
+```
+
+## Interacting with external contracts using `.call()`
+
+In the previous example, you accessed the Uniswap V3 Factory interface, however if you don't have access to the contract interface, you can use a special function called `call`.
+
+Using `call`, you can call any contract as long as you know minimal information of the function signature. In this case, you should at least know that `createPool` requires three parameters:
+
+- tokenA
+- tokenB
+- fee
+
+The newly modified smart contract code looks as follows:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+contract PoolCreator {
+ address public uniswapFactory;
+
+ constructor(address _factoryAddress) {
+ uniswapFactory = _factoryAddress;
+ }
+
+ function createPool(
+ address tokenA,
+ address tokenB,
+ uint24 fee
+ ) external returns (address poolAddress) {
+ bytes memory payload = abi.encodeWithSignature(
+ "createPool(address,address,uint24)",
+ tokenA,
+ tokenB,
+ fee
+ );
+
+ (bool success, bytes memory data) = uniswapFactory.call(payload);
+ require(success, "Uniswap factory call failed");
+
+ // The pool address should be returned as the first 32 bytes of the data
+ assembly {
+ poolAddress := mload(add(data, 32))
+ }
+
+ require(poolAddress != address(0), "Pool creation failed");
+ return poolAddress;
+ }
+}
+```
+
+Notice the following:
+
+- By using `abi.encodeWithSignature`, you encode the payload required to make a smart contract call using the `.call()` function.
+- Using `.call()` doesn't require you to import the interface.
+- You load the pool address by using a special assembly operation called `mload`.
+
+Try to run again the command `npx hardhat test` and you should expect the same result:
+
+```
+PoolCreator tests
+Contract Address 0xa76662f79A5bC06e459d0a841190C7a4e093b04d
+ ✔ should create a pool (1284ms)
+
+ 1 passing (5s)
+```
+
+## Conclusion
+
+Interfaces or the `.call` function are two ways to interact with external contracts. Using interfaces provides several advantages, including type safety, code readability, and compiler error checking. When interacting with well-documented contracts like Uniswap, using interfaces is often the preferred and safest approach.
+
+On the other hand, the `.call` function offers more flexibility but comes with greater responsibility. It allows developers to call functions on contracts even without prior knowledge of their interfaces. However, it lacks the type safety and error checking provided by interfaces, making it more error-prone.
+
+---
+
+[Uniswap]: https://docs.uniswap.org/contracts/v3/reference/core/UniswapV3Factory
+[Uniswap protocol]: https://uniswap.org
+[Hardhat forking]: https://hardhat.org/hardhat-network/docs/guides/forking-other-networks
+[Uniswap V3 core package]: https://www.npmjs.com/package/@uniswap/v3-core
+[Deployment addresses of the contracts]: https://docs.uniswap.org/contracts/v3/reference/deployments
diff --git a/_pages/learn/interfaces/intro-to-interfaces-vid.mdx b/_pages/learn/interfaces/intro-to-interfaces-vid.mdx
new file mode 100644
index 00000000..12a3d326
--- /dev/null
+++ b/_pages/learn/interfaces/intro-to-interfaces-vid.mdx
@@ -0,0 +1,16 @@
+---
+title: Intro to Interfaces
+description: Use interfaces to tell your contract how another works.
+hide_table_of_contents: false
+---
+
+# Intro to Interfaces
+
+
+This tutorial has been moved as part of a reorganization! It assumes you are using Hardhat. Everything in this lesson will work with minor adjustments if you are working in Foundry or Remix.
+
+
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/interfaces/testing-the-interface-vid.mdx b/_pages/learn/interfaces/testing-the-interface-vid.mdx
new file mode 100644
index 00000000..d5934b01
--- /dev/null
+++ b/_pages/learn/interfaces/testing-the-interface-vid.mdx
@@ -0,0 +1,16 @@
+---
+title: Testing the Interface
+description: Start writing tests for interfaces.
+hide_table_of_contents: false
+---
+
+# Testing the Interface
+
+
+This tutorial has been moved as part of a reorganization! It assumes you are using Hardhat. Everything in this lesson will work with minor adjustments if you are working in Foundry or Remix.
+
+
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/intro-to-tokens/intro-to-tokens-vid.mdx b/_pages/learn/intro-to-tokens/intro-to-tokens-vid.mdx
new file mode 100644
index 00000000..1fde6e2b
--- /dev/null
+++ b/_pages/learn/intro-to-tokens/intro-to-tokens-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Introduction
+description: Welcome to the wonderful world of tokens!
+hide_table_of_contents: false
+---
+
+# Introduction
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/intro-to-tokens/misconceptions-about-tokens-vid.mdx b/_pages/learn/intro-to-tokens/misconceptions-about-tokens-vid.mdx
new file mode 100644
index 00000000..4b6aa5e0
--- /dev/null
+++ b/_pages/learn/intro-to-tokens/misconceptions-about-tokens-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Common Misconceptions
+description: Review some common misconceptions before starting.
+hide_table_of_contents: false
+---
+
+# Common Misconceptions
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/intro-to-tokens/tokens-overview.mdx b/_pages/learn/intro-to-tokens/tokens-overview.mdx
similarity index 100%
rename from docs/pages/learn/intro-to-tokens/tokens-overview.mdx
rename to _pages/learn/intro-to-tokens/tokens-overview.mdx
diff --git a/docs/pages/learn/introduction-to-ethereum/ethereum-applications.mdx b/_pages/learn/introduction-to-ethereum/ethereum-applications.mdx
similarity index 100%
rename from docs/pages/learn/introduction-to-ethereum/ethereum-applications.mdx
rename to _pages/learn/introduction-to-ethereum/ethereum-applications.mdx
diff --git a/docs/pages/learn/introduction-to-ethereum/ethereum-dev-overview-vid.mdx b/_pages/learn/introduction-to-ethereum/ethereum-dev-overview-vid.mdx
similarity index 81%
rename from docs/pages/learn/introduction-to-ethereum/ethereum-dev-overview-vid.mdx
rename to _pages/learn/introduction-to-ethereum/ethereum-dev-overview-vid.mdx
index 304ee259..9c9a389f 100644
--- a/docs/pages/learn/introduction-to-ethereum/ethereum-dev-overview-vid.mdx
+++ b/_pages/learn/introduction-to-ethereum/ethereum-dev-overview-vid.mdx
@@ -6,6 +6,6 @@ hide_table_of_contents: false
# Ethereum Applications
-import Video from '@/components/VideoPlayer.jsx'
+import { Video } from '/snippets/VideoPlayer.mdx';
diff --git a/_pages/learn/introduction-to-ethereum/evm-diagram.mdx b/_pages/learn/introduction-to-ethereum/evm-diagram.mdx
new file mode 100644
index 00000000..4385522c
--- /dev/null
+++ b/_pages/learn/introduction-to-ethereum/evm-diagram.mdx
@@ -0,0 +1,125 @@
+---
+title: EVM Diagram
+description: An overview of the Ethereum Virtual Machine
+hide_table_of_contents: false
+---
+
+# EVM Diagram
+
+In this article, we'll examine the inner workings of the EVM, its components, and its role within the Ethereum network.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Diagram the EVM
+
+---
+
+## What is the EVM?
+
+The Ethereum Virtual Machine (EVM) is the core engine of Ethereum. It is a Turing-complete, sandboxed virtual machine designed to execute smart contracts on the network. The term "sandboxed" means that the EVM operates in an isolated environment, ensuring that each smart contract's execution does not interfere with others or the underlying blockchain. As we've learned, the EVM's Turing-complete nature allows developers to write complex programs that can perform any computationally feasible task.
+
+The EVM employs a sophisticated resource management system using gas to regulate computation costs and prevent network abuse. It also supports a rich ecosystem of onchain apps by providing a versatile set of opcodes for smart contract logic, and fostering interoperability with various programming languages, tools, and technologies. This adaptability has made the EVM a fundamental component in the advancement and growth of the Ethereum network.
+
+---
+
+## EVM Components
+
+The EVM has several key components that enable it to process and manage smart contracts. Let's define them:
+
+- **World State:** Represents the entire Ethereum network, including all accounts and their associated storage.
+- **Accounts:** Entities that interact with the Ethereum network, including Externally Owned Accounts (EOAs) and Contract Accounts.
+- **Storage:** A key-value store associated with each contract account, containing the contract's state and data.
+- **Gas:** A mechanism for measuring the cost of executing operations in the EVM, which protects the network from spam and abuse.
+- **Opcodes:** Low-level instructions that the EVM executes during smart contract processing.
+- **Execution Stack:** A last-in, first-out (LIFO) data structure for temporarily storing values during opcode execution.
+- **Memory:** A runtime memory used by smart contracts during execution.
+- **Program Counter:** A register that keeps track of the position of the next opcode to be executed.
+- **Logs:** Events emitted by smart contracts during execution, which can be used by external systems for monitoring or reacting to specific events.
+
+---
+
+## EVM Execution Model
+
+In simple terms, when a transaction is submitted to the network, the EVM first verifies its validity. If the transaction is deemed valid, the EVM establishes an execution context that incorporates the current state of the network and processes the smart contract's bytecode using opcodes. As the EVM runs the smart contract, it modifies the blockchain's world state and consumes gas accordingly. However, if the transaction is found to be invalid, it will be dismissed by the network without further processing. Throughout the smart contract's execution, logs are generated that provide insights into the contract's performance and any emitted events. These logs can be utilized by external systems for monitoring purposes or to respond to specific events.
+
+
+
+---
+
+## Gas and Opcode Execution
+
+While we have already delved into the concept of gas in a previous lesson, it is worth reiterating its critical role within the EVM and as a fundamental component of Ethereum. Gas functions as a metric for quantifying the computational effort needed to carry out operations in the EVM. Every opcode in a smart contract carries a specific gas cost, which reflects the computational resources necessary for its execution.
+
+Opcodes are the low-level instructions executed by the EVM. They represent elementary operations that allow the EVM to process and manage smart contracts.
+
+
+
+During execution, the EVM reads opcodes from the smart contract, and depending on the opcode, it may update the world state, consume gas, or revert the state if an error occurs. Some common opcodes include:
+
+- **ADD:** Adds two values from the stack.
+- **SUB:** Subtracts two values from the stack.
+- **MSTORE:** Stores a value in memory.
+- **SSTORE:** Stores a value in contract storage.
+- **CALL:** Calls another contract or sends ether.
+
+---
+
+## Stack and Memory
+
+The EVM stack and memory are critical components of the EVM architecture, as they enable smart contracts to manage temporary data during opcode execution. The stack is a last-in, first-out (LIFO) data structure that is used for temporarily storing values during opcode execution. It is managed by the EVM and is separate from the contract's storage. The stack supports two primary operations: push and pop.
+
+The push operation adds a value to the top of the stack, while the pop operation removes the top value from the stack. These operations are used to manage temporary data during opcode execution. For example, an opcode that performs an addition operation might push the two operands onto the stack, perform the addition, and then pop the result off the top of the stack.
+
+During contract execution, memory serves as a collection of bytes, organized in an array, for the purpose of temporarily storing data. It can be read from and written to by opcodes. Memory is often used to store temporary data during opcode execution, such as when working with dynamically sized data like strings or arrays that are being manipulated or computed within the smart contract before being stored in the contract's storage. When a smart contract needs to store temporary data during opcode execution, it can use the memory to store that data.
+
+
+
+---
+
+## EVM Architecture and Execution Context
+
+To understand the inner workings of the EVM, the following diagram offers a streamlined visualization of its transaction execution process. It begins with the transaction initiation, and progresses to the gas computations for each operation. Integral to the process are the EVM's stack, memory, and storage, which are engaged to manage and persist data throughout the lifecycle of a transaction. Checks and validations at each step ensure the validity of operations, safeguarding the network's integrity. This systemized sequence of actions forms the bedrock of transaction and smart contract execution, ensuring Ethereum's consistent and secure operation.
+
+
+
+**Data Bytecode in the EVM**
+
+Every transaction or smart contract call within the EVM uses "bytecode", which
+is a sequence of instructions that guides the EVM's actions. Bytecode is
+primarily presented in a compact hexadecimal format.
+
+---
+
+Decoding the example sequence: `0x6080604052`
+
+```go
+60 // PUSH1: Pushes the next byte (0x80) onto the stack.
+80 // The byte to be pushed onto the stack by the previous PUSH1.
+60 // PUSH1: Pushes the next byte (0x40) onto the stack.
+40 // The byte to be pushed onto the stack by the previous PUSH1.
+52 // MSTORE: Stores the second stack item in memory at the address of the first.
+```
+
+This bytecode sequence is not a random set of characters. Each segment corresponds to specific operations or data in the EVM. Opcodes dictate actions, while subsequent data provides specifics.
+
+
+
+---
+
+## Conclusion
+
+The EVM plays a vital role within the Ethereum network. By examining the EVM's key components as well as its architecture and execution model, we've gained insight into the engine of Ethereum and how it enables the smooth execution of smart contracts on the platform.
+
+---
+
+## See Also
+
+- [The Ethereum Virtual Machine (Mastering Ethereum)](https://cypherpunks-core.github.io/ethereumbook/13evm.html#evm_architecture)
+- [Ethereum Virtual Machine (Ethereum docs)](https://ethereum.org/en/developers/docs/evm/)
+
+
+[the ethereum virtual machine (mastering ethereum)]: https://cypherpunks-core.github.io/ethereumbook/13evm.html#evm_architecture
diff --git a/docs/pages/learn/introduction-to-ethereum/gas-use-in-eth-transactions.mdx b/_pages/learn/introduction-to-ethereum/gas-use-in-eth-transactions.mdx
similarity index 100%
rename from docs/pages/learn/introduction-to-ethereum/gas-use-in-eth-transactions.mdx
rename to _pages/learn/introduction-to-ethereum/gas-use-in-eth-transactions.mdx
diff --git a/docs/pages/learn/introduction-to-ethereum/intro-to-ethereum-vid.mdx b/_pages/learn/introduction-to-ethereum/intro-to-ethereum-vid.mdx
similarity index 79%
rename from docs/pages/learn/introduction-to-ethereum/intro-to-ethereum-vid.mdx
rename to _pages/learn/introduction-to-ethereum/intro-to-ethereum-vid.mdx
index 510a06eb..2c112c32 100644
--- a/docs/pages/learn/introduction-to-ethereum/intro-to-ethereum-vid.mdx
+++ b/_pages/learn/introduction-to-ethereum/intro-to-ethereum-vid.mdx
@@ -6,6 +6,6 @@ hide_table_of_contents: false
# Introduction
-import Video from '@/components/VideoPlayer.jsx'
+import { Video } from '/snippets/VideoPlayer.mdx';
diff --git a/_pages/learn/introduction-to-solidity/anatomy-of-a-smart-contract-vid.mdx b/_pages/learn/introduction-to-solidity/anatomy-of-a-smart-contract-vid.mdx
new file mode 100644
index 00000000..d43d50d0
--- /dev/null
+++ b/_pages/learn/introduction-to-solidity/anatomy-of-a-smart-contract-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Anatomy of a Smart Contract
+description: Review how smart contracts are organized.
+hide_table_of_contents: false
+---
+
+# Anatomy of a Smart Contract
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/introduction-to-solidity/deployment-in-remix-vid.mdx b/_pages/learn/introduction-to-solidity/deployment-in-remix-vid.mdx
new file mode 100644
index 00000000..1625e677
--- /dev/null
+++ b/_pages/learn/introduction-to-solidity/deployment-in-remix-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Deployment in Remix
+description: Learn to deploy your contracts to the Remix VM.
+hide_table_of_contents: false
+---
+
+# Deployment in Remix
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/introduction-to-solidity/deployment-in-remix.mdx b/_pages/learn/introduction-to-solidity/deployment-in-remix.mdx
similarity index 100%
rename from docs/pages/learn/introduction-to-solidity/deployment-in-remix.mdx
rename to _pages/learn/introduction-to-solidity/deployment-in-remix.mdx
diff --git a/_pages/learn/introduction-to-solidity/introduction-to-remix-vid.mdx b/_pages/learn/introduction-to-solidity/introduction-to-remix-vid.mdx
new file mode 100644
index 00000000..028871b5
--- /dev/null
+++ b/_pages/learn/introduction-to-solidity/introduction-to-remix-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Introduction to Remix
+description: Learn about the Remix online IDE.
+hide_table_of_contents: false
+---
+
+# Introduction to Remix
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/introduction-to-solidity/introduction-to-remix.mdx b/_pages/learn/introduction-to-solidity/introduction-to-remix.mdx
new file mode 100644
index 00000000..53cb29a8
--- /dev/null
+++ b/_pages/learn/introduction-to-solidity/introduction-to-remix.mdx
@@ -0,0 +1,90 @@
+---
+title: 'Introduction to Remix'
+description: An introduction to the Remix online IDE.
+hide_table_of_contents: false
+---
+
+# Introduction to Remix
+
+In this lesson, you'll be introduced to an online Solidity IDE called Remix. You'll tour the workspace and explore a sample smart contract.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- List the features, pros, and cons of using Remix as an IDE
+- Deploy and test the Storage.sol demo contract in Remix
+
+---
+
+## Remix Window Overview
+
+Begin by opening a browser window and navigating to [remix.ethereum.org]. Open the project you created and cleaned up at the end of the last reading, and open `1_Storage.sol`. The editor should be organized in a way that is familiar to you. It is divided into three areas:
+
+- Editor Pane
+- Terminal/Output
+- Left Panel
+
+### Editor Pane
+
+The editor pane loads with the Remix home screen, which contains news, helpful links, and warnings about common scams. Double-click on `1_Storage.sol` to open it in the editor. You can close the home tab if you'd like.
+
+
+
+You'll edit your code in the editor pane. It also has most of the features you're expecting, such as syntax and error highlighting. Note that in Remix, errors are not underlined. Instead, you'll see an❗to the left of the line number where the error is present.
+
+At the top, you'll see a large green arrow similar to the _Run_ button in other editors. In Solidity, this compiles your code, but it does not run it because you must first deploy your code to the simulated blockchain.
+
+### Terminal/Output
+
+Below the editor pane, you'll find the terminal:
+
+
+
+You'll primarily use this panel to observe transaction logs from your smart contracts. It's also one way to access Remix's very powerful debugging tools.
+
+### Left Panel
+
+As with many other editors, the left panel in Remix has a number of vertical tabs that allow you to switch between different tools and functions. You can explore the files in your current workspace, create and switch between workspaces, search your code, and access a number of plugins.
+
+---
+
+## Plugins
+
+Most of the features in Remix are plugins and the ones you'll use the most are active by default. You can view and manage plugins by clicking the plug button in the lower-left corner, right above the settings gear. You can turn them off and on by clicking activate/deactivate, and some, such as the _Debug_ plugin will be automatically activated through other parts of the editor.
+
+### Solidity Compiler
+
+The first default plugin (after the search function) is the _Solidity Compiler_. Be sure to check the `Auto compile` option. Smart contracts are almost always very small files, so this shouldn't ever cause a performance problem while editing code.
+
+The `Compile and Run script` button in this plugin is a little misleading. This is **not** how you will usually run your contract through testing. You can click the `I` button for more information on this feature.
+
+Finally, if you have errors in your contracts, the complete text for each error will appear at the bottom of the pane. Try it out by introducing some typos to `1_Storage.sol`.
+
+### Deploy & Run Transactions
+
+The _Deploy & Run Transactions_ plugin is what you'll use to deploy your contracts and then interact with them. At the top are controls to select which virtual machine to use, mock user wallets with test Ether, and a drop-down menu to select the contract you wish to deploy and test.
+
+Fix any errors you introduced to `1_Storage.sol` and then click the orange `Deploy` button. You'll see your contract appear below as _STORAGE AT \_.
+
+
+There are two common gotchas that can be very confusing when deploying contracts in Remix.
+
+1. Each time you hit the Deploy button, a new copy of your contract is deployed but the previous deployments remain. Unless you are comparing or debugging between different versions of a contract, or deploying multiple contracts at once, you should click the `Trash` button to erase old deployments before deploying again.
+1. If your code will not compile, **clicking the deploy button will not generate an error!** Instead, the last compiled version will be deployed. Visually check and confirm that there are no errors indicated by a number in a red circle on top of the Compiler plugin.
+
+
+
+---
+
+## Conclusion
+
+Remix is a robust editor with many features and one or two gotchas. It is an excellent tool to use at the beginning of your journey because you can jump right in and start writing code for smart contracts.
+
+## See also
+
+[Remix](https://remix.ethereum.org)
+
+[remix.ethereum.org]: https://remix.ethereum.org
diff --git a/docs/pages/learn/introduction-to-solidity/introduction-to-solidity-overview.mdx b/_pages/learn/introduction-to-solidity/introduction-to-solidity-overview.mdx
similarity index 100%
rename from docs/pages/learn/introduction-to-solidity/introduction-to-solidity-overview.mdx
rename to _pages/learn/introduction-to-solidity/introduction-to-solidity-overview.mdx
diff --git a/_pages/learn/introduction-to-solidity/introduction-to-solidity-vid.mdx b/_pages/learn/introduction-to-solidity/introduction-to-solidity-vid.mdx
new file mode 100644
index 00000000..621f63d9
--- /dev/null
+++ b/_pages/learn/introduction-to-solidity/introduction-to-solidity-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Introduction
+description: Learn about the Solidity programming language.
+hide_table_of_contents: false
+---
+
+# Introduction
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/introduction-to-solidity/solidity-overview.mdx b/_pages/learn/introduction-to-solidity/solidity-overview.mdx
new file mode 100644
index 00000000..b00be240
--- /dev/null
+++ b/_pages/learn/introduction-to-solidity/solidity-overview.mdx
@@ -0,0 +1,165 @@
+---
+title: 'Solidity Overview'
+description: An overview of the Solidity programming language.
+hide_table_of_contents: false
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+# Solidity Overview
+
+In this article, you'll learn about the origins and history of Solidity, where to find the docs, and review some of the considerations that make programming in Solidity relatively unique. You'll also learn about how to get started with development!
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Describe why languages like Solidity are used to write smart contracts
+- Relate an overview of the history (and pace of change) of Solidity and its strengths and weaknesses
+
+---
+
+## Introduction to Solidity
+
+Solidity is a high-level language used to develop smart contracts compatible with the Ethereum Virtual Machine. It is object-oriented, strongly typed, and allows variadic (more than one argument) returns. Solidity was [inspired] by a number of languages, particularly C++. Compared to other languages, Solidity changes very rapidly. Review the [releases] to see just how rapid!
+
+### The Docs
+
+The [Solidity Docs] are thorough and helpful. This guide will regularly reference them and they should be your first source for specific information related to any of the components in the language. As with any versioned doc source, always double-check that the version you're referencing matches the version you are developing with.
+
+### Origins TL;DR
+
+Solidity was developed by the Ethereum Project's Solidity team and was first previewed in 2014 at DevCon0. The original goal was to create an easy-to-use language for smart contract development. A great [history overview] can be found in the team's blog.
+
+### What it Actually Does
+
+Solidity is very similar to the programming languages you are familiar with in that it's a high-level language that is relatively human-readable, which is then compiled into byte-code that can be read by the EVM. For example, this:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.9;
+
+contract Hello {
+ function HelloWorld() public pure returns (string memory) {
+ return "Hello World!";
+ }
+}
+```
+
+compiles into this:
+
+```text
+0x608060405234801561001057600080fd5b50610173806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80637fffb7bd14610030575b600080fd5b61003861004e565b604051610045919061011b565b60405180910390f35b60606040518060400160405280600c81526020017f48656c6c6f20576f726c64210000000000000000000000000000000000000000815250905090565b600081519050919050565b600082825260208201905092915050565b60005b838110156100c55780820151818401526020810190506100aa565b60008484015250505050565b6000601f19601f8301169050919050565b60006100ed8261008b565b6100f78185610096565b93506101078185602086016100a7565b610110816100d1565b840191505092915050565b6000602082019050818103600083015261013581846100e2565b90509291505056fea2646970667358221220575a1ec2ade1712a7a3a4e91cc5d83212207e4a5c70f5b2bc50079ee65ad29b364736f6c63430008110033
+```
+
+As you can see, the first example is a little easier to read!
+
+---
+
+## Programming for Ethereum with Solidity
+
+On the surface, writing code for the EVM using Solidity isn't particularly different from other programming languages. You write code organized into functions, and those functions get executed when called, often accepting arguments and returning values. However, there are a number of unusual traits that will require you to think a little differently. Additionally, the EVM is a much smaller, slower, and less-powerful computer than a desktop, or even a mobile device.
+
+### Gas Fees
+
+Every single [operation] your code performs costs gas, which your users pay for. You're probably already well-versed in _[time complexity]_ and know how to get an operation down to _O(log(n))_, when you have no choice but to run something that is _O(2^n)_, and that sometimes, nested for-loops go brrrrr. These constraints and practices still apply, but in Solidity, every inefficiency directly costs your users money, which can make your app more expensive, and less appealing, than needed.
+
+When you were learning about _time complexity_, you probably heard the term _space complexity_ once, and then it was never mentioned again. This is because normally, computation is expensive, and storage is practically free. The opposite is true on the EVM. It costs a minimum of **20,000** gas to initialize a variable, and a minimum of **5,000** to change it. Meanwhile, the cost to add two numbers together is **3** gas. This means it is often much cheaper to repeatedly derive a value that is calculated from other values than it is to calculate it once and save it.
+
+You also have to be careful to write code with predictable execution paths. Each transaction is sent with a gas limit and which various frameworks, such as _ethers.js_, in order to do their best to estimate. If this estimate is wrong, the transaction will fail, but **it will still cost the gas used up until the point it failed!**
+
+### Contract Size Limit
+
+[EIP-170] introduced a compiled byte-code size limit of **24 KiB** (24,576 B) to Ethereum Smart Contracts. Read that sentence again, as you're probably not used to thinking in this small of a number!
+
+While there isn't an exact ratio of lines of code to compiled byte-code size, you're limited to deploying contracts that are approximately 300-500 lines of Solidity.
+
+Luckily, there are a few ways around this limitation. Contracts can expose their functions to be called by other contracts, although there is an additional cost. Using this, you can write a suite of contracts designed to work together, or even make use of contracts already deployed by others. You can also use more advanced solutions, such as [EIP-2535].
+
+### Stack Limit
+
+Programs written for computers or mobile devices often work with hundreds of variables at the same time. The EVM operates with a stack that can hold 1,024 values, but it can only access the top 16.
+
+There are many implications of this limit, but the one you'll run into most commonly is the "Stack too Deep" error because you're trying to work with too many variables at once.
+
+In Solidity/EVM, your functions are limited to a total of 16 variables that are input, output, or initialized by the function.
+
+### Permanence
+
+Once deployed, smart contracts are permanent and cannot be changed by anyone, **even their creator(s)!** It is literally not possible to edit them. If the creators of a contract discover a vulnerability, they can't do anything about it except withdraw the funds - if the contract allows them to!
+
+As a result, standard practice is to have a smart contract audited by an expert, before deployment.
+
+### Pace of Change
+
+Solidity files always start with a license and a version:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.17;
+```
+
+One of the reasons for this is that the pace of development for Solidity is **very** fast, and changes are not always backwards-compatible. As a result, the compiler needs to know which version to use when converting the Solidity code to byte-code.
+
+Review the [changelog] to see some of the recent additions and fixes.
+
+---
+
+## Development Environments
+
+We'll be covering two tools that can be used to develop in Solidity.
+
+### Remix
+
+We'll start with [Remix], an online IDE similar to Codepen, Replit, or CodeSandbox. Remix is a great place to get started because it works out of the box, has a number of demo contracts, and has great debugging tools. More information can be found at the [Remix Project] website.
+
+
+
+
+**BE VERY CAREFUL** while using Remix, as it can also be used by scammers. Remix itself will warn you about this, so take heed! One common scam is for the scammer to convince you to paste and deploy code that is allegedly some sort of automated moneymaker, such as a staking tool, or a bot.
+
+If you paste and run code that you don't understand, you may lose all assets from your currently connected wallet. You should also be careful to always navigate directly to `remix.ethereum.org`. More experienced developers prefer to use static versions of Remix deployed to [IPFS], but be careful. There are also deployments that are compromised and used as a part of a scam!
+
+
+
+### Hardhat
+
+[Hardhat] is a development environment that allows you to develop and test Solidity on your local machine. It includes debugging and unit testing tools, and has an ecosystem of third-party-developed plugins that ease development and deployment. Among other things, these plugins can help you deploy contracts, see the size of your compiled byte-code, and even see unit test coverage.
+
+We'll introduce Hardhat and local development after the basics.
+
+## Remix Setup
+
+The next lesson will explore one of the demo contracts within [Remix]. Open it up and review the quickstart information if this is your first time on the site. Then, open or create a new workspace using the `Default` template.
+
+**Delete** everything except the contracts folder and the `1_Storage.sol` contract within that folder. You can also leave `.prettierrc.json` if you'd like.
+
+
+
+---
+
+## Conclusion
+
+On the surface, Solidity is very similar to other programming languages; most developers won't struggle to write familiar operations. However, there are some critically important properties to keep in mind. Operations are much more expensive than in other environments, particularly storage. You can use most of the practices you are accustomed to, but you are limited to very small contract sizes and by the size of the stack. Finally, remember that you should always use a separate wallet for development. If you make a mistake, you could lose anything in it!
+
+---
+
+## See also
+
+[Solidity Docs](https://docs.soliditylang.org/en/v0.8.17/)
+
+[inspired]: https://docs.soliditylang.org/en/v0.8.17/language-influences.html
+[releases]: https://github.com/ethereum/solidity/releases
+[Solidity Docs]: https://docs.soliditylang.org/en/v0.8.17/
+[history overview]: https://blog.soliditylang.org/2020/07/08/solidity-turns-5/
+[operation]: https://ethereum.org/en/developers/docs/evm/opcodes/
+[time complexity]: https://en.wikipedia.org/wiki/Time_complexity
+[EIP-170]: https://eips.ethereum.org/EIPS/eip-170
+[EIP-2535]: https://eips.ethereum.org/EIPS/eip-2535
+[changelog]: https://github.com/ethereum/solidity/blob/develop/Changelog.md
+[Remix]: https://remix.ethereum.org/
+[IPFS]: https://ipfs.tech/
+[Remix Project]: https://remix-project.org/
+[Hardhat]: https://hardhat.org/
diff --git a/_pages/learn/learning-objectives.mdx b/_pages/learn/learning-objectives.mdx
new file mode 100644
index 00000000..59955449
--- /dev/null
+++ b/_pages/learn/learning-objectives.mdx
@@ -0,0 +1,258 @@
+---
+title: "Learning Objectives"
+---
+
+
+### [Ethereum Applications](/learn/introduction-to-ethereum/ethereum-applications)
+
+- Describe the origin and goals of the Ethereum blockchain
+- List common types of applications that can be developed with the Ethereum blockchain
+- Compare and contrast Web2 vs. Web3 development
+- Compare and contrast the concept of "ownership" in Web2 vs. Web3
+
+### [Gas Use in Ethereum Transactions](/learn/introduction-to-ethereum/gas-use-in-eth-transactions)
+
+- Explain what gas is in Ethereum
+- Explain why gas is necessary in Ethereum
+- Understand how gas works in Ethereum transactions
+
+### [EVM Diagram](/learn/introduction-to-ethereum/evm-diagram)
+
+- Diagram the EVM
+
+### [Setup and Overview](/learn/hardhat-setup-overview/hardhat-setup-overview-sbs)
+
+- Install and create a new Hardhat project with Typescript support
+- Describe the organization and folder structure of a Hardhat project
+- List the use and properties of hardhat.config.ts
+
+### [Testing with Hardhat and Typechain](/learn/hardhat-testing/hardhat-testing-sbs)
+
+- Set up TypeChain to enable testing
+- Write unit tests for smart contracts using Mocha, Chai, and the Hardhat Toolkit
+- Set up multiple signers and call smart contract functions with different signers
+
+### [Etherscan](/learn/etherscan/etherscan-sbs)
+
+- List some of the features of Etherscan
+- Read data from the Bored Ape Yacht Club contract on Etherscan
+- Write data to a contract using Etherscan.
+
+### [Deploying Smart Contracts](/learn/hardhat-deploy/hardhat-deploy-sbs)
+
+- Deploy a smart contract to the Base Sepolia Testnet with hardhat-deploy
+- Deploy a smart contract to the Sepolia Testnet with hardhat-deploy
+- Use BaseScan to view a deployed smart contract
+
+### [Verifying Smart Contracts](/learn/hardhat-verify/hardhat-verify-sbs)
+
+- Verify a deployed smart contract on Etherscan
+- Connect a wallet to a contract in Etherscan
+- Use etherscan to interact with your own deployed contract
+
+### [Hardhat Forking](/learn/hardhat-forking/hardhat-forking)
+
+- Use Hardhat Network to create a local fork of mainnet and deploy a contract to it
+- Utilize Hardhat forking features to configure the fork for several use cases
+
+### ['Introduction to Remix'](/learn/introduction-to-solidity/introduction-to-remix)
+
+- List the features, pros, and cons of using Remix as an IDE
+- Deploy and test the Storage.sol demo contract in Remix
+
+### [Deployment in Remix](/learn/introduction-to-solidity/deployment-in-remix)
+
+- Deploy and test the Storage.sol demo contract in Remix
+
+### [Hello World](/learn/contracts-and-basic-functions/hello-world-step-by-step)
+
+- Construct a simple "Hello World" contract
+- List the major differences between data types in Solidity as compared to other languages
+- Select the appropriate visibility for a function
+
+### [Basic Types](/learn/contracts-and-basic-functions/basic-types)
+
+- Categorize basic data types
+- List the major differences between data types in Solidity as compared to other languages
+- Compare and contrast signed and unsigned integers
+
+### [Test Networks](/learn/deployment-to-testnet/test-networks)
+
+- Describe the uses and properties of the Base testnet
+- Compare and contrast Ropsten, Rinkeby, Goerli, and Sepolia
+
+### [Deployment to Base Sepolia](/learn/deployment-to-testnet/deployment-to-base-sepolia-sbs)
+
+- Deploy a contract to the Base Sepolia testnet and interact with it in [BaseScan]
+
+### [Contract Verification](/learn/deployment-to-testnet/contract-verification-sbs)
+
+- Verify a contract on the Base Sepolia testnet and interact with it in [BaseScan]
+
+### [Control Structures](/learn/control-structures/control-structures)
+
+- Control code flow with `if`, `else`, `while`, and `for`
+- List the unique constraints for control flow in Solidity
+- Utilize `require` to write a function that can only be used when a variable is set to `true`
+- Write a `revert` statement to abort execution of a function in a specific state
+- Utilize `error` to control flow more efficiently than with `require`
+
+### [Storing Data](/learn/storage/simple-storage-sbs)
+
+- Use the constructor to initialize a variable
+- Access the data in a public variable with the automatically generated getter
+- Order variable declarations to use storage efficiently
+
+### [How Storage Works](/learn/storage/how-storage-works)
+
+- Diagram how a contract's data is stored on the blockchain (Contract -> Blockchain)
+- Order variable declarations to use storage efficiently
+- Diagram how variables in a contract are stored (Variable -> Contract)
+
+### [Arrays](/learn/arrays/arrays-in-solidity)
+
+- Describe the difference between storage, memory, and calldata arrays
+
+### [Filtering an Array](/learn/arrays/filtering-an-array-sbs)
+
+- Write a function that can return a filtered subset of an array
+
+### [Mappings](/learn/mappings/mappings-sbs)
+
+- Construct a Map (dictionary) data type
+- Recall that assignment of the Map data type is not as flexible as for other data types/in other languages
+- Restrict function calls with the `msg.sender` global variable
+- Recall that there is no collision protection in the EVM and why this is (probably) ok
+
+### [Function Visibility and State Mutability](/learn/advanced-functions/function-visibility)
+
+- Categorize functions as public, private, internal, or external based on their usage
+- Describe how pure and view functions are different than functions that modify storage
+
+### [Function Modifiers](/learn/advanced-functions/function-modifiers)
+
+- Use modifiers to efficiently add functionality to multiple functions
+
+### [Structs](/learn/structs/structs-sbs)
+
+- Construct a `struct` (user-defined type) that contains several different data types
+- Declare members of the `struct` to maximize storage efficiency
+- Describe constraints related to the assignment of `struct`s depending on the types they contain
+
+### [Inheritance](/learn/inheritance/inheritance-sbs)
+
+- Write a smart contract that inherits from another contract
+- Describe the impact inheritance has on the byte code size limit
+
+### [Multiple Inheritance](/learn/inheritance/multiple-inheritance)
+
+- Write a smart contract that inherits from multiple contracts
+
+### [Abstract Contracts](/learn/inheritance/abstract-contracts-sbs)
+
+- Use the virtual, override, and abstract keywords to create and use an abstract contract
+
+### [Imports](/learn/imports/imports-sbs)
+
+- Import and use code from another file
+- Utilize OpenZeppelin contracts within Remix
+
+### [Error Triage](/learn/error-triage/error-triage)
+
+- Debug common solidity errors including transaction reverted, out of gas, stack overflow, value overflow/underflow, index out of range, etc.
+
+### [The New Keyword](/learn/new-keyword/new-keyword-sbs)
+
+- Write a contract that creates a new contract with the new keyword
+
+### ['Contract to Contract Interaction'](/learn/interfaces/contract-to-contract-interaction)
+
+- Use interfaces to allow a smart contract to call functions in another smart contract
+- Use the `call()` function to interact with another contract without using an interface
+
+### [Events](/learn/events/hardhat-events-sbs)
+
+- Write and trigger an event
+- List common uses of events
+- Understand events vs. smart contract storage
+
+### [Address and Payable in Solidity](/learn/address-and-payable/address-and-payable)
+
+- Differentiate between address and address payable types in Solidity
+- Determine when to use each type appropriately in contract development
+- Employ address payable to send Ether and interact with payable functions
+
+### [Minimal Token](/learn/minimal-tokens/minimal-token-sbs)
+
+- Construct a minimal token and deploy to testnet
+- Identify the properties that make a token a token
+
+### [The ERC-20 Token Standard](/learn/erc-20-token/erc-20-standard)
+
+- Analyze the anatomy of an ERC-20 token
+- Review the formal specification for ERC-20
+
+### [ERC-20 Implementation](/learn/erc-20-token/erc-20-token-sbs)
+
+- Describe OpenZeppelin
+- Import the OpenZeppelin ERC-20 implementation
+- Describe the difference between the ERC-20 standard and OpenZeppelin's ERC20.sol
+- Build and deploy an ERC-20 compliant token
+
+### [The ERC-721 Token Standard](/learn/erc-721-token/erc-721-standard)
+
+- Analyze the anatomy of an ERC-721 token
+- Compare and contrast the technical specifications of ERC-20 and ERC-721
+- Review the formal specification for ERC-721
+
+### [ERC-721 Token](/learn/erc-721-token/erc-721-sbs)
+
+- Analyze the anatomy of an ERC-721 token
+- Compare and contrast the technical specifications of ERC-20 and ERC-721
+- Review the formal specification for ERC-721
+- Build and deploy an ERC-721 compliant token
+- Use an ERC-721 token to control ownership of another data structure
+
+### [Wallet Connectors](/learn/frontend-setup/wallet-connectors)
+
+- Identify the role of a wallet aggregator in an onchain app
+- Debate the pros and cons of using a template
+- Scaffold a new onchain app with RainbowKit
+- Support users of EOAs and the Coinbase Smart Wallet with the same app
+
+### [Building an Onchain App](/learn/frontend-setup/building-an-onchain-app)
+
+- Identify the role of a wallet aggregator in an onchain app
+- Debate the pros and cons of using a template
+- Add a wallet connection to a standard template app
+
+### [The `useAccount` Hook](/learn/reading-and-displaying-data/useAccount)
+
+- Implement the `useAccount` hook to show the user's address, connection state, network, and balance
+- Implement an `isMounted` hook to prevent hydration errors
+
+### [The `useReadContract` Hook](/learn/reading-and-displaying-data/useReadContract)
+
+- Implement wagmi's `useReadContract` hook to fetch data from a smart contract
+- Convert data fetched from a smart contract to information displayed to the user
+- Identify the caveats of reading data from automatically-generated getters
+
+### [Configuring `useReadContract`](/learn/reading-and-displaying-data/configuring-useReadContract)
+
+- Use `useBlockNumber` and the `queryClient` to automatically fetch updates from the blockchain
+- Describe the costs of using the above, and methods to reduce those costs
+- Configure arguments to be passed with a call to a `pure` or `view` smart contract function
+- Call an instance of `useReadContract` on demand
+- Utilize `isLoading` and `isFetching` to improve user experience
+
+### [The `useWriteContract` hook](/learn/writing-to-contracts/useWriteContract)
+
+- Implement wagmi's `useWriteContract` hook to send transactions to a smart contract
+- Configure the options in `useWriteContract`
+- Display the execution, success, or failure of a function with button state changes, and data display
+
+### [The `useSimulateContract` hook](/learn/writing-to-contracts/useSimulateContract)
+
+- Implement wagmi's `useSimulateContract` and `useWriteContract` to send transactions to a smart contract
+- Configure the options in `useSimulateContract` and `useWriteContract`
+- Call a smart contract function on-demand using the write function from `useWriteContract`, with arguments and a value
\ No newline at end of file
diff --git a/_pages/learn/mappings/how-mappings-are-stored-vid.mdx b/_pages/learn/mappings/how-mappings-are-stored-vid.mdx
new file mode 100644
index 00000000..84aee1c3
--- /dev/null
+++ b/_pages/learn/mappings/how-mappings-are-stored-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: How Mappings are Stored
+description: Learn about `msg.sender`.
+hide_table_of_contents: false
+---
+
+# How Mappings are Stored
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/mappings/mappings-exercise.mdx b/_pages/learn/mappings/mappings-exercise.mdx
new file mode 100644
index 00000000..724af816
--- /dev/null
+++ b/_pages/learn/mappings/mappings-exercise.mdx
@@ -0,0 +1,71 @@
+---
+title: Mappings Exercise
+description: Exercise - Demonstrate your knowledge of mappings.
+hide_table_of_contents: false
+---
+
+# Mappings Exercise
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Create a single contract called `FavoriteRecords`. It should not inherit from any other contracts. It should have the following properties:
+
+### State Variables
+
+The contract should have the following state variables. It is **up to you** to decide if any supporting variables are useful.
+
+- A public mapping `approvedRecords`, which returns `true` if an album name has been added as described below, and `false` if it has not
+- A mapping called `userFavorites` that indexes user addresses to a mapping of `string` record names which returns `true` or `false`, depending if the user has marked that album as a favorite
+
+### Loading Approved Albums
+
+Using the method of your choice, load `approvedRecords` with the following:
+
+- Thriller
+- Back in Black
+- The Bodyguard
+- The Dark Side of the Moon
+- Their Greatest Hits (1971-1975)
+- Hotel California
+- Come On Over
+- Rumours
+- Saturday Night Fever
+
+### Get Approved Records
+
+Add a function called `getApprovedRecords`. This function should return a list of all of the names currently indexed in `approvedRecords`.
+
+### Add Record to Favorites
+
+Create a function called `addRecord` that accepts an album name as a parameter. **If** the album is on the approved list, add it to the list under the address of the sender. Otherwise, reject it with a custom error of `NotApproved` with the submitted name as an argument.
+
+### Users' Lists
+
+Write a function called `getUserFavorites` that retrieves the list of favorites for a provided `address memory`.
+
+### Reset My Favorites
+
+Add a function called `resetUserFavorites` that resets `userFavorites` for the sender.
+
+---
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
diff --git a/docs/pages/learn/mappings/mappings-sbs.mdx b/_pages/learn/mappings/mappings-sbs.mdx
similarity index 100%
rename from docs/pages/learn/mappings/mappings-sbs.mdx
rename to _pages/learn/mappings/mappings-sbs.mdx
diff --git a/_pages/learn/mappings/mappings-vid.mdx b/_pages/learn/mappings/mappings-vid.mdx
new file mode 100644
index 00000000..bab69568
--- /dev/null
+++ b/_pages/learn/mappings/mappings-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Mappings
+description: Learn about mappings.
+hide_table_of_contents: false
+---
+
+# Mappings
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/mappings/using-msg-sender-vid.mdx b/_pages/learn/mappings/using-msg-sender-vid.mdx
new file mode 100644
index 00000000..9403dfa3
--- /dev/null
+++ b/_pages/learn/mappings/using-msg-sender-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Using `msg.sender`
+description: Learn about `msg.sender`.
+hide_table_of_contents: false
+---
+
+# Using `msg.sender`
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/minimal-tokens/creating-a-minimal-token-vid.mdx b/_pages/learn/minimal-tokens/creating-a-minimal-token-vid.mdx
new file mode 100644
index 00000000..b2bb1d0f
--- /dev/null
+++ b/_pages/learn/minimal-tokens/creating-a-minimal-token-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Create a Minimal Token
+description: Learn to build a very simple token.
+hide_table_of_contents: false
+---
+
+# Create a Minimal Token
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/minimal-tokens/minimal-token-sbs.mdx b/_pages/learn/minimal-tokens/minimal-token-sbs.mdx
new file mode 100644
index 00000000..dd878885
--- /dev/null
+++ b/_pages/learn/minimal-tokens/minimal-token-sbs.mdx
@@ -0,0 +1,185 @@
+---
+title: Minimal Token
+description: Build your own minimal token.
+hide_table_of_contents: false
+---
+
+# Minimal Token
+
+At their core, tokens are very simple. The technology powering famous NFT collections and fungible tokens worth vast amounts of money simply uses the EVM to keep track of who owns what, and provides a permissionless way for the owner to transfer what they own to someone new.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Construct a minimal token and deploy to testnet
+- Identify the properties that make a token a token
+
+---
+
+## Implementing a Token
+
+The minimal elements needed for a token are pretty basic. Start by creating a contract called `MinimalToken`. Add a `mapping` to relate user addresses to the number of tokens they possess. Finally, add a variable to track `totalSupply`:
+
+
+
+Reveal code
+
+```solidity
+contract MinimalToken {
+ mapping (address => uint) public balances;
+ uint public totalSupply;
+}
+```
+
+
+
+
+
+Add a `constructor` that initializes the `totalSupply` at 3000 and assigns ownership to the contract creator:
+
+
+
+Reveal code
+
+```solidity
+constructor() {
+ totalSupply = 3000;
+
+ balances[msg.sender] = totalSupply;
+}
+```
+
+
+
+
+
+Deploy and test to confirm that the total supply is 3000, and the balance of the first account is as well.
+
+
+
+Update the constructor and hardcode a distribution of the tokens to be evenly split between the first three test accounts:
+
+
+
+Reveal code
+
+```solidity
+constructor() {
+ totalSupply = 3000;
+
+ balances[msg.sender] = totalSupply / 3;
+ balances[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2] = totalSupply / 3;
+ balances[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db] = totalSupply / 3;
+}
+```
+
+
+
+
+
+Redeploy and test again. Now, each of the first three accounts should have 1000 tokens.
+
+
+
+---
+
+## Transferring Tokens
+
+We can set an initial distribution of tokens and we can see balances, but we're still missing a way to allow the owners of these tokens to share them or spend them.
+
+To remediate this, all we need to do is add a function that can update the balances of each party in the transfer.
+
+Add a `function` called `transfer` that accepts an `address` of `_to` and a `uint` for the `_amount`. You don't need to add anything for `_from`, because that should only be `msg.sender`. The function should subtract the `_amount` from the `msg.sender` and add it to `_to`:
+
+
+
+Reveal code
+
+```solidity
+function transfer(address _to, uint _amount) public {
+ balances[msg.sender] -= _amount;
+ balances[_to] += _amount;
+}
+```
+
+
+
+
+
+Double-check that you've switched back to the first address and redeploy. Then, try sending 500 tokens to the second address.
+
+
+
+What happens if you try to transfer more tokens than an account has? Give it a try!
+
+```text
+transact to MinimalToken.transfer pending ...
+transact to MinimalToken.transfer errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Note: The called function should be payable if you send value and the value you send should be less than your current balance.
+Debug the transaction to get more information.
+```
+
+You won't be able to do it, though the `Note:` here is **misleading**. In the EVM, `payable` **only** refers to transfers of the primary token used to pay gas fees: ETH, Base ETH, Sepolia ETH, Matic, etc. It does **not** refer to the balance of our simple token.
+
+Instead, the transaction is reverting because of the built-in overflow/underflow protection. It's not a great programming practice to depend on this, so add an error for `InsufficientTokens` that returns the `newSenderBalance`.
+
+```Solidity
+function transfer(address _to, uint _amount) public {
+ int newSenderBalance = int(balances[msg.sender] - _amount);
+ if (newSenderBalance < 0) {
+ revert InsufficientTokens(newSenderBalance);
+ }
+
+ balances[msg.sender] = uint(newSenderBalance);
+ balances[_to] += _amount;
+}
+```
+
+Try spending too much again. You'll get the same error in Remix:
+
+```text
+transact to MinimalToken.transfer pending ...
+transact to MinimalToken.transfer errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Note: The called function should be payable if you send value and the value you send should be less than your current balance.
+Debug the transaction to get more information.
+```
+
+However, you can use the debug tool to review the error in memory to see that it now matches your custom `error`.
+
+## Destroying Tokens
+
+Tokens can be effectively destroyed by accident, or on purpose. Accidental destruction happens when someone sends a token to an unowned wallet address. While it's possible that some day, some lucky person will create a new wallet and find a pleasant surprise, the most likely outcome is that any given randomly chosen address will never be used, thus no one will ever have the ability to use or transfer those tokens.
+
+Luckily, there are some protections here. Similar to credit card numbers, addresses have a built-in checksum that helps protect against typos. Try it out by trying to transfer tokens to the second Remix address, but change the first character in the address from `A` to `B`. You'll get an error:
+
+```text
+transact to MinimalToken.transfer errored: Error encoding arguments: Error: bad address checksum (argument="address", value="0xBb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", code=INVALID_ARGUMENT, version=address/5.5.0) (argument=null, value="0xBb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", code=INVALID_ARGUMENT, version=abi/5.5.0)
+```
+
+A more guaranteed way to destroy, or _burn_ a token, is to transfer it to the default address `0x0000000000000000000000000000000000000000`. This address is unowned and unownable, making it mathematically impossible to retrieve any tokens that are sent to it. Redeploy and try it out by sending 1000 tokens to the zero address.
+
+The `totalSupply` remains unchanged, and the balance of the zero address is visible, but those tokens are stuck there forever.
+
+
+The [zero address] currently has a balance of more than 11,000 ETH, worth over **20 million dollars**! Its total holding of burned assets is estimated to be worth more than **200 million dollars**!!!
+
+
+
+---
+
+## Conclusion
+
+In this lesson, you've learned to implement a simple token, which is really just a system to store the balance of each address, and a mechanism to transfer them from one wallet to another. You've also learned how to permanently destroy tokens, whether by accident, or on purpose.
+
+---
+
+[zero address]: https://etherscan.io/address/0x0000000000000000000000000000000000000000
diff --git a/_pages/learn/minimal-tokens/minimal-tokens-exercise.mdx b/_pages/learn/minimal-tokens/minimal-tokens-exercise.mdx
new file mode 100644
index 00000000..4c920784
--- /dev/null
+++ b/_pages/learn/minimal-tokens/minimal-tokens-exercise.mdx
@@ -0,0 +1,69 @@
+---
+title: Minimal Tokens Exercise
+description: Exercise - Create your own token!
+hide_table_of_contents: false
+---
+
+# Minimal Tokens Exercise
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Create a contract called `UnburnableToken`. Add the following in storage:
+
+- A public mapping called `balances` to store how many tokens are owned by each address
+- A `public uint` to hold `totalSupply`
+- A `public uint` to hold `totalClaimed`
+- Other variables as necessary to complete the task
+
+Add the following functions.
+
+### Constructor
+
+Add a constructor that sets the total supply of tokens to 100,000,000.
+
+### Claim
+
+Add a `public` function called `claim`. When called, so long as a number of tokens equalling the `totalSupply` have not yet been distributed, any wallet _that has not made a claim previously_ should be able to claim 1000 tokens. If a wallet tries to claim a second time, it should revert with `TokensClaimed`.
+
+The `totalClaimed` should be incremented by the claim amount.
+
+Once all tokens have been claimed, this function should revert with the error `AllTokensClaimed`. (We won't be able to test this, but you'll know if it's there!)
+
+### Safe Transfer
+
+Implement a `public` function called `safeTransfer` that accepts an address `_to` and an `_amount`. It should transfer tokens from the sender to the `_to` address, **only if**:
+
+- That address is not the zero address
+- That address has a balance of greater than zero Base Sepolia Eth
+
+A failure of either of these checks should result in a revert with an `UnsafeTransfer` error, containing the address.
+
+---
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+
+The contract specification contains actions that can only be performed once by a given address. As a result, the unit tests for a passing contract will only be successful the **first** time you test.
+
+**You may need to submit a fresh deployment to pass**
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
diff --git a/_pages/learn/minimal-tokens/transferring-a-minimal-token-vid.mdx b/_pages/learn/minimal-tokens/transferring-a-minimal-token-vid.mdx
new file mode 100644
index 00000000..6d250d2a
--- /dev/null
+++ b/_pages/learn/minimal-tokens/transferring-a-minimal-token-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Transferring a Minimal Token
+description: Explore how tokens are given from one owner to another.
+hide_table_of_contents: false
+---
+
+# Transferring a Minimal Token
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/new-keyword/creating-a-new-contract-vid.mdx b/_pages/learn/new-keyword/creating-a-new-contract-vid.mdx
new file mode 100644
index 00000000..f9795c0b
--- /dev/null
+++ b/_pages/learn/new-keyword/creating-a-new-contract-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Creating a `new` Contract
+description: Use the `new` keyword to create a contract that can create contracts.
+hide_table_of_contents: false
+---
+
+# Creating a `new` Contract
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/new-keyword/new-keyword-exercise.mdx b/_pages/learn/new-keyword/new-keyword-exercise.mdx
new file mode 100644
index 00000000..89a42de8
--- /dev/null
+++ b/_pages/learn/new-keyword/new-keyword-exercise.mdx
@@ -0,0 +1,92 @@
+---
+title: New Exercise
+description: Exercise - Demonstrate your knowledge of the `new` keyword.
+hide_table_of_contents: false
+---
+
+# New Exercise
+
+For this exercise, we're challenging you to build a solution requiring you to use a number of the concepts you've learned so far. Have fun and enjoy!
+
+---
+
+## Contracts
+
+Build a contract that can deploy copies of an address book contract on demand, which allows users to add, remove, and view their contacts.
+
+You'll need to develop two contracts for this exercise and import **at least** one additional contract.
+
+## Imported Contracts
+
+Review the [Ownable] contract from OpenZeppelin. You'll need to use it to solve this exercise.
+
+You may wish to use another familiar contract to help with this challenge.
+
+## AddressBook
+
+Create an `Ownable` contract called `AddressBook`. It includes:
+
+- A `struct` called `Contact` with properties for:
+ - `id`
+ - `firstName`
+ - `lastName`
+ - a `uint` array of `phoneNumbers`
+- Additional storage for `contacts`
+- Any other necessary state variables
+
+It should include the following functions:
+
+### Add Contact
+
+The `addContact` function should be usable only by the owner of the contract. It should take in the necessary arguments to add a given contact's information to `contacts`.
+
+### Delete Contact
+
+The `deleteContact` function should be usable only by the owner and should delete the contact under the supplied `_id` number.
+
+If the `_id` is not found, it should revert with an error called `ContactNotFound` with the supplied id number.
+
+### Get Contact
+
+The `getContact` function returns the contact information of the supplied `_id` number. It reverts to `ContactNotFound` if the contact isn't present.
+
+
+**Question**
+
+For bonus points (that only you will know about), explain why we can't just use the automatically generated getter for `contacts`?
+
+
+
+### Get All Contacts
+
+The `getAllContacts` function returns an array with all of the user's current, non-deleted contacts.
+
+
+You shouldn't use `onlyOwner` for the two _get_ functions. Doing so won't prevent a third party from accessing the information, because all information on the blockchain is public. However, it may give the mistaken impression that information is hidden, which could lead to a security incident.
+
+
+
+## AddressBookFactory
+
+The `AddressBookFactory` contains one function, `deploy`. It creates an instance of `AddressBook` and assigns the caller as the owner of that instance. It then returns the `address` of the newly-created contract.
+
+---
+
+## Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
+
+[Ownable]: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
diff --git a/_pages/learn/new-keyword/new-keyword-sbs.mdx b/_pages/learn/new-keyword/new-keyword-sbs.mdx
new file mode 100644
index 00000000..c514e68b
--- /dev/null
+++ b/_pages/learn/new-keyword/new-keyword-sbs.mdx
@@ -0,0 +1,121 @@
+---
+title: The New Keyword
+description: Learn to create a contract that creates other contracts.
+hide_table_of_contents: false
+---
+
+# The New Keyword
+
+You've seen the `new` keyword and used it to instantiate `memory` arrays with a size based on a variable. You can also use it to write a contract that [creates other contracts].
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Write a contract that creates a new contract with the new keyword
+
+---
+
+## Creating a Simple Contract Factory
+
+A contract factory is a contract that creates other contracts. To start, let's create and interact with a very simple one. Create a new project in Remix and add a file called `ContractFactory.sol`.
+
+### Adding the Template
+
+Imagine you want to create a contract that can store its owner's name and complement it upon request. You can create this contract fairly easily.
+
+
+
+Reveal code
+
+```solidity
+contract Complimenter {
+ string public name;
+
+ constructor(string memory _name) {
+ name = _name;
+ }
+
+ function compliment() public view returns(string memory) {
+ return string.concat("You look great today, ", name);
+ }
+}
+```
+
+
+
+
+
+Deploy and test.
+
+### Creating a Factory
+
+The `Complimenter` contract is a huge success! People love how it makes them feel and you've got customers banging on the doors and windows. Awesome!
+
+The only problem is that it takes time and effort to manually deploy a new version of the contract for each customer. Luckily, there's a way to write a contract that will act as a self-service portal for your customers.
+
+Start by adding a contract called `ComplimenterFactory`. The Remix interface makes things easier if you leave the factory in the same file as `Complimenter`.
+
+Add a function called `CreateComplimenter` that is public, accepts a `string` called `_name`, and returns an `address`.
+
+Creating a new contract is simple: `new Complimenter(_name)`
+
+You can also save the return from that instantiation into a variable. This reference can be used to call public functions in the deployed contract, and can be cast to an address. We can use it to get an easy reference to find the copies made by the factory. The end result should look similar to:
+
+
+
+Reveal code
+
+```solidity
+contract ComplimenterFactory {
+ function CreateComplimenter(string memory _name) public returns (address) {
+ Complimenter newContract = new Complimenter(_name);
+ return address(newContract);
+ }
+}
+```
+
+
+
+
+
+### Testing
+
+Clear the environment if you haven't already, then start by deploying `ComplimenterFactory`. You've been working hard and deserve nice things, so call `CreateComplimenter` with your name.
+
+In the terminal, the _decoded output_ will be the address of the new contract.
+
+```text
+{
+ "0": "address: 0x9e0BC6DB02E5aF99b8868f0b732eb45c956B92dD"
+}
+```
+
+Copy **only** the address.
+
+Switch the _CONTRACT_ to be deployed to `Complimenter`, then paste the address you copied in the field next to the _At Address_ button which is below the _Deploy_ button.
+
+
+
+Click _At Address_ and the instance of `Complimenter` should appear below `ComplimenterFactory`. Test to confirm it works, then try deploying more instances with the factory.
+
+
+
+
+If the deployed contract appears, but is instead a broken copy of the factory, it's because you didn't change the contract in the _CONTRACT_ dropdown above the deploy button.
+
+Remix is trying to interact with `Complimenter` using the _ABI_ from the factory contract, which won't work.
+
+
+
+---
+
+## Conclusion
+
+In this lesson, you learned how to deploy contracts from another contract by using the `new` keyword. You also learned that you look great today!
+
+---
+
+[creates other contracts]: https://docs.soliditylang.org/en/v0.8.17/control-structures.html?#creating-contracts-via-new
diff --git a/docs/pages/learn/reading-and-displaying-data/configuring-useReadContract.mdx b/_pages/learn/reading-and-displaying-data/configuring-useReadContract.mdx
similarity index 99%
rename from docs/pages/learn/reading-and-displaying-data/configuring-useReadContract.mdx
rename to _pages/learn/reading-and-displaying-data/configuring-useReadContract.mdx
index 3232b184..eb4b5e6f 100644
--- a/docs/pages/learn/reading-and-displaying-data/configuring-useReadContract.mdx
+++ b/_pages/learn/reading-and-displaying-data/configuring-useReadContract.mdx
@@ -72,11 +72,10 @@ It works! Unfortunately, you can't really stop here, unless you're working on a
If you were to take the obvious approach of adding a `useReadContract` for every function you wanted data from, and set it to `watch`, things would quickly get out of hand. A single open web page with 15 functions watched in this way will hit rate-limiting in as short as an hour.
-:::info
-
+
Don't do this, either use multi-call via [`useReadContracts`], or consolidate your `view`s into a single function that fetches all the data you need in one call.
+
-:::
Luckily, you have options to control these calls a little better.
diff --git a/docs/pages/learn/reading-and-displaying-data/useAccount.mdx b/_pages/learn/reading-and-displaying-data/useAccount.mdx
similarity index 100%
rename from docs/pages/learn/reading-and-displaying-data/useAccount.mdx
rename to _pages/learn/reading-and-displaying-data/useAccount.mdx
diff --git a/docs/pages/learn/reading-and-displaying-data/useReadContract.mdx b/_pages/learn/reading-and-displaying-data/useReadContract.mdx
similarity index 99%
rename from docs/pages/learn/reading-and-displaying-data/useReadContract.mdx
rename to _pages/learn/reading-and-displaying-data/useReadContract.mdx
index 31a24068..9699179d 100644
--- a/docs/pages/learn/reading-and-displaying-data/useReadContract.mdx
+++ b/_pages/learn/reading-and-displaying-data/useReadContract.mdx
@@ -28,8 +28,7 @@ The contract creates a very simple DAO, in which users can create issues and vot
But it makes it much easier to test!
-:::caution
-
+
If you're using your own contract, please redeploy it with the following `view` functions:
```solidity
@@ -49,8 +48,8 @@ function getAllIssues() public view returns(ReturnableIssue[] memory) {
```
**You also need to make the `getIssue` function `public`. The original spec called for it to be `external`.**
+
-:::
### Create Demo Issues
@@ -122,11 +121,10 @@ interface Issue {
}
```
-:::warning
-
+
Be very careful here! `bigint` is the name of the type, `BigInt` is the name of the constructor for that type. If you incorrectly use the constructor as the type, much of your code will still work, but other parts will express very confusing bugs.
+
-:::
Now, import `useState` and add a state variable to hold your list of `Issue`s.
@@ -283,11 +281,12 @@ In this guide, you've learned how to use the `useReadContract` hook to call `pur
Use this contract if you don't have your own from the [ERC 20 Tokens Exercise]. You can also use this if you want to cheat to get that badge. Doing so would be silly though!
-:::caution
+
If you use your own contract, redeploy it with the `numberOfIssues` and `getAllIssues` functions from the bottom of the contract below. We'll need this for our first pass solution for getting all the `Issues` in the contract.
**You also need to make the `getIssue` function `public`. The original spec called for it to be `external`.**
-:::
+
+
```Solidity
// SPDX-License-Identifier: MIT
diff --git a/_pages/learn/storage/how-storage-works-video.mdx b/_pages/learn/storage/how-storage-works-video.mdx
new file mode 100644
index 00000000..4be58f49
--- /dev/null
+++ b/_pages/learn/storage/how-storage-works-video.mdx
@@ -0,0 +1,11 @@
+---
+title: How Storage Works
+description: Learn how storage works in the EVM.
+hide_table_of_contents: false
+---
+
+# How Storage Works
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/pages/learn/storage/how-storage-works.mdx b/_pages/learn/storage/how-storage-works.mdx
similarity index 100%
rename from docs/pages/learn/storage/how-storage-works.mdx
rename to _pages/learn/storage/how-storage-works.mdx
diff --git a/_pages/learn/storage/simple-storage-sbs.mdx b/_pages/learn/storage/simple-storage-sbs.mdx
new file mode 100644
index 00000000..453a160e
--- /dev/null
+++ b/_pages/learn/storage/simple-storage-sbs.mdx
@@ -0,0 +1,247 @@
+---
+title: Storing Data
+description: Learn how to Store data on the blockchain.
+hide_table_of_contents: false
+---
+
+# Storing Data
+
+Ultimately, the power of the blockchain is that anyone can store their data on it via the `storage` in a smart contract. In this step-by-step guide, you'll learn how to access and use the `storage` data location.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Use the constructor to initialize a variable
+- Access the data in a public variable with the automatically generated getter
+- Order variable declarations to use storage efficiently
+
+---
+
+## Simple Storage Contract
+
+Create a contract called `SimpleStorage`.
+
+### Add a Storage Variable
+
+In Solidity, variables declared at the class level are automatically `storage` variables. Create a variable to store the age of a person and another to store the number of cars that they own. Give `age` an initial value of your choosing, but don't make an assignment for `cars`;
+
+
+
+Reveal code
+
+```solidity
+contract SimpleStorage {
+ uint8 age = 41;
+ uint8 cars;
+}
+```
+
+
+
+
+
+Because the age of a person, or the number of cars they own, is unlikely to be greater than 255, we can use a `uint8` for each of these. For types that are smaller than 32 bytes, multiple variables of the same type will be [packed] in the same storage slot. For this to work, the variables must be declared together.
+
+```solidity
+// These variables take advantage of packing
+uint8 first;
+uint8 second;
+uint third;
+
+// These variables DO NOT take advantage of packing and should be reordered
+uint8 fourth;
+uint fifth;
+uint8 sixth;
+```
+
+### Initializing a Value with the Constructor
+
+You may add a `constructor` function to your contract. Similar to other languages, this function is called exactly once, when the contract is deployed. The constructor may have parameters, but it does not require them.
+
+You can use the constructor to perform various setup tasks. For example, the constructor for the _ERC-721_ token that is the underlying mechanism for most NFTs uses the constructor to set up the name and symbol for the token.
+
+Create a constructor function and use it to assign the value of your choosing to `cars`.
+
+
+
+Reveal code
+
+```solidity
+constructor() {
+ cars = 1;
+}
+```
+
+
+
+### Accessing State Variables
+
+Deploy your contract in Remix. It should work fine, but you'll have one problem: there isn't a way to see if the variables have the expected values!
+
+You could solve this by writing functions that return the values in your state variables, but you don't need to. The Solidity compiler automatically creates getters for all `public` variables.
+
+Add the `public` keyword to both variables. Unlike most languages, `public` goes **after** the type declaration. Your contract should now be similar to:
+
+
+
+Reveal code
+
+```solidity
+contract SimpleStorage {
+ uint8 public age = 41;
+ uint8 public cars;
+ constructor() {
+ cars = 1;
+ }
+}
+```
+
+
+
+
+
+Redeploy your contract and test to confirm.
+
+---
+
+## Setting a State Variable with a Function
+
+Good news! Our user bought a second car! The only problem is that we don't have a way to update the number of `cars` stored.
+
+### Add a Function to Update `cars`
+
+Before writing the function, let's think about design considerations for this feature. At any point in time, a user could:
+
+- Buy or otherwise acquire a new car
+- Get several new cars all at once (Woohoo!)
+- Sell or give away one or more cars (😞)
+
+Given this wide variety of conditions, **a** good approach would be to handle calculating the correct number of cars on the front end, and passing the updated value to the back end.
+
+To meet this need, we can write a `public` function that takes a `uint8` for `_numberOfCars` and then simply assigns that value to the state variable `cars`. Because this function modifies state, it **does not** need `pure` or `view`. It isn't either of those.
+
+
+
+Reveal code
+
+```solidity
+function updateNumberOfCars(uint8 _numberOfCars) public {
+ cars = _numberOfCars;
+}
+```
+
+
+
+
+
+Deploy and test to make sure it works as expected.
+
+
+While packing variables can save on gas costs, it can also increase them. The EVM operates on 32 bytes at a time, so it will take additional steps to reduce the size of the element for storage.
+
+Furthermore, the savings in writing to storage only apply when writing multiple values in the same slot at the same time.
+
+Review the **Warning** in the [layout] section of the docs for more details!
+
+
+
+### Add a Function to Update `age`
+
+It would also be good to be able to update the `age` value. This problem has slightly different considerations. Sadly, `age` will never go down. It should also probably only go up by one year for each update. The `++` operator works in Solidity, so we can use that to create a function that simply increments age when called.
+
+
+
+Reveal code
+
+
+```solidity
+function increaseAge() public {
+ age++;
+}
+```
+
+
+
+
+
+
+But what if a user calls this function by mistake? Good point!
+
+On your own, add a function called `adminSetAge` that can set the `age` to a specified value.
+
+### Refactor the Constructor to Accept Arguments
+
+We've got one problem remaining with this contract. What if your user has a different `age` or number of `cars` than what you've hardcoded into the contract?
+
+As mentioned above, the `constructor` **can** take arguments and use them during deployment. Let's refactor the contract to set the two state variables in the constructor based on provided values.
+
+
+
+Reveal code
+
+```solidity
+contract SimpleStorage {
+ uint8 public age;
+ uint8 public cars;
+ constructor(uint8 _age, uint8 _cars) {
+ age = _age;
+ cars = _cars;
+ }
+}
+```
+
+
+
+
+
+Redeploy your contract. Note that now you have added parameters to the `constructor`, you'll have to provide them during deployment.
+
+
+
+Once completed, your contract should be similar to:
+
+
+
+
+Reveal code
+
+```solidity
+contract SimpleStorage {
+ uint8 public age;
+ uint8 public cars;
+ constructor(uint8 _age, uint8 _cars) {
+ age = _age;
+ cars = _cars;
+ }
+
+ function updateNumberOfCars(uint8 _numberOfCars) public {
+ cars = _numberOfCars;
+ }
+
+ function increaseAge() public {
+ age++;
+ }
+
+ function adminSetAge(uint8 _age) public {
+ age = _age;
+ }
+}
+```
+
+
+
+
+
+---
+
+## Conclusion
+
+In this lesson, you've explored how to persistently store values on the blockchain. You've also practiced updating them from functions. Finally, you've learned how to use the constructor to perform setup functionality during deployment, with and without parameters.
+
+---
+
+[packed]: https://docs.soliditylang.org/en/v0.8.17/internals/layout_in_storage.html
+[layout]: https://docs.soliditylang.org/en/v0.8.17/internals/layout_in_storage.html
diff --git a/_pages/learn/storage/simple-storage-video.mdx b/_pages/learn/storage/simple-storage-video.mdx
new file mode 100644
index 00000000..052dc035
--- /dev/null
+++ b/_pages/learn/storage/simple-storage-video.mdx
@@ -0,0 +1,11 @@
+---
+title: Simple Storage
+description: Store data on the blockchain.
+hide_table_of_contents: false
+---
+
+# Simple Storage
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/storage/storage-exercise.mdx b/_pages/learn/storage/storage-exercise.mdx
new file mode 100644
index 00000000..f6a96d73
--- /dev/null
+++ b/_pages/learn/storage/storage-exercise.mdx
@@ -0,0 +1,115 @@
+---
+title: Storage Exercise
+description: Exercise - Demonstrate your knowledge of storage.
+hide_table_of_contents: false
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+# Storage Exercise
+
+Create a contract that adheres to the following specifications:
+
+---
+
+## Contract
+
+Create a single contract called `EmployeeStorage`. It should not inherit from any other contracts. It should have the following functions:
+
+### State Variables
+
+The contract should have the following state variables, optimized to minimize storage:
+
+- A private variable `shares` storing the employee's number of shares owned
+ - Employees with more than 5,000 shares count as directors and are stored in another contract
+- Public variable `name` which stores the employee's name
+- A private variable `salary` storing the employee's salary
+ - Salaries range from 0 to 1,000,000 dollars
+- A public variable `idNumber` storing the employee's ID number
+ - Employee numbers are not sequential, so this field should allow any number up to 2^256-1
+
+### Constructor
+
+When deploying the contract, utilize the `constructor` to set:
+
+- `shares`
+- `name`
+- `salary`
+- `idNumber`
+
+For the purposes of the test, you **must** deploy the contract with the following values:
+
+- `shares` - 1000
+- `name` - Pat
+- `salary` - 50000
+- `idNumber` - 112358132134
+
+### View Salary and View Shares
+
+
+In the world of blockchain, nothing is ever secret!\* `private` variables prevent other contracts from reading the value. You should use them as a part of clean programming practices, but marking a variable as private **does _not_ hide the value**. All data is trivially available to anyone who knows how to fetch data from the chain.
+
+\*You can make clever use of encryption though!
+
+
+
+Write a function called `viewSalary` that returns the value in `salary`.
+
+Write a function called `viewShares` that returns the value in `shares`.
+
+### Grant Shares
+
+Add a public function called `grantShares` that increases the number of shares allocated to an employee by `_newShares`. It should:
+
+- Add the provided number of shares to the `shares`
+ - If this would result in more than 5000 shares, revert with a custom error called `TooManyShares` that returns the number of shares the employee would have with the new amount added
+ - If the number of `_newShares` is greater than 5000, revert with a string message, "Too many shares"
+
+### Check for Packing and Debug Reset Shares
+
+Add the following function to your contract exactly as written below.
+
+```solidity
+/**
+* Do not modify this function. It is used to enable the unit test for this pin
+* to check whether or not you have configured your storage variables to make
+* use of packing.
+*
+* If you wish to cheat, simply modify this function to always return `0`
+* I'm not your boss ¯\_(ツ)_/¯
+*
+* Fair warning though, if you do cheat, it will be on the blockchain having been
+* deployed by your wallet....FOREVER!
+*/
+function checkForPacking(uint _slot) public view returns (uint r) {
+ assembly {
+ r := sload (_slot)
+ }
+}
+
+/**
+* Warning: Anyone can use this function at any time!
+*/
+function debugResetShares() public {
+ shares = 1000;
+}
+```
+
+---
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
diff --git a/_pages/learn/structs/structs-exercise.mdx b/_pages/learn/structs/structs-exercise.mdx
new file mode 100644
index 00000000..0eb4dc8e
--- /dev/null
+++ b/_pages/learn/structs/structs-exercise.mdx
@@ -0,0 +1,85 @@
+---
+title: Structs Exercise
+description: Exercise - Demonstrate your knowledge of structs.
+hide_table_of_contents: false
+---
+
+# Structs Exercise
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Create a contract called `GarageManager`. Add the following in storage:
+
+- A public mapping called `garage` to store a list of `Car`s (described below), indexed by address
+
+Add the following types and functions.
+
+### Car Struct
+
+Implement a `struct` called `Car`. It should store the following properties:
+
+- `make`
+- `model`
+- `color`
+- `numberOfDoors`
+
+### Add Car Garage
+
+Add a function called `addCar` that adds a car to the user's collection in the `garage`. It should:
+
+- Use `msg.sender` to determine the owner
+- Accept arguments for make, model, color, and number of doors, and use those to create a new instance of `Car`
+- Add that `Car` to the `garage` under the user's address
+
+### Get All Cars for the Calling User
+
+Add a function called `getMyCars`. It should return an array with all of the cars owned by the calling user.
+
+### Get All Cars for Any User
+
+Add a function called `getUserCars`. It should return an array with all of the cars for any given `address`.
+
+### Update Car
+
+Add a function called `updateCar`. It should accept a `uint` for the index of the car to be updated, and arguments for all of the `Car` types.
+
+If the sender doesn't have a car at that index, it should revert with a custom `error` `BadCarIndex` and the index provided.
+
+Otherwise, it should update that entry to the new properties.
+
+### Reset My Garage
+
+Add a public function called `resetMyGarage`. It should delete the entry in `garage` for the sender.
+
+---
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+import CafeUnitTest from '../../../components/CafeUnitTest/index.jsx'
+
+
+
+
+
+
+ ⚠️ Spoiler Alert: Open only if tests fail
+
+
+Ensure your variable sizes align with their intended use, and consider the nuances of packing in Solidity. Resources: [Solidity - Layout in Storage](https://docs.soliditylang.org/en/v0.8.17/internals/layout_in_storage.html#layout-of-state-variables-in-storage), [Variables in Struct](https://docs.base.org/base-learn/docs/structs/structs-sbs#setting-up-the-struct)
+
+
diff --git a/docs/pages/learn/structs/structs-sbs.mdx b/_pages/learn/structs/structs-sbs.mdx
similarity index 100%
rename from docs/pages/learn/structs/structs-sbs.mdx
rename to _pages/learn/structs/structs-sbs.mdx
diff --git a/_pages/learn/structs/structs-vid.mdx b/_pages/learn/structs/structs-vid.mdx
new file mode 100644
index 00000000..f90ed8d6
--- /dev/null
+++ b/_pages/learn/structs/structs-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Structs
+description: Create user-defined types.
+hide_table_of_contents: false
+---
+
+# Structs
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/_pages/learn/welcome.mdx b/_pages/learn/welcome.mdx
new file mode 100644
index 00000000..1550f7f1
--- /dev/null
+++ b/_pages/learn/welcome.mdx
@@ -0,0 +1,40 @@
+---
+title: Learn to Build Smart Contracts and Onchain Apps
+description: Base Learn is a comprehensive, free guide to learning smart contract and onchain app development.
+keywords:
+ [
+ Smart contract development,
+ Onchain app development,
+ Solidity programming,
+ EVM-compatible chains,
+ Base blockchain,
+ Ethereum smart contracts,
+ Learn smart contract development,
+ Deploy smart contracts on Base,
+ ]
+hide_table_of_contents: false
+image: /img/base-learn-open-graph.png
+---
+
+import LearningObjectives from '/snippets/learning-objectives.mdx';
+
+# Welcome
+
+
+
+## Introduction
+
+Welcome to Base Learn, your guide to learning smart contract development. Base Learn's curriculum has been expertly crafted to equip you with the skills and knowledge needed to build and deploy smart contracts on Base, or any EVM-compatible chain, including Ethereum, Optimism, and many more. Plus, you'll be eligible to earn NFTs as you complete each module, showcasing your mastery of the material.
+
+Whether you're a curious novice or a seasoned pro looking to stay ahead of the game, our dynamic lessons cater to all levels of experience. You can start with the basics and work your way up, or dive straight into the more advanced concepts and push your limits to new heights.
+
+Begin your journey today!
+
+## What you can learn in this program
+
+Base Learn covers the following topics. If you're looking for quickstarts, or deeper guides on advanced topics, check out our [Base Builder Tutorials]!
+
+
+
+
+[Base Builder Tutorials]: https://docs.base.org/tutorials/
diff --git a/docs/pages/learn/writing-to-contracts/useSimulateContract.mdx b/_pages/learn/writing-to-contracts/useSimulateContract.mdx
similarity index 100%
rename from docs/pages/learn/writing-to-contracts/useSimulateContract.mdx
rename to _pages/learn/writing-to-contracts/useSimulateContract.mdx
diff --git a/docs/pages/learn/writing-to-contracts/useWriteContract.mdx b/_pages/learn/writing-to-contracts/useWriteContract.mdx
similarity index 99%
rename from docs/pages/learn/writing-to-contracts/useWriteContract.mdx
rename to _pages/learn/writing-to-contracts/useWriteContract.mdx
index f86ec38b..df01c3b8 100644
--- a/docs/pages/learn/writing-to-contracts/useWriteContract.mdx
+++ b/_pages/learn/writing-to-contracts/useWriteContract.mdx
@@ -22,19 +22,17 @@ By the end of this guide you should be able to:
## Sending a Transaction to the Blockchain
-:::warning
-
+
In this step-by-step, you're going to start with the [`useWriteContract`] hook. You probably won't want to use this method in production. In the next step-by-step, we'll show you the [`useSimulateContract`] hook, how it works with `useWriteContract`, and how you can use it to create a better user experience.
Exploring them separately will highlight the functionality provided by the prepare hook.
+
-:::
-
-:::caution
+
In this module, you'll extend the onchain app you build in the previous module, [Reading and Displaying Data].
+
-:::
You've built an app that can read from your Simple DAO smart contract, but so far, you've used BaseScan to send transactions that call your write functions. You can use the [`useWriteContract`] hook in a similar way to call those functions directly from your app.
@@ -76,11 +74,10 @@ useEffect(() => {
}, [blockNumber, queryClient]);
```
-:::caution
-
+
Remember, this is an expensive method to watch for data to change on the blockchain. In this case, a more production-suitable solution might be to call `balanceOf` after the user has done something that might change the balance.
+
-:::
Set the `return` for your component to display this balance to the user:
diff --git a/docs/pages/privacy-policy.md b/_pages/privacy-policy.mdx
similarity index 100%
rename from docs/pages/privacy-policy.md
rename to _pages/privacy-policy.mdx
diff --git a/_pages/quickstart.mdx b/_pages/quickstart.mdx
new file mode 100644
index 00000000..e509d250
--- /dev/null
+++ b/_pages/quickstart.mdx
@@ -0,0 +1,250 @@
+---
+title: Quickstart
+description: Deploy on Base and build your first onchain app.
+---
+
+# Quickstart
+
+Welcome to the Base quickstart guide! In this walkthrough, we'll create a simple onchain app from start to finish. Whether you're a seasoned developer or just starting out, this guide has got you covered.
+
+## What You'll Achieve
+
+By the end of this quickstart, you'll have built an onchain app by:
+
+- Configuring your development environment
+- Deploying your smart contracts to Base
+- Interacting with your deployed contracts from the frontend
+
+Our simple app will be an onchain tally app which lets you add to a total tally, stored onchain, by pressing a button.
+
+
+**Why Base?**
+
+Base is a fast, low-cost, builder-friendly Ethereum L2 built to bring the next billion users onchain. By following this guide, you'll join a vibrant ecosystem of developers, creators, and innovators who are building a global onchain economy.
+
+
+
+## Set Up Your Development Environment
+
+
+
+ OnchainKit is a library of ready-to-use React components and Typescript utilities for building onchain apps. Run the following command in your terminal and follow the prompts to bootstrap your project.
+
+ ```bash [Terminal]
+ npm create onchain@latest
+ ```
+
+ The prompots will ask you for a CDP API Key which you can get [here](https://portal.cdp.coinbase.com/projects/api-keys/client-key).
+
+ Once you've gone through the prompts, you'll have a new project directory with a basic OnchainKit app. Run the following to see it live.
+
+ ```bash [Terminal]
+ cd my-onchainkit-app
+ npm install
+ npm run dev
+ ```
+
+ You should see the following screen.
+
+
+
+ Once we've deployed our contracts, we'll add a button that lets us interact with our contracts.
+
+
+
+ The total tally will be stored onchain in a smart contract. We'll use the Foundry framework to deploy our contract to the Base Sepolia testnet.
+
+ 1. Create a new "contracts" folder in the root of your project
+
+ ```bash [Terminal]
+ mkdir contracts && cd contracts
+ ```
+
+ 2. Install and initialize Foundry
+
+ ```bash [Terminal]
+ curl -L https://foundry.paradigm.xyz | bash
+ foundryup
+ forge init --no-git
+ ```
+
+ Open the project and find the `Counter.sol` contract file in the `/contracts/src` folder. You'll find the simple logic for our tally app.
+
+
+ **--no-git**
+
+ Because `contracts` is a folder in our project, we don't want to initialize a separate git repository for it, so we add the `--no-git` flag.
+
+
+
+ To deploy your smart contracts to Base, you need two key components:
+
+ 1. A node connection to interact with the Base network
+ 2. A funded private key to deploy the contract
+
+ Let's set up both of these:
+
+ - Create a `.env` file in your `contracts` directory and add the Base and Base Sepolia RPC URLs
+
+ ```bash [contracts/.env]
+ BASE_RPC_URL="https://mainnet.base.org"
+ BASE_SEPOLIA_RPC_URL="https://sepolia.base.org"
+ ```
+
+ -Load your environment variables
+
+ ```bash [Terminal]
+ source .env
+ ```
+
+
+ **Base Sepolia**
+
+ Base Sepolia is the test network for Base, which we will use for the rest of this guide. You can obtain free Base Sepolia ETH from one of the [faucets listed here](/chain/network-faucets).
+
+
+
+ A private key with testnet funds is required to deploy the contract. You can generate a fresh private key [here](https://visualkey.link/).
+
+ 1. Store your private key in Foundry's secure keystore
+
+ ```bash [Terminal]
+ cast wallet import deployer --interactive
+ ```
+
+ 2. When prompted enter your private key and a password.
+
+ Your private key is stored in `~/.foundry/keystores` which is not tracked by git.
+
+
+ Never share or commit your private key. Always keep it secure and handle with care.
+
+
+
+
+## Deploy Your Contracts
+
+Now that your environment is set up, let's deploy your contracts to Base Sepolia. The foundry project provides a deploy script that will deploy the Counter.sol contract.
+
+
+
+ 1. Use the following command to compile and deploy your contract
+
+ ```bash [Terminal]
+ forge create ./src/Counter.sol:Counter --rpc-url $BASE_SEPOLIA_RPC_URL --account deployer
+ ```
+
+ Note the format of the contract being deployed is `:`.
+
+
+ After successful deployment, the transaction hash will be printed to the console output
+
+ Copy the deployed contract address and add it to your `.env` file
+
+ ```bash
+ COUNTER_CONTRACT_ADDRESS="0x..."
+ ```
+
+
+
+ ```bash [Terminal]
+ source .env
+ ```
+
+
+ To ensure your contract was deployed successfully:
+
+ 1. Check the transaction on [Sepolia Basescan](https://sepolia.basescan.org/).
+ 2. Use the `cast` command to interact with your deployed contract from the command line
+
+ ```bash
+ cast call $COUNTER_CONTRACT_ADDRESS "number()(uint256)" --rpc-url $BASE_SEPOLIA_RPC_URL
+ ```
+
+ This will return the initial value of the Counter contract's `number` storage variable, which will be `0`.
+
+
+
+**Congratulations! You've deployed your smart contract to Base Sepolia!**
+
+Now lets connect the frontend to interact with your recently deployed contract.
+
+## Interacting with your contract
+
+To interact with the smart contract logic, we need to submit an onchain transaction. We can do this easily with the `Transaction` component. This is a simplified version of the `Transaction` component, designed to streamline the integration process. Instead of manually defining each subcomponent and prop, we can use this shorthand version which renders our suggested implementation of the component and includes the `TransactionButton` and `TransactionToast` components.
+
+
+
+ Lets add the `Transaction` component to our `page.tsx` file. Delete the existing content in the `main` tag and replace it with the snippet below.
+
+ ```tsx twoslash [page.tsx]
+ // @noErrors: 2307 - Cannot find module '@/calls'
+ import { Transaction } from '@coinbase/onchainkit/transaction';
+ import { calls } from '@/calls';
+
+
+
+ ;
+ ```
+
+
+ In the previous code snippet, you'll see we imported `calls` from the `calls.ts` file. This file provides the details needed to interact with our contract and call the `increment` function. Create a new `calls.ts` file in the same folder as your `page.tsx` file and add the following code.
+
+ ```ts twoslash [calls.ts]
+ const counterContractAddress = '0x...'; // add your contract address here
+ const counterContractAbi = [
+ {
+ type: 'function',
+ name: 'increment',
+ inputs: [],
+ outputs: [],
+ stateMutability: 'nonpayable',
+ },
+ ] as const;
+
+ export const calls = [
+ {
+ address: counterContractAddress,
+ abi: counterContractAbi,
+ functionName: 'increment',
+ args: [],
+ },
+ ];
+ ```
+
+
+ **Contract Address**
+
+ The `calls.ts` file contains the details of the contract interaction, including the contract address, which we saved in the previous step.
+
+
+
+ Now, when you connect a wallet and click on the `Transact` button and approve the transaction, it will increment the tally onchain by one.
+
+ We can verify that the onchain count took place onchain by once again using `cast` to call the `number` function on our contract.
+
+ ```bash [Terminal]
+ cast call $COUNTER_CONTRACT_ADDRESS "number()(uint256)" --rpc-url $BASE_SEPOLIA_RPC_URL
+ ```
+
+ If the transaction was successful, the tally should have incremented by one!
+
+
+
+We now have a working onchain tally app! While the example is simple, it illustrates the end to end process of building on onchain app. We:
+
+- Configured a project with frontend and onchain infrastructure
+- Deployed a smart contract to Base Sepolia
+- Interacted with the contract from the frontend
+
+## Further Improvements
+
+This is just the beginning. There are many ways we can improve upon this app. For example, we could:
+
+- Make the `increment` transaction gasless by integrating with [Paymaster](/builderkits/onchainkit/transaction/transaction#sponsor-with-paymaster-capabilities)
+- Improve the wallet connection and sign up flow with the [WalletModal](/builderkits/onchainkit/wallet/wallet-modal) component
+- Add onchain [Identity](/builderkits/onchainkit/identity/identity) so we know who added the most recent tally
diff --git a/docs/pages/terms-of-service.md b/_pages/terms-of-service.mdx
similarity index 100%
rename from docs/pages/terms-of-service.md
rename to _pages/terms-of-service.mdx
diff --git a/_pages/use-cases/accept-crypto-payments.mdx b/_pages/use-cases/accept-crypto-payments.mdx
new file mode 100644
index 00000000..48cc15eb
--- /dev/null
+++ b/_pages/use-cases/accept-crypto-payments.mdx
@@ -0,0 +1,231 @@
+---
+title: 'Accept Crypto Payments'
+description: Learn how to integrate Coinbase Commerce payments into your application using OnchainKit.
+authors:
+ - hughescoin
+---
+
+# Accept Crypto Payments with Coinbase Commerce & OnchainKit
+
+Accepting crypto payments can help you **eliminate traditional credit card fees** and **avoid costly chargebacks**, giving you a faster, more global payment experience. In this guide, you'll learn how to quickly integrate Coinbase Commerce and OnchainKit to accept crypto payments for products or services in your application.
+
+## Objectives
+
+By following this guide, you will learn how to:
+
+- Create or configure a product in **Coinbase Commerce**
+- Configure your **OnchainKit** environment
+- Implement a checkout flow for accepting crypto payments
+- Deploy and test your app to confirm the payment flow
+
+
+## Prerequisites
+
+### 1. Coinbase Commerce Account
+
+[Coinbase Commerce](https://beta.commerce.coinbase.com/sign-up) allows you to accept cryptocurrency payments globally. Sign up to get started.
+
+### 2. Coinbase Developer Platform (CDP) Account
+
+[Coinbase Developer Platform](https://www.coinbase.com/cloud) (CDP) provides the tools and APIs you need for integration.
+
+### 3. Reown (WalletConnect) Account
+
+[Reown](https://cloud.reown.com/) (formerly WalletConnect) provides a secure way to connect wallets across different devices and platforms.
+
+
+## Step-by-Step Setup
+
+
+
+ 1. **Log in** to your Coinbase Commerce account.
+ 2. Go to the [product creation page](https://beta.commerce.coinbase.com/products).
+ 3. **Add product details** (name, description, price).
+ 4. Click **Create product**.
+ 5. Once created, select **View product** and copy the **UUID** from the URL.
+
+ 
+
+ **Tip**: Store the product UUID as an environment variable in your `.env` file. This makes it easier to reference safely in your code.
+
+
+ Use the official **OnchainKit app template** to bootstrap your project:
+
+
+ ```bash [Bun]
+ git clone https://github.com/coinbase/onchainkit-app-template.git
+ cd onchainkit-app-template
+ bun i
+ ```
+
+
+ ```bash [npm]
+ git clone https://github.com/coinbase/onchainkit-app-template.git
+ cd onchainkit-app-template
+ npm install
+ ```
+
+
+ ```bash [Yarn]
+ git clone https://github.com/coinbase/onchainkit-app-template.git
+ cd onchainkit-app-template
+ yarn
+ ```
+
+
+
+ In the project folder, open (or create) your `.env` file and add:
+
+ ```bash
+ NEXT_PUBLIC_WC_PROJECT_ID=
+ NEXT_TELEMETRY_DISABLED=1
+ NEXT_PUBLIC_ONCHAINKIT_API_KEY=
+ NEXT_PUBLIC_PRODUCT_ID=
+ ```
+
+ > **Note**:
+ > - `NEXT_PUBLIC_PRODUCT_ID` should be set to the **UUID** from your Coinbase Commerce product.
+ > - `NEXT_PUBLIC_ONCHAINKIT_API_KEY` should be your **CDP** API key.
+ > - `NEXT_PUBLIC_WC_PROJECT_ID` is your Reown (WalletConnect) project ID.
+
+
+
+ Open your Wagmi configuration file (e.g., `src/app/wagmi.ts` or similar) and **after** your `useMemo()` hook, add:
+
+ ```diff
+ // other Wagmi config
+ + coinbaseWallet.preference = 'smartWalletOnly';
+ ```
+
+ This ensures Coinbase Wallet only connects to **smart wallets**.
+
+ In `src/app/components/OnchainProviders.tsx`, set up **OnchainKitProvider** to use your **CDP** API key and **Base** as the chain:
+
+ ```typescript
+ 'use client';
+ import { OnchainKitProvider } from '@coinbase/onchainkit';
+ import { RainbowKitProvider } from '@rainbow-me/rainbowkit';
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+ import type { ReactNode } from 'react';
+ import { base } from 'viem/chains';
+ import { WagmiProvider } from 'wagmi';
+ import { useWagmiConfig } from '../wagmi';
+
+ type Props = { children: ReactNode };
+ const queryClient = new QueryClient();
+
+ function OnchainProviders({ children }: Props) {
+ const wagmiConfig = useWagmiConfig();
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ );
+ }
+
+ export default OnchainProviders;
+ ```
+
+ Finally, update your `Config.ts` (or similar config file) to read from environment variables and match your hosted URL:
+
+ ```typescript
+ export const NEXT_PUBLIC_URL =
+ process.env.NODE_ENV === 'development'
+ ? 'http://localhost:3000'
+ : 'https://based-jerseys.vercel.app';
+
+ export const NEXT_PUBLIC_CDP_API_KEY =
+ process.env.NEXT_PUBLIC_ONCHAINKIT_API_KEY;
+
+ export const NEXT_PUBLIC_WC_PROJECT_ID =
+ process.env.NEXT_PUBLIC_WC_PROJECT_ID;
+ ```
+
+
+
+ 1. **Open** `src/app/page.tsx` (or similar entry page).
+ 2. **Import** the necessary components:
+
+ ```typescript
+ import { Checkout, CheckoutButton, CheckoutStatus } from '@coinbase/onchainkit/checkout';
+ import Image from 'next/image';
+
+ const productId = process.env.NEXT_PUBLIC_PRODUCT_ID;
+ ```
+
+ 3. **Add an image** of your product or service in the `public` folder (e.g., `/public/based-jersey-front.jpeg`).
+ 4. **Display** that image and conditionally render the checkout UI only when a wallet is connected:
+
+ ```jsx
+
+
+
+ {address ? (
+
+
+
+
+ ) : (
+
+ )}
+
+
+ ```
+
+
+ Use **conditional rendering** to avoid errors when no wallet address is detected.
+
+
+
+
+ 1. **Run** the development server:
+
+
+ ```bash [Bun]
+ bun run dev
+ ```
+
+ ```bash [npm]
+ npm run dev
+ ```
+
+ ```bash [Yarn]
+ yarn dev
+ ```
+
+
+
+ 2. **Visit** `http://localhost:3000` to confirm that the payment button works for your product or service.
+ 3. **Deploy** with your preferred hosting provider (e.g., Vercel).
+
+ 
+
+
+
+## Conclusion
+
+Congratulations! You've successfully **integrated Coinbase Commerce** and **OnchainKit** into your application, enabling crypto payments. By providing this option, you can:
+
+- Eliminate traditional payment fees and chargebacks
+- Expand your audience to crypto users worldwide
+- Easily scale your offerings for more products and services
+
+**Happy building** and enjoy the benefits of a global, decentralized payment system on Base!
+
+
+[Passkeys]: https://www.coinbase.com/blog/introducing-passkeys-a-safer-and-easier-way-to-sign-in-to-coinbase
+[Reown]: https://cloud.reown.com/
+[product creation page]: https://beta.commerce.coinbase.com/products
diff --git a/_pages/use-cases/ai-instructions/eliza.mdx b/_pages/use-cases/ai-instructions/eliza.mdx
new file mode 100644
index 00000000..601bda5c
--- /dev/null
+++ b/_pages/use-cases/ai-instructions/eliza.mdx
@@ -0,0 +1,23 @@
+---
+title: "Edit .env with your own values"
+---
+
+
+
+## Eliza Framework Setup
+
+The fastest way to get started with Eliza is by using the `create-agentkit-app` CLI:
+
+
+
+```bash [Terminal]
+npx create-agentkit-app my-agent
+cd my-agent
+cp .env.example .env
+pnpm install
+pnpm start
+```
+
+
+For additional support, check out the [video tutorial](https://www.youtube.com/live/DlRR1focAiw).
+
diff --git a/_pages/use-cases/ai-instructions/langchain-local.mdx b/_pages/use-cases/ai-instructions/langchain-local.mdx
new file mode 100644
index 00000000..da44003c
--- /dev/null
+++ b/_pages/use-cases/ai-instructions/langchain-local.mdx
@@ -0,0 +1,65 @@
+---
+title: "Edit .env with your credentials"
+---
+
+
+## LangChain Local Environment Setup
+
+### TypeScript
+
+
+
+ ```bash [Terminal]
+ node --version # Should be 18+
+ npm --version # Should be 9.7.2+
+ ```
+
+
+ ```bash [Terminal]
+ git clone https://github.com/coinbase/agentkit.git
+ cd agentkit
+ npm install
+ npm run build
+ cd typescript/examples/langchain-cdp-chatbot
+ ```
+
+
+ ```bash [Terminal]
+ cp .env.local .env
+ ```
+
+
+ ```bash [Terminal]
+ npm run start
+ ```
+
+
+
+### Python
+
+
+
+ ```bash [Terminal]
+ python --version # Should be 3.10+
+ poetry --version # Verify Poetry installation
+ ```
+
+
+ ```bash [Terminal]
+ git clone https://github.com/coinbase/agentkit.git
+ cd agentkit/python/examples/cdp-langchain-chatbot
+ ```
+
+
+ ```bash [Terminal]
+ cp .env.local .env
+ # Edit .env with your credentials
+ ```
+
+
+ ```bash [Terminal]
+ poetry install
+ poetry run python main.py
+ ```
+
+
diff --git a/_pages/use-cases/ai-instructions/langchain-replit.mdx b/_pages/use-cases/ai-instructions/langchain-replit.mdx
new file mode 100644
index 00000000..320e3ee4
--- /dev/null
+++ b/_pages/use-cases/ai-instructions/langchain-replit.mdx
@@ -0,0 +1,30 @@
+## LangChain Replit Setup
+
+
+
+ 1. Fork the template from our [NodeJS](https://replit.com/@lincolnmurr/AgentKitjs-Quickstart-010?v=1) or [Python](https://replit.com/@lincolnmurr/AgentKitpy-01?v=1) Replit templates.
+ 2. Modify your forked project as needed.
+
+
+ 1. Click on "Tools" in the left sidebar and select "Secrets".
+ 2. Add the following secrets:
+
+ ```bash
+ CDP_API_KEY_NAME=your_cdp_key_name
+ CDP_API_KEY_PRIVATE_KEY=your_cdp_private_key
+ OPENAI_API_KEY=your_openai_key # Or XAI_API_KEY if using NodeJS
+ NETWORK_ID="base-sepolia" # Optional, defaults to base-sepolia
+ MNEMONIC_PHRASE=your_mnemonic_phrase # Optional
+ ```
+
+
+
+ 1. Click the "Run" button to start the chatbot.
+
+
+ **Security of wallets on Replit**
+
+ Every agent comes with an associated wallet. Wallet data is read from wallet_data.txt, and if that file does not exist, this repl will create a new wallet and persist it in a new file. Please note that this contains your wallet's private key and should not be used in production environments. Refer to the [CDP docs](https://docs.cdp.coinbase.com/wallet-api/docs/wallets#securing-a-wallet) on how to secure your wallets.
+
+
+
diff --git a/docs/pages/use-cases/decentralize-social-app.mdx b/_pages/use-cases/decentralize-social-app.mdx
similarity index 83%
rename from docs/pages/use-cases/decentralize-social-app.mdx
rename to _pages/use-cases/decentralize-social-app.mdx
index 82710780..b7030967 100644
--- a/docs/pages/use-cases/decentralize-social-app.mdx
+++ b/_pages/use-cases/decentralize-social-app.mdx
@@ -36,33 +36,31 @@ Onchain profiles are built with Basenames and ENS, services which wrap wallet ad
## Integrating ` `
-:::steps
-
-### Install and Configure OnchainKit
-
-Creating a new OnchainKit app is the easiest way to get started.
-
-```bash [Terminal]
-npm create onchain@latest
-```
-
-Or you can integrate OnchainKit into an existing app by following the [installation guide](/builderkits/onchainkit/getting-started/#manual-installation).
-
-### Add the IdentityCard component
-
-```tsx twoslash [App.tsx]
-// @errors: 2305
-import { IdentityCard } from '@coinbase/onchainkit/identity'; // [!code focus]
-import { base } from 'viem/chains';
-
- // [!code focus]
-```
-
-:::
+
+
+ Creating a new OnchainKit app is the easiest way to get started.
+
+ ```bash [Terminal]
+ npm create onchain@latest
+ ```
+
+ Or you can integrate OnchainKit into an existing app by following the [installation guide](/builderkits/onchainkit/getting-started/#manual-installation).
+
+
+
+ ```tsx twoslash [App.tsx]
+ // @errors: 2305
+ import { IdentityCard } from '@coinbase/onchainkit/identity'; // [!code focus]
+ import { base } from 'viem/chains';
+
+ // [!code focus]
+ ```
+
+
Just like that, you've added onchain identity into your app! Once a user connects a wallet with a Basename or ENS name, the IdentityCard component will display the user's profile information like you see below.
@@ -75,9 +73,12 @@ Just like that, you've added onchain identity into your app! Once a user connect
/>
-:::tip[Setting the address]
+
+**Setting the address**
+
In the above example, we hardcoded the address. In a real app, you'll dynamically set the address based on the user's connected wallet. [Use OnchainKit's ` ` component to get the user's address](/builderkits/onchainkit/wallet/wallet).
-:::
+
+
The IdentityCard component comes with helpful defaults, such as a plain avatar image, for users who don't have a Basename or ENS name.
diff --git a/_pages/use-cases/defi-your-app.mdx b/_pages/use-cases/defi-your-app.mdx
new file mode 100644
index 00000000..357c2689
--- /dev/null
+++ b/_pages/use-cases/defi-your-app.mdx
@@ -0,0 +1,307 @@
+---
+title: Defi your app
+description: Add common financial features like token swaps and yield generating strategies to your app with pre-built React components from OnchainKit.q
+---
+
+import { Danger } from "/snippets/danger.mdx";
+import { Avatar, Name } from '@coinbase/onchainkit/identity';
+import { Swap, SwapAmountInput, SwapButton, SwapDefault, SwapMessage, SwapToggleButton, SwapToast } from '@coinbase/onchainkit/swap';
+import { EarnMain, EarnDeposit, RearrangedEarnDeposit, PredefinedInputDeposit } from '@/components/EarnComponents';
+import { Buy } from '@coinbase/onchainkit/buy';
+import { FundCard } from '@coinbase/onchainkit/fund';
+import { Wallet, ConnectWallet } from '@coinbase/onchainkit/wallet';
+import App from '@/components/App';
+import SwapWrapper from '@/components/SwapWrapper';
+import BuyWrapper from '@/components/BuyWrapper';
+import FundWrapper from '@/components/FundWrapper';
+
+# Defi Your App
+When businesses and individuals make financial transactions, it often includes swapping assets and then storing eligible assets in a yield generating strategy. This guide will show you how to quickly add these features to your app with pre-built React components from OnchainKit.
+
+## Ready-to-use components
+- [` `](/builderkits/onchainkit/swap/swap): Swap assets directly within your app.
+- [` `](/builderkits/onchainkit/earn/earn): Generate yield directly within your app.
+- [` `](/builderkits/onchainkit/fund/fund-card): Fund their wallets with fiat (via USDC, Apple Pay, or debit card) without leaving your app.
+- [` `](/builderkits/onchainkit/buy/buy): Purchase tokens directly within your app.
+
+
+By embedding the `Swap` and `Earn` components, your users don't need to leave your app to execute these common actions. For users who lack onchain funds, the `Fund` and `Buy` components offer an integrated fiat-to-crypto onramp.
+
+
+## Swap Component Integration
+
+The `Swap` component lets users exchange one token for another directly in your application. It fetches live quotes, builds transactions, and executes swaps—abstracting the underlying complexity.
+
+Lets add the `Swap` component to your app.
+
+
+
+ Create a new OnchainKit app
+
+ ```bash [Terminal]
+ npm create onchain@latest
+ ```
+
+
+ ```typescript [App.tsx]
+ import { SwapDefault } from '@coinbase/onchainkit/swap'; // [!code focus]
+ import type { Token } from '@coinbase/onchainkit/token';
+
+ const eth: Token = {
+ name: 'ETH',
+ address: '',
+ symbol: 'ETH',
+ decimals: 18,
+ image:
+ 'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png',
+ chainId: 8453,
+ };
+
+ const usdc: Token = {
+ name: 'USDC',
+ address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
+ symbol: 'USDC',
+ decimals: 6,
+ image:
+ 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/44/2b/442b80bd16af0c0d9b22e03a16753823fe826e5bfd457292b55fa0ba8c1ba213-ZWUzYjJmZGUtMDYxNy00NDcyLTg0NjQtMWI4OGEwYjBiODE2',
+ chainId: 8453,
+ };
+
+ // [!code focus]
+ ```
+
+
+
+You should now see the following swap component in your app:
+
+
+
+ {({ address, swappableTokens }) => {
+ if (address) {
+ return (
+
+ )
+ }
+ return <>
+
+
+
+
+
+
+ >;
+ }}
+
+
+
+The Swap component uses **Uniswap V3** as the default router, but you can also use the **0x Aggregator** by setting the `experimental.useAggregator` prop to `true`.
+
+
+
+### Swap Settings
+The Swap component comes preconfigured but is highly customizable. Just a couple of the settings you can customize:
+- Swap settings
+- bidirectional or unidirectional swaps
+- Gasless swaps with paymasters
+
+To learn more about how to customize the Swap component, check out the [Swap docs](/builderkits/onchainkit/swap/swap).
+
+
+## Earn Component Integration
+
+The `Earn` component enables users to deposit assets into yield-generating vaults and withdraw them later—all within your app. The `Earn` component currently supports Morpho vaults on Base.
+
+
+**Get a vault address**
+
+You can get a vault address from Morpho's [Vaults page](https://app.morpho.org/base/earn). You will use this address when setting up the `Earn` component.
+
+
+
+```tsx twoslash
+// @noErrors: 2307
+import { Earn } from '@coinbase/onchainkit/earn'; // [!code focus]
+
+ // [!code focus]
+```
+
+Just like that, you've added a yield-generating vault to your app.
+
+
+
+### Advanced Customizations
+Similar to the Swap component, the Earn component is highly customizable. Lets customize our component to include custom deposit buttons for a streamlined user experience.
+
+- `useEarnContext` to access the component's context values, `EarnDeposit` and `EarnDetails`
+- `DepositButton` to render custom deposit buttons
+
+
+```tsx twoslash [index.tsx]
+// @noErrors: 2307
+import { Earn, useEarnContext } from '@coinbase/onchainkit/earn';
+import { CustomDepositButtons } from '@/custom-deposit-buttons';
+
+
+
+
+
+```
+
+```tsx twoslash [custom-deposit-buttons.tsx]
+import {EarnDetails,
+ EarnDeposit,
+ useEarnContext,
+ DepositButton} from '@coinbase/onchainkit/earn';
+
+const predefinedAmounts = ['0.1', '1', '10'];
+
+function CustomDepositButtons() {
+ const { depositAmount, setDepositAmount } = useEarnContext();
+
+ return (
+
+
+
+ {predefinedAmounts.map((amount) => {
+ const selected = amount === depositAmount;
+ return (
+ setDepositAmount(amount)}
+ className={`rounded-md px-4 py-2
+ ${selected ? 'bg-[var(--ock-bg-primary)] text-[var(--ock-text-inverse)]'
+ : 'bg-[var(--ock-bg-secondary)] text-[var(--ock-text-primary)]'}`}
+ >
+ {amount}
+
+ );
+ })}
+
+
+
+ );
+}
+```
+
+
+
+
+## Onboarding Users in DeFi
+
+In order to leverage the ` ` and ` ` components, users need to have funds in their wallet. If user's don't have funds, they'll need to onramp fiat or buy tokens in order to transact. We'll explore two out-of-the-box solutions from OnchainKit below.
+
+The `Fund` component (via ` `) offers a complete fiat onramp experience, allowing users to add funds to their wallet directly in your app. It provides:
+- Amount input with fiat/crypto switching
+- Payment method selection (Coinbase, Apple Pay, Debit Card)
+- Automatic exchange rate updates
+- Smart handling of payment method restrictions (based on country and subdivision)
+
+
+To use the `FundCard` component, you'll need to provide a Client API Key in `OnchainKitProvider`. You can get one following our [Getting Started](/builderkits/onchainkit/installation/nextjs#get-your-client-api-key) steps.
+
+
+
+```tsx [App.tsx]
+import { FundCard } from '@coinbase/onchainkit/fund';
+
+ ;
+```
+
+
+
+ {({ address }) => {
+ if (address) {
+ return ;
+ }
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+ }}
+
+
+
+To learn more about the `FundCard` component and its features, check out the [FundCard docs](/builderkits/onchainkit/fund/fund-card).
+
+## The `Buy` Component
+
+The `Buy` components provide a comprehensive interface for users to purchase [Tokens](/builderkits/onchainkit/token/types#token).
+
+The `Buy` component supports token swaps from USDC and ETH by default with the option to provide an additional token of choice using the `fromToken` prop. Users are able to purchase tokens using their Coinbase account, Apple Pay, or debit card.
+
+
+This component requires a `projectId` to be set in the `OnchainKitProvider`. You can find your `projectId` on [Coinbase Developer Platform](https://portal.cdp.coinbase.com/products/onchainkit).
+
+
+
+```tsx twoslash
+import { Buy } from '@coinbase/onchainkit/buy'; // [!code focus]
+import type { Token } from '@coinbase/onchainkit/token';
+
+export default function BuyComponents() { // [!code focus]
+ const degenToken: Token = {
+ name: 'DEGEN',
+ address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed',
+ symbol: 'DEGEN',
+ decimals: 18,
+ image:
+ 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/3b/bf/3bbf118b5e6dc2f9e7fc607a6e7526647b4ba8f0bea87125f971446d57b296d2-MDNmNjY0MmEtNGFiZi00N2I0LWIwMTItMDUyMzg2ZDZhMWNm',
+ chainId: 8453,
+ };
+
+ return ( // [!code focus]
+ // [!code focus]
+ ) // [!code focus]
+} // [!code focus]
+
+```
+
+
+
+ {({ address, toToken }) => {
+ return (
+
+ )
+ }}
+
+
+
+
+**Note: This interface is for demonstration purposes only.**
+
+Swap and Onramp flows will execute and work out of the box when you implement the component in your own app.
+
+
+
+By combining Swap, Earn, Fund, and Buy with these additional tools, you can quickly build a robust in-app DeFi experience with minimal development overhead. This lets you focus on building your unique value proposition.
+
+
+## Next Steps
+
+If you're using these components, its likely you'll benefit from the following components:
+
+- [` `](/builderkits/onchainkit/transaction/transaction): Provides a high-level transaction interface for executing custom onchain transactions.
+
+
+- [` `](/builderkits/onchainkit/token/token-chip): Offers utilities for token selection and display, ideal for building wallet-like interfaces.
+
+
+- [` `](/builderkits/onchainkit/wallet/wallet-island): An advanced, draggable wallet widget that consolidates wallet management (QR code, buy options, swap, portfolio view) in one interface.
+
+
+### Go Gasless
+For the ` ` and ` ` components, you can enable gasless transactions by setting the [`isSponsored`](/builderkits/onchainkit/buy/buy#sponsor-gas-with-paymaster) prop to `true`.
diff --git a/_pages/use-cases/go-gasless.mdx b/_pages/use-cases/go-gasless.mdx
new file mode 100644
index 00000000..cc510669
--- /dev/null
+++ b/_pages/use-cases/go-gasless.mdx
@@ -0,0 +1,398 @@
+---
+title: 'Gasless Transactions on Base using a Paymaster'
+slug: /gasless-transaction-on-base-using-a-paymaster
+description: Learn how to leverage the Base Paymaster for seamless, gasless transactions on the Coinbase Cloud Developer Platform.
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+# Gasless Transactions on Base using Base Paymaster
+
+Base transaction fees are typically less than a penny, but the concept of gas can still be confusing for new users and lead to poor user experience when users don't have gas funds in their wallet. You can abstract this away and improve your UX by using the **Base Paymaster**. The Paymaster allows you to:
+
+- Batch multi-step transactions
+- Create custom gasless experiences
+- Sponsor up to $15k monthly on mainnet (unlimited on testnet)
+
+
+If you need an increase in your sponsorship limit, please [reach out on Discord][Discord]!
+
+
+
+## Objectives
+
+1. Configure security measures to ensure safe and reliable transactions.
+2. Manage and allocate resources for sponsored transactions.
+3. Subsidize transaction fees for users, enhancing the user experience by making transactions free.
+4. Set up and manage sponsored transactions on various schedules, including weekly, monthly, and daily cadences.
+
+## Prerequisites
+
+This tutorial assumes you have:
+
+1. **A Coinbase Cloud Developer Platform Account**
+ If not, sign up on the [CDP site]. Once you have your account, you can manage projects and utilize tools like the Paymaster.
+
+2. **Familiarity with Smart Accounts and ERC 4337**
+ Smart Accounts are the backbone of advanced transaction patterns (e.g., bundling, sponsorship). If you’re new to ERC 4337, check out external resources like the official [EIP-4337 explainer](https://eips.ethereum.org/EIPS/eip-4337) before starting.
+
+3. **Foundry**
+ Foundry is a development environment, testing framework, and smart contract toolkit for Ethereum. You’ll need it installed locally for generating key pairs and interacting with smart contracts.
+
+
+**Testnet vs. Mainnet**
+If you prefer not to spend real funds, you can switch to **Base Sepolia** (testnet). The steps below are conceptually the same. Just select _Base Sepolia_ in the Coinbase Developer Platform instead of _Base Mainnet_, and use a contract deployed on Base testnet for your allowlisted methods.
+
+
+
+## Set Up a Base Paymaster & Bundler
+
+In this section, you will configure a Paymaster to sponsor payments on behalf of a specific smart contract for a specified amount.
+
+1. **Navigate to the [Coinbase Developer Platform].**
+2. Create or select your project from the upper left corner of the screen.
+3. Click on the **Paymaster** tool from the left navigation.
+4. Go to the **Configuration** tab and copy the **RPC URL** to your clipboard — you’ll need this shortly in your code.
+
+### Screenshots
+
+- **Selecting your project**
+ 
+
+- **Navigating to the Paymaster tool**
+ 
+
+- **Configuration screen**
+ 
+
+### Allowlist a Sponsorable Contract
+
+1. From the Configuration page, ensure **Base Mainnet** (or **Base Sepolia** if you’re testing) is selected.
+2. Enable your paymaster by clicking the toggle button.
+3. Click **Add** to add an allowlisted contract.
+4. For this example, add [`0x83bd615eb93eE1336acA53e185b03B54fF4A17e8`][simple NFT contract], and add the function `mintTo(address)`.
+
+
+
+
+**Use your own contract**
+ We use a [simple NFT contract][simple NFT contract] on Base mainnet as an example. Feel free to substitute your own.
+
+
+
+### Global & Per User Limits
+
+Scroll down to the **Per User Limit** section. You can set:
+
+- **Dollar amount limit** or **number of UserOperations** per user
+- **Limit cycles** that reset daily, weekly, or monthly
+
+For example, you might set:
+
+- `max USD` to `$0.05`
+- `max UserOperation` to `1`
+
+This means **each user** can only have $0.05 in sponsored gas and **1** user operation before the cycle resets.
+
+
+**Limit Cycles**
+These reset based on the selected cadence (daily, weekly, monthly).
+
+
+
+Next, **set the Global Limit**. For example, set this to `$0.07` so that once the entire paymaster has sponsored \$0.07 worth of gas (across all users), no more sponsorship occurs unless you raise the limit.
+
+
+
+## Test Your Paymaster Policy
+
+Now let’s verify that these policies work. We’ll:
+
+1. Create two local key pairs (or use private keys you own).
+2. Generate two Smart Accounts.
+3. Attempt to sponsor multiple transactions to see your policy in action.
+
+### Installing Foundry
+
+1. Ensure you have **Rust** installed
+ ```bash
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+ ```
+2. Install Foundry
+ ```bash
+ curl -L https://foundry.paradigm.xyz | bash
+ foundryup
+ ```
+3. Verify it works
+ ```bash
+ cast --help
+ ```
+ If you see Foundry usage info, you’re good to go!
+
+### Create Your Project & Generate Key Pairs
+
+1. Make a new folder and install dependencies, `viem` and `permissionless`:
+ ```bash
+ mkdir sponsored_transactions
+ cd sponsored_transactions
+ npm init es6
+ npm install permissionless
+ npm install viem
+ touch index.js
+ ```
+2. Generate two key pairs with Foundry:
+ ```bash
+ cast wallet new
+ cast wallet new
+ ```
+ You’ll see something like:
+ ```bash
+ Successfully created new keypair.
+ Address: 0xD440D746...
+ Private key: 0x01c9720c1dfa3c9...
+ ```
+ **Store these private keys somewhere safe**
+
+### Project Structure With Environment Variables
+
+Create a `.env` file in the `sponsored_transactions` directory. In the `.env`, you'll add the rpcURL for your paymaster and the private keys for your accounts:
+
+
+**Find your Paymaster & Bundler endpoint**
+
+The Paymaster & Bundler endpoint is the URL for your Coinbase Developer Platform (CDP) Paymaster.
+This was saved in the previous section and follows this format: `https://api.developer.coinbase.com/rpc/v1/base/`
+Navigate to the [Paymaster Tool] and select the `Configuration` tab at the top of the screen to obtain your RPC URL.
+
+
+
+
+**Secure your endpoints**
+
+You will create a constant for our Paymaster & Bundler endpoint obtained from cdp.portal.coinbase.com. The most secure way to do this is by using a proxy. For the purposes of this demo, hardcode it into our `index.js` file. For product, we highly recommend using a [proxy service].
+
+
+
+```bash
+PAYMASTER_RPC_URL=https://api.developer.coinbase.com/rpc/v1/base/
+PRIVATE_KEY_1=0x01c9720c1dfa3c9...
+PRIVATE_KEY_2=0xbcd6fbc1dfa3c9...
+```
+
+
+Never commit `.env` files to a public repo!
+
+
+
+## Example `index.js`
+
+Below is a full example of how you might structure `index.js`.
+
+```js twoslash
+// --- index.js ---
+// @noErrors
+
+// 1. Import modules and environment variables
+import 'dotenv/config';
+import { http, createPublicClient, encodeFunctionData } from 'viem';
+import { base } from 'viem/chains';
+import { createSmartAccountClient } from 'permissionless';
+import { privateKeyToSimpleSmartAccount } from 'permissionless/accounts';
+import { createPimlicoPaymasterClient } from 'permissionless/clients/pimlico';
+
+// 2. Retrieve secrets from .env
+// Highlight: environment variables for paymaster, private keys
+const rpcUrl = process.env.PAYMASTER_RPC_URL; // highlight
+const firstPrivateKey = process.env.PRIVATE_KEY_1; // highlight
+const secondPrivateKey = process.env.PRIVATE_KEY_2; // highlight
+
+// 3. Declare Base addresses (entrypoint & factory)
+const baseEntryPoint = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789';
+const baseFactoryAddress = '0x15Ba39375ee2Ab563E8873C8390be6f2E2F50232';
+
+// 4. Create a public client for Base
+const publicClient = createPublicClient({
+ chain: base,
+ transport: http(rpcUrl),
+});
+
+// 5. Setup Paymaster client
+const cloudPaymaster = createPimlicoPaymasterClient({
+ chain: base,
+ transport: http(rpcUrl),
+ entryPoint: baseEntryPoint,
+});
+
+// 6. Create Smart Accounts from the private keys
+async function initSmartAccounts() {
+ const simpleAccount = await privateKeyToSimpleSmartAccount(publicClient, {
+ privateKey: firstPrivateKey,
+ factoryAddress: baseFactoryAddress,
+ entryPoint: baseEntryPoint,
+ });
+
+ const simpleAccount2 = await privateKeyToSimpleSmartAccount(publicClient, {
+ privateKey: secondPrivateKey,
+ factoryAddress: baseFactoryAddress,
+ entryPoint: baseEntryPoint,
+ });
+
+ // 7. Create SmartAccountClient for each
+ const smartAccountClient = createSmartAccountClient({
+ account: simpleAccount,
+ chain: base,
+ bundlerTransport: http(rpcUrl),
+ middleware: {
+ sponsorUserOperation: cloudPaymaster.sponsorUserOperation,
+ },
+ });
+
+ const smartAccountClient2 = createSmartAccountClient({
+ account: simpleAccount2,
+ chain: base,
+ bundlerTransport: http(rpcUrl),
+ middleware: {
+ sponsorUserOperation: cloudPaymaster.sponsorUserOperation,
+ },
+ });
+
+ return { smartAccountClient, smartAccountClient2 };
+}
+
+// 8. ABI for the NFT contract
+const nftAbi = [
+ // ...
+ // truncated for brevity
+];
+
+// 9. Example function to send a transaction from a given SmartAccountClient
+async function sendTransaction(client, recipientAddress) {
+ try {
+ // encode the "mintTo" function call
+ const callData = encodeFunctionData({
+ abi: nftAbi,
+ functionName: 'mintTo',
+ args: [recipientAddress], // highlight: specify who gets the minted NFT
+ });
+
+ const txHash = await client.sendTransaction({
+ account: client.account,
+ to: '0x83bd615eb93eE1336acA53e185b03B54fF4A17e8', // address of the NFT contract
+ data: callData,
+ value: 0n,
+ });
+
+ console.log(`✅ Transaction successfully sponsored for ${client.account.address}`);
+ console.log(`🔍 View on BaseScan: https://basescan.org/tx/${txHash}`);
+ } catch (error) {
+ console.error('Transaction failed:', error);
+ }
+}
+
+// 10. Main flow: init accounts, send transactions
+(async () => {
+ const { smartAccountClient, smartAccountClient2 } = await initSmartAccounts();
+
+ // Send a transaction from the first account
+ await sendTransaction(smartAccountClient, smartAccountClient.account.address);
+
+ // Send a transaction from the second account
+ // For variety, let’s also mint to the second account's own address
+ await sendTransaction(smartAccountClient2, smartAccountClient2.account.address);
+})();
+```
+
+Now that the code is implemented, lets run it:
+Run this via `node index.js` from your project root.
+
+```bash
+node index.js
+```
+
+You should see a "Transaction successfully sponsored" output.
+
+To confirm that your spend policies are correctly in place, try running the script again. If your Paymaster settings are strict (e.g., limit 1 transaction per user), the second time you run the script, you may get a “request denied” error, indicating the policy is working.
+
+## Hitting Policy Limits & Troubleshooting
+
+1. **Per-User Limit**
+ If you see an error like:
+
+ ```json
+ {
+ "code": -32001,
+ "message": "request denied - rejected due to maximum per address transaction count reached"
+ }
+ ```
+
+ That means you’ve hit your **UserOperation** limit for a single account. Return to the [Coinbase Developer Platform] UI to adjust the policy.
+
+2. **Global Limit**
+ If you repeatedly run transactions and eventually see:
+ ```json
+ {
+ "code": -32001,
+ "message": "request denied - rejected due to max global usd spend limit reached"
+ }
+ ```
+ You’ve hit the **global** limit of sponsored gas. Increase it in the CDP dashboard and wait a few minutes for changes to take effect.
+
+## Verifying Token Ownership (Optional)
+
+Want to confirm the token actually minted? You can read the NFT’s `balanceOf` function:
+
+```js
+import { readContract } from 'viem'; // highlight
+
+// example function
+async function checkNftBalance(publicClient, contractAddress, abi, ownerAddress) {
+ const balance = await publicClient.readContract({
+ address: contractAddress,
+ abi,
+ functionName: 'balanceOf',
+ args: [ownerAddress],
+ });
+ console.log(`NFT balance of ${ownerAddress} is now: ${balance}`);
+}
+```
+
+## Conclusion
+
+In this tutorial, you:
+
+- Set up and **configured** a Base Paymaster on the Coinbase Developer Platform.
+- **Allowlisted** a contract and specific function (`mintTo`) for sponsorship.
+- Established **per-user** and **global** sponsorship **limits** to control costs.
+- Demonstrated the **sponsorship flow** with Smart Accounts using `permissionless`, `viem`, and Foundry-generated private keys.
+
+This approach can greatly improve your dApp’s user experience by removing gas friction. For more complex sponsorship schemes (like daily or weekly cycles), simply tweak your per-user and global limit settings in the Coinbase Developer Platform.
+
+> **Next Steps**
+>
+> - Use a [proxy service][proxy service] for better endpoint security.
+> - Deploy your own contracts and allowlist them.
+> - Experiment with bundling multiple calls into a single sponsored transaction.
+
+## References
+
+- [list of factory addresses]
+- [Discord]
+- [CDP site]
+- [Coinbase Developer Platform]
+- [UI]
+- [proxy service]
+- [Paymaster Tool]
+- [Foundry Book installation guide]
+- [simple NFT contract]
+
+[list of factory addresses]: https://docs.alchemy.com/reference/factory-addresses
+[Discord]: https://discord.com/invite/cdp
+[CDP site]: https://portal.cdp.coinbase.com/
+[Coinbase Developer Platform]: https://portal.cdp.coinbase.com/
+[UI]: https://portal.cdp.coinbase.com/products/bundler-and-paymaster
+[proxy service]: https://www.smartwallet.dev/guides/paymasters
+[Paymaster Tool]: https://portal.cdp.coinbase.com/products/bundler-and-paymaster
+[Foundry Book installation guide]: https://book.getfoundry.sh/getting-started/installation
+[simple NFT contract]: https://basescan.org/token/0x83bd615eb93ee1336aca53e185b03b54ff4a17e8
+
+**Happy Building on Base!**
diff --git a/docs/pages/use-cases/launch-ai-agents.mdx b/_pages/use-cases/launch-ai-agents.mdx
similarity index 100%
rename from docs/pages/use-cases/launch-ai-agents.mdx
rename to _pages/use-cases/launch-ai-agents.mdx
diff --git a/_pages/use-cases/onboard-any-user.mdx b/_pages/use-cases/onboard-any-user.mdx
new file mode 100644
index 00000000..ea645c86
--- /dev/null
+++ b/_pages/use-cases/onboard-any-user.mdx
@@ -0,0 +1,144 @@
+---
+title: Onboard any user
+description: Learn how to seamlessly integrate the WalletModal component from OnchainKit to rapidly onboard new users with a smart wallet experience.
+---
+
+import { ConnectWallet, Wallet, WalletDropdown, WalletDropdownDisconnect } from '@coinbase/onchainkit/wallet'
+import { Address, Avatar, Name, Identity, EthBalance } from '@coinbase/onchainkit/identity'
+import AppWithWalletModal from '@/components/AppWithWalletModal'
+import { color } from '@coinbase/onchainkit/theme'
+
+# Onboard any user
+
+In onchain apps, the wallet is at the center of your user model. Onboarding requires users to connect an existing wallet or sign up for a new wallet. The [` `](/builderkits/onchainkit/wallet/wallet-modal) component provides a drag-and-drop solution to handle wallet onboarding seamlessly to eliminate friction and churn.
+
+## How It Works
+
+The component offers:
+
+- Smooth onboarding for new users with guided Smart Wallet creation
+- Quick connection for existing wallets
+- Consistent handling of connection states with a responsive UI
+
+Lets add the `WalletModal` component to your app.
+
+
+
+ ```bash [Terminal]
+ npm create onchain@latest
+ ```
+
+
+ ```tsx [providers.tsx]
+
+ {children}
+
+ ```
+
+
+ ```tsx [App.tsx]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+
+
+ Ensure that the Wallet Modal is globally accessible by wrapping your key UI components:
+
+ ```tsx
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+You now have an interface to streamline user onboarding! The `WalletModal` component handles:
+
+- Smart wallet creation for new signups
+- Quick connection for existing wallets
+- Wallet connection states
+- Error handling
+- Mobile/desktop responsive behavior
+- Theme customization
+- Terms/privacy policy display
+
+## Why Smart Wallet for new signups?
+
+In addition to providing a secure and feature-rich wallet experience, Smart Wallets provide a streamlined onboarding experience through account creation with Passkeys. This allows anyone to securely create a new wallet and begin transacting without ever leaving your app.
+
+
+**More on Smart Wallets**
+
+Smart Wallet has advanced features such as sponsored transactions, spend permissions, and Magic Spend. [Learn more about Smart Wallet here](/identity/smart-wallet/quickstart/).
+
+
+
+## Conclusion
+
+By integrating ` `, you offer a robust and user-friendly wallet onboarding experience. First-time users benefit from a seamless Smart Wallet creation flow, while returning users can quickly connect their wallets to get started with your onchain app.
diff --git a/_pages/wallet-app/mini-apps.mdx b/_pages/wallet-app/mini-apps.mdx
new file mode 100644
index 00000000..afa937fa
--- /dev/null
+++ b/_pages/wallet-app/mini-apps.mdx
@@ -0,0 +1,108 @@
+---
+title: "Mini Apps"
+---
+
+
+We're so excited that mini apps are coming to Coinbase Wallet! The purpose of this guide is to go over how to build or update your mini app so it works as well as possible in Coinbase Wallet.
+
+## Using MiniKit
+
+If you use MiniKit and/or follow the [MiniKit quickstart guide](/builderkits/minikit/quickstart), your mini app will work out of the box in Coinbase Wallet!
+
+For reference, MiniKit is easiest way to build mini apps on Base, allowing developers to easily build mini apps without needing to know the details of the SDK implementation. It integrates seamlessly with OnchainKit components and provides Coinbase Wallet-specific hooks.
+
+If you're already using MiniKit and experiencing issues, you can refer to the [Debugging guide](/builderkits/minikit/debugging) and if your issue isn't covered there we'd greatly appreciate if you could flag it to our team.
+
+## Authentication
+
+As a general rule of thumb for building any mini app, we recommend that you do not automatically request that the user logs in/signs a message and instead that authentication is only used when it's needed (e.g. signing up for an event, viewing authenticated resources, etc).
+Below we will quickly cover the different methods of authentication offered for mini apps and how well each of them work in Coinbase Wallet:
+
+### Wallet
+
+Because users in Coinbase Wallet have an in-app smart wallet that doesn't require any app switching, we recommend wallet authentication as the primary method of authentication for developers looking to create a persisted session for the mini app user.
+
+As described below, we don't think it's the best practice to prompt the user to log in before that authentication will allow them to do something else in your mini app, but for cases where do you want a secure, persisted session, using a wallet connection is a great option.
+
+### Context Data
+
+All mini app host apps (including Coinbase Wallet) return [context data](https://miniapps.farcaster.xyz/docs/sdk/context), which tells developers about the app/mini app host the user is accessing their mini app in, as well as **which user** is interacting with their mini app.
+
+A common flow that other mini app developers follow is using the context data to either track analytics metrics or create an authentication session via a [JWT](https://jwt.io/introduction). This allows you to still track analytics/offer certain authenticated services to your users without the extra friction of signing a message or deeplinking to another app.
+
+Something important to note is that because of how the current mini app spec is written, context data can technically be spoofed by any developer who makes their own mini app host. Because of this, we recommend that context data is not used as the **primary** method of authentication.
+
+### Sign In with Farcaster
+
+Currently, using Sign In with Farcaster inside of Coinbase Wallet is **not recommended as the primary method of authentication**. This is because, for Farcaster accounts that were created in Warpcast (which many current accounts were), using Sign In with Farcaster will require deeplinking the user to Warpcast and then back to Coinbase Wallet. While this flow still works inside of Coinbase Wallet, we recommend either wallet auth or creating an auth strategy around the context data (eg. a custom JWT with context data) for a more optimal UX.
+
+
+Note: The Sign In with Farcaster flow will no longer require a deeplink to Warpcast when the [auth addresses FIP](https://github.com/farcasterxyz/protocol/discussions/225) lands, which will allow a set of delegated wallets to take actions on behalf of a Farcaster account's custody wallet.
+
+
+
+## Deeplinks and SDK Actions
+
+The official mini apps SDK offers a [set of actions](https://miniapps.farcaster.xyz/docs/specification#actions) (which MiniKit offers as well) so that users of your mini app can be led to do things back in clients like Coinbase Wallet (e.g. compose a cast, view a profile, etc).
+
+Although a few other mini app developers have used the workaround of inserting Warpcast-specific deeplinks in the `openUrl` function of the SDK for certain actions, **it is highly recommended that you make use of all official SDK functions instead of using Warpcast-specific deeplinks in your mini app**. This is because Warpcast-specific deeplinks might not always match Coinbase Wallet-specific deeplinks, and this could lead to a UX where the user is dead-ended from taking further action in a mini app. Using SDK functions instead will ensure your users have the best viewing experience possible in Coinbase Wallet.
+
+## Metadata
+
+Farcaster has recently [extended the metadata spec for mini apps](https://github.com/farcasterxyz/miniapps/discussions/191), which allows developers to add screenshot links, categories, and more to their manifest (farcaster.json) file. Making use of these new metadata fields is highly recommended for increasing the chance that your mini app could be featured throughout Coinbase Wallet.
+
+Below is a table of the new metadata fields introduced, what they mean/could potentially be used for, and an example of what a manifest file could look like with these new fields (all taken from the [official spec](https://github.com/farcasterxyz/miniapps/discussions/191))
+
+| Experience | Field | Purpose | Limitations | Design Guidelines |
+| ------------------------------ | ----------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| **Mini App Store Listing** | `iconUrl` | Main icon | 1024x1024 px PNG, no alpha | Use a bold, recognizable logo. No text. Avoid photos or screenshots. |
+| | `name` | Display name of the app | 30 characters, no emojis or special characters | Use brandable, unique names. Avoid generic terms. No emojis or trademark symbols. |
+| | `subtitle` | Short description under the app name | 30 characters, no emojis or special characters | Should clarify what your app does in a catchy way. Avoid repeating the title. |
+| **Mini App Store Page** | `description` | Promotional message displayed on Mini App Page | 170 characters, no emojis or special characters | Use short paragraphs, headings, and bullet points. Focus on value, not features. Include social proof if possible. Avoid jargon. |
+| | `screenshotUrls` | Visual previews of the app, max 3 screens | Portrait, 1284 x 2778 | Focus on showing the core value or magic moment of the app in the first 2–3 shots. Use device frames and short captions. |
+| **Search & Discovery** | `primaryCategory` | Primary category of app | One of the pre-defined categories: `games`, `social`, `finance`, `utility`, `productivity`, `health-fitness`, `news-media`, `music`, `shopping`, `education`, `developer-tools`, `entertainment`, `art-creativity` | Games, Social, Finance, Utility, Productivity, Health & Fitness, News & Media, Music, Shopping, Education, Developer Tools, Entertainment, Art & Creativity |
+| | `tags` | Descriptive tags for filtering/search | up to 5 tags | Use 3–5 high-volume terms; no spaces, no repeats, no brand names. Use singular form. |
+| **Mini App Promotional Asset** | `heroImageUrl` | Promotional display image on top of the mini app store | 1200 x 630px (1.91:1) | |
+| | `tagline` | Marketing tagline should be punchy and descriptive | 30 characters | Use for time-sensitive promos or CTAs. Keep copy active (e.g., "Grow, Raid & Rise in Stoke Fire"). |
+| **Sharing Experience** | `ogTitle` | | 30 characters | Use your app name + short tag (e.g., "AppName – Local News Fast"). Title case, no emojis. |
+| | `ogDescription` | | 100 characters | Summarize core benefit in 1–2 lines. Avoid repeating OG title. |
+| | `ogImageUrl` | Promotional image (same as app hero image) | 1200 x 630px (1.91:1) | 1200x630 px JPG or PNG. Should show your brand clearly. No excessive text. Logo + tagline + UI is a good combo. |
+
+**Example Manifest:**
+
+```
+{
+ "accountAssociation": {
+ "header": "eyJmaWQiOjkxNTIsInR5cGUiOiJjdXN0b2R5Iiwia2V5IjoiMHgwMmVmNzkwRGQ3OTkzQTM1ZkQ4NDdDMDUzRURkQUU5NDBEMDU1NTk2In0",
+ "payload": "eyJkb21haW4iOiJyZXdhcmRzLndhcnBjYXN0LmNvbSJ9",
+ "signature": "MHgxMGQwZGU4ZGYwZDUwZTdmMGIxN2YxMTU2NDI1MjRmZTY0MTUyZGU4ZGU1MWU0MThiYjU4ZjVmZmQxYjRjNDBiNGVlZTRhNDcwNmVmNjhlMzQ0ZGQ5MDBkYmQyMmNlMmVlZGY5ZGQ0N2JlNWRmNzMwYzUxNjE4OWVjZDJjY2Y0MDFj"
+ },
+ "frame": {
+ "version": "1",
+ "name": "Example Mini App",
+ "iconUrl": "https://example.com/app.png",
+ "splashImageUrl": "https://example.com/logo.png",
+ "splashBackgroundColor": "#000000",
+ "homeUrl": "https://example.com",
+ "webhookUrl": "https://example.com/api/webhook",
+ "subtitle": "Example Mini App subtitle",
+ "description": "Example Mini App subtitle",
+ "screenshotUrls": [
+ "https://example.com/screenshot1.png",
+ "https://example.com/screenshot2.png",
+ "https://example.com/screenshot3.png"
+ ],
+ "primaryCategory": "social",
+ "tags": [
+ "example",
+ "mini app",
+ "coinbase wallet"
+ ],
+ "heroImageUrl": "https://example.com/og.png",
+ "tagline": "Example Mini App tagline",
+ "ogTitle": "Example Mini App",
+ "ogDescription": "Example Mini App description",
+ "ogImageUrl": "https://example.com/og.png"
+ }
+}
+```
diff --git a/api/submitFeedback.ts b/api/submitFeedback.ts
deleted file mode 100644
index 26b15af9..00000000
--- a/api/submitFeedback.ts
+++ /dev/null
@@ -1,73 +0,0 @@
-import { google } from '@googleapis/sheets';
-import type { VercelRequest, VercelResponse } from '@vercel/node';
-
-// Initialize Google Sheets client
-const getGoogleSheetsClient = () => {
- try {
- const credentials = JSON.parse(
- Buffer.from(process.env.GOOGLE_SERVICE_ACCOUNT_KEY || '', 'base64').toString(),
- );
-
- const auth = new google.auth.GoogleAuth({
- credentials,
- scopes: ['https://www.googleapis.com/auth/spreadsheets'],
- });
-
- return google.sheets({ version: 'v4', auth });
- } catch (error) {
- console.error('Failed to initialize Google Sheets client:', error);
- throw new Error('Failed to initialize Google Sheets client');
- }
-};
-
-type FeedbackPayload = {
- likeOrDislike: boolean;
- options: string[];
- comment: string;
- url: string;
- ipAddress: string;
- timestamp: number;
-};
-
-export default async function handler(req: VercelRequest, res: VercelResponse) {
- if (req.method !== 'POST') {
- return res.status(405).json({ error: 'Method not allowed' });
- }
-
- try {
- const { likeOrDislike, options, comment, url, ipAddress, timestamp } =
- req.body as FeedbackPayload;
-
- const sheets = getGoogleSheetsClient();
- const spreadsheetId = process.env.GOOGLE_SHEETS_ID;
-
- if (!spreadsheetId) {
- throw new Error('GOOGLE_SHEETS_ID environment variable is not set');
- }
-
- // Format the row data
- const rowData = [
- new Date(timestamp).toISOString(),
- url,
- ipAddress,
- likeOrDislike ? 'Like' : 'Dislike',
- options.join(', '),
- comment,
- ];
-
- // Append the row to the sheet
- await sheets.spreadsheets.values.append({
- spreadsheetId,
- range: 'Sheet1!A:F', // Adjust range based on your sheet's structure
- valueInputOption: 'USER_ENTERED',
- requestBody: {
- values: [rowData],
- },
- });
-
- return res.status(200).json({ success: true });
- } catch (error) {
- console.error('Error submitting feedback:', error);
- return res.status(500).json({ error: 'Failed to submit feedback' });
- }
-}
diff --git a/contexts/Theme.tsx b/contexts/Theme.tsx
deleted file mode 100644
index 7ef33ff8..00000000
--- a/contexts/Theme.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import {
- type ReactNode,
- createContext,
- useCallback,
- useContext,
- useEffect,
- useMemo,
- useState,
-} from 'react';
-
-type Theme = 'light' | 'dark';
-type ThemeContextProps = {
- theme: Theme;
- toggleTheme: () => void;
-};
-
-export const ThemeContext = createContext({
- theme: 'dark',
- toggleTheme: () => {},
-});
-
-export function useTheme() {
- const context = useContext(ThemeContext);
- if (context === undefined) {
- throw new Error('useTheme must be used within a ThemeProvider');
- }
- return context;
-}
-
-export default function ThemeProvider({ children }: { children: ReactNode }) {
- const [theme, setTheme] = useState('dark');
-
- const toggleTheme = useCallback(() => {
- setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
- }, []);
-
- useEffect(() => {
- const rootElement = document.documentElement;
- const observer = new MutationObserver(() => {
- if (rootElement.classList.contains('dark')) {
- setTheme('dark');
- } else {
- setTheme('light');
- }
- });
-
- observer.observe(rootElement, {
- attributes: true,
- attributeFilter: ['class'],
- });
-
- if (rootElement.classList.contains('dark')) {
- setTheme('dark');
- }
-
- return () => observer.disconnect();
- }, []);
-
- const values = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);
-
- return {children} ;
-}
diff --git a/docs.json b/docs.json
new file mode 100644
index 00000000..439d31c7
--- /dev/null
+++ b/docs.json
@@ -0,0 +1,402 @@
+{
+ "$schema": "https://mintlify.com/docs.json",
+ "theme": "mint",
+ "name": "Base Documentation",
+ "colors": {
+ "primary": "#578BFA",
+ "light": "#578BFA",
+ "dark": "#578BFA"
+ },
+ "favicon": "/logo/favicon.svg",
+ "contextual": {
+ "options": [
+ "copy",
+ "chatgpt",
+ "claude"
+ ]
+ },
+ "api": {
+ "playground": {
+ "display": "simple"
+ },
+ "examples": {
+ "languages": [
+ "javascript"
+ ]
+ }
+ },
+ "navigation": {
+ "tabs": [
+ {
+ "tab": "Learn",
+ "groups": [
+ {
+ "group": "Building Onchain",
+ "pages": [
+ "learn/welcome"
+ ]
+ },
+ {
+ "group": "Onchain Concepts",
+ "pages": [
+ "learn/onchain-concepts/core-concepts",
+ "learn/onchain-concepts/understanding-the-onchain-tech-stack",
+ {
+ "group": "Web2 vs Building Onchain",
+ "pages": [
+ "learn/onchain-concepts/building-onchain-wallets",
+ "learn/onchain-concepts/building-onchain-identity",
+ "learn/onchain-concepts/building-onchain-gas",
+ "learn/onchain-concepts/building-onchain-nodes",
+ "learn/onchain-concepts/building-onchain-frontend-development",
+ "learn/onchain-concepts/building-onchain-onramps",
+ "learn/onchain-concepts/building-onchain-social-networks",
+ "learn/onchain-concepts/building-onchain-ai"
+ ]
+ },
+ "learn/onchain-concepts/development-flow",
+ "learn/onchain-concepts/continue-building-onchain"
+ ]
+ },
+ {
+ "group": "Ethereum 101",
+ "pages": [
+ "learn/introduction-to-ethereum",
+ "learn/ethereum-dev-overview",
+ "learn/ethereum-applications",
+ "learn/gas-use-in-eth-transactions",
+ "learn/evm-diagram",
+ "learn/guide-to-base"
+ ]
+ },
+ {
+ "group": "Onchain App Development",
+ "pages": [
+ "learn/onchain-app-development/deploy-with-fleek",
+ "learn/onchain-app-development/account-abstraction",
+ "learn/cross-chain-development",
+ "learn/client-side-development",
+ {
+ "group": "Account Abstraction",
+ "pages": [
+ "learn/onchain-app-development/account-abstraction/account-abstraction-on-base-using-biconomy",
+ "learn/onchain-app-development/account-abstraction/account-abstraction-on-base-using-particle-network",
+ "learn/onchain-app-development/account-abstraction/account-abstraction-on-base-using-privy-and-the-base-paymaster",
+ "learn/onchain-app-development/account-abstraction/gasless-transactions-with-paymaster"
+ ]
+ },
+ {
+ "group": "Client-Side Development",
+ "pages": [
+ "learn/onchain-app-development/client-side-development/introduction-to-providers",
+ "learn/onchain-app-development/client-side-development/viem",
+ "learn/onchain-app-development/client-side-development/web3"
+ ]
+ },
+ {
+ "group": "Cross-Chain",
+ "pages": [
+ "learn/onchain-app-development/cross-chain/bridge-tokens-with-layerzero",
+ "learn/onchain-app-development/cross-chain/send-messages-and-tokens-from-base-chainlink"
+ ]
+ },
+ {
+ "group": "Finance",
+ "pages": [
+ "learn/onchain-app-development/finance/build-a-smart-wallet-funding-app",
+ "learn/onchain-app-development/finance/access-real-world-data-chainlink",
+ "learn/onchain-app-development/finance/access-real-time-asset-data-pyth-price-feeds"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Smart Contract Development",
+ "pages": [
+ {
+ "group": "Introduction to Solidity",
+ "pages": [
+ "learn/introduction-to-solidity/introduction-to-solidity-overview",
+ "learn/introduction-to-solidity/anatomy-of-a-smart-contract-vid",
+ {
+ "group": "Introduction to Solidity",
+ "pages": [
+ "learn/introduction-to-solidity/introduction-to-solidity-vid",
+ "learn/introduction-to-solidity/solidity-overview",
+ "learn/introduction-to-solidity/introduction-to-remix-vid",
+ "learn/introduction-to-solidity/introduction-to-remix",
+ "learn/introduction-to-solidity/deployment-in-remix-vid",
+ "learn/introduction-to-solidity/deployment-in-remix"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Contracts and Basic Functions",
+ "pages": [
+ "learn/contracts-and-basic-functions/intro-to-contracts-vid",
+ "learn/contracts-and-basic-functions/hello-world-step-by-step",
+ "learn/contracts-and-basic-functions/basic-types",
+ "learn/contracts-and-basic-functions/basic-functions-exercise"
+ ]
+ },
+ {
+ "group": "Deploying to a Testnet",
+ "pages": [
+ "learn/deployment-to-testnet/overview-of-test-networks-vid",
+ "learn/deployment-to-testnet/test-networks",
+ "learn/deployment-to-testnet/deployment-to-base-sepolia-sbs",
+ "learn/deployment-to-testnet/contract-verification-sbs",
+ "learn/deployment-to-testnet/deployment-to-testnet-exercise"
+ ]
+ },
+ {
+ "group": "Control Structures",
+ "pages": [
+ "learn/control-structures/standard-control-structures-vid",
+ "learn/control-structures/loops-vid",
+ "learn/control-structures/require-revert-error-vid",
+ "learn/control-structures/control-structures",
+ "learn/control-structures/control-structures-exercise"
+ ]
+ },
+ {
+ "group": "Storage in Solidity",
+ "pages": [
+ "learn/storage/simple-storage-video",
+ "learn/storage/simple-storage-sbs",
+ "learn/storage/how-storage-works-video",
+ "learn/storage/how-storage-works",
+ "learn/storage/storage-exercise"
+ ]
+ },
+ {
+ "group": "Arrays in Solidity",
+ "pages": [
+ "learn/arrays/arrays-in-solidity-vid",
+ "learn/arrays/writing-arrays-in-solidity-vid",
+ "learn/arrays/arrays-in-solidity",
+ "learn/arrays/filtering-an-array-sbs",
+ "learn/arrays/fixed-size-arrays-vid",
+ "learn/arrays/array-storage-layout-vid",
+ "learn/arrays/arrays-exercise"
+ ]
+ },
+ {
+ "group": "The Mapping Type",
+ "pages": [
+ "learn/mappings/mappings-vid",
+ "learn/mappings/using-msg-sender-vid",
+ "learn/mappings/mappings-sbs",
+ "learn/mappings/how-mappings-are-stored-vid",
+ "learn/mappings/mappings-exercise"
+ ]
+ },
+ {
+ "group": "Advanced Functions",
+ "pages": [
+ "learn/advanced-functions/function-visibility-vid",
+ "learn/advanced-functions/function-visibility",
+ "learn/advanced-functions/function-modifiers-vid",
+ "learn/advanced-functions/function-modifiers"
+ ]
+ },
+ {
+ "group": "Structs",
+ "pages": [
+ "learn/structs/structs-vid",
+ "learn/structs/structs-sbs",
+ "learn/structs/structs-exercise"
+ ]
+ },
+ {
+ "group": "Inheritance",
+ "pages": [
+ "learn/inheritance/inheritance-vid",
+ "learn/inheritance/inheritance-sbs",
+ "learn/inheritance/multiple-inheritance-vid",
+ "learn/inheritance/multiple-inheritance",
+ "learn/inheritance/abstract-contracts-vid",
+ "learn/inheritance/abstract-contracts-sbs",
+ "learn/inheritance/inheritance-exercise"
+ ]
+ },
+ {
+ "group": "Imports",
+ "pages": [
+ "learn/imports/imports-vid",
+ "learn/imports/imports-sbs",
+ "learn/imports/imports-exercise"
+ ]
+ },
+ {
+ "group": "Errors",
+ "pages": [
+ "learn/error-triage/error-triage-vid",
+ "learn/error-triage/error-triage",
+ "learn/error-triage/error-triage-exercise"
+ ]
+ },
+ {
+ "group": "The new Keyword",
+ "pages": [
+ "learn/new-keyword/creating-a-new-contract-vid",
+ "learn/new-keyword/new-keyword-sbs",
+ "learn/new-keyword/new-keyword-exercise"
+ ]
+ },
+ {
+ "group": "Contract to Contract Interactions",
+ "pages": [
+ "learn/interfaces/intro-to-interfaces-vid",
+ "learn/interfaces/calling-another-contract-vid",
+ "learn/interfaces/testing-the-interface-vid",
+ "learn/interfaces/contract-to-contract-interaction"
+ ]
+ },
+ {
+ "group": "Events",
+ "pages": [
+ "learn/events/hardhat-events-sbs"
+ ]
+ },
+ {
+ "group": "Address and Payable",
+ "pages": [
+ "learn/address-and-payable/address-and-payable"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Development with Foundry",
+ "pages": [
+ "learn/foundry/introduction-to-foundry",
+ "learn/foundry/testing",
+ "learn/foundry/setup-with-base",
+ "learn/foundry/deploy-with-foundry",
+ "learn/foundry/testing-smart-contracts",
+ "learn/foundry/verify-contract-with-basescan",
+ "learn/foundry/generate-random-numbers-contracts"
+ ]
+ },
+ {
+ "group": "Development with Hardhat",
+ "pages": [
+ {
+ "group": "Hardhat Setup and Overview",
+ "pages": [
+ "learn/hardhat/overview",
+ "learn/hardhat/creating-project",
+ "learn/hardhat/setup"
+ ]
+ },
+ {
+ "group": "Testing with Typescript",
+ "pages": [
+ "learn/hardhat/testing",
+ "learn/hardhat/writing-tests",
+ "learn/hardhat/contract-abi-testing",
+ "learn/hardhat/testing-guide"
+ ]
+ },
+ {
+ "group": "Etherscan",
+ "pages": [
+ "learn/hardhat/etherscan-guide",
+ "learn/hardhat/etherscan-video"
+ ]
+ },
+ {
+ "group": "Deploying Smart Contracts",
+ "pages": [
+ "learn/hardhat/installing-deploy",
+ "learn/hardhat/setup-deploy-script",
+ "learn/hardhat/testing-deployment",
+ "learn/hardhat/network-configuration",
+ "learn/hardhat/deployment",
+ "learn/hardhat/deployment-guide"
+ ]
+ },
+ {
+ "group": "Verifying Smart Contracts",
+ "pages": [
+ "learn/hardhat/verify-video",
+ "learn/hardhat/verify-guide"
+ ]
+ },
+ {
+ "group": "Mainnet Forking",
+ "pages": [
+ "learn/hardhat/fork-video",
+ "learn/hardhat/fork-guide"
+ ]
+ },
+ {
+ "group": "Hardhat Tools and Guides",
+ "pages": [
+ "learn/hardhat/deploy-with-hardhat",
+ "learn/hardhat/debugging-smart-contracts",
+ "learn/hardhat/optimizing-gas-usage",
+ "learn/hardhat/reducing-contract-size",
+ "learn/hardhat/analyzing-test-coverage"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Token Development",
+ "pages": [
+ {
+ "group": "Introduction to Tokens",
+ "pages": [
+ "learn/intro-to-tokens/intro-to-tokens-vid",
+ "learn/intro-to-tokens/misconceptions-about-tokens-vid",
+ "learn/intro-to-tokens/tokens-overview"
+ ]
+ },
+ {
+ "group": "Minimal Tokens",
+ "pages": [
+ "learn/minimal-tokens/creating-a-minimal-token-vid",
+ "learn/minimal-tokens/transferring-a-minimal-token-vid",
+ "learn/minimal-tokens/minimal-token-sbs",
+ "learn/minimal-tokens/minimal-tokens-exercise"
+ ]
+ },
+ {
+ "group": "ERC-20 Tokens",
+ "pages": [
+ "learn/erc-20-token/analyzing-erc-20-vid",
+ "learn/erc-20-token/erc-20-standard",
+ "learn/erc-20-token/openzeppelin-erc-20-vid",
+ "learn/erc-20-token/erc-20-testing-vid",
+ "learn/erc-20-token/erc-20-token-sbs",
+ "learn/erc-20-token/erc-20-exercise"
+ ]
+ },
+ {
+ "group": "ERC-721 Tokens",
+ "pages": [
+ "learn/erc-721-token/erc-721-standard-video",
+ "learn/erc-721-token/erc-721-standard",
+ "learn/erc-721-token/erc-721-on-opensea-vid",
+ "learn/erc-721-token/openzeppelin-erc-721-vid",
+ "learn/erc-721-token/implementing-an-erc-721-vid",
+ "learn/erc-721-token/erc-721-sbs",
+ "learn/erc-721-token/erc-721-exercise"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Exercise Contracts",
+ "pages": [
+ "learn/exercise-contracts"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/docs/_docs.json b/docs/_docs.json
new file mode 100644
index 00000000..8ab1fe7a
--- /dev/null
+++ b/docs/_docs.json
@@ -0,0 +1,550 @@
+{
+ "$schema": "https://mintlify.com/docs.json",
+ "theme": "mint",
+ "name": "Base Documentation",
+ "colors": {
+ "primary": "#578BFA",
+ "light": "#578BFA",
+ "dark": "#578BFA"
+ },
+ "favicon": "/favicon.svg",
+ "contextual": {
+ "options": ["copy", "chatgpt", "claude"]
+ },
+ "navigation": {
+ "tabs": [
+ {
+ "tab": "Get Started",
+ "groups": [
+ {
+ "group": "Introduction",
+ "pages": ["get-started/base"]
+ },
+ {
+ "group": "Browse by",
+ "pages": ["get-started/products", "get-started/use-cases"]
+ },
+ {
+ "group": "Quickstart",
+ "pages": [
+ "get-started/build-app",
+ "get-started/launch-token",
+ "get-started/deploy-chain",
+ "get-started/deploy-smart-contracts",
+ "get-started/get-funded"
+ ]
+ },
+ {
+ "group": "Build with AI",
+ "pages": ["get-started/ai-prompting", "get-started/prompt-library"]
+ }
+ ],
+ "global": {
+ "anchors": [
+ {
+ "anchor": "Status",
+ "href": "https://status.base.org/",
+ "icon": "signal-bars"
+ },
+ {
+ "anchor": "Faucet",
+ "href": "https://test-184b3b57.mintlify.app/base-chain/tools/network-faucets",
+ "icon": "gas-pump"
+ },
+ {
+ "anchor": "Bridge",
+ "href": "https://test-184b3b57.mintlify.app/base-chain/quickstart/bridge-token",
+ "icon": "coin"
+ }
+ ]
+ }
+ },
+ {
+ "tab": "Base Chain",
+ "groups": [
+ {
+ "group": "General",
+ "pages": [
+ "base-chain/general/network-fees",
+ "base-chain/general/differences-ethereum-base",
+ "base-chain/general/base-contracts"
+ ]
+ },
+ {
+ "group": "Quickstart",
+ "pages": [
+ "base-chain/quickstart/use-base",
+ "base-chain/quickstart/deploy-on-base",
+ "base-chain/quickstart/run-base-node",
+ "base-chain/quickstart/bridge-token"
+ ]
+ },
+ {
+ "group": "Network Information",
+ "pages": [
+ "base-chain/network-information/diffs-ethereum-base",
+ "base-chain/network-information/base-contracts"
+ ]
+ },
+ {
+ "group": "Flashblocks",
+ "pages": [
+ "base-chain/flashblocks/apps",
+ "base-chain/flashblocks/node-providers"
+ ]
+ },
+ {
+ "group": "Node Operators",
+ "pages": [
+ "base-chain/node-operators/performance-tuning",
+ "base-chain/node-operators/snapshots",
+ "base-chain/node-operators/troubleshooting"
+ ]
+ },
+ {
+ "group": "Tools",
+ "pages": [
+ "base-chain/tools/base-products",
+ "base-chain/tools/onchain-registry-api",
+ "base-chain/tools/node-providers",
+ "base-chain/tools/block-explorers",
+ "base-chain/tools/network-faucets",
+ "base-chain/tools/oracles",
+ "base-chain/tools/data-indexers",
+ "base-chain/tools/cross-chain",
+ "base-chain/tools/account-abstraction",
+ "base-chain/tools/onramps"
+ ]
+ },
+ {
+ "group": "Security",
+ "pages": [
+ "base-chain/security/security-council",
+ "base-chain/security/avoid-malicious-flags",
+ "base-chain/security/report-vulnerability"
+ ]
+ }
+ ],
+ "global": {
+ "anchors": [
+ {
+ "anchor": "GitHub",
+ "href": "https://github.com/base",
+ "icon": "github"
+ },
+ {
+ "anchor": "Status",
+ "href": "https://status.base.org/",
+ "icon": "signal-bars"
+ },
+ {
+ "anchor": "Chain Stats",
+ "href": "https://www.base.org/stats",
+ "icon": "chart-line"
+ },
+ {
+ "anchor": "Explorer",
+ "href": "https://basescan.com/",
+ "icon": "magnifying-glass"
+ },
+ {
+ "anchor": "Support",
+ "href": "https://discord.com/invite/base",
+ "icon": "discord"
+ }
+ ]
+ }
+ },
+ {
+ "tab": "Smart Wallet",
+ "groups": [
+ {
+ "group": "Get started",
+ "pages": [
+ "smart-wallet/overview",
+ "smart-wallet/quickstart",
+ "smart-wallet/recommend-libraries",
+ "smart-wallet/base-gasless-campaign"
+ ]
+ },
+ {
+ "group": "Features",
+ "pages": [
+ "smart-wallet/features/single-sign-on",
+ "smart-wallet/features/networks",
+ "smart-wallet/features/passkeys",
+ "smart-wallet/features/recovery",
+ "smart-wallet/features/magicspend",
+ {
+ "group": "Optional Features",
+ "pages": [
+ "smart-wallet/features/gas-free-transactions",
+ "smart-wallet/features/spend-permissions",
+ "smart-wallet/features/batch-transactions",
+ "smart-wallet/features/custom-gas-tokens",
+ "smart-wallet/features/sub-accounts"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Usage",
+ "pages": [
+ "smart-wallet/usage/signature-verification",
+ "smart-wallet/usage/popups",
+ "smart-wallet/usage/simulations",
+ "smart-wallet/usage/gas-usage",
+ "smart-wallet/usage/self-calls"
+ ]
+ },
+ {
+ "group": "SDK",
+ "pages": [
+ "smart-wallet/sdk/install",
+ "smart-wallet/sdk/setup",
+ "smart-wallet/sdk/make-web3-provider",
+ "smart-wallet/sdk/upgrading-from-3x",
+ "smart-wallet/sdk/coinbase-wallet-provider"
+ ]
+ },
+ {
+ "group": "Guides",
+ "pages": [
+ "smart-wallet/guides/update-existing-app",
+ "smart-wallet/guides/signing-and-verifying",
+ "smart-wallet/guides/sign-in-with-ethereum",
+ "smart-wallet/guides/magicspend",
+ "smart-wallet/guides/batch-transactions",
+ "smart-wallet/guides/paymasters",
+ "smart-wallet/guides/erc20-paymasters",
+ {
+ "group": "Sub Accounts",
+ "pages": [
+ "smart-wallet/guides/sub-accounts/overview",
+ "smart-wallet/guides/sub-accounts/setup",
+ "smart-wallet/guides/sub-accounts/creating",
+ "smart-wallet/guides/sub-accounts/using"
+ ]
+ },
+ {
+ "group": "Spend Permissions",
+ "pages": [
+ "smart-wallet/guides/spend-permissions/overview",
+ "smart-wallet/guides/spend-permissions/quickstart",
+ "smart-wallet/guides/spend-permissions/api-reference"
+ ]
+ }
+ ]
+ }
+ ],
+ "global": {
+ "anchors": [
+ {
+ "anchor": "GitHub",
+ "href": "https://github.com/coinbase/onchainkit",
+ "icon": "github"
+ },
+ {
+ "anchor": "Support",
+ "href": "https://discord.com/invite/cdp",
+ "icon": "discord"
+ }
+ ]
+ }
+ },
+ {
+ "tab": "OnchainKit",
+ "groups": [
+ {
+ "group": "Get Started",
+ "pages": [
+ "onchainkit/quickstart",
+ "onchainkit/templates",
+ {
+ "group": "Installation",
+ "pages": ["onchainkit/nextjs", "onchainkit/vite"]
+ },
+ "onchainkit/troubleshoot",
+ "onchainkit/telemetry"
+ ]
+ },
+ {
+ "group": "Guides",
+ "pages": [
+ "onchainkit/guides/build-with-ai",
+ "onchainkit/guides/lifecycle-status",
+ "onchainkit/guides/reach-more-users-with-minikit",
+ "onchainkit/guides/tailwind-css-integration",
+ "onchainkit/guides/theme-customization",
+ "onchainkit/guides/use-basenames"
+ ]
+ },
+ {
+ "group": "Components",
+ "pages": [
+ "onchainkit/components/bridge",
+ "onchainkit/components/buy",
+ "onchainkit/components/checkout",
+ "onchainkit/components/earn",
+ "onchainkit/components/fund",
+ "onchainkit/components/identity",
+ "onchainkit/components/mint",
+ "onchainkit/components/swap",
+ "onchainkit/components/token",
+ "onchainkit/components/transaction",
+ "onchainkit/components/wallet"
+ ]
+ },
+ {
+ "group": "API",
+ "openapi": {
+ "source": "docs/openapi/onchainkit.yaml",
+ "directory": "api-reference"
+ }
+ },
+ {
+ "group": "Utilities",
+ "pages": ["onchainkit/utilities/is-base"]
+ },
+ {
+ "group": "Contribute",
+ "pages": ["onchainkit/contribute/report-a-bug"]
+ }
+ ],
+ "global": {
+ "anchors": [
+ {
+ "anchor": "GitHub",
+ "href": "https://github.com/coinbase/onchainkit",
+ "icon": "github"
+ },
+ {
+ "anchor": "Playground",
+ "href": "https://onchainkit.xyz/playground",
+ "icon": "gamepad"
+ },
+ {
+ "anchor": "Support",
+ "href": "https://discord.com/invite/cdp",
+ "icon": "discord"
+ }
+ ]
+ }
+ },
+ {
+ "tab": "Cookbook",
+ "groups": [
+ {
+ "group": "Use Cases",
+ "pages": [
+ "cookbook/onboard-any-user",
+ "cookbook/accept-crypto-payments",
+ "cookbook/launch-ai-agents",
+ "cookbook/launch-tokens",
+ "cookbook/deploy-a-chain",
+ "cookbook/decentralize-your-social-app",
+ "cookbook/defi-your-app",
+ "cookbook/go-gasless"
+ ]
+ },
+ {
+ "group": "Build with AI",
+ "pages": ["cookbook/ai-prompting", "cookbook/base-builder-mcp"]
+ }
+ ]
+ },
+ {
+ "tab": "Showcase",
+ "pages": ["showcase"]
+ },
+ {
+ "tab": "Learn",
+ "groups": [
+ {
+ "group": "Building Onchain",
+ "pages": [
+ "learn/welcome",
+ {
+ "group": "Onchain Concepts",
+ "pages": [
+ "learn/onchain-concepts/the-onchain-tech-stack",
+ "learn/onchain-concepts/unique-aspects-of-building-onchain",
+ "learn/onchain-concepts/onchain-development-flow",
+ "learn/onchain-concepts/offchain-to-onchain"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Ethereum 101",
+ "pages": [
+ "learn/introduction-to-ethereum",
+ "learn/ethereum-dev-overview",
+ "learn/ethereum-applications",
+ "learn/gas-use-in-eth-transactions",
+ "learn/evm-diagram",
+ "learn/guide-to-base"
+ ]
+ },
+ {
+ "group": "Onchain App Development",
+ "pages": [
+ "learn/deploy-with-fleek",
+ "learn/account-abstraction",
+ "learn/cross-chain-development",
+ "learn/client-side-development"
+ ]
+ },
+ {
+ "group": "Smart Contract Development",
+ "pages": [
+ "learn/solidity/introduction",
+ "learn/solidity/anatomy",
+ {
+ "group": "Introduction to Solidity",
+ "pages": [
+ "learn/solidity/video-tutorial",
+ "learn/solidity/overview",
+ "learn/solidity/introduction-to-remix",
+ "learn/solidity/remix-guide",
+ "learn/solidity/deployment-in-remix",
+ "learn/solidity/step-by-step"
+ ]
+ },
+ {
+ "group": "Contracts and Basic Functions",
+ "pages": [
+ "learn/solidity/introduction-to-contracts",
+ "learn/solidity/hello-world",
+ "learn/solidity/basic-types",
+ "learn/solidity/exercise-basics"
+ ]
+ },
+ {
+ "group": "Deploying to a Testnet",
+ "pages": [
+ "learn/solidity/test-networks-overview",
+ "learn/solidity/test-networks",
+ "learn/solidity/deploy-to-sepolia",
+ "learn/solidity/contract-verification",
+ "learn/solidity/exercise-deployment"
+ ]
+ },
+ {
+ "group": "Control Structures",
+ "pages": [
+ "learn/solidity/standard-control-structures",
+ "learn/solidity/loops",
+ "learn/solidity/require-revert-error",
+ "learn/solidity/control-overview",
+ "learn/solidity/exercise-control"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Development with Foundry",
+ "pages": [
+ "learn/foundry/introduction-to-foundry",
+ "learn/foundry/testing"
+ ]
+ },
+ {
+ "group": "Development with Hardhat",
+ "pages": [
+ {
+ "group": "Hardhat Setup and Overview",
+ "pages": [
+ "learn/hardhat/overview",
+ "learn/hardhat/creating-project",
+ "learn/hardhat/setup"
+ ]
+ },
+ {
+ "group": "Testing with Typescript",
+ "pages": [
+ "learn/hardhat/testing",
+ "learn/hardhat/writing-tests",
+ "learn/hardhat/contract-abi-testing",
+ "learn/hardhat/testing-guide"
+ ]
+ },
+ {
+ "group": "Etherscan",
+ "pages": [
+ "learn/hardhat/etherscan-guide",
+ "learn/hardhat/etherscan-video"
+ ]
+ },
+ {
+ "group": "Deploying Smart Contracts",
+ "pages": [
+ "learn/hardhat/installing-deploy",
+ "learn/hardhat/setup-deploy-script",
+ "learn/hardhat/testing-deployment",
+ "learn/hardhat/network-configuration",
+ "learn/hardhat/deployment",
+ "learn/hardhat/deployment-guide"
+ ]
+ },
+ {
+ "group": "Verifying Smart Contracts",
+ "pages": [
+ "learn/hardhat/verify-video",
+ "learn/hardhat/verify-guide"
+ ]
+ },
+ {
+ "group": "Mainnet Forking",
+ "pages": [
+ "learn/hardhat/fork-video",
+ "learn/hardhat/fork-guide"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Token Development",
+ "pages": []
+ },
+ {
+ "group": "Exercise Contracts",
+ "pages": ["learn/exercise-contracts"]
+ }
+ ]
+ }
+ ]
+ },
+ "logo": {
+ "light": "/logo/light.svg",
+ "dark": "/logo/dark.svg"
+ },
+ "navbar": {
+ "links": [
+ {
+ "label": "Blog",
+ "href": "https://blog.base.dev/"
+ },
+ {
+ "label": "GitHub",
+ "href": "https://github.com/base"
+ },
+ {
+ "label": "Support",
+ "href": "https://discord.com/invite/base"
+ }
+ ],
+ "primary": {
+ "type": "button",
+ "label": "Base.org",
+ "href": "https://base.org"
+ }
+ },
+ "footer": {
+ "socials": {
+ "x": "https://x.com/mintlify",
+ "github": "https://github.com/mintlify",
+ "linkedin": "https://linkedin.com/company/mintlify"
+ }
+ }
+}
diff --git a/docs/base-chain/flashblocks/apps.mdx b/docs/base-chain/flashblocks/apps.mdx
new file mode 100644
index 00000000..ac866eb2
--- /dev/null
+++ b/docs/base-chain/flashblocks/apps.mdx
@@ -0,0 +1,195 @@
+---
+title: Flashblocks
+sidebarTitle: Apps
+description: Experience lightning-fast transaction confirmations on Base by using Flashblocks. Preconfirmations happen in just 200 milliseconds—designed for real-time apps, games, and seamless UX.
+---
+
+## Overview
+
+Flashblocks enable up to 200 millisecond transaction confirmations on Base by leveraging preconfirmations, ultra-fast signals that arrive before the next block is sealed. Built for developers who demand instant UX, it's ideal for high-frequency apps, games, and real-time interactions where waiting even a few seconds is too long. By integrating directly within Base's infrastructure, Flashblocks enables, seamless, ultrafast and snappy user experiences without compromising security.
+
+## Integrating Flashblocks
+
+Flashblocks is enabled for developers on Base Sepolia with full support for mainnet coming very soon. There are two ways you can integrate with Flashblocks data. You can either use the WebSocket API to stream real-time block updates, or use the RPC API to query the Flashblocks-aware RPC endpoint.
+
+### WebSocket API
+
+Use our API to stream realtime block updates over a WebSocket.
+
+You can connect to the websocket endpoint with any WebSocket library of CLI tool. The endpoint is available at wss://sepolia.flashblocks.base.org/ws.
+
+Two recommended tools for connecting to the WebSocket endpoint are [Websocat](https://github.com/vi/websocat) and the [Javascript Websocket Client](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applicationsjk).
+
+#### Websocat Example
+
+Firstly install websocat, [following these instructions](https://github.com/vi/websocat?tab=readme-ov-file#installation).
+
+From your terminal, you can then connect to the websocket stream by running:
+```
+websocat wss://sepolia.flashblocks.base.org/ws
+```
+
+In your terminal, you'll see a stream of all the Flashblocks being sent over the websocket connection.
+
+#### Interpreting the data
+
+To minimize the amount of data sent to clients, each Flashblock only includes the diff data from the previous block. The initial Flashblock (when index is zero) includes the block properties (e.g. number, gas limit) and the subsequent Flashblocks only include the diff data (e.g. transactions that are present in that Flashblock).
+
+**Example Initial Response**
+```
+{
+ "payload_id": "0x03997352d799c31a",
+ "index": 0,
+ "base": {
+ "parent_hash": "0x9edc29b8b0a1e31d28616e40c16132ad0d58faa8bb952595b557526bdb9a960a",
+ "fee_recipient": "0x4200000000000000000000000000000000000011",
+ "block_number": "0x158a0e9",
+ "gas_limit": "0x3938700",
+ "timestamp": "0x67bf8332",
+ "base_fee_per_gas": "0xfa"
+ // ... other base fields ...
+ },
+ "diff": {
+ "state_root": "0x208fd63edc0681161105f27d03daf9f8c726d8c94e584a3c0696c98291c24333",
+ "block_hash": "0x5c330e55a190f82ea486b61e5b12e27dfb4fb3cecfc5746886ef38ca1281bce8",
+ "gas_used": "0xab3f",
+ "transactions": [
+ "0x7ef8f8a0b4afc0b7ce10e150801bbaf08ac33fecb0f38311793abccb022120d321c6d276..."
+ ],
+ "withdrawals": []
+ // ... other diff fields ...
+ },
+ "metadata": {
+ "block_number": 22585577,
+ "new_account_balances": {
+ "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": "0x0",
+ // ... other balances ...
+ },
+ "receipts": {
+ "0x07d7f06b06fea714c1d1d446efa2790c6970aa74ee006186a32b5b7dd8ca2d82": {
+ "Deposit": {
+ "status": "0x1",
+ "depositNonce": "0x158a0ea"
+ // ... other receipt fields ...
+ }
+ }
+ }
+ }
+}
+```
+
+**Example Diff Response**
+```
+{
+ "payload_id": "0x03e303378749418d",
+ "index": 4,
+ "diff": {
+ "state_root": "0x7a8f45038665072f382730e689f4a1561835c9987fca8942fa95872fb9367eaa",
+ "block_hash": "0x9b32f7a14cbd1efc8c2c5cad5eb718ec9e0c5da92c3ba7080f8d4c49d660c332",
+ "gas_used": "0x1234f",
+ "transactions": [
+ "0x7ef8f8a0b4afc0b7ce10e150801bbaf08ac33fecb0f38311793abccb022120d321c6d276..."
+ ],
+ "withdrawals": []
+ // ... other diff fields ...
+ },
+ "metadata": {
+ "block_number": 22585577,
+ "new_account_balances": {
+ "0x000f3df6d732807ef1319fb7b8bb8522d0beac02": "0x0",
+ "0x4200000000000000000000000000000000000015": "0x1234"
+ // ... other balances ...
+ },
+ "receipts": {
+ "0x07d7f06b06fea714c1d1d446efa2790c6970aa74ee006186a32b5b7dd8ca2d82": {
+ "status": "0x1",
+ "gasUsed": "0x1234f",
+ "logs": []
+ // ... other receipt fields ...
+ }
+ }
+ }
+}
+```
+
+### RPC API
+
+You can also utilize our Flashblock aware RPC endpoint at `https://sepolia-preconf.base.org`.
+
+In addition to these flashblock-specific methods, all standard Ethereum JSON-RPC methods are supported as usual.
+
+#### eth_getBlockByNumber
+
+Use the `pending` tag to retrieve the latest Flashblock:
+```
+curl https://sepolia-preconf.base.org -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["pending",true],"id":1}'
+```
+
+**Example Response**
+```
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "number": "0x1234",
+ "hash": "0x...",
+ "transactions": [...]
+ }
+}
+```
+
+#### eth_getTransactionReceipt
+
+Use the existing receipt RPC to get preconfirmed receipts:
+```
+curl https://sepolia-preconf.base.org -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0x..."],"id":1}'
+```
+
+**Example Response**
+```
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": {
+ "transactionHash": "0x...",
+ "blockNumber": "0x1234",
+ "status": "0x1"
+ }
+}
+```
+
+#### eth_getBalance
+
+Use the `pending` tag to get the address balance in the latest Flashblock:
+```
+curl https://sepolia-preconf.base.org -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_getBalance","params":["0x...","pending"],"id":1}'
+```
+
+**Example Response**
+```
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": "0x0234"
+}
+```
+
+#### eth_getTransactionCount
+
+Use the `pending` tag to get the address nonce in the latest Flashblock:
+```
+curl https://sepolia-preconf.base.org -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_getTransactionCount","params":["0x...","pending"],"id":1}'
+```
+
+**Example Response**
+```
+{
+ "jsonrpc": "2.0",
+ "id": 1,
+ "result": "0x1b" // 27 transactions
+}
+```
+
+## Support
+
+For feedback, support or questions about Flashblocks, please don't hesitate to contact us in the `#developer-chat` channel in the [Base Discord](https://base.org/discord).
diff --git a/docs/base-chain/flashblocks/node-providers.mdx b/docs/base-chain/flashblocks/node-providers.mdx
new file mode 100644
index 00000000..699e5c46
--- /dev/null
+++ b/docs/base-chain/flashblocks/node-providers.mdx
@@ -0,0 +1,48 @@
+---
+title: How to host Flashblocks-aware RPC nodes
+sidebarTitle: Node Providers
+---
+
+## Quick Start
+
+1. **Prerequisites**:
+ - Docker and Docker Compose
+ - Minimum hardware requirements (see [node README](https://github.com/base/node?tab=readme-ov-file#hardware-requirements))
+ - Access to a Flashblocks websocket endpoint, we provide public endpoints in the env files in the repo
+
+2. **Set Up Environment**:
+
+ ```sh
+ # Clone the repository
+ git clone https://github.com/base/node.git
+ cd node
+ ```
+
+3. **Start the Node with Flashblocks Support**:
+
+ ```sh
+ NODE_TYPE=base CLIENT=reth docker-compose up
+ ```
+
+## Configuration Options
+
+- Node Type: Use `NODE_TYPE=base` to enable base reth node withFlashblocks functionality
+- Network: Use `NETWORK_ENV=.env.mainnet` for mainnet or `NETWORK_ENV=.env.sepolia` for testnet
+
+## Verifying Flashblocks Functionality
+
+Test that your node is properly supporting Flashblocks by querying a pending block:
+
+```sh
+curl -X POST \
+ --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["pending", false],"id":1}' \
+ http://localhost:8545
+```
+
+## Available RPC Methods
+
+Flashblocks-aware nodes provide all standard Ethereum JSON-RPC methods plus specialized Flashblocks endpoints. For more details, see the [Flashblocks RPC API documentation](/base-chain/flashblocks/apps#rpc-api).
+
+## Further Resources
+
+For detailed information about node setup, including hardware requirements and additional configuration options, refer to the [Reth node README](https://github.com/base/node/tree/main/reth#readme).
\ No newline at end of file
diff --git a/docs/base-chain/network-information/base-contracts.mdx b/docs/base-chain/network-information/base-contracts.mdx
new file mode 100644
index 00000000..a8938937
--- /dev/null
+++ b/docs/base-chain/network-information/base-contracts.mdx
@@ -0,0 +1,131 @@
+---
+sidebarTitle: Base Contracts
+title: Contract Addresses
+description: A comprehensive list of L2 contract addresses for Base Mainnet and Base Testnet, including links to their respective blockchain explorers.
+---
+
+## L2 Contract Addresses
+
+### Base Mainnet
+
+| Name | Address |
+| :---------------------------- | :-------------------------------------------------------------------------------------------------------------------- |
+| WETH9 | [0x4200000000000000000000000000000000000006](https://basescan.org/address/0x4200000000000000000000000000000000000006) |
+| L2CrossDomainMessenger | [0x4200000000000000000000000000000000000007](https://basescan.org/address/0x4200000000000000000000000000000000000007) |
+| L2StandardBridge | [0x4200000000000000000000000000000000000010](https://basescan.org/address/0x4200000000000000000000000000000000000010) |
+| SequencerFeeVault | [0x4200000000000000000000000000000000000011](https://basescan.org/address/0x4200000000000000000000000000000000000011) |
+| OptimismMintableERC20Factory | [0xF10122D428B4bc8A9d050D06a2037259b4c4B83B](https://basescan.org/address/0xF10122D428B4bc8A9d050D06a2037259b4c4B83B) |
+| GasPriceOracle | [0x420000000000000000000000000000000000000F](https://basescan.org/address/0x420000000000000000000000000000000000000F) |
+| L1Block | [0x4200000000000000000000000000000000000015](https://basescan.org/address/0x4200000000000000000000000000000000000015) |
+| L2ToL1MessagePasser | [0x4200000000000000000000000000000000000016](https://basescan.org/address/0x4200000000000000000000000000000000000016) |
+| L2ERC721Bridge | [0x4200000000000000000000000000000000000014](https://basescan.org/address/0x4200000000000000000000000000000000000014) |
+| OptimismMintableERC721Factory | [0x4200000000000000000000000000000000000017](https://basescan.org/address/0x4200000000000000000000000000000000000017) |
+| ProxyAdmin | [0x4200000000000000000000000000000000000018](https://basescan.org/address/0x4200000000000000000000000000000000000018) |
+| BaseFeeVault | [0x4200000000000000000000000000000000000019](https://basescan.org/address/0x4200000000000000000000000000000000000019) |
+| L1FeeVault | [0x420000000000000000000000000000000000001a](https://basescan.org/address/0x420000000000000000000000000000000000001a) |
+| EAS | [0x4200000000000000000000000000000000000021](https://basescan.org/address/0x4200000000000000000000000000000000000021) |
+| EASSchemaRegistry | [0x4200000000000000000000000000000000000020](https://basescan.org/address/0x4200000000000000000000000000000000000020) |
+| LegacyERC20ETH | [0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000](https://basescan.org/address/0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000) |
+
+### Base Testnet (Sepolia)
+
+| Name | Address |
+| :---------------------------- | :---------------------------------------------------------------------------------------------------------------------------- |
+| WETH9 | [0x4200000000000000000000000000000000000006](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000006) |
+| L2CrossDomainMessenger | [0x4200000000000000000000000000000000000007](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000007) |
+| L2StandardBridge | [0x4200000000000000000000000000000000000010](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000010) |
+| SequencerFeeVault | [0x4200000000000000000000000000000000000011](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000011) |
+| OptimismMintableERC20Factory | [0x4200000000000000000000000000000000000012](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000012) |
+| GasPriceOracle | [0x420000000000000000000000000000000000000F](https://sepolia.basescan.org/address/0x420000000000000000000000000000000000000F) |
+| L1Block | [0x4200000000000000000000000000000000000015](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000015) |
+| L2ToL1MessagePasser | [0x4200000000000000000000000000000000000016](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000016) |
+| L2ERC721Bridge | [0x4200000000000000000000000000000000000014](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000014) |
+| OptimismMintableERC721Factory | [0x4200000000000000000000000000000000000017](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000017) |
+| ProxyAdmin | [0x4200000000000000000000000000000000000018](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000018) |
+| BaseFeeVault | [0x4200000000000000000000000000000000000019](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000019) |
+| L1FeeVault | [0x420000000000000000000000000000000000001a](https://sepolia.basescan.org/address/0x420000000000000000000000000000000000001a) |
+| EAS | [0x4200000000000000000000000000000000000021](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000021) |
+| EASSchemaRegistry | [0x4200000000000000000000000000000000000020](https://sepolia.basescan.org/address/0x4200000000000000000000000000000000000020) |
+| LegacyERC20ETH | [0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000](https://sepolia.basescan.org/address/0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000) |
+
+\*_L2 contract addresses are the same on both mainnet and testnet._
+
+## L1 Contract Addresses
+
+### Ethereum Mainnet
+
+| Name | Address |
+| :--------------------------- | :-------------------------------------------------------------------------------------------------------------------- |
+| AddressManager | [0x8EfB6B5c4767B09Dc9AA6Af4eAA89F749522BaE2](https://etherscan.io/address/0x8EfB6B5c4767B09Dc9AA6Af4eAA89F749522BaE2) |
+| AnchorStateRegistryProxy | [0x496286e5eE7758de84Dd17e6d2d97afC2ACE4cc7](https://etherscan.io/address/0x496286e5eE7758de84Dd17e6d2d97afC2ACE4cc7) |
+| DelayedWETHProxy (FDG) | [0xa2f2aC6F5aF72e494A227d79Db20473Cf7A1FFE8](https://etherscan.io/address/0xa2f2aC6F5aF72e494A227d79Db20473Cf7A1FFE8) |
+| DelayedWETHProxy (PDG) | [0x3E8a0B63f57e975c268d610ece93da5f78c01321](https://etherscan.io/address/0x3E8a0B63f57e975c268d610ece93da5f78c01321) |
+| DisputeGameFactoryProxy | [0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e](https://etherscan.io/address/0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e) |
+| FaultDisputeGame | [0xE17d670043c3cDd705a3223B3D89A228A1f07F0f](https://etherscan.io/address/0xE17d670043c3cDd705a3223B3D89A228A1f07F0f) |
+| L1CrossDomainMessenger | [0x866E82a600A1414e583f7F13623F1aC5d58b0Afa](https://etherscan.io/address/0x866E82a600A1414e583f7F13623F1aC5d58b0Afa) |
+| L1ERC721Bridge | [0x608d94945A64503E642E6370Ec598e519a2C1E53](https://etherscan.io/address/0x608d94945A64503E642E6370Ec598e519a2C1E53) |
+| L1StandardBridge | [0x3154Cf16ccdb4C6d922629664174b904d80F2C35](https://etherscan.io/address/0x3154Cf16ccdb4C6d922629664174b904d80F2C35) |
+| MIPS | [0xF027F4A985560fb13324e943edf55ad6F1d15Dc1](https://etherscan.io/address/0xF027F4A985560fb13324e943edf55ad6F1d15Dc1) |
+| OptimismMintableERC20Factory | [0x05cc379EBD9B30BbA19C6fA282AB29218EC61D84](https://etherscan.io/address/0x05cc379EBD9B30BbA19C6fA282AB29218EC61D84) |
+| OptimismPortal | [0x49048044D57e1C92A77f79988d21Fa8fAF74E97e](https://etherscan.io/address/0x49048044D57e1C92A77f79988d21Fa8fAF74E97e) |
+| PermissionedDisputeGame | [0xE749aA49c3eDAF1DCb997eA3DAC23dff72bcb826](https://etherscan.io/address/0xE749aA49c3eDAF1DCb997eA3DAC23dff72bcb826) |
+| PreimageOracle | [0x1fb8cdFc6831fc866Ed9C51aF8817Da5c287aDD3](https://etherscan.io/address/0x1fb8cdFc6831fc866Ed9C51aF8817Da5c287aDD3) |
+| ProxyAdmin | [0x0475cBCAebd9CE8AfA5025828d5b98DFb67E059E](https://etherscan.io/address/0x0475cBCAebd9CE8AfA5025828d5b98DFb67E059E) |
+| SystemConfig | [0x73a79Fab69143498Ed3712e519A88a918e1f4072](https://etherscan.io/address/0x73a79Fab69143498Ed3712e519A88a918e1f4072) |
+| SystemDictator | [0x1fE3fdd1F0193Dd657C0a9AAC37314D6B479E557](https://etherscan.io/address/0x1fE3fdd1F0193Dd657C0a9AAC37314D6B479E557) |
+
+**Unneeded contract addresses**
+
+Certain contracts are mandatory according to the [OP Stack SDK](https://stack.optimism.io/docs/build/sdk/#unneeded-contract-addresses), despite not being utilized. For such contracts, you can simply assign the zero address:
+
+- `StateCommitmentChain`
+- `CanonicalTransactionChain`
+- `BondManager`
+
+### Ethereum Testnet (Sepolia)
+
+| Name | Address |
+| :--------------------------- | :---------------------------------------------------------------------------------------------------------------------------- |
+| AddressManager | [0x709c2B8ef4A9feFc629A8a2C1AF424Dc5BD6ad1B](https://sepolia.etherscan.io/address/0x709c2B8ef4A9feFc629A8a2C1AF424Dc5BD6ad1B) |
+| AnchorStateRegistryProxy | [0x0729957c92A1F50590A84cb2D65D761093f3f8eB](https://sepolia.etherscan.io/address/0x0729957c92A1F50590A84cb2D65D761093f3f8eB) |
+| DelayedWETHProxy (FDG) | [0x489c2E5ebe0037bDb2DC039C5770757b8E54eA1F](https://sepolia.etherscan.io/address/0x489c2E5ebe0037bDb2DC039C5770757b8E54eA1F) |
+| DelayedWETHProxy (PDG) | [0x27A6128F707de3d99F89Bf09c35a4e0753E1B808](https://sepolia.etherscan.io/address/0x27A6128F707de3d99F89Bf09c35a4e0753E1B808) |
+| DisputeGameFactoryProxy | [0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1](https://sepolia.etherscan.io/address/0xd6E6dBf4F7EA0ac412fD8b65ED297e64BB7a06E1) |
+| FaultDisputeGame | [0xcfce7dd673fbbbffd16ab936b7245a2f2db31c9a](https://sepolia.etherscan.io/address/0xcfce7dd673fbbbffd16ab936b7245a2f2db31c9a) |
+| L1CrossDomainMessenger | [0xC34855F4De64F1840e5686e64278da901e261f20](https://sepolia.etherscan.io/address/0xC34855F4De64F1840e5686e64278da901e261f20) |
+| L1ERC721Bridge | [0x21eFD066e581FA55Ef105170Cc04d74386a09190](https://sepolia.etherscan.io/address/0x21eFD066e581FA55Ef105170Cc04d74386a09190) |
+| L1StandardBridge | [0xfd0Bf71F60660E2f608ed56e1659C450eB113120](https://sepolia.etherscan.io/address/0xfd0Bf71F60660E2f608ed56e1659C450eB113120) |
+| L2OutputOracle | [0x84457ca9D0163FbC4bbfe4Dfbb20ba46e48DF254](https://sepolia.etherscan.io/address/0x84457ca9D0163FbC4bbfe4Dfbb20ba46e48DF254) |
+| MIPS | [0xF027F4A985560fb13324e943edf55ad6F1d15Dc1](https://sepolia.etherscan.io/address/0xF027F4A985560fb13324e943edf55ad6F1d15Dc1) |
+| OptimismMintableERC20Factory | [0xb1efB9650aD6d0CC1ed3Ac4a0B7f1D5732696D37](https://sepolia.etherscan.io/address/0xb1efB9650aD6d0CC1ed3Ac4a0B7f1D5732696D37) |
+| OptimismPortal | [0x49f53e41452C74589E85cA1677426Ba426459e85](https://sepolia.etherscan.io/address/0x49f53e41452C74589E85cA1677426Ba426459e85) |
+| PermissionedDisputeGame | [0xf0102ffe22649a5421d53acc96e309660960cf44](https://sepolia.etherscan.io/address/0xf0102ffe22649a5421d53acc96e309660960cf44) |
+| PreimageOracle | [0x1fb8cdFc6831fc866Ed9C51aF8817Da5c287aDD3](https://sepolia.etherscan.io/address/0x1fb8cdFc6831fc866Ed9C51aF8817Da5c287aDD3) |
+| ProxyAdmin | [0x0389E59Aa0a41E4A413Ae70f0008e76CAA34b1F3](https://sepolia.etherscan.io/address/0x0389E59Aa0a41E4A413Ae70f0008e76CAA34b1F3) |
+| SystemConfig | [0xf272670eb55e895584501d564AfEB048bEd26194](https://sepolia.etherscan.io/address/0xf272670eb55e895584501d564AfEB048bEd26194) |
+
+## Base Admin Addresses
+
+### Base Mainnet
+
+| Admin Role | Address | Type of Key |
+| :------------------------------- | :-------------------------------------------------------------------------------------------------------------------- | :---------------------------------------- |
+| Batch Sender | [0x5050f69a9786f081509234f1a7f4684b5e5b76c9](https://etherscan.io/address/0x5050f69a9786f081509234f1a7f4684b5e5b76c9) | EOA managed by Coinbase Technologies |
+| Batch Inbox | [0xff00000000000000000000000000000000008453](https://etherscan.io/address/0xff00000000000000000000000000000000008453) | EOA (with no known private key) |
+| Output Proposer | [0x642229f238fb9de03374be34b0ed8d9de80752c5](https://etherscan.io/address/0x642229f238fb9de03374be34b0ed8d9de80752c5) | EOA managed by Coinbase Technologies |
+| Proxy Admin Owner (L1) | [0x7bB41C3008B3f03FE483B28b8DB90e19Cf07595c](https://etherscan.io/address/0x7bB41C3008B3f03FE483B28b8DB90e19Cf07595c) | Gnosis Safe |
+| Challenger | [0x8Ca1E12404d16373Aef756179B185F27b2994F3a](https://etherscan.io/address/0x8Ca1E12404d16373Aef756179B185F27b2994F3a) | EOA managed by Coinbase Technologies |
+| System config owner | [0x14536667Cd30e52C0b458BaACcB9faDA7046E056](https://etherscan.io/address/0x14536667Cd30e52C0b458BaACcB9faDA7046E056) | Gnosis Safe |
+| Guardian | [0x09f7150D8c019BeF34450d6920f6B3608ceFdAf2](https://etherscan.io/address/0x09f7150D8c019BeF34450d6920f6B3608ceFdAf2) | Gnosis Safe |
+
+### Base Testnet (Sepolia)
+
+| Admin Role | Address | Type of Key |
+| :--------------------- | :--------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------- |
+| Batch Sender | [0x6CDEbe940BC0F26850285cacA097C11c33103E47](https://sepolia.etherscan.io/address/0x6CDEbe940BC0F26850285cacA097C11c33103E47) | EOA managed by Coinbase Technologies |
+| Batch Inbox | [0xff00000000000000000000000000000000084532](https://sepolia.etherscan.io/address/0xff00000000000000000000000000000000084532) | EOA (with no known private key) |
+| Output Proposer | [0x037637067c1DbE6d2430616d8f54Cb774Daa5999](https://sepolia.etherscan.io/address/0x037637067c1DbE6d2430616d8f54Cb774Daa5999) | EOA managed by Coinbase Technologies |
+| Proxy Admin Owner (L1) | [0x0fe884546476dDd290eC46318785046ef68a0BA9](https://sepolia.etherscan.io/address/0x0fe884546476dDd290eC46318785046ef68a0BA9) | Gnosis Safe |
+| Challenger | [0x8b8c52B04A38f10515C52670fcb23f3C4C44474F](https://sepolia.etherscan.io/address/0x8b8c52B04A38f10515C52670fcb23f3C4C44474F) | EOA managed by Coinbase Technologies |
+| System config owner | [0x0fe884546476dDd290eC46318785046ef68a0BA9](https://sepolia.etherscan.io/address/0x0fe884546476dDd290eC46318785046ef68a0BA9) | Gnosis Safe |
+| Guardian | [0xA9FF930151130fd19DA1F03E5077AFB7C78F8503](https://sepolia.etherscan.io/address/0xA9FF930151130fd19DA1F03E5077AFB7C78F8503) | EOA managed by Coinbase Technologies |
diff --git a/docs/base-chain/network-information/diffs-ethereum-base.mdx b/docs/base-chain/network-information/diffs-ethereum-base.mdx
new file mode 100644
index 00000000..7dd308d1
--- /dev/null
+++ b/docs/base-chain/network-information/diffs-ethereum-base.mdx
@@ -0,0 +1,16 @@
+---
+title: "Differences between Ethereum and Base"
+sidebarTitle: 'Differences: Ethereum & Base'
+---
+
+Base is built on the [Bedrock](https://stack.optimism.io/docs/releases/bedrock/explainer/) release of the [OP Stack](https://stack.optimism.io/), which is designed from the ground up to be as close to Ethereum as possible. Because of this, there are very few differences when it comes to building on Base and Ethereum.
+
+However, there are still some minor discrepancies between the behavior of Base and Ethereum that you should be aware of when building apps on top of Base.
+
+These minor differences include:
+
+- [Opcodes](https://stack.optimism.io/docs/releases/bedrock/differences/#opcode-differences)
+- [Blocks](https://stack.optimism.io/docs/releases/bedrock/differences/#blocks)
+- [Network specifications](https://stack.optimism.io/docs/releases/bedrock/differences/#network-specifications)
+- [Transaction costs](https://stack.optimism.io/docs/releases/bedrock/differences/#transaction-costs)
+
diff --git a/docs/base-chain/network-information/ecosystem-contracts.mdx b/docs/base-chain/network-information/ecosystem-contracts.mdx
new file mode 100644
index 00000000..752e42b0
--- /dev/null
+++ b/docs/base-chain/network-information/ecosystem-contracts.mdx
@@ -0,0 +1,81 @@
+---
+title: 'Ecosystem Contracts'
+---
+
+This page lists contract addresses for onchain apps that we have deployed.
+
+## Base Mainnet
+
+### Multicall3
+
+| Contract | Address |
+| :--------- | :-------------------------------------------------------------------------------------------------------------------- |
+| Multicall3 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://basescan.org/address/0xcA11bde05977b3631167028862bE2a173976CA11) |
+
+### Uniswap v3
+
+| Contract | Address |
+| :----------------------------------- | :-------------------------------------------------------------------------------------------------------------------- |
+| `Permit2` | [0x000000000022D473030F116dDEE9F6B43aC78BA3](https://basescan.org/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) |
+| `universal router` | [0x198EF79F1F515F02dFE9e3115eD9fC07183f02fC](https://basescan.org/address/0x198EF79F1F515F02dFE9e3115eD9fC07183f02fC) |
+| `v3CoreFactory` | [0x33128a8fC17869897dcE68Ed026d694621f6FDfD](https://basescan.org/address/0x33128a8fC17869897dcE68Ed026d694621f6FDfD) |
+| `multicall` | [0x091e99cb1C49331a94dD62755D168E941AbD0693](https://basescan.org/address/0x091e99cb1C49331a94dD62755D168E941AbD0693) |
+| `proxyAdmin` | [0x3334d83e224aF5ef9C2E7DDA7c7C98Efd9621fA9](https://basescan.org/address/0x3334d83e224aF5ef9C2E7DDA7c7C98Efd9621fA9) |
+| `tickLens` | [0x0CdeE061c75D43c82520eD998C23ac2991c9ac6d](https://basescan.org/address/0x0CdeE061c75D43c82520eD998C23ac2991c9ac6d) |
+| `nftDescriptor` | [0xF9d1077fd35670d4ACbD27af82652a8d84577d9F](https://basescan.org/address/0xF9d1077fd35670d4ACbD27af82652a8d84577d9F) |
+| `nonfungibleTokenPositionDescriptor` | [0x4f225937EDc33EFD6109c4ceF7b560B2D6401009](https://basescan.org/address/0x4f225937EDc33EFD6109c4ceF7b560B2D6401009) |
+| `descriptorProxy` | [0x4615C383F85D0a2BbED973d83ccecf5CB7121463](https://basescan.org/address/0x4615C383F85D0a2BbED973d83ccecf5CB7121463) |
+| `nonfungibleTokenPositionManager` | [0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1](https://basescan.org/address/0x03a520b32C04BF3bEEf7BEb72E919cf822Ed34f1) |
+| `v3Migrator` | [0x23cF10b1ee3AdfCA73B0eF17C07F7577e7ACd2d7](https://basescan.org/address/0x23cF10b1ee3AdfCA73B0eF17C07F7577e7ACd2d7) |
+| `v3Staker` | [0x42bE4D6527829FeFA1493e1fb9F3676d2425C3C1](https://basescan.org/address/0x42bE4D6527829FeFA1493e1fb9F3676d2425C3C1) |
+| `quoterV2` | [0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a](https://basescan.org/address/0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a) |
+| `swapRouter` | [0x2626664c2603336E57B271c5C0b26F421741e481](https://basescan.org/address/0x2626664c2603336E57B271c5C0b26F421741e481) |
+
+### Uniswap v2
+
+| Contract | Address |
+| :-------- | :-------------------------------------------------------------------------------------------------------------------- |
+| `Factory` | [0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6](https://basescan.org/address/0x8909Dc15e40173Ff4699343b6eB8132c65e18eC6) |
+| `Router` | [0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24](https://basescan.org/address/0x4752ba5dbc23f44d87826276bf6fd6b1c372ad24) |
+
+## Base Testnet (Sepolia)
+
+### Multicall3
+
+| Contract | Address |
+| :--------- | :---------------------------------------------------------------------------------------------------------------------------- |
+| Multicall3 | [0xcA11bde05977b3631167028862bE2a173976CA11](https://sepolia.basescan.org/address/0xcA11bde05977b3631167028862bE2a173976CA11) |
+
+### Uniswap v3
+
+| Contract | Address |
+| :----------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- |
+| `Permit2` | [0x000000000022d473030f116ddee9f6b43ac78ba3](https://sepolia.basescan.org/address/0x000000000022d473030f116ddee9f6b43ac78ba3) |
+| `universal router` | [0x050E797f3625EC8785265e1d9BDd4799b97528A1](https://sepolia.basescan.org/address/0x050E797f3625EC8785265e1d9BDd4799b97528A1) |
+| `v3CoreFactory` | [0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24](https://sepolia.basescan.org/address/0x4752ba5DBc23f44D87826276BF6Fd6b1C372aD24) |
+| `multicall` | [0xd867e273eAbD6c853fCd0Ca0bFB6a3aE6491d2C1](https://sepolia.basescan.org/address/0xd867e273eAbD6c853fCd0Ca0bFB6a3aE6491d2C1) |
+| `proxyAdmin` | [0xD7303474Baca835743B54D73799688990f24a79D](https://sepolia.basescan.org/address/0xD7303474Baca835743B54D73799688990f24a79D) |
+| `tickLens` | [0xedf6066a2b290C185783862C7F4776A2C8077AD1](https://sepolia.basescan.org/address/0xedf6066a2b290C185783862C7F4776A2C8077AD1) |
+| `nftDescriptor` | [0x4e0caFF1Df1cCd7CF782FDdeD77f020699B57f1a](https://sepolia.basescan.org/address/0x4e0caFF1Df1cCd7CF782FDdeD77f020699B57f1a) |
+| `nonfungibleTokenPositionDescriptor` | [0xd7c6e867591608D32Fe476d0DbDc95d0cf584c8F](https://sepolia.basescan.org/address/0xd7c6e867591608D32Fe476d0DbDc95d0cf584c8F) |
+| `nonfungibleTokenPositionManager` | [0x27F971cb582BF9E50F397e4d29a5C7A34f11faA2](https://sepolia.basescan.org/address/0x27F971cb582BF9E50F397e4d29a5C7A34f11faA2) |
+| `v3Migrator` | [0xCbf8b7f80800bd4888Fbc7bf1713B80FE4E23E10](https://sepolia.basescan.org/address/0xCbf8b7f80800bd4888Fbc7bf1713B80FE4E23E10) |
+| `v3Staker` | [0x62725F55f50bdE240aCa3e740D47298CAc8d57D5](https://sepolia.basescan.org/address/0x62725F55f50bdE240aCa3e740D47298CAc8d57D5) |
+| `quoterV2` | [0xC5290058841028F1614F3A6F0F5816cAd0df5E27](https://sepolia.basescan.org/address/0xC5290058841028F1614F3A6F0F5816cAd0df5E27) |
+| `swapRouter` | [0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4](https://sepolia.basescan.org/address/0x94cC0AaC535CCDB3C01d6787D6413C739ae12bc4) |
+
+#### Testnet interfaces
+
+:::info
+
+Two community projects, [BaseX](https://basex-test.vercel.app/swap?currencyA=ETH¤cyB=0x036CbD53842c5426634e7929541eC2318f3dCF7e&focus=source) and [DapDap](https://testnet.base.dapdap.net/uniswap/swap), provide testnet interfaces for Uniswap contracts if you prefer to interact in the browser instead of with the contracts directly.
+
+:::
+
+### Uniswap v2
+
+| Contract | Address |
+| :-------- | :---------------------------------------------------------------------------------------------------------------------------- |
+| `Factory` | [0x7Ae58f10f7849cA6F5fB71b7f45CB416c9204b1e](https://sepolia.basescan.org/address/0x7Ae58f10f7849cA6F5fB71b7f45CB416c9204b1e) |
+| `Router` | [0x1689E7B1F10000AE47eBfE339a4f69dECd19F602](https://sepolia.basescan.org/address/0x1689E7B1F10000AE47eBfE339a4f69dECd19F602) |
+
diff --git a/docs/base-chain/network-information/network-fees.mdx b/docs/base-chain/network-information/network-fees.mdx
new file mode 100644
index 00000000..f735b60d
--- /dev/null
+++ b/docs/base-chain/network-information/network-fees.mdx
@@ -0,0 +1,28 @@
+---
+title: Network Fees
+description: Documentation about network fees on Base. This page covers details of the two-component cost system involving L2 execution fees and L1 security fees, and offers insights on fee variations and cost-saving strategies.
+---
+
+# Fees
+
+## How do network fees on Base work?
+
+Every Base transaction consists of two costs: an L2 (execution) fee and an L1
+(security) fee. The L2 fee is the cost to execute your transaction on the L2,
+and the L1 fee is the estimated cost to publish the transaction on the L1.
+Typically the L1 security fee is higher than the L2 execution fee.
+
+The L1 fee will vary depending on the amount of transactions on the L1. If the
+timing of your transaction is flexible, you can save costs by submitting
+transactions during periods of lower gas on the L1 (for example, over the
+weekend)
+
+Similarly, the L2 fee can increase and decrease depending on how many
+transactions are being submitted to the L2. This adjustment mechanism has the
+same implementation as the L1; you can read more about it
+[here](https://help.coinbase.com/en/coinbase/getting-started/crypto-education/eip-1559).
+
+For additional details about fee calculation on Base, please refer to the
+[op-stack developer
+documentation](https://docs.optimism.io/stack/transactions/fees).
+
diff --git a/docs/base-chain/node-operators/performance-tuning.mdx b/docs/base-chain/node-operators/performance-tuning.mdx
new file mode 100644
index 00000000..3bb251e4
--- /dev/null
+++ b/docs/base-chain/node-operators/performance-tuning.mdx
@@ -0,0 +1,103 @@
+---
+title: Node Performance
+sidebarTitle: Performance Tuning
+---
+
+This guide provides recommendations for hardware, client software, and configuration settings to optimize the performance of your Base node.
+
+## Hardware
+
+Running a performant Base node requires adequate hardware. We recommend the following minimum specifications:
+
+1. A modern multi-core CPU with good single-core performance.
+2. At least 32 GB RAM (64 GB recommended).
+3. A locally attached NVMe SSD drive. RAID 0 configurations can improve performance.
+4. Sufficient storage capacity calculated as:
+
+ ```
+ (2 \* [current chain size](https://base.org/stats) + [snapshot size](https://basechaindata.vercel.app) + 20% buffer)
+ ```
+
+ This accounts for chain data growth and snapshot restoration space.
+
+
+If utilizing Amazon Elastic Block Store (EBS), io2 Block Express volumes are recommended to ensure sufficient disk read speeds, preventing latency issues during initial sync. However, **locally attached NVMe SSDs are strongly recommended over networked storage for optimal performance.**
+
+
+### Production Hardware Examples
+
+The following are the hardware specifications used for Base production nodes:
+
+- **Geth Full Node:**
+ - Instance: AWS `i4i.12xlarge`
+ - Storage: RAID 0 of all local NVMe drives (`/dev/nvme*`)
+ - Filesystem: ext4
+
+- **Reth Archive Node:**
+ - Instance: AWS `i4ie.6xlarge`
+ - Storage: RAID 0 of all local NVMe drives (`/dev/nvme*`)
+ - Filesystem: ext4
+
+## Initial Sync
+
+Using a recent [snapshot](/base-chain/node-operators/snapshots.mdx) can significantly reduce the time required for the initial node synchronization process.
+
+## Client Software
+
+The [Base Node](https://github.com/base/node) repository contains the current stable configurations and instructions for running different client implementations.
+
+### Supported Clients
+
+Reth is currently the most performant client for running Base nodes. Future optimizations will primarily focus on Reth. You can read more about the migration to Reth [here](https://blog.base.dev/scaling-base-with-reth).
+
+| Type | Supported Clients |
+| ------- | -------------------------------------------------------------------------------------------------- |
+| Full | [Reth](https://github.com/base/node/tree/main/reth), [Geth](https://github.com/base/node/tree/main/geth) |
+| Archive | [Reth](https://github.com/base/node/tree/main/reth) |
+
+### Geth Performance Tuning
+
+#### Geth Cache Settings
+
+For Geth nodes, tuning cache allocation via environment variables can improve performance. These settings are used in the standard Docker configuration:
+
+```bash
+# .env.mainnet / .env.sepolia
+GETH_CACHE="20480" # Total P2P cache memory allowance (MB) (default: 1024)
+GETH_CACHE_DATABASE="20" # Percentage of cache memory allowance for database IO (default: 75)
+GETH_CACHE_GC="12" # Percentage of cache memory allowance for garbage collection (default: 25)
+GETH_CACHE_SNAPSHOT="24" # Percentage of cache memory allowance for snapshot caching (default: 10)
+GETH_CACHE_TRIE="44" # Percentage of cache memory allowance for trie caching (default: 25)
+```
+
+#### Geth LevelDB Tuning
+
+For teams running Geth with LevelDB, the following patch allows setting LevelDB initialization parameters via environment variables:
+
+[https://github.com/0x00101010/goleveldb/commit/55ef3429673fb70d389d052a15a4423e13d8b43c](https://github.com/0x00101010/goleveldb/commit/55ef3429673fb70d389d052a15a4423e13d8b43c)
+
+This patch can be applied using a `replace` directive in `go.mod` when building `op-geth`. Here’s how to modify your Dockerfile:
+
+```dockerfile
+RUN git clone $REPO --branch $VERSION --single-branch . && \
+ git switch -c branch-$VERSION $COMMIT && \
+ bash -c '[ "$(git rev-parse HEAD)" = "$COMMIT" ]'
+
+RUN echo '' >> go.mod && \
+ echo 'replace github.com/syndtr/goleveldb => github.com/0x00101010/goleveldb v1.0.4-param-customization' >> go.mod && \
+ go mod tidy
+
+# Continue building op-geth
+COPY op-geth/ ./
+RUN go run build/ci.go install -static ./cmd/geth
+```
+
+Recommended LevelDB environment variable values with this patch:
+
+```bash
+# Recommended LevelDB Settings
+LDB_BLOCK_SIZE="524288" # 512 KiB block size (matches common RAID 0 chunk sizes)
+LDB_COMPACTION_TABLE_SIZE="8388608" # 8 MiB compaction table size (default: 2 MiB)
+LDB_COMPACTION_TOTAL_SIZE="41943040" # 40 MiB total compaction size (default: 8 MiB)
+LDB_DEBUG_OPTIONS="1" # Emit LevelDB debug logs
+```
diff --git a/docs/base-chain/node-operators/run-a-base-node.mdx b/docs/base-chain/node-operators/run-a-base-node.mdx
new file mode 100644
index 00000000..1f7d9bfe
--- /dev/null
+++ b/docs/base-chain/node-operators/run-a-base-node.mdx
@@ -0,0 +1,113 @@
+---
+title: 'Getting Started'
+description: A tutorial that teaches how to set up and run a Base Node.
+---
+
+This tutorial will walk you through setting up your own [Base Node](https://github.com/base-org/node).
+
+## Objectives
+
+By the end of this tutorial you should be able to:
+
+- Deploy and sync a Base node
+
+## Prerequisites
+
+
+Running a node is time consuming, resource expensive, and potentially costly. If you don't already know why you want to run your own node, you probably don't need to.
+
+If you're just getting started and need an RPC URL, you can use our free endpoints:
+
+- **Mainnet**: `https://mainnet.base.org`
+- **Testnet (Sepolia)**: `https://sepolia.base.org`
+
+**Note:** Our RPCs are rate-limited, they are not suitable for production apps.
+
+If you're looking to harden your app and avoid rate-limiting for your users, please check out one of our [partners](/base-chain/tools/node-providers).
+
+
+
+### Hardware requirements
+
+We recommend you have this configuration to run a node:
+
+- 8-Core CPU
+- at least 16 GB RAM
+- a locally attached NVMe SSD drive
+- adequate storage capacity to accommodate both the snapshot restoration process (if restoring from snapshot) and chain data, ensuring a minimum of (2 \* current_chain_size) + snapshot_size + 20%\_buffer
+
+
+If utilizing Amazon Elastic Block Store (EBS), ensure timing buffered disk reads are fast enough in order to avoid latency issues alongside the rate of new blocks added to Base during the initial synchronization process; `io2 block express` is recommended.
+
+
+
+### Docker
+
+This tutorial assumes you are familiar with [Docker](https://www.docker.com/) and have it running on your machine.
+
+### L1 RPC URL
+
+You'll need your own L1 RPC URL. This can be one that you run yourself, or via a third-party provider, such as our [partners].
+
+## Running a Node
+
+1. Clone the [repo](https://github.com/base-org/node).
+2. Ensure you have an Ethereum L1 full node RPC available (not Base), and set `OP_NODE_L1_ETH_RPC` & `OP_NODE_L1_BEACON` (in the `.env.*` file if using `docker-compose`). If running your own L1 node, it needs to be synced before Base will be able to fully sync.
+3. Uncomment the line relevant to your network (`.env.sepolia`, or `.env.mainnet`) under the 2 `env_file` keys in `docker-compose.yml`.
+4. Run `docker compose up`. Confirm you get a response from:
+
+```bash [Terminal]
+curl -d '{"id":0,"jsonrpc":"2.0","method":"eth_getBlockByNumber","params":["latest",false]}' \
+ -H "Content-Type: application/json" http://localhost:8545
+```
+
+
+Syncing your node may take **days** and will consume a vast amount of your requests quota. Be sure to monitor usage and up your plan if needed.
+
+
+
+### Snapshots
+
+
+Geth Archive Nodes are no longer supported. For Archive functionality, use Reth, which provides significantly better performance in Base’s high-throughput environment.
+
+
+
+If you're a prospective or current Base Node operator and would like to restore from a snapshot to save time on the initial sync, it's possible to always get the latest available snapshot of the Base chain on mainnet and/or testnet by using the following CLI commands. The snapshots are updated every week.
+
+#### Restoring from snapshot
+
+In the home directory of your Base Node, create a folder named `geth-data` or `reth-data`. If you already have this folder, remove it to clear the existing state and then recreate it. Next, run the following code and wait for the operation to complete.
+
+| Network | Client | Snapshot Type | Command |
+| ------- | ------ | ------------- | --------------------------------------------------------------------------------------------------------------------- |
+| Testnet | Geth | Full | `wget https://sepolia-full-snapshots.base.org/$(curl https://sepolia-full-snapshots.base.org/latest)` |
+| Testnet | Reth | Archive | `wget https://sepolia-reth-archive-snapshots.base.org/$(curl https://sepolia-reth-archive-snapshots.base.org/latest)` |
+| Mainnet | Geth | Full | `wget https://mainnet-full-snapshots.base.org/$(curl https://mainnet-full-snapshots.base.org/latest)` |
+| Mainnet | Reth | Archive | `wget https://mainnet-reth-archive-snapshots.base.org/$(curl https://mainnet-reth-archive-snapshots.base.org/latest)` |
+
+You'll then need to untar the downloaded snapshot and place the `geth` subfolder inside of it in the `geth-data` folder you created (unless you changed the location of your data directory).
+
+Return to the root of your Base node folder and start your node.
+
+```bash [Terminal]
+cd ..
+docker compose up --build
+```
+
+Your node should begin syncing from the last block in the snapshot.
+
+Check the latest block to make sure you're syncing from the snapshot and that it restored correctly. If so, you can remove the snapshot archive that you downloaded.
+
+### Syncing
+
+You can monitor the progress of your sync with:
+
+```bash [Terminal]
+echo Latest synced block behind by: $((($(date +%s)-$( \
+ curl -d '{"id":0,"jsonrpc":"2.0","method":"optimism_syncStatus"}' \
+ -H "Content-Type: application/json" http://localhost:7545 | \
+ jq -r .result.unsafe_l2.timestamp))/60)) minutes
+```
+
+You'll also know that the sync hasn't completed if you get `Error: nonce has already been used` if you try to deploy using your node.
diff --git a/docs/base-chain/node-operators/snapshots.mdx b/docs/base-chain/node-operators/snapshots.mdx
new file mode 100644
index 00000000..c172c10a
--- /dev/null
+++ b/docs/base-chain/node-operators/snapshots.mdx
@@ -0,0 +1,78 @@
+---
+title: Node Snapshots
+sidebarTitle: Snapshots
+---
+
+Using a snapshot significantly reduces the initial time required to sync a Base node. Snapshots are updated regularly.
+
+
+Geth Archive Nodes are no longer supported via snapshots due to performance limitations. For Archive functionality, please use Reth.
+
+
+If you're a prospective or current Base node operator, you can restore from a snapshot to speed up your initial sync. Follow the steps below carefully.
+
+## Restoring from Snapshot
+
+These steps assume you are in the cloned `node` directory (the one containing `docker-compose.yml`).
+
+1. **Prepare Data Directory**:
+ - **Before running Docker for the first time**, create the data directory on your host machine that will be mapped into the Docker container. This directory must match the `volumes` mapping in the `docker-compose.yml` file for the client you intend to use.
+ - For Geth:
+ ```bash
+ mkdir ./geth-data
+ ```
+ - For Reth:
+ ```bash
+ mkdir ./reth-data
+ ```
+ - If you have previously run the node and have an existing data directory, **stop the node** (`docker compose down`), remove the _contents_ of the existing directory (e.g. `rm -rf ./geth-data/*`), and proceed.
+
+2. **Download Snapshot**: Choose the appropriate snapshot for your network and client from the table below. Use `wget` (or similar) to download it into the `node` directory.
+
+ | Network | Client | Snapshot Type | Download Command (`wget …`) |
+ | -------- | ------ | ------------- | ----------------------------------------------------------------------------------------------------------------- |
+ | Testnet | Geth | Full | `wget https://sepolia-full-snapshots.base.org/$(curl https://sepolia-full-snapshots.base.org/latest)` |
+ | Testnet | Reth | Archive | `wget https://sepolia-reth-archive-snapshots.base.org/$(curl https://sepolia-reth-archive-snapshots.base.org/latest)` |
+ | Mainnet | Geth | Full | `wget https://mainnet-full-snapshots.base.org/$(curl https://mainnet-full-snapshots.base.org/latest)` |
+ | Mainnet | Reth | Archive | `wget https://mainnet-reth-archive-snapshots.base.org/$(curl https://mainnet-reth-archive-snapshots.base.org/latest)` |
+
+
+ Ensure you have enough free disk space to download the snapshot archive (`.tar.gz` file) _and_ extract its contents. The extracted data will be significantly larger than the archive.
+
+
+3. **Extract Snapshot**: Untar the downloaded snapshot archive. Replace `` with the actual downloaded filename:
+
+ ```bash
+ tar -xzvf
+ ```
+
+4. **Move Data**: The extraction process will likely create a directory (e.g., `geth` or `reth`).
+
+ * Move the *contents* of that directory into the data directory you created in Step 1.
+
+ * Example (if archive extracted to a geth folder):
+
+ ```bash
+ # For Geth
+ mv ./geth/* ./geth-data/
+ rm -rf ./geth # Clean up empty extracted folder
+ ```
+
+ * Example (if archive extracted to a reth folder - **verify actual folder name**):
+
+ ```bash
+ # For Reth
+ mv ./reth/* ./reth-data/
+ rm -rf ./reth # Clean up empty extracted folder
+ ```
+
+ * The goal is to have the chain data directories (e.g., `chaindata`, `nodes`, `segments`, etc.) directly inside `./geth-data` or `./reth-data`, not nested within another subfolder.
+
+5. **Start the Node**: Now that the snapshot data is in place, start the node using the appropriate command (see the [Running a Base Node](/base-chain/quickstart/run-base-node#setting-up-and-running-the-node) guide):
+
+ ```bash
+ # Example for Mainnet Geth
+ docker compose up --build -d
+ ```
+
+6. **Verify and Clean Up**: Monitor the node logs (`docker compose logs -f `) or use the [sync monitoring](/base-chain/quickstart/run-base-node#monitoring-sync-progress) command to ensure the node starts syncing from the snapshot's block height. Once confirmed, you can safely delete the downloaded snapshot archive (`.tar.gz` file) to free up disk space.
diff --git a/docs/base-chain/node-operators/troubleshooting.mdx b/docs/base-chain/node-operators/troubleshooting.mdx
new file mode 100644
index 00000000..dfd59c32
--- /dev/null
+++ b/docs/base-chain/node-operators/troubleshooting.mdx
@@ -0,0 +1,130 @@
+---
+sidebarTitle: Troubleshooting
+title: Node Troubleshooting
+---
+
+This guide covers common issues encountered when setting up and running a Base node using the official [Base Node Docker setup](https://github.com/base/node) and provides steps to diagnose and resolve them.
+
+## General Troubleshooting Steps
+
+Before diving into specific issues, here are some general steps that often help:
+
+1. **Check Container Logs**: This is usually the most informative step. Use `docker compose logs -f ` to view the real-time logs for a specific container.
+ - L2 Client (Geth): `docker compose logs -f op-geth`
+ - L2 Client (Reth): `docker compose logs -f op-reth`
+ - Rollup Node: `docker compose logs -f op-node`. Look for errors, warnings, or repeated messages.
+
+2. **Check Container Status**: Ensure the relevant Docker containers are running: `docker compose ps`. If a container is restarting frequently or exited, check its logs.
+
+3. **Check Resource Usage**: Monitor your server’s CPU, RAM, disk I/O, and network usage. Performance issues are often linked to insufficient resources. Tools like `htop`, `iostat`, and `iftop` can be helpful.
+
+4. **Verify RPC Endpoints**: Use `curl` to check if the L2 client’s RPC endpoint is responding (see [Running a Base Node > Verify Node is Running](/base-chain/quickstart/run-base-node.mdx#verify-node-is-running)). Also, verify your L1 endpoints are correct and accessible from the node server.
+
+5. **Check L1 Node**: Ensure your configured L1 node (Execution and Consensus) is fully synced, healthy, and accessible. Issues with the L1 node will prevent the L2 node from syncing correctly.
+
+---
+
+## Common Issues and Solutions
+
+### Setup & Configuration Issues
+
+- **Issue**: Docker command fails (`docker compose up ...`)
+ - **Check**: Is Docker and Docker Compose installed and the Docker daemon running?
+ - **Check**: Are you in the correct directory (the cloned `node` directory containing `docker-compose.yml`)?
+ - **Check**: Syntax errors in the command (e.g., misspelled `NETWORK_ENV` or `CLIENT`).
+
+- **Issue**: Container fails to start, logs show errors related to `.env` files or environment variables.
+ - **Check**: Did you correctly configure the L1 endpoints (`OP_NODE_L1_ETH_RPC`, `OP_NODE_L1_BEACON`) in the correct `.env` file (`.env.mainnet` or `.env.sepolia`)?
+ - **Check**: Is the `OP_NODE_L1_BEACON_ARCHIVER` endpoint set if required by your configuration or L1 node?
+ - **Check**: Is `OP_NODE_L1_RPC_KIND` set correctly for your L1 provider?
+ - **Check**: (Reth) Are `RETH_CHAIN` and `RETH_SEQUENCER_HTTP` correctly set in the `.env` file?
+
+- **Issue**: Errors related to JWT secret or authentication between `op-node` and L2 client.
+ - **Check**: Ensure you haven't manually modified the `OP_NODE_L2_ENGINE_AUTH` variable or the JWT file path (`$OP_NODE_L2_ENGINE_AUTH`) unless you know what you're doing. The `docker-compose` setup usually handles this automatically.
+
+- **Issue**: Permission errors related to data volumes (`./geth-data`, `./reth-data`).
+ - **Check**: Ensure the user running `docker compose` has write permissions to the directory where the `node` repository was cloned. Docker needs to be able to write to `./geth-data` or `./reth-data`. Sometimes running Docker commands with `sudo` can cause permission issues later; try running as a non-root user added to the `docker` group.
+
+### Syncing Problems
+
+- **Issue**: Node doesn't start syncing or appears stuck (block height not increasing).
+ - **Check**: `op-node` logs. Look for errors connecting to L1 endpoints or the L2 client.
+ - **Check**: L2 client (`op-geth`/`op-reth`) logs. Look for errors connecting to `op-node` via the Engine API (port `8551`) or P2P issues.
+ - **Check**: L1 node health and sync status. Is the L1 node accessible and fully synced?
+ - **Check**: System time. Ensure the server’s clock is accurately synchronized (use `ntp` or `chrony`). Significant time drift can cause P2P issues.
+
+- **Issue**: Syncing is extremely slow.
+ - **Check**: Hardware specifications. Are you meeting the recommended specs (especially RAM and **NVMe SSD**) outlined in the [Node Performance](/base-chain/node-operators/performance-tuning) guide? Disk I/O is often the bottleneck.
+ - **Check**: L1 node performance. Is your L1 RPC endpoint responsive? A slow L1 node will slow down L2 sync.
+ - **Check**: Network connection quality and bandwidth.
+ - **Check**: `op-node` and L2 client logs for any performance warnings or errors.
+
+- **Issue**: `optimism_syncStatus` (port `7545` on `op-node`) shows a large time difference or errors.
+ - **Action**: Check the logs for both `op-node` and the L2 client (`op-geth`/`op-reth`) around the time the status was checked to identify the root cause (e.g., L1 connection issues, L2 client issues).
+
+- **Issue**: `Error: nonce has already been used` when trying to send transactions.
+ - **Cause**: The node is not yet fully synced to the head of the chain.
+ - **Action**: Wait for the node to fully sync. Monitor progress using `optimism_syncStatus` or logs.
+
+### Performance Issues
+
+- **Issue**: High CPU, RAM, or Disk I/O usage.
+ - **Check**: Hardware specifications against recommendations in the [Node Performance](/base-chain/node-operators/performance-tuning). Upgrade if necessary. Local NVMe SSDs are critical.
+ - **Check**: (Geth) Review Geth cache settings and LevelDB tuning options mentioned in [Node Performance – Geth Performance Tuning](/base-chain/node-operators/performance-tuning#geth-performance-tuning) and [Advanced Configuration](/base-chain/quickstart/run-base-node#geth-configuration-via-environment-variables).
+ - **Check**: Review client logs for specific errors or bottlenecks.
+ - **Action**: Consider using Reth if running Geth, as it’s generally more performant for Base.
+
+### Snapshot Restoration Problems
+
+Refer to the [Snapshots](/base-chain/node-operators/snapshots) guide for the correct procedure.
+
+- **Issue**: `wget` command fails or snapshot download is corrupted.
+ - **Check**: Network connectivity.
+ - **Check**: Available disk space.
+ - **Action**: Retry the download. Verify the download URL is correct.
+
+- **Issue**: `tar` extraction fails.
+ - **Check**: Downloaded file integrity (is it corrupted?).
+ - **Check**: Available disk space (extraction requires much more space than the download).
+ - **Check**: `tar` command syntax.
+
+- **Issue**: Node fails to start after restoring snapshot; logs show database errors or missing files.
+ - **Check**: Did you stop the node (`docker compose down`) _before_ modifying the data directory?
+ - **Check**: Did you remove the _contents_ of the old data directory (`./geth-data/*` or `./reth-data/*`) before extracting/moving the snapshot data?
+ - **Check**: Was the snapshot data moved correctly? The chain data needs to be directly inside `./geth-data` or `./reth-data`, not in a nested subfolder (e.g., `./geth-data/geth/...`). Verify the folder structure.
+
+- **Issue**: Ran out of disk space during download or extraction.
+ - **Action**: Free up disk space or provision a larger volume. Remember the storage formula:
+ ```
+ (2 * chain_size + snapshot_size + 20% buffer)
+ ```
+
+### Networking / Connectivity Issues
+
+- **Issue**: RPC/WS connection refused (e.g., `curl` to `localhost:8545` fails).
+ - **Check**: Is the L2 client container (`op-geth`/`op-reth`) running (`docker compose ps`)?
+ - **Check**: Are you using the correct port (`8545` for HTTP, `8546` for WS by default)?
+ - **Check**: L2 client logs. Did it fail to start the RPC server?
+ - **Check**: Are the `--http.addr` and `--ws.addr` flags set to `0.0.0.0` in the client config/entrypoint to allow external connections (within the Docker network)?
+
+- **Issue**: Node has low peer count.
+ - **Check**: P2P port (default `30303`) accessibility. Is it blocked by a firewall on the host or network?
+ - **Check**: Node logs for P2P errors.
+ - **Action**: If behind NAT, configure the `--nat=extip:` flag via `ADDITIONAL_ARGS` in the `.env` file (see [Advanced Configuration](/base-chain/quickstart/run-base-node#improving-peer-connectivity)).
+
+- **Issue**: Port conflicts reported in logs or `docker compose up` fails.
+ - **Check**: Are other services running on the host using the default ports (`8545`, `8546`, `8551`, `6060`, `7545`, `30303`)? Use
+ ```bash
+ sudo lsof -i -P -n | grep LISTEN
+ sudo netstat -tulpn | grep LISTEN
+ ```
+ - **Action**: Stop the conflicting service or change the ports used by the Base node containers by modifying the `ports` section in `docker-compose.yml` and updating the relevant environment variables (`$RPC_PORT`, `$WS_PORT`, etc.) in the `.env` file if necessary.
+
+---
+
+## Getting Further Help
+
+If you’ve followed this guide and are still encountering issues, seek help from the community:
+
+- **Discord**: Join the [Base Discord](https://discord.gg/buildonbase) and post in the `🛠|node-operators` channel, providing details about your setup, the issue, and relevant logs.
+- **GitHub**: Check the [Base Node repository issues](https://github.com/base-org/node/issues) or open a new one if you suspect a bug.
diff --git a/docs/base-chain/quickstart/bridge-token.mdx b/docs/base-chain/quickstart/bridge-token.mdx
new file mode 100644
index 00000000..60b5d27c
--- /dev/null
+++ b/docs/base-chain/quickstart/bridge-token.mdx
@@ -0,0 +1,27 @@
+---
+title: "Bridging an L1 token to Base"
+sidebarTitle: 'Bridge Tokens to Base'
+description: How to submit ERC-20 tokens for bridging between Ethereum and Base as a token issuer.
+---
+
+This page is intended for token issuers who already have an ERC-20 contract deployed on Ethereum and would like to submit their token for bridging between Ethereum and Base. Base uses the [Superchain token list](https://github.com/ethereum-optimism/ethereum-optimism.github.io) as a reference for tokens that have been deployed on Base.
+
+**_Disclaimer: Base does not endorse any of the tokens that are listed in the Github repository and has conducted only preliminary checks, which include automated checks listed_** [**_here_**](https://github.com/ethereum-optimism/ethereum-optimism.github.io)**_._**
+
+## Adding your token to the list
+
+The steps below explain how to get your token on the Base Token List.
+
+### Step 1: Deploy your token on Base
+
+Select your preferred bridging framework and use it to deploy an ERC-20 for your token on Base. We recommend you use the framework provided by Base's [standard bridge](https://github.com/ethereum-optimism/specs/blob/main/specs/protocol/bridges.md) contracts, and furthermore deploy your token using the [OptimismMintableERC20Factory](https://docs.optimism.io/app-developers/tutorials/bridging/standard-bridge-standard-token). You can find the list of contracts addresses [here](/base-chain/network-information/base-contracts).
+
+Deploying your token on Base in this manner provides us with guarantees that will smooth the approval process. If you choose a different bridging framework, its interface must be compatible with that of the standard bridge, otherwise it may be difficult for us to support.
+
+### Step 2: Submit details for your token
+
+Follow the instructions in the [Github repository](https://github.com/ethereum-optimism/ethereum-optimism.github.io) and submit a PR containing the required details for your token. You must specify in your token's data.json file a section for ‘base-sepolia' and/or ‘base’. The change you need to submit is particularly simple if your token has already been added to the Superchain token list. For example, [this PR](https://github.com/ethereum-optimism/ethereum-optimism.github.io/commit/27ab9b2d3388f7feba3a152e0a0748c73d732a68) shows the change required for cbETH, which was already on Optimism's token list and relies on the Base standard bridge.
+
+### Step 3: Await final approval
+
+Reviews are regularly conducted by the Base team and you should receive a reply within 24-72 hours (depending on if the PR is opened on a weekday, weekend or holiday).
diff --git a/docs/base-chain/quickstart/connecting-to-base.mdx b/docs/base-chain/quickstart/connecting-to-base.mdx
new file mode 100644
index 00000000..304e186a
--- /dev/null
+++ b/docs/base-chain/quickstart/connecting-to-base.mdx
@@ -0,0 +1,35 @@
+---
+title: 'Connecting to Base'
+description: Documentation about Base Mainnet and Base Testnet. This page covers network information for the Base network, including network names, descriptions, RPC endpoints, chain IDs, currency symbols, and block explorers.
+---
+
+## Base Mainnet
+
+| Name | Value |
+| :-------------- | :------------------------------------------------------------------------------------------------------ |
+| Network Name | Base Mainnet |
+| Description | The public mainnet for Base. |
+| RPC Endpoint | [https://mainnet.base.org](https://mainnet.base.org) _Rate limited and not for production systems._ |
+| Chain ID | 8453 |
+| Currency Symbol | ETH |
+| Block Explorer | [https://base.blockscout.com/](https://base.blockscout.com/) |
+
+## Base Testnet (Sepolia)
+
+| Name | Value |
+| :-------------- | :------------------------------------------------------------------------------------------------------ |
+| Network Name | Base Sepolia |
+| Description | A public testnet for Base. |
+| RPC Endpoint | [https://sepolia.base.org](https://sepolia.base.org) _Rate limited and not for production systems._ |
+| Chain ID | 84532 |
+| Currency Symbol | ETH |
+| Block Explorer | [https://sepolia-explorer.base.org](https://sepolia-explorer.base.org) |
+
+
+L1 & L2 protocol and network-related smart contract deployments can be found on the [Base Contracts](/base-chain/network-information/base-contracts) page.
+
+
+
+
+For production systems, we recommend using a node from one of our [node partners](/base-chain/tools/node-providers), or [running your own Base node](/base-chain/quickstart/run-base-node).
+
diff --git a/docs/base-chain/quickstart/deploy-on-base.mdx b/docs/base-chain/quickstart/deploy-on-base.mdx
new file mode 100644
index 00000000..7c525e78
--- /dev/null
+++ b/docs/base-chain/quickstart/deploy-on-base.mdx
@@ -0,0 +1,143 @@
+---
+title: 'Deploy on Base'
+---
+
+Welcome to the Base deployment quickstart guide! This comprehensive walkthrough will help you set up your environment and deploy smart contracts on Base. Whether you're a seasoned developer or just starting out, this guide has got you covered.
+
+## What You'll Achieve
+
+By the end of this quickstart, you'll be able to:
+
+- Set up your development environment to deploy on Base
+- Deploy your smart contracts to Base
+- Connect your frontend to your smart contracts
+
+
+**Why Base?**
+
+Base is a fast, low-cost, builder-friendly Ethereum L2 built to bring the next billion users onchain. By following this guide, you'll join a vibrant ecosystem of developers, creators, and innovators who are building a global onchain economy.
+
+
+
+## Set Up Your Development Environment
+
+1. Create a new project directory
+
+```bash
+mkdir my-base-project && cd my-base-project
+```
+
+2. Install Foundry, a powerful framework for smart contract development
+
+```bash
+curl -L https://foundry.paradigm.xyz | bash
+foundryup
+```
+
+This installs Foundry and updates it to the latest version.
+
+3. Initialize a new Solidity project
+
+```bash
+forge init
+```
+
+Your Foundry project is now ready. You'll find an example contract in the `src` directory, which you can replace with your own contracts. For the purposes of this guide, we'll use the Counter contract provided in `/src/Counter.sol`
+
+
+Foundry provides a suite of tools for Ethereum application development, including Forge (for testing), Cast (for interacting with the chain), and Anvil (for setting up a local node). You can learn more about Foundry [here](https://book.getfoundry.sh/).
+
+
+
+## Configure Foundry with Base
+
+To deploy your smart contracts to Base, you need two key components:
+
+1. A node connection to interact with the Base network
+2. A funded private key to deploy the contract
+
+Let's set up both of these:
+
+### 1. Set up your node connection
+
+1. Create a `.env` file in your project's root directory
+2. Add the Base network RPC URL to your `.env` file
+
+```bash
+BASE_RPC_URL="https://mainnet.base.org"
+BASE_SEPOLIA_RPC_URL="https://sepolia.base.org"
+```
+
+3. Load your environment variables
+
+```bash
+source .env
+```
+
+
+Base Sepolia is the test network for Base, which we will use for the rest of this guide. You can obtain free Base Sepolia ETH from one of the [faucets listed here](/base-chain/tools/network-faucets).
+
+
+
+### 2. Secure your private key
+
+1. Store your private key in Foundry's secure keystore
+
+```bash
+cast wallet import deployer --interactive
+```
+
+2. When prompted enter your private key and a password.
+
+Your private key is stored in `~/.foundry/keystores` which is not tracked by git.
+
+
+Never share or commit your private key. Always keep it secure and handle with care.
+
+
+
+## Deploy Your Contracts
+
+Now that your environment is set up, let's deploy your contracts to Base Sepolia.
+
+1. Use the following command to compile and deploy your contract
+
+```bash
+forge create ./src/Counter.sol:Counter --rpc-url $BASE_SEPOLIA_RPC_URL --account deployer
+```
+
+Note the format of the contract being deployed is `:`.
+
+2. After successful deployment, the transaction hash will be printed to the console output
+
+3. Copy the deployed contract address and add it to your `.env` file
+
+```bash
+COUNTER_CONTRACT_ADDRESS="0x..."
+```
+
+4. Load the new environment variable
+
+```bash
+source .env
+```
+
+### Verify Your Deployment
+
+To ensure your contract was deployed successfully:
+
+1. Check the transaction on [Sepolia Basescan](https://sepolia.basescan.org/).
+2. Use the `cast` command to interact with your deployed contract from the command line
+
+```bash
+cast call $COUNTER_CONTRACT_ADDRESS "number()(uint256)" --rpc-url $BASE_SEPOLIA_RPC_URL
+```
+This will return the initial value of the Counter contract's `number` storage variable, which will be `0`.
+
+**Congratulations! You've deployed your smart contracts to Base Sepolia!**
+
+## Next Steps
+
+- Use [Onchainkit](https://onchainkit.com) to connect your frontend to your contracts! Onchainkit is a library of ready-to-use React components and Typescript utilities.
+- Learn more about interacting with your contracts in the command line using Foundry from our [Foundry tutorial](/cookbook/smart-contract-development/foundry/deploy-with-foundry).
+
diff --git a/docs/base-chain/quickstart/why-base.mdx b/docs/base-chain/quickstart/why-base.mdx
new file mode 100644
index 00000000..47867775
--- /dev/null
+++ b/docs/base-chain/quickstart/why-base.mdx
@@ -0,0 +1,79 @@
+---
+title: 'Why Base?'
+---
+
+## TL;DR
+
+- **Cheap, fast, and open platform:** Base is a globally available platform that provides 1-second and <1-cent transactions to anyone in the world.
+- **Access to high-quality tooling:** Builders have access to tools to build incredible onchain experiences for AI, social, media, and entertainment.
+- **A place to earn:** Base has delivered grants to more than 1,000 builders, with plans to continue supporting more.
+- **Reach more users:** Base is committed to helping developers grow their user base by distributing their apps through official Base channels.
+
+
+## A platform for building innovative apps
+
+Base is a global onchain economy of people, builders, and businesses creating the next generation of the open internet. It enables builders to:
+
+- Focus on solving real user problems
+- Scale apps globally by default
+- Onboard users with a click
+- Accept payments from anyone, easily
+
+It’s fast, cheap, and permissionless, meaning anyone can build and use applications on Base.
+
+Base has become the hub for innovative use cases in media, entertainment, social, and even artificial intelligence. [Clanker], an autonomous AI agent on Base, generated more than $13 million in revenue within three months of its inception. As a cultivator of innovation, Base enables developers to focus on improving their products while retaining more of the upside they generate.
+
+Traditional app distribution models often require developers to sacrifice 30% of their revenue to app marketplaces. These platforms may incentivize predatory practices—selling user data or requiring personal information—and can restrict or deny access at their discretion. For instance, someone in Nairobi might be unable to use many U.S.-based fintech services.
+
+In contrast, decentralized finance (DeFi) apps—an onchain development sector—like [Moonwell] allow people anywhere in the world to access financial services directly from their phones. This means someone in Nairobi has the same level of access as someone in the U.S., opening opportunities that were previously inaccessible.
+
+
+
+## Expansive developer tools
+
+Developers choose Base for its:
+
+- Always-on global payment rail operating at internet speed
+- Low transaction costs (often less than a cent)
+- Robust developer tooling
+
+But there’s more: Base apps can be launched in hours, not days or weeks, thanks to an extensive suite of tools—many of which are open source. We’re reviving the spirit of innovation where two builders in a garage can create a massively successful business.
+
+Some of the tooling that makes this possible:
+
+- **Smart Wallets:** Onboard your users quickly and securely. Users never have to worry about seed phrases again.
+- **Coinbase Developer Platform:** Access specialized developer services for onchain development, such as free node software, sponsored transactions, and other tools to help you fine-tune your application.
+- **OnchainKit:** A React component library (TypeScript) to help you build apps faster.
+- **Basenames:** More than just human-readable text to replace an address—Basenames are the front page to a builder’s onchain profile.
+- **Verifications:** Enable unique experiences for users with verified credentials.
+
+
+
+## Builder support
+
+More apps mean more compelling reasons for everyone to get onchain. Base is committed to expanding its developer and user community by creating opportunities for apps to go viral. To support this, Base actively distributes apps through its official channels, including Wallet, X, and Warpcast.
+
+Beyond growth, Base is also a place to earn. Since its launch, we have dedicated a portion of our revenue to the Optimism Collective, supporting initiatives like [Retroactive Public Goods Funding (RetroPGF)] and other ecosystem projects.
+
+We have also distributed builder grants to more than 1,000 builders. Here are a few ways you can earn on Base:
+
+- **[Builder Grants]:** Small, retroactive grants up to 2 ETH
+- **[/base-builds]:** Weekly grants by rounds to Warpcast builders
+- **[Optimism RPGF]:** Projects built on Base are eligible for Optimism RPGF
+- Other community-driven collaborations through based.management, [Bountycaster], and [Talent Protocol]
+
+Base offers builders access to a high-growth, high-signal community across multiple social platforms, including a Discord server with over 400,000 members, X, and Warpcast. We actively amplify impactful projects on these platforms, helping the community discover new innovations. We believe the developers behind these applications are the key to unlocking an onchain future for everyone.
+
+
+
+[Basenames]: https://www.base.org/names
+[planned]: https://ethereum.org/en/roadmap/
+[Solidity]: https://soliditylang.org/
+[Bountycaster]: https://www.bountycaster.xyz/
+[Builder Grants]: https://docs.google.com/forms/d/e/1FAIpQLSeiSAod4PAbXlvvDGtHWu-GqzGpvHYfaTQR2f77AawD7GYc4Q/viewform
+[/base-builds]: https://warpcast.com/base/0xb3f1428b?utm_source=dotorg&urm_medium=builderkit
+[Optimism RPGF]: https://round3.optimism.io/projects?after=undefined&display=grid&sort=mostAwarded&search=&seed=1738341430276&categories=
+[Talent Protocol]: https://www.talentprotocol.com/
+[Moonwell]: https://moonwell.fi/discover
+[Clanker]: https://www.clanker.world/
+[Retroactive Public Goods Funding (RetroPGF)]: https://round3.optimism.io/projects?after=undefined&display=grid&sort=mostAwarded&search=&seed=1738341430276&categories=
diff --git a/docs/base-chain/security/avoid-malicious-flags.mdx b/docs/base-chain/security/avoid-malicious-flags.mdx
new file mode 100644
index 00000000..fd00ccc2
--- /dev/null
+++ b/docs/base-chain/security/avoid-malicious-flags.mdx
@@ -0,0 +1,33 @@
+---
+sidebarTitle: 'Avoid Malicious Flags'
+title: How to avoid getting your app flagged as malicious
+description: The Base bug bounty program and procedures for reporting vulnerabilities.
+---
+
+Ensuring that your app is perceived as trustworthy and not flagged as malicious requires attention to best practices. Here’s a quick guide on how to build a secure and compliant app from day one.
+
+## 1. Verify and reduce the risk of your smart contract
+
+- **Verify Smart Contract**: Ensure that the source code of your contracts is verified and publicly available on [block explorers](/base-chain/tools/block-explorers). For example, this can be done on [Etherscan](https://basescan.org/verifyContract) and [Basescan](https://basescan.org/verifyContract) under “Verify Contract”.
+- **Limit Exposure of User Funds**: Design your contracts to minimize the exposure of user funds. Use efficient design to reduce any unnecessary risk. For example, request the minimum amount needed to fulfill the transaction.
+
+## 2. Submit a verification request
+
+After verifying your smart contract, consider [submitting a verification request](https://report.blockaid.io/). This step helps ensure that your app is recognized as safe and verified by trusted sources in the ecosystem.
+
+## 3. Follow app best practices
+
+- **Accessibility Across Regions**: Avoid geo-blocking or access restrictions that prevent certain regions or countries from accessing your app. Depending on legal or compliance reasons, this may be necessary which you can indicate in your verification request submission.
+- **Consistent Behavior**: Avoid rapid or unexplained changes in UI that can make users feel uncertain about the app’s reliability.
+- **Transparent Onchain Interactions**: Make sure your app’s onchain interactions are clear and match the UI actions. For example, a “Mint” button should clearly emit a mint transaction.
+- **Standard Sign-in Methods**: Provide all standard connection methods for users to sign in, such as WalletConnect / Coinbase Wallet SDK or popular browser extension wallets.
+- **Audit Your Contracts**: Have your contracts audited by a reputable firm. Publish the audit report and provide a reference link so users can easily find it. Audits show that you’ve taken extra steps to secure your smart contracts.
+
+By following these recommendations, you’ll significantly reduce the chances of your app being flagged as malicious and foster a secure and trustworthy environment for your users.
+
+---
+
+**Still having trouble?**
+Coinbase Wallet may report false positives when flagging apps. To avoid false positives, please make sure you have completed the recommended actions above. If your app is still flagged as suspicious or malicious, [report it to Blockaid](https://report.blockaid.io/mistake).
+
+---
diff --git a/docs/base-chain/security/bug-bounty.mdx b/docs/base-chain/security/bug-bounty.mdx
new file mode 100644
index 00000000..7cb267c7
--- /dev/null
+++ b/docs/base-chain/security/bug-bounty.mdx
@@ -0,0 +1,3 @@
+---
+title: 'Bug Bounty'
+---
diff --git a/docs/base-chain/security/report-vulnerability.mdx b/docs/base-chain/security/report-vulnerability.mdx
new file mode 100644
index 00000000..26f81a0c
--- /dev/null
+++ b/docs/base-chain/security/report-vulnerability.mdx
@@ -0,0 +1,19 @@
+---
+sidebarTitle: 'Report a Vulnerability'
+title: Reporting Vulnerabilities
+description: The Base procedures for reporting vulnerabilities.
+---
+
+All potential vulnerability reports can be submitted via the [HackerOne](https://hackerone.com/coinbase) platform.
+
+The HackerOne platform allows us to have a centralized and single reporting source for us to deliver optimized SLAs and results. All reports submitted to the platform are triaged around the clock by our team of Coinbase engineers with domain knowledge, assuring the best quality of review.
+
+## Bug bounty program
+
+In line with our strategy of being the safest way for users to access crypto:
+
+- Coinbase will be extending our [best-in-industry](https://www.coinbase.com/blog/celebrating-10-years-of-our-bug-bounty-program) million-dollar [HackerOne bug bounty program](https://hackerone.com/coinbase?type=team) to cover the Base network, the Base bridge contracts, and Base infrastructure.
+- Coinbase will be working in tandem with OP Labs to harden the security guarantees of Bedrock and accelerate the timeline for decentralized fault-proofs on the [OP Stack](https://stack.optimism.io/).
+- Coinbase's bug bounty program will run alongside Optimism's existing [Immunefi Bedrock bounty program](https://immunefi.com/bounty/optimism/) to support the open source [Bedrock](https://stack.optimism.io/docs/releases/bedrock/) OP Stack framework.
+
+For more information on reporting vulnerabilities and our HackerOne bug bounty program, view our [security program policies](https://hackerone.com/coinbase?view_policy=true).
diff --git a/docs/base-chain/security/security-council.mdx b/docs/base-chain/security/security-council.mdx
new file mode 100644
index 00000000..f1fee274
--- /dev/null
+++ b/docs/base-chain/security/security-council.mdx
@@ -0,0 +1,141 @@
+---
+title: Security Council for Base
+description: This page outlines the purpose, goals, structure, and responsibilities of the Security Council for Base.
+---
+
+This page outlines the purpose, goals, structure, and responsibilities of the
+Security Council for Base. The Council is composed of individuals and
+organizations who hold signing keys and help manage upgrades to keep the Base
+network secure.
+
+## Purpose
+
+Base’s mission is to build a global onchain economy that increases innovation,
+creativity, and freedom. We believe this is only possible on a decentralized
+platform. This is why Base is (1) built on Ethereum, the most secure and
+decentralized L1, (2) built on the open and permissionless OP Stack, and (3)
+upholds key [Neutrality Principles](https://www.coinbase.com/blog/coinbases-neutrality-principles-for-base).
+
+As part of our ongoing commitment to decentralization, Base launched
+[permissionless fault proofs](https://base.mirror.xyz/eOsedW4tm8MU5OhdGK107A9wsn-aU7MAb8f3edgX5Tk) in
+October 2024, [decentralized control of contract upgrades](https://base.mirror.xyz/tWDMlGp48fF0MeADcLQruUBq1Qxkou4O5x3ax8Rm3jA) via a Security Council
+in April 2025, and has now reached Stage 1 Decentralization.
+
+In reaching **Stage 1**, Base provides stronger security guarantees and fewer
+trust assumptions. Builders benefit from infrastructure certainty (no unexpected
+rule changes), and there’s no single point of failure—everyone can participate
+in verifying and securing the network.
+
+## Structure
+
+**Composition and quorum**
+
+Stage 1 Decentralization requirements say that the group which approves Base
+Chain contract upgrades must contain:
+
+- at least 8 participants
+- a ≥75% quorum
+- a quorum-blocking group outside the main rollup operator (Base)
+
+In addition to the current signing entities (Optimism and Coinbase), the
+Security Council adds 10 independent entities and individuals from
+geographically diverse regions. To satisfy the ≥75% quorum, 9 out of the 12
+entities (the 10 entities in the Security Council, Optimism, and Coinbase) are
+required to approve Base upgrades. Base upgrades cannot take effect until a
+quorum of these entities sign and approve the upgrade. This composition
+satisfies all requirements.
+
+**Member Selection criteria**
+
+- Representation across diverse geographic regions and international territories
+- Strong alignment with [Base’s mission and values](https://base.mirror.xyz/jjQnUq_UNTQOk7psnGBFOsShi7FlrRp8xevQUipG_Gk)
+- Diverse organizations - each member represents a separate entity
+- Proven track record in the Base and Ethereum ecosystem - in good standing in upholding professional and ethical standards in the community
+- Technical competency and good security practices - has completed screening processes, including background checks, and have shown ability to securely store and use sensitive key materials
+
+**Current Roster**
+
+This is a living list that will stay up to date with membership. As of April 2025, the Security Council currently contains members from the following entities and individuals, based in the listed geographical jurisdictions.
+
+- [Entity] Aerodrome – signer based in Japan
+ - [Aerodrome](https://aerodrome.finance/) is a decentralized exchange on Base where users can swap, earn rewards and actively participate in the onchain economy.
+ - `0xa5959a39cA67b9fb473E4A3A898C611EEAc9CB73`
+- [Entity] Moonwell – signer based in Brazil
+ - [Moonwell](https://moonwell.fi/) is a decentralized lending and borrowing platform built on Base.
+ - `0x21C7D1e6A81Daca071bA94839ab74C39A25f851F`
+- [Entity] Blackbird – signer based in USA
+ - [Blackbird](https://www.blackbird.xyz/) is a loyalty and payments platform built specifically for the restaurant industry, powered by Base.
+ - `0xA5657B88A0130a626fcDd6aAA59522373438CdFE`
+- [Entity] ChainSafe – signer based in Canada
+ - [ChainSafe](https://chainsafe.io/) is a blockchain R&D firm focused on decentralized infrastructure.
+ - `0x1C56A6d2A6Af643cea4E62e72B75B9bDe8d62e2B`
+- [Entity] Talent Protocol – signer based in Portugal
+ - [Talent Protocol](https://app.talentprotocol.com/) brings professional reputation onchain to help Base builders showcase their skills and get the recognition they deserve.
+ - `0x5ff5C78ff194acc24C22DAaDdE4D639ebF18ACC6`
+- [Entity] Moshicam – signer based in USA
+ - [Moshicam](https://moshi.cam/) is a community-based photo editing app built on Base.
+ - `0xa8ee754FD1d069fb4B5d652730A0ca5e07a3fb06`
+- [Individual] Seneca – based in USA
+ - Seneca is the co-founder of [Rounds](https://rounds.wtf/), a social platform which has [powered](https://x.com/jessepollak/status/1781069700652523725) grant distribution to Base builders.
+ - `0x82C80F34C4b5c153dB76122a11AaD2F77C99E766`
+- [Individual] Juan Suarez – based in USA
+ - Juan is an active member of the Base ecosystem and has advised a number of key Base projects. He is a former member of the Coinbase Legal Team.
+ - `0x99DB5BbA0db16e9aD05e3ff53310683CC3C971D2`
+- [Individual] Toady Hawk – based in Canada
+ - [Toady Hawk](https://warpcast.com/toadyhawk.eth) is the founder of [Zero Rights Media](https://warpcast.com/zerorightsmedia), an open source onchain media org on Base (producers of ZEROPOD), and [The Yellow Collective](https://warpcast.com/basedandyellow), an onchain culture club for artists and creators on Base.
+ - `0x0E8A99738a50D523871739c6d676554b0E34252f`
+- [Individual] Roberto Bayardo – based in USA
+ - [Roberto Bayardo](https://warpcast.com/bayardo.eth) is an engineer at Commonware, building a framework for high-performance blockchains. He is a former core Base contributor.
+ - `0x18e982274f8C5B548D5aAc7aBef44D61504e1b3E`
+
+Individuals representing each entity are not published to protect personal privacy and to enhance security.
+
+**Member Terms**
+
+The Security Council for Base operates on a staggered “cohort” model:
+
+- Cohort 1: 6-month term beginning April 2025
+- Cohort 2: 9-month term beginning April 2025
+
+## Roles & Responsibilities
+
+**Review and approve changes**
+
+- Council members are notified about proposed upgrades, and they must verify, approve, and sign these upgrades.
+- Council members must verify, approve and sign role changes (in case of key rotations for lost devices, member rotations, etc.). Any key rotations will not disrupt quorum or security.
+
+**Maintain availability and lines of communication**
+
+- Be accessible and reachable for scheduled signings, coordination calls, and emergencies.
+- Collaborate with other members to resolve urgent issues.
+
+**Preserve key security**
+
+- Generate and store key materials securely.
+- Keys should only be used for activities which directly relate to the Security Council member role (upgrades and ownership changes).
+- Report suspected loss of access or compromise immediately.
+- Undergo onchain safety and security training at the beginning of each term.
+- At regular intervals, participate in a liveness check by signing a message, to confirm ongoing control of the key.
+
+**Act in good faith**
+
+- Avoid conflicts of interest and disclose potential conflicts.
+- Participate in removing or replacing dysfunctional signers without compromising security.
+
+## The future
+
+The Security Council for Base is a critical step toward a more decentralized,
+resilient, and secure future for the Base network. By distributing key
+responsibilities across trusted, independent participants and implementing fault
+proofs, we’ve reduced reliance on any single entity while strengthening
+guarantees for users, builders, and the broader ecosystem.
+
+This is just the beginning. As Base continues to evolve, the role of the
+Security Council will be progressively minimized, paving the way for even more
+trustless infrastructure beyond Stage 1—Stage 2—and even greater community
+control.
+
+Our mission remains the same: to build a global onchain economy that empowers
+innovation, creativity, and freedom—on a foundation that everyone can rely on.
+
+Base is for everyone.
diff --git a/docs/base-chain/tools/account-abstraction.mdx b/docs/base-chain/tools/account-abstraction.mdx
new file mode 100644
index 00000000..4ef628c5
--- /dev/null
+++ b/docs/base-chain/tools/account-abstraction.mdx
@@ -0,0 +1,53 @@
+---
+title: Account Abstraction
+description: Documentation for Account Abstraction toolkits and solutions for apps built on Base.
+---
+
+## Alchemy Account Kit
+
+[Account Kit](https://www.alchemy.com/account-kit) is a complete solution for account abstraction. Using Account Kit, you can create a smart contract wallet for every user that leverages account abstraction to simplify every step of your app's onboarding experience. It also offers Gas Manager and Bundler APIs for sponsoring gas and batching transactions.
+
+## Biconomy
+
+[Biconomy](https://www.biconomy.io) is an Account Abstraction toolkit that enables you to provide the simplest UX for your dapp or wallet. It offers modular smart accounts, as well as paymasters and bundlers as a service for sponsoring gas and executing transactions at scale.
+
+## Coinbase Account Abstraction Kit
+
+The Coinbase Developer Platform [Account Abstraction Kit](https://www.coinbase.com/developer-platform/solutions/account-abstraction-kit) is an account abstraction toolkit for building simple onchain user experiences. Account Abstraction Kit provides a paymaster and bundler that allows you to sponsor gas fees and bundle user transactions, improving the user experience of your application.
+
+## Openfort
+
+[Openfort](https://openfort.xyz) is an infrastructure provider designed to simplify the development of games and gamified experiences across their suite of API endpoints. The platform vertically integrates the AA stack, so game developers can focus on game development without worrying about private key management, the account model or the onchain interactions with paymasters and bundlers. The Openfort platform is compatible with most EVM chains, including Base.
+
+## Pimlico
+
+[Pimlico](https://pimlico.io/) provides an infrastructure platform that makes building smart accounts simpler. If you are developing, an ERC-4337 smart account, they provide bundlers, verifying paymasters, ERC-20 paymasters, and much more.
+
+## Reown (prev. known as WalletConnect)
+
+**[Reown](https://reown.com/?utm_source=base&utm_medium=docs&utm_campaign=backlinks)** gives developers the tools to build user experiences that make digital ownership effortless, intuitive, and secure. One of Reown's offerings is the AppKit SDK.
+
+**AppKit** is a powerful, free, and fully open-source SDK for developers looking to integrate wallet connections and other Web3 functionalities into their apps on any EVM and non-EVM chain. In just a few simple steps, you can provide your users with seamless wallet access, one-click authentication, social logins, and notifications—streamlining their experience while enabling advanced features like on-ramp functionality, in-app token swaps and smart accounts. Check out the [docs](https://docs.reown.com/appkit/overview?utm_source=base&utm_medium=docs&utm_campaign=backlinks) to get started.
+
+## Safe
+
+[Safe](https://docs.safe.global/getting-started/readme) provides modular smart account infrastructure and account abstraction stack via their Safe{Core} [Account Abstraction SDK](https://docs.safe.global/safe-core-aa-sdk/safe-core-sdk), [API](https://docs.safe.global/safe-core-api/supported-networks), and [Protocol](https://docs.safe.global/safe-core-protocol/safe-core-protocol).
+
+## Stackup
+
+[Stackup](https://www.stackup.sh) provides smart account tooling for building account abstraction within your apps. They offer Paymaster and Bundler APIs for sponsoring gas and sending account abstraction transactions.
+
+## thirdweb
+[thirdweb](https://portal.thirdweb.com/typescript/v5/account-abstraction/get-started) offers the complete toolkit to leverage account abstraction technology to enable seamless user experiences for your users. This includes Account Factory contracts that let your users spin up Smart Accounts, Bundler for UserOps support, and Paymaster to enable gas sponsorships.
+
+## WalletKit
+
+[WalletKit](https://walletkit.com) is an all-in-one platform for adding smart, gasless wallets to your app. It has integrated support for ERC 4337 and comes with a paymaster and bundler included, requiring no extra setup.
+
+WalletKit also offers pre-built components for onboarding users with email and social logins, which can be integrated in under 15 minutes using their React SDK or the wagmi connector. Alternatively, build completely bespoke experiences for your users using WalletKit's Wallets API.
+
+WalletKit is compatible with most EVM chains, including Base. You can check out the [WalletKit documentation here](https://docs.walletkit.com). Start building for free on the Base testnet today.
+
+## ZeroDev
+
+[ZeroDev](https://zerodev.app) is an embedded wallet powered by account abstraction. It offers you the ability to create self-custody wallets for your users, sponsor gas, and simplify user flows by batching and automating transactions.
diff --git a/docs/base-chain/tools/base-products.mdx b/docs/base-chain/tools/base-products.mdx
new file mode 100644
index 00000000..b2420788
--- /dev/null
+++ b/docs/base-chain/tools/base-products.mdx
@@ -0,0 +1,24 @@
+---
+title: 'Base Products'
+---
+
+
+
+ Neque porro quisquam est qui dolorem ipsum quia dolor sit amet
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit
+
+
+ Ut enim ad minim veniam, quis nostrud exercitation ullamco
+
+
+ Excepteur sint occaecat cupidatat non proident
+
+
+ Excepteur sint occaecat cupidatat non proident
+
+
+ Excepteur sint occaecat cupidatat non proident
+
+
\ No newline at end of file
diff --git a/docs/base-chain/tools/block-explorers.mdx b/docs/base-chain/tools/block-explorers.mdx
new file mode 100644
index 00000000..3786139c
--- /dev/null
+++ b/docs/base-chain/tools/block-explorers.mdx
@@ -0,0 +1,59 @@
+---
+title: 'Block Explorers'
+description: Documentation for block explorers for the Base network.
+---
+
+## Arkham
+
+The Arkham [Platform](https://platform.arkhamintelligence.com/) supports Base.
+
+Arkham is a crypto intelligence platform that systematically analyzes blockchain transactions, showing users the people and companies behind blockchain activity, with a suite of advanced tools for analyzing their activity.
+
+## Blockscout
+
+A Blockscout explorer is available for [Base](https://base.blockscout.com/).
+
+Blockscout provides tools to help you debug smart contracts and transactions:
+
+- View, verify, and interact with smart contract source code.
+- View detailed transaction information
+
+A testnet explorer for [Base Sepolia](https://base-sepolia.blockscout.com/) is also available.
+
+## Etherscan
+
+An Etherscan block explorer is available for [Base](https://basescan.org).
+
+Etherscan provides tools to help you view transaction data and debug smart contracts:
+
+- Search by address, transaction hash, batch, or token
+- View, verify, and interact with smart contract source code
+- View detailed transaction information
+- View L1-to-L2 and L2-to-L1 transactions
+
+A testnet explorer for [Base Sepolia](https://sepolia.basescan.org/) is also available.
+
+## DexGuru
+
+[DexGuru](https://base.dex.guru) provides a familiar UI with data on transactions, blocks, account balances and more. Developers can use it to verify smart contracts and debug transactions with interactive traces and logs visualization.
+
+## L2scan Explorer
+
+[L2scan Explorer](https://base.l2scan.co/) is a web-based tool that allows users to analyze Base and other layer 2 networks. It provides a user-friendly interface for viewing transaction history, checking account balances, and tracking the status of network activity.
+
+## OKLink
+
+[OKLink](https://www.oklink.com/base) is a multi-chain blockchain explorer that supports Base and provides the following features for developers:
+
+- Search by address, transaction, block, or token
+- View, verify, and interact with smart contract source code
+- Access a comprehensive and real-time stream of on-chain data, including large transactions and significant fund movements
+- Address labels (i.e. project labels, contract labels, risk labels, black address labels, etc.)
+
+## Routescan
+
+[Routescan](https://routescan.io/) superchain explorer allows you to search for transactions, addresses, tokens, prices and other activities taking place across all Superchain blockchains, including Base.
+
+## Tenderly Explorer
+
+With the [Tenderly](https://tenderly.co/) developer explorer you can get unparalleled visibility into your smart contract code. You can easily view detailed transaction information, spot bugs in your code, and optimize gas spend. Supporting Base mainnet and Base Sepolia testnet, Tenderly Explorer helps you track your smart contracts while providing visibility on a granular level.
diff --git a/docs/base-chain/tools/cross-chain.mdx b/docs/base-chain/tools/cross-chain.mdx
new file mode 100644
index 00000000..371d8a94
--- /dev/null
+++ b/docs/base-chain/tools/cross-chain.mdx
@@ -0,0 +1,77 @@
+---
+title: Cross-chain
+description: Documentation for cross-chain communication and messaging on the Base network. This page covers integrating tools like LayerZero with Base for web3 transactions, cross-chain messaging, and secure blockchain communication.
+---
+
+import {HeaderNoToc} from "/snippets/headerNoToc.mdx";
+
+## Axelar
+
+[Axelar](https://axelar.network/) is an interchain platform that connects blockchains to enable universal web3 transactions. By integrating with Axelar, applications built on Base can now easily send messages and assets between the 49+ blockchains connected via Axelar.
+
+To learn more about Axelar visit our [docs](https://docs.axelar.dev/). For complete end-to-end examples demonstrating various Axelar use cases please visit the available [code examples](https://github.com/axelarnetwork/axelar-examples).
+
+
+
+- [Base Mainnet](https://docs.axelar.dev/resources/mainnet)
+- [Base Testnet](https://docs.axelar.dev/resources/testnet)
+
+#### Axelarscan
+
+To view current transactions and live stats about the Axelar network, please visit the [Axelarscan block explorer](https://axelarscan.io/)
+
+
+
+## Crossmint
+
+[Crossmint](https://crossmint.com/?utm_source=backlinks&utm_medium=docs&utm_campaign=base) allows you to create and deploy NFT Collections and enable cross-chain payments. This enables your users and customers to purchase an NFT from a collection deployed on Base using Ethereum or Solana tokens.
+
+Check out [Crossmint Docs](https://docs.crossmint.com/nft-checkout/introduction/?utm_source=backlinks&utm_medium=docs&utm_campaign=base) to learn more about NFT Checkout with Crossmint. To power cross-chain payments, click [here](https://docs.crossmint.com/nft-checkout/pay-button/select-payment-options/?utm_medium=docs&utm_source=backlinks&utm_campaign=base) to get started.
+
+
+
+- [Base Mainnet](https://www.crossmint.com/products/nft-checkout/?utm_source=backlinks&utm_medium=docs&utm_campaign=base)
+- [Base Sepolia](https://www.crossmint.com/products/nft-checkout/?utm_source=backlinks&utm_medium=docs&utm_campaign=base)
+
+
+
+## Chainlink CCIP
+
+[Chainlink CCIP](https://chain.link/cross-chain) is a secure interoperability protocol that allows for securely sending messages, transferring tokens, and initiating actions across different blockchains.
+
+To get started with integrating Chainlink CCIP in your Base project, visit the Chainlink CCIP [documentation](https://docs.chain.link/ccip).
+
+
+
+- [Base Mainnet](https://docs.chain.link/ccip/supported-networks/v1_0_0/mainnet#base-mainnet)
+- [Base Sepolia](https://docs.chain.link/ccip/supported-networks/v1_2_0/testnet) (Testnet)
+
+
+
+## LayerZero
+
+[LayerZero](https://layerzero.network/) is an omnichain interoperability protocol that enables cross-chain messaging. Applications built on Base can use the LayerZero protocol to connect to 35+ supported blockchains seamlessly.
+
+To get started with integrating LayerZero, visit the LayerZero [documentation](https://docs.layerzero.network/v1/developers/evm/evm-guides/send-messages) and provided examples on [GitHub](https://github.com/LayerZero-Labs/solidity-examples).
+
+
+
+- [Base Mainnet](https://docs.layerzero.network/v2/developers/evm/technical-reference/deployed-contracts#base)
+- [Base Sepolia](https://docs.layerzero.network/v2/developers/evm/technical-reference/deployed-contracts#base-sepolia) (Testnet)
+
+
+
+## Wormhole
+
+[Wormhole](https://wormhole.com/) is a generic messaging protocol that provides secure communication between blockchains.
+
+By integrating Wormhole, a Base application can access users and liquidity on > 30 chains and > 7 different platforms.
+
+See [this quickstart](https://docs.wormhole.com/wormhole/quick-start/tutorials/hello-wormhole) to get started with integrating Wormhole in your Base project.
+
+For more information on integrating Wormhole, visit their [documentation](https://docs.wormhole.com/wormhole/) and the provided [GitHub examples](https://github.com/wormhole-foundation/wormhole-examples).
+
+
+
+- [Base Mainnet](https://docs.wormhole.com/wormhole/blockchain-environments/evm#base)
+- [Base Sepolia](https://docs.wormhole.com/wormhole/blockchain-environments/evm#base) (Testnet)
diff --git a/docs/base-chain/tools/data-indexers.mdx b/docs/base-chain/tools/data-indexers.mdx
new file mode 100644
index 00000000..c1a93ac2
--- /dev/null
+++ b/docs/base-chain/tools/data-indexers.mdx
@@ -0,0 +1,209 @@
+---
+title: Data Indexers
+description: Documentation for data indexing platforms for Base network.
+---
+
+import {HeaderNoToc} from "/snippets/headerNoToc.mdx";
+
+## Allium
+
+[Allium](https://www.allium.so/) is an Enterprise Data Platform that serves accurate, fast, and simple blockchain data. Currently serving 15 blockchains and over 100+ schemas, Allium offers near real-time Base data for infrastructure needs and enriched Base data (NFT, DEX, Decoded, Wallet360) for research and analytics.
+
+Allium supports data delivery to multiple [destinations](https://docs.allium.so/integrations/overview), including Snowflake, Bigquery, Databricks, and AWS S3.
+
+Documentation:
+
+- [Real-time](https://docs.allium.so/real-time-data/base)
+- [Batch-enriched](https://docs.allium.so/data-tables/base)
+
+To get started, contact Allium [here](https://www.allium.so/contact).
+
+## Arkham
+
+[Arkham](https://platform.arkhamintelligence.com/) is a crypto intelligence platform that systematically analyzes blockchain transactions, showing users the people and companies behind blockchain activity, with a suite of advanced tools for analyzing their activity.
+
+References:
+
+- [Platform guide](https://www.arkhamintelligence.com/guide)
+- [Whitepaper](https://www.arkhamintelligence.com/whitepaper)
+- [Codex](https://codex.arkhamintelligence.com/)
+- [Demos](https://www.youtube.com/@arkhamintel)
+
+
+
+## Covalent
+
+[Covalent](https://www.covalenthq.com/?utm_source=base&utm_medium=partner-docs) is a hosted blockchain data solution providing access to historical and current on-chain data for [100+ supported blockchains](https://www.covalenthq.com/docs/networks/?utm_source=base&utm_medium=partner-docs), including [Base](https://www.covalenthq.com/docs/networks/base/?utm_source=base&utm_medium=partner-docs).
+
+Covalent maintains a full archival copy of every supported blockchain, meaning every balance, transaction, log event, and NFT asset data is available from the genesis block. This data is available via:
+
+1. [Unified API](https://www.covalenthq.com/docs/unified-api/?utm_source=base&utm_medium=partner-docs) - Incorporate blockchain data into your app with a familiar REST API
+2. [Increment](https://www.covalenthq.com/docs/increment/?utm_source=base&utm_medium=partner-docs) - Create and embed custom charts with no-code analytics
+
+To get started, [sign up](https://www.covalenthq.com/platform/?utm_source=base&utm_medium=partner-docs) and visit the [developer documentation](https://www.covalenthq.com/docs/?utm_source=base&utm_medium=partner-docs).
+
+
+
+- [Base Mainnet](https://www.covalenthq.com/docs/networks/base/?utm_source=base&utm_medium=partner-docs)
+- [Base Sepolia](https://www.covalenthq.com/docs/networks/base/?utm_source=base&utm_medium=partner-docs) (Testnet)
+
+
+
+## DipDup
+[DipDup](https://dipdup.io) is a Python framework for building smart contract indexers. It helps developers focus on business logic instead of writing a boilerplate to store and serve data. DipDup-based indexers are selective, which means only required data is requested. This approach allows to achieve faster indexing times and decreased load on underlying APIs.
+
+To get started, visit the [documentation](https://dipdup.io/docs/supported-networks/base) or follow the [quickstart](https://dipdup.io/docs/quickstart-evm) guide.
+
+
+
+## Envio
+
+[Envio](https://envio.dev) is a full-featured data indexing solution that provides application developers with a seamless and efficient way to index and aggregate real-time and historical blockchain data for any EVM. The indexed data is easily accessible through custom GraphQL queries, providing developers with the flexibility and power to retrieve specific information.
+
+Envio [HyperSync](https://docs.envio.dev/docs/hypersync) is an indexed layer of the Base blockchain for the hyper-speed syncing of historical data (JSON-RPC bypass). What would usually take hours to sync ~100,000 events can now be done in the order of less than a minute.
+
+Designed to optimize the user experience, Envio offers automatic code generation, flexible language support, multi-chain data aggregation, and a reliable, cost-effective hosted service.
+
+To get started, visit the [documentation](https://docs.envio.dev/docs/overview) or follow the [quickstart](https://docs.envio.dev/docs/quickstart) guide.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+
+
+## GhostGraph
+
+[GhostGraph](https://ghostgraph.xyz/) makes it easy to build blazingly fast indexers (subgraphs) for smart contracts.
+
+GhostGraph is the first indexing solution that lets you write your index transformations in **Solidity**. Base dApps can query data with GraphQL using our hosted endpoints.
+
+To get started, you can [sign up for an account](https://app.ghostlogs.xyz/ghostgraph/sign-up) and follow [this quickstart](https://docs.ghostlogs.xyz/category/-getting-started-1) guide on how to create, deploy, and query a GhostGraph.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+
+
+## The Indexing Company
+
+[The Indexing Company](https://www.indexing.co/) provides indexing as a service, capable of indexing any chain (EVM and non-EVM) with an RPC endpoint and integrating off-chain data within the same infrastructure.
+
+Our services include data transformations, aggregations, and streamlined data flows, allowing teams to develop their products faster while saving on developer resources, time, and money. Our solution is ideal for teams needing advanced data engineering for modular chain setups, multi-chain products, L1/L2/L3 chains and AI.
+
+To get started contact us [here](https://www.indexing.co/get-in-touch).
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+
+
+## Moralis
+
+[Moralis](https://moralis.io/?utm_source=base-docs&utm_medium=partner-docs) offers comprehensive data APIs for crypto, offering both indexed and real-time data across 15+ chains. Moralis' APIs include portfolio and wallet balances, NFT data, token data, price data, candlestick data, net worth data, and a lot more. All of the data is enriched with things like metadata, parsed events and address labels.
+
+To get started with Moralis, you can [sign up for an account](https://moralis.io/?utm_source=base-docs&utm_medium=partner-docs), visit the Moralis [documentation](https://docs.moralis.io/?utm_source=base-docs&utm_medium=partner-docs), or check out their tutorials on [Youtube](https://www.youtube.com/c/MoralisWeb3).
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+
+
+## Nexandria
+
+[Nexandria](https://www.nexandria.com/?utm_source=base-docs&utm_medium=partner-docs) API offers access to complete historical on-chain data at blazing speeds, arbitrary granularity (as low as block-level) and at viable unit economics (think web2 level costs). Our technology lets you generate subgraphs on the fly, unlocking unique endpoints like a statement of all the balance transfers for all the tokens, or a list of all the neighbors of an address with all the historical interaction details or a portfolio balance graph covering all the tokens across arbitrary time/block ranges.
+
+References:
+
+- [API Documentation](https://docs.nexandria.com/)
+- [Sign-up](https://www.nexandria.com/api)
+
+
+
+- Base Mainnet
+
+
+
+## Shovel
+
+[Shovel](https://indexsupply.com/shovel) is an [open source](https://github.com/indexsupply/code) tool for synchronizing Ethereum data to your Postgres database. Shovel can index block data, transaction data, and decoded event data. A single Shovel can index multiple chains simultaneously. Shovel is configured via a declarative JSON config file – no custom functions to save indexed data to your database.
+
+Find out more in the [Shovel Docs](https://indexsupply.com/shovel/docs/)
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+
+
+## Subsquid
+
+[Subsquid](https://subsquid.io/) is a decentralized hyper-scalable data platform optimized for providing efficient, permissionless access to large volumes of data.
+It currently serves historical on-chain data, including event logs, transaction receipts, traces, and per-transaction state diffs.
+Subsquid offers a powerful toolkit for creating custom data extraction and processing pipelines, achieving an indexing speed of up to 150k blocks per second.
+
+To get started, visit the [documentation](https://docs.subsquid.io/) or see this [quickstart with examples](https://docs.subsquid.io/sdk/examples/) on how to easily create subgraphs via Subsquid.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+
+
+## SubQuery
+
+[SubQuery](https://subquery.network/) is a data indexer that provides developers with fast, reliable, decentralized, and customized APIs for accessing rich indexed data from over 80+ ecosystems (including Base) within their projects.
+
+SubQuery provides the ability to aggregate this data across multiple blockchains, all within a single project.
+
+Other advantages of SubQuery includes performance with multiple RPC endpoint configurations, multi-worker capabilities and a configurable caching architecture.
+
+To get started, visit the [developer documentation](https://academy.subquery.network/) or follow [this step-by-step guide](https://academy.subquery.network/quickstart/quickstart_chains/base.html) on how to index any smart contract on Base.
+
+
+
+- [Base Mainnet](https://academy.subquery.network/quickstart/quickstart_chains/base.html)
+- Base Sepolia (Testnet)
+
+
+
+## The Graph
+
+[The Graph](https://thegraph.com/) is an indexing protocol that provides an easy way to query blockchain data through APIs known as subgraphs.
+
+With The Graph, you can benefit from:
+ - **Decentralized Indexing**: Enables indexing blockchain data through multiple indexers, thus eliminating any single point of failure
+ - **GraphQL Queries**: Provides a powerful GraphQL interface for querying indexed data, making data retrieval super simple.
+ - **Customization**: Define your own logic for transforming & storing blockchain data. Reuse subgraphs published by other developers on The Graph Network.
+
+Follow this [quick-start](https://thegraph.com/docs/en/quick-start/) guide to create, deploy, and query a subgraph within 5 minutes.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+See [all supported networks](https://thegraph.com/docs/en/#supported-networks)
+
+
+## Flair
+
+[Flair](https://flair.dev) is a real-time and historical custom data indexing for any EVM chain.
+
+It offers reusable **indexing primitives** (such as fault-tolerant RPC ingestors, custom processors and aggregations, re-org aware database integrations) to make it easy to receive, transform, store and access your on-chain data.
+
+To get started, visit the [documentation](https://docs.flair.dev) or clone the [starter boilerplate](https://github.com/flair-sdk/starter-boilerplate) template and follow the instructions.
+
+
+
+- [Base Mainnet](https://docs.flair.dev/reference/manifest.yml)
+- [Base Sepolia](https://docs.flair.dev/reference/manifest.yml) (Testnet)
diff --git a/docs/base-chain/tools/network-faucets.mdx b/docs/base-chain/tools/network-faucets.mdx
new file mode 100644
index 00000000..94bc3331
--- /dev/null
+++ b/docs/base-chain/tools/network-faucets.mdx
@@ -0,0 +1,75 @@
+---
+title: 'Network Faucets'
+description: Documentation for Testnet Faucets for the Base network. Details how to obtain Base testnet ETH.
+---
+
+## Coinbase Developer Platform
+
+The [Coinbase Developer Platform Faucet](https://portal.cdp.coinbase.com/products/faucet) provides free testnet ETH on Base Sepolia - one claim per 24 hours.
+
+
+Requests to Coinbase Developer Platform's Faucet are limited to one claim per 24 hours.
+
+
+
+## thirdweb Faucet
+
+The [thirdweb Faucet](https://thirdweb.com/base-sepolia-testnet) provides free testnet ETH on Base Sepolia - one claim per 24 hours.
+
+
+The thirdweb faucet allows developers to connect their wallet through EOA or social logins and claim Base Sepolia testnet funds.
+
+
+
+## Superchain Faucet
+
+The [Superchain Faucet](https://app.optimism.io/faucet) provides testnet ETH for all OP Chains, including Base.
+
+
+The Superchain faucet allows developers to authenticate via their onchain identity. Developers that choose to authenticate via their onchain identity can claim more testnet ETH versus traditional faucets. For more information, see the [FAQ](https://app.optimism.io/faucet).
+
+
+
+## Alchemy Faucet
+
+The [Alchemy Faucet](https://basefaucet.com/) is a fast and reliable network faucet that allows users with a free Alchemy account to request testnet ETH on Base Sepolia.
+
+
+Requests to Alchemy's Base Sepolia Faucet are limited to one claim per 24 hours.
+
+
+
+## Bware Labs Faucet
+
+[Bware Labs Faucet](https://bwarelabs.com/faucets) is an easy to use faucet with no registration required. You can use Bware Labs Faucet to claim Base Sepolia testnet ETH for free - one claim per 24 hours.
+
+
+Requests to Bware Labs Faucet are limited to one claim per 24 hours.
+
+
+
+## QuickNode Faucet
+
+[QuickNode Faucet](https://faucet.quicknode.com/drip) is an easy to use Multi-Chain Faucet. You can use QuickNode Faucet to claim Base Sepolia testnet ETH for free - one drip per network every 12 hours.
+
+
+Requests to QuickNode Faucet are limited to one drip every 12 hours.
+
+
+
+## LearnWeb3 Faucet
+
+[LearnWeb3 Faucet](https://learnweb3.io/faucets/base_sepolia) is a multi-chain faucet by LearnWeb3. You can use the LearnWeb3 faucet to claim Base Sepolia testnet ETH for free - one claim every 24 hours.
+
+
+Requests to LearnWeb3 faucet are limited to one claim per 24 hours.
+
+
+
+## Ethereum Ecosystem Faucet
+
+The [Base Sepolia Faucet](https://www.ethereum-ecosystem.com/faucets/base-sepolia) is a free & easy to use testnet faucet for Base Sepolia with very generous drips that doesn't require users to log in. It's run by [Ethereum Ecosystem](https://www.ethereum-ecosystem.com).
+
+
+Each wallet is restricted to receiving 0.5 ETH from this faucet every 24 hours.
+
diff --git a/docs/base-chain/tools/node-providers.mdx b/docs/base-chain/tools/node-providers.mdx
new file mode 100644
index 00000000..86e65f29
--- /dev/null
+++ b/docs/base-chain/tools/node-providers.mdx
@@ -0,0 +1,197 @@
+---
+title: 'Node Providers'
+description: Documentation for Node Providers for the Base network. Including details on their services, supported networks, and pricing plans.
+---
+
+import {HeaderNoToc} from "/snippets/headerNoToc.mdx";
+
+## Coinbase Developer Platform (CDP)
+
+[CDP](https://portal.cdp.coinbase.com/) provides an RPC endpoint that runs on the same node infrastructure that powers Coinbase's retail exchange, meaning you get the rock solid reliability of our retail exchange as a developer. CDP gives you a free, rate limited RPC endpoint to begin building on Base.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## 1RPC
+
+[1RPC](https://1rpc.io/) is the first and only on-chain attested privacy preserving RPC that eradicates metadata exposure and leakage when interacting with blockchains. 1RPC offers free and [paid plans](https://www.1rpc.io/#pricing) with additional features and increased request limits.
+
+
+
+- Base Mainnet
+
+## Alchemy
+
+[Alchemy](https://www.alchemy.com/base) is a popular API provider and developer platform. Its robust, free tier offers access to enhanced features like SDKs, [JSON-RPC APIs](https://docs.alchemy.com/reference/base-api-quickstart), and hosted mainnet and testnet nodes for Base.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## All That Node
+
+[All That Node](https://www.allthatnode.com/base.dsrv) is a comprehensive multi-chain development suite, designed to support multiple networks from a single platform. They offer free and [paid plans](https://www.allthatnode.com/pricing.dsrv) with additional features and increased request limits.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## Ankr
+
+[Ankr](https://www.ankr.com/rpc/base/) provides private and public RPC endpoints for Base, powered by a globally distributed and decentralized network of nodes. They offer free and [paid plans](https://www.ankr.com/rpc/pricing/) with increased request limits.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## Blast
+
+[Blast](https://blastapi.io/public-api/base) provides fast and reliable decentralized blockchain APIs by partnering with third-party Node Providers. Blast offers users the ability to generate their own [dedicated RPC endpoint for Base](https://blastapi.io/login).
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## Blockdaemon
+
+[Blockdaemon](https://www.blockdaemon.com/protocols/base/) offers access to hosted Base nodes with a free plan at $0/month via the Ubiquity Data API Suite. Extra costs may be incurred depending on usage.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## BlockPI
+
+[BlockPI](https://blockpi.io/) is a high-quality, robust, and efficient RPC service network that provides access to Base nodes with [free and paid plans](https://docs.blockpi.io/documentations/pricing).
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## Chainstack
+
+[Chainstack](https://chainstack.com/build-better-with-base/) allows developers to run high-performing Base nodes and APIs in minutes. They offer elastic Base RPC nodes that provide personal, geographically diverse, and protected API endpoints, as well as archive nodes to query the entire history of the Base Mainnet. Get started with their [free and paid pricing plans](https://chainstack.com/pricing/).
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## DRPC
+
+[DRPC](https://drpc.org/) offers access to a distributed network of independent third-party partners and public nodes for Base. They provide a free tier that allows for an unlimited amount of requests over public nodes, or a paid tier which provides access to all providers, as well as other additional features.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## GetBlock
+
+[GetBlock](https://getblock.io/nodes/base/) is a Blockchain-as-a-Service (BaaS) platform that provides instant API access to full nodes for Base. They offer free, pay per use, and unlimited pricing plans.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## NodeReal
+
+[NodeReal](https://nodereal.io/) is a blockchain infrastructure and services provider that provides instant and easy-access to Base node APIs.
+
+
+
+- Base Mainnet
+
+## Nodies DLB
+
+[Nodies DLB](https://nodies.app) provides highly performant RPC Services for Base, as well as all other OP-stacked chains. They offer free public endpoints, Pay-As-You-Go, and enterprise pricing plans.
+
+
+
+- Base Mainnet
+- Base Testnet (Available on request)
+
+## NOWNodes
+
+[NOWNodes](https://nownodes.io/nodes/basechain-base) is a Web3 development tool that provides shared and dedicated no rate-limit access to Base RPC full nodes.
+
+
+
+- Base Mainnet
+
+## OnFinality
+
+[OnFinality](https://onfinality.io) provides high performance archive access to Base Mainnet and Base Sepolia, with a generous free tier and high rate limits, as well as Trace and Debug APIs, available to [paid plans](https://onfinality.io/pricing).
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## QuickNode
+
+[QuickNode](https://www.quicknode.com/chains/base) offers access to hosted Base nodes as part of their free Discover Plan. You can configure add-ons, like "Trace Mode" and "Archive Mode" for an additional cost by upgrading to one of their paid plans.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## RockX
+
+[RockX](https://access.rockx.com) offers a global blockchain node network and developer tools for onchain innovation. Start with our free [Base RPC](https://access.rockx.com/product/base-blockchain-api-for-web3-builders) to access institutional-grade solutions.
+
+
+
+- Base Mainnet
+
+## Stackup
+
+[Stackup](https://www.stackup.sh/) is a leading ERC-4337 infrastructure platform. You can access hosted Base nodes with built-in [account abstraction tools](https://docs.stackup.sh/docs) like bundlers and paymasters.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## SubQuery
+
+[SubQuery](https://subquery.network/rpc) is a globally distributed, decentralized network of RPC nodes, offering generous free public endpoints and higher access through Flex Plans
+
+
+
+- Base Mainnet
+
+## Tenderly Web3 Gateway
+
+[Tenderly Web3 Gateway](https://tenderly.co/web3-gateway) provides a fast and reliable hosted node solution with a built-in suite of developer tooling and infrastructure building blocks covering your whole development lifecycle. Develop, test, deploy, and monitor your onchain app on the Base network with both [free and paid plans](https://tenderly.co/pricing).
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## Unifra
+
+[Unifra](https://base.unifra.io/) is a Web3 developer platform that provides tools, APIs, and node infrastructure, and provides access to Base nodes that are nodes are reliable, scalable, and easy to use.
+
+
+
+- Base Mainnet
+
+## Validation Cloud
+
+[Validation Cloud](https://app.validationcloud.io/) is the world’s fastest node provider according to Compare Nodes. With 50 million compute units available for use without a credit card and a scale tier that never has rate limits, Validation Cloud is built to support your most rigorous and low-latency workloads.
+
+
+
+- Base Mainnet
diff --git a/docs/base-chain/tools/onchain-registry-api.mdx b/docs/base-chain/tools/onchain-registry-api.mdx
new file mode 100644
index 00000000..09abc735
--- /dev/null
+++ b/docs/base-chain/tools/onchain-registry-api.mdx
@@ -0,0 +1,157 @@
+---
+title: Onchain Registry API
+description: Documentation for the Onchain Registry API.
+---
+
+
+The base url for our API endpoints is [https://base.org/api/registry/](https://base.org/api/registry/). The use of Onchain Registry API is governed by the license terms outlined in our [Terms & Conditions](#terms--conditions).
+
+
+
+## Instructions
+
+1. Users of this API can use the `/entries` and `/featured` endpoints to display Onchain Registry entries on their own surfaces
+2. If your team would like to use referral codes to point your users to entries, we recommend appending your referral code to the link provided in the `target_url` field
+3. If your team would like to filter entries based on where they are hosted or by creator, we recommend implementing logic based on the `target_url` and `creator_name` fields
+
+## Endpoints
+
+### GET /entries
+
+This endpoint will display all Onchain Registry entries subject to any query parameters set below
+
+#### Query Parameters
+
+| Name | Type | Description |
+| :------- | :----- | :------------------------------------------------------------------------------------------------------------- |
+| page | number | The page number (default 1) |
+| limit | number | The number of entries per page (default 10) |
+| category | array | The category or categories of the entries of interest (Options: Games, Social, Creators, Finance, Media) |
+| curation | string | The entry's level of curation (Options: Featured, Curated, Community) |
+
+#### Response
+
+```json [JSON]
+{
+ "data": [
+ {
+ "id": "7AsRdN8uf601fCkH1e084F",
+ "category": "Creators",
+ "content": {
+ "title": "Based Project",
+ "short_description": "Short description of this based project with max char count of 30",
+ "full_description": "Full description of this based project with max char count of 200",
+ "image_url": "https://base.org/image.png",
+ "target_url": "https://base.org/target-page",
+ "cta_text": "Mint",
+ "function_signature": "mint(uint256)",
+ "contract_address": "0x1FC10ef15E041C5D3C54042e52EB0C54CB9b710c",
+ "token_id": "2",
+ "token_amount": "0.01",
+ "featured": true,
+ "creator_name": "Base",
+ "creator_image_url": "https://base.org/creator-image.png",
+ "curation": "featured",
+ "start_ts": "2024-06-25T04:00:00Z",
+ "expiration_ts": "2024-07-29T00:00:00Z"
+ },
+ "updated_at": null,
+ "created_at": "2024-07-10T18:20:42.000Z"
+ },
+ {
+ "id": "8fRbdN8uf601fCkH1e084F",
+ "category": "Games",
+ "content": {
+ "title": "Based Project II",
+ "short_description": "Short description of this second based project with max char count of 30",
+ "full_description": "Full description of this second based project with max char count of 200",
+ "image_url": "https://base.org/image2.png",
+ "target_url": "https://base.org/second-target-page",
+ "cta_text": "Mint",
+ "function_signature": "mint(uint256)",
+ "contract_address": "0x1FC10ef15E041C5D3C54042e52EB0C54CB9b710c",
+ "token_id": "1",
+ "token_amount": "0.005",
+ "featured": false,
+ "creator_name": "Base",
+ "creator_image_url": "https://base.org/creator-image2.png",
+ "curation": "community",
+ "start_ts": "2024-06-25T04:00:00Z",
+ "expiration_ts": "2024-07-29T00:00:00Z"
+ },
+ "updated_at": "2024-07-11T18:20:42.000Z",
+ "created_at": "2024-07-10T18:20:42.000Z"
+ }
+ ],
+ "pagination": {
+ "total_records": 2,
+ "current_page": 1,
+ "total_pages": 1,
+ "limit": 10
+ }
+}
+```
+
+### GET /featured
+
+This endpoint will display a single Onchain Registry entry that is being actively featured
+
+#### Response
+
+```json [JSON]
+{
+ "data": {
+ "id": "7AsRdN8uf601fCkH1e084F",
+ "category": "Creators",
+ "content": {
+ "title": "Based Project",
+ "short_description": "Short description of this based project with max char count of 30",
+ "full_description": "Full description of this based project with max char count of 200",
+ "image_url": "https://base.org/image.png",
+ "target_url": "https://base.org/target-page",
+ "cta_text": "Mint",
+ "function_signature": "mint(uint256)",
+ "contract_address": "0x1FC10ef15E041C5D3C54042e52EB0C54CB9b710c",
+ "token_id": "2",
+ "token_amount": "0.01",
+ "featured": true,
+ "creator_name": "Base",
+ "creator_image_url": "https://base.org/creator-image.png",
+ "curation": "featured",
+ "start_ts": "2024-06-25T04:00:00Z",
+ "expiration_ts": "2024-07-29T00:00:00Z"
+ },
+ "updated_at": null,
+ "created_at": "2024-07-10T18:20:42.000Z"
+ }
+}
+```
+
+## Entry Schema
+
+| Name | Type | Description |
+| :----------------- | :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| id | string | Unique entry ID |
+| category | string | The category of the entry (Options: Games, Social, Creators, Finance, Media) |
+| title | string | The title of the entry |
+| short_description | string | Short version of the entry description (max 30 char) |
+| full_description | string | Full version of the entry description (max 200 char) |
+| image_url | string | URL of the entry's featured image |
+| target_url | string | URL for the entry's desired user action |
+| cta_text | string | This is the type of user action for the entry (Options: Play, Mint, Buy, Trade, Explore) |
+| function_signature | string | The function signature associated with the desired user action on the entry's contract |
+| contract_address | string | The contract address associated with the entry |
+| token_id | string | The token ID if this is an ERC-1155 |
+| token_amount | string | The price of the entry's desired user action |
+| featured | boolean | A true or false based on whether the entry is actively featured |
+| creator_name | string | The name of the entry's creator |
+| creator_image_url | string | The logo of the entry's creator |
+| curation | string | The entry's level of curation Options: Featured - one entry per day with top placement Curated - community entries being Community - all other community entries |
+| start_ts | string | The UTC timestamp that the entry is open to users |
+| expiration_ts | string | The UTC timestamp that the entry is no longer open to users |
+| updated_at | string \|\| null | The UTC timestamp that the entry was last updated (null if the entry has not been updated since creation) |
+| created_at | string | The UTC timestamp that the entry was created |
+
+## Terms & Conditions
+
+We grant third parties a non-exclusive, worldwide, royalty-free license to use the Onchain Registry API solely for the purpose of integrating it into their applications or services. This license does not extend to any data or content accessed through the Onchain API, which remains the sole responsibility of the third party. By using the Onchain Registry API, third parties agree to comply with our license terms and any applicable laws and regulations as set forth in Coinbase Developer Platform Terms of Service. We make no warranties regarding the Onchain Registry API, and users accept all risks associated with its use. The Onchain App Registry API is an Early Access Product per Section 18 of the [Coinbase Developer Platform Terms of Service](https://www.coinbase.com/legal/developer-platform/terms-of-service) and the Coinbase [Prohibited Use Policy](https://www.coinbase.com/legal/prohibited_use), and all terms and conditions therein govern your use of the Onchain Registry API.
diff --git a/docs/base-chain/tools/onramps.mdx b/docs/base-chain/tools/onramps.mdx
new file mode 100644
index 00000000..0ca03ec3
--- /dev/null
+++ b/docs/base-chain/tools/onramps.mdx
@@ -0,0 +1,28 @@
+---
+title: Onramps
+description: Documentation for fiat-to-crypto onramps for the Base network.
+---
+
+## Coinbase Onramp
+
+[Coinbase Onramp](https://www.coinbase.com/developer-platform/products/onramp) is a fiat-to-crypto onramp that allows users to buy or transfer crypto directly from self-custody wallets and apps. Coinbase Onramp supports 60+ fiat currencies with regulatory compliance and licensing, as well as 100+ cryptocurrencies, including ETH on Base. [Get started here](https://docs.cdp.coinbase.com/onramp/docs/getting-started/) to use the Coinbase Developer Platform.
+
+## MoonPay
+
+[MoonPay](https://www.moonpay.com/business/onramps) is a crypto onramp that provides global coverage, seamless revenue sharing, and zero risk of fraud or chargebacks. MoonPay supports 30+ fiat currencies and 110+ cryptocurrencies, including ETH on Base.
+
+## Onramp
+
+[Onramp](https://onramp.money/) is a fiat-to-crypto payment gateway, which helps users seamlessly convert fiat currency to the desired cryptocurrency. Onramp currently supports 300+ cryptocurrencies and 20+ blockchain networks, including ETH on Base.
+
+## Ramp
+
+[Ramp](https://ramp.network/) is an onramp and offramp that empowers users to buy & sell crypto inside your app. Ramp supports 40+ fiat currencies and 90+ crypto assets, including ETH on Base.
+
+## Transak
+
+[Transak](https://transak.com/) is a developer integration toolkit to let users buy/sell crypto in any app, website or web plugin. It is available across 170 cryptocurrencies on 75+ blockchains, including ETH on Base.
+
+## Alchemy Pay
+
+[Alchemy Pay](https://ramp.alchemypay.org/) (ACH) is a payment solutions provider that seamlessly connects fiat and crypto economies for global consumers, merchants, developers, and institutions.
diff --git a/docs/base-chain/tools/oracles.mdx b/docs/base-chain/tools/oracles.mdx
new file mode 100644
index 00000000..ec696f67
--- /dev/null
+++ b/docs/base-chain/tools/oracles.mdx
@@ -0,0 +1,141 @@
+---
+title: Oracles
+description: Documentation for various blockchain oracles for Base. Including support for price feeds and verifiable random functions (VRF).
+---
+
+import {HeaderNoToc} from "/snippets/headerNoToc.mdx";
+
+
+## API3
+
+The API3 Market provides access to 200+ price feeds on [Base Mainnet](https://market.api3.org/base) and [Base Testnet](https://market.api3.org/base-sepolia-testnet). The price feeds operate as a native push oracle and can be activated instantly via the Market UI.
+
+The price feeds are delivered by an aggregate of [first-party oracles](https://docs.api3.org/explore/airnode/why-first-party-oracles.html) using signed data and support [OEV recapture](https://docs.api3.org/explore/introduction/oracle-extractable-value.html).
+
+Unlike traditional data feeds, reading [API3 price feeds](https://docs.api3.org/guides/dapis/) enables dApps to auction off the right to update the price feeds to searcher bots which facilitates more efficient liquidation processes for users and LPs of DeFi money markets. The OEV recaptured is returned to the dApp.
+
+Apart from data feeds, API3 also provides [Quantum Random Number Generation](https://docs.api3.org/explore/qrng/) on Base Mainnet and Testnet. QRNG is a free-to-use service that provides quantum randomness onchain. It is powered by [Airnode](https://docs.api3.org/reference/airnode/latest/understand/), the first-party oracle that is directly operated by the [QRNG API providers](https://docs.api3.org/reference/qrng/providers.html). Read more about QRNG [here](https://docs.api3.org/reference/qrng).
+
+Check out these guides to learn more:
+
+- [dAPIs](https://docs.api3.org/guides/dapis/subscribing-to-dapis/): First-party aggregated data feeds sourced directly from the data providers.
+- [Airnode](https://docs.api3.org/guides/airnode/calling-an-airnode/): The first-party serverless Oracle solution to bring any REST API onchain.
+- [QRNG](https://docs.api3.org/guides/qrng/): Quantum Random Number Generator for verifiable quantum RNG onchain.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## Chainlink
+
+[Chainlink](https://chain.link/) provides a number of [price feeds](https://docs.chain.link/data-feeds/price-feeds/addresses/?network=base) for Base.
+
+See [this guide](https://docs.chain.link/docs/get-the-latest-price/) to learn how to use the Chainlink feeds.
+
+
+To use Chainlink datafeeds, you may need [LINK](https://docs.chain.link/resources/link-token-contracts?parent=dataFeeds) token.
+
+
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## Chronicle
+
+[Chronicle](https://chroniclelabs.org/) provides a number of [Oracles](https://chroniclelabs.org/dashboard) for Base.
+
+See [this guide](https://docs.chroniclelabs.org/Builders/tutorials/Remix) to learn how to use the Chronicle Oracles.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## DIA
+
+[DIA](https://www.diadata.org/) provides 2000+ [price feeds](https://www.diadata.org/app/price/) for Base.
+See [this guide](https://docs.diadata.org/introduction/intro-to-dia-oracles/request-an-oracle) to learn how to use the DIA feeds.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## Gelato
+
+Gelato VRF (Verifiable Random Function) provides a unique system offering trustable randomness on Base.
+
+See this guide to learn how to get started with [Gelato VRF](https://docs.gelato.network/web3-services/vrf/quick-start).
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
+
+## ORA
+
+[ORA](https://ora.io) provides an [Onchain AI Oracle](https://docs.ora.io/doc/oao-onchain-ai-oracle/introduction) for Base.
+
+See [this guide](https://docs.ora.io/doc/oao-onchain-ai-oracle/develop-guide/tutorials/interaction-with-oao-tutorial) to learn how to use ORA Onchain AI Oracle.
+
+
+
+- Base Mainnet
+
+## Pyth
+
+The [Pyth Network](https://pyth.network/) is one of the largest first-party Oracle network, delivering real-time data across [a vast number of chains](https://docs.pyth.network/price-feeds/contract-addresses). Pyth introduces an innovative low-latency [pull oracle design](https://docs.pyth.network/documentation/pythnet-price-feeds/on-demand), where users can pull price updates onchain when needed, enabling everyone in the onchain environment to access that data point most efficiently. Pyth network updates the prices every **400ms**, making Pyth one of the fastest onchain oracles.
+
+### Pyth Price Feeds Features:
+
+- 400ms latency
+- Efficient and cost-effective Oracle
+- [First-party](https://pyth.network/publishers) data sourced directly from financial institutions
+- [Price feeds ranging from Crypto, Stock, FX, Metals](https://pyth.network/developers/price-feed-ids)
+- [Available on all major chains](https://docs.pyth.network/price-feeds/contract-addresses)
+
+ for Base (Pyth Price Feeds):
+- Base Mainnet: [`0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a`](https://basescan.org/address/0x8250f4aF4B972684F7b336503E2D6dFeDeB1487a)
+- Base Sepolia: [`0xA2aa501b19aff244D90cc15a4Cf739D2725B5729`](https://base-sepolia.blockscout.com/address/0xA2aa501b19aff244D90cc15a4Cf739D2725B5729)
+
+### Pyth Entropy
+Pyth Entropy allows developers to quickly and easily generate secure **random numbers** onchain.
+
+Check [how to generate random numbers in EVM contracts](https://docs.pyth.network/entropy/generate-random-numbers/evm) for a detailed walkthrough.
+
+ for Base (Pyth Entropy):
+- Base Mainnet: [`0x6E7D74FA7d5c90FEF9F0512987605a6d546181Bb`](https://basescan.org/address/0x6E7D74FA7d5c90FEF9F0512987605a6d546181Bb)
+- Base Sepolia: [`0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c`](https://base-sepolia.blockscout.com/address/0x41c9e39574F40Ad34c79f1C99B66A45eFB830d4c)
+
+Check out the following links to get started with Pyth.
+
+- [Pyth Price Feed EVM Integration Guide](https://docs.pyth.network/price-feeds/use-real-time-data/evm)
+- [Pyth Docs](https://docs.pyth.network/home)
+- [Pyth Price Feed API Reference](https://api-reference.pyth.network/price-feeds/evm/getPrice)
+- [Pyth Examples](https://github.com/pyth-network/pyth-examples)
+- [Website](https://pyth.network/)
+- [Twitter](https://x.com/PythNetwork)
+
+## RedStone
+
+[RedStone](https://redstone.finance/) provides 1200+ [price feeds](https://app.redstone.finance/) for Base.
+
+See [this guide](https://docs.redstone.finance/) to learn how to use the RedStone feeds.
+
+
+
+- Base Mainnet
+
+## Supra
+
+[Supra](https://supraoracles.com) provides VRF and decentralized oracle price feeds that can be used for onchain and offchain use-cases such as spot and perpetual DEXes, lending protocols, and payments protocols. Supra’s oracle chain and consensus algorithm makes it one of the fastest-to-finality oracle providers, with layer-1 security guarantees. The pull oracle has a sub-second response time. Aside from speed and security, Supra’s rotating node architecture gathers data from 40+ data sources and applies a robust calculation methodology to get the most accurate value. The node provenance on the data dashboard also provides a fully transparent historical audit trail. Supra’s Distributed Oracle Agreement (DORA) paper was accepted into ICDCS 2023, the oldest distributed systems conference.
+
+Visit the Supra [documentation](https://supraoracles.com/docs/) to learn more about integrating Supra's oracle and VRF into your Base project.
+
+
+
+- Base Mainnet
+- Base Sepolia (Testnet)
diff --git a/docs/base-chain/tools/tokens-in-wallet.mdx b/docs/base-chain/tools/tokens-in-wallet.mdx
new file mode 100644
index 00000000..a69268ea
--- /dev/null
+++ b/docs/base-chain/tools/tokens-in-wallet.mdx
@@ -0,0 +1,129 @@
+---
+title: Tokens in Coinbase Wallet
+---
+# How to ensure ERC-20 tokens show in Coinbase Wallet as a token issuer
+
+This page is intended for developers that will or have recently deployed ERC-20 token contracts on Base Mainnet and would like their token details to display as quickly as possible on Coinbase Wallet.
+
+Coinbase Wallet makes any ERC-20 token instantly available for swapping seconds from when the contract is deployed.
+
+Follow the instructions below to ensure your token logo, asset name, and other metadata also appear on Coinbase Wallet.
+
+:::info[Disclaimer]
+Base does not endorse any specific token that is deployed on mainnet and made available for swapping.
+:::
+
+---
+
+## Adding your token to the list
+
+The steps below explain how to have your token display quickly on Coinbase Wallet. These instructions work not only for Base, but for any EVM chain supported by Coinbase Wallet (Optimism, Arbitrum, Polygon, Avalanche, Fantom, BNB).
+
+### Step 1: Deploy your ERC-20 Token on Base Mainnet
+
+Write and deploy a compliant ERC-20 token smart contract. Test it and then deploy on Base Mainnet. If you’re not a developer, you can use available token launcher tools in the Base ecosystem.
+
+### Step 2: Prepare your metadata and asset images
+
+Prepare a high-resolution image of your token’s logo. Ensure it is clear, identifiable, and representative of your token.
+
+### Step 3: List your cryptocurrency on a listing aggregator
+
+You can list for free on CoinGecko following these [instructions](https://support.coingecko.com/hc/en-us/articles/7291312302617-How-to-list-new-cryptocurrencies-on-CoinGecko)
+
+You can pay to be listed on CoinMarketCap following these [instructions](https://support.coinmarketcap.com/hc/en-us/articles/360043659351-Listings-Criteria).
+
+Remember, once your ERC-20 contract is deployed, your asset will immediately be available for native swapping in Coinbase Wallet.
+
+However, you’ll still need to list your token on Birdeye, CoinGecko, or CoinMarketCap for the asset’s logo and other metadata to flow into Coinbase Wallet and be seen by users. **It can take 24-48 hours for metadata changes to update.**
+
+## Why does my token display in the “Newer tokens” section in the swap flow?
+
+Tokens that are newly launched and haven’t had significant trading volume appear in the **newer token** section. Once your token reaches a market cap of at least $1M it will no longer be displayed in this section.
+
+:::info[Disclaimer]
+New assets with low liquidity may result in failed swaps or may result in a user receiving less of the destination token due to slippage. An important responsibility of the token creator is to communicate to the community these risks.
+:::
+
+## How can I update my token logo or description?
+
+ERC20 metadata can be updated quickly and easily by navigating to the asset’s page on the Coinbase Wallet desktop site ([wallet.coinbase.com](https://wallet.coinbase.com)). Note that any changes you make there will be reflected on Coinbase Wallet only (desktop and mobile).
+
+**Step 1:** Search for your token on wallet.coinbase.com
+
+**Step 2:** Scroll to the bottom and click “Update here”
+
+**Step 3:** Select your logo image or enter the description you want to show
+
+**Step 4:** Choose the address you want to submit with and click submit
+
+**Step 5:** Submissions will be reviewed within 5 business days. To expedite approval, we recommend taking the following steps:
+
+1. Make a post from your official X or Warpcast account documenting the change. The Coinbase Wallet team may reach out to you if they have questions.
+
+3. Submit the change with the same deployed address that the token contract was deployed with.
+
+If the above guidance doesn’t resolve your issue, please submit more information using this [Deform](https://app.deform.cc/form/a331da5a-447b-43e8-b636-ea3b925e115a/).
+
+# Sharing your token
+
+## Custom trading links
+
+By sharing a unique link to your token’s asset page, your community can easily and safely get your token.
+
+**How to get your custom link:**
+
+**Step 1:** Grab your custom link for your token by navigating to the asset page on Coinbase Wallet
+
+**Step 2:** Click the share button
+
+![][image1]
+
+![][image2]
+
+**Step 4:** Share it with your community – either by posting it as an official link on your social accounts or as a CTA on your website.
+
+## Trending Swaps on Base
+
+The Trending Swaps on Base module reflects the top tokens traded through the Coinbase Wallet mobile app, browser extension, and website app over the past 24 hours.
+
+By encouraging your community to use Coinbase Wallet, your token can be appear on Trending Swaps and reach a wider audience.
+
+## Send via text
+
+With send via text, you can send crypto as easily as sending a text. The crypto you want to send will be retrievable using a claim link, and the link can be shared over text, Twitter, Farcaster, Telegram – anywhere.
+
+Recipients will be prompted to make a Coinbase Wallet to claim the crypto. Whether you're onboarding people to crypto or looking to easily reward your community, send via text has you covered.
+
+**How to send crypto via text:**
+
+**Step 1:** Go to the **Send** flow in Coinbase Wallet
+
+**Step 2:** Enter the amount you want to send
+
+![][image3]
+
+**Step 3:** Click “Send via text” in the upper right hand corner
+
+**Step 4:** Enter the amount and type of crypto you wish to send, and click “Create”Copy the link
+
+![][image4]
+
+**Step 5:** Copy the link and Share it on any platform.
+
+* Clicking the link will prompt the recipient to open Coinbase Wallet or download Coinbase Wallet to claim the crypto
+* If the crypto isn’t claimed within 14 weeks, it will be returned to your wallet.
+
+Send via text currently supports the top 100 tokens on Base. We’re in the process of adding support for all tokens on Base.
+
+---
+
+If you’re encountering an issue with token support on Coinbase Wallet, submit [this form](https://app.deform.cc/form/a331da5a-447b-43e8-b636-ea3b925e115a/?page_number=0) and we’ll aim to resolve your issue within 5 business days.
+
+[image1]:
+
+[image2]:
+
+[image3]:
+
+[image4]:
diff --git a/docs/components/App.tsx b/docs/components/App.tsx
deleted file mode 100644
index 503f939d..00000000
--- a/docs/components/App.tsx
+++ /dev/null
@@ -1,75 +0,0 @@
-'use client';
-
-import { OnchainKitProvider } from '@coinbase/onchainkit';
-import '@coinbase/onchainkit/styles.css';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import type { ReactNode } from 'react';
-import { http, WagmiProvider, createConfig } from 'wagmi';
-import { base, baseSepolia } from 'wagmi/chains';
-import { coinbaseWallet } from 'wagmi/connectors';
-import { useTheme } from '../contexts/Theme.tsx';
-import { defineChain } from 'viem';
-
-const queryClient = new QueryClient();
-
-export const SANDBOX_CHAIN = defineChain({
- id: 8453200058,
- name: 'Sandbox Network',
- nativeCurrency: {
- name: 'Ethereum',
- symbol: 'ETH',
- decimals: 18,
- },
- rpcUrls: {
- default: {
- http: ['https://sandbox-rpc-testnet.appchain.base.org'],
- },
- },
-});
-
-const wagmiConfig = createConfig({
- chains: [base, baseSepolia, SANDBOX_CHAIN],
- connectors: [
- coinbaseWallet({
- appName: 'OnchainKit',
- }),
- ],
- ssr: true,
- transports: {
- [base.id]: http(),
- [baseSepolia.id]: http(),
- [SANDBOX_CHAIN.id]: http(),
- },
-});
-
-export default function App({ children }: { children: ReactNode }) {
- const isServer = typeof window === 'undefined';
- if (isServer) {
- return null;
- }
-
- const viteCdpApiKey = import.meta.env.VITE_CDP_API_KEY;
- const viteProjectId = import.meta.env.VITE_CDP_PROJECT_ID;
- const { theme } = useTheme();
-
- return (
-
-
-
- {children}
-
-
-
- );
-}
diff --git a/docs/components/CafeUnitTest/index.jsx b/docs/components/CafeUnitTest/index.jsx
deleted file mode 100644
index 166c49dd..00000000
--- a/docs/components/CafeUnitTest/index.jsx
+++ /dev/null
@@ -1,600 +0,0 @@
-/* eslint-disable */
-
-import React from 'react';
-import { useEffect, useState, Suspense } from 'react';
-import { useAccount, useSwitchChain } from 'wagmi';
-import { useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
-import { WagmiProvider, createConfig, http } from 'wagmi';
-import { mainnet, base, baseSepolia, baseGoerli } from 'wagmi/chains';
-import { coinbaseWallet } from 'wagmi/connectors';
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { OnchainKitProvider } from '@coinbase/onchainkit';
-import '@coinbase/onchainkit/styles.css';
-import {
- ConnectWallet,
- ConnectWalletText,
- Wallet,
- WalletDropdown,
- WalletDropdownBasename,
- WalletDropdownDisconnect,
- WalletDefault,
-} from '@coinbase/onchainkit/wallet';
-import { Address, Avatar, Name, Identity, EthBalance } from '@coinbase/onchainkit/identity';
-import { color } from '@coinbase/onchainkit/theme';
-import useNFTData from './nft-exercise-data';
-import { decodeEventLog, defineChain } from 'viem';
-
-const pinStyle = {
- width: 300,
- height: 300,
- marginRight: 10,
- marginBottom: '25px',
- display: 'block',
-};
-
-const pinTitleStyle = {
- marginTop: '25px',
- marginBottom: '10px',
- fontStyle: 'italic',
-};
-
-const buttonStyle = {
- fontSize: '16px',
- lineHeight: '1.75rem',
- paddingTop: '10px',
- paddingBottom: '10px',
- paddingLeft: '24px',
- paddingRight: '24px',
- color: '#fff',
- borderRadius: '6px',
- border: 'none',
- cursor: 'pointer',
- fontWeight: 600,
-};
-
-const buttonEnabledColor = {
- backgroundColor: '#0051ff',
-};
-
-const buttonDisabledColor = {
- backgroundColor: '#d1d1d1',
-};
-
-const inputStyle = {
- padding: '14px',
- borderRadius: '6px',
- border: '1px solid lightGrey',
- fontSize: '14px',
- marginRight: '5px',
- width: '70%',
-};
-
-const messageStyle = {
- backgroundColor: '#d1ecf1',
- color: '#0c5460',
- border: '1px solid #bee5eb',
- padding: '10px 5px 10px 20px',
- borderRadius: '6px',
- marginBottom: '5px',
- fontWeight: '500',
-};
-
-const errorMessageStyle = {
- backgroundColor: '#f8d7da',
- color: '#721c24',
- border: '1px solid #f5c6cb',
- padding: '10px 5px 10px 20px',
- borderRadius: '6px',
- marginBottom: '5px',
- fontWeight: '500',
-};
-
-const loadingMessageStyle = {
- backgroundColor: '#fff3cd',
- color: '#856404',
- border: '1px solid #ffeeba',
- padding: '10px 5px 10px 20px',
- borderRadius: '6px',
- marginBottom: '5px',
- fontWeight: '500',
-};
-
-const directionsStyle = {
- padding: '10px 0 10px 0',
-};
-
-// Create a query client for tanstack query (required by wagmi v2)
-const queryClient = new QueryClient();
-
-// Define the SANDBOX_CHAIN (matching App.tsx)
-export const SANDBOX_CHAIN = defineChain({
- id: 8453200058,
- name: 'Sandbox Network',
- nativeCurrency: {
- name: 'Ethereum',
- symbol: 'ETH',
- decimals: 18,
- },
- rpcUrls: {
- default: {
- http: ['https://sandbox-rpc-testnet.appchain.base.org'],
- },
- },
-});
-
-// Define your wagmi config
-const wagmiConfig = createConfig({
- chains: [base, baseSepolia, SANDBOX_CHAIN],
- connectors: [
- coinbaseWallet({
- appName: 'OnchainKit',
- }),
- ],
- ssr: true,
- transports: {
- [base.id]: http(),
- [baseSepolia.id]: http(),
- [SANDBOX_CHAIN.id]: http(),
- },
-});
-
-export function CafeUnitTest({ nftNum }) {
- const { isConnecting, isDisconnected, address, chain } = useAccount();
- const { switchChain } = useSwitchChain();
-
- const [messages, setMessages] = useState(['Submit your contract address.']);
- const [contractFormEntry, setContractFormEntry] = useState('');
- const [submittedContract, setSubmittedContract] = useState('');
- const [hasPin, setHasPin] = useState(false);
- const [fetchNFTStatus, setFetchNFTStatus] = useState(true);
- const [testingState, setTestingState] = useState('idle'); // 'idle', 'testing', 'waiting', 'completed', 'error'
-
- const nftData = useNFTData();
-
- const nft = nftData[nftNum];
-
- const { data: hasNFT, error: nftError } = useReadContract({
- address: nft.deployment.address,
- abi: nft.deployment.abi,
- functionName: 'owners',
- args: [address],
- enabled: fetchNFTStatus,
- onSettled(data, error) {
- if (error) {
- console.error('Error checking NFT ownership:', error);
- setMessages([
- 'Error checking NFT ownership status.',
- 'Please check your connection and try again.',
- ]);
- } else {
- setHasPin(!!data);
- }
- setFetchNFTStatus(false);
- },
- });
-
- // Test Contract Function
- const {
- writeContract: testContract,
- isPending: isTestLoading,
- error: isTestError,
- data: transactionHash,
- reset: resetTestContract,
- } = useWriteContract();
-
- const {
- data: transactionReceipt,
- isPending: isTestReceiptLoading,
- reset: resetTransactionReceipt,
- } = useWaitForTransactionReceipt({
- hash: transactionHash,
- enabled: !!transactionHash,
- });
-
- function handleContractChange(event) {
- setContractFormEntry(event.target.value);
- }
-
- useEffect(() => {
- if (hasNFT != null) {
- setHasPin(hasNFT);
- }
- }, [hasNFT]);
-
- useEffect(() => {
- if (isTestError) {
- setMessages([
- 'Something is wrong with the contract at the address you are trying to submit',
- 'It is likely that your function signatures do not match what is expected.',
- 'You will also see this if you cancel the transaction.',
- ]);
- setTestingState('error');
- }
- }, [isTestError]);
-
- // Update manual state based on wagmi states
- useEffect(() => {
- if (isTestLoading) {
- setTestingState('testing');
- }
- }, [isTestLoading]);
-
- useEffect(() => {
- if (isTestReceiptLoading && transactionHash) {
- setTestingState('waiting');
- }
- }, [isTestReceiptLoading, transactionHash]);
-
- useEffect(() => {
- if (transactionReceipt) {
- setTestingState('completed');
- console.log('Transaction receipt received:', transactionReceipt);
- }
- }, [transactionReceipt]);
-
- // Reset everything when chain or address changes
- useEffect(() => {
- console.log('Connected to chain:', chain?.id, chain?.name);
- // Reset all state when chain or address changes
- setTestingState('idle');
- if (!submittedContract) {
- setMessages(['Submit your contract address.']);
- }
- }, [chain, address, submittedContract]);
-
- useEffect(() => {
- async function processEventLog(parsedLog) {
- const processed = [];
- if (parsedLog.eventName === 'TestSuiteResult') {
- const { testResults } = parsedLog.args;
- // Results don't know which tests failed, so find them
- for (const testResult of testResults) {
- processed.push(`✅ ${testResult.message}`);
- const { assertResults } = testResult;
- const { elements: arList, num } = assertResults;
- // Slice out unused in array - arList is a dynamic memory array implementation
- // so it may have unused elements allocated
- const elements = arList.slice(0, Number(num));
- let passedAllAsserts = true;
- for (const element of elements) {
- if (!element.passed) {
- passedAllAsserts = false;
- }
- }
- if (!passedAllAsserts) {
- processed[processed.length - 1] = `❌${processed[processed.length - 1].slice(1)}`;
- for (const element of elements) {
- if (element.passed === false) {
- try {
- processed.push(`-> ${element.assertionError}`);
- } catch {
- // An error in the assert smart contract sometimes sends strings
- // with bytes that can't be converted to utf-8
- // It can't be fixed here because the error is caused within iface.parseLog(log)
- // See: https://github.com/ethers-io/ethers.js/issues/714
-
- processed.push('-> Assertion failed (cannot parse message)');
- }
- }
- }
- }
- }
- }
-
- setMessages([...processed]);
- setTestingState('completed');
- }
-
- if (transactionReceipt) {
- for (const log of transactionReceipt.logs) {
- try {
- const parsed = decodeEventLog({
- abi: nft.deployment.abi,
- data: log.data,
- topics: log.topics,
- });
- console.log('topics', parsed);
- processEventLog(parsed);
- } catch (e) {
- // Skip other log types (can't tell type without parsing)
- console.log('SKIPPED LOG', e);
- }
- }
- }
- }, [transactionReceipt, contractFormEntry, nft.deployment.abi]);
-
- async function handleContractSubmit(event) {
- event.preventDefault();
-
- // Clear any previous submission state
- setTestingState('testing');
- setSubmittedContract(contractFormEntry);
- setMessages(['Running tests...']);
-
- try {
- await testContract({
- address: nft.deployment.address,
- abi: nft.deployment.abi,
- functionName: 'testContract',
- args: [contractFormEntry],
- });
-
- // Set a timeout in case transaction hangs
- const timeoutId = setTimeout(() => {
- if (testingState === 'waiting' || testingState === 'testing') {
- console.log('Transaction taking too long, resetting state');
- setMessages([...messages, 'Transaction taking too long. Please try again.']);
- setTestingState('idle');
- }
- }, 30000); // 30 second timeout
-
- return () => clearTimeout(timeoutId);
- } catch (error) {
- console.error('Error submitting contract:', error);
- setMessages([
- 'Error submitting contract for testing.',
- 'Please check your connection and try again.',
- ]);
- setTestingState('error');
- }
- }
-
- // Handle the manual reset button action
- function handleManualReset() {
- console.log('Manual reset triggered');
- // Reset the testing state
- setTestingState('idle');
- setMessages(['Submit your contract address.']);
-
- // Also try the wagmi resets
- if (resetTestContract) resetTestContract();
- if (resetTransactionReceipt) resetTransactionReceipt();
-
- // Force clear localStorage related to the current state
- try {
- Object.keys(localStorage).forEach((key) => {
- if (key.includes('wagmi') || key.includes('transaction')) {
- localStorage.removeItem(key);
- }
- });
- } catch (e) {
- console.log('Error clearing localStorage:', e);
- }
- }
-
- function renderTests() {
- if (submittedContract) {
- const listItems = messages.map((message, index) => {
- let style = messageStyle;
-
- // Apply appropriate style based on testing state
- if (testingState === 'error') {
- style = errorMessageStyle;
- } else if (testingState === 'testing' || testingState === 'waiting') {
- style = loadingMessageStyle;
- }
-
- return (
-
- {message}
-
- );
- });
- return {listItems}
;
- }
- return
;
- }
-
- function renderResult() {
- if (hasPin) {
- return (
-
-
- {nft.title} NFT Badge Earned on {chain?.name}!
-
-
-
- );
- }
- return Submit your passing contract to earn this badge.
;
- }
-
- function renderTestSubmission() {
- if (isDisconnected) {
- return (
-
-
-
Please connect your wallet.
-
- );
- }
- if (isConnecting) {
- return Connecting...
;
- }
- if (chain?.id !== baseSepolia.id) {
- return (
-
-
You are not connected to Base Sepolia
-
switchChain({ chainId: baseSepolia.id })}
- >
- Switch to Base Sepolia
-
-
- );
- }
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{renderTests()}
-
{renderResult()}
-
-
-
-
-
- );
- }
-
- return {renderTestSubmission()}
;
-}
-
-// Create a wrapper component that provides the necessary context
-function CafeUnitTestWithProviders(props) {
- // Use client-side only rendering to avoid hydration issues
- const [mounted, setMounted] = useState(false);
-
- // Create stable instances of query client and config
- const queryClientRef = React.useRef(new QueryClient());
- const configRef = React.useRef(
- createConfig({
- chains: [base, baseSepolia, SANDBOX_CHAIN],
- connectors: [
- coinbaseWallet({
- appName: 'OnchainKit',
- }),
- ],
- ssr: true,
- transports: {
- [base.id]: http(),
- [baseSepolia.id]: http(),
- [SANDBOX_CHAIN.id]: http(),
- },
- }),
- );
-
- // This prevents hydration errors by only rendering client-side
- useEffect(() => {
- setMounted(true);
- }, []);
-
- // Return a loading state on the server
- if (!mounted) {
- return Loading wallet connection...
;
- }
-
- // Get API keys from environment variables
- const viteCdpApiKey = import.meta.env.VITE_CDP_API_KEY;
- const viteProjectId = import.meta.env.VITE_CDP_PROJECT_ID;
-
- return (
-
-
-
-
-
-
-
-
-
- );
-}
-
-// Simple error boundary component to catch and display errors
-class ErrorBoundary extends React.Component {
- constructor(props) {
- super(props);
- this.state = { hasError: false, error: null };
- }
-
- static getDerivedStateFromError(error) {
- return { hasError: true, error };
- }
-
- componentDidCatch(error, errorInfo) {
- console.error('Error in CafeUnitTest component:', error, errorInfo);
- }
-
- render() {
- if (this.state.hasError) {
- return (
-
-
Something went wrong
-
Please try refreshing the page or connecting a different wallet.
-
- );
- }
- return this.props.children;
- }
-}
-
-// Export the wrapped component instead
-export default CafeUnitTestWithProviders;
diff --git a/docs/components/smart-wallet/SubAccount.tsx b/docs/components/smart-wallet/SubAccount.tsx
deleted file mode 100644
index 663b3fa9..00000000
--- a/docs/components/smart-wallet/SubAccount.tsx
+++ /dev/null
@@ -1,125 +0,0 @@
-'use client';
-
-import { useCallback, useEffect, useState } from 'react';
-import Button from '../Button/index.tsx';
-import {
- createCoinbaseWalletSDK,
- getCryptoKeyAccount,
- ProviderInterface,
-} from '@coinbase/wallet-sdk-canary';
-import { Address } from '@coinbase/onchainkit/identity';
-import { SubAccountIllustration } from '@/components/smart-wallet/Illustration.tsx';
-
-type Signer = Awaited>;
-const BASE_SEPOLIA_CHAIN_ID = 84532;
-
-export default function SubAccount() {
- const [provider, setProvider] = useState(null);
- const [signer, setSigner] = useState(null);
-
- const [universalAccount, setUniversalAccount] = useState(null);
- const [subAccount, setSubAccount] = useState(null);
-
- useEffect(() => {
- const walletProvider = createCoinbaseWalletSDK({
- appName: 'Smart Wallet Doc',
- appChainIds: [BASE_SEPOLIA_CHAIN_ID],
- preference: {
- keysUrl: 'https://keys-dev.coinbase.com/connect',
- options: 'smartWalletOnly',
- },
- subaccount: {
- getSigner: getCryptoKeyAccount,
- },
- }).getProvider();
-
- setProvider(walletProvider);
-
- const loadKey = async () => {
- const cryptoKeyAccount = await getCryptoKeyAccount();
- setSigner(cryptoKeyAccount);
- };
-
- void loadKey();
- }, []);
-
- const createSubAccount = useCallback(async () => {
- if (!provider || !signer?.account) return;
-
- const walletConnectResponse = (await provider.request({
- method: 'wallet_connect',
- params: [
- {
- capabilities: {
- addSubAccount: {
- account: {
- type: 'create',
- keys: [
- {
- type: 'webauthn-p256',
- key: signer.account.publicKey,
- },
- ],
- },
- },
- },
- },
- ],
- })) as {
- accounts: {
- address: string;
- capabilities: {
- addSubAccount: {
- address: string;
- };
- };
- }[];
- };
-
- setSubAccount(walletConnectResponse.accounts[0].capabilities.addSubAccount.address);
- setUniversalAccount(walletConnectResponse.accounts[0].address);
- }, [provider, signer?.account]);
-
- // Universal Account Signing Function
- const universalAccountSigning = useCallback(async () => {
- if (!provider || !signer?.account) return;
-
- const hash = await provider.request({
- method: 'personal_sign',
- params: ['Hello, world!', universalAccount],
- });
-
- alert(`Sign was successful: ${hash}`);
- }, [provider, signer?.account, universalAccount]);
-
- // Sub Account Signing Function
- const subAccountSigning = useCallback(async () => {
- if (!provider || !signer?.account) return;
-
- const hash = await provider.request({
- method: 'personal_sign',
- params: ['Hello, world!', subAccount],
- });
-
- alert(`Sign was successful: ${hash}`);
- }, [provider, signer?.account, subAccount]);
-
- return subAccount ? (
- universalAccount && subAccount && (
- }
- subAccount={ }
- universalAccountSigning={universalAccountSigning}
- subAccountSigning={subAccountSigning}
- />
- )
- ) : (
-
- Create Sub Account
-
- );
-}
diff --git a/docs/config/guides.ts b/docs/config/guides.ts
deleted file mode 100644
index af4bb35b..00000000
--- a/docs/config/guides.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-export const topGuides = [
- {
- title: 'Build an E-commerce App',
- path: '/guides/use-case-guides/commerce/build-an-ecommerce-app'
- },
- {
- title: 'Add an In-App Onramp ',
- path: '/guides/use-case-guides/finance/build-a-smart-wallet-funding-app'
- },
- {
- title: 'Run a Base Node',
- path: '/docs/chain/run-a-base-node'
- }
-] as const
-
-export type Guide = typeof topGuides[number]
\ No newline at end of file
diff --git a/docs/constants.ts b/docs/constants.ts
deleted file mode 100644
index a9d39e81..00000000
--- a/docs/constants.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const isDevelopment = import.meta.env.MODE === 'development';
diff --git a/docs/contexts/AppProviders.tsx b/docs/contexts/AppProviders.tsx
deleted file mode 100644
index a4f70a28..00000000
--- a/docs/contexts/AppProviders.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { lazy, Suspense } from 'react';
-
-const CookieBannerWrapper = lazy(async () => import('./CookieBannerWrapper'));
-
-export function AppProviders({ children }: { children: React.ReactNode }) {
- return (
- <>
- {children}
-
-
-
- >
- );
-}
diff --git a/docs/contexts/CookieBannerWrapper.tsx b/docs/contexts/CookieBannerWrapper.tsx
deleted file mode 100644
index 62885ff4..00000000
--- a/docs/contexts/CookieBannerWrapper.tsx
+++ /dev/null
@@ -1,71 +0,0 @@
-import { CookieManagerProvider } from '@/components/CookieManager/CookieManagerProvider.tsx';
-import ClientAnalyticsScript from '@/components/ClientAnalyticsScript/ClientAnalyticsScript.tsx';
-import { isDevelopment } from '@/constants.ts';
-/*
- * CJS import
- * This import structure for CookieBanner is necessary because in prod, direct
- * destructuring from @coinbase/cookie-banner fails.
- * However in dev, the import fails because the pkg is not found.
- * This structure allows the import to work in prod, and disables the banner in dev.
- */
-import pkg from '@coinbase/cookie-banner';
-const { CookieBanner } = isDevelopment ? {} : pkg;
-
-export const cookieBannerTheme = {
- colors: {
- primary: '#1652F0',
- positive: '#05B169',
- negative: '#DF5F67',
- warning: '#F4C622',
- background: '#FFFFFF',
- backgroundMuted: '#EEF0F3',
- onBackground: '#050F1A',
- onBackgroundMuted: '#0A0B0D',
- onPrimary: '#FFFFFF',
- overlay: 'rgba(17,52,83,0.6)',
- },
- border: {
- border: '1px solid #D8D8D8',
- borderRadius: '4px',
- },
- fontSize: {
- sm: '14px',
- md: '16px',
- },
- fontWeight: {
- regular: '400',
- bold: '500',
- },
- size: {
- xs: '8px',
- sm: '16px',
- md: '24px',
- lg: '32px',
- },
- breakpoints: {
- phone: 560,
- desktop: 992,
- tablet: 768,
- },
- zIndex: {
- hidden: 0,
- normal: 1,
- elevated: 2,
- high: 2,
- extraHigh: 3,
- backdrop: 999,
- overlay: 1000,
- top: 1001,
- },
-};
-
-export default function CookieBannerWrapper() {
- if (isDevelopment || typeof window === 'undefined') return null;
-
- return (
-
-
-
-
- );
-}
diff --git a/docs/contexts/Theme.tsx b/docs/contexts/Theme.tsx
deleted file mode 100644
index 7ef33ff8..00000000
--- a/docs/contexts/Theme.tsx
+++ /dev/null
@@ -1,62 +0,0 @@
-import {
- type ReactNode,
- createContext,
- useCallback,
- useContext,
- useEffect,
- useMemo,
- useState,
-} from 'react';
-
-type Theme = 'light' | 'dark';
-type ThemeContextProps = {
- theme: Theme;
- toggleTheme: () => void;
-};
-
-export const ThemeContext = createContext({
- theme: 'dark',
- toggleTheme: () => {},
-});
-
-export function useTheme() {
- const context = useContext(ThemeContext);
- if (context === undefined) {
- throw new Error('useTheme must be used within a ThemeProvider');
- }
- return context;
-}
-
-export default function ThemeProvider({ children }: { children: ReactNode }) {
- const [theme, setTheme] = useState('dark');
-
- const toggleTheme = useCallback(() => {
- setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));
- }, []);
-
- useEffect(() => {
- const rootElement = document.documentElement;
- const observer = new MutationObserver(() => {
- if (rootElement.classList.contains('dark')) {
- setTheme('dark');
- } else {
- setTheme('light');
- }
- });
-
- observer.observe(rootElement, {
- attributes: true,
- attributeFilter: ['class'],
- });
-
- if (rootElement.classList.contains('dark')) {
- setTheme('dark');
- }
-
- return () => observer.disconnect();
- }, []);
-
- const values = useMemo(() => ({ theme, toggleTheme }), [theme, toggleTheme]);
-
- return {children} ;
-}
diff --git a/docs/cookbook/accept-crypto-payments.mdx b/docs/cookbook/accept-crypto-payments.mdx
new file mode 100644
index 00000000..dd293311
--- /dev/null
+++ b/docs/cookbook/accept-crypto-payments.mdx
@@ -0,0 +1,394 @@
+---
+title: 'Accept Crypto Payments with Coinbase Commerce & OnchainKit'
+sidebarTitle: 'Accept Crypto Payments'
+description: 'Learn how to integrate Coinbase Commerce payments into your application using OnchainKit to eliminate traditional fees and expand your global reach.'
+---
+
+Accepting crypto payments can help you **eliminate traditional credit card fees** and **avoid costly chargebacks**, giving you a faster, more global payment experience. In this guide, you'll learn how to quickly integrate Coinbase Commerce and OnchainKit to accept crypto payments for products or services in your application.
+
+## What You'll Build
+
+By the end of this guide, you'll have a fully functional checkout flow that:
+
+- Displays your product with an attractive interface
+- Connects users' wallets securely
+- Processes crypto payments through Coinbase Commerce
+- Provides real-time payment status updates
+
+
+
+ Remove traditional credit card processing fees and chargebacks
+
+
+ Accept payments from crypto users worldwide, 24/7
+
+
+ Receive payments instantly with blockchain confirmation
+
+
+ Get started in minutes with OnchainKit components
+
+
+
+## Prerequisites
+
+Before you begin, ensure you have the following accounts and tools set up:
+
+
+
+ [Sign up for Coinbase Commerce](https://beta.commerce.coinbase.com/sign-up) to accept cryptocurrency payments globally.
+
+
+ You'll need this to create products and manage payments.
+
+
+
+
+ [Create a CDP account](https://www.coinbase.com/cloud) to access OnchainKit APIs and services.
+
+
+ CDP provides the infrastructure for seamless crypto integrations.
+
+
+
+
+ [Set up Reown](https://cloud.reown.com/) (formerly WalletConnect) for secure wallet connections across devices and platforms.
+
+
+
+## Implementation Guide
+
+
+
+
+ First, you'll create a product in Coinbase Commerce that represents what you're selling.
+
+ 1. **Log in** to your [Coinbase Commerce dashboard](https://beta.commerce.coinbase.com/)
+ 2. Navigate to the [product creation page](https://beta.commerce.coinbase.com/products)
+ 3. **Fill in your product details**:
+ - Product name (clear and descriptive)
+ - Description (what customers are buying)
+ - Price (in your preferred currency)
+ 4. Click **Create product**
+ 5. Once created, select **View product** and copy the **UUID** from the URL
+
+
+ 
+
+
+
+ Save the product UUID immediately - you'll need it as an environment variable. The UUID appears in the product URL after creation.
+
+
+
+
+
+ Clone the official OnchainKit app template to get started quickly with best practices already configured.
+
+
+ ```bash Bun
+ git clone https://github.com/coinbase/onchainkit-app-template.git
+ cd onchainkit-app-template
+ bun install
+ ```
+
+ ```bash npm
+ git clone https://github.com/coinbase/onchainkit-app-template.git
+ cd onchainkit-app-template
+ npm install
+ ```
+
+ ```bash Yarn
+ git clone https://github.com/coinbase/onchainkit-app-template.git
+ cd onchainkit-app-template
+ yarn install
+ ```
+
+
+
+ Verify the installation completed successfully by running `ls` to see the project files.
+
+
+
+
+
+ Create your environment configuration with the required API keys and identifiers.
+
+ In your project root, create or update your `.env.local` file:
+
+ ```bash .env.local
+ # Coinbase Commerce Product ID (from Step 1)
+ NEXT_PUBLIC_PRODUCT_ID=your_product_uuid_here
+
+ # Coinbase Developer Platform API Key
+ NEXT_PUBLIC_ONCHAINKIT_API_KEY=your_cdp_api_key_here
+
+ # Reown (WalletConnect) Project ID
+ NEXT_PUBLIC_WC_PROJECT_ID=your_walletconnect_project_id_here
+
+ # Disable Next.js telemetry (optional)
+ NEXT_TELEMETRY_DISABLED=1
+ ```
+
+
+ Never commit API keys to version control. Add `.env.local` to your `.gitignore` file.
+
+
+
+ Use descriptive variable names and keep them organized with comments for team members.
+
+
+
+
+
+ Set up Wagmi to prioritize smart wallets for better user experience.
+
+ Update your Wagmi configuration file (typically `src/app/wagmi.ts`):
+
+ ```typescript src/app/wagmi.ts
+ // ... existing Wagmi configuration
+
+ // After your useMemo() hook, add:
+ coinbaseWallet.preference = 'smartWalletOnly';
+ ```
+
+
+ This configuration ensures users connect with smart wallets, which provide enhanced security and better UX.
+
+
+
+
+
+ Configure the OnchainKit provider to connect with Base network and your CDP API key.
+
+ Update `src/app/components/OnchainProviders.tsx`:
+
+ ```typescript src/app/components/OnchainProviders.tsx
+ 'use client';
+ import { OnchainKitProvider } from '@coinbase/onchainkit';
+ import { RainbowKitProvider } from '@rainbow-me/rainbowkit';
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+ import type { ReactNode } from 'react';
+ import { base } from 'viem/chains';
+ import { WagmiProvider } from 'wagmi';
+ import { useWagmiConfig } from '../wagmi';
+
+ type Props = { children: ReactNode };
+ const queryClient = new QueryClient();
+
+ function OnchainProviders({ children }: Props) {
+ const wagmiConfig = useWagmiConfig();
+
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ );
+ }
+
+ export default OnchainProviders;
+ ```
+
+ Update your configuration file to handle environment variables properly:
+
+ ```typescript Config.ts
+ export const NEXT_PUBLIC_URL =
+ process.env.NODE_ENV === 'development'
+ ? 'http://localhost:3000'
+ : 'https://your-app-domain.vercel.app'; // Replace with your actual domain
+
+ export const NEXT_PUBLIC_CDP_API_KEY =
+ process.env.NEXT_PUBLIC_ONCHAINKIT_API_KEY;
+
+ export const NEXT_PUBLIC_WC_PROJECT_ID =
+ process.env.NEXT_PUBLIC_WC_PROJECT_ID;
+ ```
+
+
+ Using environment variables makes your app more secure and easier to deploy across different environments.
+
+
+
+
+
+ Create an attractive payment interface that showcases your product and handles the checkout flow.
+
+ Update your main page (`src/app/page.tsx`):
+
+ ```typescript src/app/page.tsx
+ import { Checkout, CheckoutButton, CheckoutStatus } from '@coinbase/onchainkit/checkout';
+ import Image from 'next/image';
+
+ const productId = process.env.NEXT_PUBLIC_PRODUCT_ID;
+
+ export default function PaymentPage() {
+ return (
+
+
+ {/* Product showcase section */}
+
+
+ {/* Product image container */}
+
+
+ {/* Product information */}
+
+
Your Amazing Product
+
High-quality product with crypto payment support
+
+
+ {/* Payment section */}
+
+ {address ? (
+
+
+
+
+ ) : (
+
+ )}
+
+
+
+
+ );
+ }
+ ```
+
+
+ Make sure to add your product image to the `public` folder and update the image path accordingly.
+
+
+
+ The conditional rendering prevents errors when no wallet is connected, providing a smooth user experience.
+
+
+
+
+
+ Test your payment flow locally before deploying to production.
+
+ **Local Testing:**
+
+
+ ```bash Bun
+ bun run dev
+ ```
+
+ ```bash npm
+ npm run dev
+ ```
+
+ ```bash Yarn
+ yarn dev
+ ```
+
+
+ 1. **Visit** `http://localhost:3000`
+ 2. **Connect your wallet** using the wallet connection button
+ 3. **Test the checkout flow** with a small amount
+ 4. **Verify payment status** updates correctly
+
+
+ 
+
+
+ **Production Deployment:**
+
+ 1. **Update your configuration** with production URLs
+ 2. **Deploy to your preferred platform** (Vercel, Netlify, etc.)
+ 3. **Test the live application** with real transactions
+ 4. **Monitor payment confirmations** in your Coinbase Commerce dashboard
+
+
+ Start with testnet transactions to ensure everything works before going live with mainnet.
+
+
+
+
+## Troubleshooting
+
+
+
+ **Common solutions:**
+ - Ensure your WalletConnect project ID is correctly configured
+ - Check that your wallet extension is updated to the latest version
+ - Try connecting with a different wallet provider
+ - Clear your browser cache and cookies
+
+
+
+ **Check these items:**
+ - Verify your product ID matches exactly with Coinbase Commerce
+ - Ensure your CDP API key has the necessary permissions
+ - Confirm the user has sufficient funds for the transaction
+ - Check network connectivity and blockchain status
+
+
+
+ **Configuration checklist:**
+ - All required environment variables are set in `.env.local`
+ - No extra spaces or quotes around variable values
+ - File is in the project root directory
+ - Restart your development server after making changes
+
+
+
+## Next Steps
+
+Now that you have crypto payments working, consider these enhancements:
+
+
+
+ Scale your setup to handle multiple products and services
+
+
+ Set up payment confirmation webhooks for automated processing
+
+
+ Track payment metrics and customer behavior
+
+
+ Accept payments on multiple blockchain networks
+
+
+
+## Conclusion
+
+Congratulations! You've successfully integrated Coinbase Commerce and OnchainKit into your application. Your users can now make crypto payments, giving you access to:
+
+✅ **Zero traditional payment fees and chargebacks**
+✅ **Global customer reach with 24/7 payment processing**
+✅ **Instant settlement with blockchain confirmation**
+✅ **Enhanced security through smart wallet integration**
+
+**Ready to scale?** Consider expanding to multiple products, implementing automated fulfillment, or adding analytics to track your crypto payment performance.
+
+
+Your application now supports the future of global payments. Happy building on Base!
+
diff --git a/docs/cookbook/ai-prompting.mdx b/docs/cookbook/ai-prompting.mdx
new file mode 100644
index 00000000..4b3e5fcc
--- /dev/null
+++ b/docs/cookbook/ai-prompting.mdx
@@ -0,0 +1,10 @@
+---
+sidebarTitle: AI-powered IDEs
+title: Use AI-powered IDEs
+description: How to use AI-powered IDEs to generate code for OnchainKit.
+---
+
+import AiPowered from "/snippets/ai-powered.mdx";
+
+
+
diff --git a/docs/cookbook/base-builder-mcp.mdx b/docs/cookbook/base-builder-mcp.mdx
new file mode 100644
index 00000000..c243c3f6
--- /dev/null
+++ b/docs/cookbook/base-builder-mcp.mdx
@@ -0,0 +1,10 @@
+---
+sidebarTitle: 'Prompt Library'
+title: Developer's Guide to Effective AI Prompting
+description: Learn practical AI prompting techniques to enhance your coding workflow and get better results from AI coding assistants.
+---
+
+
+import AiPrompt from "/snippets/prompt-library.mdx";
+
+
\ No newline at end of file
diff --git a/docs/cookbook/defi-your-app.mdx b/docs/cookbook/defi-your-app.mdx
new file mode 100644
index 00000000..e538b8cf
--- /dev/null
+++ b/docs/cookbook/defi-your-app.mdx
@@ -0,0 +1,326 @@
+---
+title: Defi Your App
+description: Add common financial features like token swaps and yield generating strategies to your app with pre-built React components from OnchainKit.q
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+When businesses and individuals make financial transactions, it often includes swapping assets and then storing eligible assets in a yield generating strategy. This guide will show you how to quickly add these features to your app with pre-built React components from OnchainKit.
+
+## Ready-to-use components
+- [` `](/onchainkit/swap/swap): Swap assets directly within your app.
+- [` `](/onchainkit/earn/earn): Generate yield directly within your app.
+- [` `](/onchainkit/fund/fund-card): Fund their wallets with fiat (via USDC, Apple Pay, or debit card) without leaving your app.
+- [` `](/onchainkit/buy/buy): Purchase tokens directly within your app.
+
+
+By embedding the `Swap` and `Earn` components, your users don't need to leave your app to execute these common actions. For users who lack onchain funds, the `Fund` and `Buy` components offer an integrated fiat-to-crypto onramp.
+
+
+## Swap Component Integration
+
+The `Swap` component lets users exchange one token for another directly in your application. It fetches live quotes, builds transactions, and executes swaps—abstracting the underlying complexity.
+
+Lets add the `Swap` component to your app.
+
+
+
+ Create a new OnchainKit app
+
+ ```bash Terminal
+ npm create onchain@latest
+ ```
+
+
+ ```typescript App.tsx
+ import { SwapDefault } from '@coinbase/onchainkit/swap'; // [!code focus]
+ import type { Token } from '@coinbase/onchainkit/token';
+
+ const eth: Token = {
+ name: 'ETH',
+ address: '',
+ symbol: 'ETH',
+ decimals: 18,
+ image:
+ 'https://wallet-api-production.s3.amazonaws.com/uploads/tokens/eth_288.png',
+ chainId: 8453,
+ };
+
+ const usdc: Token = {
+ name: 'USDC',
+ address: '0x833589fcd6edb6e08f4c7c32d4f71b54bda02913',
+ symbol: 'USDC',
+ decimals: 6,
+ image:
+ 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/44/2b/442b80bd16af0c0d9b22e03a16753823fe826e5bfd457292b55fa0ba8c1ba213-ZWUzYjJmZGUtMDYxNy00NDcyLTg0NjQtMWI4OGEwYjBiODE2',
+ chainId: 8453,
+ };
+
+ // [!code focus]
+ ```
+
+
+
+You should now see the following swap component in your app:
+
+
+
+{/*
+
+ {({ address, swappableTokens }) => {
+ if (address) {
+ return (
+
+ )
+ }
+ return <>
+
+
+
+
+
+
+ >;
+ }}
+
+ */}
+
+The Swap component uses **Uniswap V3** as the default router, but you can also use the **0x Aggregator** by setting the `experimental.useAggregator` prop to `true`.
+
+
+
+### Swap Settings
+The Swap component comes preconfigured but is highly customizable. Just a couple of the settings you can customize:
+- Swap settings
+- bidirectional or unidirectional swaps
+- Gasless swaps with paymasters
+
+To learn more about how to customize the Swap component, check out the [Swap docs](/onchainkit/swap/swap).
+
+
+## Earn Component Integration
+
+The `Earn` component enables users to deposit assets into yield-generating vaults and withdraw them later—all within your app. The `Earn` component currently supports Morpho vaults on Base.
+
+
+**Get a vault address**
+
+You can get a vault address from Morpho's [Vaults page](https://app.morpho.org/base/earn). You will use this address when setting up the `Earn` component.
+
+
+
+```tsx twoslash
+// @noErrors: 2307
+import { Earn } from '@coinbase/onchainkit/earn'; // [!code focus]
+
+ // [!code focus]
+```
+
+Just like that, you've added a yield-generating vault to your app.
+
+
+
+{/* */}
+
+### Advanced Customizations
+Similar to the Swap component, the Earn component is highly customizable. Lets customize our component to include custom deposit buttons for a streamlined user experience.
+
+- `useEarnContext` to access the component's context values, `EarnDeposit` and `EarnDetails`
+- `DepositButton` to render custom deposit buttons
+
+
+```tsx index.tsx
+// @noErrors: 2307
+import { Earn, useEarnContext } from '@coinbase/onchainkit/earn';
+import { CustomDepositButtons } from '@/custom-deposit-buttons';
+
+
+
+
+
+```
+
+```tsx custom-deposit-buttons.tsx
+import {EarnDetails,
+ EarnDeposit,
+ useEarnContext,
+ DepositButton} from '@coinbase/onchainkit/earn';
+
+const predefinedAmounts = ['0.1', '1', '10'];
+
+function CustomDepositButtons() {
+ const { depositAmount, setDepositAmount } = useEarnContext();
+
+ return (
+
+
+
+ {predefinedAmounts.map((amount) => {
+ const selected = amount === depositAmount;
+ return (
+ setDepositAmount(amount)}
+ className={`rounded-md px-4 py-2
+ ${selected ? 'bg-[var(--ock-bg-primary)] text-[var(--ock-text-inverse)]'
+ : 'bg-[var(--ock-bg-secondary)] text-[var(--ock-text-primary)]'}`}
+ >
+ {amount}
+
+ );
+ })}
+
+
+
+ );
+}
+```
+
+
+
+
+{/* */}
+
+## Onboarding Users in DeFi
+
+In order to leverage the ` ` and ` ` components, users need to have funds in their wallet. If user's don't have funds, they'll need to onramp fiat or buy tokens in order to transact. We'll explore two out-of-the-box solutions from OnchainKit below.
+
+The `Fund` component (via ` `) offers a complete fiat onramp experience, allowing users to add funds to their wallet directly in your app. It provides:
+- Amount input with fiat/crypto switching
+- Payment method selection (Coinbase, Apple Pay, Debit Card)
+- Automatic exchange rate updates
+- Smart handling of payment method restrictions (based on country and subdivision)
+
+
+To use the `FundCard` component, you'll need to provide a Client API Key in `OnchainKitProvider`. You can get one following our [Getting Started](/onchainkit/installation/nextjs#get-your-client-api-key) steps.
+
+
+
+```tsx App.tsx
+import { FundCard } from '@coinbase/onchainkit/fund';
+
+ ;
+```
+
+
+
+{/*
+
+ {({ address }) => {
+ if (address) {
+ return ;
+ }
+ return (
+ <>
+
+
+
+
+
+
+ >
+ );
+ }}
+
+ */}
+
+To learn more about the `FundCard` component and its features, check out the [FundCard docs](/onchainkit/fund/fund-card).
+
+## The `Buy` Component
+
+The `Buy` components provide a comprehensive interface for users to purchase [Tokens](/onchainkit/token/types#token).
+
+The `Buy` component supports token swaps from USDC and ETH by default with the option to provide an additional token of choice using the `fromToken` prop. Users are able to purchase tokens using their Coinbase account, Apple Pay, or debit card.
+
+
+This component requires a `projectId` to be set in the `OnchainKitProvider`. You can find your `projectId` on [Coinbase Developer Platform](https://portal.cdp.coinbase.com/products/onchainkit).
+
+
+
+```tsx twoslash
+import { Buy } from '@coinbase/onchainkit/buy'; // [!code focus]
+import type { Token } from '@coinbase/onchainkit/token';
+
+export default function BuyComponents() { // [!code focus]
+ const degenToken: Token = {
+ name: 'DEGEN',
+ address: '0x4ed4e862860bed51a9570b96d89af5e1b0efefed',
+ symbol: 'DEGEN',
+ decimals: 18,
+ image:
+ 'https://d3r81g40ycuhqg.cloudfront.net/wallet/wais/3b/bf/3bbf118b5e6dc2f9e7fc607a6e7526647b4ba8f0bea87125f971446d57b296d2-MDNmNjY0MmEtNGFiZi00N2I0LWIwMTItMDUyMzg2ZDZhMWNm',
+ chainId: 8453,
+ };
+
+ return ( // [!code focus]
+ // [!code focus]
+ ) // [!code focus]
+} // [!code focus]
+
+```
+
+
+
+{/*
+
+ {({ address, toToken }) => {
+ return (
+
+ )
+ }}
+
+ */}
+
+
+**Note: This interface is for demonstration purposes only.**
+
+Swap and Onramp flows will execute and work out of the box when you implement the component in your own app.
+
+
+
+By combining Swap, Earn, Fund, and Buy with these additional tools, you can quickly build a robust in-app DeFi experience with minimal development overhead. This lets you focus on building your unique value proposition.
+
+
+## Next Steps
+
+If you're using these components, its likely you'll benefit from the following components:
+
+- [` `](/onchainkit/transaction/transaction): Provides a high-level transaction interface for executing custom onchain transactions.
+
+
+- [` `](/onchainkit/token/token-chip): Offers utilities for token selection and display, ideal for building wallet-like interfaces.
+
+
+- [` `](/onchainkit/wallet/wallet-island): An advanced, draggable wallet widget that consolidates wallet management (QR code, buy options, swap, portfolio view) in one interface.
+
+
+### Go Gasless
+For the ` ` and ` ` components, you can enable gasless transactions by setting the [`isSponsored`](/onchainkit/buy/buy#sponsor-gas-with-paymaster) prop to `true`.
diff --git a/docs/cookbook/defi/access-real-time-asset-data.mdx b/docs/cookbook/defi/access-real-time-asset-data.mdx
new file mode 100644
index 00000000..dfd2b3f7
--- /dev/null
+++ b/docs/cookbook/defi/access-real-time-asset-data.mdx
@@ -0,0 +1,7 @@
+---
+sidebarTitle: 'Access Real-Time Asset Data'
+title: Access Real-Time Asset Data (Pyth)
+description: 'Guide to accessing real-time asset data using Pyth Network'
+---
+
+[Guide for integrating Pyth Network to access real-time asset price data in DeFi applications]
\ No newline at end of file
diff --git a/docs/cookbook/defi/access-real-world-data.mdx b/docs/cookbook/defi/access-real-world-data.mdx
new file mode 100644
index 00000000..2ed43ea8
--- /dev/null
+++ b/docs/cookbook/defi/access-real-world-data.mdx
@@ -0,0 +1,7 @@
+---
+title: Access Real-World Data (Chainlink)
+sidebarTitle: 'Access Real-World Data'
+description: 'Guide to accessing real-world data using Chainlink oracles'
+---
+
+[Guide for integrating Chainlink oracles to access real-world data in DeFi applications]
\ No newline at end of file
diff --git a/docs/cookbook/defi/add-in-app-funding.mdx b/docs/cookbook/defi/add-in-app-funding.mdx
new file mode 100644
index 00000000..01009f4a
--- /dev/null
+++ b/docs/cookbook/defi/add-in-app-funding.mdx
@@ -0,0 +1,7 @@
+---
+sidebarTitle: 'Add In-App Onramp'
+title: Add In-App Funding (Onramp)
+description: 'Guide to implementing in-app funding using onramps'
+---
+
+[Guide for implementing in-app funding solutions using onramps in DeFi applications]
\ No newline at end of file
diff --git a/docs/cookbook/deploy-a-chain.mdx b/docs/cookbook/deploy-a-chain.mdx
new file mode 100644
index 00000000..4f8ff591
--- /dev/null
+++ b/docs/cookbook/deploy-a-chain.mdx
@@ -0,0 +1,286 @@
+---
+title: 'Deploy an Appchain'
+keywords: ['deploy a chain', 'deploy a base chain', 'deploy a base appchain', 'deploy a base l3', 'l3']
+---
+
+Transform your high-traffic application into a dedicated blockchain with **1-second block times**, **sub-cent transactions**, and **enterprise-grade infrastructure**. Base Appchains provide dedicated blockspace for mature applications that need to scale beyond shared network limitations.
+
+
+
+Get early access to Base Appchains and transform your scaling strategy
+
+
+
+Jump to our step-by-step onboarding process
+
+
+
+## What Are Base Appchains?
+
+Base Appchains are **app-specific Layer 3 rollups** built on Base that provide dedicated blockspace for individual applications. Instead of competing with thousands of other apps for network resources, you get your own high-performance blockchain that rolls up to Base and inherits Ethereum's security.
+
+
+Layer 3 appchains roll up to Base (Layer 2), which settles on Ethereum, providing you with dedicated performance while maintaining the security guarantees of the Ethereum ecosystem.
+
+
+Think of it as the difference between **sharing a highway during rush hour** versus **having your own dedicated express lane**. With shared blockspace, your app's performance depends on network-wide activity. With an Appchain, you get consistent, predictable performance regardless of what's happening elsewhere.
+
+
+
+Compete with other apps for network resources, leading to variable performance, unpredictable costs, and user experience issues during peak times.
+
+
+
+Your own infrastructure with predictable performance, custom gas tokens, full control over throughput, and consistent user experience.
+
+
+
+## Why Choose Base Appchains?
+
+### High-Speed Performance Built for Scale
+
+Stop letting network congestion impact your user experience. Base Appchains deliver **1-second block times** and **sub-10 second withdrawals**, making them 10x faster than typical blockchain interactions.
+
+
+Gaming applications like Blocklords have processed over 80 million transactions across 1.8 million wallets using Base Appchains, achieving the scale needed for their gaming ecosystem without performance degradation.
+
+
+### Predictable, Cost-Effective Operations
+
+Replace unpredictable gas costs with **fixed monthly pricing**. Process transactions for fractions of cents while eliminating the need to sponsor gas costs for your users.
+
+
+
+Variable gas costs, expensive user onboarding, unpredictable operational expenses, and complex gas sponsorship management.
+
+
+
+Fixed monthly pricing, sub-cent transactions, predictable budgeting, and no gas sponsorship complexity.
+
+
+
+### Enterprise-Grade Infrastructure
+
+With Base Appchains, you get:
+
+
+Fully-managed sequencer and node infrastructure
+
+
+Automated maintenance and upgrades
+
+
+Real-time monitoring and performance alerts
+
+
+Dedicated block explorer for your chain
+
+
+### Seamless Base Ecosystem Integration
+
+Maintain access to Base's **users**, **liquidity**, and **developer tools** while getting dedicated performance. Your Appchain integrates seamlessly with Smart Wallet, Paymaster, OnchainKit, and other Base ecosystem tools.
+
+
+
+Enable seamless account abstraction across Base Mainnet and your Appchain with unified user experiences.
+
+
+
+Sponsor gas costs across multiple chains with unified billing and simplified user onboarding.
+
+
+
+Use the same familiar developer tools and components across the entire Base ecosystem.
+
+
+
+## Technical Architecture
+
+Base Appchains are built on the **OP Enclave framework**, providing fast withdrawals and seamless integration with Base Mainnet. This architecture enables near-instant bridging while maintaining security through innovative proving mechanisms.
+
+
+
+Built on Optimism's latest technology for **near-instant bridging** between your Appchain and Base Mainnet. Users can move funds in seconds rather than the typical 7-day withdrawal periods of traditional rollups.
+
+
+
+Uses **Amazon S3** for cost-efficient data storage while maintaining security through **AWS Nitro Enclave** verification. This approach significantly reduces costs while ensuring data integrity and availability.
+
+
+
+Control which contracts can be called on your chain, effectively managing blockspace allocation. Implement **custom gas tokens** and **permission systems** while protecting users from censorship through guaranteed deposit lanes.
+
+
+
+Unlike traditional rollups that rely on challenge periods, Base Appchains use **immediate state proving** through secure enclaves, enabling instant finality and faster user experiences.
+
+
+
+## Use Cases & Success Stories
+
+Base Appchains power applications across gaming, DeFi, and enterprise sectors that require high performance and predictable costs.
+
+
+
+Process millions of micro-transactions for in-game actions, NFT trades, and player interactions without network congestion affecting gameplay performance.
+
+**Success Story**: Super Champs chose Base Appchains for consistent throughput, comparing the experience to "gaming on iOS" - smooth, predictable, and reliable.
+
+
+
+Handle high-frequency trading, yield farming, and complex financial operations with consistent, low-cost transactions that don't fluctuate with network activity.
+
+**Success Story**: Applications processing high-volume trading data benefit from predictable costs and dedicated throughput for time-sensitive operations.
+
+
+
+Deploy compliance-ready solutions with dedicated infrastructure, custom permissions, and the ability to manage access controls while maintaining transparency.
+
+**Success Story**: Proof 8 uses blockchain technology for verifiable inventory ownership in warehouses and distilleries, where enterprise customers prioritize performance, security, and privacy.
+
+
+
+
+Base Appchains are designed for **mature projects** with significant transaction volumes. If you're just starting out or have low transaction volumes, consider building on Base Mainnet first to establish product-market fit.
+
+
+## When Should You Consider an Appchain?
+
+Base Appchains are ideal for applications that have outgrown shared blockspace limitations. Use this checklist to determine if an Appchain is right for your project:
+
+
+**High Transaction Volume**: Your app generates thousands of transactions daily and performance is affected by network congestion
+
+
+
+**Significant Gas Sponsorship**: You're spending substantial amounts sponsoring gas costs for users through Paymaster or similar solutions
+
+
+
+**Performance-Critical Operations**: User experience is negatively impacted by variable transaction times or network congestion
+
+
+
+**Custom Requirements**: You need custom gas tokens, specialized permissions, or governance mechanisms not available on shared chains
+
+
+
+**Predictable Costs**: Fixed operational costs are important for your business model and budgeting
+
+
+
+If you're considering launching your own L1 or L2 blockchain, Base Appchains offer a compelling alternative with faster time-to-market, proven infrastructure, and immediate access to Base's ecosystem.
+
+
+
+## Getting Started
+
+Base Appchains are currently in **beta with a waitlist**. We're working with select partners to refine the platform before broader availability.
+
+
+
+Complete our application form to be considered for early access to Base Appchains. We prioritize applications from mature projects with clear scaling needs.
+
+
+Join the Base Appchains beta program
+
+
+
+
+Our team will review your application and schedule a consultation to understand your specific scaling requirements, transaction patterns, and technical needs.
+
+
+During the consultation, we'll help you determine if an Appchain is the right solution and design the optimal configuration for your use case.
+
+
+
+
+Once approved, you'll receive access to both testnet and mainnet features. Start with the **$1/month testnet** to validate your architecture and integration.
+
+
+Use the testnet environment to test bridging, custom gas tokens, and permission systems before deploying to mainnet.
+
+
+
+
+Deploy to production with full enterprise support, monitoring, and maintenance included. Our team provides ongoing support for your Appchain infrastructure.
+
+
+Full technical support during launch and ongoing operations
+
+
+
+
+
+**Coming Soon**: Self-serve, one-click deployment will be available for approved projects, making it even easier to launch and manage your Appchain.
+
+
+## Frequently Asked Questions
+
+
+
+Base Appchains offer significant advantages over launching independent blockchain infrastructure:
+
+- **Faster time-to-market**: Deploy in weeks, not months or years
+- **Proven infrastructure**: Built on battle-tested Base and OP Stack technology
+- **Immediate ecosystem access**: Users and liquidity from Base Mainnet
+- **Lower operational overhead**: Fully managed infrastructure and maintenance
+- **Ethereum alignment**: Inherit security without custom validator sets or consensus mechanisms
+
+
+
+Base Appchains provide **seamless onboarding** similar to Base Mainnet applications:
+
+- Users can bridge funds between Base and your Appchain in **seconds**
+- Same wallet experience across Base ecosystem
+- Smart Wallet integration for account abstraction
+- Familiar transaction patterns and interfaces
+
+
+
+**Coinbase manages the core infrastructure** on your behalf, including:
+
+- Sequencer operation and maintenance
+- Node infrastructure and upgrades
+- Security key management
+- Monitoring and alerting systems
+
+You maintain control over **application-level configurations** like gas tokens, permissions, and governance while benefiting from enterprise-grade infrastructure management.
+
+
+
+Base Appchains use the **OP Enclave framework** for **near-instant bridging**:
+
+- Move funds between Base and your Appchain in seconds
+- No 7-day withdrawal periods like traditional rollups
+- Maintains security through cryptographic proofs
+- Seamless user experience across chains
+
+
+
+Base Appchains balance **operational efficiency** with **censorship resistance**:
+
+- Custom permissions control high-throughput operations
+- **Guaranteed deposit lanes** prevent censorship through direct Base deposits
+- Users always have recourse through the Base bridge
+- Inherits Ethereum's long-term decentralization roadmap
+
+
+
+## Ready to Scale?
+
+Base Appchains represent the next evolution in blockchain scaling, providing dedicated infrastructure without sacrificing ecosystem benefits. Whether you're processing millions of gaming transactions, handling high-frequency DeFi operations, or building enterprise solutions, Appchains deliver the performance and predictability your users expect.
+
+
+
+Apply for early access to Base Appchains beta program
+
+
+
+Discover the full ecosystem of Base developer tools and integrations
+
+
+
+
+**Next Steps**: After joining the waitlist, explore Base's developer documentation to understand how Appchains integrate with Smart Wallet, Paymaster, OnchainKit, and other ecosystem tools.
+
\ No newline at end of file
diff --git a/docs/cookbook/go-gasless.mdx b/docs/cookbook/go-gasless.mdx
new file mode 100644
index 00000000..9bd29ada
--- /dev/null
+++ b/docs/cookbook/go-gasless.mdx
@@ -0,0 +1,406 @@
+---
+sidebarTitle: Go Gasless
+title: 'Gasless Transactions on Base using a Paymaster'
+description: Learn how to leverage the Base Paymaster for seamless, gasless transactions on the Coinbase Cloud Developer Platform.
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+Base transaction fees are typically less than a penny, but the concept of gas can still be confusing for new users and lead to poor user experience when users don't have gas funds in their wallet. You can abstract this away and improve your UX by using the **Base Paymaster**. The Paymaster allows you to:
+
+- Batch multi-step transactions
+- Create custom gasless experiences
+- Sponsor up to $15k monthly on mainnet (unlimited on testnet)
+
+
+If you need an increase in your sponsorship limit, please [reach out on Discord][Discord]!
+
+
+
+## Objectives
+
+1. Configure security measures to ensure safe and reliable transactions.
+2. Manage and allocate resources for sponsored transactions.
+3. Subsidize transaction fees for users, enhancing the user experience by making transactions free.
+4. Set up and manage sponsored transactions on various schedules, including weekly, monthly, and daily cadences.
+
+## Prerequisites
+
+This tutorial assumes you have:
+
+1. **A Coinbase Cloud Developer Platform Account**
+ If not, sign up on the [CDP site]. Once you have your account, you can manage projects and utilize tools like the Paymaster.
+
+2. **Familiarity with Smart Accounts and ERC 4337**
+ Smart Accounts are the backbone of advanced transaction patterns (e.g., bundling, sponsorship). If you’re new to ERC 4337, check out external resources like the official [EIP-4337 explainer](https://eips.ethereum.org/EIPS/eip-4337) before starting.
+
+3. **Foundry**
+ Foundry is a development environment, testing framework, and smart contract toolkit for Ethereum. You’ll need it installed locally for generating key pairs and interacting with smart contracts.
+
+
+**Testnet vs. Mainnet**
+If you prefer not to spend real funds, you can switch to **Base Sepolia** (testnet). The steps below are conceptually the same. Just select _Base Sepolia_ in the Coinbase Developer Platform instead of _Base Mainnet_, and use a contract deployed on Base testnet for your allowlisted methods.
+
+
+
+## Set Up a Base Paymaster & Bundler
+
+In this section, you will configure a Paymaster to sponsor payments on behalf of a specific smart contract for a specified amount.
+
+1. **Navigate to the [Coinbase Developer Platform].**
+2. Create or select your project from the upper left corner of the screen.
+3. Click on the **Paymaster** tool from the left navigation.
+4. Go to the **Configuration** tab and copy the **RPC URL** to your clipboard — you’ll need this shortly in your code.
+
+### Screenshots
+
+- **Selecting your project**
+
+ 
+
+
+- **Navigating to the Paymaster tool**
+
+ 
+
+
+- **Configuration screen**
+
+ 
+
+
+### Allowlist a Sponsorable Contract
+
+1. From the Configuration page, ensure **Base Mainnet** (or **Base Sepolia** if you’re testing) is selected.
+2. Enable your paymaster by clicking the toggle button.
+3. Click **Add** to add an allowlisted contract.
+4. For this example, add [`0x83bd615eb93eE1336acA53e185b03B54fF4A17e8`][simple NFT contract], and add the function `mintTo(address)`.
+
+
+
+
+
+
+**Use your own contract**
+ We use a [simple NFT contract][simple NFT contract] on Base mainnet as an example. Feel free to substitute your own.
+
+
+
+### Global & Per User Limits
+
+Scroll down to the **Per User Limit** section. You can set:
+
+- **Dollar amount limit** or **number of UserOperations** per user
+- **Limit cycles** that reset daily, weekly, or monthly
+
+For example, you might set:
+
+- `max USD` to `$0.05`
+- `max UserOperation` to `1`
+
+This means **each user** can only have $0.05 in sponsored gas and **1** user operation before the cycle resets.
+
+
+**Limit Cycles**
+These reset based on the selected cadence (daily, weekly, monthly).
+
+
+
+Next, **set the Global Limit**. For example, set this to `$0.07` so that once the entire paymaster has sponsored \$0.07 worth of gas (across all users), no more sponsorship occurs unless you raise the limit.
+
+
+
+
+
+## Test Your Paymaster Policy
+
+Now let’s verify that these policies work. We’ll:
+
+1. Create two local key pairs (or use private keys you own).
+2. Generate two Smart Accounts.
+3. Attempt to sponsor multiple transactions to see your policy in action.
+
+### Installing Foundry
+
+1. Ensure you have **Rust** installed
+ ```bash
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+ ```
+2. Install Foundry
+ ```bash
+ curl -L https://foundry.paradigm.xyz | bash
+ foundryup
+ ```
+3. Verify it works
+ ```bash
+ cast --help
+ ```
+ If you see Foundry usage info, you’re good to go!
+
+### Create Your Project & Generate Key Pairs
+
+1. Make a new folder and install dependencies, `viem` and `permissionless`:
+ ```bash
+ mkdir sponsored_transactions
+ cd sponsored_transactions
+ npm init es6
+ npm install permissionless
+ npm install viem
+ touch index.js
+ ```
+2. Generate two key pairs with Foundry:
+ ```bash
+ cast wallet new
+ cast wallet new
+ ```
+ You’ll see something like:
+ ```bash
+ Successfully created new keypair.
+ Address: 0xD440D746...
+ Private key: 0x01c9720c1dfa3c9...
+ ```
+ **Store these private keys somewhere safe**
+
+### Project Structure With Environment Variables
+
+Create a `.env` file in the `sponsored_transactions` directory. In the `.env`, you'll add the rpcURL for your paymaster and the private keys for your accounts:
+
+
+**Find your Paymaster & Bundler endpoint**
+
+The Paymaster & Bundler endpoint is the URL for your Coinbase Developer Platform (CDP) Paymaster.
+This was saved in the previous section and follows this format: `https://api.developer.coinbase.com/rpc/v1/base/`
+Navigate to the [Paymaster Tool] and select the `Configuration` tab at the top of the screen to obtain your RPC URL.
+
+
+
+
+**Secure your endpoints**
+
+You will create a constant for our Paymaster & Bundler endpoint obtained from cdp.portal.coinbase.com. The most secure way to do this is by using a proxy. For the purposes of this demo, hardcode it into our `index.js` file. For product, we highly recommend using a [proxy service].
+
+
+
+```bash
+PAYMASTER_RPC_URL=https://api.developer.coinbase.com/rpc/v1/base/
+PRIVATE_KEY_1=0x01c9720c1dfa3c9...
+PRIVATE_KEY_2=0xbcd6fbc1dfa3c9...
+```
+
+
+Never commit `.env` files to a public repo!
+
+
+
+## Example `index.js`
+
+Below is a full example of how you might structure `index.js`.
+
+```js index.js
+// --- index.js ---
+// @noErrors
+
+// 1. Import modules and environment variables
+import 'dotenv/config';
+import { http, createPublicClient, encodeFunctionData } from 'viem';
+import { base } from 'viem/chains';
+import { createSmartAccountClient } from 'permissionless';
+import { privateKeyToSimpleSmartAccount } from 'permissionless/accounts';
+import { createPimlicoPaymasterClient } from 'permissionless/clients/pimlico';
+
+// 2. Retrieve secrets from .env
+// Highlight: environment variables for paymaster, private keys
+const rpcUrl = process.env.PAYMASTER_RPC_URL; // highlight
+const firstPrivateKey = process.env.PRIVATE_KEY_1; // highlight
+const secondPrivateKey = process.env.PRIVATE_KEY_2; // highlight
+
+// 3. Declare Base addresses (entrypoint & factory)
+const baseEntryPoint = '0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789';
+const baseFactoryAddress = '0x15Ba39375ee2Ab563E8873C8390be6f2E2F50232';
+
+// 4. Create a public client for Base
+const publicClient = createPublicClient({
+ chain: base,
+ transport: http(rpcUrl),
+});
+
+// 5. Setup Paymaster client
+const cloudPaymaster = createPimlicoPaymasterClient({
+ chain: base,
+ transport: http(rpcUrl),
+ entryPoint: baseEntryPoint,
+});
+
+// 6. Create Smart Accounts from the private keys
+async function initSmartAccounts() {
+ const simpleAccount = await privateKeyToSimpleSmartAccount(publicClient, {
+ privateKey: firstPrivateKey,
+ factoryAddress: baseFactoryAddress,
+ entryPoint: baseEntryPoint,
+ });
+
+ const simpleAccount2 = await privateKeyToSimpleSmartAccount(publicClient, {
+ privateKey: secondPrivateKey,
+ factoryAddress: baseFactoryAddress,
+ entryPoint: baseEntryPoint,
+ });
+
+ // 7. Create SmartAccountClient for each
+ const smartAccountClient = createSmartAccountClient({
+ account: simpleAccount,
+ chain: base,
+ bundlerTransport: http(rpcUrl),
+ middleware: {
+ sponsorUserOperation: cloudPaymaster.sponsorUserOperation,
+ },
+ });
+
+ const smartAccountClient2 = createSmartAccountClient({
+ account: simpleAccount2,
+ chain: base,
+ bundlerTransport: http(rpcUrl),
+ middleware: {
+ sponsorUserOperation: cloudPaymaster.sponsorUserOperation,
+ },
+ });
+
+ return { smartAccountClient, smartAccountClient2 };
+}
+
+// 8. ABI for the NFT contract
+const nftAbi = [
+ // ...
+ // truncated for brevity
+];
+
+// 9. Example function to send a transaction from a given SmartAccountClient
+async function sendTransaction(client, recipientAddress) {
+ try {
+ // encode the "mintTo" function call
+ const callData = encodeFunctionData({
+ abi: nftAbi,
+ functionName: 'mintTo',
+ args: [recipientAddress], // highlight: specify who gets the minted NFT
+ });
+
+ const txHash = await client.sendTransaction({
+ account: client.account,
+ to: '0x83bd615eb93eE1336acA53e185b03B54fF4A17e8', // address of the NFT contract
+ data: callData,
+ value: 0n,
+ });
+
+ console.log(`✅ Transaction successfully sponsored for ${client.account.address}`);
+ console.log(`🔍 View on BaseScan: https://basescan.org/tx/${txHash}`);
+ } catch (error) {
+ console.error('Transaction failed:', error);
+ }
+}
+
+// 10. Main flow: init accounts, send transactions
+(async () => {
+ const { smartAccountClient, smartAccountClient2 } = await initSmartAccounts();
+
+ // Send a transaction from the first account
+ await sendTransaction(smartAccountClient, smartAccountClient.account.address);
+
+ // Send a transaction from the second account
+ // For variety, let’s also mint to the second account's own address
+ await sendTransaction(smartAccountClient2, smartAccountClient2.account.address);
+})();
+```
+
+Now that the code is implemented, lets run it:
+Run this via `node index.js` from your project root.
+
+```bash
+node index.js
+```
+
+You should see a "Transaction successfully sponsored" output.
+
+To confirm that your spend policies are correctly in place, try running the script again. If your Paymaster settings are strict (e.g., limit 1 transaction per user), the second time you run the script, you may get a “request denied” error, indicating the policy is working.
+
+## Hitting Policy Limits & Troubleshooting
+
+1. **Per-User Limit**
+ If you see an error like:
+
+ ```json
+ {
+ "code": -32001,
+ "message": "request denied - rejected due to maximum per address transaction count reached"
+ }
+ ```
+
+ That means you’ve hit your **UserOperation** limit for a single account. Return to the [Coinbase Developer Platform] UI to adjust the policy.
+
+2. **Global Limit**
+ If you repeatedly run transactions and eventually see:
+ ```json
+ {
+ "code": -32001,
+ "message": "request denied - rejected due to max global usd spend limit reached"
+ }
+ ```
+ You’ve hit the **global** limit of sponsored gas. Increase it in the CDP dashboard and wait a few minutes for changes to take effect.
+
+## Verifying Token Ownership (Optional)
+
+Want to confirm the token actually minted? You can read the NFT’s `balanceOf` function:
+
+```js
+import { readContract } from 'viem'; // highlight
+
+// example function
+async function checkNftBalance(publicClient, contractAddress, abi, ownerAddress) {
+ const balance = await publicClient.readContract({
+ address: contractAddress,
+ abi,
+ functionName: 'balanceOf',
+ args: [ownerAddress],
+ });
+ console.log(`NFT balance of ${ownerAddress} is now: ${balance}`);
+}
+```
+
+## Conclusion
+
+In this tutorial, you:
+
+- Set up and **configured** a Base Paymaster on the Coinbase Developer Platform.
+- **Allowlisted** a contract and specific function (`mintTo`) for sponsorship.
+- Established **per-user** and **global** sponsorship **limits** to control costs.
+- Demonstrated the **sponsorship flow** with Smart Accounts using `permissionless`, `viem`, and Foundry-generated private keys.
+
+This approach can greatly improve your dApp’s user experience by removing gas friction. For more complex sponsorship schemes (like daily or weekly cycles), simply tweak your per-user and global limit settings in the Coinbase Developer Platform.
+
+> **Next Steps**
+>
+> - Use a [proxy service][proxy service] for better endpoint security.
+> - Deploy your own contracts and allowlist them.
+> - Experiment with bundling multiple calls into a single sponsored transaction.
+
+## References
+
+- [list of factory addresses]
+- [Discord]
+- [CDP site]
+- [Coinbase Developer Platform]
+- [UI]
+- [proxy service]
+- [Paymaster Tool]
+- [Foundry Book installation guide]
+- [simple NFT contract]
+
+[list of factory addresses]: https://docs.alchemy.com/reference/factory-addresses
+[Discord]: https://discord.com/invite/cdp
+[CDP site]: https://portal.cdp.coinbase.com/
+[Coinbase Developer Platform]: https://portal.cdp.coinbase.com/
+[UI]: https://portal.cdp.coinbase.com/products/bundler-and-paymaster
+[proxy service]: https://www.smartwallet.dev/guides/paymasters
+[Paymaster Tool]: https://portal.cdp.coinbase.com/products/bundler-and-paymaster
+[Foundry Book installation guide]: https://book.getfoundry.sh/getting-started/installation
+[simple NFT contract]: https://basescan.org/token/0x83bd615eb93ee1336aca53e185b03b54ff4a17e8
+
+**Happy Building on Base!**
diff --git a/docs/cookbook/growth/cast-actions.mdx b/docs/cookbook/growth/cast-actions.mdx
new file mode 100644
index 00000000..a477eb2d
--- /dev/null
+++ b/docs/cookbook/growth/cast-actions.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Cast Actions'
+description: 'Guide to implementing Cast Actions for growth'
+---
+
+[Guide for implementing and utilizing Cast Actions to grow your application]
\ No newline at end of file
diff --git a/docs/cookbook/growth/deploy-to-vercel.mdx b/docs/cookbook/growth/deploy-to-vercel.mdx
new file mode 100644
index 00000000..0e370596
--- /dev/null
+++ b/docs/cookbook/growth/deploy-to-vercel.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Deploy to Vercel'
+description: 'Guide to deploying your application on Vercel'
+---
+
+[Step-by-step guide for deploying your Base application to Vercel]
\ No newline at end of file
diff --git a/docs/cookbook/growth/email-campaigns.mdx b/docs/cookbook/growth/email-campaigns.mdx
new file mode 100644
index 00000000..f3027cc6
--- /dev/null
+++ b/docs/cookbook/growth/email-campaigns.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Email Campaigns'
+description: 'Set up and manage email campaigns'
+---
+
+[Guide for setting up and managing email campaigns for user engagement]
\ No newline at end of file
diff --git a/docs/cookbook/growth/gating-and-redirects.mdx b/docs/cookbook/growth/gating-and-redirects.mdx
new file mode 100644
index 00000000..10810c6f
--- /dev/null
+++ b/docs/cookbook/growth/gating-and-redirects.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Gating and Redirects'
+description: 'Implement access control and redirects'
+---
+
+[Guide for implementing content gating and smart redirects in your application]
\ No newline at end of file
diff --git a/docs/cookbook/growth/hyperframes.mdx b/docs/cookbook/growth/hyperframes.mdx
new file mode 100644
index 00000000..54a83b21
--- /dev/null
+++ b/docs/cookbook/growth/hyperframes.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Hyperframes'
+description: 'Implement Hyperframes for enhanced engagement'
+---
+
+[Guide for implementing Hyperframes to enhance user engagement and distribution]
\ No newline at end of file
diff --git a/docs/cookbook/growth/retaining-users.mdx b/docs/cookbook/growth/retaining-users.mdx
new file mode 100644
index 00000000..1a34fad4
--- /dev/null
+++ b/docs/cookbook/growth/retaining-users.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Retaining Users'
+description: 'Strategies and implementations for user retention'
+---
+
+[Guide for implementing user retention strategies in your application]
\ No newline at end of file
diff --git a/docs/cookbook/launch-ai-agents.mdx b/docs/cookbook/launch-ai-agents.mdx
new file mode 100644
index 00000000..07a1cc93
--- /dev/null
+++ b/docs/cookbook/launch-ai-agents.mdx
@@ -0,0 +1,460 @@
+---
+title: 'Launch AI Agents on Base'
+description: 'Learn how to build and deploy autonomous AI agents on Base with access to stablecoins, tokens, NFTs, and onchain actions using CDP AgentKit.'
+---
+
+import LangChainReplitInstructions from '/snippets/ai-instructions/langchain-replit.mdx';
+import LangChainLocalInstructions from '/snippets/ai-instructions/langchain-local.mdx';
+import ElizaInstructions from '/snippets/ai-instructions/eliza.mdx';
+
+AI Agents become exponentially more powerful when they're onchain! By deploying AI agents on Base, you unlock access to **stablecoins**, **tokens**, **NFTs**, and a vast ecosystem of **DeFi protocols**. This significantly increases their autonomy and the universe of tasks they can perform, from automated trading to complex multi-step financial operations.
+
+## What You'll Build
+
+By the end of this guide, you'll have deployed a fully functional AI agent that can:
+
+- **Execute onchain transactions** autonomously
+- **Interact with DeFi protocols** for trading and liquidity
+- **Manage digital assets** including tokens and NFTs
+- **Respond to market conditions** with intelligent automation
+
+
+
+ Agents can execute transactions, trade tokens, and interact with smart contracts
+
+
+ Access to DEXs, lending protocols, and yield farming opportunities
+
+
+ Manage portfolios, NFT collections, and cross-chain assets
+
+
+ Real-time market analysis and automated trading strategies
+
+
+
+## Prerequisites
+
+Before launching your AI agent, ensure you have the following setup:
+
+
+
+ Choose your preferred development environment and ensure you have the necessary tools installed.
+
+ **For Local Development:**
+ - Node.js 18+ or Python 3.10+
+ - Git for repository management
+ - Code editor (VS Code recommended)
+
+ **For Replit (Browser-based):**
+ - Replit account for cloud development
+ - No local installation required
+
+
+ Replit is perfect for quick prototyping, while local development gives you more control and flexibility.
+
+
+
+
+ Gather the required API keys for your AI agent:
+
+ - **Coinbase Developer Platform (CDP) API Key**: [Get your CDP credentials](https://www.coinbase.com/cloud)
+ - **OpenAI API Key**: [Create an OpenAI account](https://platform.openai.com/api-keys) for AI capabilities
+ - **Base Network Access**: Your agent will operate on Base Sepolia testnet initially
+
+
+ Store all API keys securely and never commit them to version control. Use environment variables for all sensitive credentials.
+
+
+
+
+ Choose the framework that best fits your needs:
+
+ - **LangChain**: Full-featured framework with extensive integrations
+ - **Eliza**: Lightweight, fast setup for simple agents
+
+
+ Each framework has different strengths. LangChain offers more customization, while Eliza provides rapid deployment.
+
+
+
+
+## Choose Your Agent Framework
+
+Select the framework and environment that best matches your development preferences and project requirements:
+
+
+
+ **LangChain** provides a comprehensive framework for building sophisticated AI agents with extensive tooling and integrations.
+
+
+ **Best for:** Complex agents requiring custom tools, advanced reasoning, and extensive integrations with external services.
+
+
+ ### Development Environment
+
+
+
+ Perfect for getting started quickly without local setup requirements.
+
+
+
+
+ **Replit Advantages:**
+ - No local environment setup required
+ - Built-in collaboration features
+ - Automatic deployment and hosting
+ - Great for learning and prototyping
+
+
+
+
+ Recommended for production applications and advanced customization.
+
+
+
+
+ **Local Development Advantages:**
+ - Full control over your environment
+ - Better performance for intensive operations
+ - Easier integration with existing toolchains
+ - Enhanced security for production applications
+
+
+
+
+ ### Advanced LangChain Features
+
+ Once your basic agent is running, you can enhance it with:
+
+
+
+ ```python
+ # Example: Custom DeFi interaction tool
+ from cdp_langchain.tools import CdpTool
+
+ class CustomDeFiTool(CdpTool):
+ def __init__(self):
+ super().__init__(
+ name="defi_analyzer",
+ description="Analyze DeFi opportunities on Base"
+ )
+ ```
+
+
+
+ ```python
+ # Add memory to your agent
+ from langchain.memory import ConversationBufferWindowMemory
+
+ memory = ConversationBufferWindowMemory(
+ k=10, # Remember last 10 interactions
+ return_messages=True
+ )
+ ```
+
+
+
+ ```python
+ # Coordinate multiple specialized agents
+ trading_agent = create_trading_agent()
+ portfolio_agent = create_portfolio_agent()
+
+ # Orchestrate agents for complex strategies
+ coordinator = AgentCoordinator([trading_agent, portfolio_agent])
+ ```
+
+
+
+
+
+ **Eliza** offers the fastest path to deploying AI agents with minimal configuration and maximum speed.
+
+
+ **Best for:** Quick deployments, simple autonomous agents, and rapid prototyping with immediate results.
+
+
+
+
+ ### Eliza Framework Benefits
+
+
+
+ Get your agent running in under 5 minutes with the CLI tool
+
+
+ Pre-configured templates for common agent patterns
+
+
+ Native support for popular onchain actions and APIs
+
+
+ Access to pre-built agent templates from the community
+
+
+
+ ### Extending Your Eliza Agent
+
+
+
+ Extend your agent's capabilities by adding custom onchain actions:
+
+ ```typescript
+ // Add to your agent's action registry
+ export const customActions = [
+ {
+ name: "YIELD_FARM",
+ description: "Automatically farm yield on Base protocols",
+ handler: async (params) => {
+ // Your yield farming logic
+ }
+ }
+ ];
+ ```
+
+
+
+ Implement automated trading strategies:
+
+ ```typescript
+ export const tradingConfig = {
+ strategies: ["DCA", "MOMENTUM", "ARBITRAGE"],
+ riskLevel: "MODERATE",
+ maxSlippage: 0.01
+ };
+ ```
+
+
+
+ Add monitoring and alerts for your agent:
+
+ ```typescript
+ export const monitoringConfig = {
+ alerts: {
+ lowBalance: true,
+ failedTx: true,
+ profitTarget: 0.05
+ }
+ };
+ ```
+
+
+
+
+
+## Testing Your AI Agent
+
+Before deploying to mainnet, thoroughly test your agent's capabilities:
+
+
+
+ **Test all functions on Base Sepolia testnet:**
+
+ 1. **Wallet Operations**: Create wallets, check balances, transfer tokens
+ 2. **DeFi Interactions**: Test swaps, liquidity provision, lending
+ 3. **NFT Operations**: Mint, transfer, and trade NFTs
+ 4. **Error Handling**: Ensure graceful handling of failed transactions
+
+
+ Your agent should handle all basic operations without errors before proceeding.
+
+
+
+
+ **Evaluate agent performance under various conditions:**
+
+ - Response time to market changes
+ - Transaction success rates
+ - Gas optimization effectiveness
+ - Resource utilization
+
+
+ Monitor your agent's performance metrics to identify optimization opportunities.
+
+
+
+
+ **Verify security measures are in place:**
+
+ - API keys are properly secured
+ - Wallet private keys are encrypted
+ - Rate limiting is implemented
+ - Transaction limits are configured
+
+
+ Never deploy to mainnet without proper security auditing. Consider professional security reviews for high-value operations.
+
+
+
+
+## Deployment and Monitoring
+
+
+
+ **Deploy your agent to a production environment:**
+
+
+
+ ```bash
+ # Deploy to cloud provider
+ npm run build
+ npm run deploy:production
+
+ # Set production environment variables
+ export CDP_API_KEY_NAME="your_production_key"
+ export NETWORK_ID="base-mainnet"
+ ```
+
+
+
+ ```bash
+ # Set up production server
+ pm2 start ecosystem.config.js
+ pm2 startup
+ pm2 save
+
+ # Configure monitoring
+ pm2 install pm2-logrotate
+ ```
+
+
+
+
+ Ensure your deployment includes proper logging, monitoring, and backup systems.
+
+
+
+
+ **Set up comprehensive monitoring:**
+
+ - **Transaction Monitoring**: Track success rates and gas usage
+ - **Performance Metrics**: Monitor response times and throughput
+ - **Financial Tracking**: Watch portfolio performance and P&L
+ - **System Health**: Monitor server resources and uptime
+
+
+ Use tools like Grafana, DataDog, or custom dashboards to visualize your agent's performance.
+
+
+
+
+## Troubleshooting
+
+
+
+ **Common solutions:**
+ - Check API key validity and permissions
+ - Verify network connectivity to Base RPC endpoints
+ - Ensure sufficient gas funds in agent wallet
+ - Review agent logs for error messages
+ - Confirm OpenAI API quota and rate limits
+
+
+
+ **Debug transaction issues:**
+ - Verify sufficient token balance for operations
+ - Check gas price settings and network congestion
+ - Confirm smart contract addresses are correct
+ - Review transaction simulation results
+ - Validate slippage tolerance settings
+
+
+
+ **Optimize agent performance:**
+ - Implement request batching for multiple operations
+ - Use connection pooling for database operations
+ - Cache frequently accessed data
+ - Optimize trading frequency to reduce gas costs
+ - Review and tune AI model parameters
+
+
+
+ **Enhance security measures:**
+ - Rotate API keys regularly
+ - Implement transaction signing verification
+ - Set up multi-signature requirements for large transactions
+ - Enable wallet spending limits
+ - Monitor for unusual activity patterns
+
+
+
+## Advanced Use Cases
+
+Explore advanced patterns for sophisticated AI agents:
+
+
+
+ Build agents that identify and execute arbitrage opportunities across DEXs
+
+
+ Create agents that rebalance portfolios based on market conditions
+
+
+ Deploy agents that automatically find and compound the best yields
+
+
+ Develop agents that analyze and trade NFTs based on market trends
+
+
+
+## Next Steps
+
+Expand your AI agent's capabilities:
+
+
+
+ Connect your agent to more DeFi protocols:
+ - **Uniswap** for advanced trading strategies
+ - **Aave** for lending and borrowing
+ - **Compound** for yield generation
+ - **1inch** for optimal trade routing
+
+
+
+ Build sophisticated trading and management strategies:
+ - Dollar-cost averaging (DCA) algorithms
+ - Mean reversion trading
+ - Momentum-based strategies
+ - Risk-adjusted portfolio rebalancing
+
+
+
+ Extend your agent across multiple chains:
+ - **Ethereum** for additional DeFi access
+ - **Polygon** for low-cost operations
+ - **Arbitrum** for advanced trading
+ - Cross-chain bridging automation
+
+
+
+## Resources and Community
+
+
+
+ Complete documentation for building agents with CDP
+
+
+ Connect with other developers building AI agents on Base
+
+
+ Access source code, examples, and contribute to the project
+
+
+ Watch step-by-step tutorials for agent development
+
+
+
+## Conclusion
+
+Congratulations! You've successfully launched an AI agent on Base with full onchain capabilities. Your agent can now:
+
+✅ **Execute autonomous transactions** with real-world financial impact
+✅ **Interact with DeFi protocols** for advanced financial operations
+✅ **Manage digital assets** including tokens and NFTs
+✅ **Respond intelligently** to market conditions and opportunities
+
+**Ready for production?** Consider implementing advanced monitoring, security audits, and gradual scaling as your agent proves its effectiveness in live markets.
+
+
+Your AI agent is now part of the onchain economy, ready to operate 24/7 in the world of decentralized finance. Happy building on Base!
+
diff --git a/docs/cookbook/launch-tokens.mdx b/docs/cookbook/launch-tokens.mdx
new file mode 100644
index 00000000..aa3402cd
--- /dev/null
+++ b/docs/cookbook/launch-tokens.mdx
@@ -0,0 +1,423 @@
+---
+title: 'Launch a Token'
+---
+
+
+Launching a token on Base can be accomplished through multiple approaches, from no-code platforms to custom smart contract development. This guide helps you choose the right method and provides implementation details for both approaches.
+
+
+
+ Use existing platforms like Zora, Clanker, or Flaunch for quick deployment
+
+
+ Build custom ERC-20 tokens with Foundry for maximum control
+
+
+ Decision framework to help you pick the right method
+
+
+ Security, community building, and post-launch guidance
+
+
+
+
+**For most users:** Use existing token launch platforms like Zora, Clanker, or Flaunch. These tools handle the technical complexity while providing unique features for different use cases.
+
+**For developers:** Build custom ERC-20 tokens using Foundry and OpenZeppelin's battle-tested contracts for maximum control and customization.
+
+
+## Choosing Your Launch Approach
+
+### Platform-Based Launch (Recommended for Most Users)
+
+Choose a platform when you want:
+- Quick deployment without coding
+- Built-in community features
+- Automated liquidity management
+- Social integration capabilities
+
+### Custom Development (For Developers)
+
+Build your own smart contract when you need:
+- Custom tokenomics or functionality
+- Full control over contract behavior
+- Integration with existing systems
+- Advanced security requirements
+
+## Token Launch Platforms on Base
+
+### Zora
+**Best for:** Content creators and social tokens
+
+Zora transforms every post into a tradeable ERC-20 token with automatic Uniswap integration. Each post becomes a "coin" with 1 billion supply, creators receive 10 million tokens, and earn 1% of all trading fees.
+
+**Key Features:**
+- Social-first token creation
+- Automatic liquidity pools
+- Revenue sharing for creators
+- Built-in trading interface
+
+[Get started with Zora →](https://zora.co)
+
+### Clanker
+**Best for:** Quick memecoin launches via social media
+
+Clanker is an AI-driven token deployment tool that operates through Farcaster. Users can create ERC-20 tokens on Base by simply tagging @clanker with their token concept.
+
+**Key Features:**
+- AI-powered automation
+- Social media integration via Farcaster
+- Instant deployment
+- Community-driven discovery
+
+[Get started with Clanker →](https://warpcast.com) or visit [clanker.world](https://clanker.world)
+
+### Flaunch
+**Best for:** Advanced memecoin projects with sophisticated tokenomics
+
+Flaunch leverages Uniswap V4 to enable programmable revenue splits, automated buybacks, and Progressive Bid Walls for price support. Creators can customize fee distributions and treasury management.
+
+**Key Features:**
+- Programmable revenue sharing
+- Automated buyback mechanisms
+- Progressive Bid Wall technology
+- Treasury management tools
+
+[Get started with Flaunch →](https://flaunch.gg)
+
+## Technical Implementation with Foundry
+
+For developers who want full control over their token implementation, here's how to create and deploy a custom ERC-20 token on Base using Foundry.
+
+
+Before launching a custom developed token to production, always conduct security reviews by expert smart contract developers.
+
+
+### Prerequisites
+
+
+
+ Install Foundry on your system:
+ ```bash Terminal
+ curl -L https://foundry.paradigm.xyz | bash
+ foundryup
+ ```
+ For detailed installation instructions, see the [Foundry documentation](https://book.getfoundry.sh/getting-started/installation).
+
+
+ Obtain Base Sepolia ETH for testing from the [Base Faucet](https://docs.base.org/docs/tools/network-faucets)
+
+
+ Configure your wallet and development tools for Base testnet deployment
+
+
+
+### Project Setup
+
+Initialize a new Foundry project and clean up template files:
+
+```bash Terminal
+# Create new project
+forge init my-token-project
+cd my-token-project
+
+# Remove template files we don't need
+rm src/Counter.sol script/Counter.s.sol test/Counter.t.sol
+```
+
+Install OpenZeppelin contracts for secure, audited ERC-20 implementation:
+
+```bash Terminal
+# Install OpenZeppelin contracts library
+forge install OpenZeppelin/openzeppelin-contracts
+```
+
+### Smart Contract Development
+
+Create your token contract using OpenZeppelin's ERC-20 implementation:
+
+```solidity src/MyToken.sol
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+
+/**
+ * @title MyToken
+ * @dev ERC-20 token with minting capabilities and supply cap
+ */
+contract MyToken is ERC20, Ownable {
+ // Maximum number of tokens that can ever exist
+ uint256 public constant MAX_SUPPLY = 1_000_000_000 * 10**18; // 1 billion tokens
+
+ constructor(
+ string memory name,
+ string memory symbol,
+ uint256 initialSupply,
+ address initialOwner
+ ) ERC20(name, symbol) Ownable(initialOwner) {
+ require(initialSupply <= MAX_SUPPLY, "Initial supply exceeds max supply");
+ // Mint initial supply to the contract deployer
+ _mint(initialOwner, initialSupply);
+ }
+
+ /**
+ * @dev Mint new tokens (only contract owner can call this)
+ * @param to Address to mint tokens to
+ * @param amount Amount of tokens to mint
+ */
+ function mint(address to, uint256 amount) public onlyOwner {
+ require(totalSupply() + amount <= MAX_SUPPLY, "Minting would exceed max supply");
+ _mint(to, amount);
+ }
+
+ /**
+ * @dev Burn tokens from caller's balance
+ * @param amount Amount of tokens to burn
+ */
+ function burn(uint256 amount) public {
+ _burn(msg.sender, amount);
+ }
+}
+```
+
+### Deployment Script
+
+Create a deployment script following Foundry best practices:
+
+```solidity script/DeployToken.s.sol
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+import {Script, console} from "forge-std/Script.sol";
+import {MyToken} from "../src/MyToken.sol";
+
+contract DeployToken is Script {
+ function run() external {
+ // Load deployer's private key from environment variables
+ uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
+ address deployerAddress = vm.addr(deployerPrivateKey);
+
+ // Token configuration parameters
+ string memory name = "My Token";
+ string memory symbol = "MTK";
+ uint256 initialSupply = 100_000_000 * 10**18; // 100 million tokens
+
+ // Start broadcasting transactions
+ vm.startBroadcast(deployerPrivateKey);
+
+ // Deploy the token contract
+ MyToken token = new MyToken(
+ name,
+ symbol,
+ initialSupply,
+ deployerAddress
+ );
+
+ // Stop broadcasting transactions
+ vm.stopBroadcast();
+
+ // Log deployment information
+ console.log("Token deployed to:", address(token));
+ console.log("Token name:", token.name());
+ console.log("Token symbol:", token.symbol());
+ console.log("Initial supply:", token.totalSupply());
+ console.log("Deployer balance:", token.balanceOf(deployerAddress));
+ }
+}
+```
+
+### Environment Configuration
+
+Create a `.env` file with your configuration:
+
+```bash .env
+PRIVATE_KEY=your_private_key_here
+BASE_SEPOLIA_RPC_URL=https://sepolia.base.org
+BASE_MAINNET_RPC_URL=https://mainnet.base.org
+BASESCAN_API_KEY=your_basescan_api_key_here
+```
+
+Update `foundry.toml` for Base network configuration:
+
+```toml foundry.toml
+[profile.default]
+src = "src"
+out = "out"
+libs = ["lib"]
+remappings = ["@openzeppelin/=lib/openzeppelin-contracts/"]
+
+[rpc_endpoints]
+base_sepolia = "${BASE_SEPOLIA_RPC_URL}"
+base_mainnet = "${BASE_MAINNET_RPC_URL}"
+
+[etherscan]
+base_sepolia = { key = "${BASESCAN_API_KEY}", url = "https://api-sepolia.basescan.org/api" }
+base = { key = "${BASESCAN_API_KEY}", url = "https://api.basescan.org/api" }
+```
+
+### Testing
+
+Create comprehensive tests for your token:
+
+```solidity test/MyToken.t.sol
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+import {Test, console} from "forge-std/Test.sol";
+import {MyToken} from "../src/MyToken.sol";
+
+contract MyTokenTest is Test {
+ MyToken public token;
+ address public owner = address(0x1);
+ address public user = address(0x2);
+
+ uint256 constant INITIAL_SUPPLY = 100_000_000 * 10**18;
+
+ function setUp() public {
+ // Deploy token contract before each test
+ vm.prank(owner);
+ token = new MyToken("Test Token", "TEST", INITIAL_SUPPLY, owner);
+ }
+
+ function testInitialState() public {
+ // Verify token was deployed with correct parameters
+ assertEq(token.name(), "Test Token");
+ assertEq(token.symbol(), "TEST");
+ assertEq(token.totalSupply(), INITIAL_SUPPLY);
+ assertEq(token.balanceOf(owner), INITIAL_SUPPLY);
+ }
+
+ function testMinting() public {
+ uint256 mintAmount = 1000 * 10**18;
+
+ // Only owner should be able to mint
+ vm.prank(owner);
+ token.mint(user, mintAmount);
+
+ assertEq(token.balanceOf(user), mintAmount);
+ assertEq(token.totalSupply(), INITIAL_SUPPLY + mintAmount);
+ }
+
+ function testBurning() public {
+ uint256 burnAmount = 1000 * 10**18;
+
+ // Owner burns their own tokens
+ vm.prank(owner);
+ token.burn(burnAmount);
+
+ assertEq(token.balanceOf(owner), INITIAL_SUPPLY - burnAmount);
+ assertEq(token.totalSupply(), INITIAL_SUPPLY - burnAmount);
+ }
+
+ function testFailMintExceedsMaxSupply() public {
+ // This test should fail when trying to mint more than max supply
+ uint256 excessiveAmount = token.MAX_SUPPLY() + 1;
+
+ vm.prank(owner);
+ token.mint(user, excessiveAmount);
+ }
+
+ function testFailUnauthorizedMinting() public {
+ // This test should fail when non-owner tries to mint
+ vm.prank(user);
+ token.mint(user, 1000 * 10**18);
+ }
+}
+```
+
+Run your tests:
+
+```bash Terminal
+# Run all tests with verbose output
+forge test -vv
+```
+
+### Deployment and Verification
+
+Deploy to Base Sepolia testnet:
+
+```bash Terminal
+# Load environment variables
+source .env
+
+# Deploy to Base Sepolia with automatic verification
+forge script script/DeployToken.s.sol:DeployToken \
+ --rpc-url base_sepolia \
+ --broadcast \
+ --verify
+```
+
+
+The `--verify` flag automatically verifies your contract on BaseScan, making it easier for users to interact with your token.
+
+
+
+To deploy to Base Mainnet, simply change `base_sepolia` to `base_mainnet` in your deployment command. Ensure you have sufficient ETH on Base Mainnet for deployment and gas fees.
+
+
+## Post-Launch Considerations
+
+Once your token is deployed, here are the key next steps to consider:
+
+### Token Distribution and Economics
+
+Carefully consider your token's supply and distribution settings. Think through how tokens will be distributed to your community, team, and ecosystem participants. Consider factors like vesting schedules, allocation percentages, and long-term incentive alignment.
+
+### Community and Social Presence
+
+Establish a community and social presence around your token and project. This includes creating documentation, setting up social media accounts, engaging with the Base ecosystem, and building relationships with other projects and developers.
+
+### Liquidity and Trading
+
+Add liquidity to decentralized exchanges like Uniswap to enable trading. Note that token launchers will typically handle this for you automatically, but for custom deployments, you'll need to create trading pairs and provide initial liquidity.
+
+### Continued Development
+
+For comprehensive guidance on growing your project on Base, including marketing strategies, ecosystem integration, and growth tactics, visit the [Base Launch Playbook](https://www.launchonbase.xyz/).
+
+
+Remember to always prioritize security, transparency, and community value when developing and launching tokens. Consider conducting security audits and following best practices for token distribution.
+
+
+## Resources
+
+
+
+ Complete guide to getting started on Base
+
+
+ Technical specifications and network information
+
+
+ Comprehensive guide to using Foundry
+
+
+ Security-focused smart contract library
+
+
+ Explore transactions and contracts on Base
+
+
+ Get testnet ETH for development
+
+
+
+### Community
+
+
+
+ Join the Base community
+
+
+ Follow Base updates
+
+
+ Contribute to Base development
+
+
+
+---
+
+Whether you choose a platform-based approach for speed and convenience, or custom development for maximum control, Base provides a robust foundation for token launches. Start with the approach that best fits your technical expertise and project requirements, and leverage Base's growing ecosystem to build successful token projects.
\ No newline at end of file
diff --git a/docs/cookbook/nfts/complex-onchain-nfts.mdx b/docs/cookbook/nfts/complex-onchain-nfts.mdx
new file mode 100644
index 00000000..87e582e1
--- /dev/null
+++ b/docs/cookbook/nfts/complex-onchain-nfts.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Complex Onchain NFTs'
+description: 'Create advanced onchain NFTs'
+---
+
+[Guide for creating complex onchain NFTs with advanced features]
\ No newline at end of file
diff --git a/docs/cookbook/nfts/dynamic-nfts.mdx b/docs/cookbook/nfts/dynamic-nfts.mdx
new file mode 100644
index 00000000..ae65005a
--- /dev/null
+++ b/docs/cookbook/nfts/dynamic-nfts.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Dynamic NFTs'
+description: 'Create NFTs with dynamic properties'
+---
+
+[Guide for creating dynamic NFTs that can change over time]
\ No newline at end of file
diff --git a/docs/cookbook/nfts/nft-minting-zora.mdx b/docs/cookbook/nfts/nft-minting-zora.mdx
new file mode 100644
index 00000000..6d35f221
--- /dev/null
+++ b/docs/cookbook/nfts/nft-minting-zora.mdx
@@ -0,0 +1,6 @@
+---
+title: 'NFT Minting with Zora'
+description: 'Guide to minting NFTs using Zora protocol'
+---
+
+[Guide for minting NFTs using the Zora protocol on Base]
\ No newline at end of file
diff --git a/docs/cookbook/nfts/signature-mint.mdx b/docs/cookbook/nfts/signature-mint.mdx
new file mode 100644
index 00000000..c293c7ed
--- /dev/null
+++ b/docs/cookbook/nfts/signature-mint.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Signature Mint'
+description: 'Implement signature-based NFT minting'
+---
+
+[Guide for implementing signature-based NFT minting]
\ No newline at end of file
diff --git a/docs/cookbook/nfts/simple-onchain-nfts.mdx b/docs/cookbook/nfts/simple-onchain-nfts.mdx
new file mode 100644
index 00000000..252a6b4f
--- /dev/null
+++ b/docs/cookbook/nfts/simple-onchain-nfts.mdx
@@ -0,0 +1,619 @@
+---
+title: Simple Onchain NFTs
+sidebarTitle: Simple NFTs
+description: 'Create basic NFT collections with metadata stored fully onchain for permanence and decentralization'
+---
+
+Build NFT collections where all metadata lives onchain for true decentralization and permanence. This approach ensures your NFTs remain accessible forever, regardless of external hosting or IPFS availability.
+
+
+
+ Understand the advantages of storing metadata onchain
+
+
+ Deploy your onchain NFT contract
+
+
+ Structure your onchain metadata properly
+
+
+ Create a user-friendly minting experience
+
+
+
+## Why Onchain Metadata?
+
+Traditional NFTs store metadata on centralized servers or IPFS, creating potential points of failure. Onchain metadata ensures true permanence and decentralization.
+
+
+**Onchain vs Offchain Comparison**
+
+- **Onchain**: Metadata stored in smart contract, permanent, higher gas costs
+- **Offchain**: Metadata on IPFS/servers, lower gas costs, potential availability issues
+- **Hybrid**: Critical data onchain, detailed media offchain
+
+
+### Benefits of Onchain NFTs
+
+
+**Permanent Access**: Metadata exists as long as the blockchain exists
+
+
+**True Decentralization**: No reliance on external hosting services
+
+
+**Composability**: Other contracts can easily read and use metadata
+
+
+**Transparency**: All data is publicly verifiable onchain
+
+
+## Prerequisites
+
+Before building your onchain NFT collection:
+
+
+Understanding of Solidity and smart contract development
+
+
+Foundry or Hardhat development environment
+
+
+Base testnet ETH for deployment testing
+
+
+Basic knowledge of JSON and Base64 encoding
+
+
+## Smart Contract Implementation
+
+### Basic Onchain NFT Contract
+
+Here's a foundational contract that stores all metadata onchain:
+
+```solidity contracts/SimpleOnchainNFT.sol
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+import "@openzeppelin/contracts/utils/Base64.sol";
+
+contract SimpleOnchainNFT is ERC721, Ownable {
+ using Strings for uint256;
+
+ uint256 private _tokenIdCounter;
+ mapping(uint256 => NFTMetadata) private _tokenMetadata;
+
+ struct NFTMetadata {
+ string name;
+ string description;
+ string image; // Can be SVG data or onchain-generated art
+ string[] attributes;
+ }
+
+ event NFTMinted(address indexed to, uint256 indexed tokenId, string name);
+
+ constructor(address initialOwner)
+ ERC721("Simple Onchain NFT", "SONFT")
+ Ownable(initialOwner)
+ {}
+
+ function mint(
+ address to,
+ string memory name,
+ string memory description,
+ string memory image,
+ string[] memory attributes
+ ) public onlyOwner {
+ uint256 tokenId = _tokenIdCounter++;
+
+ _tokenMetadata[tokenId] = NFTMetadata({
+ name: name,
+ description: description,
+ image: image,
+ attributes: attributes
+ });
+
+ _safeMint(to, tokenId);
+ emit NFTMinted(to, tokenId, name);
+ }
+
+ function tokenURI(uint256 tokenId)
+ public
+ view
+ override
+ returns (string memory)
+ {
+ require(_exists(tokenId), "Token does not exist");
+
+ NFTMetadata memory metadata = _tokenMetadata[tokenId];
+
+ // Build JSON metadata
+ bytes memory json = abi.encodePacked(
+ '{"name":"', metadata.name,
+ '","description":"', metadata.description,
+ '","image":"', metadata.image,
+ '","attributes":[', _buildAttributes(metadata.attributes),
+ ']}'
+ );
+
+ // Encode as base64 data URI
+ return string(
+ abi.encodePacked(
+ "data:application/json;base64,",
+ Base64.encode(json)
+ )
+ );
+ }
+
+ function _buildAttributes(string[] memory attributes)
+ private
+ pure
+ returns (string memory)
+ {
+ if (attributes.length == 0) return "";
+
+ bytes memory result;
+ for (uint256 i = 0; i < attributes.length; i += 2) {
+ if (i > 0) result = abi.encodePacked(result, ",");
+
+ if (i + 1 < attributes.length) {
+ result = abi.encodePacked(
+ result,
+ '{"trait_type":"', attributes[i],
+ '","value":"', attributes[i + 1], '"}'
+ );
+ }
+ }
+ return string(result);
+ }
+
+ function getMetadata(uint256 tokenId)
+ public
+ view
+ returns (NFTMetadata memory)
+ {
+ require(_exists(tokenId), "Token does not exist");
+ return _tokenMetadata[tokenId];
+ }
+
+ function totalSupply() public view returns (uint256) {
+ return _tokenIdCounter;
+ }
+}
+```
+
+### Generative Art Contract
+
+For purely generative onchain art:
+
+```solidity contracts/GenerativeOnchainNFT.sol
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+import "@openzeppelin/contracts/utils/Strings.sol";
+import "@openzeppelin/contracts/utils/Base64.sol";
+
+contract GenerativeOnchainNFT is ERC721, Ownable {
+ using Strings for uint256;
+
+ uint256 private _tokenIdCounter;
+ uint256 public constant MAX_SUPPLY = 1000;
+ uint256 public constant MINT_PRICE = 0.01 ether;
+
+ // Colors for generative art
+ string[] private colors = ["red", "blue", "green", "purple", "orange", "yellow"];
+ string[] private shapes = ["circle", "square", "triangle", "diamond"];
+
+ constructor(address initialOwner)
+ ERC721("Generative Onchain Art", "GOA")
+ Ownable(initialOwner)
+ {}
+
+ function mint() public payable {
+ require(_tokenIdCounter < MAX_SUPPLY, "Max supply reached");
+ require(msg.value >= MINT_PRICE, "Insufficient payment");
+
+ uint256 tokenId = _tokenIdCounter++;
+ _safeMint(msg.sender, tokenId);
+ }
+
+ function tokenURI(uint256 tokenId)
+ public
+ view
+ override
+ returns (string memory)
+ {
+ require(_exists(tokenId), "Token does not exist");
+
+ // Generate deterministic traits based on tokenId
+ uint256 seed = uint256(keccak256(abi.encodePacked(tokenId, block.timestamp)));
+
+ string memory color = colors[seed % colors.length];
+ string memory shape = shapes[(seed >> 8) % shapes.length];
+ uint256 size = (seed % 100) + 50; // Size between 50-149
+
+ // Generate SVG
+ string memory svg = generateSVG(tokenId, color, shape, size);
+
+ // Build metadata JSON
+ bytes memory json = abi.encodePacked(
+ '{"name":"Generative Art #', tokenId.toString(),
+ '","description":"Fully onchain generative art",',
+ '"image":"data:image/svg+xml;base64,', Base64.encode(bytes(svg)),
+ '","attributes":[',
+ '{"trait_type":"Color","value":"', color, '"},',
+ '{"trait_type":"Shape","value":"', shape, '"},',
+ '{"trait_type":"Size","value":', size.toString(), '}',
+ ']}'
+ );
+
+ return string(
+ abi.encodePacked(
+ "data:application/json;base64,",
+ Base64.encode(json)
+ )
+ );
+ }
+
+ function generateSVG(
+ uint256 tokenId,
+ string memory color,
+ string memory shape,
+ uint256 size
+ ) private pure returns (string memory) {
+ string memory shapeElement;
+
+ if (keccak256(bytes(shape)) == keccak256(bytes("circle"))) {
+ shapeElement = string(
+ abi.encodePacked(
+ ' '
+ )
+ );
+ } else if (keccak256(bytes(shape)) == keccak256(bytes("square"))) {
+ uint256 x = 150 - (size / 2);
+ uint256 y = 150 - (size / 2);
+ shapeElement = string(
+ abi.encodePacked(
+ ' '
+ )
+ );
+ }
+ // Add more shapes as needed
+
+ return string(
+ abi.encodePacked(
+ '',
+ ' ',
+ '',
+ 'Generative Art #', tokenId.toString(),
+ ' ',
+ shapeElement,
+ ' '
+ )
+ );
+ }
+
+ function withdraw() external onlyOwner {
+ payable(owner()).transfer(address(this).balance);
+ }
+}
+```
+
+## Metadata Structure
+
+### Standard JSON Metadata
+
+Follow the OpenSea metadata standard for compatibility:
+
+```json metadata-example.json
+{
+ "name": "Simple Onchain NFT #1",
+ "description": "A beautiful NFT with metadata stored entirely onchain",
+ "image": "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzAwIi...",
+ "attributes": [
+ {
+ "trait_type": "Color",
+ "value": "Blue"
+ },
+ {
+ "trait_type": "Rarity",
+ "value": "Common"
+ },
+ {
+ "trait_type": "Size",
+ "value": "Large"
+ }
+ ]
+}
+```
+
+### SVG Image Generation
+
+Create dynamic SVG images in your smart contract:
+
+```solidity utils/SVGGenerator.sol
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+library SVGGenerator {
+ function generateBackground(string memory color)
+ internal
+ pure
+ returns (string memory)
+ {
+ return string(
+ abi.encodePacked(
+ ' '
+ )
+ );
+ }
+
+ function generateText(
+ string memory text,
+ uint256 x,
+ uint256 y,
+ string memory color
+ ) internal pure returns (string memory) {
+ return string(
+ abi.encodePacked(
+ '',
+ text,
+ ' '
+ )
+ );
+ }
+
+ function wrapSVG(string memory content)
+ internal
+ pure
+ returns (string memory)
+ {
+ return string(
+ abi.encodePacked(
+ '',
+ content,
+ ' '
+ )
+ );
+ }
+}
+```
+
+## Building Mint Interface
+
+### Next.js Minting App
+
+Create a user-friendly interface for minting:
+
+```typescript app/mint/page.tsx
+'use client'
+
+import { useState } from 'react'
+import { useAccount, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
+import { parseEther } from 'viem'
+
+const CONTRACT_ADDRESS = '0x...' // Your deployed contract address
+const ABI = [...] // Your contract ABI
+
+export default function MintPage() {
+ const { address, isConnected } = useAccount()
+ const [isLoading, setIsLoading] = useState(false)
+
+ const { writeContract, data: hash } = useWriteContract()
+
+ const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
+ hash,
+ })
+
+ const handleMint = async () => {
+ if (!isConnected) return
+
+ setIsLoading(true)
+
+ try {
+ writeContract({
+ abi: ABI,
+ address: CONTRACT_ADDRESS,
+ functionName: 'mint',
+ value: parseEther('0.01'),
+ })
+ } catch (error) {
+ console.error('Mint failed:', error)
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ return (
+
+
Mint Onchain NFT
+
+
+
+
Collection Details
+
+ Generative Onchain Art - Each NFT is unique and generated entirely onchain
+
+
+ Price: 0.01 ETH
+
+
+
+
+ {!isConnected ? (
+
+ Connect your wallet to mint
+
+ ) : (
+
+ {isLoading || isConfirming ? 'Minting...' : 'Mint NFT'}
+
+ )}
+
+ {isSuccess && (
+
+
+ NFT minted successfully! 🎉
+
+
+ )}
+
+ )
+}
+```
+
+### Display NFT Collection
+
+```typescript components/NFTGallery.tsx
+'use client'
+
+import { useState, useEffect } from 'react'
+import { useReadContract } from 'wagmi'
+
+interface NFT {
+ tokenId: number
+ name: string
+ image: string
+ attributes: Array<{ trait_type: string; value: string }>
+}
+
+export function NFTGallery({ contractAddress }: { contractAddress: string }) {
+ const [nfts, setNfts] = useState([])
+
+ const { data: totalSupply } = useReadContract({
+ abi: ABI,
+ address: contractAddress,
+ functionName: 'totalSupply',
+ })
+
+ useEffect(() => {
+ if (totalSupply) {
+ loadNFTs(Number(totalSupply))
+ }
+ }, [totalSupply])
+
+ const loadNFTs = async (supply: number) => {
+ const nftPromises = []
+
+ for (let i = 0; i < supply; i++) {
+ // Load token URI and parse metadata
+ // Implementation depends on your setup
+ }
+
+ const loadedNFTs = await Promise.all(nftPromises)
+ setNfts(loadedNFTs)
+ }
+
+ return (
+
+ {nfts.map((nft) => (
+
+
+
+
{nft.name}
+
+ {nft.attributes.map((attr, index) => (
+
+ {attr.trait_type}:
+ {attr.value}
+
+ ))}
+
+
+
+ ))}
+
+ )
+}
+```
+
+## Best Practices
+
+### Gas Optimization
+
+
+**Optimize Storage**
+
+Store only essential data onchain. Use efficient data structures and consider gas costs when designing your metadata schema.
+
+
+1. **Pack Data Efficiently**: Use structs and pack data to minimize storage slots
+2. **Lazy Generation**: Generate images and metadata on-demand in `tokenURI()`
+3. **Batch Operations**: Allow batch minting to reduce per-NFT costs
+4. **Optimize SVG**: Keep SVG code minimal and efficient
+
+### Security Considerations
+
+
+**Immutable Data**
+
+Remember that onchain data is permanent and immutable. Thoroughly test your generation logic before deployment.
+
+
+1. **Access Controls**: Implement proper access controls for minting functions
+2. **Input Validation**: Validate all inputs to prevent malformed metadata
+3. **Reentrancy Protection**: Use OpenZeppelin's ReentrancyGuard for payable functions
+4. **Audit Code**: Have your contracts audited before mainnet deployment
+
+## Deployment
+
+### Deploy to Base
+
+```bash Terminal
+# Using Foundry
+forge create --rpc-url https://mainnet.base.org \
+ --private-key $PRIVATE_KEY \
+ --verify \
+ contracts/SimpleOnchainNFT.sol:SimpleOnchainNFT \
+ --constructor-args $OWNER_ADDRESS
+```
+
+### Verify Contract
+
+```bash Terminal
+forge verify-contract \
+ --chain base \
+ --compiler-version v0.8.19 \
+ $CONTRACT_ADDRESS \
+ contracts/SimpleOnchainNFT.sol:SimpleOnchainNFT
+```
+
+## Resources
+
+
+
+ Secure smart contract building blocks
+
+
+ Onchain Base64 encoding utilities
+
+
+ Learn SVG for onchain graphics
+
+
+ NFT metadata standards
+
+
+
+---
+
+Onchain NFTs provide true digital ownership and permanence that can't be achieved with traditional hosted metadata. While they require more careful planning and higher gas costs, they offer unmatched durability and composability for your digital assets.
\ No newline at end of file
diff --git a/docs/cookbook/nfts/thirdweb-unreal-nft-items.mdx b/docs/cookbook/nfts/thirdweb-unreal-nft-items.mdx
new file mode 100644
index 00000000..07124250
--- /dev/null
+++ b/docs/cookbook/nfts/thirdweb-unreal-nft-items.mdx
@@ -0,0 +1,6 @@
+---
+title: 'ThirdWeb Unreal NFT Items'
+description: 'Create NFT items using ThirdWeb and Unreal Engine'
+---
+
+[Guide for creating NFT items using ThirdWeb and Unreal Engine]
\ No newline at end of file
diff --git a/docs/cookbook/onboard-any-user.mdx b/docs/cookbook/onboard-any-user.mdx
new file mode 100644
index 00000000..01b28960
--- /dev/null
+++ b/docs/cookbook/onboard-any-user.mdx
@@ -0,0 +1,143 @@
+---
+title: Onboard Any User
+sidebarTitle: Onboard User
+description: Learn how to seamlessly integrate the WalletModal component from OnchainKit to rapidly onboard new users with a smart wallet experience.
+---
+
+In onchain apps, the wallet is at the center of your user model. Onboarding requires users to connect an existing wallet or sign up for a new wallet. The [` `](/onchainkit/wallet/wallet-modal) component provides a drag-and-drop solution to handle wallet onboarding seamlessly to eliminate friction and churn.
+
+## How It Works
+
+The component offers:
+
+- Smooth onboarding for new users with guided Smart Wallet creation
+- Quick connection for existing wallets
+- Consistent handling of connection states with a responsive UI
+
+Lets add the `WalletModal` component to your app.
+
+
+
+ ```bash Terminal
+ npm create onchain@latest
+ ```
+
+
+ ```tsx providers.tsx
+
+ {children}
+
+ ```
+
+
+ ```tsx App.tsx
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+
+
+ Ensure that the Wallet Modal is globally accessible by wrapping your key UI components:
+
+ ```tsx
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ```
+
+
+
+
+{/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ */}
+
+You now have an interface to streamline user onboarding! The `WalletModal` component handles:
+
+- Smart wallet creation for new signups
+- Quick connection for existing wallets
+- Wallet connection states
+- Error handling
+- Mobile/desktop responsive behavior
+- Theme customization
+- Terms/privacy policy display
+
+## Why Smart Wallet for new signups?
+
+In addition to providing a secure and feature-rich wallet experience, Smart Wallets provide a streamlined onboarding experience through account creation with Passkeys. This allows anyone to securely create a new wallet and begin transacting without ever leaving your app.
+
+
+**More on Smart Wallets**
+
+Smart Wallet has advanced features such as sponsored transactions, spend permissions, and Magic Spend. [Learn more about Smart Wallet here](/smart-wallet/quickstart).
+
+
+
+## Conclusion
+
+By integrating ` `, you offer a robust and user-friendly wallet onboarding experience. First-time users benefit from a seamless Smart Wallet creation flow, while returning users can quickly connect their wallets to get started with your onchain app.
diff --git a/docs/cookbook/onchain-social.mdx b/docs/cookbook/onchain-social.mdx
new file mode 100644
index 00000000..c89364e1
--- /dev/null
+++ b/docs/cookbook/onchain-social.mdx
@@ -0,0 +1,374 @@
+---
+title: Onchain Social
+---
+
+In this guide, you'll discover the world of onchain social applications and learn how to build interactive Mini Apps that live directly inside social feeds, creating seamless user experiences without traditional platform limitations.
+
+## What Is Onchain Social?
+
+Traditional social media follows a familiar but limiting pattern: users sign in with email addresses, scroll through centralized feeds, and click links that redirect them away from the conversation. While this model works, it has fundamental limitations that onchain social aims to solve.
+
+
+
+ Platform-owned identity and content with limited user control
+
+
+ User-owned identity, programmable feeds, and embedded applications
+
+
+
+### The Onchain Social Paradigm
+
+Onchain social represents a fundamental shift in how we think about digital identity and social interactions:
+
+**Traditional Model Problems:**
+- Your identity, content, and interactions are owned by the platform
+- Building requires working on top of platforms, never inside them
+- Users must leave conversations to access new experiences
+- Developers are limited by platform APIs and restrictions
+
+**Onchain Social Solutions:**
+- **Portable Identity**: Your Farcaster ID (FID) belongs to you, not locked to any single platform
+- **Embedded Experiences**: Rich, interactive apps run natively inside posts and conversations
+- **Developer Freedom**: Build as first-class citizens within the social graph
+- **User Ownership**: Control your data, content, and social connections
+
+
+This isn't about replacing existing platforms—it's about removing the walls between users, developers, and the experiences they create together.
+
+
+## What You'll Build
+
+By the end of this guide, you'll have created a fully functional Mini App with these capabilities:
+
+
+
+ Polls, games, and collaborative tools that run inside social feeds
+
+
+ Apps that know who opened them and adapt accordingly
+
+
+ Direct access to wallets, DeFi protocols, and blockchain transactions
+
+
+ User-owned identity through Farcaster without separate logins
+
+
+
+## Mini Apps: The Interface for Onchain Social
+
+Mini Apps are lightweight, expressive web applications that live directly inside social feeds. They launch instantly without installation and provide rich, interactive experiences that respond to your onchain identity.
+
+**What makes Mini Apps special:**
+- **Embedded experiences** that don't redirect users away from conversations
+- **Social context awareness** - they know who opened them and from where
+- **Onchain identity integration** with automatic personalization
+- **Native feel** within social platforms
+
+**Use cases include:**
+- Interactive polls and real-time voting
+- Social games and entertainment
+- E-commerce with instant checkout
+- DeFi interfaces and portfolio management
+- NFT showcases and trading
+- Collaborative decision-making tools
+
+
+Mini Apps offer developers direct access to social distribution - you're building inside the conversation, not trying to pull users away from it.
+
+
+## Build Your First Mini App
+
+
+
+ The fastest way to build a Mini App is with MiniKit, which handles authentication, social context, and onchain integrations automatically.
+
+ **Create a new Mini App:**
+ ```bash
+ npx create-onchain --mini
+ cd your-mini-app-name
+ npm install
+ ```
+
+ **Start the development server:**
+ ```bash
+ npm run dev
+ ```
+
+
+ Your Mini App should now be running at `http://localhost:3000` with a fully functional social interface.
+
+
+ **What you get out of the box:**
+ - Complete frontend and backend scaffold
+ - Built-in support for Farcaster identity and notifications
+ - Native integrations with Base blockchain and OnchainKit
+ - Responsive design optimized for mobile social feeds
+ - Development tools for testing and debugging
+
+
+ The MiniKit scaffold includes example components and pages to help you understand the architecture quickly.
+
+
+
+
+ Mini Apps have access to rich social context that traditional web apps lack. This context makes personalization effortless and enables intelligent interactions.
+
+ **Access user and social context:**
+ ```tsx
+ import { useMiniKit } from "@coinbase/onchainkit/minikit";
+
+ export default function App() {
+ const { context } = useMiniKit();
+
+ // Verified user information (always available)
+ const userFid = context?.user?.fid;
+
+ // Additional user data (when available)
+ const username = context?.user?.username;
+ const displayName = context?.user?.displayName;
+ const pfpUrl = context?.user?.pfpUrl;
+
+ // Client information
+ const isAdded = context?.client?.added; // Whether user has added this Mini App
+ const location = context?.location; // Where the Mini App was launched from
+
+ return (
+
+
Welcome{displayName ? `, ${displayName}` : ''}!
+
Your FID: {userFid}
+ {username &&
Username: @{username}
}
+ {isAdded &&
✅ You've added this Mini App
}
+ {location &&
Launched from: {location}
}
+
+ );
+ }
+ ```
+
+ **Verified context properties:**
+ - **User identity**: FID (always available), username, display name, profile image URL
+ - **Client status**: Whether the user has added your Mini App to their favorites
+ - **Launch context**: General location information about where the Mini App was opened
+ - **Safe area insets**: For proper mobile layout within Farcaster clients
+
+
+ Always handle cases where context might be undefined, especially during development and testing.
+
+
+
+
+ Create engaging social experiences that leverage the Mini App's embedded nature:
+
+ **Example: Social Voting App**
+ ```tsx
+ import { useState, useEffect } from 'react';
+ import { useMiniKit } from "@coinbase/onchainkit/minikit";
+
+ export default function VotingApp() {
+ const { context } = useMiniKit();
+ const [votes, setVotes] = useState({ option1: 0, option2: 0 });
+ const [userVote, setUserVote] = useState(null);
+
+ const handleVote = async (option) => {
+ if (userVote) return; // Prevent double voting
+
+ // Submit vote to your backend
+ const response = await fetch('/api/vote', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ fid: context?.user?.fid,
+ option,
+ location: context?.location
+ })
+ });
+
+ if (response.ok) {
+ setUserVote(option);
+ // Update vote counts
+ setVotes(prev => ({
+ ...prev,
+ [option]: prev[option] + 1
+ }));
+ }
+ };
+
+ return (
+
+
What's your preference?
+
+
+ handleVote('option1')}
+ disabled={userVote}
+ className={userVote === 'option1' ? 'selected' : ''}
+ >
+ Option A ({votes.option1} votes)
+
+
+ handleVote('option2')}
+ disabled={userVote}
+ className={userVote === 'option2' ? 'selected' : ''}
+ >
+ Option B ({votes.option2} votes)
+
+
+
+ {userVote && (
+
Thanks for voting! Your choice: {userVote}
+ )}
+
+ );
+ }
+ ```
+
+
+ Use the user's FID to prevent duplicate votes and create personalized experiences without requiring separate authentication.
+
+
+
+
+ Enhance your Mini App with blockchain functionality using OnchainKit:
+
+ ```tsx
+ import {
+ Transaction,
+ TransactionButton,
+ TransactionStatus
+ } from '@coinbase/onchainkit/transaction';
+
+ export default function OnchainMiniApp() {
+ const { context } = useMiniKit();
+
+ const handleTransaction = async () => {
+ // Create transaction based on social context
+ const txData = {
+ to: '0x...',
+ value: '1000000000000000000', // 1 ETH
+ data: '0x...'
+ };
+
+ return txData;
+ };
+
+ return (
+
+
Onchain Social Action
+
Initiated by FID: {context?.user?.fid}
+
+
{
+ console.log('Transaction successful:', receipt);
+ }}
+ >
+
+
+
+
+ );
+ }
+ ```
+
+
+ Your Mini App can now trigger blockchain transactions directly from social interactions.
+
+
+
+
+ Deploy your Mini App to make it accessible within social feeds:
+
+ **Deploy to Vercel (recommended):**
+ ```bash
+ npm install -g vercel
+ vercel --prod
+ ```
+
+ **Test your Mini App:**
+ 1. **Local testing**: Use the MiniKit development tools
+ 2. **Frame testing**: Test as a Farcaster Frame
+ 3. **Social testing**: Deploy and test in actual social contexts
+ 4. **Performance testing**: Ensure fast loading in mobile environments
+
+
+ Always test your Mini App in actual social contexts before broad deployment, as the social environment can affect performance and user experience.
+
+
+
+
+## Convert Existing Apps to Mini Apps
+
+Transform your existing Next.js application into a Mini App without major restructuring. The process is straightforward and doesn't require rebuilding your entire application.
+
+
+Follow our comprehensive guide for integrating MiniKit into existing applications with step-by-step instructions, environment setup, and testing procedures.
+
+
+**Key integration steps:**
+- Install MiniKit as part of OnchainKit
+- Wrap your app with `MiniKitProvider`
+- Add social context integration to existing components
+- Configure environment variables and deployment
+
+
+For new projects, use the [MiniKit CLI](/builderkits/minikit/quickstart) for automatic setup with all features pre-configured.
+
+
+## Advanced MiniKit Features
+
+Once you have your basic Mini App running, explore advanced capabilities:
+
+
+
+ Send push notifications to users who have added your Mini App
+
+
+ Implement Farcaster authentication for secure, persistent sessions
+
+
+ Navigate users to Farcaster profiles and build social connections
+
+
+ Allow users to save your Mini App for easy access
+
+
+
+## Best Practices & Troubleshooting
+
+Building successful Mini Apps requires understanding social-specific patterns and common pitfalls:
+
+
+
+ Design patterns and best practices for building social Mini Apps
+
+
+ Common issues and solutions when developing Mini Apps
+
+
+ Optimize loading times and user experience for mobile social environments
+
+
+ Specific guidance for optimizing Mini Apps in Coinbase Wallet
+
+
+
+## Resources and Community
+
+
+
+ Complete documentation for building Mini Apps with MiniKit
+
+
+ Get started with MiniKit in under 10 minutes
+
+
+ Learn about the underlying Farcaster social protocol
+
+
+ Connect with other developers building onchain social apps
+
+
+
+
diff --git a/docs/cookbook/payments/build-ecommerce-app.mdx b/docs/cookbook/payments/build-ecommerce-app.mdx
new file mode 100644
index 00000000..eb365cd2
--- /dev/null
+++ b/docs/cookbook/payments/build-ecommerce-app.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Build an E-commerce App'
+description: 'Learn how to build an e-commerce application on Base'
+---
+
+[Guide for building an e-commerce application on Base]
\ No newline at end of file
diff --git a/docs/cookbook/payments/deploy-shopify-storefront.mdx b/docs/cookbook/payments/deploy-shopify-storefront.mdx
new file mode 100644
index 00000000..405118a1
--- /dev/null
+++ b/docs/cookbook/payments/deploy-shopify-storefront.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Deploy a Shopify Storefront'
+description: 'Guide to deploying a Shopify storefront on Base'
+---
+
+[Instructions for deploying a Shopify storefront on Base]
\ No newline at end of file
diff --git a/docs/cookbook/payments/transaction-guide.mdx b/docs/cookbook/payments/transaction-guide.mdx
new file mode 100644
index 00000000..885e2e80
--- /dev/null
+++ b/docs/cookbook/payments/transaction-guide.mdx
@@ -0,0 +1,6 @@
+---
+title: 'Transaction Guide'
+description: 'Comprehensive guide for handling transactions'
+---
+
+[Guide for handling transactions in payment applications]
\ No newline at end of file
diff --git a/docs/cookbook/social/convert-farcaster-frame.mdx b/docs/cookbook/social/convert-farcaster-frame.mdx
new file mode 100644
index 00000000..c4b7659e
--- /dev/null
+++ b/docs/cookbook/social/convert-farcaster-frame.mdx
@@ -0,0 +1,436 @@
+---
+title: Convert Farcaster Frame to Open Frame
+sidebarTitle: Convert to Open Frame
+description: 'Step-by-step guide for converting Farcaster Frames to Open Frames for broader platform support'
+---
+
+Expand your Frame's reach beyond Farcaster by converting it to an Open Frame. Open Frames are supported across multiple platforms including X (Twitter), making your interactive content accessible to a much wider audience.
+
+
+
+ Learn about the Open Frame standard and its benefits
+
+
+ Step-by-step conversion guide
+
+
+ Deploy your Frame across multiple platforms
+
+
+ Tips for optimal cross-platform compatibility
+
+
+
+## Understanding Open Frames
+
+Open Frames extend the Farcaster Frame standard to work across multiple platforms. They maintain the same interactive capabilities while adding broader platform compatibility.
+
+
+**Open Frame Benefits**
+
+- **Wider Reach**: Deploy to X (Twitter), Lens Protocol, and other platforms
+- **Standardized Format**: Use a single codebase for multiple platforms
+- **Future-Proof**: Built on open standards for broader adoption
+- **Enhanced Discovery**: Tap into larger social networks
+
+
+### Key Differences
+
+| Feature | Farcaster Frames | Open Frames |
+|---------|------------------|-------------|
+| Platform Support | Farcaster only | Multiple platforms |
+| Metadata Format | Farcaster-specific | Open Graph + extensions |
+| Button Actions | Limited to Farcaster | Cross-platform compatible |
+| Image Handling | Farcaster standards | Universal image formats |
+
+## Prerequisites
+
+Before converting your Frame, ensure you have:
+
+
+A working Farcaster Frame
+
+
+Understanding of Open Graph meta tags
+
+
+Access to Frame deployment infrastructure
+
+
+Test accounts on target platforms
+
+
+## Conversion Steps
+
+### Step 1: Update Meta Tags
+
+Convert your Farcaster-specific meta tags to Open Frame format:
+
+```html Before (Farcaster Frame)
+
+
+
+
+```
+
+```html After (Open Frame)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Step 2: Update Frame Handler
+
+Modify your Frame handler to support multiple platforms:
+
+```typescript api/frame/route.ts
+import { NextRequest, NextResponse } from 'next/server'
+
+export async function POST(req: NextRequest) {
+ const body = await req.json()
+
+ // Detect the platform
+ const platform = detectPlatform(body)
+
+ // Handle different platform formats
+ switch (platform) {
+ case 'farcaster':
+ return handleFarcasterFrame(body)
+ case 'xmtp':
+ return handleXMTPFrame(body)
+ case 'lens':
+ return handleLensFrame(body)
+ default:
+ return handleGenericFrame(body)
+ }
+}
+
+function detectPlatform(body: any): string {
+ // Check for platform-specific identifiers
+ if (body.untrustedData?.fid) return 'farcaster'
+ if (body.clientProtocol?.includes('xmtp')) return 'xmtp'
+ if (body.clientProtocol?.includes('lens')) return 'lens'
+ return 'generic'
+}
+
+function handleFarcasterFrame(body: any) {
+ const { buttonIndex, fid, castId } = body.untrustedData
+
+ // Your existing Farcaster logic
+ return NextResponse.json({
+ image: "https://example.com/response.jpg",
+ buttons: [
+ { label: "Next", action: "post" }
+ ]
+ })
+}
+
+function handleGenericFrame(body: any) {
+ // Generic handling for other platforms
+ return NextResponse.json({
+ image: "https://example.com/response.jpg",
+ buttons: [
+ { label: "Continue", action: "post" }
+ ]
+ })
+}
+```
+
+### Step 3: Add Platform Detection
+
+Create a utility to handle platform-specific logic:
+
+```typescript utils/platform.ts
+export interface FrameRequest {
+ platform: 'farcaster' | 'xmtp' | 'lens' | 'generic'
+ user?: {
+ id: string
+ username?: string
+ address?: string
+ }
+ interaction: {
+ buttonIndex: number
+ inputText?: string
+ state?: string
+ }
+}
+
+export function parseFrameRequest(body: any): FrameRequest {
+ // Farcaster format
+ if (body.untrustedData?.fid) {
+ return {
+ platform: 'farcaster',
+ user: {
+ id: body.untrustedData.fid.toString(),
+ address: body.untrustedData.address,
+ },
+ interaction: {
+ buttonIndex: body.untrustedData.buttonIndex,
+ inputText: body.untrustedData.inputText,
+ state: body.untrustedData.state,
+ }
+ }
+ }
+
+ // Generic format for other platforms
+ return {
+ platform: 'generic',
+ user: {
+ id: body.user?.id || 'anonymous',
+ },
+ interaction: {
+ buttonIndex: body.buttonIndex || 1,
+ inputText: body.inputText,
+ state: body.state,
+ }
+ }
+}
+```
+
+### Step 4: Update Response Format
+
+Ensure your responses work across platforms:
+
+```typescript utils/frameResponse.ts
+export interface FrameResponse {
+ image: string
+ imageAspectRatio?: '1.91:1' | '1:1'
+ buttons?: Array<{
+ label: string
+ action?: 'post' | 'post_redirect' | 'link' | 'mint'
+ target?: string
+ }>
+ input?: {
+ text: string
+ }
+ state?: string
+}
+
+export function createFrameResponse(response: FrameResponse) {
+ const html = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${response.buttons?.map((button, index) => `
+
+
+ ${button.action ? ` ` : ''}
+ ${button.action ? ` ` : ''}
+ ${button.target ? ` ` : ''}
+ ${button.target ? ` ` : ''}
+ `).join('') || ''}
+
+ ${response.input ? `
+
+
+ ` : ''}
+
+ ${response.state ? `
+
+
+ ` : ''}
+
+
+
+
+
+ `
+
+ return new Response(html, {
+ headers: { 'Content-Type': 'text/html' },
+ })
+}
+```
+
+## Platform Support
+
+### X (Twitter) Integration
+
+To make your Open Frame work on X:
+
+1. **Ensure proper Open Graph tags** are present
+2. **Test with X's card validator**: Use the Card Validator to verify your meta tags
+3. **Handle X-specific user interactions**: X may pass different user data
+
+```typescript utils/xIntegration.ts
+export function handleXFrame(body: any) {
+ // X-specific handling
+ return {
+ platform: 'x',
+ user: {
+ id: body.user?.id || 'x_user',
+ username: body.user?.username,
+ },
+ interaction: {
+ buttonIndex: body.buttonIndex || 1,
+ }
+ }
+}
+```
+
+### Lens Protocol Support
+
+For Lens Protocol integration:
+
+```typescript utils/lensIntegration.ts
+export function handleLensFrame(body: any) {
+ return {
+ platform: 'lens',
+ user: {
+ id: body.profileId,
+ address: body.address,
+ },
+ interaction: {
+ buttonIndex: body.buttonIndex,
+ state: body.state,
+ }
+ }
+}
+```
+
+## Best Practices
+
+### Cross-Platform Compatibility
+
+
+**Universal Design**
+
+Design your Frame content and interactions to work across all platforms. Avoid platform-specific features in your core experience.
+
+
+1. **Consistent Branding**: Use the same visual elements across platforms
+2. **Universal Button Labels**: Use clear, platform-agnostic button text
+3. **Responsive Images**: Ensure images work at different aspect ratios
+4. **Graceful Degradation**: Handle missing platform features gracefully
+
+### Testing Strategy
+
+
+
+Ensure your original Farcaster Frame still works perfectly.
+
+
+
+Use validation tools to check your Open Graph and Open Frame meta tags.
+
+
+
+Test your Frame on each target platform with real user accounts.
+
+
+
+Track engagement across platforms to optimize performance.
+
+
+
+### Performance Optimization
+
+```typescript utils/optimization.ts
+// Cache images across platforms
+export function getCachedImage(imageKey: string) {
+ // Implement caching logic
+ return `https://cdn.example.com/frames/${imageKey}.jpg`
+}
+
+// Optimize for different platform requirements
+export function optimizeImageForPlatform(
+ baseImage: string,
+ platform: string
+): string {
+ switch (platform) {
+ case 'farcaster':
+ return `${baseImage}?w=1200&h=630` // 1.91:1 aspect ratio
+ case 'x':
+ return `${baseImage}?w=1200&h=675` // X card format
+ default:
+ return baseImage
+ }
+}
+```
+
+## Deployment
+
+### Environment Configuration
+
+```bash .env
+# Platform-specific configurations
+FARCASTER_HUB_URL=https://neynar.com/v1/farcaster
+XMTP_ENV=production
+LENS_API_URL=https://api.lens.dev
+
+# Image optimization
+IMAGE_CDN_URL=https://cdn.example.com
+```
+
+### Monitoring and Analytics
+
+Track Frame performance across platforms:
+
+```typescript utils/analytics.ts
+export function trackFrameInteraction(
+ platform: string,
+ action: string,
+ userId?: string
+) {
+ // Send analytics to your preferred service
+ analytics.track('frame_interaction', {
+ platform,
+ action,
+ userId,
+ timestamp: new Date().toISOString(),
+ })
+}
+```
+
+## Resources
+
+
+
+ Official Open Frames specification
+
+
+ Tools for testing your Frames
+
+
+ Platform-specific Frame documentation
+
+
+ Cross-platform optimization tips
+
+
+
+---
+
+Converting your Farcaster Frame to an Open Frame significantly expands your reach while maintaining the interactive experience users love. Start with backward compatibility and gradually optimize for each platform's unique features and audience.
\ No newline at end of file
diff --git a/docs/cookbook/social/farcaster-nft-minting-guide.mdx b/docs/cookbook/social/farcaster-nft-minting-guide.mdx
new file mode 100644
index 00000000..c8180900
--- /dev/null
+++ b/docs/cookbook/social/farcaster-nft-minting-guide.mdx
@@ -0,0 +1,419 @@
+---
+title: Farcaster NFT Minting Guide
+sidebarTitle: Farcaster NFT Minting
+description: 'Comprehensive guide to NFT minting with Farcaster integration'
+---
+
+Build engaging NFT minting experiences by integrating Farcaster Frames with Base smart contracts. This guide shows you how to create seamless social-first minting flows that leverage Farcaster's social graph.
+
+
+
+ Configure Farcaster Frames for your NFT collection
+
+
+ Deploy and integrate your NFT contract with Frame actions
+
+
+ Add social proof and community features to your mint
+
+
+ Security and user experience considerations
+
+
+
+## Overview
+
+Farcaster Frames provide a powerful way to embed interactive NFT minting experiences directly in social feeds. Users can mint NFTs without leaving their Farcaster client, creating frictionless onboarding and viral distribution.
+
+
+**What are Farcaster Frames?**
+
+Frames are interactive components that can be embedded in Farcaster casts (posts). They support buttons, images, and actions that can trigger onchain transactions directly from the social feed.
+
+
+## Prerequisites
+
+Before starting, ensure you have:
+
+
+A deployed NFT contract on Base (or Base Sepolia for testing)
+
+
+Basic understanding of Farcaster and Frame development
+
+
+Next.js development environment set up
+
+
+Farcaster account for testing
+
+
+## Setting Up Farcaster Frames
+
+### Project Structure
+
+Create a new Frame project or add Frame functionality to your existing Next.js app:
+
+```bash Terminal
+npx create-next-app@latest farcaster-nft-mint
+cd farcaster-nft-mint
+npm install frog hono @coinbase/onchainkit
+```
+
+### Frame Metadata
+
+Create your Frame with proper metadata for NFT minting:
+
+```typescript app/frame/route.tsx
+import { Button, Frog } from 'frog'
+import { handle } from 'frog/next'
+import { neynar } from 'frog/hubs'
+
+const app = new Frog({
+ title: 'Base NFT Mint',
+ hub: neynar({ apiKey: process.env.NEYNAR_API_KEY! }),
+ assetsPath: '/',
+ basePath: '/frame',
+})
+
+app.frame('/', (c) => {
+ return c.res({
+ image: (
+
+
+ Mint Your Base NFT
+
+
+ Limited Edition • 1000 Supply
+
+
+ ),
+ intents: [
+ Mint NFT (0.001 ETH) ,
+ Learn More ,
+ ],
+ })
+})
+
+export const GET = handle(app)
+export const POST = handle(app)
+```
+
+## Smart Contract Integration
+
+### NFT Contract Setup
+
+Use OpenZeppelin's ERC-721 contracts for a secure foundation:
+
+```solidity contracts/BaseNFT.sol
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
+
+contract BaseNFT is ERC721, Ownable, ReentrancyGuard {
+ uint256 public constant MAX_SUPPLY = 1000;
+ uint256 public constant MINT_PRICE = 0.001 ether;
+ uint256 public totalSupply;
+
+ mapping(address => bool) public hasMinted;
+
+ event NFTMinted(address indexed to, uint256 tokenId);
+
+ constructor(address initialOwner)
+ ERC721("Base Social NFT", "BSN")
+ Ownable(initialOwner)
+ {}
+
+ function mint() external payable nonReentrant {
+ require(totalSupply < MAX_SUPPLY, "Max supply reached");
+ require(msg.value >= MINT_PRICE, "Insufficient payment");
+ require(!hasMinted[msg.sender], "Already minted");
+
+ uint256 tokenId = totalSupply + 1;
+ totalSupply++;
+ hasMinted[msg.sender] = true;
+
+ _safeMint(msg.sender, tokenId);
+
+ emit NFTMinted(msg.sender, tokenId);
+ }
+
+ function withdraw() external onlyOwner {
+ payable(owner()).transfer(address(this).balance);
+ }
+}
+```
+
+### Frame Action Handler
+
+Create the mint action handler that integrates with your smart contract:
+
+```typescript app/frame/mint/route.tsx
+import { Button, Frog, parseEther } from 'frog'
+import { handle } from 'frog/next'
+
+app.frame('/mint', async (c) => {
+ const { buttonValue, frameData } = c
+
+ // Get user's Farcaster ID and ETH address
+ const fid = frameData?.fid
+ const address = frameData?.address
+
+ if (!address) {
+ return c.res({
+ image: (
+
+ Connect your wallet to mint
+
+ ),
+ intents: [
+ ← Back ,
+ ],
+ })
+ }
+
+ try {
+ // Check if user already minted
+ const hasMinted = await checkIfMinted(address)
+
+ if (hasMinted) {
+ return c.res({
+ image: (
+
+ Already minted! Check your wallet.
+
+ ),
+ intents: [
+
+ View on BaseScan
+ ,
+ ],
+ })
+ }
+
+ return c.res({
+ image: (
+
+ Ready to mint your NFT?
+
+ ),
+ intents: [
+
+ Mint NFT (0.001 ETH)
+ ,
+ ← Cancel ,
+ ],
+ })
+
+ } catch (error) {
+ console.error('Mint error:', error)
+ return c.error({ message: 'Failed to process mint' })
+ }
+})
+```
+
+### Transaction Handler
+
+Handle the actual NFT minting transaction:
+
+```typescript app/frame/mint-tx/route.tsx
+import { Frog, parseEther } from 'frog'
+import { erc721Abi } from 'viem'
+
+app.transaction('/mint-tx', (c) => {
+ return c.contract({
+ abi: [
+ {
+ "inputs": [],
+ "name": "mint",
+ "outputs": [],
+ "stateMutability": "payable",
+ "type": "function"
+ }
+ ],
+ chainId: 'eip155:8453', // Base mainnet
+ functionName: 'mint',
+ to: process.env.NFT_CONTRACT_ADDRESS as `0x${string}`,
+ value: parseEther('0.001'),
+ })
+})
+```
+
+## Social Integration
+
+### Add Social Proof
+
+Display minting activity and social proof in your Frame:
+
+```typescript utils/social.ts
+export async function getMintingActivity() {
+ // Fetch recent mints from your contract
+ const recentMints = await publicClient.getLogs({
+ address: NFT_CONTRACT_ADDRESS,
+ event: parseAbiItem('event NFTMinted(address indexed to, uint256 tokenId)'),
+ fromBlock: 'earliest',
+ })
+
+ return recentMints.slice(-5) // Last 5 mints
+}
+
+export async function getFarcasterProfile(fid: number) {
+ // Use Neynar API to get user profile
+ const response = await fetch(`https://api.neynar.com/v2/farcaster/user/bulk?fids=${fid}`, {
+ headers: {
+ 'api_key': process.env.NEYNAR_API_KEY!,
+ },
+ })
+
+ return response.json()
+}
+```
+
+### Display Community Activity
+
+```typescript app/frame/activity/route.tsx
+app.frame('/activity', async (c) => {
+ const recentMints = await getMintingActivity()
+
+ return c.res({
+ image: (
+
+
Recent Mints
+ {recentMints.map((mint, index) => (
+
+ #{mint.tokenId} minted by {mint.to.slice(0, 6)}...
+
+ ))}
+
+ ),
+ intents: [
+ ← Back to Mint ,
+ ],
+ })
+})
+```
+
+## Best Practices
+
+### Security Considerations
+
+
+**Smart Contract Security**
+
+Always audit your NFT contracts before mainnet deployment. Use OpenZeppelin's battle-tested contracts and follow security best practices.
+
+
+1. **Rate Limiting**: Implement proper rate limiting to prevent spam
+2. **Input Validation**: Validate all Frame inputs and user data
+3. **Error Handling**: Provide clear error messages for failed transactions
+4. **Access Controls**: Use proper access controls for admin functions
+
+### User Experience
+
+
+**Seamless Experience**
+
+Keep the minting flow simple - ideally just 1-2 clicks from seeing the Frame to completing the mint.
+
+
+1. **Clear Imagery**: Use high-quality images that represent your NFT collection
+2. **Progress Indicators**: Show users their progress through the minting flow
+3. **Error Recovery**: Provide clear paths for users to retry failed transactions
+4. **Social Sharing**: Encourage users to share their successful mints
+
+### Gas Optimization
+
+Consider using Base's low fees and fast confirmation times:
+
+```typescript utils/gasOptimization.ts
+// Use efficient batch minting for multiple NFTs
+export async function batchMint(recipients: string[], amounts: number[]) {
+ return await contract.write.batchMint(recipients, amounts)
+}
+
+// Consider using ERC-721A for efficient batch minting
+// if your collection supports it
+```
+
+## Testing Your Frame
+
+### Local Development
+
+1. **Frame Validator**: Use the [Frame Validator](https://warpcast.com/~/developers/frames) to test your Frame
+2. **Local Testing**: Test with ngrok or similar tools to expose your localhost
+3. **Testnet First**: Deploy to Base Sepolia before mainnet
+
+### Production Deployment
+
+
+
+Deploy your NFT contract to Base mainnet using Foundry or Hardhat.
+
+
+
+Set up your production environment variables:
+
+```bash .env.production
+NFT_CONTRACT_ADDRESS=0x...
+NEYNAR_API_KEY=your_api_key
+BASE_RPC_URL=https://mainnet.base.org
+```
+
+
+
+Deploy your Frame app to Vercel or your preferred hosting platform.
+
+
+
+Test the complete flow from Frame display to successful NFT mint.
+
+
+
+## Resources
+
+
+
+ Official documentation for building Farcaster Frames
+
+
+ Farcaster data and infrastructure
+
+
+ Secure smart contract templates
+
+
+ Deploy contracts on Base
+
+
+
+---
+
+Building NFT minting experiences with Farcaster Frames creates viral, social-first onboarding that can dramatically increase your collection's reach and engagement. Start with a simple implementation and iterate based on user feedback and community response.
\ No newline at end of file
diff --git a/docs/cookbook/social/farcaster-no-code-nft-minting.mdx b/docs/cookbook/social/farcaster-no-code-nft-minting.mdx
new file mode 100644
index 00000000..9ddbfbef
--- /dev/null
+++ b/docs/cookbook/social/farcaster-no-code-nft-minting.mdx
@@ -0,0 +1,6 @@
+---
+title: Farcaster No-Code NFT Minting
+description: 'Create NFT minting experiences without code using Farcaster'
+---
+
+[Guide for creating no-code NFT minting experiences using Farcaster]
\ No newline at end of file
diff --git a/docs/custom.css b/docs/custom.css
new file mode 100644
index 00000000..e11340b9
--- /dev/null
+++ b/docs/custom.css
@@ -0,0 +1,34 @@
+/* Danger admonition coloring */
+
+.danger-admonition {
+ border: 1px solid rgba(239, 68, 68, 0.2);
+ background-color: rgba(254, 242, 242, 0.5);
+}
+
+.dark\:danger-admonition:is(.dark *) {
+ border-color: rgba(239, 68, 68, 0.3);
+ background-color: rgba(239, 68, 68, 0.1);
+}
+
+.base_header_img {
+ margin: auto;
+}
+
+.homepage_wrapper {
+ width: 75%;
+ max-width: 1376px;
+ margin: auto;
+ margin-bottom: 50px;
+}
+
+.home_header {
+ padding-bottom: 0 !important;
+}
+
+.home_header h1 {
+ margin-bottom: 24px;
+}
+
+.home_header div p {
+ margin-top: 10px;
+}
diff --git a/docs/docs.json b/docs/docs.json
new file mode 100644
index 00000000..9fba3077
--- /dev/null
+++ b/docs/docs.json
@@ -0,0 +1,1028 @@
+{
+ "$schema": "https://mintlify.com/docs.json",
+ "theme": "mint",
+ "name": "Base Documentation",
+ "colors": {
+ "primary": "#578BFA",
+ "light": "#578BFA",
+ "dark": "#578BFA"
+ },
+ "favicon": "/logo/favicon.svg",
+ "contextual": {
+ "options": [
+ "copy",
+ "chatgpt",
+ "claude"
+ ]
+ },
+ "api": {
+ "playground": {
+ "display": "simple"
+ },
+ "examples": {
+ "languages": [
+ "javascript"
+ ]
+ }
+ },
+ "navigation": {
+ "tabs": [
+ {
+ "tab": "Get Started",
+ "groups": [
+ {
+ "group": "Introduction",
+ "pages": [
+ "get-started/base"
+ ]
+ },
+ {
+ "group": "Browse by",
+ "pages": [
+ "get-started/products",
+ "get-started/use-cases"
+ ]
+ },
+ {
+ "group": "Quickstart",
+ "pages": [
+ "get-started/build-app",
+ "get-started/launch-token",
+ "get-started/deploy-chain",
+ "get-started/deploy-smart-contracts",
+ "get-started/get-funded"
+ ]
+ },
+ {
+ "group": "Build with AI",
+ "pages": [
+ "get-started/ai-prompting",
+ "get-started/prompt-library"
+ ]
+ }
+ ],
+ "global": {
+ "anchors": [
+ {
+ "anchor": "Status",
+ "href": "https://status.base.org/",
+ "icon": "signal-bars"
+ },
+ {
+ "anchor": "Faucet",
+ "href": "https://test-184b3b57.mintlify.app/base-chain/tools/network-faucets",
+ "icon": "gas-pump"
+ },
+ {
+ "anchor": "Bridge",
+ "href": "https://test-184b3b57.mintlify.app/base-chain/quickstart/bridge-token",
+ "icon": "coin"
+ }
+ ]
+ }
+ },
+ {
+ "tab": "Base Chain",
+ "groups": [
+ {
+ "group": "Quickstart",
+ "pages": [
+
+ "base-chain/quickstart/why-base",
+ "base-chain/quickstart/deploy-on-base",
+ "base-chain/quickstart/connecting-to-base",
+ "base-chain/quickstart/bridge-token"
+ ]
+ },
+ {
+ "group": "Network Information",
+ "pages": [
+ "base-chain/network-information/base-contracts",
+ "base-chain/network-information/network-fees",
+ "base-chain/network-information/ecosystem-contracts",
+ "base-chain/network-information/diffs-ethereum-base"
+ ]
+ },
+ {
+ "group": "Flashblocks",
+ "pages": [
+ "base-chain/flashblocks/apps",
+ "base-chain/flashblocks/node-providers"
+ ]
+ },
+ {
+ "group": "Node Operators",
+ "pages": [
+ "base-chain/node-operators/run-a-base-node",
+ "base-chain/node-operators/performance-tuning",
+ "base-chain/node-operators/snapshots",
+ "base-chain/node-operators/troubleshooting"
+ ]
+ },
+ {
+ "group": "Tools",
+ "pages": [
+ "base-chain/tools/base-products",
+ "base-chain/tools/onchain-registry-api",
+ "base-chain/tools/node-providers",
+ "base-chain/tools/block-explorers",
+ "base-chain/tools/network-faucets",
+ "base-chain/tools/oracles",
+ "base-chain/tools/data-indexers",
+ "base-chain/tools/cross-chain",
+ "base-chain/tools/account-abstraction",
+ "base-chain/tools/onramps",
+ "base-chain/tools/tokens-in-wallet"
+ ]
+ },
+ {
+ "group": "Security",
+ "pages": [
+ "base-chain/security/security-council",
+ "base-chain/security/avoid-malicious-flags",
+ "base-chain/security/report-vulnerability"
+ ]
+ }
+ ],
+ "global": {
+ "anchors": [
+ {
+ "anchor": "GitHub",
+ "href": "https://github.com/base",
+ "icon": "github"
+ },
+ {
+ "anchor": "Status",
+ "href": "https://status.base.org/",
+ "icon": "signal-bars"
+ },
+ {
+ "anchor": "Chain Stats",
+ "href": "https://www.base.org/stats",
+ "icon": "chart-line"
+ },
+ {
+ "anchor": "Explorer",
+ "href": "https://basescan.com/",
+ "icon": "magnifying-glass"
+ },
+ {
+ "anchor": "Support",
+ "href": "https://discord.com/invite/base",
+ "icon": "discord"
+ }
+ ]
+ }
+ },
+ {
+ "tab": "Smart Wallet",
+ "groups": [
+ {
+ "group": "Quickstart",
+ "pages": [
+ "smart-wallet/quickstart",
+ "smart-wallet/quickstart/quick-demo",
+ "smart-wallet/quickstart/nextjs-project",
+ "smart-wallet/quickstart/react-native-project",
+ "smart-wallet/quickstart/ai-tools-available-for-devs"
+ ]
+ },
+ {
+ "group": "Concepts",
+ "pages": [
+ "smart-wallet/concepts/what-is-smart-wallet",
+ {
+ "group": "Features",
+ "pages": [
+ {
+ "group": "Built-in Features",
+ "pages": [
+ "smart-wallet/concepts/features/built-in/single-sign-on",
+ "smart-wallet/concepts/features/built-in/networks",
+ "smart-wallet/concepts/features/built-in/passkeys",
+ "smart-wallet/concepts/features/built-in/recovery-keys",
+ "smart-wallet/concepts/features/built-in/MagicSpend"
+ ]
+ },
+ {
+ "group": "Optional Features",
+ "pages": [
+ "smart-wallet/concepts/features/optional/gas-free-transactions",
+ "smart-wallet/concepts/features/optional/spend-limits",
+ "smart-wallet/concepts/features/optional/batch-operations",
+ "smart-wallet/concepts/features/optional/custom-gas-tokens",
+ "smart-wallet/concepts/features/optional/sub-accounts"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Usage Details",
+ "pages": [
+ "smart-wallet/concepts/usage-details/signature-verification",
+ "smart-wallet/concepts/usage-details/popups",
+ "smart-wallet/concepts/usage-details/gas-usage",
+ "smart-wallet/concepts/usage-details/unsupported-calls"
+ ]
+ },
+ "smart-wallet/concepts/base-gasless-campaign"
+ ]
+ },
+ {
+ "group": "Guides",
+ "pages": [
+ "smart-wallet/guides/siwe",
+ "smart-wallet/guides/signing-and-verifying-messages",
+ "smart-wallet/guides/magic-spend",
+ "smart-wallet/guides/batch-transactions",
+ "smart-wallet/guides/paymasters",
+ "smart-wallet/guides/erc20-paymasters",
+ {
+ "group": "Sub Accounts",
+ "pages": [
+ "smart-wallet/guides/sub-accounts",
+ "smart-wallet/guides/sub-accounts/setup",
+ "smart-wallet/guides/sub-accounts/using-sub-accounts"
+ ]
+ },
+ "smart-wallet/guides/spend-limits"
+ ]
+ },
+ {
+ "group": "Examples",
+ "pages": [
+ "smart-wallet/examples/coin-a-joke-app"
+ ]
+ },
+ {
+ "group": "Technical Reference",
+ "pages": [
+ {
+ "group": "Coinbase Wallet SDK",
+ "pages": [
+ "smart-wallet/technical-reference/sdk",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_accounts",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_blockNumber",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_chainId",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_coinbase",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_estimateGas",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_feeHistory",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_gasPrice",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBalance",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockByHash",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockByNumber",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockTransactionCountByHash",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getBlockTransactionCountByNumber",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getCode",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getLogs",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getProof",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getStorageAt",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionByBlockHashAndIndex",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionByBlockNumberAndIndex",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionByHash",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionCount",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getTransactionReceipt",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getUncleCountByBlockHash",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_getUncleCountByBlockNumber",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_requestAccounts",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_sendRawTransaction",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_sendTransaction",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/eth_signTypedData_v4",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/personal_sign",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_addEthereumChain",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_addSubAccount",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_connect",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_switchEthereumChain",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/wallet_watchAsset",
+ "smart-wallet/technical-reference/sdk/CoinbaseWalletProvider/web3_clientVersion"
+ ]
+ },
+ {
+ "group": "Spend Limits",
+ "pages": [
+ "smart-wallet/technical-reference/spend-permissions/spendpermissionmanager",
+ "smart-wallet/technical-reference/spend-permissions/coinbase-fetchpermissions"
+ ]
+ },
+ "smart-wallet/technical-reference/sdk/sub-account-reference"
+ ]
+ },
+ {
+ "group": "Contribute",
+ "pages": [
+ "smart-wallet/contribute/contribute-to-smart-wallet-docs",
+ "smart-wallet/contribute/security-and-bug-bounty"
+ ]
+ }
+ ],
+ "global": {
+ "anchors": [
+ {
+ "anchor": "GitHub",
+ "href": "https://github.com/coinbase/onchainkit",
+ "icon": "github"
+ },
+ {
+ "anchor": "Support",
+ "href": "https://discord.com/invite/cdp",
+ "icon": "discord"
+ }
+ ]
+ }
+ },
+ {
+ "tab": "OnchainKit",
+ "groups": [
+ {
+ "group": "Introduction",
+ "pages": [
+ "onchainkit/getting-started",
+ "onchainkit/guides/telemetry",
+ "onchainkit/guides/troubleshooting"
+ ]
+ },
+ {
+ "group": "Installation",
+ "pages": [
+ "onchainkit/installation/nextjs",
+ "onchainkit/installation/vite",
+ "onchainkit/installation/remix",
+ "onchainkit/installation/astro"
+ ]
+ },
+ {
+ "group": "Config",
+ "pages": [
+ "onchainkit/config/onchainkit-provider",
+ "onchainkit/config/supplemental-providers"
+ ]
+ },
+ {
+ "group": "Guides",
+ "pages": [
+ "onchainkit/guides/lifecycle-status",
+ "onchainkit/guides/tailwind",
+ "onchainkit/guides/themes",
+ "onchainkit/guides/use-basename-in-onchain-app",
+ "onchainkit/guides/using-ai-powered-ides",
+ "onchainkit/guides/ai-prompting-guide"
+ ]
+ },
+ {
+ "group": "Templates",
+ "pages": [
+ "onchainkit/templates/onchain-nft-app",
+ "onchainkit/templates/onchain-commerce-app",
+ "onchainkit/templates/onchain-social-profile"
+ ]
+ },
+ {
+ "group": "Components",
+ "pages": [
+ {
+ "group": "Appchain",
+ "pages": [
+ "onchainkit/appchain/bridge"
+ ]
+ },
+ {
+ "group": "Buy",
+ "pages": [
+ "onchainkit/buy/buy"
+ ]
+ },
+ {
+ "group": "Checkout",
+ "pages": [
+ "onchainkit/checkout/checkout"
+ ]
+ },
+ {
+ "group": "Earn",
+ "pages": [
+ "onchainkit/earn/earn"
+ ]
+ },
+ {
+ "group": "Fund",
+ "pages": [
+ "onchainkit/fund/fund-button",
+ "onchainkit/fund/fund-card"
+ ]
+ },
+ {
+ "group": "Identity",
+ "pages": [
+ "onchainkit/identity/identity",
+ "onchainkit/identity/address",
+ "onchainkit/identity/avatar",
+ "onchainkit/identity/badge",
+ "onchainkit/identity/identity-card",
+ "onchainkit/identity/name",
+ "onchainkit/identity/socials"
+ ]
+ },
+ {
+ "group": "Mint",
+ "pages": [
+ "onchainkit/mint/nft-card",
+ "onchainkit/mint/nft-mint-card"
+ ]
+ },
+ "onchainkit/signature/signature",
+ {
+ "group": "Swap",
+ "pages": [
+ "onchainkit/swap/swap",
+ "onchainkit/swap/swap-settings"
+ ]
+ },
+ {
+ "group": "Token",
+ "pages": [
+ "onchainkit/token/token-chip",
+ "onchainkit/token/token-image",
+ "onchainkit/token/token-row",
+ "onchainkit/token/token-search",
+ "onchainkit/token/token-select-dropdown"
+ ]
+ },
+ "onchainkit/transaction/transaction",
+ {
+ "group": "Wallet",
+ "pages": [
+ "onchainkit/wallet/wallet",
+ "onchainkit/wallet/wallet-dropdown-basename",
+ "onchainkit/wallet/wallet-dropdown-disconnect",
+ "onchainkit/wallet/wallet-dropdown-fund-link",
+ "onchainkit/wallet/wallet-dropdown-link",
+ "onchainkit/wallet/wallet-island",
+ "onchainkit/wallet/wallet-modal"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "API",
+ "pages": [
+ {
+ "group": "Mint",
+ "pages": [
+ "onchainkit/api/get-token-details",
+ "onchainkit/api/get-mint-details",
+ "onchainkit/api/build-mint-transaction"
+ ]
+ },
+ {
+ "group": "Swap",
+ "pages": [
+ "onchainkit/api/build-swap-transaction",
+ "onchainkit/api/get-swap-quote"
+ ]
+ },
+ {
+ "group": "Token",
+ "pages": [
+ "onchainkit/api/get-tokens"
+ ]
+ },
+ {
+ "group": "Wallet",
+ "pages": [
+ "onchainkit/api/get-portfolios"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Utilities",
+ "pages": [
+ {
+ "group": "Config",
+ "pages": [
+ "onchainkit/config/is-base",
+ "onchainkit/config/is-ethereum"
+ ]
+ },
+ {
+ "group": "Earn",
+ "pages": [
+ "onchainkit/api/build-deposit-to-morpho-tx",
+ "onchainkit/api/build-withdraw-from-morpho-tx",
+ "onchainkit/hooks/use-build-deposit-to-morpho-tx",
+ "onchainkit/hooks/use-build-withdraw-from-morpho-tx",
+ "onchainkit/hooks/use-earn-context"
+ ]
+ },
+ {
+ "group": "Fund",
+ "pages": [
+ "onchainkit/fund/get-onramp-buy-url",
+ "onchainkit/fund/fetch-onramp-config",
+ "onchainkit/fund/fetch-onramp-quote",
+ "onchainkit/fund/fetch-onramp-options",
+ "onchainkit/fund/fetch-onramp-transaction-status",
+ "onchainkit/fund/setup-onramp-event-listeners"
+ ]
+ },
+ {
+ "group": "Identity",
+ "pages": [
+ "onchainkit/identity/get-address",
+ "onchainkit/identity/get-attestations",
+ "onchainkit/identity/get-avatar",
+ "onchainkit/identity/get-avatars",
+ "onchainkit/identity/get-name",
+ "onchainkit/identity/get-names",
+ "onchainkit/identity/use-address",
+ "onchainkit/identity/use-avatar",
+ "onchainkit/identity/use-avatars",
+ "onchainkit/identity/use-name",
+ "onchainkit/identity/use-names"
+ ]
+ },
+ {
+ "group": "Mint",
+ "pages": [
+ "onchainkit/hooks/use-token-details",
+ "onchainkit/hooks/use-mint-details"
+ ]
+ },
+ {
+ "group": "Token",
+ "pages": [
+ "onchainkit/token/format-amount"
+ ]
+ },
+ {
+ "group": "Wallet",
+ "pages": [
+ "onchainkit/wallet/is-valid-aa-entrypoint",
+ "onchainkit/wallet/is-wallet-a-coinbase-smart-wallet"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Types",
+ "pages": [
+ "onchainkit/api/types",
+ "onchainkit/appchain/types",
+ "onchainkit/checkout/types",
+ "onchainkit/config/types",
+ "onchainkit/earn/types",
+ "onchainkit/fund/types",
+ "onchainkit/identity/types",
+ "onchainkit/mint/types",
+ "onchainkit/signature/types",
+ "onchainkit/swap/types",
+ "onchainkit/token/types",
+ "onchainkit/transaction/types",
+ "onchainkit/wallet/types"
+ ]
+ },
+ {
+ "group": "Contribution",
+ "pages": [
+ "onchainkit/guides/contribution",
+ "onchainkit/guides/reporting-bug"
+ ]
+ }
+ ],
+ "global": {
+ "anchors": [
+ {
+ "anchor": "GitHub",
+ "href": "https://github.com/coinbase/onchainkit",
+ "icon": "github"
+ },
+ {
+ "anchor": "Playground",
+ "href": "https://onchainkit.xyz/playground",
+ "icon": "gamepad"
+ },
+ {
+ "anchor": "Support",
+ "href": "https://discord.com/invite/cdp",
+ "icon": "discord"
+ }
+ ]
+ }
+ },
+ {
+ "tab": "Wallet App",
+ "groups": [
+ {
+ "group": "Introduction",
+ "pages": [
+ "wallet-app/introduction/getting-started",
+ "wallet-app/introduction/mini-apps",
+ "wallet-app/introduction/beta-faq"
+ ]
+ },
+ {
+ "group": "Build with MiniKit",
+ "pages": [
+
+ "wallet-app/build-with-minikit/overview",
+ "wallet-app/build-with-minikit/quickstart",
+ "wallet-app/build-with-minikit/existing-app-integration",
+ "wallet-app/build-with-minikit/debugging"
+ ]
+ },
+ {
+ "group": "Guides",
+ "pages": [
+ "wallet-app/guides/thinking-social"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "Cookbook",
+ "groups": [
+ {
+ "group": "Use Cases",
+ "pages": [
+ "cookbook/onboard-any-user",
+ "cookbook/accept-crypto-payments",
+ "cookbook/launch-ai-agents",
+ "cookbook/launch-tokens",
+ "cookbook/deploy-a-chain",
+ "cookbook/onchain-social",
+ "cookbook/defi-your-app",
+ "cookbook/go-gasless"
+ ]
+ },
+ {
+ "group": "Build with AI",
+ "pages": [
+ "cookbook/ai-prompting",
+ "cookbook/base-builder-mcp"
+ ]
+ }
+ ]
+ },
+ {
+ "tab": "Showcase",
+ "pages": [
+ "showcase"
+ ]
+ },
+ {
+ "tab": "Learn",
+ "groups": [
+ {
+ "group": "Building Onchain",
+ "pages": [
+ "learn/welcome"
+ ]
+ },
+ {
+ "group": "Onchain Concepts",
+ "pages": [
+ "learn/onchain-concepts/core-concepts",
+ "learn/onchain-concepts/understanding-the-onchain-tech-stack",
+ {
+ "group": "Web2 vs Building Onchain",
+ "pages": [
+ "learn/onchain-concepts/building-onchain-wallets",
+ "learn/onchain-concepts/building-onchain-identity",
+ "learn/onchain-concepts/building-onchain-gas",
+ "learn/onchain-concepts/building-onchain-nodes",
+ "learn/onchain-concepts/building-onchain-frontend-development",
+ "learn/onchain-concepts/building-onchain-onramps",
+ "learn/onchain-concepts/building-onchain-social-networks",
+ "learn/onchain-concepts/building-onchain-ai"
+ ]
+ },
+ "learn/onchain-concepts/development-flow"
+ ]
+ },
+ {
+ "group": "Ethereum 101",
+ "pages": [
+ "learn/introduction-to-ethereum",
+ "learn/ethereum-dev-overview",
+ "learn/ethereum-applications",
+ "learn/gas-use-in-eth-transactions",
+ "learn/evm-diagram",
+ "learn/guide-to-base"
+ ]
+ },
+ {
+ "group": "Onchain App Development",
+ "pages": [
+ "learn/deploy-with-fleek",
+ "learn/account-abstraction",
+ "learn/cross-chain-development",
+ "learn/client-side-development"
+ ]
+ },
+ {
+ "group": "Smart Contract Development",
+ "pages": [
+ {
+ "group": "Introduction to Solidity",
+ "pages": [
+ "learn/introduction-to-solidity/introduction-to-solidity-overview",
+ "learn/introduction-to-solidity/anatomy-of-a-smart-contract-vid",
+ {
+ "group": "Introduction to Solidity",
+ "pages": [
+ "learn/introduction-to-solidity/introduction-to-solidity-vid",
+ "learn/introduction-to-solidity/solidity-overview",
+ "learn/introduction-to-solidity/introduction-to-remix-vid",
+ "learn/introduction-to-solidity/introduction-to-remix",
+ "learn/introduction-to-solidity/deployment-in-remix-vid",
+ "learn/introduction-to-solidity/deployment-in-remix"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Contracts and Basic Functions",
+ "pages": [
+ "learn/contracts-and-basic-functions/intro-to-contracts-vid",
+ "learn/contracts-and-basic-functions/hello-world-step-by-step",
+ "learn/contracts-and-basic-functions/basic-types",
+ "learn/contracts-and-basic-functions/basic-functions-exercise"
+ ]
+ },
+ {
+ "group": "Deploying to a Testnet",
+ "pages": [
+ "learn/deployment-to-testnet/overview-of-test-networks-vid",
+ "learn/deployment-to-testnet/test-networks",
+ "learn/deployment-to-testnet/deployment-to-base-sepolia-sbs",
+ "learn/deployment-to-testnet/contract-verification-sbs",
+ "learn/deployment-to-testnet/deployment-to-testnet-exercise"
+ ]
+ },
+ {
+ "group": "Control Structures",
+ "pages": [
+ "learn/control-structures/standard-control-structures-vid",
+ "learn/control-structures/loops-vid",
+ "learn/control-structures/require-revert-error-vid",
+ "learn/control-structures/control-structures",
+ "learn/control-structures/control-structures-exercise"
+ ]
+ },
+ {
+ "group": "Storage in Solidity",
+ "pages": [
+ "learn/storage/simple-storage-video",
+ "learn/storage/simple-storage-sbs",
+ "learn/storage/how-storage-works-video",
+ "learn/storage/how-storage-works",
+ "learn/storage/storage-exercise"
+ ]
+ },
+ {
+ "group": "Arrays in Solidity",
+ "pages": [
+ "learn/arrays/arrays-in-solidity-vid",
+ "learn/arrays/writing-arrays-in-solidity-vid",
+ "learn/arrays/arrays-in-solidity",
+ "learn/arrays/filtering-an-array-sbs",
+ "learn/arrays/fixed-size-arrays-vid",
+ "learn/arrays/array-storage-layout-vid",
+ "learn/arrays/arrays-exercise"
+ ]
+ },
+ {
+ "group": "The Mapping Type",
+ "pages": [
+ "learn/mappings/mappings-vid",
+ "learn/mappings/using-msg-sender-vid",
+ "learn/mappings/mappings-sbs",
+ "learn/mappings/how-mappings-are-stored-vid",
+ "learn/mappings/mappings-exercise"
+ ]
+ },
+ {
+ "group": "Advanced Functions",
+ "pages": [
+ "learn/advanced-functions/function-visibility-vid",
+ "learn/advanced-functions/function-visibility",
+ "learn/advanced-functions/function-modifiers-vid",
+ "learn/advanced-functions/function-modifiers"
+ ]
+ },
+ {
+ "group": "Structs",
+ "pages": [
+ "learn/structs/structs-vid",
+ "learn/structs/structs-sbs",
+ "learn/structs/structs-exercise"
+ ]
+ },
+ {
+ "group": "Inheritance",
+ "pages": [
+ "learn/inheritance/inheritance-vid",
+ "learn/inheritance/inheritance-sbs",
+ "learn/inheritance/multiple-inheritance-vid",
+ "learn/inheritance/multiple-inheritance",
+ "learn/inheritance/abstract-contracts-vid",
+ "learn/inheritance/abstract-contracts-sbs",
+ "learn/inheritance/inheritance-exercise"
+ ]
+ },
+ {
+ "group": "Imports",
+ "pages": [
+ "learn/imports/imports-vid",
+ "learn/imports/imports-sbs",
+ "learn/imports/imports-exercise"
+ ]
+ },
+ {
+ "group": "Errors",
+ "pages": [
+ "learn/error-triage/error-triage-vid",
+ "learn/error-triage/error-triage",
+ "learn/error-triage/error-triage-exercise"
+ ]
+ },
+ {
+ "group": "The new Keyword",
+ "pages": [
+ "learn/new-keyword/creating-a-new-contract-vid",
+ "learn/new-keyword/new-keyword-sbs",
+ "learn/new-keyword/new-keyword-exercise"
+ ]
+ },
+ {
+ "group": "Contract to Contract Interactions",
+ "pages": [
+ "learn/interfaces/intro-to-interfaces-vid",
+ "learn/interfaces/calling-another-contract-vid",
+ "learn/interfaces/testing-the-interface-vid",
+ "learn/interfaces/contract-to-contract-interaction"
+ ]
+ },
+ {
+ "group": "Events",
+ "pages": [
+ "learn/events/hardhat-events-sbs"
+ ]
+ },
+ {
+ "group": "Address and Payable",
+ "pages": [
+ "learn/address-and-payable/address-and-payable"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Development with Foundry",
+ "pages": [
+ "learn/foundry/introduction-to-foundry",
+ "learn/foundry/testing"
+ ]
+ },
+ {
+ "group": "Development with Hardhat",
+ "pages": [
+ {
+ "group": "Hardhat Setup and Overview",
+ "pages": [
+ "learn/hardhat/overview",
+ "learn/hardhat/creating-project",
+ "learn/hardhat/setup"
+ ]
+ },
+ {
+ "group": "Testing with Typescript",
+ "pages": [
+ "learn/hardhat/testing",
+ "learn/hardhat/writing-tests",
+ "learn/hardhat/contract-abi-testing",
+ "learn/hardhat/testing-guide"
+ ]
+ },
+ {
+ "group": "Etherscan",
+ "pages": [
+ "learn/hardhat/etherscan-guide",
+ "learn/hardhat/etherscan-video"
+ ]
+ },
+ {
+ "group": "Deploying Smart Contracts",
+ "pages": [
+ "learn/hardhat/installing-deploy",
+ "learn/hardhat/setup-deploy-script",
+ "learn/hardhat/testing-deployment",
+ "learn/hardhat/network-configuration",
+ "learn/hardhat/deployment",
+ "learn/hardhat/deployment-guide"
+ ]
+ },
+ {
+ "group": "Verifying Smart Contracts",
+ "pages": [
+ "learn/hardhat/verify-video",
+ "learn/hardhat/verify-guide"
+ ]
+ },
+ {
+ "group": "Mainnet Forking",
+ "pages": [
+ "learn/hardhat/fork-video",
+ "learn/hardhat/fork-guide"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Token Development",
+ "pages": [
+ {
+ "group": "Introduction to Tokens",
+ "pages": [
+ "learn/intro-to-tokens/intro-to-tokens-vid",
+ "learn/intro-to-tokens/misconceptions-about-tokens-vid",
+ "learn/intro-to-tokens/tokens-overview"
+ ]
+ },
+ {
+ "group": "Minimal Tokens",
+ "pages": [
+ "learn/minimal-tokens/creating-a-minimal-token-vid",
+ "learn/minimal-tokens/transferring-a-minimal-token-vid",
+ "learn/minimal-tokens/minimal-token-sbs",
+ "learn/minimal-tokens/minimal-tokens-exercise"
+ ]
+ },
+ {
+ "group": "ERC-20 Tokens",
+ "pages": [
+ "learn/erc-20-token/analyzing-erc-20-vid",
+ "learn/erc-20-token/erc-20-standard",
+ "learn/erc-20-token/openzeppelin-erc-20-vid",
+ "learn/erc-20-token/erc-20-testing-vid",
+ "learn/erc-20-token/erc-20-token-sbs",
+ "learn/erc-20-token/erc-20-exercise"
+ ]
+ },
+ {
+ "group": "ERC-721 Tokens",
+ "pages": [
+ "learn/erc-721-token/erc-721-standard-video",
+ "learn/erc-721-token/erc-721-standard",
+ "learn/erc-721-token/erc-721-on-opensea-vid",
+ "learn/erc-721-token/openzeppelin-erc-721-vid",
+ "learn/erc-721-token/implementing-an-erc-721-vid",
+ "learn/erc-721-token/erc-721-sbs",
+ "learn/erc-721-token/erc-721-exercise"
+ ]
+ }
+ ]
+ },
+ {
+ "group": "Exercise Contracts",
+ "pages": [
+ "learn/exercise-contracts"
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ "logo": {
+ "light": "/logo/logo_light.svg",
+ "dark": "/logo/logo_dark.svg"
+ },
+ "navbar": {
+ "links": [
+ {
+ "label": "Blog",
+ "href": "https://blog.base.dev/"
+ },
+ {
+ "label": "GitHub",
+ "href": "https://github.com/base"
+ },
+ {
+ "label": "Support",
+ "href": "https://discord.com/invite/base"
+ }
+ ],
+ "primary": {
+ "type": "button",
+ "label": "Base.org",
+ "href": "https://base.org"
+ }
+ },
+ "footer": {
+ "socials": {
+ "x": "https://x.com/mintlify",
+ "github": "https://github.com/mintlify",
+ "linkedin": "https://linkedin.com/company/mintlify"
+ }
+ }
+}
\ No newline at end of file
diff --git a/docs/footer.tsx b/docs/footer.tsx
deleted file mode 100644
index e679c1af..00000000
--- a/docs/footer.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-export default function Footer() {
- return (
-
-
-
-
-
- Base Docs
-
-
-
- © {new Date().getFullYear()} base.org. All rights reserved.
-
-
- {/* TODO: Add a link to the privacy policy, terms of service, and cookie policy all in the same row in 3 columns*/}
-
-
-
-
-
- )
-}
diff --git a/docs/get-started/ai-prompting.mdx b/docs/get-started/ai-prompting.mdx
new file mode 100644
index 00000000..6c5725ec
--- /dev/null
+++ b/docs/get-started/ai-prompting.mdx
@@ -0,0 +1,9 @@
+---
+sidebarTitle: AI-powered IDEs
+title: Use AI-powered IDEs
+description: How to use AI-powered IDEs to generate code for OnchainKit.
+---
+
+import AiPowered from "/snippets/ai-powered.mdx";
+
+
diff --git a/docs/get-started/base.mdx b/docs/get-started/base.mdx
new file mode 100644
index 00000000..b592e034
--- /dev/null
+++ b/docs/get-started/base.mdx
@@ -0,0 +1,85 @@
+---
+title: Base
+description: "The #1 Ethereum Layer 2, incubated by Coinbase"
+---
+
+Build on Base and choose the features that fits your needs — from sub-cent global payments to creator-first monetization and built-in distribution.
+
+
+
+
+
+
+## Base Offers
+
+
+
+**Payments & Financial Services:** Move value globally with sub-second, sub-cent payments and build on existing financial services for trading, yield generation, and more.
+
+
+**Creator Monetization:** Creators of all kinds are exploring new ways to monetize their work. Innovate on the frontier of creator monetiziation with the creator economy on Base.
+
+
+**Comprehensive Builder Support:** Base provides developer tools, infrastructure, and support. Plus, Base has one of the largest onchain builder communities whith which you can collaborate and grow.
+
+
+**Launch to real users:** Tap Base activations, grants, and mini-app channels that surface your project to millions.
+
+
+
+
+### Start Building
+
+
+
+ Build and deploy your app in minutes.
+
+
+ Launch a new token.
+
+
+ Deploy your own chain on Base.
+
+
+
+Explore [all products](/get-started/products) and [use cases](/get-started/use-cases).
+
+### Get Funded
+
+
+
+ Get rewarded up to 2 ETH weekly through the Builder Rewards Program.
+
+
+ Apply for fast, retroactive, Base Builder Grants (~1-5 ETH).
+
+
+ A global program for builders creating the next wave of onchain apps.
+
+
+ Apply for OP Retro Funding for your contributions to the Base ecosystem.
+
+
+
+### Reach More Users
+
+
+
+ Mini Apps run directly inside the social feed: Make your existing app a Mini App or build a new one.
+
+
+ Showcase your project to the Base community and get discovered.
+
+
+
+
+
+
diff --git a/docs/get-started/build-app.mdx b/docs/get-started/build-app.mdx
new file mode 100644
index 00000000..a2f2ab91
--- /dev/null
+++ b/docs/get-started/build-app.mdx
@@ -0,0 +1,250 @@
+---
+title: Build an App
+description: A guide to building a next.js app on Base using OnchainKit
+---
+
+Welcome to the Base quickstart guide! In this walkthrough, we'll create a simple onchain app from start to finish. Whether you're a seasoned developer or just starting out, this guide has got you covered.
+
+## What You'll Achieve
+
+By the end of this quickstart, you'll have built an onchain app by:
+
+- Configuring your development environment
+- Deploying your smart contracts to Base
+- Interacting with your deployed contracts from the frontend
+
+Our simple app will be an onchain tally app which lets you add to a total tally, stored onchain, by pressing a button.
+
+
+**Why Base?**
+
+Base is a fast, low-cost, builder-friendly Ethereum L2 built to bring the next billion users onchain. By following this guide, you'll join a vibrant ecosystem of developers, creators, and innovators who are building a global onchain economy.
+
+
+
+## Set Up Your Development Environment
+
+
+
+ OnchainKit is a library of ready-to-use React components and Typescript utilities for building onchain apps. Run the following command in your terminal and follow the prompts to bootstrap your project.
+
+ ```bash [Terminal]
+ npm create onchain@latest
+ ```
+
+ The prompots will ask you for a CDP API Key which you can get [here](https://portal.cdp.coinbase.com/projects/api-keys/client-key).
+
+ Once you've gone through the prompts, you'll have a new project directory with a basic OnchainKit app. Run the following to see it live.
+
+ ```bash [Terminal]
+ cd my-onchainkit-app
+ npm install
+ npm run dev
+ ```
+
+ You should see the following screen.
+
+
+
+
+
+ Once we've deployed our contracts, we'll add a button that lets us interact with our contracts.
+
+
+
+ The total tally will be stored onchain in a smart contract. We'll use the Foundry framework to deploy our contract to the Base Sepolia testnet.
+
+ 1. Create a new "contracts" folder in the root of your project
+
+ ```bash [Terminal]
+ mkdir contracts && cd contracts
+ ```
+
+ 2. Install and initialize Foundry
+
+ ```bash [Terminal]
+ curl -L https://foundry.paradigm.xyz | bash
+ foundryup
+ forge init --no-git
+ ```
+
+ Open the project and find the `Counter.sol` contract file in the `/contracts/src` folder. You'll find the simple logic for our tally app.
+
+
+ **--no-git**
+
+ Because `contracts` is a folder in our project, we don't want to initialize a separate git repository for it, so we add the `--no-git` flag.
+
+
+
+ To deploy your smart contracts to Base, you need two key components:
+
+ 1. A node connection to interact with the Base network
+ 2. A funded private key to deploy the contract
+
+ Let's set up both of these:
+
+ - Create a `.env` file in your `contracts` directory and add the Base and Base Sepolia RPC URLs
+
+ ```bash [contracts/.env]
+ BASE_RPC_URL="https://mainnet.base.org"
+ BASE_SEPOLIA_RPC_URL="https://sepolia.base.org"
+ ```
+
+ -Load your environment variables
+
+ ```bash [Terminal]
+ source .env
+ ```
+
+
+ **Base Sepolia**
+
+ Base Sepolia is the test network for Base, which we will use for the rest of this guide. You can obtain free Base Sepolia ETH from one of the [faucets listed here](/base-chain/tools/network-faucets).
+
+
+
+ A private key with testnet funds is required to deploy the contract. You can generate a fresh private key [here](https://visualkey.link/).
+
+ 1. Store your private key in Foundry's secure keystore
+
+ ```bash [Terminal]
+ cast wallet import deployer --interactive
+ ```
+
+ 2. When prompted enter your private key and a password.
+
+ Your private key is stored in `~/.foundry/keystores` which is not tracked by git.
+
+
+ Never share or commit your private key. Always keep it secure and handle with care.
+
+
+
+
+## Deploy Your Contracts
+
+Now that your environment is set up, let's deploy your contracts to Base Sepolia. The foundry project provides a deploy script that will deploy the Counter.sol contract.
+
+
+
+ 1. Use the following command to compile and deploy your contract
+
+ ```bash [Terminal]
+ forge create ./src/Counter.sol:Counter --rpc-url $BASE_SEPOLIA_RPC_URL --account deployer
+ ```
+
+ Note the format of the contract being deployed is `:`.
+
+
+ After successful deployment, the transaction hash will be printed to the console output
+
+ Copy the deployed contract address and add it to your `.env` file
+
+ ```bash
+ COUNTER_CONTRACT_ADDRESS="0x..."
+ ```
+
+
+
+ ```bash [Terminal]
+ source .env
+ ```
+
+
+ To ensure your contract was deployed successfully:
+
+ 1. Check the transaction on [Sepolia Basescan](https://sepolia.basescan.org/).
+ 2. Use the `cast` command to interact with your deployed contract from the command line
+
+ ```bash
+ cast call $COUNTER_CONTRACT_ADDRESS "number()(uint256)" --rpc-url $BASE_SEPOLIA_RPC_URL
+ ```
+
+ This will return the initial value of the Counter contract's `number` storage variable, which will be `0`.
+
+
+
+**Congratulations! You've deployed your smart contract to Base Sepolia!**
+
+Now lets connect the frontend to interact with your recently deployed contract.
+
+## Interacting with your contract
+
+To interact with the smart contract logic, we need to submit an onchain transaction. We can do this easily with the `Transaction` component. This is a simplified version of the `Transaction` component, designed to streamline the integration process. Instead of manually defining each subcomponent and prop, we can use this shorthand version which renders our suggested implementation of the component and includes the `TransactionButton` and `TransactionToast` components.
+
+
+
+ Lets add the `Transaction` component to our `page.tsx` file. Delete the existing content in the `main` tag and replace it with the snippet below.
+
+ ```tsx twoslash [page.tsx]
+ // @noErrors: 2307 - Cannot find module '@/calls'
+ import { Transaction } from '@coinbase/onchainkit/transaction';
+ import { calls } from '@/calls';
+
+
+
+ ;
+ ```
+
+
+ In the previous code snippet, you'll see we imported `calls` from the `calls.ts` file. This file provides the details needed to interact with our contract and call the `increment` function. Create a new `calls.ts` file in the same folder as your `page.tsx` file and add the following code.
+
+ ```ts twoslash [calls.ts]
+ const counterContractAddress = '0x...'; // add your contract address here
+ const counterContractAbi = [
+ {
+ type: 'function',
+ name: 'increment',
+ inputs: [],
+ outputs: [],
+ stateMutability: 'nonpayable',
+ },
+ ] as const;
+
+ export const calls = [
+ {
+ address: counterContractAddress,
+ abi: counterContractAbi,
+ functionName: 'increment',
+ args: [],
+ },
+ ];
+ ```
+
+
+ **Contract Address**
+
+ The `calls.ts` file contains the details of the contract interaction, including the contract address, which we saved in the previous step.
+
+
+
+ Now, when you connect a wallet and click on the `Transact` button and approve the transaction, it will increment the tally onchain by one.
+
+ We can verify that the onchain count took place onchain by once again using `cast` to call the `number` function on our contract.
+
+ ```bash [Terminal]
+ cast call $COUNTER_CONTRACT_ADDRESS "number()(uint256)" --rpc-url $BASE_SEPOLIA_RPC_URL
+ ```
+
+ If the transaction was successful, the tally should have incremented by one!
+
+
+
+We now have a working onchain tally app! While the example is simple, it illustrates the end to end process of building on onchain app. We:
+
+- Configured a project with frontend and onchain infrastructure
+- Deployed a smart contract to Base Sepolia
+- Interacted with the contract from the frontend
+
+## Further Improvements
+
+This is just the beginning. There are many ways we can improve upon this app. For example, we could:
+
+- Make the `increment` transaction gasless by integrating with [Paymaster](/onchainkit/transaction/transaction#sponsor-with-paymaster-capabilities)
+- Improve the wallet connection and sign up flow with the [WalletModal](/onchainkit/wallet/wallet-modal) component
+- Add onchain [Identity](/onchainkit/identity/identity) so we know who added the most recent tally
diff --git a/docs/get-started/concepts.mdx b/docs/get-started/concepts.mdx
new file mode 100644
index 00000000..748c81ca
--- /dev/null
+++ b/docs/get-started/concepts.mdx
@@ -0,0 +1,13 @@
+---
+title: 'Core Concepts'
+---
+
+Key concept overview:
+- Gas & Faucets
+- Blocks
+- Transactions
+- Signatures
+- Smart Contracts
+- Wallets
+- Nodes
+...
\ No newline at end of file
diff --git a/docs/get-started/deploy-chain.mdx b/docs/get-started/deploy-chain.mdx
new file mode 100644
index 00000000..4f8ff591
--- /dev/null
+++ b/docs/get-started/deploy-chain.mdx
@@ -0,0 +1,286 @@
+---
+title: 'Deploy an Appchain'
+keywords: ['deploy a chain', 'deploy a base chain', 'deploy a base appchain', 'deploy a base l3', 'l3']
+---
+
+Transform your high-traffic application into a dedicated blockchain with **1-second block times**, **sub-cent transactions**, and **enterprise-grade infrastructure**. Base Appchains provide dedicated blockspace for mature applications that need to scale beyond shared network limitations.
+
+
+
+Get early access to Base Appchains and transform your scaling strategy
+
+
+
+Jump to our step-by-step onboarding process
+
+
+
+## What Are Base Appchains?
+
+Base Appchains are **app-specific Layer 3 rollups** built on Base that provide dedicated blockspace for individual applications. Instead of competing with thousands of other apps for network resources, you get your own high-performance blockchain that rolls up to Base and inherits Ethereum's security.
+
+
+Layer 3 appchains roll up to Base (Layer 2), which settles on Ethereum, providing you with dedicated performance while maintaining the security guarantees of the Ethereum ecosystem.
+
+
+Think of it as the difference between **sharing a highway during rush hour** versus **having your own dedicated express lane**. With shared blockspace, your app's performance depends on network-wide activity. With an Appchain, you get consistent, predictable performance regardless of what's happening elsewhere.
+
+
+
+Compete with other apps for network resources, leading to variable performance, unpredictable costs, and user experience issues during peak times.
+
+
+
+Your own infrastructure with predictable performance, custom gas tokens, full control over throughput, and consistent user experience.
+
+
+
+## Why Choose Base Appchains?
+
+### High-Speed Performance Built for Scale
+
+Stop letting network congestion impact your user experience. Base Appchains deliver **1-second block times** and **sub-10 second withdrawals**, making them 10x faster than typical blockchain interactions.
+
+
+Gaming applications like Blocklords have processed over 80 million transactions across 1.8 million wallets using Base Appchains, achieving the scale needed for their gaming ecosystem without performance degradation.
+
+
+### Predictable, Cost-Effective Operations
+
+Replace unpredictable gas costs with **fixed monthly pricing**. Process transactions for fractions of cents while eliminating the need to sponsor gas costs for your users.
+
+
+
+Variable gas costs, expensive user onboarding, unpredictable operational expenses, and complex gas sponsorship management.
+
+
+
+Fixed monthly pricing, sub-cent transactions, predictable budgeting, and no gas sponsorship complexity.
+
+
+
+### Enterprise-Grade Infrastructure
+
+With Base Appchains, you get:
+
+
+Fully-managed sequencer and node infrastructure
+
+
+Automated maintenance and upgrades
+
+
+Real-time monitoring and performance alerts
+
+
+Dedicated block explorer for your chain
+
+
+### Seamless Base Ecosystem Integration
+
+Maintain access to Base's **users**, **liquidity**, and **developer tools** while getting dedicated performance. Your Appchain integrates seamlessly with Smart Wallet, Paymaster, OnchainKit, and other Base ecosystem tools.
+
+
+
+Enable seamless account abstraction across Base Mainnet and your Appchain with unified user experiences.
+
+
+
+Sponsor gas costs across multiple chains with unified billing and simplified user onboarding.
+
+
+
+Use the same familiar developer tools and components across the entire Base ecosystem.
+
+
+
+## Technical Architecture
+
+Base Appchains are built on the **OP Enclave framework**, providing fast withdrawals and seamless integration with Base Mainnet. This architecture enables near-instant bridging while maintaining security through innovative proving mechanisms.
+
+
+
+Built on Optimism's latest technology for **near-instant bridging** between your Appchain and Base Mainnet. Users can move funds in seconds rather than the typical 7-day withdrawal periods of traditional rollups.
+
+
+
+Uses **Amazon S3** for cost-efficient data storage while maintaining security through **AWS Nitro Enclave** verification. This approach significantly reduces costs while ensuring data integrity and availability.
+
+
+
+Control which contracts can be called on your chain, effectively managing blockspace allocation. Implement **custom gas tokens** and **permission systems** while protecting users from censorship through guaranteed deposit lanes.
+
+
+
+Unlike traditional rollups that rely on challenge periods, Base Appchains use **immediate state proving** through secure enclaves, enabling instant finality and faster user experiences.
+
+
+
+## Use Cases & Success Stories
+
+Base Appchains power applications across gaming, DeFi, and enterprise sectors that require high performance and predictable costs.
+
+
+
+Process millions of micro-transactions for in-game actions, NFT trades, and player interactions without network congestion affecting gameplay performance.
+
+**Success Story**: Super Champs chose Base Appchains for consistent throughput, comparing the experience to "gaming on iOS" - smooth, predictable, and reliable.
+
+
+
+Handle high-frequency trading, yield farming, and complex financial operations with consistent, low-cost transactions that don't fluctuate with network activity.
+
+**Success Story**: Applications processing high-volume trading data benefit from predictable costs and dedicated throughput for time-sensitive operations.
+
+
+
+Deploy compliance-ready solutions with dedicated infrastructure, custom permissions, and the ability to manage access controls while maintaining transparency.
+
+**Success Story**: Proof 8 uses blockchain technology for verifiable inventory ownership in warehouses and distilleries, where enterprise customers prioritize performance, security, and privacy.
+
+
+
+
+Base Appchains are designed for **mature projects** with significant transaction volumes. If you're just starting out or have low transaction volumes, consider building on Base Mainnet first to establish product-market fit.
+
+
+## When Should You Consider an Appchain?
+
+Base Appchains are ideal for applications that have outgrown shared blockspace limitations. Use this checklist to determine if an Appchain is right for your project:
+
+
+**High Transaction Volume**: Your app generates thousands of transactions daily and performance is affected by network congestion
+
+
+
+**Significant Gas Sponsorship**: You're spending substantial amounts sponsoring gas costs for users through Paymaster or similar solutions
+
+
+
+**Performance-Critical Operations**: User experience is negatively impacted by variable transaction times or network congestion
+
+
+
+**Custom Requirements**: You need custom gas tokens, specialized permissions, or governance mechanisms not available on shared chains
+
+
+
+**Predictable Costs**: Fixed operational costs are important for your business model and budgeting
+
+
+
+If you're considering launching your own L1 or L2 blockchain, Base Appchains offer a compelling alternative with faster time-to-market, proven infrastructure, and immediate access to Base's ecosystem.
+
+
+
+## Getting Started
+
+Base Appchains are currently in **beta with a waitlist**. We're working with select partners to refine the platform before broader availability.
+
+
+
+Complete our application form to be considered for early access to Base Appchains. We prioritize applications from mature projects with clear scaling needs.
+
+
+Join the Base Appchains beta program
+
+
+
+
+Our team will review your application and schedule a consultation to understand your specific scaling requirements, transaction patterns, and technical needs.
+
+
+During the consultation, we'll help you determine if an Appchain is the right solution and design the optimal configuration for your use case.
+
+
+
+
+Once approved, you'll receive access to both testnet and mainnet features. Start with the **$1/month testnet** to validate your architecture and integration.
+
+
+Use the testnet environment to test bridging, custom gas tokens, and permission systems before deploying to mainnet.
+
+
+
+
+Deploy to production with full enterprise support, monitoring, and maintenance included. Our team provides ongoing support for your Appchain infrastructure.
+
+
+Full technical support during launch and ongoing operations
+
+
+
+
+
+**Coming Soon**: Self-serve, one-click deployment will be available for approved projects, making it even easier to launch and manage your Appchain.
+
+
+## Frequently Asked Questions
+
+
+
+Base Appchains offer significant advantages over launching independent blockchain infrastructure:
+
+- **Faster time-to-market**: Deploy in weeks, not months or years
+- **Proven infrastructure**: Built on battle-tested Base and OP Stack technology
+- **Immediate ecosystem access**: Users and liquidity from Base Mainnet
+- **Lower operational overhead**: Fully managed infrastructure and maintenance
+- **Ethereum alignment**: Inherit security without custom validator sets or consensus mechanisms
+
+
+
+Base Appchains provide **seamless onboarding** similar to Base Mainnet applications:
+
+- Users can bridge funds between Base and your Appchain in **seconds**
+- Same wallet experience across Base ecosystem
+- Smart Wallet integration for account abstraction
+- Familiar transaction patterns and interfaces
+
+
+
+**Coinbase manages the core infrastructure** on your behalf, including:
+
+- Sequencer operation and maintenance
+- Node infrastructure and upgrades
+- Security key management
+- Monitoring and alerting systems
+
+You maintain control over **application-level configurations** like gas tokens, permissions, and governance while benefiting from enterprise-grade infrastructure management.
+
+
+
+Base Appchains use the **OP Enclave framework** for **near-instant bridging**:
+
+- Move funds between Base and your Appchain in seconds
+- No 7-day withdrawal periods like traditional rollups
+- Maintains security through cryptographic proofs
+- Seamless user experience across chains
+
+
+
+Base Appchains balance **operational efficiency** with **censorship resistance**:
+
+- Custom permissions control high-throughput operations
+- **Guaranteed deposit lanes** prevent censorship through direct Base deposits
+- Users always have recourse through the Base bridge
+- Inherits Ethereum's long-term decentralization roadmap
+
+
+
+## Ready to Scale?
+
+Base Appchains represent the next evolution in blockchain scaling, providing dedicated infrastructure without sacrificing ecosystem benefits. Whether you're processing millions of gaming transactions, handling high-frequency DeFi operations, or building enterprise solutions, Appchains deliver the performance and predictability your users expect.
+
+
+
+Apply for early access to Base Appchains beta program
+
+
+
+Discover the full ecosystem of Base developer tools and integrations
+
+
+
+
+**Next Steps**: After joining the waitlist, explore Base's developer documentation to understand how Appchains integrate with Smart Wallet, Paymaster, OnchainKit, and other ecosystem tools.
+
\ No newline at end of file
diff --git a/docs/get-started/deploy-smart-contracts.mdx b/docs/get-started/deploy-smart-contracts.mdx
new file mode 100644
index 00000000..a40e43d5
--- /dev/null
+++ b/docs/get-started/deploy-smart-contracts.mdx
@@ -0,0 +1,144 @@
+---
+title: 'Deploy Smart Contracts'
+description: A guide to help you get started with deploying your smart contracts on Base.
+---
+
+Welcome to the Base deployment quickstart guide! This comprehensive walkthrough will help you set up your environment and deploy smart contracts on Base. Whether you're a seasoned developer or just starting out, this guide has got you covered.
+
+## What You'll Achieve
+
+By the end of this quickstart, you'll be able to:
+
+- Set up your development environment to deploy on Base
+- Deploy your smart contracts to Base
+- Connect your frontend to your smart contracts
+
+
+**Why Base?**
+
+Base is a fast, low-cost, builder-friendly Ethereum L2 built to bring the next billion users onchain. By following this guide, you'll join a vibrant ecosystem of developers, creators, and innovators who are building a global onchain economy.
+
+
+
+## Set Up Your Development Environment
+
+1. Create a new project directory
+
+```bash
+mkdir my-base-project && cd my-base-project
+```
+
+2. Install Foundry, a powerful framework for smart contract development
+
+```bash
+curl -L https://foundry.paradigm.xyz | bash
+foundryup
+```
+
+This installs Foundry and updates it to the latest version.
+
+3. Initialize a new Solidity project
+
+```bash
+forge init
+```
+
+Your Foundry project is now ready. You'll find an example contract in the `src` directory, which you can replace with your own contracts. For the purposes of this guide, we'll use the Counter contract provided in `/src/Counter.sol`
+
+
+Foundry provides a suite of tools for Ethereum application development, including Forge (for testing), Cast (for interacting with the chain), and Anvil (for setting up a local node). You can learn more about Foundry [here](https://book.getfoundry.sh/).
+
+
+
+## Configure Foundry with Base
+
+To deploy your smart contracts to Base, you need two key components:
+
+1. A node connection to interact with the Base network
+2. A funded private key to deploy the contract
+
+Let's set up both of these:
+
+### 1. Set up your node connection
+
+1. Create a `.env` file in your project's root directory
+2. Add the Base network RPC URL to your `.env` file
+
+```bash
+BASE_RPC_URL="https://mainnet.base.org"
+BASE_SEPOLIA_RPC_URL="https://sepolia.base.org"
+```
+
+3. Load your environment variables
+
+```bash
+source .env
+```
+
+
+Base Sepolia is the test network for Base, which we will use for the rest of this guide. You can obtain free Base Sepolia ETH from one of the [faucets listed here](/base-chain/tools/network-faucets).
+
+
+
+### 2. Secure your private key
+
+1. Store your private key in Foundry's secure keystore
+
+```bash
+cast wallet import deployer --interactive
+```
+
+2. When prompted enter your private key and a password.
+
+Your private key is stored in `~/.foundry/keystores` which is not tracked by git.
+
+
+Never share or commit your private key. Always keep it secure and handle with care.
+
+
+
+## Deploy Your Contracts
+
+Now that your environment is set up, let's deploy your contracts to Base Sepolia.
+
+1. Use the following command to compile and deploy your contract
+
+```bash
+forge create ./src/Counter.sol:Counter --rpc-url $BASE_SEPOLIA_RPC_URL --account deployer
+```
+
+Note the format of the contract being deployed is `:`.
+
+2. After successful deployment, the transaction hash will be printed to the console output
+
+3. Copy the deployed contract address and add it to your `.env` file
+
+```bash
+COUNTER_CONTRACT_ADDRESS="0x..."
+```
+
+4. Load the new environment variable
+
+```bash
+source .env
+```
+
+### Verify Your Deployment
+
+To ensure your contract was deployed successfully:
+
+1. Check the transaction on [Sepolia Basescan](https://sepolia.basescan.org/).
+2. Use the `cast` command to interact with your deployed contract from the command line
+
+```bash
+cast call $COUNTER_CONTRACT_ADDRESS "number()(uint256)" --rpc-url $BASE_SEPOLIA_RPC_URL
+```
+This will return the initial value of the Counter contract's `number` storage variable, which will be `0`.
+
+**Congratulations! You've deployed your smart contracts to Base Sepolia!**
+
+## Next Steps
+
+- Use [Onchainkit](https://onchainkit.com) to connect your frontend to your contracts! Onchainkit is a library of ready-to-use React components and Typescript utilities.
+- Learn more about interacting with your contracts in the command line using Foundry from our [Foundry tutorial](/cookbook/smart-contract-development/foundry/deploy-with-foundry).
+
diff --git a/docs/get-started/get-funded.mdx b/docs/get-started/get-funded.mdx
new file mode 100644
index 00000000..19dc73b0
--- /dev/null
+++ b/docs/get-started/get-funded.mdx
@@ -0,0 +1,205 @@
+---
+title: 'Get Funded'
+description: "The Base ecosystem offers multiple funding pathways designed specifically for builders at every stage—from weekend experiments to full-scale ventures."
+---
+
+## Funding Pathways
+
+Whether you're just starting to experiment or ready to become a full-time founder, Base provides structured funding opportunities that grow with your ambitions. Each pathway is designed for different stages of your builder journey, with clear progression paths between programs.
+
+
+
+ 2 ETH in weekly rewards for prototypes and experiments through Talent Protocol.
+
+
+
+ Fast, retroactive grants ranging from 1-5 ETH. Perfect for shipped projects ready to scale.
+
+
+
+ Get rewarded for creating public goods that benefit the entire Base ecosystem.
+
+
+
+ Comprehensive founder program with mentorship, resources, and significant funding opportunities.
+
+
+
+## Before You Apply
+
+
+
+ Deploy your project to Base mainnet. Some programs will consider early prototypes and testnet deployments but being deployed on Base mainnet gives you a leg up.
+
+
+ Verify your contract is deployed and functional on Base.
+
+
+
+
+ Create clear documentation including setup instructions, project goals, and demo materials.
+
+
+ Ensure your README includes installation steps and your project has a working demo.
+
+
+
+
+ Measure relevant metrics like user adoption, transaction volume, or community engagement.
+
+
+ Even small metrics matter. Early usage data strengthens your application significantly.
+
+
+
+
+## Weekly Rewards: Start Building Today
+
+The Builder Rewards Program distributes 2 ETH weekly to active builders—perfect for experimentation and learning.
+
+[Join Builder Rewards Program](https://www.builderscore.xyz/)
+
+### How It Works
+1. Build anything on Base (prototypes welcome)
+2. Share your progress on social media
+3. Earn weekly rewards based on activity
+4. No minimum project size required
+
+
+Prototyping counts! Early-stage projects and experiments are explicitly encouraged.
+
+
+### Perfect For
+- First-time Base builders learning the ecosystem
+- Developers experimenting with new ideas
+- Weekend projects and hackathon submissions
+- Educational content creation
+
+
+Most successful Base builders started with weekly rewards while learning. Use this as your entry point to the ecosystem.
+
+
+## Builder Grants: Live on Base
+
+Need funding for your shipped project? Base Builder Grants offer retroactive funding for projects that demonstrate real value to the ecosystem.
+
+[Apply for Builder Grants](https://paragraph.com/@grants.base.eth/calling-based-builders)
+
+### What You Get
+- **Grant Range:** 1-5 ETH
+- **Application Type:** Retroactive (for shipped projects)
+
+
+
+Already have a working prototype? You're ready to apply. Builder Grants reward shipped code over perfect pitches.
+
+
+## OP Retro Funding: Long-term Impact
+
+Creating tools, infrastructure, or resources that benefit everyone? OP Retro Funding recognizes and rewards public goods contributions to the Base ecosystem.
+
+[Track Your Impact on Atlas](https://atlas.optimism.io/)
+
+### Ideal Projects
+- Open-source libraries and development frameworks
+- Community tools and ecosystem infrastructure
+- Analytics dashboards and ecosystem insights
+
+### Funding Approach
+- **Focus:** Public goods and ecosystem-wide impact
+- **Recognition:** Both funding and ecosystem acknowledgment
+
+### Application Process
+
+
+
+ Create tools, content, or infrastructure that benefits the entire Base community.
+
+
+ Ensure your project is open-source and publicly accessible.
+
+
+
+
+ Track usage metrics, community adoption, and ecosystem benefits.
+
+
+ Include testimonials from other builders who've used your tools.
+
+
+
+
+ Use the Optimism Atlas platform to track contributions and apply for funding.
+
+
+ Complete your Atlas profile with detailed project information.
+
+
+
+
+## Base Batches: The Founder Track
+
+For builders ready to become founders, Base Batches provides the most comprehensive support system in the ecosystem. This recurring initiative combines structured development, mentorship, and significant funding opportunities.
+
+[Learn More About Base Batches](https://basebatches.xyz)
+
+### Program Structure
+
+
+
+ Rapid development sprint with direct access to mentorship and technical resources from the Base team.
+
+
+ Complete your MVP and receive detailed technical feedback from Base core team members.
+
+
+
+
+ 4-week structured support to refine your product, validate your market, and develop your business model.
+
+
+ Achieve product-market fit indicators and prepare comprehensive pitch materials.
+
+
+
+
+ Present to top-tier investors including Coinbase Ventures and other leading crypto VCs.
+
+
+ Secure follow-on funding commitments from investor network.
+
+
+
+
+### What Each Cohort Receives
+- **Direct mentorship** from Base team members and industry experts
+- **Access to exclusive resources** including technical infrastructure and partnerships
+- **Investor introductions** to Coinbase Ventures and leading crypto funds
+- **Deep ecosystem integration** with Base protocol and community
+
+
+Base Batches #001 is currently in progress. The next Base Batches will be announced in the second half of 2025.
+
+
+## Get Started Today
+
+Building something on Base? The ecosystem is ready to support you at every stage of your journey.
+
+
+The best builders in Base started exactly where you are now. Most successful applicants begin with smaller programs and progress to larger opportunities.
+
+
+### Your Next Steps
+
+1. **Choose your starting point** based on your current project stage
+2. **Join the Base community** on Discord and social media
+3. **Start building** and documenting your progress
+4. **Apply to your selected program** with confidence
+
+
+Don't wait for the "perfect" project. The Base ecosystem values builders at all stages. Start where you are and grow with the community's support.
+
+
+Remember: Whether you're tinkering with a weekend project or building the next breakthrough application, there's a funding path designed for your journey. The Base ecosystem has invested millions in builders who started with nothing more than an idea and the determination to build.
+
+**Take the first step today.**
\ No newline at end of file
diff --git a/docs/get-started/launch-token.mdx b/docs/get-started/launch-token.mdx
new file mode 100644
index 00000000..aa3402cd
--- /dev/null
+++ b/docs/get-started/launch-token.mdx
@@ -0,0 +1,423 @@
+---
+title: 'Launch a Token'
+---
+
+
+Launching a token on Base can be accomplished through multiple approaches, from no-code platforms to custom smart contract development. This guide helps you choose the right method and provides implementation details for both approaches.
+
+
+
+ Use existing platforms like Zora, Clanker, or Flaunch for quick deployment
+
+
+ Build custom ERC-20 tokens with Foundry for maximum control
+
+
+ Decision framework to help you pick the right method
+
+
+ Security, community building, and post-launch guidance
+
+
+
+
+**For most users:** Use existing token launch platforms like Zora, Clanker, or Flaunch. These tools handle the technical complexity while providing unique features for different use cases.
+
+**For developers:** Build custom ERC-20 tokens using Foundry and OpenZeppelin's battle-tested contracts for maximum control and customization.
+
+
+## Choosing Your Launch Approach
+
+### Platform-Based Launch (Recommended for Most Users)
+
+Choose a platform when you want:
+- Quick deployment without coding
+- Built-in community features
+- Automated liquidity management
+- Social integration capabilities
+
+### Custom Development (For Developers)
+
+Build your own smart contract when you need:
+- Custom tokenomics or functionality
+- Full control over contract behavior
+- Integration with existing systems
+- Advanced security requirements
+
+## Token Launch Platforms on Base
+
+### Zora
+**Best for:** Content creators and social tokens
+
+Zora transforms every post into a tradeable ERC-20 token with automatic Uniswap integration. Each post becomes a "coin" with 1 billion supply, creators receive 10 million tokens, and earn 1% of all trading fees.
+
+**Key Features:**
+- Social-first token creation
+- Automatic liquidity pools
+- Revenue sharing for creators
+- Built-in trading interface
+
+[Get started with Zora →](https://zora.co)
+
+### Clanker
+**Best for:** Quick memecoin launches via social media
+
+Clanker is an AI-driven token deployment tool that operates through Farcaster. Users can create ERC-20 tokens on Base by simply tagging @clanker with their token concept.
+
+**Key Features:**
+- AI-powered automation
+- Social media integration via Farcaster
+- Instant deployment
+- Community-driven discovery
+
+[Get started with Clanker →](https://warpcast.com) or visit [clanker.world](https://clanker.world)
+
+### Flaunch
+**Best for:** Advanced memecoin projects with sophisticated tokenomics
+
+Flaunch leverages Uniswap V4 to enable programmable revenue splits, automated buybacks, and Progressive Bid Walls for price support. Creators can customize fee distributions and treasury management.
+
+**Key Features:**
+- Programmable revenue sharing
+- Automated buyback mechanisms
+- Progressive Bid Wall technology
+- Treasury management tools
+
+[Get started with Flaunch →](https://flaunch.gg)
+
+## Technical Implementation with Foundry
+
+For developers who want full control over their token implementation, here's how to create and deploy a custom ERC-20 token on Base using Foundry.
+
+
+Before launching a custom developed token to production, always conduct security reviews by expert smart contract developers.
+
+
+### Prerequisites
+
+
+
+ Install Foundry on your system:
+ ```bash Terminal
+ curl -L https://foundry.paradigm.xyz | bash
+ foundryup
+ ```
+ For detailed installation instructions, see the [Foundry documentation](https://book.getfoundry.sh/getting-started/installation).
+
+
+ Obtain Base Sepolia ETH for testing from the [Base Faucet](https://docs.base.org/docs/tools/network-faucets)
+
+
+ Configure your wallet and development tools for Base testnet deployment
+
+
+
+### Project Setup
+
+Initialize a new Foundry project and clean up template files:
+
+```bash Terminal
+# Create new project
+forge init my-token-project
+cd my-token-project
+
+# Remove template files we don't need
+rm src/Counter.sol script/Counter.s.sol test/Counter.t.sol
+```
+
+Install OpenZeppelin contracts for secure, audited ERC-20 implementation:
+
+```bash Terminal
+# Install OpenZeppelin contracts library
+forge install OpenZeppelin/openzeppelin-contracts
+```
+
+### Smart Contract Development
+
+Create your token contract using OpenZeppelin's ERC-20 implementation:
+
+```solidity src/MyToken.sol
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+import "@openzeppelin/contracts/access/Ownable.sol";
+
+/**
+ * @title MyToken
+ * @dev ERC-20 token with minting capabilities and supply cap
+ */
+contract MyToken is ERC20, Ownable {
+ // Maximum number of tokens that can ever exist
+ uint256 public constant MAX_SUPPLY = 1_000_000_000 * 10**18; // 1 billion tokens
+
+ constructor(
+ string memory name,
+ string memory symbol,
+ uint256 initialSupply,
+ address initialOwner
+ ) ERC20(name, symbol) Ownable(initialOwner) {
+ require(initialSupply <= MAX_SUPPLY, "Initial supply exceeds max supply");
+ // Mint initial supply to the contract deployer
+ _mint(initialOwner, initialSupply);
+ }
+
+ /**
+ * @dev Mint new tokens (only contract owner can call this)
+ * @param to Address to mint tokens to
+ * @param amount Amount of tokens to mint
+ */
+ function mint(address to, uint256 amount) public onlyOwner {
+ require(totalSupply() + amount <= MAX_SUPPLY, "Minting would exceed max supply");
+ _mint(to, amount);
+ }
+
+ /**
+ * @dev Burn tokens from caller's balance
+ * @param amount Amount of tokens to burn
+ */
+ function burn(uint256 amount) public {
+ _burn(msg.sender, amount);
+ }
+}
+```
+
+### Deployment Script
+
+Create a deployment script following Foundry best practices:
+
+```solidity script/DeployToken.s.sol
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+import {Script, console} from "forge-std/Script.sol";
+import {MyToken} from "../src/MyToken.sol";
+
+contract DeployToken is Script {
+ function run() external {
+ // Load deployer's private key from environment variables
+ uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
+ address deployerAddress = vm.addr(deployerPrivateKey);
+
+ // Token configuration parameters
+ string memory name = "My Token";
+ string memory symbol = "MTK";
+ uint256 initialSupply = 100_000_000 * 10**18; // 100 million tokens
+
+ // Start broadcasting transactions
+ vm.startBroadcast(deployerPrivateKey);
+
+ // Deploy the token contract
+ MyToken token = new MyToken(
+ name,
+ symbol,
+ initialSupply,
+ deployerAddress
+ );
+
+ // Stop broadcasting transactions
+ vm.stopBroadcast();
+
+ // Log deployment information
+ console.log("Token deployed to:", address(token));
+ console.log("Token name:", token.name());
+ console.log("Token symbol:", token.symbol());
+ console.log("Initial supply:", token.totalSupply());
+ console.log("Deployer balance:", token.balanceOf(deployerAddress));
+ }
+}
+```
+
+### Environment Configuration
+
+Create a `.env` file with your configuration:
+
+```bash .env
+PRIVATE_KEY=your_private_key_here
+BASE_SEPOLIA_RPC_URL=https://sepolia.base.org
+BASE_MAINNET_RPC_URL=https://mainnet.base.org
+BASESCAN_API_KEY=your_basescan_api_key_here
+```
+
+Update `foundry.toml` for Base network configuration:
+
+```toml foundry.toml
+[profile.default]
+src = "src"
+out = "out"
+libs = ["lib"]
+remappings = ["@openzeppelin/=lib/openzeppelin-contracts/"]
+
+[rpc_endpoints]
+base_sepolia = "${BASE_SEPOLIA_RPC_URL}"
+base_mainnet = "${BASE_MAINNET_RPC_URL}"
+
+[etherscan]
+base_sepolia = { key = "${BASESCAN_API_KEY}", url = "https://api-sepolia.basescan.org/api" }
+base = { key = "${BASESCAN_API_KEY}", url = "https://api.basescan.org/api" }
+```
+
+### Testing
+
+Create comprehensive tests for your token:
+
+```solidity test/MyToken.t.sol
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+
+import {Test, console} from "forge-std/Test.sol";
+import {MyToken} from "../src/MyToken.sol";
+
+contract MyTokenTest is Test {
+ MyToken public token;
+ address public owner = address(0x1);
+ address public user = address(0x2);
+
+ uint256 constant INITIAL_SUPPLY = 100_000_000 * 10**18;
+
+ function setUp() public {
+ // Deploy token contract before each test
+ vm.prank(owner);
+ token = new MyToken("Test Token", "TEST", INITIAL_SUPPLY, owner);
+ }
+
+ function testInitialState() public {
+ // Verify token was deployed with correct parameters
+ assertEq(token.name(), "Test Token");
+ assertEq(token.symbol(), "TEST");
+ assertEq(token.totalSupply(), INITIAL_SUPPLY);
+ assertEq(token.balanceOf(owner), INITIAL_SUPPLY);
+ }
+
+ function testMinting() public {
+ uint256 mintAmount = 1000 * 10**18;
+
+ // Only owner should be able to mint
+ vm.prank(owner);
+ token.mint(user, mintAmount);
+
+ assertEq(token.balanceOf(user), mintAmount);
+ assertEq(token.totalSupply(), INITIAL_SUPPLY + mintAmount);
+ }
+
+ function testBurning() public {
+ uint256 burnAmount = 1000 * 10**18;
+
+ // Owner burns their own tokens
+ vm.prank(owner);
+ token.burn(burnAmount);
+
+ assertEq(token.balanceOf(owner), INITIAL_SUPPLY - burnAmount);
+ assertEq(token.totalSupply(), INITIAL_SUPPLY - burnAmount);
+ }
+
+ function testFailMintExceedsMaxSupply() public {
+ // This test should fail when trying to mint more than max supply
+ uint256 excessiveAmount = token.MAX_SUPPLY() + 1;
+
+ vm.prank(owner);
+ token.mint(user, excessiveAmount);
+ }
+
+ function testFailUnauthorizedMinting() public {
+ // This test should fail when non-owner tries to mint
+ vm.prank(user);
+ token.mint(user, 1000 * 10**18);
+ }
+}
+```
+
+Run your tests:
+
+```bash Terminal
+# Run all tests with verbose output
+forge test -vv
+```
+
+### Deployment and Verification
+
+Deploy to Base Sepolia testnet:
+
+```bash Terminal
+# Load environment variables
+source .env
+
+# Deploy to Base Sepolia with automatic verification
+forge script script/DeployToken.s.sol:DeployToken \
+ --rpc-url base_sepolia \
+ --broadcast \
+ --verify
+```
+
+
+The `--verify` flag automatically verifies your contract on BaseScan, making it easier for users to interact with your token.
+
+
+
+To deploy to Base Mainnet, simply change `base_sepolia` to `base_mainnet` in your deployment command. Ensure you have sufficient ETH on Base Mainnet for deployment and gas fees.
+
+
+## Post-Launch Considerations
+
+Once your token is deployed, here are the key next steps to consider:
+
+### Token Distribution and Economics
+
+Carefully consider your token's supply and distribution settings. Think through how tokens will be distributed to your community, team, and ecosystem participants. Consider factors like vesting schedules, allocation percentages, and long-term incentive alignment.
+
+### Community and Social Presence
+
+Establish a community and social presence around your token and project. This includes creating documentation, setting up social media accounts, engaging with the Base ecosystem, and building relationships with other projects and developers.
+
+### Liquidity and Trading
+
+Add liquidity to decentralized exchanges like Uniswap to enable trading. Note that token launchers will typically handle this for you automatically, but for custom deployments, you'll need to create trading pairs and provide initial liquidity.
+
+### Continued Development
+
+For comprehensive guidance on growing your project on Base, including marketing strategies, ecosystem integration, and growth tactics, visit the [Base Launch Playbook](https://www.launchonbase.xyz/).
+
+
+Remember to always prioritize security, transparency, and community value when developing and launching tokens. Consider conducting security audits and following best practices for token distribution.
+
+
+## Resources
+
+
+
+ Complete guide to getting started on Base
+
+
+ Technical specifications and network information
+
+
+ Comprehensive guide to using Foundry
+
+
+ Security-focused smart contract library
+
+
+ Explore transactions and contracts on Base
+
+
+ Get testnet ETH for development
+
+
+
+### Community
+
+
+
+ Join the Base community
+
+
+ Follow Base updates
+
+
+ Contribute to Base development
+
+
+
+---
+
+Whether you choose a platform-based approach for speed and convenience, or custom development for maximum control, Base provides a robust foundation for token launches. Start with the approach that best fits your technical expertise and project requirements, and leverage Base's growing ecosystem to build successful token projects.
\ No newline at end of file
diff --git a/docs/get-started/products.mdx b/docs/get-started/products.mdx
new file mode 100644
index 00000000..2c2ffa42
--- /dev/null
+++ b/docs/get-started/products.mdx
@@ -0,0 +1,19 @@
+---
+title: 'Products'
+keywords: ['onchainkit', 'minikit', 'agentkit', 'base account', 'appchains', 'paymaster','l3','deploy a chain','smart wallet']
+---
+
+
+
+ All-in-one toolkit and ready-to-use, full-stack components.
+
+
+ Feature your mini app on decentralized social platforms with a few lines of code.
+
+
+ A passkey-based universal account to connect with the onchain world.
+
+
+ Launch a chain with dedicated blockspace on Base, in minutes.
+
+
\ No newline at end of file
diff --git a/docs/get-started/prompt-library.mdx b/docs/get-started/prompt-library.mdx
new file mode 100644
index 00000000..32846ff9
--- /dev/null
+++ b/docs/get-started/prompt-library.mdx
@@ -0,0 +1,9 @@
+---
+sidebarTitle: 'Prompt Library'
+title: Developer's Guide to Effective AI Prompting
+description: Learn practical AI prompting techniques to enhance your coding workflow and get better results from AI coding assistants.
+---
+
+import AiPrompt from "/snippets/prompt-library.mdx";
+
+
\ No newline at end of file
diff --git a/docs/get-started/use-cases.mdx b/docs/get-started/use-cases.mdx
new file mode 100644
index 00000000..805fc9fc
--- /dev/null
+++ b/docs/get-started/use-cases.mdx
@@ -0,0 +1,24 @@
+---
+title: 'Use Cases'
+---
+
+
+
+ Unlock the power of DeFi protocols and services directly in your app.
+
+
+ Deploy AI agents that can interact with onchain data and smart contracts.
+
+
+ Integrate secure and efficient crypto payment solutions for your apps.
+
+
+ Let users sign up and sign in with Smart Wallet — the universal account for the onchain world.
+
+
+ Use decentralized social graphs to grow your app and find users — wherever they are.
+
+
+ Enable gasless transactions and simplify user onboarding.
+
+
\ No newline at end of file
diff --git a/docs/get-started/use-cases/agents.mdx b/docs/get-started/use-cases/agents.mdx
new file mode 100644
index 00000000..c3fbbdd7
--- /dev/null
+++ b/docs/get-started/use-cases/agents.mdx
@@ -0,0 +1,3 @@
+---
+title: 'Agents'
+---
\ No newline at end of file
diff --git a/docs/get-started/use-cases/defi.mdx b/docs/get-started/use-cases/defi.mdx
new file mode 100644
index 00000000..2ae239cb
--- /dev/null
+++ b/docs/get-started/use-cases/defi.mdx
@@ -0,0 +1,3 @@
+---
+title: 'DeFi'
+---
\ No newline at end of file
diff --git a/docs/get-started/use-cases/gasless.mdx b/docs/get-started/use-cases/gasless.mdx
new file mode 100644
index 00000000..314ada32
--- /dev/null
+++ b/docs/get-started/use-cases/gasless.mdx
@@ -0,0 +1,3 @@
+---
+title: 'Gasless'
+---
\ No newline at end of file
diff --git a/docs/get-started/use-cases/onboarding.mdx b/docs/get-started/use-cases/onboarding.mdx
new file mode 100644
index 00000000..8e6bf157
--- /dev/null
+++ b/docs/get-started/use-cases/onboarding.mdx
@@ -0,0 +1,3 @@
+---
+title: 'Onboarding'
+---
\ No newline at end of file
diff --git a/docs/get-started/use-cases/payments.mdx b/docs/get-started/use-cases/payments.mdx
new file mode 100644
index 00000000..4240d030
--- /dev/null
+++ b/docs/get-started/use-cases/payments.mdx
@@ -0,0 +1,3 @@
+---
+title: 'Payments'
+---
\ No newline at end of file
diff --git a/docs/get-started/use-cases/social.mdx b/docs/get-started/use-cases/social.mdx
new file mode 100644
index 00000000..9243782a
--- /dev/null
+++ b/docs/get-started/use-cases/social.mdx
@@ -0,0 +1,3 @@
+---
+title: 'Social'
+---
\ No newline at end of file
diff --git a/docs/iframe-theme.js b/docs/iframe-theme.js
new file mode 100644
index 00000000..ab0046cd
--- /dev/null
+++ b/docs/iframe-theme.js
@@ -0,0 +1,62 @@
+(function () {
+ function updateIframesForDarkMode() {
+ const isDark = document.documentElement.classList.contains("dark");
+ console.log("Dark mode:", isDark);
+
+ document.querySelectorAll('iframe[src*="chromatic.com/iframe.html"]').forEach((iframe) => {
+ iframe.style.background = "transparent";
+ iframe.allowTransparency = true;
+ console.log("Found iframe:", iframe.src);
+
+ // Use postMessage to communicate with the iframe since we can't access contentDocument due to CORS
+ const sendThemeMessage = () => {
+ try {
+ const message = {
+ type: 'THEME_CHANGE',
+ isDark: isDark,
+ styles: {
+ background: isDark ? '#0b0d0f' : 'transparent' // transparent for light mode
+ }
+ };
+ console.log("Sending theme message to iframe:", message);
+ iframe.contentWindow.postMessage(message, '*');
+ } catch (e) {
+ console.warn("Could not send message to iframe:", e);
+ }
+ };
+
+ // Send message immediately if iframe might be loaded
+ sendThemeMessage();
+
+ // Also send message when iframe loads
+ iframe.addEventListener("load", () => {
+ console.log("iframe loaded, sending theme message");
+ // Add a small delay to ensure iframe is ready
+ setTimeout(sendThemeMessage, 100);
+ });
+ });
+ }
+
+ // Listen for theme changes on the parent document
+ const observer = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ if (mutation.type === 'attributes' &&
+ mutation.attributeName === 'class' &&
+ mutation.target === document.documentElement) {
+ console.log("Theme class changed, updating iframes");
+ updateIframesForDarkMode();
+ }
+ });
+ });
+
+ observer.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ['class']
+ });
+
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", updateIframesForDarkMode);
+ } else {
+ updateIframesForDarkMode();
+ }
+})();
diff --git a/docs/public/images/account-abstraction/privy-console.png b/docs/images/account-abstraction/privy-console.png
similarity index 100%
rename from docs/public/images/account-abstraction/privy-console.png
rename to docs/images/account-abstraction/privy-console.png
diff --git a/docs/public/images/account-abstraction/privy-dashboard-page.png b/docs/images/account-abstraction/privy-dashboard-page.png
similarity index 100%
rename from docs/public/images/account-abstraction/privy-dashboard-page.png
rename to docs/images/account-abstraction/privy-dashboard-page.png
diff --git a/docs/public/images/account-abstraction/privy-login-methods.png b/docs/images/account-abstraction/privy-login-methods.png
similarity index 100%
rename from docs/public/images/account-abstraction/privy-login-methods.png
rename to docs/images/account-abstraction/privy-login-methods.png
diff --git a/docs/public/images/account-abstraction/privy-login-modal.png b/docs/images/account-abstraction/privy-login-modal.png
similarity index 100%
rename from docs/public/images/account-abstraction/privy-login-modal.png
rename to docs/images/account-abstraction/privy-login-modal.png
diff --git a/docs/public/images/account-abstraction/privy-login-page.png b/docs/images/account-abstraction/privy-login-page.png
similarity index 100%
rename from docs/public/images/account-abstraction/privy-login-page.png
rename to docs/images/account-abstraction/privy-login-page.png
diff --git a/docs/public/images/base-docs-og.png b/docs/images/base-docs-og.png
similarity index 100%
rename from docs/public/images/base-docs-og.png
rename to docs/images/base-docs-og.png
diff --git a/docs/public/images/basenames-tutorial/basename-profile-home.png b/docs/images/basenames-tutorial/basename-profile-home.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/basename-profile-home.png
rename to docs/images/basenames-tutorial/basename-profile-home.png
diff --git a/docs/public/images/basenames-tutorial/basenames-frame-final.png b/docs/images/basenames-tutorial/basenames-frame-final.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/basenames-frame-final.png
rename to docs/images/basenames-tutorial/basenames-frame-final.png
diff --git a/docs/public/images/basenames-tutorial/basenames-homepage.png b/docs/images/basenames-tutorial/basenames-homepage.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/basenames-homepage.png
rename to docs/images/basenames-tutorial/basenames-homepage.png
diff --git a/docs/public/images/basenames-tutorial/confirm-textrecord-update.png b/docs/images/basenames-tutorial/confirm-textrecord-update.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/confirm-textrecord-update.png
rename to docs/images/basenames-tutorial/confirm-textrecord-update.png
diff --git a/docs/public/images/basenames-tutorial/confirm-txn.png b/docs/images/basenames-tutorial/confirm-txn.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/confirm-txn.png
rename to docs/images/basenames-tutorial/confirm-txn.png
diff --git a/docs/public/images/basenames-tutorial/edit-basename-profile.png b/docs/images/basenames-tutorial/edit-basename-profile.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/edit-basename-profile.png
rename to docs/images/basenames-tutorial/edit-basename-profile.png
diff --git a/docs/public/images/basenames-tutorial/final-basename.png b/docs/images/basenames-tutorial/final-basename.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/final-basename.png
rename to docs/images/basenames-tutorial/final-basename.png
diff --git a/docs/public/images/basenames-tutorial/frame-preview.png b/docs/images/basenames-tutorial/frame-preview.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/frame-preview.png
rename to docs/images/basenames-tutorial/frame-preview.png
diff --git a/docs/public/images/basenames-tutorial/frames-selector.png b/docs/images/basenames-tutorial/frames-selector.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/frames-selector.png
rename to docs/images/basenames-tutorial/frames-selector.png
diff --git a/docs/public/images/basenames-tutorial/preview-frame.png b/docs/images/basenames-tutorial/preview-frame.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/preview-frame.png
rename to docs/images/basenames-tutorial/preview-frame.png
diff --git a/docs/public/images/basenames-tutorial/profile-component-dropdown.png b/docs/images/basenames-tutorial/profile-component-dropdown.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/profile-component-dropdown.png
rename to docs/images/basenames-tutorial/profile-component-dropdown.png
diff --git a/docs/public/images/basenames-tutorial/show-preview.png b/docs/images/basenames-tutorial/show-preview.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/show-preview.png
rename to docs/images/basenames-tutorial/show-preview.png
diff --git a/docs/public/images/basenames-tutorial/try-now.png b/docs/images/basenames-tutorial/try-now.png
similarity index 100%
rename from docs/public/images/basenames-tutorial/try-now.png
rename to docs/images/basenames-tutorial/try-now.png
diff --git a/docs/public/images/build-with-nouns/auction.png b/docs/images/build-with-nouns/auction.png
similarity index 100%
rename from docs/public/images/build-with-nouns/auction.png
rename to docs/images/build-with-nouns/auction.png
diff --git a/docs/public/images/build-with-nouns/create-dao.png b/docs/images/build-with-nouns/create-dao.png
similarity index 100%
rename from docs/public/images/build-with-nouns/create-dao.png
rename to docs/images/build-with-nouns/create-dao.png
diff --git a/docs/public/images/build-with-thirdweb/car-color-nft.gif b/docs/images/build-with-thirdweb/car-color-nft.gif
similarity index 100%
rename from docs/public/images/build-with-thirdweb/car-color-nft.gif
rename to docs/images/build-with-thirdweb/car-color-nft.gif
diff --git a/docs/public/images/build-with-thirdweb/deploy-contract.png b/docs/images/build-with-thirdweb/deploy-contract.png
similarity index 100%
rename from docs/public/images/build-with-thirdweb/deploy-contract.png
rename to docs/images/build-with-thirdweb/deploy-contract.png
diff --git a/docs/public/images/build-with-thirdweb/get-nft-colors.png b/docs/images/build-with-thirdweb/get-nft-colors.png
similarity index 100%
rename from docs/public/images/build-with-thirdweb/get-nft-colors.png
rename to docs/images/build-with-thirdweb/get-nft-colors.png
diff --git a/docs/public/images/build-with-thirdweb/hex-to-linear-color.png b/docs/images/build-with-thirdweb/hex-to-linear-color.png
similarity index 100%
rename from docs/public/images/build-with-thirdweb/hex-to-linear-color.png
rename to docs/images/build-with-thirdweb/hex-to-linear-color.png
diff --git a/docs/public/images/build-with-thirdweb/import-image-instance.png b/docs/images/build-with-thirdweb/import-image-instance.png
similarity index 100%
rename from docs/public/images/build-with-thirdweb/import-image-instance.png
rename to docs/images/build-with-thirdweb/import-image-instance.png
diff --git a/docs/public/images/build-with-thirdweb/mint-nft.png b/docs/images/build-with-thirdweb/mint-nft.png
similarity index 100%
rename from docs/public/images/build-with-thirdweb/mint-nft.png
rename to docs/images/build-with-thirdweb/mint-nft.png
diff --git a/docs/public/images/build-with-thirdweb/open-thirdweb-manager.png b/docs/images/build-with-thirdweb/open-thirdweb-manager.png
similarity index 100%
rename from docs/public/images/build-with-thirdweb/open-thirdweb-manager.png
rename to docs/images/build-with-thirdweb/open-thirdweb-manager.png
diff --git a/docs/public/images/build-with-thirdweb/open-unreal-project.png b/docs/images/build-with-thirdweb/open-unreal-project.png
similarity index 100%
rename from docs/public/images/build-with-thirdweb/open-unreal-project.png
rename to docs/images/build-with-thirdweb/open-unreal-project.png
diff --git a/docs/public/images/build-with-thirdweb/perform-nft-claim.png b/docs/images/build-with-thirdweb/perform-nft-claim.png
similarity index 100%
rename from docs/public/images/build-with-thirdweb/perform-nft-claim.png
rename to docs/images/build-with-thirdweb/perform-nft-claim.png
diff --git a/docs/public/images/build-with-thirdweb/play-button.png b/docs/images/build-with-thirdweb/play-button.png
similarity index 100%
rename from docs/public/images/build-with-thirdweb/play-button.png
rename to docs/images/build-with-thirdweb/play-button.png
diff --git a/docs/public/images/build-with-thirdweb/scene-game.png b/docs/images/build-with-thirdweb/scene-game.png
similarity index 100%
rename from docs/public/images/build-with-thirdweb/scene-game.png
rename to docs/images/build-with-thirdweb/scene-game.png
diff --git a/docs/public/images/build-with-thirdweb/token-airdrop-dashboard.png b/docs/images/build-with-thirdweb/token-airdrop-dashboard.png
similarity index 100%
rename from docs/public/images/build-with-thirdweb/token-airdrop-dashboard.png
rename to docs/images/build-with-thirdweb/token-airdrop-dashboard.png
diff --git a/docs/public/images/build-with-zora/cdp-pick-node.png b/docs/images/build-with-zora/cdp-pick-node.png
similarity index 100%
rename from docs/public/images/build-with-zora/cdp-pick-node.png
rename to docs/images/build-with-zora/cdp-pick-node.png
diff --git a/docs/public/images/build-with-zora/dashboard.png b/docs/images/build-with-zora/dashboard.png
similarity index 100%
rename from docs/public/images/build-with-zora/dashboard.png
rename to docs/images/build-with-zora/dashboard.png
diff --git a/docs/public/images/builder-anniversary-nft/builder-anniversary-nft-preview.webp b/docs/images/builder-anniversary-nft/builder-anniversary-nft-preview.webp
similarity index 100%
rename from docs/public/images/builder-anniversary-nft/builder-anniversary-nft-preview.webp
rename to docs/images/builder-anniversary-nft/builder-anniversary-nft-preview.webp
diff --git a/docs/public/images/connecting-to-the-blockchain/add-project-information.png b/docs/images/connecting-to-the-blockchain/add-project-information.png
similarity index 100%
rename from docs/public/images/connecting-to-the-blockchain/add-project-information.png
rename to docs/images/connecting-to-the-blockchain/add-project-information.png
diff --git a/docs/public/images/connecting-to-the-blockchain/alchemy-new-app.png b/docs/images/connecting-to-the-blockchain/alchemy-new-app.png
similarity index 100%
rename from docs/public/images/connecting-to-the-blockchain/alchemy-new-app.png
rename to docs/images/connecting-to-the-blockchain/alchemy-new-app.png
diff --git a/docs/public/images/connecting-to-the-blockchain/blockdaemon-create-key.png b/docs/images/connecting-to-the-blockchain/blockdaemon-create-key.png
similarity index 100%
rename from docs/public/images/connecting-to-the-blockchain/blockdaemon-create-key.png
rename to docs/images/connecting-to-the-blockchain/blockdaemon-create-key.png
diff --git a/docs/public/images/connecting-to-the-blockchain/connected.png b/docs/images/connecting-to-the-blockchain/connected.png
similarity index 100%
rename from docs/public/images/connecting-to-the-blockchain/connected.png
rename to docs/images/connecting-to-the-blockchain/connected.png
diff --git a/docs/public/images/connecting-to-the-blockchain/quicknode-select-chain.png b/docs/images/connecting-to-the-blockchain/quicknode-select-chain.png
similarity index 100%
rename from docs/public/images/connecting-to-the-blockchain/quicknode-select-chain.png
rename to docs/images/connecting-to-the-blockchain/quicknode-select-chain.png
diff --git a/docs/public/images/connecting-to-the-blockchain/rainbowkit-modal.png b/docs/images/connecting-to-the-blockchain/rainbowkit-modal.png
similarity index 100%
rename from docs/public/images/connecting-to-the-blockchain/rainbowkit-modal.png
rename to docs/images/connecting-to-the-blockchain/rainbowkit-modal.png
diff --git a/docs/public/images/connecting-to-the-blockchain/wallet-connect-create-button.png b/docs/images/connecting-to-the-blockchain/wallet-connect-create-button.png
similarity index 100%
rename from docs/public/images/connecting-to-the-blockchain/wallet-connect-create-button.png
rename to docs/images/connecting-to-the-blockchain/wallet-connect-create-button.png
diff --git a/docs/public/images/deployment-with-remix/base-confirm-transaction.png b/docs/images/deployment-with-remix/base-confirm-transaction.png
similarity index 100%
rename from docs/public/images/deployment-with-remix/base-confirm-transaction.png
rename to docs/images/deployment-with-remix/base-confirm-transaction.png
diff --git a/docs/public/images/deployment-with-remix/compiler-debug-log.png b/docs/images/deployment-with-remix/compiler-debug-log.png
similarity index 100%
rename from docs/public/images/deployment-with-remix/compiler-debug-log.png
rename to docs/images/deployment-with-remix/compiler-debug-log.png
diff --git a/docs/public/images/deployment-with-remix/editor-pane.png b/docs/images/deployment-with-remix/editor-pane.png
similarity index 100%
rename from docs/public/images/deployment-with-remix/editor-pane.png
rename to docs/images/deployment-with-remix/editor-pane.png
diff --git a/docs/public/images/deployment-with-remix/remix-base-goerli-connected.png b/docs/images/deployment-with-remix/remix-base-goerli-connected.png
similarity index 100%
rename from docs/public/images/deployment-with-remix/remix-base-goerli-connected.png
rename to docs/images/deployment-with-remix/remix-base-goerli-connected.png
diff --git a/docs/public/images/deployment-with-remix/remix-home.png b/docs/images/deployment-with-remix/remix-home.png
similarity index 100%
rename from docs/public/images/deployment-with-remix/remix-home.png
rename to docs/images/deployment-with-remix/remix-home.png
diff --git a/docs/public/images/deployment-with-remix/remix-terminal.png b/docs/images/deployment-with-remix/remix-terminal.png
similarity index 100%
rename from docs/public/images/deployment-with-remix/remix-terminal.png
rename to docs/images/deployment-with-remix/remix-terminal.png
diff --git a/docs/public/images/deployment-with-remix/select-provider.png b/docs/images/deployment-with-remix/select-provider.png
similarity index 100%
rename from docs/public/images/deployment-with-remix/select-provider.png
rename to docs/images/deployment-with-remix/select-provider.png
diff --git a/docs/public/images/deployment-with-remix/verify-and-publish.png b/docs/images/deployment-with-remix/verify-and-publish.png
similarity index 100%
rename from docs/public/images/deployment-with-remix/verify-and-publish.png
rename to docs/images/deployment-with-remix/verify-and-publish.png
diff --git a/docs/public/images/deployment-with-tenderly/configuration.png b/docs/images/deployment-with-tenderly/configuration.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/configuration.png
rename to docs/images/deployment-with-tenderly/configuration.png
diff --git a/docs/public/images/deployment-with-tenderly/create-template.png b/docs/images/deployment-with-tenderly/create-template.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/create-template.png
rename to docs/images/deployment-with-tenderly/create-template.png
diff --git a/docs/public/images/deployment-with-tenderly/debugger-button.png b/docs/images/deployment-with-tenderly/debugger-button.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/debugger-button.png
rename to docs/images/deployment-with-tenderly/debugger-button.png
diff --git a/docs/public/images/deployment-with-tenderly/devnet-project-slug.png b/docs/images/deployment-with-tenderly/devnet-project-slug.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/devnet-project-slug.png
rename to docs/images/deployment-with-tenderly/devnet-project-slug.png
diff --git a/docs/public/images/deployment-with-tenderly/github-actions.png b/docs/images/deployment-with-tenderly/github-actions.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/github-actions.png
rename to docs/images/deployment-with-tenderly/github-actions.png
diff --git a/docs/public/images/deployment-with-tenderly/modifying-source.png b/docs/images/deployment-with-tenderly/modifying-source.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/modifying-source.png
rename to docs/images/deployment-with-tenderly/modifying-source.png
diff --git a/docs/public/images/deployment-with-tenderly/output.png b/docs/images/deployment-with-tenderly/output.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/output.png
rename to docs/images/deployment-with-tenderly/output.png
diff --git a/docs/public/images/deployment-with-tenderly/overrides.png b/docs/images/deployment-with-tenderly/overrides.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/overrides.png
rename to docs/images/deployment-with-tenderly/overrides.png
diff --git a/docs/public/images/deployment-with-tenderly/result-of-expression.png b/docs/images/deployment-with-tenderly/result-of-expression.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/result-of-expression.png
rename to docs/images/deployment-with-tenderly/result-of-expression.png
diff --git a/docs/public/images/deployment-with-tenderly/result-of-simulation.png b/docs/images/deployment-with-tenderly/result-of-simulation.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/result-of-simulation.png
rename to docs/images/deployment-with-tenderly/result-of-simulation.png
diff --git a/docs/public/images/deployment-with-tenderly/setgreeting.png b/docs/images/deployment-with-tenderly/setgreeting.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/setgreeting.png
rename to docs/images/deployment-with-tenderly/setgreeting.png
diff --git a/docs/public/images/deployment-with-tenderly/simulation.png b/docs/images/deployment-with-tenderly/simulation.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/simulation.png
rename to docs/images/deployment-with-tenderly/simulation.png
diff --git a/docs/public/images/deployment-with-tenderly/simulator-button.png b/docs/images/deployment-with-tenderly/simulator-button.png
similarity index 100%
rename from docs/public/images/deployment-with-tenderly/simulator-button.png
rename to docs/images/deployment-with-tenderly/simulator-button.png
diff --git a/docs/public/images/dynamic-nfts/NFT_level_1.png b/docs/images/dynamic-nfts/NFT_level_1.png
similarity index 100%
rename from docs/public/images/dynamic-nfts/NFT_level_1.png
rename to docs/images/dynamic-nfts/NFT_level_1.png
diff --git a/docs/public/images/dynamic-nfts/NFT_level_2.png b/docs/images/dynamic-nfts/NFT_level_2.png
similarity index 100%
rename from docs/public/images/dynamic-nfts/NFT_level_2.png
rename to docs/images/dynamic-nfts/NFT_level_2.png
diff --git a/docs/public/images/dynamic-nfts/NFT_level_3.png b/docs/images/dynamic-nfts/NFT_level_3.png
similarity index 100%
rename from docs/public/images/dynamic-nfts/NFT_level_3.png
rename to docs/images/dynamic-nfts/NFT_level_3.png
diff --git a/docs/public/images/dynamic-nfts/all-characters.png b/docs/images/dynamic-nfts/all-characters.png
similarity index 100%
rename from docs/public/images/dynamic-nfts/all-characters.png
rename to docs/images/dynamic-nfts/all-characters.png
diff --git a/docs/public/images/dynamic-nfts/base-dynamic-nft.zip b/docs/images/dynamic-nfts/base-dynamic-nft.zip
similarity index 100%
rename from docs/public/images/dynamic-nfts/base-dynamic-nft.zip
rename to docs/images/dynamic-nfts/base-dynamic-nft.zip
diff --git a/docs/public/images/dynamic-nfts/image-level-1.png b/docs/images/dynamic-nfts/image-level-1.png
similarity index 100%
rename from docs/public/images/dynamic-nfts/image-level-1.png
rename to docs/images/dynamic-nfts/image-level-1.png
diff --git a/docs/public/images/dynamic-nfts/image-level-2.png b/docs/images/dynamic-nfts/image-level-2.png
similarity index 100%
rename from docs/public/images/dynamic-nfts/image-level-2.png
rename to docs/images/dynamic-nfts/image-level-2.png
diff --git a/docs/public/images/dynamic-nfts/image-level-3.png b/docs/images/dynamic-nfts/image-level-3.png
similarity index 100%
rename from docs/public/images/dynamic-nfts/image-level-3.png
rename to docs/images/dynamic-nfts/image-level-3.png
diff --git a/docs/public/images/dynamic-nfts/mutable-references.png b/docs/images/dynamic-nfts/mutable-references.png
similarity index 100%
rename from docs/public/images/dynamic-nfts/mutable-references.png
rename to docs/images/dynamic-nfts/mutable-references.png
diff --git a/docs/public/images/dynamic-nfts/open-sea-mockup.jpg b/docs/images/dynamic-nfts/open-sea-mockup.jpg
similarity index 100%
rename from docs/public/images/dynamic-nfts/open-sea-mockup.jpg
rename to docs/images/dynamic-nfts/open-sea-mockup.jpg
diff --git a/docs/public/images/dynamic-nfts/refresh-metadata.png b/docs/images/dynamic-nfts/refresh-metadata.png
similarity index 100%
rename from docs/public/images/dynamic-nfts/refresh-metadata.png
rename to docs/images/dynamic-nfts/refresh-metadata.png
diff --git a/docs/public/images/frames/100-lines-frame.png b/docs/images/frames/100-lines-frame.png
similarity index 100%
rename from docs/public/images/frames/100-lines-frame.png
rename to docs/images/frames/100-lines-frame.png
diff --git a/docs/public/images/frames/2024-a-base-odyssey.png b/docs/images/frames/2024-a-base-odyssey.png
similarity index 100%
rename from docs/public/images/frames/2024-a-base-odyssey.png
rename to docs/images/frames/2024-a-base-odyssey.png
diff --git a/docs/public/images/frames/first-frame.png b/docs/images/frames/first-frame.png
similarity index 100%
rename from docs/public/images/frames/first-frame.png
rename to docs/images/frames/first-frame.png
diff --git a/docs/public/images/frames/gave-me-away.png b/docs/images/frames/gave-me-away.png
similarity index 100%
rename from docs/public/images/frames/gave-me-away.png
rename to docs/images/frames/gave-me-away.png
diff --git a/docs/public/images/frames/install-action.png b/docs/images/frames/install-action.png
similarity index 100%
rename from docs/public/images/frames/install-action.png
rename to docs/images/frames/install-action.png
diff --git a/docs/public/images/frames/link-button-test.png b/docs/images/frames/link-button-test.png
similarity index 100%
rename from docs/public/images/frames/link-button-test.png
rename to docs/images/frames/link-button-test.png
diff --git a/docs/public/images/frames/real-nft.png b/docs/images/frames/real-nft.png
similarity index 100%
rename from docs/public/images/frames/real-nft.png
rename to docs/images/frames/real-nft.png
diff --git a/docs/public/images/frames/story-time.png b/docs/images/frames/story-time.png
similarity index 100%
rename from docs/public/images/frames/story-time.png
rename to docs/images/frames/story-time.png
diff --git a/docs/public/images/frames/updated-100-lines.png b/docs/images/frames/updated-100-lines.png
similarity index 100%
rename from docs/public/images/frames/updated-100-lines.png
rename to docs/images/frames/updated-100-lines.png
diff --git a/docs/public/images/frames/vercel-build.png b/docs/images/frames/vercel-build.png
similarity index 100%
rename from docs/public/images/frames/vercel-build.png
rename to docs/images/frames/vercel-build.png
diff --git a/docs/public/images/frames/vercel-import.png b/docs/images/frames/vercel-import.png
similarity index 100%
rename from docs/public/images/frames/vercel-import.png
rename to docs/images/frames/vercel-import.png
diff --git a/docs/public/images/frames/vercel-install.png b/docs/images/frames/vercel-install.png
similarity index 100%
rename from docs/public/images/frames/vercel-install.png
rename to docs/images/frames/vercel-install.png
diff --git a/docs/public/images/frames/vercel-projects.png b/docs/images/frames/vercel-projects.png
similarity index 100%
rename from docs/public/images/frames/vercel-projects.png
rename to docs/images/frames/vercel-projects.png
diff --git "a/docs/public/images/gasless-transaction-on-base/Screenshot 2024-07-11 at 3.10.17\342\200\257PM.png" "b/docs/images/gasless-transaction-on-base/Screenshot 2024-07-11 at 3.10.17\342\200\257PM.png"
similarity index 100%
rename from "docs/public/images/gasless-transaction-on-base/Screenshot 2024-07-11 at 3.10.17\342\200\257PM.png"
rename to "docs/images/gasless-transaction-on-base/Screenshot 2024-07-11 at 3.10.17\342\200\257PM.png"
diff --git "a/docs/public/images/gasless-transaction-on-base/Screenshot 2024-07-11 at 8.05.25\342\200\257AM.png" "b/docs/images/gasless-transaction-on-base/Screenshot 2024-07-11 at 8.05.25\342\200\257AM.png"
similarity index 100%
rename from "docs/public/images/gasless-transaction-on-base/Screenshot 2024-07-11 at 8.05.25\342\200\257AM.png"
rename to "docs/images/gasless-transaction-on-base/Screenshot 2024-07-11 at 8.05.25\342\200\257AM.png"
diff --git a/docs/public/images/gasless-transaction-on-base/cdp-allowlist-contract.png b/docs/images/gasless-transaction-on-base/cdp-allowlist-contract.png
similarity index 100%
rename from docs/public/images/gasless-transaction-on-base/cdp-allowlist-contract.png
rename to docs/images/gasless-transaction-on-base/cdp-allowlist-contract.png
diff --git a/docs/public/images/gasless-transaction-on-base/cdp-config.png b/docs/images/gasless-transaction-on-base/cdp-config.png
similarity index 100%
rename from docs/public/images/gasless-transaction-on-base/cdp-config.png
rename to docs/images/gasless-transaction-on-base/cdp-config.png
diff --git a/docs/public/images/gasless-transaction-on-base/cdp-global-user-limits.png b/docs/images/gasless-transaction-on-base/cdp-global-user-limits.png
similarity index 100%
rename from docs/public/images/gasless-transaction-on-base/cdp-global-user-limits.png
rename to docs/images/gasless-transaction-on-base/cdp-global-user-limits.png
diff --git a/docs/public/images/gasless-transaction-on-base/cdp-home.png b/docs/images/gasless-transaction-on-base/cdp-home.png
similarity index 100%
rename from docs/public/images/gasless-transaction-on-base/cdp-home.png
rename to docs/images/gasless-transaction-on-base/cdp-home.png
diff --git a/docs/public/images/gasless-transaction-on-base/cdp-paymaster.png b/docs/images/gasless-transaction-on-base/cdp-paymaster.png
similarity index 100%
rename from docs/public/images/gasless-transaction-on-base/cdp-paymaster.png
rename to docs/images/gasless-transaction-on-base/cdp-paymaster.png
diff --git a/docs/public/images/gasless-transaction-on-base/cdp-select-network.png b/docs/images/gasless-transaction-on-base/cdp-select-network.png
similarity index 100%
rename from docs/public/images/gasless-transaction-on-base/cdp-select-network.png
rename to docs/images/gasless-transaction-on-base/cdp-select-network.png
diff --git a/docs/public/images/gasless-transaction-on-base/cdp-select-project.png b/docs/images/gasless-transaction-on-base/cdp-select-project.png
similarity index 100%
rename from docs/public/images/gasless-transaction-on-base/cdp-select-project.png
rename to docs/images/gasless-transaction-on-base/cdp-select-project.png
diff --git a/docs/public/images/gasless-transaction-on-base/cdp-userops-10.png b/docs/images/gasless-transaction-on-base/cdp-userops-10.png
similarity index 100%
rename from docs/public/images/gasless-transaction-on-base/cdp-userops-10.png
rename to docs/images/gasless-transaction-on-base/cdp-userops-10.png
diff --git a/docs/public/images/hardhat-test-coverage/coverage-report.png b/docs/images/hardhat-test-coverage/coverage-report.png
similarity index 100%
rename from docs/public/images/hardhat-test-coverage/coverage-report.png
rename to docs/images/hardhat-test-coverage/coverage-report.png
diff --git a/docs/images/hero.png b/docs/images/hero.png
new file mode 100644
index 00000000..f45b554f
Binary files /dev/null and b/docs/images/hero.png differ
diff --git a/docs/public/images/homepage/hero-background.png b/docs/images/homepage/hero-background.png
similarity index 100%
rename from docs/public/images/homepage/hero-background.png
rename to docs/images/homepage/hero-background.png
diff --git a/docs/public/images/learn/deployment-to-testnet/add-injected-provider.png b/docs/images/learn/deployment-to-testnet/add-injected-provider.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/add-injected-provider.png
rename to docs/images/learn/deployment-to-testnet/add-injected-provider.png
diff --git a/docs/public/images/learn/deployment-to-testnet/balance.png b/docs/images/learn/deployment-to-testnet/balance.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/balance.png
rename to docs/images/learn/deployment-to-testnet/balance.png
diff --git a/docs/public/images/learn/deployment-to-testnet/base-confirm-transaction.png b/docs/images/learn/deployment-to-testnet/base-confirm-transaction.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/base-confirm-transaction.png
rename to docs/images/learn/deployment-to-testnet/base-confirm-transaction.png
diff --git a/docs/public/images/learn/deployment-to-testnet/compiler-debug-log.png b/docs/images/learn/deployment-to-testnet/compiler-debug-log.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/compiler-debug-log.png
rename to docs/images/learn/deployment-to-testnet/compiler-debug-log.png
diff --git a/docs/public/images/learn/deployment-to-testnet/confirm-transaction.png b/docs/images/learn/deployment-to-testnet/confirm-transaction.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/confirm-transaction.png
rename to docs/images/learn/deployment-to-testnet/confirm-transaction.png
diff --git a/docs/public/images/learn/deployment-to-testnet/connected.png b/docs/images/learn/deployment-to-testnet/connected.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/connected.png
rename to docs/images/learn/deployment-to-testnet/connected.png
diff --git a/docs/public/images/learn/deployment-to-testnet/deployment-transaction.png b/docs/images/learn/deployment-to-testnet/deployment-transaction.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/deployment-transaction.png
rename to docs/images/learn/deployment-to-testnet/deployment-transaction.png
diff --git a/docs/public/images/learn/deployment-to-testnet/importance-of-testnets.png b/docs/images/learn/deployment-to-testnet/importance-of-testnets.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/importance-of-testnets.png
rename to docs/images/learn/deployment-to-testnet/importance-of-testnets.png
diff --git a/docs/public/images/learn/deployment-to-testnet/remix-base-goerli-connected.png b/docs/images/learn/deployment-to-testnet/remix-base-goerli-connected.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/remix-base-goerli-connected.png
rename to docs/images/learn/deployment-to-testnet/remix-base-goerli-connected.png
diff --git a/docs/public/images/learn/deployment-to-testnet/select-provider.png b/docs/images/learn/deployment-to-testnet/select-provider.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/select-provider.png
rename to docs/images/learn/deployment-to-testnet/select-provider.png
diff --git a/docs/public/images/learn/deployment-to-testnet/testnet-comparison.png b/docs/images/learn/deployment-to-testnet/testnet-comparison.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/testnet-comparison.png
rename to docs/images/learn/deployment-to-testnet/testnet-comparison.png
diff --git a/docs/public/images/learn/deployment-to-testnet/verify-and-publish.png b/docs/images/learn/deployment-to-testnet/verify-and-publish.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/verify-and-publish.png
rename to docs/images/learn/deployment-to-testnet/verify-and-publish.png
diff --git a/docs/public/images/learn/deployment-to-testnet/view-transaction.png b/docs/images/learn/deployment-to-testnet/view-transaction.png
similarity index 100%
rename from docs/public/images/learn/deployment-to-testnet/view-transaction.png
rename to docs/images/learn/deployment-to-testnet/view-transaction.png
diff --git a/docs/public/images/learn/erc-20/deployed-token.png b/docs/images/learn/erc-20/deployed-token.png
similarity index 100%
rename from docs/public/images/learn/erc-20/deployed-token.png
rename to docs/images/learn/erc-20/deployed-token.png
diff --git a/docs/public/images/learn/erc-20/erc20-dev-perspective.png b/docs/images/learn/erc-20/erc20-dev-perspective.png
similarity index 100%
rename from docs/public/images/learn/erc-20/erc20-dev-perspective.png
rename to docs/images/learn/erc-20/erc20-dev-perspective.png
diff --git a/docs/public/images/learn/erc-20/erc20-user-perspective.png b/docs/images/learn/erc-20/erc20-user-perspective.png
similarity index 100%
rename from docs/public/images/learn/erc-20/erc20-user-perspective.png
rename to docs/images/learn/erc-20/erc20-user-perspective.png
diff --git a/docs/public/images/learn/erc-20/evolution-eth-erc20.png b/docs/images/learn/erc-20/evolution-eth-erc20.png
similarity index 100%
rename from docs/public/images/learn/erc-20/evolution-eth-erc20.png
rename to docs/images/learn/erc-20/evolution-eth-erc20.png
diff --git a/docs/public/images/learn/erc-721/erc-721-standard.png b/docs/images/learn/erc-721/erc-721-standard.png
similarity index 100%
rename from docs/public/images/learn/erc-721/erc-721-standard.png
rename to docs/images/learn/erc-721/erc-721-standard.png
diff --git a/docs/public/images/learn/erc-721/evolution-eth-erc721.png b/docs/images/learn/erc-721/evolution-eth-erc721.png
similarity index 100%
rename from docs/public/images/learn/erc-721/evolution-eth-erc721.png
rename to docs/images/learn/erc-721/evolution-eth-erc721.png
diff --git a/docs/public/images/learn/error-triage/array-out-of-bounds.png b/docs/images/learn/error-triage/array-out-of-bounds.png
similarity index 100%
rename from docs/public/images/learn/error-triage/array-out-of-bounds.png
rename to docs/images/learn/error-triage/array-out-of-bounds.png
diff --git a/docs/public/images/learn/error-triage/debugger.png b/docs/images/learn/error-triage/debugger.png
similarity index 100%
rename from docs/public/images/learn/error-triage/debugger.png
rename to docs/images/learn/error-triage/debugger.png
diff --git a/docs/public/images/learn/error-triage/divide-by-zero.png b/docs/images/learn/error-triage/divide-by-zero.png
similarity index 100%
rename from docs/public/images/learn/error-triage/divide-by-zero.png
rename to docs/images/learn/error-triage/divide-by-zero.png
diff --git a/docs/public/images/learn/error-triage/gas-limit.png b/docs/images/learn/error-triage/gas-limit.png
similarity index 100%
rename from docs/public/images/learn/error-triage/gas-limit.png
rename to docs/images/learn/error-triage/gas-limit.png
diff --git a/docs/public/images/learn/error-triage/highlight-code.png b/docs/images/learn/error-triage/highlight-code.png
similarity index 100%
rename from docs/public/images/learn/error-triage/highlight-code.png
rename to docs/images/learn/error-triage/highlight-code.png
diff --git a/docs/public/images/learn/error-triage/underflow.png b/docs/images/learn/error-triage/underflow.png
similarity index 100%
rename from docs/public/images/learn/error-triage/underflow.png
rename to docs/images/learn/error-triage/underflow.png
diff --git a/docs/public/images/learn/ethereum-virtual-machine/evm-architecture-execution.png b/docs/images/learn/ethereum-virtual-machine/evm-architecture-execution.png
similarity index 100%
rename from docs/public/images/learn/ethereum-virtual-machine/evm-architecture-execution.png
rename to docs/images/learn/ethereum-virtual-machine/evm-architecture-execution.png
diff --git a/docs/public/images/learn/ethereum-virtual-machine/evm-execution-basic.png b/docs/images/learn/ethereum-virtual-machine/evm-execution-basic.png
similarity index 100%
rename from docs/public/images/learn/ethereum-virtual-machine/evm-execution-basic.png
rename to docs/images/learn/ethereum-virtual-machine/evm-execution-basic.png
diff --git a/docs/public/images/learn/ethereum-virtual-machine/evm-stack-memory.png b/docs/images/learn/ethereum-virtual-machine/evm-stack-memory.png
similarity index 100%
rename from docs/public/images/learn/ethereum-virtual-machine/evm-stack-memory.png
rename to docs/images/learn/ethereum-virtual-machine/evm-stack-memory.png
diff --git a/docs/public/images/learn/ethereum-virtual-machine/opcode-execution.png b/docs/images/learn/ethereum-virtual-machine/opcode-execution.png
similarity index 100%
rename from docs/public/images/learn/ethereum-virtual-machine/opcode-execution.png
rename to docs/images/learn/ethereum-virtual-machine/opcode-execution.png
diff --git a/docs/public/images/learn/etherscan/bayc-query.png b/docs/images/learn/etherscan/bayc-query.png
similarity index 100%
rename from docs/public/images/learn/etherscan/bayc-query.png
rename to docs/images/learn/etherscan/bayc-query.png
diff --git a/docs/public/images/learn/etherscan/bayc-read.png b/docs/images/learn/etherscan/bayc-read.png
similarity index 100%
rename from docs/public/images/learn/etherscan/bayc-read.png
rename to docs/images/learn/etherscan/bayc-read.png
diff --git a/docs/public/images/learn/etherscan/bayc-verified.png b/docs/images/learn/etherscan/bayc-verified.png
similarity index 100%
rename from docs/public/images/learn/etherscan/bayc-verified.png
rename to docs/images/learn/etherscan/bayc-verified.png
diff --git a/docs/public/images/learn/etherscan/bayc-write-connected.png b/docs/images/learn/etherscan/bayc-write-connected.png
similarity index 100%
rename from docs/public/images/learn/etherscan/bayc-write-connected.png
rename to docs/images/learn/etherscan/bayc-write-connected.png
diff --git a/docs/public/images/learn/etherscan/bayc-write.png b/docs/images/learn/etherscan/bayc-write.png
similarity index 100%
rename from docs/public/images/learn/etherscan/bayc-write.png
rename to docs/images/learn/etherscan/bayc-write.png
diff --git a/docs/public/images/learn/etherscan/bayc.png b/docs/images/learn/etherscan/bayc.png
similarity index 100%
rename from docs/public/images/learn/etherscan/bayc.png
rename to docs/images/learn/etherscan/bayc.png
diff --git a/docs/public/images/learn/etherscan/blocks.png b/docs/images/learn/etherscan/blocks.png
similarity index 100%
rename from docs/public/images/learn/etherscan/blocks.png
rename to docs/images/learn/etherscan/blocks.png
diff --git a/docs/public/images/learn/etherscan/etherscan-user-interface.png b/docs/images/learn/etherscan/etherscan-user-interface.png
similarity index 100%
rename from docs/public/images/learn/etherscan/etherscan-user-interface.png
rename to docs/images/learn/etherscan/etherscan-user-interface.png
diff --git a/docs/public/images/learn/hardhat-deploying/new-deploy.png b/docs/images/learn/hardhat-deploying/new-deploy.png
similarity index 100%
rename from docs/public/images/learn/hardhat-deploying/new-deploy.png
rename to docs/images/learn/hardhat-deploying/new-deploy.png
diff --git a/docs/public/images/learn/hardhat-forking/hardhat-forking.png b/docs/images/learn/hardhat-forking/hardhat-forking.png
similarity index 100%
rename from docs/public/images/learn/hardhat-forking/hardhat-forking.png
rename to docs/images/learn/hardhat-forking/hardhat-forking.png
diff --git a/docs/public/images/learn/hardhat-testing/autocomplete-unlockTime.png b/docs/images/learn/hardhat-testing/autocomplete-unlockTime.png
similarity index 100%
rename from docs/public/images/learn/hardhat-testing/autocomplete-unlockTime.png
rename to docs/images/learn/hardhat-testing/autocomplete-unlockTime.png
diff --git a/docs/public/images/learn/hardhat-verify/hardhat-verify-success.png b/docs/images/learn/hardhat-verify/hardhat-verify-success.png
similarity index 100%
rename from docs/public/images/learn/hardhat-verify/hardhat-verify-success.png
rename to docs/images/learn/hardhat-verify/hardhat-verify-success.png
diff --git a/docs/public/images/learn/hardhat-verify/hardhat-verify.png b/docs/images/learn/hardhat-verify/hardhat-verify.png
similarity index 100%
rename from docs/public/images/learn/hardhat-verify/hardhat-verify.png
rename to docs/images/learn/hardhat-verify/hardhat-verify.png
diff --git a/docs/public/images/learn/hardhat-verify/harhat-verify-create-key.png b/docs/images/learn/hardhat-verify/harhat-verify-create-key.png
similarity index 100%
rename from docs/public/images/learn/hardhat-verify/harhat-verify-create-key.png
rename to docs/images/learn/hardhat-verify/harhat-verify-create-key.png
diff --git a/docs/public/images/learn/icons/coding-white.svg b/docs/images/learn/icons/coding-white.svg
similarity index 100%
rename from docs/public/images/learn/icons/coding-white.svg
rename to docs/images/learn/icons/coding-white.svg
diff --git a/docs/public/images/learn/icons/coding.svg b/docs/images/learn/icons/coding.svg
similarity index 100%
rename from docs/public/images/learn/icons/coding.svg
rename to docs/images/learn/icons/coding.svg
diff --git a/docs/public/images/learn/icons/quizzes-white.svg b/docs/images/learn/icons/quizzes-white.svg
similarity index 100%
rename from docs/public/images/learn/icons/quizzes-white.svg
rename to docs/images/learn/icons/quizzes-white.svg
diff --git a/docs/public/images/learn/icons/quizzes.svg b/docs/images/learn/icons/quizzes.svg
similarity index 100%
rename from docs/public/images/learn/icons/quizzes.svg
rename to docs/images/learn/icons/quizzes.svg
diff --git a/docs/public/images/learn/icons/reading-white.svg b/docs/images/learn/icons/reading-white.svg
similarity index 100%
rename from docs/public/images/learn/icons/reading-white.svg
rename to docs/images/learn/icons/reading-white.svg
diff --git a/docs/public/images/learn/icons/reading.svg b/docs/images/learn/icons/reading.svg
similarity index 100%
rename from docs/public/images/learn/icons/reading.svg
rename to docs/images/learn/icons/reading.svg
diff --git a/docs/public/images/learn/icons/stepbystep-white.svg b/docs/images/learn/icons/stepbystep-white.svg
similarity index 100%
rename from docs/public/images/learn/icons/stepbystep-white.svg
rename to docs/images/learn/icons/stepbystep-white.svg
diff --git a/docs/public/images/learn/icons/stepbystep.svg b/docs/images/learn/icons/stepbystep.svg
similarity index 100%
rename from docs/public/images/learn/icons/stepbystep.svg
rename to docs/images/learn/icons/stepbystep.svg
diff --git a/docs/public/images/learn/icons/video-white.svg b/docs/images/learn/icons/video-white.svg
similarity index 100%
rename from docs/public/images/learn/icons/video-white.svg
rename to docs/images/learn/icons/video-white.svg
diff --git a/docs/public/images/learn/icons/video.svg b/docs/images/learn/icons/video.svg
similarity index 100%
rename from docs/public/images/learn/icons/video.svg
rename to docs/images/learn/icons/video.svg
diff --git a/docs/public/images/learn/introduction-to-ethereum/btc-eth-comparison.png b/docs/images/learn/introduction-to-ethereum/btc-eth-comparison.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-ethereum/btc-eth-comparison.png
rename to docs/images/learn/introduction-to-ethereum/btc-eth-comparison.png
diff --git a/docs/public/images/learn/introduction-to-ethereum/gas-costs.png b/docs/images/learn/introduction-to-ethereum/gas-costs.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-ethereum/gas-costs.png
rename to docs/images/learn/introduction-to-ethereum/gas-costs.png
diff --git a/docs/public/images/learn/introduction-to-ethereum/web2-web3-development.png b/docs/images/learn/introduction-to-ethereum/web2-web3-development.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-ethereum/web2-web3-development.png
rename to docs/images/learn/introduction-to-ethereum/web2-web3-development.png
diff --git a/docs/public/images/learn/introduction-to-ethereum/web2-web3-limitations.png b/docs/images/learn/introduction-to-ethereum/web2-web3-limitations.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-ethereum/web2-web3-limitations.png
rename to docs/images/learn/introduction-to-ethereum/web2-web3-limitations.png
diff --git a/docs/public/images/learn/introduction-to-solidity/delete.png b/docs/images/learn/introduction-to-solidity/delete.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/delete.png
rename to docs/images/learn/introduction-to-solidity/delete.png
diff --git a/docs/public/images/learn/introduction-to-solidity/deploy-button.png b/docs/images/learn/introduction-to-solidity/deploy-button.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/deploy-button.png
rename to docs/images/learn/introduction-to-solidity/deploy-button.png
diff --git a/docs/public/images/learn/introduction-to-solidity/editor-pane.png b/docs/images/learn/introduction-to-solidity/editor-pane.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/editor-pane.png
rename to docs/images/learn/introduction-to-solidity/editor-pane.png
diff --git a/docs/public/images/learn/introduction-to-solidity/key-value-store.png b/docs/images/learn/introduction-to-solidity/key-value-store.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/key-value-store.png
rename to docs/images/learn/introduction-to-solidity/key-value-store.png
diff --git a/docs/public/images/learn/introduction-to-solidity/remix-contract-buttons.png b/docs/images/learn/introduction-to-solidity/remix-contract-buttons.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/remix-contract-buttons.png
rename to docs/images/learn/introduction-to-solidity/remix-contract-buttons.png
diff --git a/docs/public/images/learn/introduction-to-solidity/remix-deploy-chevron.png b/docs/images/learn/introduction-to-solidity/remix-deploy-chevron.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/remix-deploy-chevron.png
rename to docs/images/learn/introduction-to-solidity/remix-deploy-chevron.png
diff --git a/docs/public/images/learn/introduction-to-solidity/remix-deploy-log.png b/docs/images/learn/introduction-to-solidity/remix-deploy-log.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/remix-deploy-log.png
rename to docs/images/learn/introduction-to-solidity/remix-deploy-log.png
diff --git a/docs/public/images/learn/introduction-to-solidity/remix-editor.png b/docs/images/learn/introduction-to-solidity/remix-editor.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/remix-editor.png
rename to docs/images/learn/introduction-to-solidity/remix-editor.png
diff --git a/docs/public/images/learn/introduction-to-solidity/remix-home.png b/docs/images/learn/introduction-to-solidity/remix-home.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/remix-home.png
rename to docs/images/learn/introduction-to-solidity/remix-home.png
diff --git a/docs/public/images/learn/introduction-to-solidity/remix-retrieve.png b/docs/images/learn/introduction-to-solidity/remix-retrieve.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/remix-retrieve.png
rename to docs/images/learn/introduction-to-solidity/remix-retrieve.png
diff --git a/docs/public/images/learn/introduction-to-solidity/remix-settings.png b/docs/images/learn/introduction-to-solidity/remix-settings.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/remix-settings.png
rename to docs/images/learn/introduction-to-solidity/remix-settings.png
diff --git a/docs/public/images/learn/introduction-to-solidity/remix-terminal.png b/docs/images/learn/introduction-to-solidity/remix-terminal.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/remix-terminal.png
rename to docs/images/learn/introduction-to-solidity/remix-terminal.png
diff --git a/docs/public/images/learn/introduction-to-solidity/remix-transaction-console.png b/docs/images/learn/introduction-to-solidity/remix-transaction-console.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/remix-transaction-console.png
rename to docs/images/learn/introduction-to-solidity/remix-transaction-console.png
diff --git a/docs/public/images/learn/introduction-to-solidity/variable-order-inefficient.png b/docs/images/learn/introduction-to-solidity/variable-order-inefficient.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/variable-order-inefficient.png
rename to docs/images/learn/introduction-to-solidity/variable-order-inefficient.png
diff --git a/docs/public/images/learn/introduction-to-solidity/variable-order-optimized.png b/docs/images/learn/introduction-to-solidity/variable-order-optimized.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/variable-order-optimized.png
rename to docs/images/learn/introduction-to-solidity/variable-order-optimized.png
diff --git a/docs/public/images/learn/introduction-to-solidity/variable-packing.png b/docs/images/learn/introduction-to-solidity/variable-packing.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-solidity/variable-packing.png
rename to docs/images/learn/introduction-to-solidity/variable-packing.png
diff --git a/docs/public/images/learn/introduction-to-tokens/erc-1155.png b/docs/images/learn/introduction-to-tokens/erc-1155.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-tokens/erc-1155.png
rename to docs/images/learn/introduction-to-tokens/erc-1155.png
diff --git a/docs/public/images/learn/introduction-to-tokens/erc-20.png b/docs/images/learn/introduction-to-tokens/erc-20.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-tokens/erc-20.png
rename to docs/images/learn/introduction-to-tokens/erc-20.png
diff --git a/docs/public/images/learn/introduction-to-tokens/erc-721.png b/docs/images/learn/introduction-to-tokens/erc-721.png
similarity index 100%
rename from docs/public/images/learn/introduction-to-tokens/erc-721.png
rename to docs/images/learn/introduction-to-tokens/erc-721.png
diff --git a/docs/public/images/learn/minimal-tokens/balance.png b/docs/images/learn/minimal-tokens/balance.png
similarity index 100%
rename from docs/public/images/learn/minimal-tokens/balance.png
rename to docs/images/learn/minimal-tokens/balance.png
diff --git a/docs/public/images/learn/minimal-tokens/split-balances.png b/docs/images/learn/minimal-tokens/split-balances.png
similarity index 100%
rename from docs/public/images/learn/minimal-tokens/split-balances.png
rename to docs/images/learn/minimal-tokens/split-balances.png
diff --git a/docs/public/images/learn/minimal-tokens/transferred.png b/docs/images/learn/minimal-tokens/transferred.png
similarity index 100%
rename from docs/public/images/learn/minimal-tokens/transferred.png
rename to docs/images/learn/minimal-tokens/transferred.png
diff --git a/docs/public/images/learn/new-keyword/at-address.png b/docs/images/learn/new-keyword/at-address.png
similarity index 100%
rename from docs/public/images/learn/new-keyword/at-address.png
rename to docs/images/learn/new-keyword/at-address.png
diff --git a/docs/public/images/learn/new-keyword/deployed.png b/docs/images/learn/new-keyword/deployed.png
similarity index 100%
rename from docs/public/images/learn/new-keyword/deployed.png
rename to docs/images/learn/new-keyword/deployed.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_01.png b/docs/images/learn/nft-pins/Base_Camp_NFT_01.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_01.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_01.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_02.png b/docs/images/learn/nft-pins/Base_Camp_NFT_02.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_02.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_02.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_03.png b/docs/images/learn/nft-pins/Base_Camp_NFT_03.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_03.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_03.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_04.png b/docs/images/learn/nft-pins/Base_Camp_NFT_04.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_04.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_04.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_05.png b/docs/images/learn/nft-pins/Base_Camp_NFT_05.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_05.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_05.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_06.png b/docs/images/learn/nft-pins/Base_Camp_NFT_06.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_06.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_06.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_07.png b/docs/images/learn/nft-pins/Base_Camp_NFT_07.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_07.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_07.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_08.png b/docs/images/learn/nft-pins/Base_Camp_NFT_08.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_08.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_08.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_09.png b/docs/images/learn/nft-pins/Base_Camp_NFT_09.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_09.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_09.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_10.png b/docs/images/learn/nft-pins/Base_Camp_NFT_10.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_10.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_10.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_11.png b/docs/images/learn/nft-pins/Base_Camp_NFT_11.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_11.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_11.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_12.png b/docs/images/learn/nft-pins/Base_Camp_NFT_12.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_12.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_12.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_13.png b/docs/images/learn/nft-pins/Base_Camp_NFT_13.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_13.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_13.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_14.png b/docs/images/learn/nft-pins/Base_Camp_NFT_14.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_14.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_14.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_15.png b/docs/images/learn/nft-pins/Base_Camp_NFT_15.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_15.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_15.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_16.png b/docs/images/learn/nft-pins/Base_Camp_NFT_16.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_16.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_16.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_17.png b/docs/images/learn/nft-pins/Base_Camp_NFT_17.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_17.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_17.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_18.png b/docs/images/learn/nft-pins/Base_Camp_NFT_18.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_18.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_18.png
diff --git a/docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_19.png b/docs/images/learn/nft-pins/Base_Camp_NFT_19.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/Base_Camp_NFT_19.png
rename to docs/images/learn/nft-pins/Base_Camp_NFT_19.png
diff --git a/docs/components/CafeUnitTest/assets/images/TestPin.png b/docs/images/learn/nft-pins/TestPin.png
similarity index 100%
rename from docs/components/CafeUnitTest/assets/images/TestPin.png
rename to docs/images/learn/nft-pins/TestPin.png
diff --git a/docs/public/images/learn/reading-and-displaying-data/issues-console-log.png b/docs/images/learn/reading-and-displaying-data/issues-console-log.png
similarity index 100%
rename from docs/public/images/learn/reading-and-displaying-data/issues-console-log.png
rename to docs/images/learn/reading-and-displaying-data/issues-console-log.png
diff --git a/docs/public/images/learn/reading-and-displaying-data/missing-data.png b/docs/images/learn/reading-and-displaying-data/missing-data.png
similarity index 100%
rename from docs/public/images/learn/reading-and-displaying-data/missing-data.png
rename to docs/images/learn/reading-and-displaying-data/missing-data.png
diff --git a/docs/public/images/learn/storage/deployment-with-params.png b/docs/images/learn/storage/deployment-with-params.png
similarity index 100%
rename from docs/public/images/learn/storage/deployment-with-params.png
rename to docs/images/learn/storage/deployment-with-params.png
diff --git a/docs/public/images/learn/welcome/Base_Learn_Hero.png b/docs/images/learn/welcome/Base_Learn_Hero.png
similarity index 100%
rename from docs/public/images/learn/welcome/Base_Learn_Hero.png
rename to docs/images/learn/welcome/Base_Learn_Hero.png
diff --git a/docs/public/images/minikit/example_embed.png b/docs/images/minikit/example_embed.png
similarity index 100%
rename from docs/public/images/minikit/example_embed.png
rename to docs/images/minikit/example_embed.png
diff --git a/docs/public/images/minikit/manifest-setup.png b/docs/images/minikit/manifest-setup.png
similarity index 100%
rename from docs/public/images/minikit/manifest-setup.png
rename to docs/images/minikit/manifest-setup.png
diff --git a/docs/public/images/minikit/minikit-cli.gif b/docs/images/minikit/minikit-cli.gif
similarity index 100%
rename from docs/public/images/minikit/minikit-cli.gif
rename to docs/images/minikit/minikit-cli.gif
diff --git a/docs/public/images/mobile-wallet-protocol/diffie-hellman.png b/docs/images/mobile-wallet-protocol/diffie-hellman.png
similarity index 100%
rename from docs/public/images/mobile-wallet-protocol/diffie-hellman.png
rename to docs/images/mobile-wallet-protocol/diffie-hellman.png
diff --git a/docs/public/images/mobile-wallet-protocol/handshake.png b/docs/images/mobile-wallet-protocol/handshake.png
similarity index 100%
rename from docs/public/images/mobile-wallet-protocol/handshake.png
rename to docs/images/mobile-wallet-protocol/handshake.png
diff --git a/docs/public/images/mobile-wallet-protocol/overview.png b/docs/images/mobile-wallet-protocol/overview.png
similarity index 100%
rename from docs/public/images/mobile-wallet-protocol/overview.png
rename to docs/images/mobile-wallet-protocol/overview.png
diff --git a/docs/public/images/onchain-generative-nfts/architecture.png b/docs/images/onchain-generative-nfts/architecture.png
similarity index 100%
rename from docs/public/images/onchain-generative-nfts/architecture.png
rename to docs/images/onchain-generative-nfts/architecture.png
diff --git a/docs/public/images/onchain-generative-nfts/first_pass.png b/docs/images/onchain-generative-nfts/first_pass.png
similarity index 100%
rename from docs/public/images/onchain-generative-nfts/first_pass.png
rename to docs/images/onchain-generative-nfts/first_pass.png
diff --git a/docs/public/images/onchain-generative-nfts/mockup.png b/docs/images/onchain-generative-nfts/mockup.png
similarity index 100%
rename from docs/public/images/onchain-generative-nfts/mockup.png
rename to docs/images/onchain-generative-nfts/mockup.png
diff --git a/docs/public/images/onchain-generative-nfts/progress.png b/docs/images/onchain-generative-nfts/progress.png
similarity index 100%
rename from docs/public/images/onchain-generative-nfts/progress.png
rename to docs/images/onchain-generative-nfts/progress.png
diff --git a/docs/public/images/onchainkit-tutorials/fund-funding-options.png b/docs/images/onchainkit-tutorials/fund-funding-options.png
similarity index 100%
rename from docs/public/images/onchainkit-tutorials/fund-funding-options.png
rename to docs/images/onchainkit-tutorials/fund-funding-options.png
diff --git a/docs/public/images/onchainkit-tutorials/fund-onramp-config.png b/docs/images/onchainkit-tutorials/fund-onramp-config.png
similarity index 100%
rename from docs/public/images/onchainkit-tutorials/fund-onramp-config.png
rename to docs/images/onchainkit-tutorials/fund-onramp-config.png
diff --git a/docs/public/images/onchainkit-tutorials/fund-wallet-balance.png b/docs/images/onchainkit-tutorials/fund-wallet-balance.png
similarity index 100%
rename from docs/public/images/onchainkit-tutorials/fund-wallet-balance.png
rename to docs/images/onchainkit-tutorials/fund-wallet-balance.png
diff --git a/docs/public/images/onchainkit-tutorials/pay-commerce-products.png b/docs/images/onchainkit-tutorials/pay-commerce-products.png
similarity index 100%
rename from docs/public/images/onchainkit-tutorials/pay-commerce-products.png
rename to docs/images/onchainkit-tutorials/pay-commerce-products.png
diff --git a/docs/public/images/onchainkit-tutorials/pay-copy-product-link.png b/docs/images/onchainkit-tutorials/pay-copy-product-link.png
similarity index 100%
rename from docs/public/images/onchainkit-tutorials/pay-copy-product-link.png
rename to docs/images/onchainkit-tutorials/pay-copy-product-link.png
diff --git a/docs/public/images/onchainkit-tutorials/pay-create-product-details.png b/docs/images/onchainkit-tutorials/pay-create-product-details.png
similarity index 100%
rename from docs/public/images/onchainkit-tutorials/pay-create-product-details.png
rename to docs/images/onchainkit-tutorials/pay-create-product-details.png
diff --git a/docs/public/images/onchainkit-tutorials/pay-final-product.png b/docs/images/onchainkit-tutorials/pay-final-product.png
similarity index 100%
rename from docs/public/images/onchainkit-tutorials/pay-final-product.png
rename to docs/images/onchainkit-tutorials/pay-final-product.png
diff --git a/docs/public/images/onchainkit/NFTCard.gif b/docs/images/onchainkit/NFTCard.gif
similarity index 100%
rename from docs/public/images/onchainkit/NFTCard.gif
rename to docs/images/onchainkit/NFTCard.gif
diff --git a/docs/public/images/onchainkit/NFTMintCard.gif b/docs/images/onchainkit/NFTMintCard.gif
similarity index 100%
rename from docs/public/images/onchainkit/NFTMintCard.gif
rename to docs/images/onchainkit/NFTMintCard.gif
diff --git a/docs/public/images/onchainkit/buy.gif b/docs/images/onchainkit/buy.gif
similarity index 100%
rename from docs/public/images/onchainkit/buy.gif
rename to docs/images/onchainkit/buy.gif
diff --git a/docs/public/images/onchainkit/checkout.gif b/docs/images/onchainkit/checkout.gif
similarity index 100%
rename from docs/public/images/onchainkit/checkout.gif
rename to docs/images/onchainkit/checkout.gif
diff --git a/docs/public/images/onchainkit/commerce-1.png b/docs/images/onchainkit/commerce-1.png
similarity index 100%
rename from docs/public/images/onchainkit/commerce-1.png
rename to docs/images/onchainkit/commerce-1.png
diff --git a/docs/public/images/onchainkit/commerce-2.png b/docs/images/onchainkit/commerce-2.png
similarity index 100%
rename from docs/public/images/onchainkit/commerce-2.png
rename to docs/images/onchainkit/commerce-2.png
diff --git a/docs/public/images/onchainkit/commerce-3.png b/docs/images/onchainkit/commerce-3.png
similarity index 100%
rename from docs/public/images/onchainkit/commerce-3.png
rename to docs/images/onchainkit/commerce-3.png
diff --git a/docs/public/images/onchainkit/commerce-4.png b/docs/images/onchainkit/commerce-4.png
similarity index 100%
rename from docs/public/images/onchainkit/commerce-4.png
rename to docs/images/onchainkit/commerce-4.png
diff --git a/docs/public/images/onchainkit/copy-api-key-guide.png b/docs/images/onchainkit/copy-api-key-guide.png
similarity index 100%
rename from docs/public/images/onchainkit/copy-api-key-guide.png
rename to docs/images/onchainkit/copy-api-key-guide.png
diff --git a/docs/public/images/onchainkit/copy-project-id.png b/docs/images/onchainkit/copy-project-id.png
similarity index 100%
rename from docs/public/images/onchainkit/copy-project-id.png
rename to docs/images/onchainkit/copy-project-id.png
diff --git a/docs/public/images/onchainkit/cursor-dir.gif b/docs/images/onchainkit/cursor-dir.gif
similarity index 100%
rename from docs/public/images/onchainkit/cursor-dir.gif
rename to docs/images/onchainkit/cursor-dir.gif
diff --git a/docs/public/images/onchainkit/fetch-frame-part-II.png b/docs/images/onchainkit/fetch-frame-part-II.png
similarity index 100%
rename from docs/public/images/onchainkit/fetch-frame-part-II.png
rename to docs/images/onchainkit/fetch-frame-part-II.png
diff --git a/docs/public/images/onchainkit/fetch-frame.png b/docs/images/onchainkit/fetch-frame.png
similarity index 100%
rename from docs/public/images/onchainkit/fetch-frame.png
rename to docs/images/onchainkit/fetch-frame.png
diff --git a/docs/public/images/onchainkit/frame.png b/docs/images/onchainkit/frame.png
similarity index 100%
rename from docs/public/images/onchainkit/frame.png
rename to docs/images/onchainkit/frame.png
diff --git a/docs/public/images/onchainkit/fund-card.gif b/docs/images/onchainkit/fund-card.gif
similarity index 100%
rename from docs/public/images/onchainkit/fund-card.gif
rename to docs/images/onchainkit/fund-card.gif
diff --git a/docs/public/images/onchainkit/getting-started-create-env-file.png b/docs/images/onchainkit/getting-started-create-env-file.png
similarity index 100%
rename from docs/public/images/onchainkit/getting-started-create-env-file.png
rename to docs/images/onchainkit/getting-started-create-env-file.png
diff --git a/docs/public/images/onchainkit/onchain-app-template-1.png b/docs/images/onchainkit/onchain-app-template-1.png
similarity index 100%
rename from docs/public/images/onchainkit/onchain-app-template-1.png
rename to docs/images/onchainkit/onchain-app-template-1.png
diff --git a/docs/public/images/onchainkit/onchain-figma.png b/docs/images/onchainkit/onchain-figma.png
similarity index 100%
rename from docs/public/images/onchainkit/onchain-figma.png
rename to docs/images/onchainkit/onchain-figma.png
diff --git a/docs/public/images/onchainkit/onchainkit-components-paymaster-endpoint.png b/docs/images/onchainkit/onchainkit-components-paymaster-endpoint.png
similarity index 100%
rename from docs/public/images/onchainkit/onchainkit-components-paymaster-endpoint.png
rename to docs/images/onchainkit/onchainkit-components-paymaster-endpoint.png
diff --git a/docs/public/images/onchainkit/onchainkit-components-transaction-anatomy.png b/docs/images/onchainkit/onchainkit-components-transaction-anatomy.png
similarity index 100%
rename from docs/public/images/onchainkit/onchainkit-components-transaction-anatomy.png
rename to docs/images/onchainkit/onchainkit-components-transaction-anatomy.png
diff --git a/docs/public/images/onchainkit/onchainkit-figma-design-components.png b/docs/images/onchainkit/onchainkit-figma-design-components.png
similarity index 100%
rename from docs/public/images/onchainkit/onchainkit-figma-design-components.png
rename to docs/images/onchainkit/onchainkit-figma-design-components.png
diff --git a/docs/public/images/onchainkit/onchainkit-figma-design-how-to-use.png b/docs/images/onchainkit/onchainkit-figma-design-how-to-use.png
similarity index 100%
rename from docs/public/images/onchainkit/onchainkit-figma-design-how-to-use.png
rename to docs/images/onchainkit/onchainkit-figma-design-how-to-use.png
diff --git a/docs/public/images/onchainkit/onchainkit-figma-design.png b/docs/images/onchainkit/onchainkit-figma-design.png
similarity index 100%
rename from docs/public/images/onchainkit/onchainkit-figma-design.png
rename to docs/images/onchainkit/onchainkit-figma-design.png
diff --git a/docs/public/images/onchainkit/onchainkit-identity.png b/docs/images/onchainkit/onchainkit-identity.png
similarity index 100%
rename from docs/public/images/onchainkit/onchainkit-identity.png
rename to docs/images/onchainkit/onchainkit-identity.png
diff --git a/docs/public/images/onchainkit/onchainkit-lifecycle-status-vibes.png b/docs/images/onchainkit/onchainkit-lifecycle-status-vibes.png
similarity index 100%
rename from docs/public/images/onchainkit/onchainkit-lifecycle-status-vibes.png
rename to docs/images/onchainkit/onchainkit-lifecycle-status-vibes.png
diff --git a/docs/public/images/onchainkit/onchainkit-template.png b/docs/images/onchainkit/onchainkit-template.png
similarity index 100%
rename from docs/public/images/onchainkit/onchainkit-template.png
rename to docs/images/onchainkit/onchainkit-template.png
diff --git a/docs/public/images/onchainkit/onchainkit-themes.gif b/docs/images/onchainkit/onchainkit-themes.gif
similarity index 100%
rename from docs/public/images/onchainkit/onchainkit-themes.gif
rename to docs/images/onchainkit/onchainkit-themes.gif
diff --git a/docs/public/images/onchainkit/onchainkit-token.png b/docs/images/onchainkit/onchainkit-token.png
similarity index 100%
rename from docs/public/images/onchainkit/onchainkit-token.png
rename to docs/images/onchainkit/onchainkit-token.png
diff --git a/docs/public/images/onchainkit/onchainkit-wallet-1.png b/docs/images/onchainkit/onchainkit-wallet-1.png
similarity index 100%
rename from docs/public/images/onchainkit/onchainkit-wallet-1.png
rename to docs/images/onchainkit/onchainkit-wallet-1.png
diff --git a/docs/public/images/onchainkit/onchainkit-wallet-2.png b/docs/images/onchainkit/onchainkit-wallet-2.png
similarity index 100%
rename from docs/public/images/onchainkit/onchainkit-wallet-2.png
rename to docs/images/onchainkit/onchainkit-wallet-2.png
diff --git a/docs/public/images/onchainkit/onramp-secure-init.png b/docs/images/onchainkit/onramp-secure-init.png
similarity index 100%
rename from docs/public/images/onchainkit/onramp-secure-init.png
rename to docs/images/onchainkit/onramp-secure-init.png
diff --git a/docs/public/images/onchainkit/pay-button.png b/docs/images/onchainkit/pay-button.png
similarity index 100%
rename from docs/public/images/onchainkit/pay-button.png
rename to docs/images/onchainkit/pay-button.png
diff --git a/docs/public/images/onchainkit/quickstart.png b/docs/images/onchainkit/quickstart.png
similarity index 100%
rename from docs/public/images/onchainkit/quickstart.png
rename to docs/images/onchainkit/quickstart.png
diff --git a/docs/public/images/onchainkit/use-onchain-app-template.png b/docs/images/onchainkit/use-onchain-app-template.png
similarity index 100%
rename from docs/public/images/onchainkit/use-onchain-app-template.png
rename to docs/images/onchainkit/use-onchain-app-template.png
diff --git a/docs/public/images/onchainkit/wallet-island.gif b/docs/images/onchainkit/wallet-island.gif
similarity index 100%
rename from docs/public/images/onchainkit/wallet-island.gif
rename to docs/images/onchainkit/wallet-island.gif
diff --git a/docs/public/images/onchainkit/wallet-modal.gif b/docs/images/onchainkit/wallet-modal.gif
similarity index 100%
rename from docs/public/images/onchainkit/wallet-modal.gif
rename to docs/images/onchainkit/wallet-modal.gif
diff --git a/docs/public/images/onchainkit/wallet-modal.png b/docs/images/onchainkit/wallet-modal.png
similarity index 100%
rename from docs/public/images/onchainkit/wallet-modal.png
rename to docs/images/onchainkit/wallet-modal.png
diff --git a/docs/public/images/onchainkit/warpcast-logo.png b/docs/images/onchainkit/warpcast-logo.png
similarity index 100%
rename from docs/public/images/onchainkit/warpcast-logo.png
rename to docs/images/onchainkit/warpcast-logo.png
diff --git a/docs/public/images/openframes-fc/debugger-of-not-valid-zoom.png b/docs/images/openframes-fc/debugger-of-not-valid-zoom.png
similarity index 100%
rename from docs/public/images/openframes-fc/debugger-of-not-valid-zoom.png
rename to docs/images/openframes-fc/debugger-of-not-valid-zoom.png
diff --git a/docs/public/images/openframes-fc/debugger-of-not-valid.png b/docs/images/openframes-fc/debugger-of-not-valid.png
similarity index 100%
rename from docs/public/images/openframes-fc/debugger-of-not-valid.png
rename to docs/images/openframes-fc/debugger-of-not-valid.png
diff --git a/docs/public/images/openframes-fc/debugger-of-valid.png b/docs/images/openframes-fc/debugger-of-valid.png
similarity index 100%
rename from docs/public/images/openframes-fc/debugger-of-valid.png
rename to docs/images/openframes-fc/debugger-of-valid.png
diff --git a/docs/public/images/openframes-fc/debugger-protocol-selector.png b/docs/images/openframes-fc/debugger-protocol-selector.png
similarity index 100%
rename from docs/public/images/openframes-fc/debugger-protocol-selector.png
rename to docs/images/openframes-fc/debugger-protocol-selector.png
diff --git a/docs/public/images/openframes-fc/frame-custom-no-url.png b/docs/images/openframes-fc/frame-custom-no-url.png
similarity index 100%
rename from docs/public/images/openframes-fc/frame-custom-no-url.png
rename to docs/images/openframes-fc/frame-custom-no-url.png
diff --git a/docs/public/images/openframes-fc/frame-custom-url.png b/docs/images/openframes-fc/frame-custom-url.png
similarity index 100%
rename from docs/public/images/openframes-fc/frame-custom-url.png
rename to docs/images/openframes-fc/frame-custom-url.png
diff --git a/docs/public/images/paymaster/pb-paymaster-chainid.png b/docs/images/paymaster/pb-paymaster-chainid.png
similarity index 100%
rename from docs/public/images/paymaster/pb-paymaster-chainid.png
rename to docs/images/paymaster/pb-paymaster-chainid.png
diff --git a/docs/public/images/paymaster/pb-paymaster-config-highlight.png b/docs/images/paymaster/pb-paymaster-config-highlight.png
similarity index 100%
rename from docs/public/images/paymaster/pb-paymaster-config-highlight.png
rename to docs/images/paymaster/pb-paymaster-config-highlight.png
diff --git a/docs/public/images/paymaster/pb-paymaster-config.png b/docs/images/paymaster/pb-paymaster-config.png
similarity index 100%
rename from docs/public/images/paymaster/pb-paymaster-config.png
rename to docs/images/paymaster/pb-paymaster-config.png
diff --git a/docs/public/images/paymaster/pb-paymaster-policy-erc20-flow.png b/docs/images/paymaster/pb-paymaster-policy-erc20-flow.png
similarity index 100%
rename from docs/public/images/paymaster/pb-paymaster-policy-erc20-flow.png
rename to docs/images/paymaster/pb-paymaster-policy-erc20-flow.png
diff --git a/docs/public/images/paymaster/pb-paymaster-policy-erc20.png b/docs/images/paymaster/pb-paymaster-policy-erc20.png
similarity index 100%
rename from docs/public/images/paymaster/pb-paymaster-policy-erc20.png
rename to docs/images/paymaster/pb-paymaster-policy-erc20.png
diff --git a/docs/public/images/paymaster/pb-paymaster-tenderly-dev-debug.png b/docs/images/paymaster/pb-paymaster-tenderly-dev-debug.png
similarity index 100%
rename from docs/public/images/paymaster/pb-paymaster-tenderly-dev-debug.png
rename to docs/images/paymaster/pb-paymaster-tenderly-dev-debug.png
diff --git a/docs/public/images/paymaster/pb-paymaster-tenderly-entrypoint.png b/docs/images/paymaster/pb-paymaster-tenderly-entrypoint.png
similarity index 100%
rename from docs/public/images/paymaster/pb-paymaster-tenderly-entrypoint.png
rename to docs/images/paymaster/pb-paymaster-tenderly-entrypoint.png
diff --git a/docs/public/images/paymaster/pb-sponsorship-scw.png b/docs/images/paymaster/pb-sponsorship-scw.png
similarity index 100%
rename from docs/public/images/paymaster/pb-sponsorship-scw.png
rename to docs/images/paymaster/pb-sponsorship-scw.png
diff --git a/docs/public/images/resend-email-campaigns/ock-dashboard.png b/docs/images/resend-email-campaigns/ock-dashboard.png
similarity index 100%
rename from docs/public/images/resend-email-campaigns/ock-dashboard.png
rename to docs/images/resend-email-campaigns/ock-dashboard.png
diff --git a/docs/public/images/resend-email-campaigns/ock-use-template.png b/docs/images/resend-email-campaigns/ock-use-template.png
similarity index 100%
rename from docs/public/images/resend-email-campaigns/ock-use-template.png
rename to docs/images/resend-email-campaigns/ock-use-template.png
diff --git a/docs/public/images/resend-email-campaigns/resend-1.gif b/docs/images/resend-email-campaigns/resend-1.gif
similarity index 100%
rename from docs/public/images/resend-email-campaigns/resend-1.gif
rename to docs/images/resend-email-campaigns/resend-1.gif
diff --git a/docs/public/images/resend-email-campaigns/resend-api-keys.png b/docs/images/resend-email-campaigns/resend-api-keys.png
similarity index 100%
rename from docs/public/images/resend-email-campaigns/resend-api-keys.png
rename to docs/images/resend-email-campaigns/resend-api-keys.png
diff --git a/docs/public/images/resend-email-campaigns/resend-contact-added.png b/docs/images/resend-email-campaigns/resend-contact-added.png
similarity index 100%
rename from docs/public/images/resend-email-campaigns/resend-contact-added.png
rename to docs/images/resend-email-campaigns/resend-contact-added.png
diff --git a/docs/public/images/resend-email-campaigns/resend-mailing-list-prompt.png b/docs/images/resend-email-campaigns/resend-mailing-list-prompt.png
similarity index 100%
rename from docs/public/images/resend-email-campaigns/resend-mailing-list-prompt.png
rename to docs/images/resend-email-campaigns/resend-mailing-list-prompt.png
diff --git a/docs/public/images/resend-email-campaigns/resend-user-subscribed.png b/docs/images/resend-email-campaigns/resend-user-subscribed.png
similarity index 100%
rename from docs/public/images/resend-email-campaigns/resend-user-subscribed.png
rename to docs/images/resend-email-campaigns/resend-user-subscribed.png
diff --git a/docs/public/images/resend-email-campaigns/site-load.png b/docs/images/resend-email-campaigns/site-load.png
similarity index 100%
rename from docs/public/images/resend-email-campaigns/site-load.png
rename to docs/images/resend-email-campaigns/site-load.png
diff --git a/docs/public/images/resend-email-campaigns/vercel-import-project.png b/docs/images/resend-email-campaigns/vercel-import-project.png
similarity index 100%
rename from docs/public/images/resend-email-campaigns/vercel-import-project.png
rename to docs/images/resend-email-campaigns/vercel-import-project.png
diff --git a/docs/public/images/resend-email-campaigns/verel-login.png b/docs/images/resend-email-campaigns/verel-login.png
similarity index 100%
rename from docs/public/images/resend-email-campaigns/verel-login.png
rename to docs/images/resend-email-campaigns/verel-login.png
diff --git a/docs/public/images/resend-email-campaigns/wc-project-page.png b/docs/images/resend-email-campaigns/wc-project-page.png
similarity index 100%
rename from docs/public/images/resend-email-campaigns/wc-project-page.png
rename to docs/images/resend-email-campaigns/wc-project-page.png
diff --git a/docs/public/images/shopify-storefront-commerce/shopify-install-commerce.gif b/docs/images/shopify-storefront-commerce/shopify-install-commerce.gif
similarity index 100%
rename from docs/public/images/shopify-storefront-commerce/shopify-install-commerce.gif
rename to docs/images/shopify-storefront-commerce/shopify-install-commerce.gif
diff --git a/docs/images/showcase/aerodrome.svg b/docs/images/showcase/aerodrome.svg
new file mode 100644
index 00000000..dbdc34ee
--- /dev/null
+++ b/docs/images/showcase/aerodrome.svg
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/images/showcase/blocklords.svg b/docs/images/showcase/blocklords.svg
new file mode 100644
index 00000000..9cc9d311
--- /dev/null
+++ b/docs/images/showcase/blocklords.svg
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/images/showcase/cat-town.svg b/docs/images/showcase/cat-town.svg
new file mode 100644
index 00000000..2834c137
--- /dev/null
+++ b/docs/images/showcase/cat-town.svg
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/images/showcase/heyelsa.svg b/docs/images/showcase/heyelsa.svg
new file mode 100644
index 00000000..d6a3aa18
--- /dev/null
+++ b/docs/images/showcase/heyelsa.svg
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/images/showcase/morpho.svg b/docs/images/showcase/morpho.svg
new file mode 100644
index 00000000..c1da924b
--- /dev/null
+++ b/docs/images/showcase/morpho.svg
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/images/showcase/opensea.svg b/docs/images/showcase/opensea.svg
new file mode 100644
index 00000000..44305367
--- /dev/null
+++ b/docs/images/showcase/opensea.svg
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/public/images/smart-wallet/CDPSteps.png b/docs/images/smart-wallet/CDPSteps.png
similarity index 100%
rename from docs/public/images/smart-wallet/CDPSteps.png
rename to docs/images/smart-wallet/CDPSteps.png
diff --git a/docs/public/images/smart-wallet/PaymasterAllowlist.png b/docs/images/smart-wallet/PaymasterAllowlist.png
similarity index 100%
rename from docs/public/images/smart-wallet/PaymasterAllowlist.png
rename to docs/images/smart-wallet/PaymasterAllowlist.png
diff --git a/docs/public/images/smart-wallet/PaymasterCDP.png b/docs/images/smart-wallet/PaymasterCDP.png
similarity index 100%
rename from docs/public/images/smart-wallet/PaymasterCDP.png
rename to docs/images/smart-wallet/PaymasterCDP.png
diff --git a/docs/public/images/smart-wallet/ReownSteps.png b/docs/images/smart-wallet/ReownSteps.png
similarity index 100%
rename from docs/public/images/smart-wallet/ReownSteps.png
rename to docs/images/smart-wallet/ReownSteps.png
diff --git a/docs/public/images/smart-wallet/accountRecovery.png b/docs/images/smart-wallet/accountRecovery.png
similarity index 100%
rename from docs/public/images/smart-wallet/accountRecovery.png
rename to docs/images/smart-wallet/accountRecovery.png
diff --git a/docs/public/images/smart-wallet/buttonPlacementExamples.png b/docs/images/smart-wallet/buttonPlacementExamples.png
similarity index 100%
rename from docs/public/images/smart-wallet/buttonPlacementExamples.png
rename to docs/images/smart-wallet/buttonPlacementExamples.png
diff --git a/docs/public/images/smart-wallet/copyTxnSimClickZone.png b/docs/images/smart-wallet/copyTxnSimClickZone.png
similarity index 100%
rename from docs/public/images/smart-wallet/copyTxnSimClickZone.png
rename to docs/images/smart-wallet/copyTxnSimClickZone.png
diff --git a/docs/public/images/smart-wallet/create-a-passkey.png b/docs/images/smart-wallet/create-a-passkey.png
similarity index 100%
rename from docs/public/images/smart-wallet/create-a-passkey.png
rename to docs/images/smart-wallet/create-a-passkey.png
diff --git a/docs/public/images/smart-wallet/createWalletButtonBlack.png b/docs/images/smart-wallet/createWalletButtonBlack.png
similarity index 100%
rename from docs/public/images/smart-wallet/createWalletButtonBlack.png
rename to docs/images/smart-wallet/createWalletButtonBlack.png
diff --git a/docs/public/images/smart-wallet/createWalletButtonBlue.png b/docs/images/smart-wallet/createWalletButtonBlue.png
similarity index 100%
rename from docs/public/images/smart-wallet/createWalletButtonBlue.png
rename to docs/images/smart-wallet/createWalletButtonBlue.png
diff --git a/docs/public/images/smart-wallet/customized-rainbow.png b/docs/images/smart-wallet/customized-rainbow.png
similarity index 100%
rename from docs/public/images/smart-wallet/customized-rainbow.png
rename to docs/images/smart-wallet/customized-rainbow.png
diff --git a/docs/public/images/smart-wallet/customized-wallet-list.png b/docs/images/smart-wallet/customized-wallet-list.png
similarity index 100%
rename from docs/public/images/smart-wallet/customized-wallet-list.png
rename to docs/images/smart-wallet/customized-wallet-list.png
diff --git a/docs/public/images/smart-wallet/examplePlacement.png b/docs/images/smart-wallet/examplePlacement.png
similarity index 100%
rename from docs/public/images/smart-wallet/examplePlacement.png
rename to docs/images/smart-wallet/examplePlacement.png
diff --git a/docs/public/images/smart-wallet/logo.svg b/docs/images/smart-wallet/logo.svg
similarity index 100%
rename from docs/public/images/smart-wallet/logo.svg
rename to docs/images/smart-wallet/logo.svg
diff --git a/docs/public/images/smart-wallet/meme-vs-content.png b/docs/images/smart-wallet/meme-vs-content.png
similarity index 100%
rename from docs/public/images/smart-wallet/meme-vs-content.png
rename to docs/images/smart-wallet/meme-vs-content.png
diff --git a/docs/public/images/smart-wallet/onchainkit-default-eoa.png b/docs/images/smart-wallet/onchainkit-default-eoa.png
similarity index 100%
rename from docs/public/images/smart-wallet/onchainkit-default-eoa.png
rename to docs/images/smart-wallet/onchainkit-default-eoa.png
diff --git a/docs/public/images/smart-wallet/onchainkit-default-smart.png b/docs/images/smart-wallet/onchainkit-default-smart.png
similarity index 100%
rename from docs/public/images/smart-wallet/onchainkit-default-smart.png
rename to docs/images/smart-wallet/onchainkit-default-smart.png
diff --git a/docs/public/images/smart-wallet/one-click-pay.png b/docs/images/smart-wallet/one-click-pay.png
similarity index 100%
rename from docs/public/images/smart-wallet/one-click-pay.png
rename to docs/images/smart-wallet/one-click-pay.png
diff --git a/docs/public/images/smart-wallet/pendingOwnershipChange.png b/docs/images/smart-wallet/pendingOwnershipChange.png
similarity index 100%
rename from docs/public/images/smart-wallet/pendingOwnershipChange.png
rename to docs/images/smart-wallet/pendingOwnershipChange.png
diff --git a/docs/public/images/smart-wallet/rainbow-smart-wallet.png b/docs/images/smart-wallet/rainbow-smart-wallet.png
similarity index 100%
rename from docs/public/images/smart-wallet/rainbow-smart-wallet.png
rename to docs/images/smart-wallet/rainbow-smart-wallet.png
diff --git a/docs/public/images/smart-wallet/rainbowkit-default.png b/docs/images/smart-wallet/rainbowkit-default.png
similarity index 100%
rename from docs/public/images/smart-wallet/rainbowkit-default.png
rename to docs/images/smart-wallet/rainbowkit-default.png
diff --git a/docs/public/images/smart-wallet/random-color-nft.png b/docs/images/smart-wallet/random-color-nft.png
similarity index 100%
rename from docs/public/images/smart-wallet/random-color-nft.png
rename to docs/images/smart-wallet/random-color-nft.png
diff --git a/docs/public/images/smart-wallet/recoveryKeyAddPasskey.png b/docs/images/smart-wallet/recoveryKeyAddPasskey.png
similarity index 100%
rename from docs/public/images/smart-wallet/recoveryKeyAddPasskey.png
rename to docs/images/smart-wallet/recoveryKeyAddPasskey.png
diff --git a/docs/public/images/smart-wallet/recoveryKeyCreated.png b/docs/images/smart-wallet/recoveryKeyCreated.png
similarity index 100%
rename from docs/public/images/smart-wallet/recoveryKeyCreated.png
rename to docs/images/smart-wallet/recoveryKeyCreated.png
diff --git a/docs/public/images/smart-wallet/recoveryKeyScamWarning.png b/docs/images/smart-wallet/recoveryKeyScamWarning.png
similarity index 100%
rename from docs/public/images/smart-wallet/recoveryKeyScamWarning.png
rename to docs/images/smart-wallet/recoveryKeyScamWarning.png
diff --git a/docs/public/images/smart-wallet/recoveryKeySignIn.png b/docs/images/smart-wallet/recoveryKeySignIn.png
similarity index 100%
rename from docs/public/images/smart-wallet/recoveryKeySignIn.png
rename to docs/images/smart-wallet/recoveryKeySignIn.png
diff --git a/docs/public/images/smart-wallet/sponsored-by-base.png b/docs/images/smart-wallet/sponsored-by-base.png
similarity index 100%
rename from docs/public/images/smart-wallet/sponsored-by-base.png
rename to docs/images/smart-wallet/sponsored-by-base.png
diff --git a/docs/public/images/smart-wallet/sub-account-demo.gif b/docs/images/smart-wallet/sub-account-demo.gif
similarity index 100%
rename from docs/public/images/smart-wallet/sub-account-demo.gif
rename to docs/images/smart-wallet/sub-account-demo.gif
diff --git a/docs/public/images/smart-wallet/sub-account-popup.png b/docs/images/smart-wallet/sub-account-popup.png
similarity index 100%
rename from docs/public/images/smart-wallet/sub-account-popup.png
rename to docs/images/smart-wallet/sub-account-popup.png
diff --git a/docs/public/images/smart-wallet/submitRecoveryKey.png b/docs/images/smart-wallet/submitRecoveryKey.png
similarity index 100%
rename from docs/public/images/smart-wallet/submitRecoveryKey.png
rename to docs/images/smart-wallet/submitRecoveryKey.png
diff --git a/docs/public/images/smart-wallet/wagmi-template-normal.png b/docs/images/smart-wallet/wagmi-template-normal.png
similarity index 100%
rename from docs/public/images/smart-wallet/wagmi-template-normal.png
rename to docs/images/smart-wallet/wagmi-template-normal.png
diff --git a/docs/public/images/verifications/attestation-creation-flow.png b/docs/images/verifications/attestation-creation-flow.png
similarity index 100%
rename from docs/public/images/verifications/attestation-creation-flow.png
rename to docs/images/verifications/attestation-creation-flow.png
diff --git a/docs/public/images/verifications/coinbase-verification-flow.png b/docs/images/verifications/coinbase-verification-flow.png
similarity index 100%
rename from docs/public/images/verifications/coinbase-verification-flow.png
rename to docs/images/verifications/coinbase-verification-flow.png
diff --git a/docs/public/images/verifications/developer-data-flow.png b/docs/images/verifications/developer-data-flow.png
similarity index 100%
rename from docs/public/images/verifications/developer-data-flow.png
rename to docs/images/verifications/developer-data-flow.png
diff --git a/docs/public/images/verifications/onchain-attestation.png b/docs/images/verifications/onchain-attestation.png
similarity index 100%
rename from docs/public/images/verifications/onchain-attestation.png
rename to docs/images/verifications/onchain-attestation.png
diff --git a/docs/public/images/verifications/rpc-endpoint.png b/docs/images/verifications/rpc-endpoint.png
similarity index 100%
rename from docs/public/images/verifications/rpc-endpoint.png
rename to docs/images/verifications/rpc-endpoint.png
diff --git a/docs/public/images/verify-with-basescan-api/basescan-apikey-page-add.png b/docs/images/verify-with-basescan-api/basescan-apikey-page-add.png
similarity index 100%
rename from docs/public/images/verify-with-basescan-api/basescan-apikey-page-add.png
rename to docs/images/verify-with-basescan-api/basescan-apikey-page-add.png
diff --git a/docs/public/images/verify-with-basescan-api/basescan-apikey-page.png b/docs/images/verify-with-basescan-api/basescan-apikey-page.png
similarity index 100%
rename from docs/public/images/verify-with-basescan-api/basescan-apikey-page.png
rename to docs/images/verify-with-basescan-api/basescan-apikey-page.png
diff --git a/docs/public/images/verify-with-basescan-api/basescan-menu.png b/docs/images/verify-with-basescan-api/basescan-menu.png
similarity index 100%
rename from docs/public/images/verify-with-basescan-api/basescan-menu.png
rename to docs/images/verify-with-basescan-api/basescan-menu.png
diff --git a/docs/public/images/verify-with-basescan-api/cbw-show-private-key.png b/docs/images/verify-with-basescan-api/cbw-show-private-key.png
similarity index 100%
rename from docs/public/images/verify-with-basescan-api/cbw-show-private-key.png
rename to docs/images/verify-with-basescan-api/cbw-show-private-key.png
diff --git a/docs/public/images/verify-with-basescan-api/cdp-node-full.png b/docs/images/verify-with-basescan-api/cdp-node-full.png
similarity index 100%
rename from docs/public/images/verify-with-basescan-api/cdp-node-full.png
rename to docs/images/verify-with-basescan-api/cdp-node-full.png
diff --git a/docs/public/images/verify-with-basescan-api/cdp-rpc-url.png b/docs/images/verify-with-basescan-api/cdp-rpc-url.png
similarity index 100%
rename from docs/public/images/verify-with-basescan-api/cdp-rpc-url.png
rename to docs/images/verify-with-basescan-api/cdp-rpc-url.png
diff --git a/docs/public/images/wallet-sdk/developer-settings-overview.gif b/docs/images/wallet-sdk/developer-settings-overview.gif
similarity index 100%
rename from docs/public/images/wallet-sdk/developer-settings-overview.gif
rename to docs/images/wallet-sdk/developer-settings-overview.gif
diff --git a/docs/public/images/wallet-sdk/dynamic-coinbase.jpg b/docs/images/wallet-sdk/dynamic-coinbase.jpg
similarity index 100%
rename from docs/public/images/wallet-sdk/dynamic-coinbase.jpg
rename to docs/images/wallet-sdk/dynamic-coinbase.jpg
diff --git a/docs/public/images/wallet-sdk/dynamic.jpg b/docs/images/wallet-sdk/dynamic.jpg
similarity index 100%
rename from docs/public/images/wallet-sdk/dynamic.jpg
rename to docs/images/wallet-sdk/dynamic.jpg
diff --git a/docs/public/images/wallet-sdk/plaid-link-demo.png b/docs/images/wallet-sdk/plaid-link-demo.png
similarity index 100%
rename from docs/public/images/wallet-sdk/plaid-link-demo.png
rename to docs/images/wallet-sdk/plaid-link-demo.png
diff --git a/docs/public/images/wallet-sdk/show-private-key (1).gif b/docs/images/wallet-sdk/show-private-key (1).gif
similarity index 100%
rename from docs/public/images/wallet-sdk/show-private-key (1).gif
rename to docs/images/wallet-sdk/show-private-key (1).gif
diff --git a/docs/public/images/wallet-sdk/show-private-key.gif b/docs/images/wallet-sdk/show-private-key.gif
similarity index 100%
rename from docs/public/images/wallet-sdk/show-private-key.gif
rename to docs/images/wallet-sdk/show-private-key.gif
diff --git a/docs/public/images/wallet-sdk/smart-wallet-create-passkey-2.png b/docs/images/wallet-sdk/smart-wallet-create-passkey-2.png
similarity index 100%
rename from docs/public/images/wallet-sdk/smart-wallet-create-passkey-2.png
rename to docs/images/wallet-sdk/smart-wallet-create-passkey-2.png
diff --git a/docs/public/images/wallet-sdk/smart-wallet-create-passkey.png b/docs/images/wallet-sdk/smart-wallet-create-passkey.png
similarity index 100%
rename from docs/public/images/wallet-sdk/smart-wallet-create-passkey.png
rename to docs/images/wallet-sdk/smart-wallet-create-passkey.png
diff --git a/docs/public/images/wallet-sdk/smart-wallet-entry.png b/docs/images/wallet-sdk/smart-wallet-entry.png
similarity index 100%
rename from docs/public/images/wallet-sdk/smart-wallet-entry.png
rename to docs/images/wallet-sdk/smart-wallet-entry.png
diff --git a/docs/public/images/wallet-sdk/testnet-assets.png b/docs/images/wallet-sdk/testnet-assets.png
similarity index 100%
rename from docs/public/images/wallet-sdk/testnet-assets.png
rename to docs/images/wallet-sdk/testnet-assets.png
diff --git a/docs/public/images/wallet-sdk/testnets.png b/docs/images/wallet-sdk/testnets.png
similarity index 100%
rename from docs/public/images/wallet-sdk/testnets.png
rename to docs/images/wallet-sdk/testnets.png
diff --git a/docs/public/images/wallet-sdk/wagmi_custom_modal.png b/docs/images/wallet-sdk/wagmi_custom_modal.png
similarity index 100%
rename from docs/public/images/wallet-sdk/wagmi_custom_modal.png
rename to docs/images/wallet-sdk/wagmi_custom_modal.png
diff --git a/docs/public/images/wallet-sdk/wallet-onboard.png b/docs/images/wallet-sdk/wallet-onboard.png
similarity index 100%
rename from docs/public/images/wallet-sdk/wallet-onboard.png
rename to docs/images/wallet-sdk/wallet-onboard.png
diff --git a/docs/public/images/wallet-sdk/web3-onboard_modal.png b/docs/images/wallet-sdk/web3-onboard_modal.png
similarity index 100%
rename from docs/public/images/wallet-sdk/web3-onboard_modal.png
rename to docs/images/wallet-sdk/web3-onboard_modal.png
diff --git a/docs/public/images/wallet-sdk/web3-react_custom_modal.png b/docs/images/wallet-sdk/web3-react_custom_modal.png
similarity index 100%
rename from docs/public/images/wallet-sdk/web3-react_custom_modal.png
rename to docs/images/wallet-sdk/web3-react_custom_modal.png
diff --git a/docs/public/images/wallet-sdk/web3modal_modal.png b/docs/images/wallet-sdk/web3modal_modal.png
similarity index 100%
rename from docs/public/images/wallet-sdk/web3modal_modal.png
rename to docs/images/wallet-sdk/web3modal_modal.png
diff --git a/docs/index.mdx b/docs/index.mdx
new file mode 100644
index 00000000..d8017d42
--- /dev/null
+++ b/docs/index.mdx
@@ -0,0 +1,117 @@
+---
+title: Base Documentation
+mode: "custom"
+---
+
+import {HomeWrapper} from "/snippets/HomeWrapper.mdx";
+import {HomeHeader} from "/snippets/HomeHeader.mdx";
+import {BrowseUseCaseCard} from "/snippets/BrowseUseCaseCard.mdx";
+import {BrowseCard} from "/snippets/BrowseCard.mdx";
+import {onboardingSvg} from "/snippets/svg/onboardingSvg.mdx";
+import {paymentsSvg} from "/snippets/svg/paymentsSvg.mdx";
+import {agentSvg} from "/snippets/svg/agentSvg.mdx";
+import {socialSvg} from "/snippets/svg/socialSvg.mdx";
+import {depositSvg} from "/snippets/svg/depositSvg.mdx";
+import {gaslessSvg} from "/snippets/svg/gaslessSvg.mdx";
+import {onchainKitSvg} from "/snippets/svg/onchainKitSvg.mdx";
+import {smartWalletSvg} from "/snippets/svg/smartWalletSvg.mdx";
+import {agentKitSvg} from "/snippets/svg/agentKitSvg.mdx";
+import {paymasterSvg} from "/snippets/svg/paymasterSvg.mdx";
+import {miniKitSvg} from "/snippets/svg/miniKitSvg.mdx";
+import {verificationsSvg} from "/snippets/svg/verificationsSvg.mdx";
+
+
+
+
+ Base is a secure, low-cost, builder-friendly Ethereum L2 built to bring the next billion users onchain.
+
+ Base is incubated within Coinbase and plans to progressively decentralize in the years ahead. We believe that decentralization is critical to creating an open, global cryptocurrency that is accessible to everyone.
+
+
+{/* Browse by Use Cases Section */}
+
+
Browse by use cases
+
+
+
+
+
+
+
+
+
+
+{/* Browse by Tools Section */}
+
+
Browse by tools
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/layout.tsx b/docs/layout.tsx
deleted file mode 100644
index 50ddae9a..00000000
--- a/docs/layout.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useEffect, type ReactNode } from 'react';
-import ThemeProvider from './contexts/Theme.tsx';
-import { AppProviders } from './contexts/AppProviders.tsx';
-import { Buffer } from 'buffer';
-
-// polyfill Buffer for cookie-banner
-globalThis.Buffer = Buffer;
-
-export default function Layout({ children }: { children: ReactNode }) {
- useEffect(() => {
- window.addEventListener('vite:preloadError', handlePreloadError);
- return () => {
- window.removeEventListener('vite:preloadError', handlePreloadError);
- };
- }, []);
-
- return (
-
- );
-}
-
-/**
- * https://vite.dev/guide/build#load-error-handling
- * This is a workaround for a known issue with Vite, where a user who visited
- * before a new deployment might encounter an import error. This error happens
- * because the assets running on that user's device are outdated and it tries to
- * import the corresponding old chunk, which is deleted.
- */
-function handlePreloadError() {
- window.location.reload();
-}
diff --git a/docs/learn/address-and-payable/address-and-payable.mdx b/docs/learn/address-and-payable/address-and-payable.mdx
new file mode 100644
index 00000000..1878e6ac
--- /dev/null
+++ b/docs/learn/address-and-payable/address-and-payable.mdx
@@ -0,0 +1,91 @@
+---
+title: Address and Payable in Solidity
+sidebarTitle: Guide
+description: A comprehensive guide to understanding and using address and payable address types in Solidity.
+hide_table_of_contents: false
+---
+
+Understanding address and payable address types is crucial for managing Ether transfers and interactions within your Solidity contracts. This article will delve into their key distinctions and practical applications.
+
+---
+
+## Objectives
+
+By the end of this lesson, you should be able to:
+
+- Differentiate between address and address payable types in Solidity
+- Determine when to use each type appropriately in contract development
+- Employ address payable to send Ether and interact with payable functions
+
+---
+
+## Ethereum Addresses
+
+In Solidity, Ethereum addresses play a crucial role in interacting with the Ethereum blockchain. An Ethereum address is a 20-byte hexadecimal string that represents the destination of transactions or the owner of a smart contract. These addresses are used to send and receive Ether and interact with smart contracts.
+
+### Addresses
+
+Regular addresses in Solidity are used for various purposes, including:
+
+- Identifying the owner of a smart contract
+- Sending Ether from one address to another
+- Checking the balance of an address
+ Here's an example of declaring a regular address variable in Solidity:
+
+
+
+```solidity
+address public owner;
+```
+
+### Payable Addresses
+
+`payable` keyword is a language-level feature provided by Solidity to enable the handling of Ether within smart contracts, and it is not a feature of the Ethereum Virtual Machine itself, but rather a part of the Solidity language's syntax. They are used when you want a contract to be able to receive Ether from external sources, such as other contracts or user accounts.
+
+Payable addresses are often used when creating crowdfunding or token sale contracts, where users send Ether to the contract's address in exchange for tokens or to fund a project.
+
+Here's an example of declaring a payable address variable in Solidity:
+
+```solidity
+address payable public projectWallet;
+```
+
+Payable [Address] are marked as payable, which means they can accept incoming Ether transactions. It's important to note that regular addresses cannot receive Ether directly.
+
+## Receiving Ether with Payable Addresses
+
+To receive Ether in a contract using a payable address, you need to define a payable function that can accept incoming transactions. This function is typically named receive or fallback. Here's an example:
+
+```solidity
+fallback() external payable {
+ // Handle the incoming Ether here
+}
+```
+
+In this example, the fallback function is marked as external and payable, which means it can receive Ether when someone sends it to the contract's address. You can then add custom logic to handle the received Ether, such as updating contract balances or triggering specific actions.
+
+## Usage
+
+```solidity
+contract PaymentReceiver {
+ address payable owner;
+
+ constructor() payable {
+ owner = payable(msg.sender); // Convert msg.sender to payable
+ }
+
+ function receiveEther() public payable {
+ // This function can receive Ether
+ }
+
+ function withdrawEther() public {
+ owner.transfer(address(this).balance); // Send Ether to owner
+ }
+}
+```
+
+## Conclusion
+
+Appropriately using address and address payable types is essential for secure and efficient Solidity contract development. By understanding their distinctions and applying them correctly, you can effectively manage Ether transfers and interactions within your contracts.
+
+[Address]: https://docs.soliditylang.org/en/latest/types.html#address
diff --git a/docs/learn/advanced-functions/function-modifiers-vid.mdx b/docs/learn/advanced-functions/function-modifiers-vid.mdx
new file mode 100644
index 00000000..21a9fa35
--- /dev/null
+++ b/docs/learn/advanced-functions/function-modifiers-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Function Modifiers
+description: Use modifiers to control how functions work.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/advanced-functions/function-modifiers.mdx b/docs/learn/advanced-functions/function-modifiers.mdx
new file mode 100644
index 00000000..c92a6894
--- /dev/null
+++ b/docs/learn/advanced-functions/function-modifiers.mdx
@@ -0,0 +1,151 @@
+---
+title: Function Modifiers
+sidebarTitle: Modifiers Guide
+description: Build custom function modifiers to efficiently modify functionality.
+hide_table_of_contents: false
+---
+
+Function modifiers allow you to efficiently change the behavior of functions. In some ways, it's similar to inheritance, but there are restrictions, particularly in variable scope.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Use modifiers to efficiently add functionality to multiple functions
+
+---
+
+## Adding a Simple OnlyOwner Modifier
+
+By default, `public` functions can be called by **anyone**, without restriction. Often this is desirable. You want any user to be able to see what NFTs are for sale on your platform, sign up for a service, or read various items stored in state.
+
+However, there will be many functions you **don't** want any user to be able to do, such as setting the fee for using the app, or withdrawing all funds in the contract! A common pattern to protect these functions is to use `modifier`s to make sure that only the owner can call these functions.
+
+
+For a production app, you'll want to use a more robust implementation of `onlyOwner`, such as the [one provided by OpenZeppelin].
+
+
+
+### Adding an Owner
+
+The address of the deployer of a contract is **not** included as an accessible property. To make it available, add it as a state variable and assign `msg.sender` in the `constructor`.
+
+
+```solidity
+contract Modifiers {
+ address owner;
+
+ constructor () {
+ owner = msg.sender;
+ }
+}
+```
+
+
+
+### Creating an `onlyOwner` Modifier
+
+[Modifiers] are very similar to functions and are declared with the `modifier` keyword. The modifier can run any Solidity code, including functions, and is allowed to modify state. Modifiers must have a special `_` character, which serves as a placeholder for where the code contained within the modified function will run.
+
+Create a simple `onlyOwner` modifier, which returns an `error` of `NotOwner` with the sending address if the sender is not the owner.
+
+
+```solidity
+error NotOwner(address _msgSender);
+```
+
+```solidity
+modifier onlyOwner {
+ if (msg.sender != owner) {
+ revert NotOwner(msg.sender);
+ }
+ _;
+}
+```
+
+
+Test your `modifier` by adding a function that uses it:
+
+
+```solidity
+function iOwnThis() public view onlyOwner returns (string memory) {
+ return "You own this!";
+}
+```
+
+
+To test, deploy your contract and call the `iOwnThis` function. You should see the message "You own this!".
+
+Next, switch the _Account_, and try the function again. You should see an error in the console:
+
+```text
+call to Modifiers.iOwnThis errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Error provided by the contract:
+NotOwner
+Parameters:
+{
+ "_msgSender": {
+ "value": "0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db"
+ }
+}
+Debug the transaction to get more information.
+```
+
+
+Always verify the output of a function call in the console. The result that appears under the button for the function is convenient, but it does **not** clear or change if a subsequent call reverts.
+
+
+
+---
+
+## Modifiers and Variables
+
+Modifiers can have parameters, which essentially work the same as in functions. These parameters can be independent values, or they can overlap with the arguments provided to a function call.
+
+### Modifiers with Parameters
+
+Modifier parameters can be the arguments provided to the functions they modify. You can perform calculations and trigger errors based on these values.
+
+```solidity
+error NotEven(uint number);
+
+modifier onlyEven(uint _number) {
+ if(_number % 2 != 0) {
+ revert NotEven(_number);
+ }
+ _;
+}
+
+function halver(uint _number) public pure onlyEven(_number) returns (uint) {
+ return _number / 2;
+}
+```
+
+### Independent Scope
+
+While `modifiers` are used to modify functions and can share inputs, they have separate scopes. The following example will **not** work:
+
+```solidity
+// Bad code example, does not work
+modifier doubler(uint _number) {
+ _number *= 2;
+ _;
+}
+
+function modifierDoubler(uint _number) public pure doubler(_number) returns (uint) {
+ return _number; // Returns the original number, NOT number * 2
+}
+```
+
+---
+
+## Conclusion
+
+Function `modifier`s are an efficient and reusable way to add checks, trigger errors, and control function execution. In this lesson, you've seen examples of how they can be used to abort execution under certain conditions. You've also learned that they have separate scopes and cannot be used to modify variables within the function they modify.
+
+[one provided by OpenZeppelin]: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
diff --git a/docs/learn/advanced-functions/function-visibility-vid.mdx b/docs/learn/advanced-functions/function-visibility-vid.mdx
new file mode 100644
index 00000000..d6beda13
--- /dev/null
+++ b/docs/learn/advanced-functions/function-visibility-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Function Visibility
+description: Learn how to control the visibility of functions.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/advanced-functions/function-visibility.mdx b/docs/learn/advanced-functions/function-visibility.mdx
new file mode 100644
index 00000000..b3426537
--- /dev/null
+++ b/docs/learn/advanced-functions/function-visibility.mdx
@@ -0,0 +1,130 @@
+---
+title: Function Visibility and State Mutability
+sidebarTitle: Visibility Overview
+description: A quick reference for all your function declaring needs.
+hide_table_of_contents: false
+---
+
+You've seen much of this before, but this document outlines and highlights the options for _function visibility_ and _state mutability_ all in one document.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Categorize functions as public, private, internal, or external based on their usage
+- Describe how pure and view functions are different than functions that modify storage
+
+---
+
+## Function Visibility
+
+There are four types of [visibility] for functions in Solidity: `external`, `public`, `internal`, and `private`. These labels represent a further division of the _public_ and _private_ labels you might use in another language.
+
+### External
+
+Functions with `external` visibility are **only** callable from other contracts and cannot be called within their own contract. You may see older references stating you should use `external` over `public` because it forces the function to use `calldata`. This is no longer correct, because both function visibilities can now use `calldata` and `memory` for parameters. However, using `calldata` for either will cost less gas.
+
+```solidity
+contract Foo {
+ constructor() {
+ // Bad code example, will not work
+ uint bar = foo(3);
+ // ... other code
+ }
+
+ function foo(uint _number) external pure returns (uint) {
+ return _number*2;
+ }
+}
+```
+
+### Public
+
+Public functions work the same as external, except they may also be called within the contract that contains them.
+
+```solidity
+contract Foo {
+ constructor() {
+ // Public functions may be called within the contract
+ uint bar = foo(3);
+ // ... other code
+ }
+
+ function foo(uint _number) public pure returns (uint) {
+ return _number*2;
+ }
+}
+```
+
+### Private and Internal
+
+Functions visible as `private` and `internal` operate nearly identically. Beyond writing hygienic code, these have a very important effect. Because they are not a part of the contract's ABI, you can use `mapping`s and `storage` variable references as parameters.
+
+The difference is that `private` functions can't be called from derived contracts. You'll learn more about that when we cover inheritance.
+
+Some developers prepend an underscore to `private` and `internal` functions.
+
+```solidity
+function _foo(uint _number) private returns (uint) {
+ return _number*2;
+}
+```
+
+
+All data on a blockchain is public. Don't mistake hiding visibility while coding for hiding information from the world!
+
+
+
+---
+
+## Function State Mutability
+
+State mutability labels are relatively unique to Solidity. They determine how a function can interact with state, which has a substantial impact on gas costs.
+
+### Pure
+
+`pure` functions promise to neither read nor write state. They're usually used for helper functions that support other functionality.
+
+```solidity
+function abs(int x) public pure returns (int) {
+ return x >= 0 ? x : -x;
+}
+```
+
+`pure` functions can be called from outside the blockchain without using gas, if they are also `public` or `external`.
+
+### View
+
+`view` functions access state, but don't modify it. You've used these for tasks such as returning all the values in an array.
+
+```solidity
+function getArr() public view returns (uint[] memory) {
+ return arr;
+}
+```
+
+`view` functions can be called from outside the blockchain without using gas, if they are also `public` or `external`.
+
+### Unlabeled Functions
+
+Functions that are not labeled `view` or `pure` can modify state and the compiler will generate a warning if they do not.
+
+```solidity
+function addToArr(uint _number) public {
+ arr.push(_number);
+}
+```
+
+They can have any visibility and will always cost gas when called.
+
+---
+
+## Conclusion
+
+The visibility and mutability keywords in Solidity help you organize your code and alert other developers to the properties of each of your functions. Use them to keep your code organized and readable.
+
+---
+
+[visibility]: https://docs.soliditylang.org/en/v0.8.17/contracts.html?highlight=pure#function-visibility
diff --git a/docs/learn/arrays/array-storage-layout-vid.mdx b/docs/learn/arrays/array-storage-layout-vid.mdx
new file mode 100644
index 00000000..26724cf1
--- /dev/null
+++ b/docs/learn/arrays/array-storage-layout-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Array Storage Layout
+description: Learn how arrays are kept in storage.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/arrays/arrays-exercise.mdx b/docs/learn/arrays/arrays-exercise.mdx
new file mode 100644
index 00000000..786b6a8e
--- /dev/null
+++ b/docs/learn/arrays/arrays-exercise.mdx
@@ -0,0 +1,93 @@
+---
+title: Arrays Exercise
+sidebarTitle: Exercise
+description: Exercise - Demonstrate your knowledge of arrays.
+hide_table_of_contents: false
+---
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Review the contract in the starter snippet called `ArraysExercise`. It contains an array called `numbers` that is initialized with the numbers 1–10. Copy and paste this into your file.
+
+```solidity
+contract ArraysExercise {
+ uint[] public numbers = [1,2,3,4,5,6,7,8,9,10];
+}
+```
+
+Add the following functions:
+
+### Return a Complete Array
+
+The compiler automatically adds a getter for individual elements in the array, but it does not automatically provide functionality to retrieve the entire array.
+
+Write a function called `getNumbers` that returns the entire `numbers` array.
+
+### Reset Numbers
+
+Write a `public` function called `resetNumbers` that resets the `numbers` array to its initial value, holding the numbers from 1-10.
+
+
+We'll award the pin for any solution that works, but one that **doesn't** use `.push()` is more gas-efficient!
+
+
+
+
+Remember, _anyone_ can call a `public` function! You'll learn how to protect functionality in another lesson.
+
+
+
+### Append to an Existing Array
+
+Write a function called `appendToNumbers` that takes a `uint[] calldata` array called `_toAppend`, and adds that array to the `storage` array called `numbers`, already present in the starter.
+
+### Timestamp Saving
+
+At the contract level, add an `address` array called `senders` and a `uint` array called `timestamps`.
+
+Write a function called `saveTimestamp` that takes a `uint` called `_unixTimestamp` as an argument. When called, it should add the address of the caller to the end of `senders` and the `_unixTimestamp` to `timestamps`.
+
+
+You'll need to research on your own to discover the correct _Special Variables and Functions_ that can help you with this challenge!
+
+
+
+### Timestamp Filtering
+
+Write a function called `afterY2K` that takes no arguments. When called, it should return two arrays.
+
+The first should return all timestamps that are more recent than January 1, 2000, 12:00am. To save you a click, the Unix timestamp for this date and time is `946702800`.
+
+The second should return a list of `senders` addresses corresponding to those timestamps.
+
+### Resets
+
+Add `public` functions called `resetSenders` and `resetTimestamps` that reset those storage variables.
+
+---
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+
+{/* */}
+
+
\ No newline at end of file
diff --git a/docs/learn/arrays/arrays-in-solidity-vid.mdx b/docs/learn/arrays/arrays-in-solidity-vid.mdx
new file mode 100644
index 00000000..d16a816b
--- /dev/null
+++ b/docs/learn/arrays/arrays-in-solidity-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Arrays
+description: Learn about the unique properties of arrays in Solidity.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/arrays/arrays-in-solidity.mdx b/docs/learn/arrays/arrays-in-solidity.mdx
new file mode 100644
index 00000000..e342b776
--- /dev/null
+++ b/docs/learn/arrays/arrays-in-solidity.mdx
@@ -0,0 +1,142 @@
+---
+title: Arrays
+sidebarTitle: Arrays Guide
+description: An overview of how arrays work in Solidity.
+hide_table_of_contents: false
+---
+
+Solidity arrays are collections of the same type, accessed via an index, the same as any other language. Unlike other languages, there are three types of arrays - _storage_, _memory_, and _calldata_. Each has their own properties and constraints.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Describe the difference between storage, memory, and calldata arrays
+
+---
+
+## Storage, Memory, and Calldata
+
+The `storage`, `memory`, or `calldata` keywords are required when declaring a new [reference type] variable. This keyword determines the [data location] where the variable is stored and how long it will persist.
+
+### Storage
+
+The `storage` keyword is used to assign state variables that become a part of the blockchain as a part of the _storage_ for your contract. These remain as assigned until modified, for the lifetime of the contract.
+
+Storage is **very expensive** compared to most other environments. It costs a minimum of 20000 gas to [store a value] in a new storage slot, though it's cheaper to update that value after the initial assignment (~5000+ gas).
+
+This cost isn't a reason to be afraid of using storage. In the long run, writing clear, maintainable, and logical code will always cost less than jumping through hoops to save gas here and there. Just be as thoughtful with storage on the EVM as you would be with computation in most other environments.
+
+### Memory
+
+The `memory` keyword creates temporary variables that only exist within the scope in which they are created. Memory is less expensive than storage, although this is relative. There are often circumstances where it is cheaper to work directly in storage rather than convert to memory and back. Copying from one location to another can be quite expensive!
+
+### Calldata
+
+The `calldata` storage location is where function arguments are stored. It is non-modifiable and the Solidity docs recommend using it where possible to avoid unnecessary copying, because it can't be modified. You'll learn more about this later, but doing so can help prevent confusing bugs when calling a function from another contract that takes in values from that contract's `storage`.
+
+---
+
+## Array Data Locations
+
+Arrays behave differently based on their data location. Assignment behavior also depends on data location. To [quote the docs]:
+
+> - Assignments between `storage` and `memory` (or from `calldata`) always create an independent copy.
+>
+> - Assignments from `memory` to `memory` only create references. This means that changes to one memory variable are also visible in all other memory variables that refer to the same data.
+>
+> - Assignments from `storage` to a **local** storage variable also only assign a reference.
+>
+> - All other assignments to `storage` always copy. Examples for this case are assignments to state variables or to members of local variables of storage struct type, even if the local variable itself is just a reference.
+
+### Storage Arrays
+
+_Arrays_ in `storage` are passed by _reference_. In other words, if you assign a _storage array_ half a dozen names, any changes you make will always modify the original, underlying storage array.
+
+```solidity
+contract StorageArray {
+ // Variables declared at the class level are always `storage`
+ uint[] arr = [1, 2, 3, 4, 5];
+
+ function function_1() public {
+ uint[] storage arr2 = arr;
+
+ arr2[0] = 99; // <- arr is now [99, 2, 3, 4, 5];
+ }
+}
+```
+
+You cannot use a `storage` array as a function parameter, and you cannot write a function that `return`s a `storage` array.
+
+Storage arrays are dynamic, unless they are declared with an explicit size. However, their functionality is limited compared to other languages. The `.push(value)` function works as expected. The `.pop()` function removes the last value of an array, but it **does not** return that value. You also **may not** use `.pop()` with an index to remove an element from the middle of an array, or to remove more than one element.
+
+You can use the `delete` keyword with an array. Doing so on an entire array will reset the array to zero length. Calling it on an element within the array will reset that value to its default. It **will not** resize the array!
+
+```solidity
+uint[] arr_2 = [1, 2, 3, 4, 5];
+function function_2(uint _num) public returns(uint[] memory) {
+ arr_2.push(_num); // <- arr_2 is [1, 2, 3, 4, 5, <_num>]
+
+ delete arr_2[2]; // <- arr_2 is [1, 2, 0, 4, 5, <_num>]
+
+ arr_2.pop(); // <- arr_2 is [1, 2, 0, 4, 5] (_num IS NOT returned by .pop())
+
+ delete arr_2; // <- arr_2 is []
+
+ return arr_2; // <- returns []
+}
+```
+
+Storage arrays are implicitly convertible to `memory` arrays.
+
+### Memory Arrays
+
+Arrays declared as `memory` are temporary and only exist within the scope in which they are created. Arrays in `memory` are **not** dynamic and must be declared with a fixed size. This can be done at compile time, by declaring a size inside the `[]` or during runtime by using the `new` keyword. Finally, `memory` arrays can be implicitly cast from `storage` arrays.
+
+```solidity
+function function_3(uint _arrSize) public {
+ uint[5] memory arrSizeFive;
+ uint[] memory arrWithCustomSize = new uint[](_arrSize);
+ uint[] memory localCopyOfArr = arr;
+ // ...do something
+}
+```
+
+The declaration pattern impacts gas cost, though keep in mind that the first two examples are empty, and would cost additional gas depending on how they are eventually filled.
+
+```solidity
+function declareMemoryArrays() public view {
+ uint[5] memory simpleArr; // this line costs 135 gas
+ uint[] memory emptyArr = new uint[](5); // This line costs 194 gas
+ uint[] memory arrCopy = arr; // This line costs 13166 gas
+}
+```
+
+The lack of dynamic `memory` arrays can require some gymnastics if you need to create an array where the size is not initially known. Depending on the specific needs of the problem, valid solutions for filtering an array and returning a smaller array could include:
+
+- Looping through a larger array twice, first to count the number, then to copy the appropriate elements
+- Tracking the number of elements that meet condition X with a storage variable, then instantiating the array with `[] memory filteredArray = new [](numX);`
+- Using multiple data structures to track references to different subsets
+
+### Calldata Arrays
+
+Arrays in `calldata` are read only. Otherwise, they function the same as any other array.
+
+Array [slices] are currently only implemented for `calldata` arrays.
+
+---
+
+## Conclusion
+
+In this lesson, you've learned the differences between the `memory`, `storage`, and `calldata` data locations. You've also learned how they apply to arrays, with each having its own properties, restrictions, and costs.
+
+---
+
+
+[data location]: https://docs.soliditylang.org/en/v0.8.17/types.html?highlight=calldata#data-location
+[reference type]: https://docs.soliditylang.org/en/v0.8.17/types.html?highlight=array#reference-types
+[store a value]: https://github.com/wolflo/evm-opcodes/blob/main/gas.md#a7-sstore
+[quote the docs]: https://docs.soliditylang.org/en/v0.8.17/types.html?#data-location-and-assignment-behaviour
+[slices]: https://docs.soliditylang.org/en/v0.8.17/types.html?#array-slices
diff --git a/docs/learn/arrays/filtering-an-array-sbs.mdx b/docs/learn/arrays/filtering-an-array-sbs.mdx
new file mode 100644
index 00000000..608f2478
--- /dev/null
+++ b/docs/learn/arrays/filtering-an-array-sbs.mdx
@@ -0,0 +1,230 @@
+---
+title: Filtering an Array
+sidebarTitle: Filtering Arrays
+description: Explore techniques to filter an array.
+hide_table_of_contents: false
+---
+
+In this exercise, you'll explore two different solutions for filtering an array in Solidity. By doing so, you'll gain a better understanding of the constraints present while working with arrays, and have the chance to learn and compare the gas costs of different approaches.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Write a function that can return a filtered subset of an array
+
+---
+
+## First Pass Solution
+
+### Setup
+
+Create a new workspace in Remix and add a file called `ArrayDemo.sol` containing a `contract` called `ArrayDemo`. Initialize an array containing the numbers from 1 to 10. Add a stub for a function called `getEvenNumbers` that returns a `uint[] memory`.
+
+```solidity
+contract ArrayDemo {
+ uint[] public numbers = [1,2,3,4,5,6,7,8,9,10];
+
+ function getEvenNumbers() external view returns(uint[] memory) {
+ // TODO
+ }
+}
+```
+
+
+You don't have to declare the size of the memory array to be returned. You usually don't want to either, unless the results will always be the same, known size.
+
+
+
+### Finding the Number of Even Numbers
+
+We need to initialize a `memory` array to hold the results, but to do so, we need to know how big to make the array. Don't be tempted to count the number of evens in `numbers`, as what happens if we modify it later?
+
+The simple and obvious solution is to simply iterate through `numbers` and count how many even numbers are present. You could add that functionality in `getEvenNumbers()`, but it might be useful elsewhere, so a better practice would be to separate these concerns into another function.
+
+Go ahead and write it on your own. It needs to:
+
+- Instantiate a `uint` to hold the results
+- Iterate through all values in `numbers` and increment that number if the value is even
+- Return the result
+
+You should end up with something like:
+
+
+
+```solidity
+function _countEvenNumbers() internal view returns(uint) {
+ uint result = 0;
+
+ for(uint i = 0; i < numbers.length; i++) {
+ if(numbers[i] % 2 == 0) {
+ result++;
+ }
+ }
+
+ return result;
+}
+```
+
+
+The `_` in front of the function name is a practice used by some developers, in Solidity and in other languages, to indicate visually that this function is intended for internal use only.
+
+### Returning Only Even Numbers
+
+Now that we have a method to find out how big the return array needs to be in `getEvenNumbers()`, we can simply loop through `numbers`, and add the even numbers to the array to be returned.
+
+Finish the function on your own. It needs to:
+
+- Determine the number of results and instantiate an array that size
+- Loop through the `numbers` array and if a given number is even, add it to the next unused index in the results array
+
+You should end up with something like:
+
+
+
+```solidity
+
+function getEvenNumbers() external view returns(uint[] memory) {
+ uint resultsLength = _countEvenNumbers();
+ uint[] memory results = new uint[](resultsLength);
+ uint cursor = 0;
+
+ for(uint i = 0; i < numbers.length; i++) {
+ if(numbers[i] % 2 == 0) {
+ results[cursor] = numbers[i];
+ cursor++;
+ }
+ }
+
+ return results;
+}
+
+```
+
+
+
+Did you catch the compiler warning about `view`? You aren't modifying state, so you should mark it as such.
+
+### Testing the Function
+
+Deploy your contract and test the function. You should get a return of `[2,4,6,8,10]`. The total gas cost will be about 63,947, depending on if you used the same helper variables, etc.
+
+---
+
+## Optimizing the Function
+
+It does seem inefficient to loop through the same array twice. What if we instead kept track of how many even numbers to expect. That way, we would only need to loop once, thus saving gas! Right?
+
+Only one way to find out.
+
+### Tracking Relevant Data
+
+Add a contract-level variable called `numEven`, and initialize it with **5**, the number of even numbers in the array. Modify `getEvenNumbers()` to use `numEven` instead of the `_countEvenNumbers()` function. It should now look like:
+
+
+
+```solidity
+function getEvenNumbers() external view returns(uint[] memory) {
+ uint resultsLength = numEven; // <- Changed here
+ uint[] memory results = new uint[](resultsLength);
+ uint cursor = 0;
+
+ for(uint i = 0; i < numbers.length; i++) {
+ if(numbers[i] % 2 == 0) {
+ results[cursor] = numbers[i];
+ cursor++;
+ }
+ }
+
+ return results;
+}
+```
+
+
+
+Redeploy and test again. Success, the function now only costs about 57,484 gas to run! Except there is a catch. Remember, it's going to cost about 5000 gas to update `numEven` **each time** the array adds an even number.
+
+### A More Realistic Accounting
+
+As we considered above, in a real-world example, we wouldn't declare the array up front, it would be modified over time. A slightly more realistic example would be to fill the array with a function.
+
+Change the declaration for `numbers` and `numEven` so that they have their respective default values to begin with.
+
+```solidity
+uint[] public numbers;
+uint numEven;
+```
+
+Add a new function called `debugLoadArray` that takes a `uint` called `_number` as an argument, and fills the array by looping through `_number` times, pushing each number into the array. **For now, _don't_ update `numEven`**.
+
+
+
+```solidity
+function debugLoadArray(uint _number) external {
+ for(uint i = 0; i < _number; i++) {
+ numbers.push(i);
+ }
+}
+```
+
+
+
+Test out the function by loading in **10** numbers. It costs about 249,610 gas to load the array. Now, add functionality to **also** increment `numEven` when the number added is even. We can't just calculate it, because although the numbers are sequential in the debug function, they might not be in real world use.
+
+
+
+```solidity
+function debugLoadArray(uint _number) external {
+ for(uint i = 0; i < _number; i++) {
+ numbers.push(i);
+ if(i % 2 == 0) {
+ numEven++;
+ }
+ }
+}
+```
+
+
+
+**Be sure to redeploy** and try again with **10** numbers. This time, the cost was about 275,335 gas. That's almost 26,000 more gas in an effort to save the 5,000 gas needed to run `_countEvenNumbers()`.
+
+### Looking at the Big Picture
+
+What about more? What if there are a thousand numbers in the array? What about a million?
+
+Let's start with 500, any more will break the Remix EVM simulation, and/or would trigger an out of gas error because we're approaching the gas limit for the entire block.
+
+**Comment out** the `if` statement in `debugLoadArray` that checks for even numbers and load 500 numbers. The Remix EVM should be able to handle this, but it might hang up for a moment, or even crash. (You can also do this experiment with 250 numbers instead.)
+
+```solidity
+function debugLoadArray(uint _number) external {
+ for(uint i = 0; i < _number; i++) {
+ numbers.push(i);
+ // if(i % 2 == 0) {
+ // numEven++;
+ //}
+ }
+}
+```
+
+You'll get a result of about 11,323,132 gas to load the array. That's a lot! The target total gas for a single block is 15 million, and the limit is 30 million.
+
+Try again with the code to increment `numEven`. You should get about 11,536,282, or an increase of about 213,150 gas.
+
+Now, test out `getEvenNumbers()` using `numEven` vs. using `_countEvenNumbers()`. With `numEven`, it should cost about 1,578,741 gas to find the even numbers. Using `_countEvenNumbers()`, that cost increases to 1,995,579 gas, an increase of 416,838 gas.
+
+### Which is Better?
+
+As is often the case with code, it depends. You might think that the experiment makes things obvious. Paying 213k gas up front to track `_numEven` results in a savings of over 400k gas when filtering for even numbers. Even better, you might realize that the upfront cost difference will be spread across all of your users over time, making them almost trivial. You also might think that it's possible that the filter function could be called dozens of times for each time 500 numbers are loaded.
+
+These are all valid considerations that you should evaluate as you are developing your code solution to a business problem. One last critical element to consider is that there is only a gas cost to read from the blockchain if it's another contract calling the function. It **doesn't** cost any gas to call `view` or `pure` functions from a front end or app.
+
+If `getEvenNumbers` will never be called by another contract, then using `numEven` might cost more for no benefit!
+
+---
+
+## Conclusion
+
+In this lesson, you've explored a few different approaches to a problem. You've learned how to filter an array, but more importantly, you've learned some of the specific considerations in blockchain development. Finally, you've seen that pushing 500 integers to an array, usually a trivial operation, is very large and very expensive on the EVM.
diff --git a/docs/learn/arrays/fixed-size-arrays-vid.mdx b/docs/learn/arrays/fixed-size-arrays-vid.mdx
new file mode 100644
index 00000000..4d3189d7
--- /dev/null
+++ b/docs/learn/arrays/fixed-size-arrays-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Fixed-Size Arrays
+sidebarTitle: Fixed Size Arrays
+description: Learn about fixed-size arrays.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/arrays/writing-arrays-in-solidity-vid.mdx b/docs/learn/arrays/writing-arrays-in-solidity-vid.mdx
new file mode 100644
index 00000000..a4128612
--- /dev/null
+++ b/docs/learn/arrays/writing-arrays-in-solidity-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Writing Arrays in Solidity
+sidebarTitle: Writing Arrays
+description: Learn how to write arrays in Solidity.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/client-side-development.mdx b/docs/learn/client-side-development.mdx
new file mode 100644
index 00000000..e006f903
--- /dev/null
+++ b/docs/learn/client-side-development.mdx
@@ -0,0 +1,3 @@
+---
+title: Client Side Development
+---
\ No newline at end of file
diff --git a/docs/learn/contracts-and-basic-functions/basic-functions-exercise.mdx b/docs/learn/contracts-and-basic-functions/basic-functions-exercise.mdx
new file mode 100644
index 00000000..2aa5e4be
--- /dev/null
+++ b/docs/learn/contracts-and-basic-functions/basic-functions-exercise.mdx
@@ -0,0 +1,38 @@
+---
+title: 'Basic Functions Exercise'
+description: Exercise - Create and deploy a contract with simple math functions.
+hide_table_of_contents: false
+---
+
+Each module in this course will contain exercises in which you are given a specification for a contract **without** being given specific instructions on how to build the contract. You must use what you've learned to figure out the best solution on your own!
+
+
+Once you've learned how to deploy your contracts to a test network, you'll be given the opportunity to submit your contract address for review by an onchain unit test. If it passes, you'll receive an NFT pin recognizing your accomplishment.
+
+**You'll deploy and submit this contract in the next module.**
+
+
+
+The following exercise asks you to create a contract that adheres to the following stated specifications.
+
+## Contract
+
+Create a contract called `BasicMath`. It should not inherit from any other contracts and does not need a constructor. It should have the following two functions:
+
+### Adder
+
+A function called `adder`. It must:
+
+- Accept two `uint` arguments, called `_a` and `_b`
+- Return a `uint` `sum` and a `bool` `error`
+- If `_a` + `_b` does not overflow, it should return the `sum` and an `error` of `false`
+- If `_a` + `_b` overflows, it should return `0` as the `sum`, and an `error` of `true`
+
+### Subtractor
+
+A function called `subtractor`. It must:
+
+- Accept two `uint` arguments, called `_a` and `_b`
+- Return a `uint` `difference` and a `bool` `error`
+- If `_a` - `_b` does not underflow, it should return the `difference` and an `error` of `false`
+- If `_a` - `_b` underflows, it should return `0` as the `difference`, and an `error` of `true`
diff --git a/docs/learn/contracts-and-basic-functions/basic-types.mdx b/docs/learn/contracts-and-basic-functions/basic-types.mdx
new file mode 100644
index 00000000..9c395733
--- /dev/null
+++ b/docs/learn/contracts-and-basic-functions/basic-types.mdx
@@ -0,0 +1,221 @@
+---
+title: Basic Types
+description: Introduction to basic types in Solidity.
+hide_table_of_contents: false
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+Solidity contains most of the basic [types] you are used to from other languages, but their properties and usage are often a little different than other languages and are likely much more restrictive. In particular, Solidity is a very **explicit** language and will not allow you to make inferences most of the time.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Categorize basic data types
+- List the major differences between data types in Solidity as compared to other languages
+- Compare and contrast signed and unsigned integers
+
+---
+
+## Common Properties
+
+In Solidity, [types] must always have a value and are never `undefined`, `null`, or `none`. Because of this, each type has a default value. If you declare a variable without assigning a value, it will instead have the default value for that type. This property can lead to some tricky bugs until you get used to it.
+
+```solidity
+uint defaultValue;
+uint explicitValue = 0;
+
+// (defaultValue == explicitValue) <-- true
+```
+
+Types can be cast from one type to another, but not as freely as you may expect. For example, to convert a `uint256` into a `int8`, you need to cast twice:
+
+```solidity
+uint256 first = 1;
+int8 second = int8(int256(first));
+```
+
+
+Overflow/underflow protection (described below), does not provide protection when casting.
+
+```solidity
+uint256 first = 256;
+int8 second = int8(int256(first)); // <- The value stored in second is 0
+```
+
+
+
+## Boolean
+
+[Booleans] can have a value of `true` or `false`. Solidity does not have the concept of _truthy_ or _falsey_, and non-boolean values cannot be cast to bools by design. The short conversation in this [issue] explains why, and explains the philosophy why.
+
+### Logical Operators
+
+Standard logical operators (`!`, `&&`, `||`, `==`, `!=`) apply to booleans. Short-circuiting rules do apply, which can sometimes be used for gas savings since if the first operator in an `&&` is `false` or `||` is `true`, the second will not be evaluated. For example, the following code will execute without an error, despite the divide by zero in the second statement.
+
+```solidity
+// Bad code for example. Do not use.
+uint divisor = 0;
+if(1 < 2 || 1 / divisor > 0) {
+ // Do something...
+}
+```
+
+You cannot use any variant of `>` or `<` with booleans, because they cannot be implicitly or explicitly cast to a type that uses those operators.
+
+---
+
+## Numbers
+
+Solidity has a number of types for signed and unsigned [integers], which are not ignored as much as they are in other languages, due to potential gas-savings when storing smaller numbers. Support for [fixed point numbers] is under development, but is not fully implemented as of version `0.8.17`.
+
+Floating point numbers are not supported and are not likely to be. Floating precision includes an inherent element of ambiguity that doesn't work for explicit environments like blockchains.
+
+### Min, Max, and Overflow
+
+Minimum and maximum values for each type can be accessed with `type().min` and `type().max`. For example, `type(uint).min` is **0**, and `type(uint).max` is equal to **2^256-1**.
+
+An overflow or underflow will cause a transaction to _revert_, unless it occurs in a code block that is marked as [unchecked].
+
+### `uint` vs. `int`
+
+In Solidity, it is common practice to favor `uint` over `int` when it is known that a value will never (or should never) be below zero. This practice helps you write more secure code by requiring you to declare whether or not a given value should be allowed to be negative. Use `uint` for values that should not, such as array indexes, account balances, etc. and `int` for a value that does **need** to be negative.
+
+### Integer Variants
+
+Smaller and larger variants of integers exist in many languages but have fallen out of favor in many instances, in part because memory and storage are relatively cheap. Solidity supports sizes in steps of eight from `uint8` to `uint256`, and the same for `int`.
+
+Smaller sized integers are used to optimize gas usage in storage operations, but there is a cost. The EVM operates with 256 bit words, so operations involving smaller data types must be cast first, which costs gas.
+
+`uint` is an alias for `uint256` and can be considered the default.
+
+### Operators
+
+Comparisons (`<=`, `<`, `==`, `!=`, `>=`, `>`) and arithmetic (`+`, `-`, `*`, `/`, `%`, `**`) operators are present and work as expected. You can also use bit and shift operators.
+
+`uint` and `int` variants can be compared directly, such as `uint8` and `uint256`, but you must cast one value to compare a `uint` to an `int`.
+
+```solidity
+uint first = 1;
+int8 second = 1;
+
+if(first == uint8(second)) {
+ // Do something...
+}
+```
+
+---
+
+## Addresses
+
+The [address] type is a relatively unique type representing a wallet or contract address. It holds a 20-byte value, similar to the one we explored when you deployed your _Hello World_ contract in _Remix_. `address payable` is a variant of `address` that allows you to use the `transfer` and `send` methods. This distinction helps prevent sending Ether, or other tokens, to a contract that is not designed to receive it. If that were to happen, the Ether would be lost.
+
+Addresses are **not** strings and do not need quotes when represented literally, but conversions from `bytes20` and `uint160` are allowed.
+
+```solidity
+address existingWallet = 0xd9145CCE52D386f254917e481eB44e9943F39138;
+```
+
+### Members of Addresses
+
+Addresses contain a number of functions. `balance` returns the balance of an address, and `transfer`, mentioned above, can be used to send `ether`.
+
+```solidity
+function getBalance(address _address) public view returns(uint) {
+ return _address.balance;
+}
+```
+
+Later on, you'll learn about `call`, `delegatecall`, and `staticcall`, which can be used to call functions deployed in other contracts.
+
+---
+
+## Contracts
+
+When you declare a [contract], you are defining a type. This type can be used to instantiate one contract as a local variable inside a second contract, allowing the second to interact with the first.
+
+---
+
+## Byte Arrays and Strings
+
+[Byte arrays] come as both fixed-size and dynamically-sized. They hold a sequence of bytes. Arrays are a little more complicated than in other languages and will be covered in-depth later.
+
+### Strings
+
+Strings are arrays in Solidity, not a type. You cannot concat them with `+`, but as of _0.8.12_, you can use `string.concat(first, second)`. They are limited to printable characters and escaped characters. Casting other data types to `string` is at best tricky, and sometimes impossible.
+
+Generally speaking, you should be deliberate when working with strings inside of a smart contract. Don't be afraid to use them when appropriate, but if possible, craft and display messages on the front end rather than spending gas to assemble them on the back end.
+
+---
+
+## Enums
+
+[Enums] allow you to apply human-readable labels to a list of unsigned integers.
+
+```solidity
+enum Flavors { Vanilla, Chocolate, Strawberry, Coffee }
+
+Flavors chosenFlavor = Flavors.Coffee;
+```
+
+Enums can be explicitly cast to and from `uint`, but not implicitly. They are limited to 256 members.
+
+---
+
+## Constant and Immutable
+
+The [constant and immutable] keywords allow you to declare variables that cannot be changed. Both result in gas savings because the compiler does not need to reserve a storage slot for these values.
+
+As of _0.8.17_, `constant` and `immutable` are not fully implemented. Both are supported on [value types], and `constant` can also be used with strings.
+
+### Constant
+
+Constants can be declared at the file level, or at the contract level. In Solidity, modifiers come after the type declaration. You must initialize a value when declaring a constant. Convention is to use SCREAMING_SNAKE_CASE for constants.
+
+```solidity
+uint constant NUMBER_OF_TEAMS = 10;
+
+contract Cars {
+ uint constant NUMBER_OF_CARS = 20;
+}
+```
+
+At compilation, the compiler replaces every instance of the constant variable with its literal value.
+
+### Immutable
+
+The immutable keyword is used to declare variables that are set once within the constructor, which are then never changed:
+
+```solidity
+contract Season {
+ immutable numberOfRaces;
+
+ constructor(uint _numberOfRaces) {
+ numberOfRaces = _numberOfRaces;
+ }
+}
+```
+
+---
+
+## Conclusion
+
+You've learned the usage and some of the unique quirks of common variable types in Solidity. You've seen how overflow and underflow are handled and how that behavior can be overridden. You've learned why unsigned integers are used more commonly than in other languages, why floats are not present, and have been introduced to some of the quirks of working with strings. Finally, you've been introduced to the address and contract data types.
+
+---
+
+[types]: https://docs.soliditylang.org/en/v0.8.17/types.html
+[Booleans]: https://docs.soliditylang.org/en/v0.8.17/types.html#booleans
+[issue]: https://github.com/ethereum/solidity/issues/1200
+[integers]: https://docs.soliditylang.org/en/v0.8.17/types.html#integers
+[fixed point numbers]: https://docs.soliditylang.org/en/v0.8.17/types.html#fixed-point-numbers
+[unchecked]: https://docs.soliditylang.org/en/v0.8.17/control-structures.html#unchecked
+[address]: https://docs.soliditylang.org/en/v0.8.17/types.html#address
+[contract]: https://docs.soliditylang.org/en/v0.8.17/types.html#contract-types
+[Byte arrays]: https://docs.soliditylang.org/en/v0.8.17/types.html#fixed-size-byte-arrays
+[Enums]: https://docs.soliditylang.org/en/v0.8.17/types.html#enums
+[constant and immutable]: https://docs.soliditylang.org/en/v0.8.17/contracts.html?constant-and-immutable-state-variables#constant-and-immutable-state-variables
+[value types]: https://docs.soliditylang.org/en/v0.8.17/types.html#value-types
diff --git a/docs/learn/contracts-and-basic-functions/hello-world-step-by-step.mdx b/docs/learn/contracts-and-basic-functions/hello-world-step-by-step.mdx
new file mode 100644
index 00000000..69242090
--- /dev/null
+++ b/docs/learn/contracts-and-basic-functions/hello-world-step-by-step.mdx
@@ -0,0 +1,205 @@
+---
+sidebarTitle: Hello World Guide
+title: Hello World
+description: Write your first contract with Solidity.
+hide_table_of_contents: false
+---
+
+As is tradition, we'll begin coding with a variant of "Hello World" written as a smart contract. There isn't really a console to write to\*, so instead, we'll write a contract that says hello to the sender, using the name they provide.
+
+\*You will be able to use `console.log` with _Hardhat_, with some restrictions.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Construct a simple "Hello World" contract
+- List the major differences between data types in Solidity as compared to other languages
+- Select the appropriate visibility for a function
+
+---
+
+## Hello World
+
+Writing "Hello World" in a smart contract requires a little more consideration than in other languages. Your code is deployed remotely, but it isn't running on a server where you can access logs, or on your local machine where you have access to a console. One way to do it is to write a function that returns "Hello World".
+
+### Creating the Contract
+
+To create a contract:
+
+1. Create a new workspace in Remix.
+2. Name it `Hello World` and delete the `.deps` folder.
+3. Leave `.prettierrc.json` and click the settings gear in the bottom left.
+4. Uncheck the top option to _Generate contract metadata..._
+5. Open the _Solidity Compiler_ plugin and enable _Auto compile_.
+6. Create a new folder called `contracts`, and within that folder, create a file called `hello-world.sol`.
+
+Solidity files usually start with a comment containing an [_SPDX-License-Identifier_]. It's not a requirement, but there are a couple of advantages to doing this. First, everything you deploy on the blockchain is public. This doesn't mean you are giving away everything you deploy for free, nor does it mean you have the right to use the code from any deployed contract. The license determines allowed usage and is generally protected by international copyright laws, the same as any other code.
+
+If you don't want to give a license, you can put `UNLICENSED`. Common open source licenses, such as `MIT` and `GPL-3.0` are popular as well. Add your license identifier:
+
+```Solidity
+// SPDX-License-Identifier: MIT
+```
+
+Below the license identifier, you need to specify which [version] of Solidity the compiler should use to compile your code. If by the time you read this, the version has advanced, you should try to use the most current version. Doing so may cause you to run into unexpected errors, but it's great practice for working in real-world conditions!
+
+```Solidity
+pragma solidity 0.8.17;
+```
+
+Finally, add a `contract` called `HelloWorld`. You should end up with:
+
+```Solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity 0.8.17;
+
+contract HelloWorld {
+
+}
+```
+
+### SayHello Function
+
+Add a function to your contract called `SayHello`:
+
+```Solidity
+function SayHello() {
+
+}
+```
+
+You'll get a compiler syntax error for _No visibility specified. Did you intend to add "public"?_.
+
+Is `public` the most appropriate [visibility specifier]?
+
+It would work, but you won't be calling this function from within the contract, so `external` is more appropriate.
+
+You also need to specify a return type, and we've decided this function should return a string. You'll learn more about this later, but in Solidity, many of the more complex types require you to specify if they are `storage` or `memory`. You can then have your function return a string of `"Hello World!"`.
+
+Don't forget your semicolon. They're mandatory in Solidity!
+
+You should have:
+
+```Solidity
+function SayHello() external returns (string memory) {
+ return "Hello World!";
+}
+```
+
+Before you deploy, check the `Compiler` plugin. You've got one last warning:
+
+> Warning: Function state mutability can be restricted to pure
+
+[Modifiers] are used to modify the behavior of a function. The `pure` modifier prevents the function from modifying, or even accessing state. While not mandatory, using these modifiers can help you and other programmers know the intention and impact of the functions you write. Your final function should be similar to:
+
+```Solidity
+function SayHello() external pure returns (string memory) {
+ return "Hello World!";
+}
+```
+
+### Deployment and Testing
+
+Confirm that there is a green checkmark on the icon for the compiler plugin, and then switch to the _Deploy & Run Transactions_ plugin.
+
+Click the _Deploy_ button and your contract should appear in _Deployed Contracts_. Open it up and then click the _SayHello_ button. Did it work?
+
+You should see your message below the button. Another option to see the return for your `HelloWorld` function is to expand the entry in the console. You should see a _decoded output_ of:
+
+```text
+{
+ "0": "string: Hello World!"
+}
+```
+
+---
+
+## Greeter
+
+Now, let's modify your say hello function to greet a user by name, instead of just saying "Hello World!"
+
+### First Pass Attempt
+
+You'd probably expect this to be pretty easy. Start by changing the name of the function (or adding a new one) to `Greeter` and giving it a parameter for a `string memory _name`. The underscore is a common convention to mark functions and variables as internal to their scope. Doing so helps you tell the difference between a storage variable, and a memory variable that only exists within the call.
+
+Finally, try creating a return string similar to how you might in another language with `"Hello " + _name`. You should have:
+
+```Solidity
+// Bad code example: Does not work
+function Greeter(string memory _name) external pure returns (string memory) {
+ return "Hello " + _name + "!";
+}
+```
+
+Unfortunately, this does not work in Solidity. The error message you receive is a little confusing:
+
+> TypeError: Operator + not compatible with types literal_string "Hello" and string memory.
+
+You might think that there is some sort of type casting or conversion error that could be solved by explicitly casting the string literal to string memory, or vice versa. This is a great instinct. Solidity is a very explicit language.
+
+However, you receive a similar error with `"Hello " + "world"`.
+
+String concatenation is possible in Solidity, but it's a bit more complicated than most languages, for good reason. Working with string costs a large amount of gas, so it's usually better to handle this sort of processing on the front end.
+
+### Plan B
+
+We still want to return something with the name provided by the user, so let's try something a little different. Solidity is a _variadic_ language, which means it allows functions to return more than one value.
+
+Modify your return declaration: `returns (string memory, string memory)`
+
+Now, your function can return a [tuple] containing two strings!
+
+`return ("Hello", _name)`;
+
+Deploy and test your contract. You should get a _decoded output_ with:
+
+```text
+{
+ "string _name": "Your Name"
+}
+```
+
+### Full Example Code
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity 0.8.17;
+
+contract HelloWorld {
+
+ function SayHello() external pure returns (string memory) {
+ return "Hello World!";
+ }
+
+ // Bad code example: Does not work
+ // function Greeter(string memory _name) external pure returns (string memory) {
+ // return "Hello " + _name;
+ // }
+
+ function Greeter(string memory _name) external pure returns (string memory, string memory) {
+ return ("Hello", _name);
+ }
+}
+```
+
+---
+
+## Conclusion
+
+Congratulations! You've written and tested your first smart contract! You selected a license and a version of Solidity. You declared a contract and added a function that returns a value.
+
+You also learned more about some of the ways in which Solidity is more challenging to work with than other languages, and the additional elements you sometimes need to declare functions and types.
+
+---
+
+
+[_SPDX-License-Identifier_]: https://spdx.org/licenses/
+[version]: https://docs.soliditylang.org/en/v0.8.17/layout-of-source-files.html?#version-pragma
+[visibility specifier]: https://docs.soliditylang.org/en/v0.8.17/cheatsheet.html?#function-visibility-specifiers
+[modifiers]: https://docs.soliditylang.org/en/v0.8.17/cheatsheet.html?#modifiers
+[tuple]: https://en.wikipedia.org/wiki/Tuple
diff --git a/docs/learn/contracts-and-basic-functions/intro-to-contracts-vid.mdx b/docs/learn/contracts-and-basic-functions/intro-to-contracts-vid.mdx
new file mode 100644
index 00000000..beb52d48
--- /dev/null
+++ b/docs/learn/contracts-and-basic-functions/intro-to-contracts-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Introduction to Contracts
+description: Learn about the core structure of EVM programs.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/control-structures/control-structures-exercise.mdx b/docs/learn/control-structures/control-structures-exercise.mdx
new file mode 100644
index 00000000..999cd226
--- /dev/null
+++ b/docs/learn/control-structures/control-structures-exercise.mdx
@@ -0,0 +1,57 @@
+---
+title: Control Structures Exercise
+sidebarTitle: Exercise
+description: Exercise - Demonstrate your knowledge of control structures.
+hide_table_of_contents: false
+---
+
+Create a contract that adheres to the following specifications:
+
+---
+
+## Contract
+
+Create a single contract called `ControlStructures`. It should not inherit from any other contracts and does not need a constructor. It should have the following functions:
+
+### Smart Contract FizzBuzz
+
+Create a function called `fizzBuzz` that accepts a `uint` called `_number` and returns a `string memory`. The function should return:
+
+- "Fizz" if the `_number` is divisible by 3
+- "Buzz" if the `_number` is divisible by 5
+- "FizzBuzz" if the `_number` is divisible by 3 and 5
+- "Splat" if none of the above conditions are true
+
+### Do Not Disturb
+
+Create a function called `doNotDisturb` that accepts a `uint` called `_time`, and returns a `string memory`. It should adhere to the following properties:
+
+- If `_time` is greater than or equal to 2400, trigger a `panic`
+- If `_time` is greater than 2200 or less than 800, `revert` with a custom error of `AfterHours`, and include the time provided
+- If `_time` is between `1200` and `1259`, `revert` with a string message "At lunch!"
+- If `_time` is between 800 and 1199, return "Morning!"
+- If `_time` is between 1300 and 1799, return "Afternoon!"
+- If `_time` is between 1800 and 2200, return "Evening!"
+
+---
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+{/* */}
+
+
\ No newline at end of file
diff --git a/docs/learn/control-structures/control-structures.mdx b/docs/learn/control-structures/control-structures.mdx
new file mode 100644
index 00000000..605c12c6
--- /dev/null
+++ b/docs/learn/control-structures/control-structures.mdx
@@ -0,0 +1,233 @@
+---
+title: Control Structures
+sidebarTitle: Overview
+description: Learn how to control code flow in Solidity.
+hide_table_of_contents: false
+---
+
+
+Solidity supports many familiar control structures, but these come with additional restrictions and considerations due to the cost of gas and the necessity of setting a maximum amount of gas that can be spent in a given transaction.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Control code flow with `if`, `else`, `while`, and `for`
+- List the unique constraints for control flow in Solidity
+- Utilize `require` to write a function that can only be used when a variable is set to `true`
+- Write a `revert` statement to abort execution of a function in a specific state
+- Utilize `error` to control flow more efficiently than with `require`
+
+---
+
+## Control Structures
+
+Solidity supports the basic conditional and iterative [control structures] found in other curly bracket languages, but it **does not** support more advanced statements such as `switch`, `forEach`, `in`, `of`, etc.
+
+Solidity does support `try`/`catch`, but only for calls to other contracts.
+
+
+[Yul] is an intermediate-level language that can be embedded in Solidity contracts and is documented within the docs for Solidity. Yul **does** contain the `switch` statement, which can confuse search results.
+
+
+
+### Conditional Control Structure Examples
+
+The `if`, `else if`, and `else`, statements work as expected. Curly brackets may be omitted for single-line bodies, but we recommend avoiding this as it is less explicit.
+
+```solidity
+function ConditionalExample(uint _number) external pure returns (string memory) {
+ if(_number == 0) {
+ return "The number is zero.";
+ } else if(_number % 2 == 0) {
+ return "The number is even and greater than zero.";
+ } else {
+ return "The number is odd and is greater than zero.";
+ }
+}
+```
+
+### Iterative Control Structures
+
+The `while`, `for`, and `do`, keywords function the same as in other languages. You can use `continue` to skip the rest of a loop and start the next iteration. `break` will terminate execution of the loop, and you can use `return` to exit the function and return a value at any point.
+
+
+You can use `console.log` by importing `import "hardhat/console.sol";`. Doing so will require you to mark otherwise `pure` contracts as `view`.
+
+
+
+```solidity
+uint times; // Default value is 0!
+for(uint i = 0; i <= times; i++) {
+ console.log(i);
+}
+
+uint timesWithContinue;
+for(uint i = 0; i <= timesWithContinue; i++) {
+ if(i % 2 == 1) {
+ continue;
+ }
+ console.log(i);
+}
+
+uint timesWithBreak;
+for(uint i = 0; i <= timesWithBreak; i++) {
+ // Always stop at 7
+ if(i == 7) {
+ break;
+ }
+ console.log(i);
+}
+
+uint stopAt = 10;
+while(stopAt <= 10) {
+ console.log(i);
+ stopAt++;
+}
+
+uint doFor = 10;
+do {
+ console.log(i);
+ doFor++;
+} while(doFor <= 10);
+```
+
+---
+
+## Error Handling
+
+Solidity contains a set of relatively unique, built-in functions and keywords to handle [errors]. They ensure certain requirements are met, and completely abort all execution of the function and revert any state changes that occurred during function execution. You can use these functions to help protect the security of your contracts and limit their execution.
+
+The approach may seem different than in other environments. If an error occurs partly through a high-stakes transaction such as transferring millions of dollars of tokens, you **do not** want execution to carry on, partially complete, or swallow any errors.
+
+### Revert and Error
+
+The `revert` keyword halts and reverses execution. It must be paired with a custom `error`. Revert should be used to prevent operations that are logically valid, but should not be allowed for business reasons. It is **not** a bug if a `revert` is triggered. Examples where `revert` and `error` would be used to control operations include:
+
+- Allowing only certain senders to access functionality
+- Preventing the withdrawal of a deposit before a certain date
+- Allowing inputs under certain state conditions and denying them under others
+
+Custom `error`s can be declared without parameters, but they are much more useful if you include them:
+
+```solidity
+error OddNumberSubmitted(uint _first, uint _second);
+function onlyAddEvenNumbers(uint _first, uint _second) public pure returns (uint) {
+ if(_first % 2 != 0 || _second % 2 != 0) {
+ revert OddNumberSubmitted(_first, _second);
+ }
+ return _first + _second;
+}
+```
+
+When triggered, the `error` provides the values in the parameters provided. This information is very useful when debugging, and/or to transmit information to the front end to share what has happened with the user:
+
+```text
+call to HelloWorld.onlyAddEvenNumbers errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Error provided by the contract:
+OddNumberSubmitted
+Parameters:
+{
+ "_first": {
+ "value": "1"
+ },
+ "_second": {
+ "value": "2"
+ }
+}
+Debug the transaction to get more information.
+```
+
+You'll also encounter `revert` used as a function, returning a string error. This legacy pattern has been retained to maintain compatibility with older contracts:
+
+```solidity
+function oldRevertAddEvenNumbers(uint _first, uint _second) public pure returns (uint) {
+ if(_first % 2 != 0 || _second % 2 != 0) {
+ // Legacy use of revert, do not use
+ revert("One of the numbers is odd");
+ }
+ return _first + _second;
+}
+```
+
+The error provided is less helpful:
+
+```text
+call to HelloWorld.oldRevertAddEvenNumbers errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+The reason provided by the contract: "One of the numbers is odd".
+Debug the transaction to get more information.
+```
+
+### Require
+
+The `require` function is falling out of favor because it uses more gas than the pattern above. You should still become familiar with it because it is present in innumerable contracts, tutorials, and examples.
+
+`require` takes a logical condition and a string error as arguments. It is more gas efficient to separate logical statements if they are not interdependent. In other words, don't use `&&` or `||` in a `require` if you can avoid it.
+
+For example:
+
+```solidity
+function requireAddEvenNumbers(uint _first, uint _second) public pure returns (uint) {
+ // Legacy pattern, do not use
+ require(_first % 2 == 0, "First number is not even");
+ require(_second % 2 == 0, "Second number is not even");
+
+ return _first + _second;
+}
+```
+
+The output error message will be the first one that fails. If you were to submit `1`, and `3` to this function, the error will only contain the first message:
+
+```test
+call to HelloWorld.requireAddEvenNumbers errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+The reason provided by the contract: "First number is not even".
+Debug the transaction to get more information.
+```
+
+### Assert and Panic
+
+The `assert` keyword throws a `panic` error if triggered. A `panic` is the same type of error that is thrown if you try to divide by zero or access an array out-of-bounds. It is used for testing internal errors and should never be triggered by normal operations, even with flawed input. You have a bug that should be resolved if an assert throws an exception:
+
+```solidity
+function ProcessEvenNumber(uint _validatedInput) public pure {
+ // If assert triggers, input validation has failed. This should never
+ // happen!
+ assert(_validatedInput % 2 == 0);
+ // Do something...
+}
+```
+
+The output here isn't as helpful, so you may wish to use one of the patterns above instead.
+
+```text
+call to HelloWorld.ProcessEvenNumber errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Note: The called function should be payable if you send value and the value you send should be less than your current balance.
+Debug the transaction to get more information.
+```
+
+---
+
+## Conclusion
+
+In this lesson, you've learned how to control code flow with standard conditional and iterative operators. You've also learned about the unique keywords Solidity uses to generate errors and reset changes if one of them has been triggered. You've been exposed to both newer and legacy methods of writing errors, and learned the difference between `assert` and `require`.
+
+
+
+[switch]: https://docs.soliditylang.org/en/v0.8.17/yul.html?#switch
+[yul]: https://docs.soliditylang.org/en/v0.8.17/yul.html
+[control structures]: https://docs.soliditylang.org/en/v0.8.17/control-structures.html
+[errors]: https://docs.soliditylang.org/en/v0.8.17/control-structures.html#error-handling-assert-require-revert-and-exceptions
diff --git a/docs/learn/control-structures/loops-vid.mdx b/docs/learn/control-structures/loops-vid.mdx
new file mode 100644
index 00000000..f9418400
--- /dev/null
+++ b/docs/learn/control-structures/loops-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Loops
+description: Explore loops in Solidity.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/control-structures/require-revert-error-vid.mdx b/docs/learn/control-structures/require-revert-error-vid.mdx
new file mode 100644
index 00000000..0ccf4874
--- /dev/null
+++ b/docs/learn/control-structures/require-revert-error-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Require, Revert, and Error
+description: Handle errors in Solidity.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/control-structures/standard-control-structures-vid.mdx b/docs/learn/control-structures/standard-control-structures-vid.mdx
new file mode 100644
index 00000000..1b0edea6
--- /dev/null
+++ b/docs/learn/control-structures/standard-control-structures-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: If, Else, and Else If
+sidebarTitle: Standard Control Structures
+description: Learn how to control your code.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/cross-chain-development.mdx b/docs/learn/cross-chain-development.mdx
new file mode 100644
index 00000000..86a6691a
--- /dev/null
+++ b/docs/learn/cross-chain-development.mdx
@@ -0,0 +1,3 @@
+---
+title: Cross Chain Development
+---
\ No newline at end of file
diff --git a/docs/learn/deployment-to-testnet/contract-verification-sbs.mdx b/docs/learn/deployment-to-testnet/contract-verification-sbs.mdx
new file mode 100644
index 00000000..396f644a
--- /dev/null
+++ b/docs/learn/deployment-to-testnet/contract-verification-sbs.mdx
@@ -0,0 +1,68 @@
+---
+title: Contract Verification
+description: Verify your contract and interact with it.
+hide_table_of_contents: false
+---
+
+Once your contract is deployed, you can verify it using a number of popular services. Doing so will let your users have confidence that your contract does what you claim, and will allow you to interact with it using a similar interface to what you used in Remix.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Verify a contract on the Base Sepolia testnet and interact with it in [BaseScan](https://sepolia.basescan.org/).
+
+---
+
+### Verify the Contract
+
+Make sure you still have the address of the contract you deployed in the last article copied to the clipboard.
+
+You can interact with your deployed contract using Remix, the same as before, but it's also possible to interact with it through BaseScan. Paste your address in the search field to find it.
+
+On this page, you can review the balance, information about, and all the transactions that have ever occurred with your contract.
+
+Click the _Contract_ tab in the main panel. At the top is a message asking you to _Verify and Publish_ your contract source code.
+
+
+
+
+
+Verifying your contract maps the names of your functions and variables to the compiled byte code, which makes it possible to interact with the contract using a human-readable interface.
+
+Click the link. Your contract address is already entered.
+
+Under _Please select Compiler Type_ choose Solidity (Single file)
+
+For _Please Select Compiler Version_ select the version matching the `pragma` at the top of your source file. Our examples are currently using _v0.8.17+commit.8df45f5f_.
+
+For _Please select Open Source License Type_ pick the license that matches what you selected for your contract as the `SPDX-License-Identifier`. Pick _None_ if you followed the Solidity-recommended practice of using `UNLICENSED`.
+
+On the next page, copy and paste your source code in the window. Verify that you are not a robot, and click _Verify and Publish_. You should see a success message.
+
+
+
+
+
+Click the linked address to your contract to return to the contract page. You'll now see your code!
+
+
+If you have imports, you'll need to right-click on the name of the file and choose `Flatten`. Submit the newly generated `filename_flattened.sol` for verification.
+
+
+
+### Interact with the Contract
+
+You can now interact with your contract using BaseScan. Click the _Read Contract_ button. Both of your functions will be listed here and can be tested using the web interface.
+
+You won't have anything under _Write Contract_ because this contract doesn't have any functions that save data to state.
+
+---
+
+## Conclusion
+
+With your contracts verified, you can interact with them using online tools and your users can be secure that your code does what you claim.
+
+---
diff --git a/docs/learn/deployment-to-testnet/deployment-to-base-sepolia-sbs.mdx b/docs/learn/deployment-to-testnet/deployment-to-base-sepolia-sbs.mdx
new file mode 100644
index 00000000..bb8d330b
--- /dev/null
+++ b/docs/learn/deployment-to-testnet/deployment-to-base-sepolia-sbs.mdx
@@ -0,0 +1,108 @@
+---
+sidebarTitle: Deploy to Base Sepolia
+title: Deployment to Base Sepolia
+description: Deploy your smart contract to a test network.
+hide_table_of_contents: false
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+Remix contains a simulation of a blockchain that you can use to rapidly deploy and test your contracts. This simulation only exists within your browser so you can't share it with others, use external tools, or a front end to interact with it. However, you can also deploy to a variety of testnets from within Remix. Doing so will allow you to share your contract with others, at the cost of making it public.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Deploy a contract to the Base Sepolia testnet and interact with it in [BaseScan](https://sepolia.basescan.org/).
+
+---
+
+## Prepare for Deployment
+
+Testnets operate in a similar, **but not exactly the same** manner as the main networks they shadow. You need a wallet with the appropriate token to interact with them by deploying a new contract or calling functions in a deployed contract.
+
+### Set Up a Wallet
+
+If you already have a wallet set up **exclusively for development**, you can skip to the next section. Otherwise, now is the time to jump in!
+
+
+It is very dangerous to use a wallet with valuable assets for development. You could easily write code with a bug that transfers the wrong amount of the wrong token to the wrong address. Transactions cannot be reversed once sent!
+
+Be safe and use separate wallets for separate purposes.
+
+
+
+First, add the [Coinbase](https://www.coinbase.com/wallet) or [Metamask](https://metamask.io/) wallet to your browser, and then [set up] a new wallet. As a developer, you need to be doubly careful about the security of your wallet! Many apps grant special powers to the wallet address that is the owner of the contract, such as allowing the withdrawal of all the Ether that customers have paid to the contract or changing critical settings.
+
+Once you've completed the wallet setup, enable developer settings and turn on testnets ([Coinbase Settings](https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings), [Metamask Settings](https://support.metamask.io/hc/en-us/articles/13946422437147-How-to-view-testnets-in-MetaMask)).
+
+### Add Base Sepolia to your Wallet
+
+Use the [faucet](/base-chain/tools/network-faucets) to add Base Sepolia ETH to your wallet. You can also ask Base personnel on Discord or other social media for some!
+
+### Get Testnet Ether
+
+Testnet tokens have no real value, but the supply is not unlimited. You can use a faucet to get a small amount of Sepolia Ether to pay gas fees for testing. Most faucets allow you to ask for a small amount each day, and some won't send you more if your balance is too high.
+
+You can find many faucets by searching, and it's good to keep a few bookmarked because they have a tendency to go down from time to time. Faucet providers are constantly combating bad actors and sometimes need to disable their faucets while doing so.
+
+You can also access the [faucets on the web](https://coinbase.com/faucets).
+
+Once you have testnet Base Sepolia Ether, you can view your balance under the _Testnets_ tab in the Coinbase wallet or by selecting the testnet from the network dropdown in Metamask. Sadly, it's not actually worth the amount listed!
+
+
+
+
+
+---
+
+## Deploying to Testnet
+
+Once you have testnet Ether, you can deploy your BasicMath contract!
+
+### Selecting the Environment
+
+Open the _Deploy & Run Transactions_ tab. Under _Environment_, select _Injected Provider_. It will list _Coinbase_, _Metamask_, or any other wallet you have activated here.
+
+
+
+
+
+If that option is not available, you can add it by choosing `Customize this list...`
+
+
+
+
+
+The first time you do this, your wallet will ask you to confirm that you want to connect this app (Remix) to your wallet.
+
+Once you are connected, you'll see the name of the network below the _Environment_ dropdown.
+
+
+
+
+
+For Base Sepolia, you should see `Custom (84532) network`. The old network, Goerli, was `84531`. If you don't see the correct network, change the active network in your wallet.
+
+### Deploy the Contract
+
+Click the orange _Deploy_ button. Because it costs gas to deploy a contract, you'll be asked to review and confirm a transaction.
+
+
+
+
+
+
+Always carefully review all transactions, confirming the transaction cost, assets transferred, and network. As a developer, you'll get used to approving transactions regularly. Do the best you can to avoid getting into the habit of clicking _Confirm_ without reviewing the transaction carefully. If you feel pressured to _Confirm_ before you run out of time, it is almost certainly a scam.
+
+
+
+After you click the _Confirm_ button, return to Remix and wait for the transaction to deploy. Copy its address and navigate to [`sepolia.basescan.org`](https://sepolia.basescan.org/).
+
+## Conclusion
+
+You now have the power to put smart contracts on the blockchain! You've only deployed to a test network, but the process for real networks is exactly the same - just more expensive!
+
+---
diff --git a/docs/learn/deployment-to-testnet/deployment-to-testnet-exercise.mdx b/docs/learn/deployment-to-testnet/deployment-to-testnet-exercise.mdx
new file mode 100644
index 00000000..18cba9e9
--- /dev/null
+++ b/docs/learn/deployment-to-testnet/deployment-to-testnet-exercise.mdx
@@ -0,0 +1,36 @@
+---
+title: 'Deployment Exercise'
+sidebarTitle: 'Exercise'
+description: Exercise - Deploy your basic math contract and earn an NFT.
+hide_table_of_contents: false
+---
+
+You've already built and deployed your [Basic Math](/learn/contracts-and-basic-functions/basic-functions-exercise) contract for this exercise. Now it's time to submit the address and earn an NFT pin to commemorate your accomplishment!
+
+
+We're currently in beta, so you'll only need to pay testnet funds to submit your contract, but this means you'll be getting a testnet NFT.
+
+Stay tuned for updates!
+
+
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+{/* */}
+
+
diff --git a/docs/learn/deployment-to-testnet/overview-of-test-networks-vid.mdx b/docs/learn/deployment-to-testnet/overview-of-test-networks-vid.mdx
new file mode 100644
index 00000000..2a6e1964
--- /dev/null
+++ b/docs/learn/deployment-to-testnet/overview-of-test-networks-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Overview of Test Networks
+description: Learn about test networks.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/deployment-to-testnet/test-networks.mdx b/docs/learn/deployment-to-testnet/test-networks.mdx
new file mode 100644
index 00000000..6b21c62a
--- /dev/null
+++ b/docs/learn/deployment-to-testnet/test-networks.mdx
@@ -0,0 +1,97 @@
+---
+title: Test Networks
+description: An overview of Base test networks
+hide_table_of_contents: false
+---
+
+This article provides a concise overview of Base test networks, highlighting their advantages, potential challenges, and comparing some of the most popular testnets.
+
+---
+
+## Objectives:
+
+By the end of this lesson you should be able to:
+
+- Describe the uses and properties of the Base testnet
+- Compare and contrast Ropsten, Rinkeby, Goerli, and Sepolia
+
+---
+
+## Why Testnets?
+
+As you dive into the development of smart contracts and onchain apps on Base, mainnet, or other networks, you'll need a safe, controlled, and efficient environment for testing and experimentation. Test networks, or testnets, serve as essential tools for you to test your smart contracts before deploying them to the mainnet, minimizing the risk of failures or vulnerabilities in live applications.
+
+By simulating the mainnet environment, testnets offer a realistic representation of real-world conditions, complete with network latency, gas fees, and other factors that impact the performance of smart contracts. This accurate representation enables you to identify potential issues, optimize your applications, and ensure the best possible user experience for your end-users. Moreover, testnets allow you to familiarize yourself with the Base ecosystem and gain valuable hands-on experience, making them indispensable tools for both seasoned developers and newcomers to the world of blockchain development.
+
+
+
+
+
+---
+
+## The Advantages of Using Testnets
+
+Testnets offer several key advantages to developers:
+
+- **Real-time feedback:** Developers can quickly identify and fix errors or vulnerabilities in their smart contracts, ensuring robust and secure applications.
+
+- **No cost or risk:** Testnets use "fake" ether, enabling developers to deploy and interact with smart contracts without incurring any financial cost or risk.
+
+- **Easy accessibility:** Testnets are readily available for developers to join, allowing them to focus on development rather than infrastructure setup.
+
+- **Stress testing:** Testnets provide a suitable environment to stress test the Ethereum network infrastructure under various conditions. By simulating high transaction volumes, developers can evaluate how their applications perform under load and optimize them accordingly.
+
+- **Protocol upgrades and improvements:** Testnets allow developers to test new protocol updates, improvements, and potential forks before implementing them on the mainnet. This process helps identify any issues or incompatibilities and ensures a smoother transition to new features and optimizations.
+
+---
+
+## Challenges Associated with Testnets
+
+While Ethereum testnets provide a valuable testing environment for developers, there are some challenges and limitations you should be aware of when using them:
+
+- **Network congestion:** Just like the mainnet, testnets can experience periods of network congestion due to high transaction volumes or other factors. During these periods, developers might face slow transaction processing times, which could impact their testing process and potentially delay development.
+
+- **Testnet instability:** Testnets may occasionally face downtime or network issues, which can disrupt the testing process. While these events are relatively rare, it's essential to be prepared for such occurrences and have a backup plan, such as switching to another testnet or using a local development environment.
+
+- **Differences in network behavior:** Although testnets simulate the mainnet environment, there might be subtle differences in network behavior, gas fees, or transaction processing times between the two. These differences could potentially impact the performance of your smart contracts on the mainnet. It's crucial to be aware of these discrepancies and make any necessary adjustments before deploying your smart contracts to the mainnet.
+
+- **Limited resources:** Testnet Ether is generally available for free through faucets, but these sources might have daily limits or other restrictions on the amount of testnet Ether that can be obtained. This limitation could affect your ability to perform extensive testing or carry out large-scale experiments on the testnet.
+
+---
+
+## Popular Testnets
+
+Several well-known testnets have emerged over the years, each with its own set of features and benefits.
+
+
+
+
+
+### L1 Testnets
+
+- **Ropsten:** Ropsten played a significant role in Ethereum's history but was effectively deprecated by late 2022 when the Merge took place. The Merge marked the transition from proof-of-work to proof-of-stake consensus for the Ethereum mainnet. Ropsten's vulnerability to spam attacks and network instability made it unreliable for testing purposes.
+
+- **Rinkeby:** Rinkeby offered better security than Ropsten and used a proof-of-authority consensus mechanism. However, it lacked decentralization and client diversity, which ultimately led to its decline in popularity. After the Merge, Rinkeby is no longer a recommended test network.
+
+- **Goerli:** Launched in early 2019, Goerli initially utilized a multi-client proof-of-authority consensus model to improve stability and security. Following the Merge, it transitioned to a proof-of-stake consensus mechanism, maintaining its cross-client compatibility and making it an ideal choice for developers. As of January 2024, Goerli is being sunset in favor of Sepolia.
+
+- **Sepolia:** As one of the two original primary testnets alongside Goerli, Sepolia is designed for developers seeking a lighter weight chain for faster synchronization and interaction. As of January 2024, it is now the preferred testnet and developers should migrate to using it.
+
+### L2 Testnets
+
+- **Base Sepolia:** As new Layer-2 networks emerged that settled on Ethereum's Layer-1, the need for testnets dedicated to these L2 networks also arose. For instance, the L2 network Base has its own testnet, known as Base Sepolia. This testnet settles on the Ethereum Sepolia L1 testnet, providing an environment for testing L2-specific features and smart contracts.
+
+- **Optimism Sepolia:** Optimism, an Ethereum Layer-2 scaling solution utilizing Optimistic Rollups, has its own testnet called Optimism Sepolia. This testnet is also built on the Ethereum Sepolia L1 testnet and offers a testing environment for developers to experiment with Optimism's Layer-2 features, smart contracts, and apps.
+
+---
+
+## Conclusion
+
+Ethereum and L2 testnets are essential for the safe and efficient development of smart contracts and apps, offering numerous advantages such as real-time feedback, cost-free testing, easy accessibility, stress testing, and protocol upgrade testing. Despite certain challenges associated with testnets, developers continue to rely on them for a robust testing environment. Among the various options, Sepolia has emerged as preferred choices for Ethereum developers due to enhanced security, stability, and strong community support. As the Ethereum ecosystem evolves, incorporating Layer-2 networks and other innovations, testnets will continue to play a crucial role in fostering blockchain development and contributing to the overall success and growth of the space.
+
+---
+
+## See Also
+
+- [Networks](https://ethereum.org/en/developers/docs/networks/)
+- [The History of Ethereum Testnets](https://consensys.net/blog/news/the-history-of-ethereum-testnets/)
diff --git a/docs/learn/erc-20-token/analyzing-erc-20-vid.mdx b/docs/learn/erc-20-token/analyzing-erc-20-vid.mdx
new file mode 100644
index 00000000..b7cd981d
--- /dev/null
+++ b/docs/learn/erc-20-token/analyzing-erc-20-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Analyzing the ERC-20 Token
+description: Explore the ERC-20 standard.
+sidebarTitle: Analyzing ERC-20
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/erc-20-token/erc-20-exercise.mdx b/docs/learn/erc-20-token/erc-20-exercise.mdx
new file mode 100644
index 00000000..c6293fd4
--- /dev/null
+++ b/docs/learn/erc-20-token/erc-20-exercise.mdx
@@ -0,0 +1,124 @@
+---
+title: ERC-20 Tokens Exercise
+description: Exercise - Create your own ERC-20 token!
+hide_table_of_contents: false
+sidebarTitle: Exercise
+---
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Create a contract called `WeightedVoting`. Add the following:
+
+- A `maxSupply` of 1,000,000
+- Errors for:
+ - `TokensClaimed`
+ - `AllTokensClaimed`
+ - `NoTokensHeld`
+ - `QuorumTooHigh`, returning the quorum amount proposed
+ - `AlreadyVoted`
+ - `VotingClosed`
+- A struct called `Issue` containing:
+ - An OpenZeppelin Enumerable Set storing addresses called `voters`
+ - A string `issueDesc`
+ - Storage for the number of `votesFor`, `votesAgainst`, `votesAbstain`, `totalVotes`, and `quorum`
+ - Bools storing if the issue is `passed` and `closed`
+
+
+The unit tests require this `struct` to be constructed with the variables in the order above.
+
+
+
+- An array of `Issue`s called `issues`
+- An `enum` for `Vote` containing:
+ - `AGAINST`
+ - `FOR`
+ - `ABSTAIN`
+- Anything else needed to complete the tasks
+
+Add the following functions.
+
+### Constructor
+
+Initialize the ERC-20 token and burn the zeroeth element of `issues`.
+
+### Claim
+
+Add a `public` function called `claim`. When called, so long as a number of tokens equalling the `maximumSupply` have not yet been distributed, any wallet _that has not made a claim previously_ should be able to claim 100 tokens. If a wallet tries to claim a second time, it should revert with `TokensClaimed`.
+
+Once all tokens have been claimed, this function should revert with an error `AllTokensClaimed`.
+
+
+In our simple token, we used `totalSupply` to mint our tokens up front. The ERC20 implementation we're using also tracks `totalSupply`, but does it differently.
+
+Review the docs and code comments to learn how.
+
+
+
+### Create Issue
+
+Implement an `external` function called `createIssue`. It should add a new `Issue` to `issues`, allowing the user to set the description of the issue, and `quorum` - which is how many votes are needed to close the issue.
+
+Only token holders are allowed to create issues, and issues cannot be created that require a `quorum` greater than the current total number of tokens.
+
+This function must return the index of the newly-created issue.
+
+
+One of the unit tests will break if you place your check for `quorum` before the check that the user holds a token. The test compares encoded error names, which are **not** human-readable. If you are getting `-> AssertionError: �s is not equal to �9�` or similar, this is likely the issue.
+
+
+
+### Get Issue
+
+Add an `external` function called `getIssue` that can return all of the data for the issue of the provided `_id`.
+
+`EnumerableSet` has a `mapping` underneath, so it can't be returned outside of the contract. You'll have to figure something else out.
+
+
+**Hint**
+
+The return type for this function should be a `struct` very similar to the one that stores the issues.
+
+
+### Vote
+
+Add a `public` function called `vote` that accepts an `_issueId` and the token holder's vote. The function should revert if the issue is closed, or the wallet has already voted on this issue.
+
+Holders must vote all of their tokens for, against, or abstaining from the issue. This amount should be added to the appropriate member of the issue and the total number of votes collected.
+
+If this vote takes the total number of votes to or above the `quorum` for that vote, then:
+
+- The issue should be set so that `closed` is true
+- If there are **more** votes for than against, set `passed` to `true`
+
+---
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+
+The contract specification contains actions that can only be performed once by a given address. As a result, the unit tests for a passing contract will only be successful the **first** time you test.
+
+**You may need to submit a fresh deployment to pass**
+
+
+{/* */}
+
+
\ No newline at end of file
diff --git a/docs/learn/erc-20-token/erc-20-standard.mdx b/docs/learn/erc-20-token/erc-20-standard.mdx
new file mode 100644
index 00000000..9ef86281
--- /dev/null
+++ b/docs/learn/erc-20-token/erc-20-standard.mdx
@@ -0,0 +1,103 @@
+---
+title: The ERC-20 Token Standard
+description: An overview of the ERC-20 token standard
+sidebarTitle: ERC-20 Standard
+hide_table_of_contents: false
+---
+
+
+In this article, we'll delve into the structure and specifications of ERC-20 tokens, uncovering the key elements that contribute to their widespread adoption and diverse use cases.
+
+---
+
+## Objectives:
+
+By the end of this lesson you should be able to:
+
+- Analyze the anatomy of an ERC-20 token
+- Review the formal specification for ERC-20
+
+---
+
+## Introduction
+
+The emergence of the ERC-20 token standard marked a significant milestone in the evolution of the Ethereum ecosystem, providing a unified and adaptable framework for creating and managing fungible tokens. As the backbone for a vast array of applications in decentralized finance and beyond, ERC-20 tokens facilitate seamless collaboration and interoperability within the Ethereum ecosystem. Their adaptable nature and standardized structure have made them the go-to choice for developers and users alike, laying the groundwork for continued innovation and growth in the Ethereum space.
+
+The ERC-20 token standard has not only streamlined the creation of new tokens but also bolstered the overall user experience by establishing a consistent set of rules and functions. As a result, it has garnered widespread adoption and solidified its position as the de facto standard for fungible tokens on Ethereum, driving the expansion of the decentralized economy and fostering the development of novel applications and services.
+
+
+
+
+
+---
+
+## ERC-20 Specification
+
+EIP-20 (Ethereum Improvement Proposal 20) is the formal specification for ERC-20, defining the requirements to create compliant tokens on the Ethereum blockchain. EIP-20 prescribes the mandatory functions, optional functions, and events a token must implement to achieve ERC-20 compliance. Adherence to EIP-20 allows developers to create tokens compatible with existing Ethereum applications and services, streamlining integration.
+
+---
+
+## Anatomy of an ERC-20 Token
+
+An ERC-20 token consists of a smart contract that implements the standardized interface, which comprises a set of six mandatory functions:
+
+- **totalSupply():** Returns the total supply of the token.
+- **balanceOf(address):** Provides the balance of tokens held by a specific address.
+- **transfer(address, uint256):** Transfers a specified amount of tokens from the sender's address to the specified recipient's address.
+- **transferFrom(address, address, uint256):** Enables a third party to transfer tokens on behalf of the token owner, given that the owner has approved the transaction.
+- **approve(address, uint256):** Allows the token owner to grant permission to a third party to spend a specified amount of tokens on their behalf.
+- **allowance(address, address):** Returns the amount of tokens the token owner has allowed a third party to spend on their behalf.
+
+Additionally, ERC-20 tokens can include optional functions that provide descriptive information about the token:
+
+- **name():** Returns the name of the token, for example, "Uniswap."
+- **symbol():** Provides the token's symbol, like "UNI."
+- **decimals():** Indicates the number of decimal places the token can be divided into, typically 18 for most tokens.
+
+---
+
+## Benefits of ERC-20 Standardization
+
+The standardization of ERC-20 tokens provides several benefits for both developers and users. For developers, it simplifies the process of creating new tokens by providing a consistent set of functions and conventions. This reduces the likelihood of errors and ensures a smooth integration with existing applications and services in the Ethereum ecosystem.
+
+
+
+
+
+For users, the standardized interface makes it easier to interact with a wide variety of tokens, regardless of their specific purpose or implementation. This means that users can effortlessly check their token balance, transfer tokens, or approve transactions using the same set of functions, whether they are interacting with a governance token like UNI or a stablecoin like USDC.
+
+
+
+
+
+---
+
+## Applications
+
+ERC-20 tokens find wide-ranging applications in various categories, each with its unique purpose and functionality:
+
+- **Utility tokens:** These tokens can be used to access specific services or features within a platform. Examples include Filecoin (FIL) for decentralized storage, Basic Attention Token (BAT) for digital advertising, and Decentraland's MANA for purchasing virtual land and assets.
+
+- **Governance tokens:** These tokens grant voting rights and influence over the development of a project, allowing holders to participate in decision-making processes. Examples include Uniswap (UNI), Aave (AAVE), and Compound (COMP).
+
+- **Stablecoins:** These tokens maintain a relatively stable value pegged to a reserve of assets or fiat currency, providing a less volatile option for transactions and trading. Examples include USD Coin (USDC), Tether (USDT), and MakerDAO's DAI.
+
+- **Liquidity tokens:** Liquidity providers on DeFi platforms often receive ERC-20 tokens as a representation of their share in a liquidity pool. These tokens can be staked or traded, and they enable users to earn rewards for providing liquidity. Examples include Uniswap LP tokens and Curve LP tokens.
+
+- **Rewards tokens:** Some platforms issue ERC-20 tokens as incentives for users to participate in their ecosystem, such as staking, lending, or providing liquidity. These tokens can be earned as passive income or used to access additional platform features. Examples include Synthetix (SNX) and SushiSwap (SUSHI).
+
+Each of these use cases demonstrates the adaptability of ERC-20 tokens to serve different needs within the blockchain ecosystem.
+
+---
+
+## Conclusion
+
+By providing a consistent framework for fungible tokens and adhering to the formal EIP-20 specification, ERC-20 has enabled the development of countless projects and applications that have revolutionized how value is exchanged and managed on Ethereum. Analyzing the anatomy of an ERC-20 token and reviewing its formal specification reveal the versatility and importance of this token standard.
+
+---
+
+## See Also
+
+- [Introduction to Ethereum Improvement Proposals (EIPs)](https://ethereum.org/en/eips/)
+- [EIP-20: ERC-20 Token Standard](https://eips.ethereum.org/EIPS/eip-20)
+- [ERC-20 Token Standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/)
diff --git a/docs/learn/erc-20-token/erc-20-testing-vid.mdx b/docs/learn/erc-20-token/erc-20-testing-vid.mdx
new file mode 100644
index 00000000..4214a1c6
--- /dev/null
+++ b/docs/learn/erc-20-token/erc-20-testing-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: ERC-20 Testing
+description: Test the OpenZeppelin ERC-20 implementation.
+hide_table_of_contents: false
+sidebarTitle: Testing ERC-20
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/erc-20-token/erc-20-token-sbs.mdx b/docs/learn/erc-20-token/erc-20-token-sbs.mdx
new file mode 100644
index 00000000..34b9c815
--- /dev/null
+++ b/docs/learn/erc-20-token/erc-20-token-sbs.mdx
@@ -0,0 +1,141 @@
+---
+title: ERC-20 Implementation
+sidebarTitle: Step by Step Guide
+description: Implement your own ERC-20 token.
+hide_table_of_contents: false
+---
+
+The ERC-20 is a standard that allows for the development of fungible tokens and helps sites and apps, such as exchanges, know how to find and display information about these tokens. You can leverage existing implementations, such as the one by [OpenZeppelin] to develop your own tokens.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Describe OpenZeppelin
+- Import the OpenZeppelin ERC-20 implementation
+- Describe the difference between the ERC-20 standard and OpenZeppelin's ERC20.sol
+- Build and deploy an ERC-20 compliant token
+
+---
+
+## Setting Up the Contract
+
+Create a new Solidity file, add the license and pragma, and import the ERC-20 implementation linked above.
+
+Add a contract called `MyERC20Token` that inherits from `ERC20`.
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.17;
+
+import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol";
+
+contract MyERC20Token is ERC20 {
+
+}
+```
+
+### Adding a Constructor
+
+Review the constructor on line 53 of the [OpenZeppelin] implementation. It requires strings for the name and symbol you wish to use for your token. They're using a slightly different naming convention by putting the `_` after the name of the parameters. Like any other function, you can pass variables of **any** name as long as they're the right type, so feel free to continue adding the `_` in front in your contract's constructor:
+
+```solidity
+constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {
+
+}
+```
+
+
+There is neither a governing body nor built-in programmatic rules preventing you, or anyone else, from using the same name and symbol as an already in-use token. Scammers often take advantage of this fact, and even well-meaning developers can cause confusion by not being careful here.
+
+
+
+That's it. You're done! Deploy and test, and you should see all of the functionality called for by the standard and provided by the OpenZeppelin implementation.
+
+
+
+
+
+Do some testing. You'll see that the `totalSupply` and all balances are zero.
+
+By default, the decimal for the token will be 18, which is the most common choice. Remember, there aren't decimal types yet, so 1.0 ETH is really a `uint` holding 1 \* 10\*\*18, or 1000000000000000000.
+
+---
+
+## ERC-20 Further Testing
+
+Line 251 of the [OpenZeppelin] implementation contains a `_mint` function, but it's internal. As a result, you'll need to figure out a minting mechanism and add it via your own contract.
+
+### Minting in the Constructor
+
+One method of using the `_mint` function is to create an initial supply of tokens in the constructor. Add a call to `_mint` that awards 1 full token to the contract creator. Remember, the decimal is 18. Minting literally `1` is creating a tiny speck of dust.
+
+```solidity
+constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {
+ _mint(msg.sender, 1 * 10**18);
+}
+```
+
+Redeploy. Without you needing to do anything, you should find that the `totalSupply` is now 1000000000000000000, as is the `balanceOf` the deploying address.
+
+You can also use this to mint to other users. Go ahead and add the second and third accounts:
+
+
+
+```solidity
+constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {
+ _mint(msg.sender, 1 * 10**18);
+ _mint(0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2, 1 * 10**18);
+ _mint(0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db, 1 * 10**18);
+}
+```
+
+
+
+
+**Switch back** to the first account and redeploy. Test to confirm that each account has the appropriate amount of tokens.
+
+### Testing the Transfer Function
+
+Try using the `transfer` function to move tokens around.
+
+What happens if you try to burn a token by sending it to the zero address? Give it a try!
+
+You'll get an error, because protecting from burning is built into the `_transfer` function.
+
+```text
+transact to MyERC20Token.transfer pending ...
+transact to MyERC20Token.transfer errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Reason provided by the contract: "ERC20: transfer to the zero address".
+Debug the transaction to get more information.
+```
+
+### Testing the Transfer From Function
+
+You might have noticed that there's another function called `transferFrom`. What's that for? Check the documentation in the contract to find out!
+
+This function works with the `allowance` function to give the owner of one wallet permission to spend up to a specified amount of tokens owned by another. Exchanges can make use of this to allow a user to post tokens for sale at a given price without needing to take possession of them.
+
+---
+
+## ERC-20 Final Thoughts
+
+The world is still figuring out how to handle all of the new possibilities tokens provide. Old laws are being applied in new ways, and new laws are being written. Different jurisdictions are doing this in unique and sometimes conflicting ways.
+
+You should consult with a lawyer in your jurisdiction before releasing your own tokens.
+
+---
+
+## Conclusion
+
+In this lesson, you've learned how easy it is to create an ERC-20 compliant token by using the OpenZeppelin implementation. You've reviewed at least one method to mint an initial supply of tokens, and that it's up to you to figure out the best way to create your tokens and follow all relevant laws and regulations.
+
+---
+
+[OpenZeppelin]: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol
diff --git a/docs/learn/erc-20-token/openzeppelin-erc-20-vid.mdx b/docs/learn/erc-20-token/openzeppelin-erc-20-vid.mdx
new file mode 100644
index 00000000..baf1e817
--- /dev/null
+++ b/docs/learn/erc-20-token/openzeppelin-erc-20-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: OpenZeppelin ERC-20 Implementation
+description: Review a popular implementation of the ERC-20 standard.
+hide_table_of_contents: false
+sidebarTitle: OpenZeppelin ERC-20
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/erc-721-token/erc-721-exercise.mdx b/docs/learn/erc-721-token/erc-721-exercise.mdx
new file mode 100644
index 00000000..7951160b
--- /dev/null
+++ b/docs/learn/erc-721-token/erc-721-exercise.mdx
@@ -0,0 +1,87 @@
+---
+title: ERC-721 Tokens Exercise
+description: Exercise - Create your own NFT!
+sidebarTitle: Exercise
+hide_table_of_contents: false
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Create a contract called `HaikuNFT`. Add the following to the contract:
+
+- A `struct` called `Haiku` to store the `address` of the `author` and `line1`, `line2`, and `line3`
+- A public array to store these `haikus`
+- A public `mapping` to relate `sharedHaikus` from the `address` of the wallet shared with, to the id of the Haiku NFT shared
+- A public `counter` to use as the id and to track and share the total number of Haikus minted
+ - If 10 Haikus have been minted, the counter should be at 11, to serve as the next id
+ - Do **NOT** assign an id of 0 to a haiku
+- Other variables as necessary to complete the task
+
+Add the following functions.
+
+### Constructor
+
+As appropriate.
+
+### Mint Haiku
+
+Add an `external` function called `mintHaiku` that takes in the three lines of the poem. This function should mint an NFT for the minter and save their Haiku.
+
+Haikus must be **unique**! If any line in the Haiku has been used as any line of a previous Haiku, revert with `HaikuNotUnique()`.
+
+You **don't** have to count syllables, but it would be neat if you did! (No promises on whether or not we counted the same as you did)
+
+### Share Haiku
+
+Add a `public` function called `shareHaiku` that allows the owner of a Haiku NFT to share that Haiku with the designated `address` they are sending it `_to`. Doing so should add it to that address's entry in `sharedHaikus`.
+
+If the sender isn't the owner of the Haiku, instead revert with an error of `NotYourHaiku`. Include the id of the Haiku in the error.
+
+
+Remember, everything on the blockchain is public. This sharing functionality can be expanded for features similar to allowing an app user to display the selected shared haiku on their profile.
+
+It does nothing to prevent anyone and everyone from seeing or copy/pasting the haiku!
+
+
+
+### Get Your Shared Haikus
+
+Add a `public` function called `getMySharedHaikus`. When called, it should return an array containing all of the haikus shared with the caller.
+
+If there are no haikus shared with the caller's wallet, it should revert with a custom error of `NoHaikusShared`, with no arguments.
+
+---
+
+
+The contract specification contains actions that can only be performed once by a given address. As a result, the unit tests for a passing contract will only be successful the **first** time you test.
+
+**You may need to submit a fresh deployment to pass**
+
+
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](../deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+{/* */}
+
+
\ No newline at end of file
diff --git a/docs/learn/erc-721-token/erc-721-on-opensea-vid.mdx b/docs/learn/erc-721-token/erc-721-on-opensea-vid.mdx
new file mode 100644
index 00000000..6fc97303
--- /dev/null
+++ b/docs/learn/erc-721-token/erc-721-on-opensea-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: ERC-721 Token On Opensea
+description: Learn how a popular marketplace interprets tokens.
+hide_table_of_contents: false
+sidebarTitle: OpenSea Integration
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/erc-721-token/erc-721-sbs.mdx b/docs/learn/erc-721-token/erc-721-sbs.mdx
new file mode 100644
index 00000000..5de91dfa
--- /dev/null
+++ b/docs/learn/erc-721-token/erc-721-sbs.mdx
@@ -0,0 +1,295 @@
+---
+title: ERC-721 Token
+description: Build your own NFT based on the ERC-721 standard.
+hide_table_of_contents: false
+sidebarTitle: Step by Step Guide
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+Punks, Apes, and birds of all kinds. You've heard about them, seen them, and may even be lucky enough to own a famous NFT. Or maybe you've just bought into a random collection and aren't sure what to do with your NFT. NFTs aren't really pictures, or anything else specific. They're a method of proving ownership of a digital asset. Anyone can right-click on a picture of a monkey and set it as their profile picture, but only the owner can use it with apps that utilize web3 ownership.
+
+The ERC-721 token standard is the underlying technical specification that not only makes digital ownership possible, it provides a standardized way for marketplaces, galleries, and other sites to know how to interact with these digital items.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Analyze the anatomy of an ERC-721 token
+- Compare and contrast the technical specifications of ERC-20 and ERC-721
+- Review the formal specification for ERC-721
+- Build and deploy an ERC-721 compliant token
+- Use an ERC-721 token to control ownership of another data structure
+
+---
+
+## Implementing the OpenZeppelin ERC-721 Token
+
+JPGs may be all the rage right now but in the future, the selfie you post on social media, a text message you send to your mother, and the +4 battleaxe you wield in your favorite MMO might all be NFTs.
+
+### Import and Setup
+
+Start by opening the [OpenZeppelin] ERC-721 in Github. Copy the link and use it to import the ERC-721 contract. Create your own contract, called `MyERC721`, that inherits from `ERC721Token`. Add a constructor that initializes the `_name` and `_symbol`.
+
+
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.17;
+
+import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol";
+
+contract MyERC721Token is ERC721 {
+ constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {
+
+ }
+}
+```
+
+
+### Minting NFTs
+
+The minting function that is provided by OpenZeppelin, `_safeMint`, is `internal`. To use it to let your customers mint NFTs, you'll need to implement a function in your contract that calls the one in the imported contract.
+
+Before you can do that, you need a way to supply the two parameters needed for `_safeMint`:
+
+- `address to` - the owner of the new NFT
+- `uint256 tokenId` - the ID number for the new NFT
+
+The owner is easy, you can simply use `msg.sender` to grant ownership to the wallet doing the minting.
+
+ID is slightly more challenging. A common practice is to simply assign the total number of NFTs, including the one being minted, as the `tokenId`. Doing so is straightforward, makes it easier to find all of the NFTs within a collection, and helps lean in to the common community perception that lower-number NFTs are better, just like other limited-edition collectibles.
+
+
+Obfuscating certain information, such as customer IDs, is often considered a best practice. Doing so might make it harder for an attacker who has circumvented other security functions from getting access to more data. If `134` is a valid `customer_id`, it is likely that `135` is too. The same can't be said for `bfcb51bd-c04f-42d5-8116-3def754e8c32`.
+
+This practice is not as useful on the blockchain, because all information is public.
+
+
+
+To implement ID generation, simply add a `uint` called `counter` to storage and initialize it as 1, either at declaration or in the constructor.
+
+Now, you can add a function called `redeemNFT` that calls `safeMint` using the `msg.sender` and `counter`, and then increments the `counter`:
+
+
+
+```solidity
+function redeemNFT() external {
+ _safeMint(msg.sender, counter);
+ counter++;
+}
+```
+
+
+
+
+As a programmer, you've probably gone through great pains to internalize the idea of zero-indexing. Arrays start at 0. The pixel in the top-left corner of your screen is located at 0, 0.
+
+As a result, you need to be very careful when working with Solidity because there isn't the concept of `undefined`, and "deleted" values return to their default value, which is 0 for numbers.
+
+To prevent security risks, you'll need to make sure that you never give an ID or array index of 0 to anything. Otherwise, attempting to delete a value, such as a `struct` member called `authorizedSellerID` might give the wallet address stored at index 0 access to that resource.
+
+
+
+Deploy and test. Be sure to:
+
+- Mint several NFTs
+- Transfer an NFT from one Remix account to another
+- Try to transfer an NFT to `0x0000000000000000000000000000000000000000`
+
+---
+
+## ERC-721 URIs
+
+The ERC-721 standard includes the option to define a [URI] associated with each NFT. These are intended to point to a `json` file following the _ERC721 Metadata JSON Schema_
+
+```json
+{
+ "title": "Asset Metadata",
+ "type": "object",
+ "properties": {
+ "name": {
+ "type": "string",
+ "description": "Identifies the asset to which this NFT represents"
+ },
+ "description": {
+ "type": "string",
+ "description": "Describes the asset to which this NFT represents"
+ },
+ "image": {
+ "type": "string",
+ "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
+ }
+ }
+}
+```
+
+Note that they don't have to. In the OpenZeppelin implementation, the function that returns the `_baseURI` is `virtual` and must be overridden by an inheriting contract.
+
+```
+// OpenZeppelin ERC-721
+/**
+ * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
+ * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
+ * by default, can be overridden in child contracts.
+ */
+function _baseURI() internal view virtual returns (string memory) {
+ return "";
+}
+```
+
+The owner of the contract can therefore choose what the value is and when, how, or if it is changeable. For example, the [Bored Ape Yacht Club] contract has a function allowing the owner to set or change the \_baseURI, changing where the metadata is stored, and potentially what is in it.
+
+```solidity
+// From boredapeyachtclub.sol
+function setBaseURI(string memory baseURI) public onlyOwner {
+ _setBaseURI(baseURI);
+}
+```
+
+The metadata for [BAYC] is [stored on IPFS], but some projects even use centralized, web2 storage options!
+
+### NFT Switcheroo
+
+[Doodles] is another NFT collection that [uses IPFS] to store metadata. Let's modify our contract to swap metadata back and forth from one collection to the other.
+
+Start by saving the IPFS metadata bases as constants, at the contract level. Add an enum to enable selection between these two choices, and an instance of that enum.
+
+
+
+```solidity
+ string constant BAYC = "https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/";
+ string constant DOODLES = "https://ipfs.io/ipfs/QmPMc4tcBsMqLRuCQtPmPe84bpSjrC3Ky7t3JWuHXYB4aS/";
+
+ enum NFTMetadata { BAYC, DOODLES }
+ NFTMetadata nftMetadata = NFTMetadata.BAYC;
+```
+
+
+
+Finally, add an override of `_baseURI` that returns the appropriate selection based on which collection is active, and a function to swap the URI.
+
+
+
+```solidity
+function _baseURI() internal override view returns(string memory) {
+ if (nftMetadata == NFTMetadata.BAYC) {
+ return BAYC;
+ } else if (nftMetadata == NFTMetadata.DOODLES){
+ return DOODLES;
+ } else {
+ revert("Error...");
+ }
+}
+
+function switchURI() public {
+ // TODO: Limit to contract owner
+ nftMetadata = nftMetadata == NFTMetadata.BAYC ? NFTMetadata.DOODLES : NFTMetadata.BAYC;
+}
+```
+
+
+
+Deploy, mint some NFTs, and call `tokenURI` to find the information for token number 1. You should get:
+
+```text
+https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/1
+```
+
+This links to the metadata json file for the first Bored Ape:
+
+```json
+{
+ "image": "ipfs://QmPbxeGcXhYQQNgsC6a36dDyYUcHgMLnGKnF8pVFmGsvqi",
+ "attributes": [
+ {
+ "trait_type": "Mouth",
+ "value": "Grin"
+ },
+ {
+ "trait_type": "Clothes",
+ "value": "Vietnam Jacket"
+ },
+ {
+ "trait_type": "Background",
+ "value": "Orange"
+ },
+ {
+ "trait_type": "Eyes",
+ "value": "Blue Beams"
+ },
+ {
+ "trait_type": "Fur",
+ "value": "Robot"
+ }
+ ]
+}
+```
+
+IPFS links don't work natively directly in the browser, but you can see the image here:
+
+https://ipfs.io/ipfs/QmPbxeGcXhYQQNgsC6a36dDyYUcHgMLnGKnF8pVFmGsvqi/
+
+Now, call your `switchURI` function and then call `tokenURI` again for token 1.
+
+Now, you'll get a new link for metadata:
+
+```text
+https://ipfs.io/ipfs/QmPMc4tcBsMqLRuCQtPmPe84bpSjrC3Ky7t3JWuHXYB4aS/1
+```
+
+Which contains the metadata for Doodle 1 instead of BAYC 1:
+
+```json
+{
+ "image": "ipfs://QmTDxnzcvj2p3xBrKcGv1wxoyhAn2yzCQnZZ9LmFjReuH9",
+ "name": "Doodle #1",
+ "description": "A community-driven collectibles project featuring art by Burnt Toast. Doodles come in a joyful range of colors, traits and sizes with a collection size of 10,000. Each Doodle allows its owner to vote for experiences and activations paid for by the Doodles Community Treasury. Burnt Toast is the working alias for Scott Martin, a Canadian\u2013based illustrator, designer, animator and muralist.",
+ "attributes": [
+ {
+ "trait_type": "face",
+ "value": "holographic beard"
+ },
+ {
+ "trait_type": "hair",
+ "value": "white bucket cap"
+ },
+ {
+ "trait_type": "body",
+ "value": "purple sweater with satchel"
+ },
+ {
+ "trait_type": "background",
+ "value": "grey"
+ },
+ {
+ "trait_type": "head",
+ "value": "gradient 2"
+ }
+ ]
+}
+```
+
+Your robot ape is now a person with a rainbow beard!
+
+https://ipfs.io/ipfs/QmTDxnzcvj2p3xBrKcGv1wxoyhAn2yzCQnZZ9LmFjReuH9
+
+---
+
+## Conclusion
+
+In this lesson, you've learned how to use OpenZeppelin's ERC-721 implementation to create your own NFT contract. You've also learned how NFT metadata is stored, and that it is not necessarily immutable.
+
+---
+
+[OpenZeppelin]: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol
+[Coinbase NFT]: https://nft.coinbase.com/
+[URI]: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier
+[stored on IPFS]: https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
+[BAYC]: https://nft.coinbase.com/collection/ethereum/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d
+[CryptoPunks]: https://nft.coinbase.com/collection/ethereum/0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb
+[Doodles]: https://nft.coinbase.com/collection/ethereum/0x8a90cab2b38dba80c64b7734e58ee1db38b8992e
+[uses IPFS]: https://ipfs.io/ipfs/QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
diff --git a/docs/learn/erc-721-token/erc-721-standard-video.mdx b/docs/learn/erc-721-token/erc-721-standard-video.mdx
new file mode 100644
index 00000000..6b5ea85a
--- /dev/null
+++ b/docs/learn/erc-721-token/erc-721-standard-video.mdx
@@ -0,0 +1,10 @@
+---
+title: ERC-721 Token Standard
+description: Review the formal standard for the ERC-721 Token.
+hide_table_of_contents: false
+sidebarTitle: ERC-721 Standard
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/erc-721-token/erc-721-standard.mdx b/docs/learn/erc-721-token/erc-721-standard.mdx
new file mode 100644
index 00000000..9b0802a9
--- /dev/null
+++ b/docs/learn/erc-721-token/erc-721-standard.mdx
@@ -0,0 +1,100 @@
+---
+title: The ERC-721 Token Standard
+description: An overview of the ERC-721 token standard
+hide_table_of_contents: false
+sidebarTitle: Standard Overview
+---
+
+In this article, we'll delve into the ERC-721 token standard, exploring its technical specs, applications, and how it differs from the ERC-20 standard.
+
+---
+
+## Objectives:
+
+By the end of this lesson you should be able to:
+
+- Analyze the anatomy of an ERC-721 token
+- Compare and contrast the technical specifications of ERC-20 and ERC-721
+- Review the formal specification for ERC-721
+
+---
+
+## Introduction
+
+The development of the Ethereum ecosystem has been marked by key milestones, two of which are the inception of the ERC-20 and ERC-721 token standards. While ERC-20 provided a foundational framework for fungible tokens, ERC-721 established a flexible and adaptable infrastructure for non-fungible tokens (NFTs).
+
+The ERC-721 token standard is pivotal in the Ethereum ecosystem for creating and managing unique digital assets. With its consistent rules and functions, it has greatly enhanced the user experience, solidifying its position as the go-to standard for non-fungible tokens. ERC-721 has been instrumental in expanding the digital collectibles market and spurring the development of new applications and services.
+
+
+
+
+
+---
+
+## ERC-721 Specification
+
+EIP-721 (Ethereum Improvement Proposal 721) is the formal specification for ERC-721, defining the requirements for creating compliant non-fungible tokens on Ethereum. EIP-721 prescribes mandatory functions and events that a token must implement to achieve ERC-721 compliance. Adherence to EIP-721 ensures compatibility of unique tokens with existing Ethereum applications and services, simplifying integration.
+
+---
+
+## Anatomy of an ERC-721 Token
+
+An ERC-721 token comprises a smart contract implementing the standardized interface, which includes six primary functions:
+
+- **balanceOf(address)** Returns the number of tokens held by a specific address.
+- **ownerOf(uint256):** Provides the owner of a specified token.
+- **safeTransferFrom(address, address, uint256):** Transfers a specific token's ownership from one address to another.
+- **transferFrom(address, address, uint256):** Allows a third party to transfer tokens on the token owner's behalf, given the owner's approval.
+- **approve(address, uint256):** Enables the token owner to permit a third party to transfer a specific token on their behalf.
+- **getApproved(uint256):** Shows the approved address for a specific token.
+
+These functions ensure each ERC-721 token has a unique identifier and can be owned and transferred individually.
+
+---
+
+## ERC-721 Vs ERC-20
+
+The ERC-721 and ERC-20 token standards share a common goal of providing a set of standards for tokens on the Ethereum network but diverge in terms of functionality and use cases.
+
+ERC-20 tokens are fungible, meaning each token is identical to every other token; they are interchangeable like currency. On the other hand, ERC-721 tokens are non-fungible, meaning each token is unique and not interchangeable with any other token. This uniqueness is made possible through the ownerOf() and getApproved() functions, which provide information about the ownership of each unique token.
+
+The ERC-20 standard has primarily found use in creating cryptocurrencies for apps, governance tokens, utility tokens, stablecoins, and more. The ERC-721 standard, conversely, has been adopted largely for creating unique digital assets like collectibles, digital art, and tokenized virtual real estate, among other applications.
+
+---
+
+## Benefits of ERC-721 Standardization
+
+Standardizing non-fungible tokens via the ERC-721 token standard presents substantial benefits to developers and users in the Ethereum ecosystem. Developers have access to a standardized set of functions, leading to less code ambiguity, fewer errors, and a streamlined development process. This uniformity also ensures smooth integration with existing apps and platforms on Ethereum.
+
+For users, the ERC-721 standard offers an intuitive, consistent interface for interacting with a wide array of unique tokens. Regardless of the token's specific use or design, users can reliably check their ownership of tokens, transfer tokens to other addresses, and approve transactions. This consistency enhances usability across the Ethereum platform, from digital art marketplaces to tokenized real estate and gaming applications.
+
+
+
+
+
+---
+
+## Applications
+
+ERC-721 tokens find wide-ranging applications in various categories:
+
+- **Digital Art:** Artists can create unique digital artworks as ERC-721 tokens. These tokens can be sold or traded on platforms like OpenSea, Rarible, and Coinbase NFT. Examples include work by the digital artist Beeple.
+
+- **Gaming:** Game assets such as characters, items, and land can be tokenized as ERC-721 tokens, providing players with true ownership of their in-game assets. Examples include Axie Infinity and Decentraland.
+
+- **Collectibles:** ERC-721 tokens can represent unique collectible items in a digital space. Examples include NBA Top Shot moments and CryptoPunks.
+
+- **Virtual Real Estate:** Virtual real estate can be tokenized as ERC-721 tokens, providing proof of ownership and facilitating trade on virtual platforms. Examples include parcels of land in Cryptovoxels and Decentraland.
+
+---
+
+## Conclusion
+
+ERC-721, with its consistent framework for non-fungible tokens, has revolutionized the unique digital asset space on Ethereum. This standard, when contrasted with ERC-20, highlights Ethereum's capacity for both fungible and unique asset types. Adhering to the EIP-721 specification, ERC-721 tokens have significantly influenced the Ethereum-based digital economy. From digital art to gaming, these tokens underscore their importance and role as catalysts in the burgeoning NFT revolution.
+
+---
+
+## See Also
+
+- [EIP-721: ERC-721 Token Standard](https://eips.ethereum.org/EIPS/eip-721)
+- [ERC-721 Token Standard](https://ethereum.org/en/developers/docs/standards/tokens/erc-721/)
diff --git a/docs/learn/erc-721-token/implementing-an-erc-721-vid.mdx b/docs/learn/erc-721-token/implementing-an-erc-721-vid.mdx
new file mode 100644
index 00000000..ddf84902
--- /dev/null
+++ b/docs/learn/erc-721-token/implementing-an-erc-721-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Implementing an ERC-721
+description: Deploy your own NFT.
+hide_table_of_contents: false
+sidebarTitle: Implementation Guide
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/erc-721-token/openzeppelin-erc-721-vid.mdx b/docs/learn/erc-721-token/openzeppelin-erc-721-vid.mdx
new file mode 100644
index 00000000..ffa4c0f6
--- /dev/null
+++ b/docs/learn/erc-721-token/openzeppelin-erc-721-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: OpenZeppelin ERC-721 Implementation
+description: Review the ERC-721 implementation by OpenZeppelin.
+hide_table_of_contents: false
+sidebarTitle: OpenZeppelin ERC-721
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/error-triage/error-triage-exercise-source.sol b/docs/learn/error-triage/error-triage-exercise-source.sol
new file mode 100644
index 00000000..2544a99e
--- /dev/null
+++ b/docs/learn/error-triage/error-triage-exercise-source.sol
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.17;
+
+contract ErrorTriageExercise {
+ /**
+ * Finds the difference between each uint with it's neighbor (a to b, b to c, etc.)
+ * and returns a uint array with the absolute integer difference of each pairing.
+ */
+ function diffWithNeighbor(
+ uint _a,
+ uint _b,
+ uint _c,
+ uint _d
+ ) public pure returns (uint[] memory) {
+ uint[] memory results = new uint[](3);
+
+ results[0] = _a - _b;
+ results[1] = _b - _c;
+ results[2] = _c - _d;
+
+ return results;
+ }
+
+ /**
+ * Changes the _base by the value of _modifier. Base is always >= 1000. Modifiers can be
+ * between positive and negative 100;
+ */
+ function applyModifier(
+ uint _base,
+ int _modifier
+ ) public pure returns (uint) {
+ return _base + _modifier;
+ }
+
+ /**
+ * Pop the last element from the supplied array, and return the popped
+ * value (unlike the built-in function)
+ */
+ uint[] arr;
+
+ function popWithReturn() public returns (uint) {
+ uint index = arr.length - 1;
+ delete arr[index];
+ return arr[index];
+ }
+
+ // The utility functions below are working as expected
+ function addToArr(uint _num) public {
+ arr.push(_num);
+ }
+
+ function getArr() public view returns (uint[] memory) {
+ return arr;
+ }
+
+ function resetArr() public {
+ delete arr;
+ }
+}
diff --git a/docs/learn/error-triage/error-triage-exercise.mdx b/docs/learn/error-triage/error-triage-exercise.mdx
new file mode 100644
index 00000000..abba699d
--- /dev/null
+++ b/docs/learn/error-triage/error-triage-exercise.mdx
@@ -0,0 +1,97 @@
+---
+sidebarTitle: Exercise
+title: Error Triage Exercise
+description: Exercise - Demonstrate your debugging skill.
+hide_table_of_contents: false
+---
+
+Copy the starter code into a new file in Remix.
+
+Debug the existing functions in the provided contract.
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.17;
+
+contract ErrorTriageExercise {
+ /**
+ * Finds the difference between each uint with it's neighbor (a to b, b to c, etc.)
+ * and returns a uint array with the absolute integer difference of each pairing.
+ */
+ function diffWithNeighbor(
+ uint _a,
+ uint _b,
+ uint _c,
+ uint _d
+ ) public pure returns (uint[] memory) {
+ uint[] memory results = new uint[](3);
+
+ results[0] = _a - _b;
+ results[1] = _b - _c;
+ results[2] = _c - _d;
+
+ return results;
+ }
+
+ /**
+ * Changes the _base by the value of _modifier. Base is always >= 1000. Modifiers can be
+ * between positive and negative 100;
+ */
+ function applyModifier(
+ uint _base,
+ int _modifier
+ ) public pure returns (uint) {
+ return _base + _modifier;
+ }
+
+ /**
+ * Pop the last element from the supplied array, and return the popped
+ * value (unlike the built-in function)
+ */
+ uint[] arr;
+
+ function popWithReturn() public returns (uint) {
+ uint index = arr.length - 1;
+ delete arr[index];
+ return arr[index];
+ }
+
+ // The utility functions below are working as expected
+ function addToArr(uint _num) public {
+ arr.push(_num);
+ }
+
+ function getArr() public view returns (uint[] memory) {
+ return arr;
+ }
+
+ function resetArr() public {
+ delete arr;
+ }
+}
+
+```
+
+---
+
+## Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+{/* */}
+
+
diff --git a/docs/learn/error-triage/error-triage-vid.mdx b/docs/learn/error-triage/error-triage-vid.mdx
new file mode 100644
index 00000000..c8435b89
--- /dev/null
+++ b/docs/learn/error-triage/error-triage-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Error Triage
+description: Learn to debug common errors.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/error-triage/error-triage.mdx b/docs/learn/error-triage/error-triage.mdx
new file mode 100644
index 00000000..590ff7d9
--- /dev/null
+++ b/docs/learn/error-triage/error-triage.mdx
@@ -0,0 +1,449 @@
+---
+title: Error Triage
+sidebarTitle: Error Guide
+description: Learn how to identify and resolve common errors in Solidity.
+hide_table_of_contents: false
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Debug common solidity errors including transaction reverted, out of gas, stack overflow, value overflow/underflow, index out of range, etc.
+
+---
+
+## Compiler Errors
+
+Compiler errors are manifold but almost always very easy to debug, since the error message usually tells you what is wrong and how to fix it.
+
+### Type Errors
+
+You will get a compiler error if you try to assign a literal to the wrong type.
+
+```solidity
+// Bad code example, do not use
+function compilerTypeError() public pure returns (uint) {
+ uint myNumber = "One";
+ return myNumber;
+}
+```
+
+```text
+from solidity:
+TypeError: Type literal_string "One" is not implicitly convertible to expected type uint256.
+ --> contracts/ErrorTriage.sol:8:9:
+ |
+8 | uint myNumber = "One";
+ | ^^^^^^^^^^^^^^^^^^^^^
+```
+
+Fix by correcting the type or value, as appropriate for your needs:
+
+
+
+
+```solidity
+function compilerTypeErrorFixed() public pure returns (string) {
+ string myNumber = "One";
+ return myNumber;
+}
+```
+
+
+
+### Conversion Errors
+
+Conversion errors occur when you attempt to _implicitly_ convert one type to another. Solidity only allows this under very narrow circumstances where there is no possibility of ambiguous interpretation of the data.
+
+```solidity
+// Bad code example, do not use
+function compilerConversionError() public pure returns (uint) {
+ int8 first = 1;
+
+ return first;
+}
+```
+
+```text
+from solidity:
+TypeError: Return argument type int8 is not implicitly convertible to expected type (type of first return variable) uint256.
+ --> contracts/ErrorTriage.sol:15:16:
+ |
+15 | return first;
+ | ^^^^^
+```
+
+Fix by explicitly casting as necessary:
+
+
+
+
+```solidity
+function compilerConversionErrorFixed() public pure returns (uint) {
+ int8 first = 1;
+
+ return uint(uint8(first));
+}
+```
+
+
+
+
+You'll commonly need to use multiple conversions to bridge from one type to another.
+
+
+
+### Operator Errors
+
+You cannot use operators between types as flexibly as you may be used to.
+
+```solidity
+// Bad code example, do not use
+function compilerOperatorError() public pure returns (uint) {
+ int8 first = 1;
+ uint256 second = 2;
+
+ uint sum = first + second;
+
+ return sum;
+}
+```
+
+Operator errors are often paired with a type error.
+
+```text
+from solidity:
+TypeError: Operator + not compatible with types int8 and uint256.
+ --> contracts/ErrorTriage.sol:22:20:
+ |
+22 | uint sum = first + second;
+ | ^^^^^^^^^^^^^^
+
+from solidity:
+TypeError: Type int8 is not implicitly convertible to expected type uint256.
+ --> contracts/ErrorTriage.sol:22:9:
+ |
+22 | uint sum = first + second;
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^
+```
+
+Resolve by explicitly converting to the final type:
+
+
+
+
+```
+function compilerOperatorErrorFixed() public pure returns (uint) {
+ int8 first = 1;
+ uint256 second = 2;
+
+ uint sum = uint(uint8(first)) + second;
+
+ return sum;
+}
+```
+
+
+
+### Stack Depth Limit
+
+The [EVM stack] has 1024 slots, but only the top 16 slots are accessible. As a result, you can only have fewer than 16 variables in scope at one time.
+
+
+Other items can also use up these slots. You are **not** guaranteed 15 slots, it can be lower.
+
+
+
+```solidity
+// Bad code example, do not use
+function stackDepthLimit() public pure returns (uint) {
+ uint first = 1;
+ uint second = 2;
+ uint third = 3;
+ uint fourth = 4;
+ uint fifth = 5;
+ uint sixth = 6;
+ uint seventh = 7;
+ uint eighth = 8;
+ uint ninth = 9;
+ uint tenth = 10;
+ uint eleventh = 11;
+ uint twelfth = 12;
+ uint thirteenth = 13;
+ uint fourteenth = 14;
+ uint fifteenth = 15;
+ uint sixteenth = 16;
+
+ return first +
+ second +
+ third +
+ fourth +
+ fifth +
+ sixth +
+ seventh +
+ eighth +
+ ninth +
+ tenth +
+ eleventh +
+ twelfth +
+ thirteenth +
+ fourteenth +
+ fifteenth +
+ sixteenth;
+ }
+```
+
+```text
+from solidity:
+CompilerError: Stack too deep. Try compiling with --via-ir (cli) or the equivalent viaIR: true (standard JSON) while enabling the optimizer. Otherwise, try removing local variables.
+ --> contracts/ErrorTriage.sol:92:17:
+ |
+92 | eighth +
+ | ^^^^^^
+```
+
+Resolve this error by breaking up large functions and separating operations into different levels of scope.
+
+
+
+
+```solidity
+function stackDepthLimitFixed() public pure returns (uint) {
+ uint subtotalA;
+ {
+ uint first = 1;
+ uint second = 2;
+ uint third = 3;
+ uint fourth = 4;
+ uint fifth = 5;
+ uint sixth = 6;
+ uint seventh = 7;
+ uint eighth = 8;
+ subtotalA = first +
+ second +
+ third +
+ fourth +
+ fifth +
+ sixth +
+ seventh +
+ eighth;
+ }
+
+ uint subtotalB;
+ {
+ uint ninth = 9;
+ uint tenth = 10;
+ uint eleventh = 11;
+ uint twelfth = 12;
+ uint thirteenth = 13;
+ uint fourteenth = 14;
+ uint fifteenth = 15;
+ uint sixteenth = 16;
+ subtotalB = ninth +
+ tenth +
+ eleventh +
+ twelfth +
+ thirteenth +
+ fourteenth +
+ fifteenth +
+ sixteenth;
+ }
+
+ return subtotalA + subtotalB;
+}
+```
+
+
+
+---
+
+## Logical Errors
+
+Logical errors occur when your code is syntactically correct, but still results in a data state that is a violation of the rules of the language.
+
+A [panic] occurs when your code tries to do an illegal operation. These return with a very basic error code, which Remix unfortunately hides. However, it makes up for that annoyance by providing a very powerful debugger.
+
+
+The Remix VM doesn't behave exactly the same as true onchain operations, so note that these errors will not behave exactly the same if triggered while testing with Hardhat, or called from a front end.
+
+caution
+
+For each of these examples, copy them into Remix to explore with the debugger on your own.
+
+### Array Index Out-of-Bounds
+
+A panic will be triggered if you try to access an array at an invalid index.
+
+```solidity
+// Bad code example, do not use
+function badGetLastValue() public pure returns (uint) {
+ uint[4] memory arr = [uint(1), 2, 3, 4];
+
+ return arr[arr.length];
+}
+```
+
+Running this function will result in the following error in the console:
+
+```text
+call to ErrorTriage.badGetLastValue errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Note: The called function should be payable if you send value and the value you send should be less than your current balance.
+Debug the transaction to get more information.
+```
+
+Click the _Debug_ button to open the debugger.
+
+
+
+
+
+The debugger contains panels with information about variables in storage, memory, what's on the stack, and so on. You can also add breakpoints to lines of code to further help with debugging.
+
+One of the most useful features is the link near the top instructing you to _"Click here to jump where the call reverted."_
+
+Click that link and the debugger will jump to the point of failure, **and highlight the code that caused the panic.** Neat!
+
+
+
+
+
+You can find the specific error here, but it's difficult.
+
+Look in the _Memory_ panel. The first item at `0x0` has a hash starting with `0x4e487b71`. This code indicates a panic.
+
+The second item, at `0x20` has the error code of `32` hidden in it, which is for array out-of-bounds.
+
+
+
+
+
+It's sometimes better to just review the code first to see if the error is obvious.
+
+```solidity
+function badGetLastValueFixed() public pure returns (uint) {
+ uint[4] memory arr = [uint(1), 2, 3, 4];
+
+ return arr[arr.length-1];
+}
+```
+
+### Out of Gas
+
+The default settings for Remix make it difficult to trigger an out of gas error because the VM will often crash first. For this example, go to the _Deploy & Run Transactions_ tab and reduce the gas limit to **300000**.
+
+If you write code that can have an ambiguous execution time, it becomes very difficult to accurately estimate gas limits.
+
+In this example, each loop has a 1 in 1000 chance of ending.
+
+
+`block.timestamp` can be manipulated. **DO NOT** use this as a source of randomness if any value can be derived from one outcome over another!
+
+
+
+```solidity
+// Bad code example, do not use
+function badRandomLoop() public view returns (uint) {
+ uint seed = 0;
+ // DO NOT USE THIS METHOD FOR RANDOM NUMBERS!!! IT IS EASILY EXPLOITABLE!!!
+ while(uint(keccak256(abi.encodePacked(block.timestamp, seed))) % 1000 != 0) {
+ seed++;
+ // ...do something
+ }
+
+ return seed;
+}
+```
+
+Run this function a few times. Often, it will work just fine. Other times, an error appears:
+
+```text
+call to ErrorTriage.badLoop errored: VM error: out of gas.
+
+out of gas
+ The transaction ran out of gas. Please increase the Gas Limit.
+
+Debug the transaction to get more information.
+```
+
+The error message here is a bit misleading. You do **not** usually want to fix this by increasing the gas limit. If you're getting a gas error because the transaction didn't estimate for enough gas, it's better to refactor for better predictability.
+
+```solidity
+function badRandomLoopFixed() public view returns (uint) {
+ // DO NOT USE THIS METHOD FOR RANDOM NUMBERS!!! IT IS EASILY EXPLOITABLE!!!
+ uint times = uint(keccak256(abi.encodePacked(block.timestamp))) % 1000;
+
+ for(uint i = 0; i <= times; i++) {
+ // ...do something
+ }
+
+ return times;
+}
+```
+
+### Overflow or Underflow
+
+The `uint` type will _panic_ in the event of an overflow or underflow.
+
+```solidity
+function badSubtraction() public pure returns (uint) {
+ uint first = 1;
+ uint second = 2;
+ return first - second;
+}
+```
+
+As before, you can see the panic code and panic type in _memory_.
+
+
+
+
+
+In this case, the error type is `11`, for overflow/underflow outside of an `unchecked` block.
+
+Fix by changing your code to handle the expected range of values.
+
+
+
+
+```solidity
+function badSubstractionFixed() public pure returns (int) {
+ int first = 1;
+ int second = 2;
+ return first - second;
+}
+```
+
+
+
+### Divide by Zero
+
+Divide by zero errors also trigger a panic, with a code of `12`.
+
+```solidity
+function badDivision() public pure returns (uint) {
+ uint first = 1;
+ uint second = 0;
+ return first / second;
+}
+```
+
+
+
+
+
+Don't divide by zero.
+
+---
+
+## Conclusion
+
+In this lesson, you reviewed the causes of and solutions for a number of compiler errors and logical errors that you may encounter.
+
+---
+
+[panic]: https://docs.soliditylang.org/en/v0.8.17/control-structures.html?#panic-via-assert-and-error-via-require
+[EVM stack]: https://docs.soliditylang.org/en/v0.8.17/introduction-to-smart-contracts.html#storage-memory-and-the-stack
diff --git a/docs/learn/ethereum-applications.mdx b/docs/learn/ethereum-applications.mdx
new file mode 100644
index 00000000..8e76c336
--- /dev/null
+++ b/docs/learn/ethereum-applications.mdx
@@ -0,0 +1,193 @@
+---
+title: Ethereum Applications
+description: An overview of the development ethos of Ethereum, applications built on its network, and a high-level comparison of Web2 and Web3
+---
+
+In this article, we'll explore Ethereum's significance and impact in the crypto ecosystem as well as its role in shaping the Web3 landscape. We'll learn about Ethereum's ethos and goals and also examine the different types of applications developed on Ethereum. Lastly, we'll take a look at the evolution of the web with an emphasis on comparing Web2 and Web3 development.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Describe the origin and goals of the Ethereum blockchain
+- List common types of applications that can be developed with the Ethereum blockchain
+- Compare and contrast Web2 vs. Web3 development
+- Compare and contrast the concept of "ownership" in Web2 vs. Web3
+
+---
+
+## The Ethos and Goals of Ethereum
+
+Ethereum was originally proposed in 2013 by Vitalik Buterin, who was then a young developer in the Bitcoin community. Vitalik had a vision that the potential of blockchain technology extended far beyond a decentralized digital currency. When his ideas were rejected by the Bitcoin community, he set out to create a platform that could bring his vision to life.
+
+The ethos of Ethereum is fundamentally different from Bitcoin's. Bitcoin development is conservative; it's focused on maintaining the existing protocol, making only incremental improvements over time rather than implementing radical changes. In other words, changes are slow and deliberate and any unnecessary risk-taking is generally frowned upon. Ethereum development, on the other hand, is focused on innovation and experimentation. There is more of a willingness to take risks and make radical changes to the protocol in order to improve on and expand upon functionality and enable new use cases.
+
+Ethereum's primary goal is to be a general, all-purpose blockchain that allows developers to create any type of decentralized application that their minds can conjure up. One of the most important features that unlock all of these possibilities is smart contracts. Without smart contract functionality, most applications built on the platform today would be nonexistent.
+
+
+
+
+
+---
+
+## Applications on Ethereum
+
+Before delving into the different types of applications built on Ethereum, let's review one of the underlying forces of the smart contracts that make them possible.
+
+### Scripting
+
+As we've learned, one of Bitcoin's limitations when it comes to complex applications being built on the protocol is scripting. Its simple stack-based, left-to-right scripting system lacks the flexibility to support smart contracts. In the Ethereum whitepaper, Vitalik pointed out several limitations to Bitcoin scripting. A couple of key ones to note are:
+
+- Lack of Turing-completeness. Although this is an intentional feature of Bitcoin to avoid infinite loops during transaction verification, without loops or recursion, running a complex program that a decentralized application demands, is not possible.
+
+- Lack of state. Bitcoin's UTXO model only allows for simple contracts and not the complex stateful contracts needed for most decentralized applications. What that means is that there is no internal state beyond a UTXO being spent or unspent.
+
+Ethereum's scripting languages, most notably Solidity, are Turing-complete and stateful, among other features. These features allow smart contracts to be executed deterministically, meaning that the outcome of the contract is predictable and can be enforced automatically and autonomously. They also allow developers to write much more complex programs that can execute a much wider range of operations.
+
+It is this flexibility and versatility in Ethereum's scripting that ultimately powers the decentralized applications that we know.
+
+### Decentralized Finance (DeFi)
+
+DeFi is one of the most popular use cases for Ethereum. In DeFi Summer 2020, we saw an explosion in the usage and popularity of DeFi applications built on Ethereum. During this brief period alone, the total value locked in DeFi protocols increased from less than $1 billion to more than $10 billion. This period marked a turning point, as it brought more attention to the space, and it solidified Ethereum as the de facto smart contract platform of the DeFi and greater Web3 ecosystem.
+
+DeFi applications are designed to provide traditional financial services, such as lending, borrowing, trading, and much more, in a transparent, open and accessible manner. All of these services are facilitated by smart contracts.
+
+How exactly does this work? Let's take a look at a simple example in the context of a DeFi lending platform, such as Aave or Compound.
+
+Suppose Alice wants to borrow 5 ETH but doesn't want to sell her 10,000 USDC. She can deposit that USDC as collateral and borrow 5 ETH against it.
+
+First, Alice must interact with the smart contract of the platform. The smart contract will check if Alice has 10,000 USDC in her wallet and, if so, will lock it up as collateral for the loan. The smart contract then transfers 5 ETH to Alice's wallet. The smart contract will also define the terms of the loan, such as the interest rate and the repayment date.
+
+Alice now has 5 ETH that she can use for whatever she wants. However, she must repay the loan within the specified period. If Alice repays the loan on time, the smart contract will release her deposited collateral back to her. Otherwise, it will automatically liquidate her collateral and transfer it to the lender's address in exchange for the 5 ETH that was lent to her. The repayment will also include any accrued interest based on the interest rate set by the smart contract.
+
+In this way, smart contracts enable DeFi platforms to operate autonomously without a centralized entity. The smart contract provides security and transparency to both the borrower and the lender, as the terms of the loan are defined in the code and enforced automatically.
+
+Of course, this does not mean that DeFi comes without risks. Although it's beyond the scope of this article, it's worth mentioning that DeFi has been the target of numerous smart contract exploits involving hundreds of millions of dollars in value.
+
+### Non-Fungible Token (NFT)
+
+NFTs are another application of Ethereum that has gained significant attention in recent years. NFTs are unique digital assets that represent ownership of a specific item. They can be used to represent just about anything, but most notably digital artwork, sports collectibles, and in-game items.
+
+Smart contracts play a crucial role in NFTs by providing a way to represent and enforce ownership of the digital asset. When a new NFT is created, a smart contract is deployed on Ethereum that defines the unique characteristics of that asset as well as the ownership information and rules for transferring ownership.
+
+Ethereum's ERC-721 standard was the first to introduce NFTs in 2017, and it has since become the most popular standard for creating and trading NFTs on Ethereum.
+
+We'll cover more on tokens and token standards for fungible and non-fungible assets later on in the course.
+
+### Decentralized Autonomous Organization (DAO)
+
+DAOs are another common use case for Ethereum and also one of the earliest use cases implemented on the network. In simple words, DAOs are software-enabled, community-led organizations. They allow a community to pool resources toward a shared goal, such as [buying one of the original copies of the U.S. Constitution](https://www.theverge.com/22820563/constitution-meme-47-million-crypto-crowdfunding-blockchain-ethereum-constitution) or determining the future of a protocol. Because DAOs aren't tied to a physical location, they are able to mobilize quickly and
+attract resources and capital from all over the world.
+
+The rules of a DAO are established by community members through the use of smart contracts, which lay the groundwork for how a DAO operates. When a new DAO is created, a contract is deployed to the network. It contains the rules that govern the organization, including how its resources are managed. Members of the DAO can then interact with the contract by sending transactions to the blockchain.
+
+DAOs typically use a token-based system to govern voting and decision-making. Members of the DAO are issued tokens that represent their ownership and influence within the organization. These tokens can be used to vote on proposals and allocate resources.
+
+When a proposal is submitted to the DAO, members can vote on whether to accept or reject it. The smart contract tracks the votes and automatically executes the proposal if it receives enough support from the members. This process allows members of the DAO to collectively make decisions and take actions in a decentralized way.
+
+### Other Applications
+
+While the above use cases have been the most prominent applications on Ethereum, there are a plethora of others, including:
+
+- **Identity Management** is one use case that has come to the forefront in recent years. The most notable example is the Ethereum Name Service (ENS). It allows users to register human-readable domain names, similar to the traditional DNS system, but with the added functionality of being able to associate Ethereum and other blockchain addresses with a domain name, such as `vitalik.eth`. This makes it easier for users to send and receive transactions without having to remember or type in long and complex addresses.
+
+- **Gaming** is another common use case. Axie Infinity and Decentraland are both popular examples of decentralized games that make use of fungible and non-fungible tokens for a variety of purposes.
+
+- **Prediction markets** are another use case where users can bet on the outcome of real-world events, such as forecasting election results or predicting which team will win a game. Augur and Gnosis are popular examples of this application.
+
+There are many other use cases from supply chain, energy, and intellectual property management to decentralized storage and content management to governance and voting systems. The list goes on and on, and it's worth taking some time to explore these types of applications on your own.
+
+---
+
+## Evolution of the Web
+
+Opinions abound in the history, divisions, and eras of the development and evolution of the internet. One popular explanation divides the web into three major eras (so far).
+
+#### Web1
+
+The web has come a long way since its humble read-only beginnings in the early 1990s. Web1 is often referred to as the _static web_, meaning it was primarily a collection of static web pages that provided information to users with limited interaction and dynamic content.
+
+#### Web2
+
+In the early 2000s, a _dynamic web_ emerged. Web2 introduced more interactive and dynamic content, such as social media and e-commerce. It's characterized by the use of centralized servers that store and control user data. In other words, with Web2, users can interact with content, share information, and collaborate with others, but they have limited control over their data. A vast majority of the web today operates in this paradigm.
+
+#### Web3
+
+Web3 or the _decentralized web_ is the next phase of the web that has started to emerge in recent years with the rise in popularity of Ethereum. This iteration of the web is focused on user ownership and control over data, providing a more private, decentralized, and secure web experience.
+
+### The Limitations of Web2
+
+While Web2 brought many benefits beyond its predecessor, there are some key limitations to consider.
+
+- **Privacy & Control:** Users have limited control over their data and how it is used. Companies often have broad or even complete control over user data on a given platform.
+
+- **Censorship:** Due to centralized control of user data, corporations or governments can censor content they deem as inappropriate or dangerous, which can limit free speech and expression or block access to certain online services. This is especially concerning in countries with authoritarian regimes, which often use censorship as a tool to control citizens and maintain power.
+
+- **Lack of transparency:** Users cannot always verify how their data is being used or who has access to it.
+
+- **Security vulnerabilities:** Because Web2 relies on centralized servers, it is more vulnerable to hacking and data breaches, which can expose sensitive user information and compromise online safety.
+
+- **Limited interoperability:** Most Web2 platforms are not interoperable, meaning that different platforms may not be compatible with each other. Data is generally confined to one system.
+
+### The Limitations of Web3
+
+While many of the limitations of Web2 have spurred the development of Web3 by aiming to provide a more decentralized, secure, and private web, Web3 does not come without its own set of limitations.
+
+- **Speed:** The reliance on decentralized networks and consensus mechanisms result in much slower processing times compared to centralized systems.
+
+- **Storage:** Storing data onchain can be very expensive, which can make it challenging for developers to create apps that require large amounts of storage.
+
+- **Smart contract limitations:**
+
+ - Smart contracts on Ethereum are currently limited to a maximum size of 24 KB, which can limit the complexity of the logic that can be programmed into them.
+
+ - Once a smart contract is deployed, it cannot be updated or changed. If there is a bug or a flaw in the contract, it cannot be fixed unless a new contract is deployed.
+
+- **All data is public:** The transparency of blockchain means that all data is public and visible to anyone. While this can be an advantage in terms of transparency and accountability, it can also be a limitation for applications that may require privacy or confidentiality.
+
+
+
+
+
+### Web2 vs Web3 Development
+
+While there are many general distinctions to be made between Web2 and Web3, these characteristics are even more apparent when examining their development approaches.
+
+#### Web2
+
+In Web2, engineering is centered around a client-server architecture, and development is focused on building applications for specific platforms and using APIs and tools provided by those platforms to create user interfaces and access data. There is a top-down corporate approach to development processes, and code is generally proprietary and closed-sourced. As a result, there tends to be very limited collaboration with developers outside of a company and there is little integration between different platforms.
+
+Web2 developers rely on centralized infrastructure, such as servers and cloud-computing services provided by large tech companies to host their applications and store data. This creates a centralized system where the platforms and companies that control the infrastructure have significant power and control over the applications and data that are built on top of them.
+
+#### Web3
+
+In contrast, the Web3 development paradigm is centered around a distributed architecture, where developers build applications that run on decentralized protocols and smart contracts. There is a bottom-up community approach to development processes, and there is an emphasis on open-source code and open standards. Web3 development culture is collaborative, and there is strong integration and interoperability between platforms.
+
+Web3 development requires a different set of engineering skills and tools. Developers need to have a strong understanding of blockchain technology, cryptography, and distributed systems, and they also need to be proficient in programming languages like Solidity.
+
+There is also a different approach to testing and deployment. Because onchain apps run on distributed systems, developers need to consider factors like network latency and the possibility of network partitions. They also need to ensure that their applications are extremely secure and resistant to a variety of attacks because the stakes can often be very high when it comes to dealing with millions and even billions of dollars of value that cannot be recovered in the event of a hack. Developers also have to consider concepts like immutability because once code is deployed to a blockchain, it cannot be edited.
+
+Overall, Web3 development requires a different set of engineering skills and tools as well as a deeper understanding of distributed systems and cryptography. Developers also need the ability to think creatively about how to build applications that are constrained by the technical limitations of the Web3 paradigm, such as speed, storage, and scalability.
+
+
+
+
+
+---
+
+## Conclusion
+
+Ethereum was created to extend the potential of blockchain technology beyond just a decentralized digital currency platform. Its ethos of innovation and experimentation has made a major impact on shaping the crypto ecosystem and has played a significant role in shaping the landscape of Web3. The use of smart contracts has enabled a wide range of new web applications, including DeFi, NFTs, DAOs, and many more.
+
+The evolution of the web has brought us from a static Web1 to a dynamic Web2, and now to a decentralized Web3. While Web2 brought many key benefits, it also came with many drawbacks regarding privacy, censorship, and security vulnerabilities. Web3 aims to address these challenges but has its own set of limitations. Lastly, the development paradigms of Web2 and Web3 are distinct in their architecture, infrastructure, and their development approaches. Web3 development requires a different set of skills and a different mental framework from its predecessor.
+
+---
+
+## See also
+
+- [Ethereum Whitepaper](https://ethereum.org/en/whitepaper/)
+- [Decentralized Applications (Dapps)](https://ethereum.org/en/dapps/)
+- [Web2 vs Web3](https://ethereum.org/en/developers/docs/web2-vs-web3/)
+- [The Architecture of a Web 3.0 Application](https://www.preethikasireddy.com/post/the-architecture-of-a-web-3-0-application)
diff --git a/docs/learn/ethereum-dev-overview.mdx b/docs/learn/ethereum-dev-overview.mdx
new file mode 100644
index 00000000..e0692856
--- /dev/null
+++ b/docs/learn/ethereum-dev-overview.mdx
@@ -0,0 +1,9 @@
+---
+title: Ethereum Dev Overview
+description: An overview of web 3 application development.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/events/hardhat-events-sbs.mdx b/docs/learn/events/hardhat-events-sbs.mdx
new file mode 100644
index 00000000..9691cd39
--- /dev/null
+++ b/docs/learn/events/hardhat-events-sbs.mdx
@@ -0,0 +1,285 @@
+---
+title: Events
+sidebarTitle: Step by Step Guide
+description: Events in Solidity
+hide_table_of_contents: false
+---
+
+In this article, you'll learn how events work in Solidity by reviewing some practical examples and common use cases of events.
+
+
+This tutorial has been moved as part of a reorganization! It assumes you are using Hardhat. Everything in this lesson will work with minor adjustments if you are working in Foundry or Remix.
+
+
+
+---
+
+## Objectives
+
+By the end of this lesson, you should be able to:
+
+- Write and trigger an event
+- List common uses of events
+- Understand events vs. smart contract storage
+
+---
+
+## Overview
+
+Understanding how Solidity events work is important in the world of smart contract development. Events provide a powerful way to create event-driven applications on the blockchain. They allow you to notify external parties, such as off-chain applications, user interfaces, and any entity that wants to listen for events of a particular contract.
+
+In this tutorial, you'll learn how to declare, trigger, and utilize events, gaining the knowledge necessary to enhance the functionality and user experience of your decentralized applications.
+
+## What are events?
+
+From the official solidity documentation, [events] are:
+
+> _...an abstraction on top of the EVM’s logging functionality. Applications can subscribe and listen to these events through the RPC interface of an Ethereum client._
+
+> _...when you call them, they cause the arguments to be stored in the transaction’s log – a special data structure in the blockchain. These logs are associated with the address of the contract that emitted them, are incorporated into the blockchain, and stay there as long as a block is accessible (forever as of now, but this might change in the future)._
+
+In other words, events are an abstraction that allow you to store a transaction's log information in the blockchain.
+
+## Your first solidity event
+
+Start by creating a first event in the `Lock.sol` contract that's included by default in Hardhat.
+
+The event is called `Created` and includes the address of the creator and the amount that was sent during the creation of the smart contract. Then, `emit` the event in the constructor:
+
+```solidity
+emit Created(msg.sender, msg.value);
+```
+
+The contract is:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+contract Lock {
+ uint public unlockTime;
+ address payable public owner;
+
+ event Created(address owner, uint amount);
+
+ constructor(uint _unlockTime) payable {
+ require(
+ block.timestamp < _unlockTime,
+ "Unlock time should be in the future"
+ );
+
+ unlockTime = _unlockTime;
+ owner = payable(msg.sender);
+
+ emit Created(msg.sender, msg.value);
+ }
+}
+```
+
+Events can be defined at the file level or as inheritable members of contracts (including interfaces). You can also define the event in an interface as:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+interface ILock {
+ event Created(address owner, uint amount);
+}
+
+contract Lock is ILock {
+ uint public unlockTime;
+ address payable public owner;
+
+ constructor(uint _unlockTime) payable {
+ require(
+ block.timestamp < _unlockTime,
+ "Unlock time should be in the future"
+ );
+
+ unlockTime = _unlockTime;
+ owner = payable(msg.sender);
+
+ emit Created(msg.sender, msg.value);
+ }
+}
+```
+
+You can test the event by simplifying the original test file with the following code:
+
+```solidity
+import {
+ time,
+} from "@nomicfoundation/hardhat-toolbox/network-helpers";
+import { ethers } from "hardhat";
+
+describe("Lock tests", function () {
+ describe("Deployment", function () {
+ it("Should set the right unlockTime", async function () {
+ const ONE_YEAR_IN_SECS = 365 * 24 * 60 * 60;
+ const ONE_GWEI = 1_000_000_000;
+
+ const lockedAmount = ONE_GWEI;
+ const unlockTime = (await time.latest()) + ONE_YEAR_IN_SECS;
+
+ // Contracts are deployed using the first signer/account by default
+ const [owner] = await ethers.getSigners();
+
+ // But we do it explicit by using the owner signer
+ const LockFactory = await ethers.getContractFactory("Lock", owner);
+ const lock = await LockFactory.deploy(unlockTime, { value: lockedAmount });
+
+ const hash = await lock.deploymentTransaction()?.hash
+ const receipt = await ethers.provider.getTransactionReceipt(hash as string)
+
+ console.log("Sender Address", owner.address)
+ console.log("Receipt.logs", receipt?.logs)
+
+ const defaultDecoder = ethers.AbiCoder.defaultAbiCoder()
+ const decodedData = defaultDecoder.decode(['address', 'uint256'], receipt?.logs[0].data as string)
+ console.log("decodedData", decodedData)
+ });
+ });
+});
+```
+
+Notice that the previous code is logging the sender address and the logs coming from the transaction receipt. You are also decoding the `receipts.logs[0].data` field that contains the information emitted by the event but not in a human-readable way, since it is encoded. For that reason, you can use `AbiCoder` to decode the raw data.
+
+By running `npx hardhat test`, you should be able to see the following:
+
+```solidity
+ Lock tests
+ Deployment
+Sender Address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
+Receipt.logs [
+ Log {
+ provider: HardhatEthersProvider {
+ _hardhatProvider: [LazyInitializationProviderAdapter],
+ _networkName: 'hardhat',
+ _blockListeners: [],
+ _transactionHashListeners: Map(0) {},
+ _eventListeners: []
+ },
+ transactionHash: '0xad4ff104036f23096ea5ed165bff1c3e1bc0f53e375080f84bce4cc108c28cee',
+ blockHash: '0xb2117cfd2aa8493a451670acb0ce14228b06d17bf545cd7efad6791aeac83c05',
+ blockNumber: 1,
+ removed: undefined,
+ address: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
+ data: '0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266000000000000000000000000000000000000000000000000000000003b9aca00',
+ topics: [
+ '0x0ce3610e89a4bb9ec9359763f99110ed52a4abaea0b62028a1637e242ca2768b'
+ ],
+ index: 0,
+ transactionIndex: 0
+ }
+]
+decodedData Result(2) [ '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266', 1000000000n ]
+ ✔ Should set the right unlockTime (1008ms)
+```
+
+Notice the value `f39fd6e51aad88f6f4ce6ab8827279cfffb92266` is encoded in the property data, which is the address of the sender.
+
+## Event topics
+
+Another important feature is that events can be indexed by adding the indexed attribute to the event declaration.
+
+For example, if you modify the interface with:
+
+```solidity
+interface ILock {
+ event Created(address indexed owner, uint amount);
+}
+```
+
+Then, if you run `npx hardhat test` again, an error may occur because the decoding assumes that the data field contains an `address` and a `uint256`. But by adding the indexed attribute, you are instructing that the events will be added to a special data structure known as "topics". Topics have some limitations, since the maximum indexed attributes can be up to three parameters and a topic can only hold a single word (32 bytes).
+
+You then need to modify the decoding line in the test file with the following:
+
+```solidity
+const decodedData = defaultDecoder.decode(['uint256'], receipt?.logs[0].data as string)
+```
+
+Then, you should be able to see the receipt as:
+
+```solidity
+ Lock tests
+ Deployment
+Sender Address 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
+Receipt.logs [
+ Log {
+ provider: HardhatEthersProvider {
+ _hardhatProvider: [LazyInitializationProviderAdapter],
+ _networkName: 'hardhat',
+ _blockListeners: [],
+ _transactionHashListeners: Map(0) {},
+ _eventListeners: []
+ },
+ transactionHash: '0x0fd52fd72bca26879474d3e512fb812489111a6654473fd288c6e8ec0432e09d',
+ blockHash: '0x138f74df5637315099d31aedf5bf643cf95c2bb7ae923c21fcd7f0075cb55324',
+ blockNumber: 1,
+ removed: undefined,
+ address: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
+ data: '0x000000000000000000000000000000000000000000000000000000003b9aca00',
+ topics: [
+ '0x0ce3610e89a4bb9ec9359763f99110ed52a4abaea0b62028a1637e242ca2768b',
+ '0x000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb92266'
+ ],
+ index: 0,
+ transactionIndex: 0
+ }
+]
+decodedData Result(1) [ 1000000000n ]
+ ✔ Should set the right unlockTime (994ms)
+```
+
+Notice the topics property, which now contains the address of the sender: `f39fd6e51aad88f6f4ce6ab8827279cfffb92266`.
+
+## Common uses of events
+
+Solidity events have several common use cases, which are described in the following sections.
+
+### User notifications
+
+Events can be used to notify users or external systems about certain contract actions.
+
+### Logging
+
+Events are primarily used to log significant changes within the contract, providing a transparent and verifiable history of what has occurred.
+
+### Historical state reconstruction
+
+Events can be valuable for recreating the historical state of a contract. By capturing and analyzing emitted event logs, you can reconstruct past states, offering a transparent and auditable history of the contract's actions and changes.
+
+### Debugging and monitoring
+
+Events are essential for debugging and monitoring contract behavior, as they provide a way to observe what's happening on the blockchain.
+
+The ability to use events to recreate historical states provides an important auditing and transparency feature, allowing users and external parties to verify the contract's history and actions. While not a common use, it's a powerful capability that can be particularly useful in certain contexts.
+
+## Events vs. smart contract storage
+
+Although it is possible to rely on events to fully recreate the state of a particular contract, there are a few other options to consider.
+
+Existing services such as [The Graph] allow you to index and create GraphQL endpoints for your smart contracts and generate entities based on custom logic. However, you must pay for that service since you are adding an intermediate layer to your application. This has the following benefits, such as:
+
+- the ability to simply query one particular endpoint to get all the information you need
+- your users will pay less gas costs due to the minimization of storage usage in your contract
+
+But storing all of the information within the smart contract and relying fully on it to access data can create more complexity, since not all of the data is directly query-able. The benefits of this approach include:
+
+- your application requires only the smart contract address to access all of the required data
+- there are fewer dependencies involved, which makes this approach more crypto native in the sense that everything is in the blockchain (but, storing all the data in the blockchain will cause higher gas costs)
+
+As a smart contract developer, you must evaluate which options work best for you.
+
+## Conclusion
+
+In this lesson, you've learned the basics of Solidity events and their importance in Ethereum smart contract development. You now understand how to declare and trigger events, a few of their common use cases, and the difference between events and smart contract storage.
+
+Now that you have a solid grasp of events and their versatile applications, you can leverage them to build more sophisticated and interactive smart contracts that meet your specific needs, all while being mindful of the cost considerations.
+
+---
+
+## See also
+
+[events]: https://docs.soliditylang.org/en/latest/contracts.html#events
+[The Graph]: https://thegraph.com/
diff --git a/docs/learn/evm-diagram.mdx b/docs/learn/evm-diagram.mdx
new file mode 100644
index 00000000..303a6398
--- /dev/null
+++ b/docs/learn/evm-diagram.mdx
@@ -0,0 +1,109 @@
+---
+title: EVM Diagram
+description: An overview of the Ethereum Virtual Machine
+---
+
+In this article, we'll examine the inner workings of the EVM, its components, and its role within the Ethereum network.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Diagram the EVM
+
+---
+
+## What is the EVM?
+
+The Ethereum Virtual Machine (EVM) is the core engine of Ethereum. It is a Turing-complete, sandboxed virtual machine designed to execute smart contracts on the network. The term "sandboxed" means that the EVM operates in an isolated environment, ensuring that each smart contract's execution does not interfere with others or the underlying blockchain. As we've learned, the EVM's Turing-complete nature allows developers to write complex programs that can perform any computationally feasible task.
+
+The EVM employs a sophisticated resource management system using gas to regulate computation costs and prevent network abuse. It also supports a rich ecosystem of apps by providing a versatile set of opcodes for smart contract logic, and fostering interoperability with various programming languages, tools, and technologies. This adaptability has made the EVM a fundamental component in the advancement and growth of the Ethereum network.
+
+---
+
+## EVM Components
+
+The EVM has several key components that enable it to process and manage smart contracts. Let's define them:
+
+- **World State:** Represents the entire Ethereum network, including all accounts and their associated storage.
+- **Accounts:** Entities that interact with the Ethereum network, including Externally Owned Accounts (EOAs) and Contract Accounts.
+- **Storage:** A key-value store associated with each contract account, containing the contract's state and data.
+- **Gas:** A mechanism for measuring the cost of executing operations in the EVM, which protects the network from spam and abuse.
+- **Opcodes:** Low-level instructions that the EVM executes during smart contract processing.
+- **Execution Stack:** A last-in, first-out (LIFO) data structure for temporarily storing values during opcode execution.
+- **Memory:** A runtime memory used by smart contracts during execution.
+- **Program Counter:** A register that keeps track of the position of the next opcode to be executed.
+- **Logs:** Events emitted by smart contracts during execution, which can be used by external systems for monitoring or reacting to specific events.
+
+---
+
+## EVM Execution Model
+
+In simple terms, when a transaction is submitted to the network, the EVM first verifies its validity. If the transaction is deemed valid, the EVM establishes an execution context that incorporates the current state of the network and processes the smart contract's bytecode using opcodes. As the EVM runs the smart contract, it modifies the blockchain's world state and consumes gas accordingly. However, if the transaction is found to be invalid, it will be dismissed by the network without further processing. Throughout the smart contract's execution, logs are generated that provide insights into the contract's performance and any emitted events. These logs can be utilized by external systems for monitoring purposes or to respond to specific events.
+
+
+
+
+
+---
+
+## Gas and Opcode Execution
+
+While we have already delved into the concept of gas in a previous lesson, it is worth reiterating its critical role within the EVM and as a fundamental component of Ethereum. Gas functions as a metric for quantifying the computational effort needed to carry out operations in the EVM. Every opcode in a smart contract carries a specific gas cost, which reflects the computational resources necessary for its execution.
+
+Opcodes are the low-level instructions executed by the EVM. They represent elementary operations that allow the EVM to process and manage smart contracts.
+
+
+
+
+
+During execution, the EVM reads opcodes from the smart contract, and depending on the opcode, it may update the world state, consume gas, or revert the state if an error occurs. Some common opcodes include:
+
+- **ADD:** Adds two values from the stack.
+- **SUB:** Subtracts two values from the stack.
+- **MSTORE:** Stores a value in memory.
+- **SSTORE:** Stores a value in contract storage.
+- **CALL:** Calls another contract or sends ether.
+
+---
+
+## Stack and Memory
+
+The EVM stack and memory are critical components of the EVM architecture, as they enable smart contracts to manage temporary data during opcode execution. The stack is a last-in, first-out (LIFO) data structure that is used for temporarily storing values during opcode execution. It is managed by the EVM and is separate from the contract's storage. The stack supports two primary operations: push and pop.
+
+The push operation adds a value to the top of the stack, while the pop operation removes the top value from the stack. These operations are used to manage temporary data during opcode execution. For example, an opcode that performs an addition operation might push the two operands onto the stack, perform the addition, and then pop the result off the top of the stack.
+
+During contract execution, memory serves as a collection of bytes, organized in an array, for the purpose of temporarily storing data. It can be read from and written to by opcodes. Memory is often used to store temporary data during opcode execution, such as when working with dynamically sized data like strings or arrays that are being manipulated or computed within the smart contract before being stored in the contract's storage. When a smart contract needs to store temporary data during opcode execution, it can use the memory to store that data.
+
+
+
+
+
+---
+
+## EVM Architecture and Execution Context
+
+To fully grasp the EVM architecture and its components, it's important to see how they all come together in a cohesive manner. The following diagram provides an in-depth visualization of the EVM architecture, showcasing the interactions between key elements such as transactions, gas, opcodes, and the world state. With this diagram, you can see how each component plays a vital role in the seamless execution of smart contracts on the Ethereum network.
+
+
+
+
+Image Source: [Mastering Ethereum](https://github.com/ethereumbook/ethereumbook) by Andreas M. Antonopoulos and Gavin Wood, licensed under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)
+
+---
+
+## Conclusion
+
+The EVM plays a vital role within the Ethereum network. By examining the EVM's key components as well as its architecture and execution model, we've gained insight into the engine of Ethereum and how it enables the smooth execution of smart contracts on the platform.
+
+---
+
+## See Also
+
+- [The Ethereum Virtual Machine (Mastering Ethereum)](https://cypherpunks-core.github.io/ethereumbook/13evm.html#evm_architecture)
+- [Ethereum Virtual Machine (Ethereum docs)](https://ethereum.org/en/developers/docs/evm/)
+
+
+[the ethereum virtual machine (mastering ethereum)]: https://cypherpunks-core.github.io/ethereumbook/13evm.html#evm_architecture
diff --git a/docs/learn/exercise-contracts.mdx b/docs/learn/exercise-contracts.mdx
new file mode 100644
index 00000000..b6187691
--- /dev/null
+++ b/docs/learn/exercise-contracts.mdx
@@ -0,0 +1,26 @@
+---
+title: "Exercise Contracts"
+description: A list of verified unit test contracts for Base Learn exercises.
+---
+
+Many of the sections in Base Learn contain an exercise to test your knowledge on the material you have just completed. We tell you **what** to do, but not **how** to do it. You have to apply your knowledge and demonstrate the new abilities you have earned.
+
+Upon success, you'll be granted a non-transferable, or soulbound, NFT as a memento of your learning. You can track your progress on the [progress page].
+
+Below is a list of the exercises, with links to view their code. The unit tests are written in a bespoke framework in Solidity, but the patterns should be recognizable to most engineers.
+
+| Exercise | Code |
+| :--------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------- |
+| [Deploying to a Testnet](/learn/deployment-to-testnet/deployment-to-testnet-exercise) | [0x075eB9Dc52177Aa3492E1D26f0fDE3d729625d2F](https://sepolia.basescan.org/address/0x075eb9dc52177aa3492e1d26f0fde3d729625d2f#code#F16#L1) |
+| [Control Structures](/learn/control-structures/control-structures-exercise) | [0xF4D953A3976F392aA5509612DEfF395983f22a84](https://sepolia.basescan.org/address/0xf4d953a3976f392aa5509612deff395983f22a84#code#F17#L1) |
+| [Storage](/learn/storage/storage-exercise) | [0x567452C6638c0D2D9778C20a3D59749FDCaa7aB3](https://sepolia.basescan.org/address/0x567452c6638c0d2d9778c20a3d59749fdcaa7ab3#code#F17#L1) |
+| [Arrays](/learn/arrays/arrays-exercise) | [0x5B0F80cA6f5bD60Cc3b64F0377f336B2B2A56CdF](https://sepolia.basescan.org/address/0x5b0f80ca6f5bd60cc3b64f0377f336b2b2a56cdf) |
+| [Mappings](/learn/mappings/mappings-exercise) | [0xD32E3ACe3272e2037003Ca54CA7E5676f9b8D06C](https://sepolia.basescan.org/address/0xd32e3ace3272e2037003ca54ca7e5676f9b8d06c#code#F17#L1) |
+| [Structs](/learn/structs/structs-exercise) | [0x9eB1Fa4cD9bd29ca2C8e72217a642811c1F6176d](https://sepolia.basescan.org/address/0x9eb1fa4cd9bd29ca2c8e72217a642811c1f6176d#code#F17#L1) |
+| [Inheritance](/learn/inheritance/inheritance-exercise) | [0xF90dA05e77a33Fe6D64bc2Df84e7dd0069A2111C](https://sepolia.basescan.org/address/0xF90dA05e77a33Fe6D64bc2Df84e7dd0069A2111C#code#F17#L1) |
+| [Imports](/learn/imports/imports-exercise) | [0x8dD188Ec36084D59948F90213AFCd04429E33c0c](https://sepolia.basescan.org/address/0x8dd188ec36084d59948f90213afcd04429e33c0c#code#F17#L1) |
+| [Errors](/learn/error-triage/error-triage-exercise) | [0xC1BD0d9A8863f2318001BC5024c7f5F58a2236F7](https://sepolia.basescan.org/address/0xc1bd0d9a8863f2318001bc5024c7f5f58a2236f7#code#F17#L1) |
+| [The "new" Keyword](/learn/new-keyword/new-keyword-exercise) | [0x4f21e69d0CDE8C21cF82a6b37Dda5444716AFA46](https://sepolia.basescan.org/address/0x4f21e69d0cde8c21cf82a6b37dda5444716afa46#code#F17#L1) |
+| [Minimal Tokens](/learn/minimal-tokens/minimal-tokens-exercise) | [0x10Ce928030E136EcC74d4a4416Db9b533e3c694D](https://sepolia.basescan.org/address/0x10ce928030e136ecc74d4a4416db9b533e3c694d#code#F17#L1) |
+| [ERC-20 Tokens](/learn/erc-20-token/erc-20-exercise) | [0x4F333c49B820013e5E6Fe86634DC4Da88039CE50](https://sepolia.basescan.org/address/0x4f333c49b820013e5e6fe86634dc4da88039ce50#code#F21#L1) |
+| [ERC-721 Tokens](/learn/erc-721-token/erc-721-exercise) | [0x15534ED3d1dBA55148695B2Ba4164F147E47a10c](https://sepolia.basescan.org/address/0x15534ed3d1dba55148695b2ba4164f147e47a10c#code#F18#L1) |
diff --git a/docs/pages/cookbook/smart-contract-development/foundry/deploy-with-foundry.mdx b/docs/learn/foundry/deploy-with-foundry.mdx
similarity index 100%
rename from docs/pages/cookbook/smart-contract-development/foundry/deploy-with-foundry.mdx
rename to docs/learn/foundry/deploy-with-foundry.mdx
diff --git a/docs/pages/cookbook/smart-contract-development/foundry/generate-random-numbers-contracts.md b/docs/learn/foundry/generate-random-numbers-contracts.md
similarity index 100%
rename from docs/pages/cookbook/smart-contract-development/foundry/generate-random-numbers-contracts.md
rename to docs/learn/foundry/generate-random-numbers-contracts.md
diff --git a/docs/learn/foundry/introduction-to-foundry.mdx b/docs/learn/foundry/introduction-to-foundry.mdx
new file mode 100644
index 00000000..4a494f32
--- /dev/null
+++ b/docs/learn/foundry/introduction-to-foundry.mdx
@@ -0,0 +1,5 @@
+---
+title: 'Introduction to Foundry'
+---
+
+[Content about Foundry overview and introduction goes here]
\ No newline at end of file
diff --git a/docs/learn/foundry/setup-with-base.mdx b/docs/learn/foundry/setup-with-base.mdx
new file mode 100644
index 00000000..0940c641
--- /dev/null
+++ b/docs/learn/foundry/setup-with-base.mdx
@@ -0,0 +1,148 @@
+---
+title: 'Foundry: Setting up Foundry with Base'
+slug: /intro-to-foundry-setup
+description: A tutorial that teaches how to set up your development environment to work with Foundry.
+author: Edson Alcala
+---
+
+# Setting up Foundry with Base
+
+In this tutorial, you'll learn how to set up [Foundry], a toolchain for smart contract development. You'll also learn how to configure it to work with Base.
+
+## Objectives
+
+By the end of this tutorial, you should be able to:
+
+- Install Foundry
+- Create a Foundry project
+- Compile a smart contract using Foundry
+- Configure Foundry to work with Base
+
+## Overview
+
+Foundry is a smart contract development toolchain that is composed of multiple small command line tools:
+
+- _[forge]_: Compile, test, and deploy your smart contracts
+- _[cast]_: Interact with the Blockchain over RPC. You can make smart contract calls, send transactions, or retrieve any type of chain data
+- _[chisel]_: A Solidity REPL. You can write Solidity code directly
+- _[anvil]_: A local Blockchain node for testing and development
+
+Using Foundry you can manage your dependencies, compile your project, run tests, deploy smart contracts and interact with the chain from the command-line and via Solidity scripts.
+
+For a deep dive on the Foundry features and full capabilities, check out the [Foundry Book].
+
+## Installing Foundry
+
+In order to install Foundry, you can use `Foundryup`, the Foundry's toolchain installer.
+
+To install `Foundryup` you have to run in the terminal:
+
+```bash
+$ curl -L https://foundry.paradigm.xyz | bash
+```
+
+After `Foundryup` is installed, you can install `Foundry` by running:
+
+```bash
+$ foundryup
+```
+
+You can verify the installation by trying the following commands:
+
+```bash
+$ forge --version
+$ cast --version
+$ chisel --version
+$ anvil --version
+```
+
+## My First Foundry Project
+
+To create a foundry project you can simply run:
+
+```bash filename="terminal"
+$ forge init hello_foundry_in_base
+```
+
+This will create a foundry project with the following structure:
+
+```text filename="project-structure"
+├── lib # all the libraries installed
+├── script # scripts folder, e.g., deploy scripts
+├── src # smart contracts folder
+├── test # tests folder
+└── foundry.toml # foundry configuration file
+```
+
+You will also notice a `.gitmodules` file -- this is because `Foundry` handles dependencies using [Git submodules].
+
+By default the Foundry structure stores smart contracts in the `src` folder. You can change this in the `foundry.toml` configuration file.
+
+For instance:
+
+```toml filename="foundry.toml"
+[profile.default]
+src = 'contracts'
+```
+
+In order to compile the project, simply run:
+
+```bash filename="terminal"
+forge build
+```
+
+## Setting up Foundry with Base
+
+In order to work with Base, you need to configure a couple of settings in the configuration `foundry.toml` file.
+
+The first thing is the Solidity version.
+
+You need to configure your config file as follows:
+
+```toml filename="foundry.toml"
+[profile.default]
+src = 'src'
+out = 'out'
+libs = ['lib']
+solc_version = "0.8.23"
+```
+
+Be sure that you modify the pragma of your contracts and simply run `forge build` to ensure everything works well.
+
+We also recommend setting up JSON RPC endpoints for Base and the API key for [Basescan] in the configuration file so that your environment is ready to deploy your smart contracts.
+
+Your configuration file should look like the following:
+
+```toml filename="foundry.toml"
+[profile.default]
+src = "src"
+out = "out"
+libs = ["lib"]
+solc_version = "0.8.23"
+
+[rpc_endpoints]
+base = "https://mainnet.base.org"
+baseSepolia = "https://sepolia.base.org"
+
+[etherscan]
+baseSepolia = { key = "${BASESCAN_API_KEY}", url = "https://api-sepolia.basescan.org/api" }
+base = { key = "${BASESCAN_API_KEY}", url = "https://api.basescan.org/api" }
+```
+
+We included 2 JSON RPC endpoints for `Base` and `Base Sepolia` and similar for the Etherscan section, we included the configuration for `Basescan` for Sepolia and Mainnet. Both rely on the same API Key, `BASESCAN_API_KEY`.
+
+## Conclusion
+
+In this tutorial, you've embarked on the journey of smart contract development with Base and Foundry. You've learned the essential steps, from installing Foundry using the convenient `Foundryup` toolchain installer to creating your first project and configuring Foundry to seamlessly integrate with Base.
+
+[Foundry]: https://github.com/foundry-rs/foundry
+[Foundry Book]: https://book.getfoundry.sh/
+[chisel]: https://book.getfoundry.sh/chisel/
+[cast]: https://book.getfoundry.sh/cast/
+[anvil]: https://book.getfoundry.sh/anvil/
+[forge]: https://book.getfoundry.sh/forge/
+[Git submodules]: https://git-scm.com/book/en/v2/Git-Tools-Submodules
+[OP Stack]: https://stack.optimism.io/
+[Differences between Ethereum and Base]: https://docs.base.org/differences/
+[Basescan]: https://basescan.org/
+
diff --git a/docs/learn/foundry/testing-smart-contracts.mdx b/docs/learn/foundry/testing-smart-contracts.mdx
new file mode 100644
index 00000000..4208e215
--- /dev/null
+++ b/docs/learn/foundry/testing-smart-contracts.mdx
@@ -0,0 +1,215 @@
+---
+title: 'Foundry: Testing smart contracts'
+slug: /intro-to-foundry-testing
+author: Edson Alcala
+description: A tutorial that teaches how to test your smart contracts using Foundry.
+---
+
+# Testing smart contracts using Foundry
+
+In this tutorial, you'll learn how to test your smart contracts using [Foundry], the toolchain for smart contract development.
+
+## Objectives
+
+By the end of this tutorial, you should be able to:
+
+- Understand the increased importance of testing in smart contract development
+- Write and execute tests written in Solidity using the Forge Standard Library with Foundry
+- Use the `cheatcodes` that Foundry provides to test your smart contracts
+
+## Overview
+
+Testing is a crucial aspect of smart contract development, ensuring the reliability and security of your code. Because it is impossible to patch a smart contract after deployment, you must thoroughly and completely test your code. Foundry provides a robust testing framework that allows developers to create comprehensive test suites for their projects using Solidity.
+
+## My First Test with Foundry
+
+Consider the default test that the `forge init hello_foundry_in_base` command provides in the seed Foundry project.
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "forge-std/Test.sol";
+import "../src/Counter.sol";
+
+contract CounterTest is Test {
+ Counter public counter;
+
+ function setUp() public {
+ counter = new Counter();
+ counter.setNumber(0);
+ }
+
+ function testIncrement() public {
+ counter.increment();
+ assertEq(counter.number(), 1);
+ }
+
+ function testSetNumber(uint256 x) public {
+ counter.setNumber(x);
+ assertEq(counter.number(), x);
+ }
+}
+```
+
+Take note of the following:
+
+- Foundry test files are named following the pattern: `.t.sol`
+- Smart contract test files are named following the pattern: `Test`
+- All tests inherit from `forge-std/Test.sol`.
+- All tests contain a public function called `setUp`, which is executed before each test. This is similar to the `beforeEach` hook in the Mocha/Typescript world.
+- Test cases start with the `test` keyword, for instance `testIncrement`.
+- Test cases functions are public.
+
+For more information about writing tests in Foundry, you can follow the official guide for [Writing tests]
+
+In order to run the test in Foundry, run:
+
+```bash
+$ forge test
+```
+
+You should see in the terminal:
+
+```bash
+Running 2 tests for test/Counter.t.sol:CounterTest
+[PASS] testIncrement() (gas: 28334)
+[PASS] testSetNumber(uint256) (runs: 256, μ: 27565, ~: 28343)
+Test result: ok. 2 passed; 0 failed; finished in 13.57ms
+```
+
+## Using Cheatcodes
+
+Foundry includes a set of [cheatcodes], which are special instructions that are accessible using the `vm` instance in your tests. Cheatcodes allow you to perform various tasks, including:
+
+- Manipulate the state of the blockchain
+- Test reverts
+- Test events
+- Change block number
+- Change identity
+- And more!
+
+To start, use a cheatcode to modify the `msg.sender` of your tests, and add some console logs via importing the `forge-std/console.sol` contract.
+
+The `Counter` contract should look as follows:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "forge-std/console.sol";
+
+contract Counter {
+ uint256 public number;
+
+ function setNumber(uint256 newNumber) public {
+ console.log("The sender is %s", msg.sender);
+ number = newNumber;
+ }
+
+ function increment() public {
+ console.log("The sender is %s", msg.sender);
+ number++;
+ }
+}
+```
+
+If you run the tests using `forge test`, you will see the following:
+
+```bash
+Running 2 tests for test/Counter.t.sol:CounterTest
+[PASS] testIncrement() (gas: 31531)
+[PASS] testSetNumber(uint256) (runs: 256, μ: 30684, ~: 31540)
+Test result: ok. 2 passed; 0 failed; finished in 19.64ms
+```
+
+It seems the logs are not being shown. The reason is because the `forge test` command includes a flag that enable you to include more details of the logs emitted during the execution of the tests.
+
+You can control that by including different levels of the verbose flag -- `-vv` up to `-vvvvv`. For more details about the level of verbosity you can refer to the [Logs and Traces] section of the Foundry documentation.
+
+Run the `foundry test -vv`. You should see:
+
+```bash
+Running 2 tests for test/Counter.t.sol:CounterTest
+[PASS] testIncrement() (gas: 31531)
+Logs:
+ The sender is 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
+ The sender is 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
+
+[PASS] testSetNumber(uint256) (runs: 256, μ: 30607, ~: 31540)
+Logs:
+ The sender is 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
+
+Test result: ok. 2 passed; 0 failed; finished in 17.89ms
+```
+
+Now, modify the test file using `prank` cheatcode, which allow you to modify the `msg.sender` of the next transaction. You will also use the `addr` cheatcode, which allow you to generate an address using any private key, which can simply be a hex number.
+
+Include some `console.log` statements to understand better the execution flow.
+
+The code should look like:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "forge-std/Test.sol";
+import "../src/Counter.sol";
+
+contract CounterTest is Test {
+ Counter public counter;
+
+ function setUp() public {
+ counter = new Counter();
+ console.log("Calling on Setup");
+ counter.setNumber(0);
+ }
+
+ function testIncrement() public {
+ console.log("Calling on testIncrement");
+ vm.prank(vm.addr(0x01));
+ counter.increment();
+ assertEq(counter.number(), 1);
+ }
+
+ function testSetNumber(uint256 x) public {
+ console.log("Calling on testSetNumber");
+ vm.prank(vm.addr(0x02));
+ counter.setNumber(x);
+ assertEq(counter.number(), x);
+ }
+}
+```
+
+Then if you run the `forge test -vv` command, you should see:
+
+```bash
+Running 2 tests for test/Counter.t.sol:CounterTest
+[PASS] testIncrement() (gas: 35500)
+Logs:
+ Calling on Setup
+ The sender is 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
+ Calling on testIncrement
+ The sender is 0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf
+
+[PASS] testSetNumber(uint256) (runs: 256, μ: 34961, ~: 35506)
+Logs:
+ Calling on Setup
+ The sender is 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
+
+Test result: ok. 2 passed; 0 failed; finished in 48.75ms
+```
+
+Notice how you call the cheatcode `vm.prank` before the call to the `counter.increment()` and `counter.setNumber(x)` functions. This allows you to specify a particular address to become the `msg.sender` in the contract. Since the `vm.prank` accepts an address, you simply generate an address using the cheatcode `vm.addr`, where you pass a simple hexadecimal number, which is a valid private key.
+
+## Conclusion
+
+Congratulations! You've successfully completed your first step in your journey of testing smart contracts using Foundry. As you move forward, keep exploring its rich features and functionalities. The ability to write comprehensive tests and leverage cheatcodes ensures the reliability and security of your smart contracts.
+
+Happy coding and testing with Foundry!
+
+[Foundry]: https://book.getfoundry.sh/
+[Writing tests]: https://book.getfoundry.sh/forge/writing-tests
+[cheatcodes]: https://book.getfoundry.sh/forge/cheatcodes
+[Logs and Traces]: https://book.getfoundry.sh/forge/tests?highlight=vvv#logs-and-traces
+
diff --git a/docs/learn/foundry/testing.mdx b/docs/learn/foundry/testing.mdx
new file mode 100644
index 00000000..d00057ca
--- /dev/null
+++ b/docs/learn/foundry/testing.mdx
@@ -0,0 +1,5 @@
+---
+title: 'Testing Smart Contracts'
+---
+
+[Content about testing smart contracts using Foundry goes here]
\ No newline at end of file
diff --git a/docs/pages/cookbook/smart-contract-development/foundry/verify-contract-with-basescan.md b/docs/learn/foundry/verify-contract-with-basescan.md
similarity index 100%
rename from docs/pages/cookbook/smart-contract-development/foundry/verify-contract-with-basescan.md
rename to docs/learn/foundry/verify-contract-with-basescan.md
diff --git a/docs/learn/gas-use-in-eth-transactions.mdx b/docs/learn/gas-use-in-eth-transactions.mdx
new file mode 100644
index 00000000..8230daf7
--- /dev/null
+++ b/docs/learn/gas-use-in-eth-transactions.mdx
@@ -0,0 +1,117 @@
+---
+title: Gas Use in Ethereum Transactions
+description: An overview of how gas works in Ethereum
+---
+
+In this article, we'll delve into the concept of gas and its importance in the Ethereum ecosystem. You'll learn why Ethereum relies on a system of gas to regulate the execution of transactions and smart contracts, and how it plays a crucial role in the proper functioning of the network.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Explain what gas is in Ethereum
+- Explain why gas is necessary in Ethereum
+- Understand how gas works in Ethereum transactions
+
+---
+
+## What is gas?
+
+Gas is a term used in Ethereum to describe a computational unit that measures the amount of computational work needed to perform specific operations on the network. Unlike Bitcoin, where transaction fees only consider the size of a transaction, Ethereum accounts for every computational step performed by transactions and smart contract code execution. In other words, every single operation that is performed on Ethereum requires a certain amount of gas.
+
+### Complexity
+
+The amount of gas required for an operation depends on its complexity. More complex operations require more computational resources and therefore require more gas to be executed. For example, a simple transaction that involves sending ETH from one address to another may require less gas than a complex smart contract that executes multiple operations or interacts with multiple other contracts.
+
+### State of the Network
+
+Gas costs can also vary depending on the state of the network, or more specifically, how congested it is. When there are more transactions waiting to be processed than the network can handle, it will prioritize transactions based on the gas price that was set by the user, meaning that higher gas prices are more likely to get processed first. When the network is congested, gas prices increase to encourage more efficient use of the network's resources and decrease when network usage is lower. This dynamic pricing mechanism ensures that the Ethereum network remains accessible and functional for all users, while also incentivizing responsible and efficient use of the network's resources.
+
+
+
+
+
+---
+
+## Why is gas necessary?
+
+### Turing Completeness
+
+As we've learned, Ethereum is a Turing-complete platform, which means that any program that can be represented in code can theoretically be expressed and executed on the network. This opens up the door to countless different types of applications that can be built, but it also creates the possibility that malicious or inefficient code can clog up the network, potentially leading to denial-of-service attacks, network spam, and other problems.
+
+### Preventing Infinite Loops
+
+Gas to the rescue! To prevent accidental or intentional infinite loops in smart contract code, Ethereum requires that every transaction specify a gas limit. The gas limit establishes the maximum amount of gas that the transaction can consume, and they ensure that transactions are executed within a predetermined amount of computational resources, preventing the execution of code that might consume too much computation power and potentially cause the network to freeze or crash. Without gas, Ethereum's Turing completeness would be insecure and inefficient.
+
+### Autonomous Execution
+
+It's also important to note that gas enables the execution of smart contracts without the need for a central authority to monitor their execution. The gas system provides a mechanism for regulating the resources required to execute the code of these contracts as well. In other words, without gas, it would be difficult to guarantee that smart contracts could operate autonomously, fairly and efficiently.
+
+---
+
+## How does gas work?
+
+### Ethereum Denominations
+
+Before diving into the inner workings of gas, it's important to understand a few of the most common denominations used in Ethereum.
+
+#### Ether (ETH)
+
+Ether is the native cryptocurrency of the Ethereum network. Gas fees are paid in ETH.
+
+#### Wei
+
+Wei is the smallest denomination of Ethereum and is equivalent to 10^-18 ETH. It is used to represent very small amounts of ETH, usually gas prices and transaction fees. To put 10^-18 into perspective:
+
+- 1 ETH = 1,000,000,000,000,000,000 wei
+- 1 wei = 0.000000000000000001 ETH
+
+#### Gwei
+
+Gwei is commonly used to express the price of gas. One gwei is equivalent to one billionth of one ETH or 10^-9 ETH.
+
+- 1 ETH = 1,000,000,000 gwei
+- 1 gwei = 0.000000001 ETH
+
+### Gas Price
+
+Gas price on the network is denominated in gwei, and the gas fee is calculated as the product of the gas price and the amount of gas required for an operation. For example, if the gas price is 50 gwei, and an operation requires 100,000 units of gas, the gas fee would be 0.005 ETH (50 gwei x 100,000 gas = 0.005 ETH).
+
+### Gas Limit
+
+Gas limit is an essential component of the gas system in Ethereum. It defines the maximum amount of gas a user is willing to spend for a transaction to be processed. This gas limit is set by the sender of the transaction and represents the upper limit of computational resources that the transaction can consume. The Ethereum Virtual Machine (EVM) starts deducting the amount of gas used from the gas limit as soon as it starts processing the transaction.
+
+Consider Alice wants to send some ETH to Bob. Alice specifies a gas limit of 100,000 units and a gas price of 10 gwei (0.00000001 ETH) per unit of gas. So, she's willing to spend a maximum of 0.001 ETH for this transaction (1,000,000 gwei).
+
+The EVM, upon receiving Alice's transaction, starts executing it. As the transaction is processed, the EVM deducts the used gas from the gas limit. If the transaction completes before reaching the gas limit, the remaining unused gas is refunded to Alice's account.
+
+Let's illustrate this with a couple scenarios:
+
+- Suppose the transaction used 80,000 units of gas, leaving 20,000 units unused. Since the gas price was set at 10 gwei per unit, Alice would receive a refund of 0.0002 ETH (200,000 gwei) for the unused gas.
+
+- In a different scenario, suppose Alice sends a transaction with a gas limit of 100,000 units. After processing all the opcodes in the transaction except for the last one, Alice's transaction has consumed 99,998 units of gas. The EVM checks and sees that the last opcode will initiate because there are 2 units of gas remaining, enough to start it. However, as the opcode executes, it becomes clear that it actually requires more than 2 units of gas. At this point, the EVM throws an "Out of Gas" exception and halts the transaction. In this scenario, Alice loses all 100,000 units of gas, as they are consumed in the attempted execution. All state changes that might have occurred during the execution are rolled back, and the ETH Alice tried to send to Bob is returned to her.
+
+### Gas Estimation
+
+Gas estimation is another key concept to understand. It refers to the process of predicting the amount of gas that will be required to execute a transaction. This is important because as we've seen in our example, the gas limit of a transaction needs to be set before it can be broadcasted to the network. If the gas limit is set too low, the transaction may fail to execute, while if it is set too high, the sender may end up paying more in transaction fees than is necessary.
+
+There are several methods that can be used for gas estimation. One common method is to use historical gas prices and gas limits as a reference point, and to estimate the gas needed for a new transaction based on the gas used in similar past transactions. Another method is to simulate the execution of the transaction in a test environment to determine the actual amount of gas that would be used.
+
+Thankfully, most Ethereum wallet applications have built-in gas estimation algorithms that can automatically calculate an appropriate gas limit for a transaction based on the network conditions at the time the transaction is initiated. This helps to prevent a transaction from failing from the gas limit being too low while optimizing for the best possible cost for the sender.
+
+---
+
+## Conclusion
+
+Gas is a vital component of Ethereum. It's what regulates the execution of all transactions and smart contracts, and it plays a significant role in the proper functioning and security of the network. Without gas, Ethereum's Turing-complete architecture would be inefficient and vulnerable to attacks. Gas also ensures that smart contracts can operate autonomously, fairly, and efficiently without the need for a central authority to monitor their execution. Understanding how gas works is essential for anyone who wants to develop applications or smart contracts on the Ethereum network.
+
+---
+
+## See also
+
+- [Gas and Fees (Ethereum Docs)](https://ethereum.org/en/developers/docs/gas/)
+- [Transaction Gas (Mastering Ethereum)](https://github.com/ethereumbook/ethereumbook/blob/develop/06transactions.asciidoc#transaction-gas)
+- [Turing Completeness and Gas (Mastering Ethereum)](https://github.com/ethereumbook/ethereumbook/blob/develop/13evm.asciidoc#turing-completeness-and-gas)
+- [Gas (Mastering Ethereum)](https://github.com/ethereumbook/ethereumbook/blob/develop/13evm.asciidoc#gas)
diff --git a/docs/learn/guide-to-base.mdx b/docs/learn/guide-to-base.mdx
new file mode 100644
index 00000000..9d3d9b23
--- /dev/null
+++ b/docs/learn/guide-to-base.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Guide to Base'
+url: https://www.coinbase.com/developer-platform/discover/protocol-guides/guide-to-base
+---
\ No newline at end of file
diff --git a/docs/learn/hardhat/analyzing-test-coverage.mdx b/docs/learn/hardhat/analyzing-test-coverage.mdx
new file mode 100644
index 00000000..d04ade8c
--- /dev/null
+++ b/docs/learn/hardhat/analyzing-test-coverage.mdx
@@ -0,0 +1,238 @@
+---
+title: 'Hardhat: Analyzing the test coverage of smart contracts'
+slug: /hardhat-test-coverage
+description: A tutorial that teaches how to profile the test coverage of your smart contracts using Hardhat and the Solidity Coverage plugin.
+author: Edson Alcala
+---
+
+# Analyzing the test coverage of smart contracts using Hardhat
+
+In this tutorial, you'll learn how to profile the test coverage of your smart contracts with [Hardhat] and the [Solidity Coverage] community plugin.
+
+## Objectives
+
+By the end of this tutorial, you should be able to:
+
+- Use the Solidity Coverage plugin to analyze the coverage of your test suite
+- Increase the coverage of your test suite
+
+## Overview
+
+The Solidity Coverage plugin allows you to analyze and visualize the coverage of your smart contracts' test suite. This enables you to see what portions of your smart contract are being tested and what areas may have been overlooked. It's an indispensable plugin for developers seeking to fortify their testing practices and ensure robust smart contract functionality.
+
+## Setting up the Solidity Coverage plugin
+
+The Solidity Coverage plugin is integrated into the Hardhat toolbox package, which is installed by default when you use the `npx hardhat init` command.
+
+To install manually, run `npm install -D solidity-coverage`.
+
+Then, import `solidity-coverage` in `hardhat.config.ts`:
+
+```solidity
+import "solidity-coverage"
+```
+
+Once the installation completes either manually or via the default Hardhat template, the task `coverage` becomes available via the `npx hardhat coverage` command.
+
+## My first test coverage
+
+Review the following contract and test suite (You'll recognize these if you completed the [Hardhat testing lesson] in our [Base Learn] series).
+
+Contract:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+contract Lock {
+ uint public unlockTime;
+ address payable public owner;
+
+ event Withdrawal(uint amount, uint when);
+
+ constructor(uint _unlockTime) payable {
+ require(
+ block.timestamp < _unlockTime,
+ "Unlock time should be in the future"
+ );
+
+ unlockTime = _unlockTime;
+ owner = payable(msg.sender);
+ }
+
+ function withdraw() public {
+ require(block.timestamp >= unlockTime, "You can't withdraw yet");
+ require(msg.sender == owner, "You aren't the owner");
+
+ emit Withdrawal(address(this).balance, block.timestamp);
+
+ owner.transfer(address(this).balance);
+ }
+}
+```
+
+`Lock.test.ts`:
+
+```solidity
+import { expect } from "chai";
+import { ethers } from "hardhat";
+import { time } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'
+import { Lock__factory, Lock} from '../typechain-types'
+
+describe("Lock Tests", function () {
+ const UNLOCK_TIME = 10000;
+ const VALUE_LOCKED = ethers.parseEther("0.01");
+
+ let lastBlockTimeStamp: number;
+ let lockInstance: Lock;
+ let ownerSigner: SignerWithAddress
+ let otherUserSigner: SignerWithAddress;
+
+ before(async() => {
+ lastBlockTimeStamp = await time.latest()
+ const signers = await ethers.getSigners()
+ ownerSigner = signers[0]
+ otherUserSigner= signers[1]
+
+ const unlockTime = lastBlockTimeStamp + UNLOCK_TIME;
+
+ lockInstance = await new Lock__factory(ownerSigner).deploy(unlockTime, {
+ value: VALUE_LOCKED
+ })
+ })
+
+ it('should get the unlockTime value', async() => {
+ const unlockTime = await lockInstance.unlockTime();
+
+ expect(unlockTime).to.equal(lastBlockTimeStamp + UNLOCK_TIME)
+ })
+
+ it('should have the right ether balance', async() => {
+ const lockInstanceAddress = await lockInstance.getAddress()
+
+ const contractBalance = await ethers.provider.getBalance(lockInstanceAddress)
+
+ expect(contractBalance).to.equal(VALUE_LOCKED)
+ })
+
+ it('should have the right owner', async()=> {
+ expect(await lockInstance.owner()).to.equal(ownerSigner.address)
+ })
+
+ it('should not allow to withdraw before unlock time', async()=> {
+ await expect(lockInstance.withdraw()).to.be.revertedWith("You can't withdraw yet")
+ })
+
+ it('should not allow to withdraw a non owner', async()=> {
+ const newLastBlockTimeStamp = await time.latest()
+
+ await time.setNextBlockTimestamp(newLastBlockTimeStamp + UNLOCK_TIME)
+
+ const newInstanceUsingAnotherSigner = lockInstance.connect(otherUserSigner)
+
+ await expect(newInstanceUsingAnotherSigner.withdraw()).to.be.revertedWith("You aren't the owner")
+ })
+
+ it('should allow to withdraw a owner', async()=> {
+ const balanceBefore = await ethers.provider.getBalance(await lockInstance.getAddress());
+
+ expect(balanceBefore).to.equal(VALUE_LOCKED)
+
+ const newLastBlockTimeStamp = await time.latest()
+
+ await time.setNextBlockTimestamp(newLastBlockTimeStamp + UNLOCK_TIME)
+
+ await lockInstance.withdraw();
+
+ const balanceAfter = await ethers.provider.getBalance(await lockInstance.getAddress());
+ expect(balanceAfter).to.equal(0)
+ })
+});
+```
+
+If you run `npx hardhat coverage`, you should get:
+
+```terminal
+ Lock Tests
+ ✔ should get the unlockTime value
+ ✔ should have the right ether balance
+ ✔ should have the right owner
+ ✔ shouldn't allow to withdraw before unlock time
+ ✔ shouldn't allow to withdraw a non owner
+ ✔ should allow to withdraw a owner
+
+ 6 passing (195ms)
+
+------------|----------|----------|----------|----------|----------------|
+File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
+------------|----------|----------|----------|----------|----------------|
+ contracts/ | 100 | 83.33 | 100 | 100 | |
+ Lock.sol | 100 | 83.33 | 100 | 100 | |
+------------|----------|----------|----------|----------|----------------|
+All files | 100 | 83.33 | 100 | 100 | |
+------------|----------|----------|----------|----------|----------------|
+```
+
+Which then gives you a report of the test coverage of your test suite. Notice there is a new folder called `coverage`, which was generated by the `solidity-coverage` plugin. Inside the `coverage` folder there is a `index.html` file. Open it in a browser, you'll see a report similar to the following:
+
+
+
+## Increasing test coverage
+
+Although the coverage of the previous test suite is almost perfect, there is one missing branch when creating the contract. Because you have not tested the condition that the `_unlockTime` has to be greater than the `block.timestamp`:
+
+```solidity
+require(
+ block.timestamp < _unlockTime,
+ "Unlock time should be in the future"
+ );
+```
+
+In order to increase the coverage, include a new test with the following:
+
+```solidity
+ it('should verify the unlock time to be in the future', async () => {
+ const newLockInstance = new Lock__factory(ownerSigner).deploy(lastBlockTimeStamp, {
+ value: VALUE_LOCKED
+ })
+
+ await expect(newLockInstance).to.be.revertedWith("Unlock time should be in the future")
+ })
+```
+
+Then, run `npx hardhat coverage` and you should get:
+
+```solidity
+ Lock Tests
+ ✔ should verify the unlock time to be in the future (39ms)
+ ✔ should get the unlockTime value
+ ✔ should have the right ether balance
+ ✔ should have the right owner
+ ✔ shouldn't allow to withdraw before unlock time
+ ✔ shouldn't allow to withdraw a non owner
+ ✔ should allow to withdraw a owner
+
+ 7 passing (198ms)
+
+------------|----------|----------|----------|----------|----------------|
+File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
+------------|----------|----------|----------|----------|----------------|
+ contracts/ | 100 | 100 | 100 | 100 | |
+ Lock.sol | 100 | 100 | 100 | 100 | |
+------------|----------|----------|----------|----------|----------------|
+All files | 100 | 100 | 100 | 100 | |
+------------|----------|----------|----------|----------|----------------|
+```
+
+## Conclusion
+
+In this tutorial, you've learned how to profile and analyze the test coverage of your smart contracts' test suite. You learned how to visualize the coverage report and improve the coverage of the test suite by using the Solidity Coverage plugin.
+
+## See also
+
+[Hardhat]: https://hardhat.org/
+[Solidity Coverage]: https://github.com/sc-forks/solidity-coverage
+[Hardhat testing lesson]: https://docs.base.org/base-learn/docs/hardhat-testing/hardhat-testing-sbs
+[Base Learn]: https://base.org/learn
+
diff --git a/docs/learn/hardhat/contract-abi-and-testing-vid.mdx b/docs/learn/hardhat/contract-abi-and-testing-vid.mdx
new file mode 100644
index 00000000..116baf84
--- /dev/null
+++ b/docs/learn/hardhat/contract-abi-and-testing-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Contract ABIs and Testing
+description: Learn how the contract ABI is related to writing tests.
+hide_table_of_contents: false
+---
+
+# Contract ABIs and Testing
+
+import {Video} from '/snippets/VideoPlayer.mdx'
+
+
diff --git a/docs/learn/hardhat/contract-abi-testing.mdx b/docs/learn/hardhat/contract-abi-testing.mdx
new file mode 100644
index 00000000..61b9f63c
--- /dev/null
+++ b/docs/learn/hardhat/contract-abi-testing.mdx
@@ -0,0 +1,5 @@
+---
+title: 'Contract ABI and Testing'
+---
+
+[Content about contract ABI and testing goes here]
\ No newline at end of file
diff --git a/docs/learn/hardhat/creating-project.mdx b/docs/learn/hardhat/creating-project.mdx
new file mode 100644
index 00000000..6250b6b2
--- /dev/null
+++ b/docs/learn/hardhat/creating-project.mdx
@@ -0,0 +1,5 @@
+---
+title: 'Creating a Project'
+---
+
+[Content about creating a new Hardhat project goes here]
\ No newline at end of file
diff --git a/docs/learn/hardhat/debugging-smart-contracts.mdx b/docs/learn/hardhat/debugging-smart-contracts.mdx
new file mode 100644
index 00000000..9d026404
--- /dev/null
+++ b/docs/learn/hardhat/debugging-smart-contracts.mdx
@@ -0,0 +1,407 @@
+---
+title: 'Hardhat: Debugging smart contracts'
+slug: /hardhat-debugging
+description: A tutorial that teaches how to debug your smart contracts using Hardhat.
+author: Edson Alcala
+---
+
+# Debugging smart contracts using Hardhat
+
+In this tutorial, you'll learn how to debug your smart contracts using the built-in debugging capabilities of Hardhat.
+
+## Objectives
+
+By the end of this tutorial, you should be able to:
+
+- Use `console.log` to write debugging logs
+- List common errors and their resolutions
+- Determine if an error is a contract error or an error in the test
+
+## Overview
+
+Debugging smart contracts can be a challenging task, especially when dealing with decentralized applications and blockchain technology. Hardhat provides powerful tools to simplify the debugging process.
+
+In this tutorial, you will explore the essential debugging features offered by Hardhat and learn how to effectively identify and resolve common errors in your smart contracts.
+
+## Your first `console.log`
+
+One of the key features of Hardhat is the ability to use `console.log` for writing debugging logs in your smart contracts. In order to use it, you must include `hardhat/console.sol` in the contract you wish to debug.
+
+In the following contract `Lock.sol` for example, you include `hardhat/console.sol` by importing it and adding a few `console.log`s in the constructor with the text "Creating" and the Ether balance of the contract. This can help you not only with tracking that the contract was created successfully but also, more importantly, with the ability to include additional logs such as the balance of the contract after it was created:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "hardhat/console.sol";
+
+contract Lock {
+ uint256 public unlockTime;
+ address payable public owner;
+
+ constructor(uint _unlockTime) payable {
+ require(
+ block.timestamp < _unlockTime,
+ "Unlock time should be in the future"
+ );
+
+ unlockTime = _unlockTime;
+ owner = payable(msg.sender);
+
+ console.log("Creating");
+ console.log("Balance", address(this).balance);
+ }
+}
+```
+
+In order to test it, you need to create a new file in the `test` folder called `Lock.test.ts` with the following content:
+
+```solidity
+import { expect } from "chai";
+import { ethers } from "hardhat";
+
+import { time } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'
+
+import { Lock__factory, Lock } from '../typechain-types'
+
+describe("Lock Tests", function () {
+ const UNLOCK_TIME = 10000;
+ const VALUE_LOCKED = ethers.parseEther("0.01");
+
+ let lastBlockTimeStamp: number;
+ let lockInstance: Lock;
+ let ownerSigner: SignerWithAddress
+
+ before(async () => {
+ lastBlockTimeStamp = await time.latest()
+
+ const signers = await ethers.getSigners()
+ ownerSigner = signers[0]
+
+ lockInstance = await new Lock__factory().connect(ownerSigner).deploy(lastBlockTimeStamp + UNLOCK_TIME, {
+ value: VALUE_LOCKED
+ })
+ })
+
+ it('should get the unlockTime value', async () => {
+ expect(await lockInstance.unlockTime()).to.equal(lastBlockTimeStamp + UNLOCK_TIME)
+ })
+});
+```
+
+Notice that a single test is included in order to get proper logs. However, you're only interested in the creation process that happens in the `before` hook. Then, you can run:
+
+```bash
+npx hardhat test
+```
+
+You should see the following in the terminal:
+
+```bash
+ Lock
+Creating
+Balance 10000000000000000
+ ✔ should get the unlockTime value
+```
+
+The terminal shows the text "Creating" and the balance (which is 0.01 Ether) because during the creation, you are depositing Ether in the smart contract via the `value` property.
+
+### A note about `console.log`
+
+In the previous example, you used `console.log` to include some debugging logs. Be aware that the `console.log` version of Solidity is limited compared to the ones that are provided in other programming languages, where you can log almost anything.
+
+`Console.log` can be called with up to four parameters of the following types:
+
+- uint
+- string
+- bool
+- address
+
+Hardhat includes other `console` functions, such as:
+
+- console.logInt(int i)
+- console.logBytes(bytes memory b)
+- console.logBytes1(bytes1 b)
+- console.logBytes2(bytes2 b)
+- ...
+- console.logBytes32(bytes32 b)
+
+These log functions are handy when the type you intend to log doesn't fall within the default accepted types of `console.log`. For further details, refer to the official [console.log] documentation.
+
+## Identifying common errors
+
+While debugging your smart contracts, it's crucial to be familiar with common errors that can arise during development. Recognizing these errors and knowing how to resolve them is an important skill.
+
+In our [Base Learn] series of tutorials, we cover a few compile-time errors in [Error Triage]. Other errors, such as `reverts` or `index out of bounds errors` can be unexpected during the runtime of the smart contract.
+
+The following explores typical techniques to debug these types of errors.
+
+### Revert errors
+
+When a transaction fails due to a `require` or `revert` statement, you'll need to diagnose why the condition isn't met and then resolve it. Typically, this involves verifying input parameters, state variables, or contract conditions.
+
+The following is the `Lock.sol` contract with a require statement that validates that the parameter you are passing (`_unlockTime`) must be greater than the current `block.timestamp`.
+
+A simple solution to troubleshoot this error is to log the value of `block.timestamp` and `_unlockTime`, which will help you compare these values and then ensure that you are passing the correct ones:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "hardhat/console.sol";
+
+contract Lock {
+ uint public unlockTime;
+ address payable public owner;
+
+ // event Withdrawal(uint amount, uint when);
+
+ constructor(uint _unlockTime) payable {
+ console.log("_unlockTime",_unlockTime);
+ console.log("block.timestamp",block.timestamp);
+ require(
+ block.timestamp < _unlockTime,
+ "Unlock time should be in the future"
+ );
+
+ unlockTime = _unlockTime;
+ owner = payable(msg.sender);
+
+ console.log("Creating");
+ console.log("Balance", address(this).balance);
+ }
+}
+```
+
+When you run the tests with `npx hardhat test`, you'll then see the following:
+
+```bash
+Lock Tests
+_unlockTime 1697493891
+block.timestamp 1697483892
+Creating
+Balance 10000000000000000
+ ✔ should get the unlockTime value
+```
+
+You are now able to see the `block.timestamp` and the value you are passing, which makes it easier to detect the error.
+
+### Unintended behavior errors
+
+Unintended behavior errors occur when you introduce unexpected behavior into the codebase due to a misunderstanding in the way Solidity works.
+
+In the following example, `LockCreator` is a contract that allows anybody to deploy a `Lock.sol` instance. However, the `LockCreator` contains an error: the `createLock` functions are able to accept Ether to be locked but the amount sent is not being transferred to the `Lock` contract:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "hardhat/console.sol";
+
+import {Lock} from "./Lock.sol";
+
+contract LockCreator {
+
+ Lock[] internal locks;
+
+ // Example of bad code, do not use
+ function createLock(uint256 _unlockTime) external payable {
+ Lock newLock = new Lock(_unlockTime);
+ locks.push(newLock);
+ }
+}
+```
+
+You can create a test file `LockCreator.test.ts` that can identify the error and then solve it:
+
+```solidity
+import { ethers } from "hardhat";
+
+import { time } from "@nomicfoundation/hardhat-network-helpers";
+import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'
+
+import { LockCreator, LockCreator__factory } from '../typechain-types'
+
+describe("LockCreator Tests", function () {
+ const UNLOCK_TIME = 10000;
+ const VALUE_LOCKED = ethers.parseEther("0.01");
+
+ let lastBlockTimeStamp: number;
+ let lockInstance: LockCreator;
+ let ownerSigner: SignerWithAddress
+
+ before(async () => {
+ const signers = await ethers.getSigners()
+ ownerSigner = signers[0]
+
+ lockInstance = await new LockCreator__factory().connect(ownerSigner).deploy()
+ })
+
+ it('should create a lock', async () => {
+ lastBlockTimeStamp = await time.latest()
+ await lockInstance.createLock(lastBlockTimeStamp + UNLOCK_TIME, {
+ value: VALUE_LOCKED
+ })
+ })
+});
+```
+
+The following appears in the terminal where you can see the balance is `0`:
+
+```bash
+ LockCreator Tests
+Creating
+Balance 0
+ ✔ should create a lock (318ms)
+```
+
+Although this issue can be avoided by adding more test cases with proper assertions, the re-transfer of Ether from the `LockCreator` was something you may have overlooked.
+
+The solution is to modify the `createLock` function with:
+
+```solidity
+function createLock(uint256 _unlockTime) external payable {
+ Lock newLock = new Lock{ value: msg.value}(_unlockTime);
+ locks.push(newLock);
+}
+```
+
+### Out-of-bounds errors
+
+Attempting to access arrays at an invalid position can also cause errors.
+
+If you wish to retrieve all the `Lock` contract instances being created in the previous example, you can make the `locks` array public. In order to illustrate this example, though, you can create a custom function called `getAllLocks`:
+
+```solidity
+contract LockCreator {
+ //
+ // rest of the code..
+ //
+ function getAllLocks() external view returns(Lock[] memory result) {
+ result = new Lock[](locks.length);
+ for(uint i = 0; i <= locks.length; i++){
+ result[i] = locks[i];
+ }
+ }
+}
+```
+
+The function can be tested with the following test:
+
+```solidity
+describe("LockCreator Tests", function () {
+ const UNLOCK_TIME = 10000;
+ const VALUE_LOCKED = ethers.parseEther("0.01");
+
+ let lastBlockTimeStamp: number;
+ let lockInstance: LockCreator;
+ let ownerSigner: SignerWithAddress
+
+ before(async () => {
+ const signers = await ethers.getSigners()
+ ownerSigner = signers[0]
+
+ lockInstance = await new LockCreator__factory().connect(ownerSigner).deploy()
+
+ lastBlockTimeStamp = await time.latest()
+
+ await lockInstance.createLock(lastBlockTimeStamp + UNLOCK_TIME, {
+ value: VALUE_LOCKED
+ })
+ })
+
+ it('should get all locks', async () => {
+ const allLocks = await lockInstance.getAllLocks()
+
+ console.log("all locks", allLocks)
+ })
+});
+```
+
+Which will then throw an error:
+
+```bash
+LockCreator Tests
+Creating
+Balance 10000000000000000
+ 1) should get all locks
+
+ 0 passing (3s)
+ 1 failing
+
+ 1) LockCreator Tests
+ should get all locks:
+ Error: VM Exception while processing transaction: reverted with panic code 0x32 (Array accessed at an out-of-bounds or negative index)
+```
+
+You can include some debugging logs to identify the issue:
+
+```solidity
+ function getAllLocks() external view returns(Lock[] memory result) {
+ result = new Lock[](locks.length);
+
+ console.log("locks length %s", locks.length);
+
+ for(uint i = 0; i <= locks.length; i++){
+ console.log("Locks index %s", i);
+ result[i] = locks[i];
+ }
+}
+```
+
+Then, you see the following in the terminal:
+
+```bash
+ LockCreator Tests
+Creating
+Balance 10000000000000000
+locks length 1
+Locks index 0
+Locks index 1
+1) LockCreator Tests
+ should get all locks:
+ Error: VM Exception while processing transaction: reverted with panic code 0x32 (Array accessed at an out-of-bounds or negative index)
+```
+
+Since arrays are 0 index based, an array with 1 item will store that item at the 0 index. In the above example, the `if` statement compares `<=` against the length of the array, so it tries to access the element in position 1, and crashes.
+
+Here's the simple solution:
+
+```solidity
+ function getAllLocks() external view returns(Lock[] memory result) {
+ result = new Lock[](locks.length);
+
+ console.log("locks length %s", locks.length);
+
+ for(uint i = 0; i < locks.length; i++){
+ console.log("Locks index %s", i);
+ result[i] = locks[i];
+ }
+}
+```
+
+Which immediately solves the problem:
+
+```bash
+ LockCreator Tests
+Creating
+Balance 10000000000000000
+locks length 1
+Locks index 0
+all locks Result(1) [ '0x83BA8C2028EE8a6476396145C7692fBD09337acD' ]
+ ✔ should get all locks
+
+ 1 passing (3s)
+```
+
+## Conclusion
+
+In this tutorial, you've learned some techniques about how to debug smart contracts using Hardhat. You explored some common cases of various errors and how by simply using `console.log` and a proper test, you can identify and solve the problem.
+
+## See also
+
+[Console.log]: https://hardhat.org/hardhat-network/docs/reference#console.log
+[Error Triage]: https://docs.base.org/learn/error-triage/error-triage
+[Base Learn]: https://base.org/learn
+
diff --git a/docs/learn/hardhat/deploy-with-hardhat.mdx b/docs/learn/hardhat/deploy-with-hardhat.mdx
new file mode 100644
index 00000000..08cfdfc8
--- /dev/null
+++ b/docs/learn/hardhat/deploy-with-hardhat.mdx
@@ -0,0 +1,367 @@
+---
+title: Deploying a smart contract using Hardhat
+slug: /deploy-with-hardhat
+description: "A tutorial that teaches how to deploy a smart contract on the Base test network using Hardhat. Includes instructions for setting up the environment, compiling, and deploying the smart contract."
+author: taycaldwell
+---
+
+This section will guide you through deploying an NFT smart contract (ERC-721) on the Base test network using [Hardhat](https://hardhat.org/).
+
+Hardhat is a developer tool that provides a simple way to deploy, test, and debug smart contracts.
+
+
+
+## Objectives
+
+By the end of this tutorial, you should be able to do the following:
+
+- Setup Hardhat for Base
+- Create an NFT smart contract for Base
+- Compile a smart contract for Base
+- Deploy a smart contract to Base
+- Interact with a smart contract deployed on Base
+
+
+
+## Prerequisites
+
+### Node v18+
+
+This tutorial requires you have Node version 18+ installed.
+
+- Download [Node v18+](https://nodejs.org/en/download/)
+
+If you are using `nvm` to manage your node versions, you can just run `nvm install 18`.
+
+### Coinbase Wallet
+
+In order to deploy a smart contract, you will first need a web3 wallet. You can create a wallet by downloading the Coinbase Wallet browser extension.
+
+- Download [Coinbase Wallet](https://chrome.google.com/webstore/detail/coinbase-wallet-extension/hnfanknocfeofbddgcijnmhnfnkdnaad?hl=en)
+
+### Wallet funds
+
+Deploying contracts to the blockchain requires a gas fee. Therefore, you will need to fund your wallet with ETH to cover those gas fees.
+
+For this tutorial, you will be deploying a contract to the Base Sepolia test network. You can fund your wallet with Base Sepolia ETH using one of the faucets listed on the Base [Network Faucets](https://docs.base.org/chain/network-faucets) page.
+
+
+
+## Creating a project
+
+Before you can begin deploying smart contracts to Base, you need to set up your development environment by creating a Node.js project.
+
+To create a new Node.js project, run:
+
+```bash
+npm init --y
+```
+
+Next, you will need to install Hardhat and create a new Hardhat project
+
+To install Hardhat, run:
+
+```bash
+npm install --save-dev hardhat
+```
+
+To create a new Hardhat project, run:
+
+```bash
+npx hardhat init
+```
+
+Select `Create a TypeScript project` then press _enter_ to confirm the project root.
+
+Select `y` for both adding a `.gitignore` and loading the sample project. It will take a moment for the project setup process to complete.
+
+
+
+## Configuring Hardhat with Base
+
+In order to deploy smart contracts to the Base network, you will need to configure your Hardhat project and add the Base network.
+
+To configure Hardhat to use Base, add Base as a network to your project's `hardhat.config.ts` file:
+
+```tsx
+import { HardhatUserConfig } from 'hardhat/config';
+import '@nomicfoundation/hardhat-toolbox';
+
+require('dotenv').config();
+
+const config: HardhatUserConfig = {
+ solidity: {
+ version: '0.8.23',
+ },
+ networks: {
+ // for mainnet
+ 'base-mainnet': {
+ url: 'https://mainnet.base.org',
+ accounts: [process.env.WALLET_KEY as string],
+ gasPrice: 1000000000,
+ },
+ // for testnet
+ 'base-sepolia': {
+ url: 'https://sepolia.base.org',
+ accounts: [process.env.WALLET_KEY as string],
+ gasPrice: 1000000000,
+ },
+ // for local dev environment
+ 'base-local': {
+ url: 'http://localhost:8545',
+ accounts: [process.env.WALLET_KEY as string],
+ gasPrice: 1000000000,
+ },
+ },
+ defaultNetwork: 'hardhat',
+};
+
+export default config;
+```
+
+### Install Hardhat toolbox
+
+The above configuration uses the `@nomicfoundation/hardhat-toolbox` plugin to bundle all the commonly used packages and Hardhat plugins recommended to start developing with Hardhat.
+
+To install `@nomicfoundation/hardhat-toolbox`, run:
+
+```bash
+npm install --save-dev @nomicfoundation/hardhat-toolbox
+```
+
+### Loading environment variables
+
+The above configuration also uses [dotenv](https://www.npmjs.com/package/dotenv) to load the `WALLET_KEY` environment variable from a `.env` file to `process.env.WALLET_KEY`. You should use a similar method to avoid hardcoding your private keys within your source code.
+
+To install `dotenv`, run:
+
+```bash
+npm install --save-dev dotenv
+```
+
+Once you have `dotenv` installed, you can create a `.env` file with the following content:
+
+```
+WALLET_KEY=""
+```
+
+Substituting `` with the private key for your wallet.
+
+:::caution
+
+`WALLET_KEY` is the private key of the wallet to use when deploying a contract. For instructions on how to get your private key from Coinbase Wallet, visit the [Coinbase Wallet documentation](https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings#show-private-key). **It is critical that you do NOT commit this to a public repo**
+
+:::
+
+### Local Networks
+
+You can run the Base network locally, and deploy using it. If this is what you are looking to do, see the [repo containing the relevant Docker builds](https://github.com/base-org/node).
+
+It will take a **very** long time for your node to sync with the network. If you get errors that the `nonce has already been used` when trying to deploy, you aren't synced yet.
+
+For quick testing, such as if you want to add unit tests to the below NFT contract, you may wish to leave the `defaultNetwork` as `'hardhat'`.
+
+
+
+## Compiling the smart contract
+
+Below is a simple NFT smart contract (ERC-721) written in the Solidity programming language:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.23;
+
+import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
+
+contract NFT is ERC721 {
+ uint256 public currentTokenId;
+
+ constructor() ERC721("NFT Name", "NFT") {}
+
+ function mint(address recipient) public payable returns (uint256) {
+ uint256 newItemId = ++currentTokenId;
+ _safeMint(recipient, newItemId);
+ return newItemId;
+ }
+}
+```
+
+The Solidity code above defines a smart contract named `NFT`. The code uses the `ERC721` interface provided by the [OpenZeppelin Contracts library](https://docs.openzeppelin.com/contracts/5.x/) to create an NFT smart contract. OpenZeppelin allows developers to leverage battle-tested smart contract implementations that adhere to official ERC standards.
+
+To add the OpenZeppelin Contracts library to your project, run:
+
+```bash
+npm install --save @openzeppelin/contracts
+```
+
+In your project, delete the `contracts/Lock.sol` contract that was generated with the project and add the above code in a new file called `contracts/NFT.sol`. (You can also delete the `test/Lock.ts` test file, but you should add your own tests ASAP!).
+
+To compile the contract using Hardhat, run:
+
+```bash
+npx hardhat compile
+```
+
+
+
+## Deploying the smart contract
+
+Once your contract has been successfully compiled, you can deploy the contract to the Base Sepolia test network.
+
+To deploy the contract to the Base Sepolia test network, you'll need to modify the `scripts/deploy.ts` in your project:
+
+```tsx
+import { ethers } from 'hardhat';
+
+async function main() {
+ const nft = await ethers.deployContract('NFT');
+
+ await nft.waitForDeployment();
+
+ console.log('NFT Contract Deployed at ' + nft.target);
+}
+
+// We recommend this pattern to be able to use async/await everywhere
+// and properly handle errors.
+main().catch((error) => {
+ console.error(error);
+ process.exitCode = 1;
+});
+```
+
+You'll also need testnet ETH in your wallet. See the [prerequisites](#prerequisites) if you haven't done that yet. Otherwise, the deployment attempt will fail.
+
+Finally, run:
+
+```bash
+npx hardhat run scripts/deploy.ts --network base-sepolia
+```
+
+The contract will be deployed on the Base Sepolia test network. You can view the deployment status and contract by using a [block explorer](/chain/block-explorers) and searching for the address returned by your deploy script. If you've deployed an exact copy of the NFT contract above, it will already be verified and you'll be able to read and write to the contract using the web interface.
+
+
+
+If you'd like to deploy to mainnet, you'll modify the command like so:
+
+```bash
+npx hardhat run scripts/deploy.ts --network base-mainnet
+```
+
+
+
+Regardless of the network you're deploying to, if you're deploying a new or modified contract, you'll need to verify it first.
+
+
+
+## Verifying the Smart Contract
+
+If you want to interact with your contract on the block explorer, you, or someone, needs to verify it first. The above contract has already been verified, so you should be able to view your version on a block explorer already. For the remainder of this tutorial, we'll walk through how to verify your contract on Base Sepolia testnet.
+
+In `hardhat.config.ts`, configure Base Sepolia as a custom network. Add the following to your `HardhatUserConfig`:
+
+
+```tsx
+etherscan: {
+ apiKey: {
+ "base-sepolia": "PLACEHOLDER_STRING"
+ },
+ customChains: [
+ {
+ network: "base-sepolia",
+ chainId: 84532,
+ urls: {
+ apiURL: "https://api-sepolia.basescan.org/api",
+ browserURL: "https://sepolia.basescan.org"
+ }
+ }
+ ]
+ },
+```
+
+
+You can get your Basescan API key from [basescan.org](https://basescan.org/myapikey) when you sign up for an account.
+
+
+
+
+
+
+```tsx
+// Hardhat expects etherscan here, even if you're using Blockscout.
+etherscan: {
+ apiKey: {
+ "base-sepolia": process.env.BLOCKSCOUT_KEY as string
+ },
+ customChains: [
+ {
+ network: "base-sepolia",
+ chainId: 84532,
+ urls: {
+ apiURL: "https://base-sepolia.blockscout.com/api",
+ browserURL: "https://base-sepolia.blockscout.com"
+ }
+ }
+ ]
+ },
+```
+
+
+You can get your Blockscout API key from [here](https://base-sepolia.blockscout.com/account/api_key) after you sign up for an account.
+
+
+
+
+
+Now, you can verify your contract. Grab the deployed address and run:
+
+```bash
+npx hardhat verify --network base-sepolia
+```
+
+You should see an output similar to:
+
+
+```
+Nothing to compile
+No need to generate any newer typings.
+Successfully submitted source code for contract
+contracts/NFT.sol:NFT at 0x6527E5052de5521fE370AE5ec0aFCC6cD5a221de
+for verification on the block explorer. Waiting for verification result...
+
+Successfully verified contract NFT on Etherscan.
+```
+
+
+
+
+```
+Nothing to compile
+No need to generate any newer typings.
+Successfully submitted source code for contract
+contracts/NFT.sol:NFT at 0x6527E5052de5521fE370AE5ec0aFCC6cD5a221de
+for verification on the block explorer. Waiting for verification result...
+
+Successfully verified contract NFT on Etherscan.
+```
+
+
+
+
+
+
+You can't re-verify a contract identical to one that has already been verified. If you attempt to do so, such as verifying the above contract, you'll get an error similar to:
+
+```text
+Error in plugin @nomiclabs/hardhat-etherscan: The API responded with an unexpected message.
+Contract verification may have succeeded and should be checked manually.
+Message: Already Verified
+```
+
+
+Search for your contract on [Blockscout](https://base-sepolia.blockscout.com/) or [Basescan](https://sepolia.basescan.org/) to confirm it is verified.
+
+## Interacting with the Smart Contract
+
+If you verified on Basescan, you can use the `Read Contract` and `Write Contract` tabs to interact with the deployed contract. You'll need to connect your wallet first, by clicking the Connect button.
+
+
diff --git a/docs/learn/hardhat/deployment-guide.mdx b/docs/learn/hardhat/deployment-guide.mdx
new file mode 100644
index 00000000..ceb2d4b5
--- /dev/null
+++ b/docs/learn/hardhat/deployment-guide.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Deployment Guide'
+---
+
diff --git a/docs/learn/hardhat/deployment-vid.mdx b/docs/learn/hardhat/deployment-vid.mdx
new file mode 100644
index 00000000..f22aef98
--- /dev/null
+++ b/docs/learn/hardhat/deployment-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Deployment
+description: Configure test networks.
+hide_table_of_contents: false
+---
+
+# Deployment
+
+import {Video} from '/snippets/VideoPlayer.mdx'
+
+
diff --git a/docs/learn/hardhat/deployment.mdx b/docs/learn/hardhat/deployment.mdx
new file mode 100644
index 00000000..d25834d4
--- /dev/null
+++ b/docs/learn/hardhat/deployment.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Deployment'
+---
+
diff --git a/docs/learn/hardhat/etherscan-guide.mdx b/docs/learn/hardhat/etherscan-guide.mdx
new file mode 100644
index 00000000..43791588
--- /dev/null
+++ b/docs/learn/hardhat/etherscan-guide.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Etherscan Guide'
+---
+
diff --git a/docs/learn/hardhat/etherscan-video.mdx b/docs/learn/hardhat/etherscan-video.mdx
new file mode 100644
index 00000000..fa5ad753
--- /dev/null
+++ b/docs/learn/hardhat/etherscan-video.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Etherscan Video'
+---
+
diff --git a/docs/learn/hardhat/fork-guide.mdx b/docs/learn/hardhat/fork-guide.mdx
new file mode 100644
index 00000000..90d0b89a
--- /dev/null
+++ b/docs/learn/hardhat/fork-guide.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Fork Guide'
+---
+
diff --git a/docs/learn/hardhat/fork-video.mdx b/docs/learn/hardhat/fork-video.mdx
new file mode 100644
index 00000000..2e646d53
--- /dev/null
+++ b/docs/learn/hardhat/fork-video.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Fork Video'
+---
+
diff --git a/docs/pages/learn/hardhat-deploy/hardhat-deploy-sbs.mdx b/docs/learn/hardhat/hardhat-deploy-sbs.mdx
similarity index 100%
rename from docs/pages/learn/hardhat-deploy/hardhat-deploy-sbs.mdx
rename to docs/learn/hardhat/hardhat-deploy-sbs.mdx
diff --git a/docs/learn/hardhat/hardhat-forking.mdx b/docs/learn/hardhat/hardhat-forking.mdx
new file mode 100644
index 00000000..120159e0
--- /dev/null
+++ b/docs/learn/hardhat/hardhat-forking.mdx
@@ -0,0 +1,179 @@
+---
+title: Hardhat Forking
+description: Learn how to fork
+hide_table_of_contents: false
+---
+
+# Hardhat Forking
+
+In this article, you'll learn how to fork smart contracts in Ethereum mainnet using Hardhat.
+
+---
+
+## Objectives
+
+By the end of this lesson, you should be able to:
+
+- Use Hardhat Network to create a local fork of mainnet and deploy a contract to it
+- Utilize Hardhat forking features to configure the fork for several use cases
+
+---
+
+## Overview
+
+Hardhat forking is a powerful feature that allows developers to create a local replica or fork of the Ethereum network or any other EVM-compatible Blockchain. By using this feature, you can develop smart contracts that rely on smart contracts that are already deployed to a particular network.
+
+You will create a BalanceReader.sol contract that reads the USDC balance of a particular holder.
+
+In order to achieve that, you need to:
+
+- Create the BalanceReader.sol contract
+- Configure Hardhat to support forking
+- Create a test for the BalanceReader.sol contract
+
+Hardhat forking also has other capabilities like:
+
+- hardhat_impersonateAccount (useful to impersonate an account and others)
+- hardhat_stopImpersonatingAccount
+- hardhat_setNonce
+- hardhat_setBalance
+- hardhat_setCode
+- hardhat_setStorageAt
+
+Those won't be covered in this guide, however it's recommended to explore them a bit more in the following link:
+
+- https://hardhat.org/hardhat-network/guides/mainnet-forking.html
+
+## Creating the Balance Reader contract
+
+The BalanceReader contract is created as follows:
+
+```tsx
+pragma solidity 0.8.9;
+
+import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+
+contract BalanceReader {
+ function getERC20BalanceOf(address _account, address _tokenAddress)
+ external
+ view
+ returns (uint256)
+ {
+ // we create an instance only using the interface and the address
+ return IERC20(_tokenAddress).balanceOf(_account);
+ }
+}
+```
+
+You simply pass the address of an account and the address of a token, then you get and return the balance.
+
+You will need to install @openzeppelin by running:
+
+```bash
+npm install @openzeppelin/contracts
+```
+
+Then, check that everything is working correctly by running:
+
+```bash
+npx hardhat compile
+```
+
+You should get:
+
+```
+Generating typings for: 2 artifacts in dir: typechain-types for target: ethers-v6
+Successfully generated 18 typings!
+Compiled 2 Solidity files successfully
+```
+
+## Configuring Hardhat to support forking
+
+By default, Hardhat uses a network called `hardhat`. You must change its default configuration by going to the `hardhat.config.ts` file and include the following in the network:
+
+```json
+hardhat: {
+ forking: {
+ url: `https://eth-mainnet.g.alchemy.com/v2/${process.env.ALCHEMY_MAINNET_KEY ?? ""}`,
+ enabled: true
+ }
+},
+```
+
+Be aware that you need to have an `ALCHEMY_MAINNET_KEY` in your .env file. You can get one directly from [Alchemy](https://www.alchemy.com/).
+
+Also notice that forking is enabled by specifying `enabled: true`, however this value can be changed via environment variables.
+
+## Creating a test for the BalanceReader.sol contract
+
+Create a test file in the test folder called `BalanceReader.ts` and include the following:
+
+```tsx
+import { Signer } from 'ethers';
+import { ethers } from 'hardhat';
+
+import { BalanceReader, BalanceReader__factory } from '../typechain-types';
+
+describe('BalanceReader tests', () => {
+ let instance: BalanceReader;
+ let accounts: Signer[];
+
+ // Configure the addresses we can to check balances for
+ const USDC_MAINNET_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'; // https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48
+ const ARBITRUM_ONE_GATEWAY = '0xcEe284F754E854890e311e3280b767F80797180d';
+ const USDC_DECIMALS = 6;
+
+ it('gets arbitrum gateway balance', async () => {
+ // We get signers as in a normal test
+ accounts = await ethers.getSigners();
+ const factory = new BalanceReader__factory(accounts[0]);
+
+ // We deploy the contract to our local test environment
+ instance = await factory.deploy();
+
+ // Our contract will be able to check the balances of the mainnet deployed contracts and address
+ const balance = await instance.getERC20BalanceOf(ARBITRUM_ONE_GATEWAY, USDC_MAINNET_ADDRESS);
+ const balanceAsString = ethers.utils.formatUnits(balance, USDC_DECIMALS);
+
+ console.log(
+ 'The USDC Balance of Arbitrum Gateway is $',
+ Number(balanceAsString).toLocaleString(),
+ );
+ });
+});
+```
+
+In this example, the [USDC address](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) is used and since USDC is an ERC-20 token, you can explore the token holders of that particular token directly in Etherscan:
+
+
+
+Or, visit https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48#balances, where you can see, at the time or writing, Arbitrum ONE Gateway is the top token holder.
+
+Then, run the following command:
+
+```bash
+npx hardhat test ./test/BalanceReader.ts
+```
+
+You should get:
+
+```
+BalanceReader tests
+The USDC Balance of Arbitrum Gateway is $ 1,116,923,836.506
+ ✔ gets arbitrum gateway balance (4345ms)
+
+ 1 passing (4s)
+```
+
+## Conclusion
+
+In this lesson, you've learned how to use hardhat forking capabilities to test smart contracts. You learned how contracts can interact with already-deployed contracts in an easy way.
+
+---
+
+## See also
+
+[Solidity Docs]: https://docs.soliditylang.org/en/v0.8.17/
+[Remix Project]: https://remix-project.org/
+[Hardhat Deploy]: https://github.com/wighawag/hardhat-deploy
+[Hardhat Forking]: https://hardhat.org/hardhat-network/docs/guides/forking-other-networks
diff --git a/docs/learn/hardhat/hardhat-testing-sbs.mdx b/docs/learn/hardhat/hardhat-testing-sbs.mdx
new file mode 100644
index 00000000..7fe366d1
--- /dev/null
+++ b/docs/learn/hardhat/hardhat-testing-sbs.mdx
@@ -0,0 +1,340 @@
+---
+title: Testing with Hardhat and Typechain
+description: Testing smart contracts with Hardhat and Typechain.
+hide_table_of_contents: false
+---
+
+# Testing with Hardhat and Typechain
+
+In this article, you'll learn how to test smart contracts with Hardhat and Typechain.
+
+---
+
+## Objectives
+
+By the end of this lesson, you should be able to:
+
+- Set up TypeChain to enable testing
+- Write unit tests for smart contracts using Mocha, Chai, and the Hardhat Toolkit
+- Set up multiple signers and call smart contract functions with different signers
+
+---
+
+## Overview
+
+Testing is an important aspect of software development and developing smart contracts is no different. In fact, you need to be more careful because
+smart contracts usually manage money and live in an adversarial environment, where anyone can see the code and interact with your smart contract. This means you can expect bad actors to try to exploit your smart contracts.
+
+## Setup Typechain
+
+In the previous guide, you created a new project using the `init` command that by default installs `@nomicfoundation/hardhat-toolbox`. This package already contains Typechain, which is a plugin that generates static types for your smart contracts. This means you can interact with your contracts and get immediate feedback about the parameters received by a particular function and the functions of a smart contract.
+
+The best way to see its true potential is to start writing tests.
+
+After compiling the hardhat project in the previous lesson, a new folder called `typechain-types` was created, which Typechain is already installed and running.
+
+### Writing your first unit test with Typechain
+
+Hardhat includes a sample smart contract named `Lock.sol` and a sample test inside the test folder named `Lock.ts`.
+
+In the following, you reuse this smart contract but rewrite the test using Typechain.
+
+To remove the body of the `Lock.ts` file:
+
+```tsx
+import { expect } from 'chai';
+import { ethers } from 'hardhat';
+
+describe('Lock', function () {});
+```
+
+Then, import two files from `typechain-types`, `Lock`, and `Lock__Factory`.
+
+Typechain always creates two files per contract. The first one `Lock` refers to the type and functions of a particular contract. `Lock__Factory` is used to deploy the Lock contract or to create instances of a particular contract.
+
+The `Lock.sol` contract allows the creator to lock Ether until an unlock time has passed.
+
+Notice the constructor has a payable keyword:
+
+```tsx
+constructor(uint _unlockTime) payable {
+ require(
+ block.timestamp < _unlockTime,
+ "Unlock time should be in the future"
+ );
+
+ unlockTime = _unlockTime;
+ owner = payable(msg.sender);
+ }
+```
+
+This means the contract is expecting to receive an amount of ether.
+
+Next, test the following:
+
+- The unlock time value
+- The value locked during creation
+- The owner address
+- The withdraw function
+
+
+
+Reveal code
+
+Start with the value locked, however you must set up a `before` function, which will run before each test case.
+
+Then, include some new imports and variables:
+
+```tsx
+import { expect } from 'chai';
+import { ethers } from 'hardhat';
+
+// A helper utility to get the timestamp.
+import { time } from '@nomicfoundation/hardhat-network-helpers';
+
+// We import this type to have our signers typed.
+import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers';
+
+// Types from typechain
+import { Lock__factory, Lock } from '../typechain-types';
+
+describe('Lock', function () {
+ // This represents the time in the future we expect to release the funds locked.
+ const UNLOCK_TIME = 10000;
+
+ // The amount of ether we plan to lock.
+ const VALUE_LOCKED = ethers.parseEther('0.01');
+
+ // This variable will store the last block timestamp.
+ let lastBlockTimeStamp: number;
+
+ // Typechain allow us to type an instance of the Lock contract.
+ let lockInstance: Lock;
+
+ // This is the Signer of the owner.
+ let ownerSigner: SignerWithAddress;
+
+ // A non owner signed is useful to test non owner transactions.
+ let otherUserSigner: SignerWithAddress;
+
+ before(async () => {
+ // We get the latest block.timestamp using the latest function of time.
+ lastBlockTimeStamp = await time.latest();
+
+ // Hardhat provide us with some sample signers that simulate Ethereum accounts.
+ const signers = await ethers.getSigners();
+
+ // We simply assign the first signer to ownerSigner
+ ownerSigner = signers[0];
+
+ // We assign the second signer to otherUserSigner
+ otherUserSigner = signers[1];
+
+ // We estimate unlockTime to be the last time stamp plus UNLOCK_TIME
+ const unlockTime = lastBlockTimeStamp + UNLOCK_TIME;
+
+ // Notice how we use the Lock__factory and pass a signer. Then we deploy by passing the unlockTime and the amount of ether we will lock.
+ lockInstance = await new Lock__factory(ownerSigner).deploy(unlockTime, {
+ value: VALUE_LOCKED,
+ });
+ });
+});
+```
+
+
+
+### Testing `unlockTime`
+
+Next, you include test cases after the `before` function.
+
+The first test case should verify that the `unlockTime` variable is correct.
+
+
+
+Reveal code
+
+```tsx
+it('should get the unlockTime value', async () => {
+ // we get the value from the contract
+ const unlockTime = await lockInstance.unlockTime();
+
+ // We assert against the
+ expect(unlockTime).to.equal(lastBlockTimeStamp + UNLOCK_TIME);
+});
+```
+
+Notice how autocomplete appears after entering `lockInstance`:
+
+
+
+You can simply run `npx hardhat test` and then get:
+
+```
+ Lock
+ ✔ should get the unlockTime value
+
+ 1 passing (1s)
+```
+
+
+
+### Testing Ether balance
+
+In order to get the balance of your `Lock` contract, you simply call `ethers.provider.getBalance`.
+
+Create a new test case:
+
+
+
+Reveal code
+
+```tsx
+it('should have the right ether balance', async () => {
+ // Get the Lock contract address
+ const lockInstanceAddress = await lockInstance.getAddress();
+
+ // Get the balance using ethers.provider.getBalance
+ const contractBalance = await ethers.provider.getBalance(lockInstanceAddress);
+
+ // We assert the balance against the VALUE_LOCKED we initially sent
+ expect(contractBalance).to.equal(VALUE_LOCKED);
+});
+```
+
+
+
+
+Then, run `npx hardhat test` and you should get:
+
+```
+ Lock
+ ✔ should get the unlockTime value
+ ✔ should have the right ether balance
+
+ 2 passing (1s)
+```
+
+### Testing `owner`
+
+Similar to the previous test cases, you can verify that the owner is correct.
+
+
+
+Reveal code
+
+```tsx
+it('should have the right owner', async () => {
+ // Notice ownerSigned has an address property
+ expect(await lockInstance.owner()).to.equal(ownerSigner.address);
+});
+```
+
+
+
+
+Then, run `npx hardhat test` and you should get:
+
+```
+ Lock
+ ✔ should get the unlockTime value
+ ✔ should have the right ether balance
+ ✔ should have the right owner
+
+ 3 passing (1s)
+```
+
+### Testing withdraw
+
+Testing withdrawal is more complex because you need to assert certain conditions, such as:
+
+- The owner cannot withdraw before the unlock time.
+- Only the owner can withdraw.
+- The withdraw function works as expected.
+
+Hardhat allow you to test reverts with a set of custom matchers.
+
+For example, the following code checks that an attempt to call the function `withdraw` reverts with a particular message:
+
+```tsx
+it('shouldn"t allow to withdraw before unlock time', async () => {
+ await expect(lockInstance.withdraw()).to.be.revertedWith("You can't withdraw yet");
+});
+```
+
+In addition, Hardhat also allows you to manipulate the time of the environment where the tests are executed. You can think of it as a Blockchain that is running before the tests and then the tests are executed against it.
+
+You can modify `the block.timestamp` by using the time helper:
+
+```tsx
+it('shouldn"t allow to withdraw a non owner', async () => {
+ const newLastBlockTimeStamp = await time.latest();
+
+ // We set the next block time stamp using this helper.
+ // We assign a value further in the future.
+ await time.setNextBlockTimestamp(newLastBlockTimeStamp + UNLOCK_TIME);
+
+ // Then we try to withdraw using other user signer. Notice the .connect function that is useful
+ // to create and instance but have the msg.sender as the new signer.
+ const newInstanceUsingAnotherSigner = lockInstance.connect(otherUserSigner);
+
+ // We attempt to withdraw, but since the sender is not the owner, it will revert.
+ await expect(newInstanceUsingAnotherSigner.withdraw()).to.be.revertedWith("You aren't the owner");
+});
+```
+
+Finally, test that the owner can withdraw. You can manipulate the time similarly to the previous test case but you won't change the signer and will assert the new balances.
+
+
+
+Reveal code
+
+```tsx
+it('should allow to withdraw an owner', async () => {
+ const balanceBefore = await ethers.provider.getBalance(await lockInstance.getAddress());
+
+ // Its value will be the one we lock at deployment time.
+ expect(balanceBefore).to.equal(VALUE_LOCKED);
+
+ const newLastBlockTimeStamp = await time.latest();
+
+ // We increase time
+ await time.setNextBlockTimestamp(newLastBlockTimeStamp + UNLOCK_TIME);
+
+ // Attempt to withdraw
+ await lockInstance.withdraw();
+
+ // Get new balance and assert that is 0
+ const balanceAfter = await ethers.provider.getBalance(await lockInstance.getAddress());
+ expect(balanceAfter).to.equal(0);
+});
+```
+
+
+
+
+
+You can then run `npx hardhat test` and you should get:
+
+```
+ Lock
+ ✔ should get the unlockTime value
+ ✔ should have the right ether balance
+ ✔ should have the right owner
+ ✔ shouldn"t allow to withdraw before unlock time (51ms)
+ ✔ shouldn"t allow to withdraw a non owner
+ ✔ should allow to withdraw an owner
+
+ 6 passing (2s)
+```
+
+## Conclusion
+
+In this lesson, you've learned how to test smart contracts using Hardhat and Typechain.
+
+---
+
+## See also
+
+[Solidity Docs](https://docs.soliditylang.org/en/v0.8.17/)
+[Remix Project]: https://remix-project.org/
+[Hardhat]: https://hardhat.org/
diff --git a/docs/learn/hardhat/hardhat-verify-sbs.mdx b/docs/learn/hardhat/hardhat-verify-sbs.mdx
new file mode 100644
index 00000000..ba738cd8
--- /dev/null
+++ b/docs/learn/hardhat/hardhat-verify-sbs.mdx
@@ -0,0 +1,111 @@
+---
+title: Verifying Smart Contracts
+description: Verifying smart contracts with Hardhat.
+hide_table_of_contents: false
+---
+
+# Verifying Smart Contracts
+
+In this article, you'll learn how to verify smart contracts in Etherscan with hardhat and the hardhat deploy plugin.
+
+---
+
+## Objectives
+
+By the end of this lesson, you should be able to:
+
+- Verify a deployed smart contract on Etherscan
+- Connect a wallet to a contract in Etherscan
+- Use etherscan to interact with your own deployed contract
+
+---
+
+## Overview
+
+Verifying smart contracts plays an important role in providing security and certainty to the users of your decentralized applications. By offering full visibility of the source code of your smart contract, you provide confidence and transparency of the intention of the code that is being executed.
+
+The way smart contracts are verified is by simply uploading the source code and contract address to services such as Etherscan.
+
+Once the contract is verified, the Etherscan explorer shows a status like the following image:
+
+
+
+Luckily, Hardhat and Hardhat-deploy already contain a built-in capability to do this task easily on your behalf.
+
+This process involves the following steps:
+
+1. Getting an Etherscan key
+2. Configuring Hardhat
+3. Verifying
+
+## Getting an Etherscan key
+
+In order to obtain an Etherscan API key, visit [Etherscan](https://etherscan.io/) and create an account.
+
+Then, go to [https://etherscan.io/myapikey](https://etherscan.io/myapikey) and create an API key by clicking the **Add** button:
+
+
+
+Bear in mind that different networks have other Blockchain explorers. For example:
+
+- [Base](https://basescan.org/)
+- [Sepolia](https://sepolia.etherscan.io/)
+
+You'll need to go to that particular explorer and get the API Key following a similar process as mentioned previously (except for Sepolia Etherscan, where you can use the Etherscan mainnet one instead).
+
+## Configuring Hardhat
+
+You can configure the Etherscan API Key for each different network. For example, include the following to the `hardhat.config.ts` file for Base Sepolia:
+
+```tsx
+base_sepolia: {
+ url: "https://sepolia.base.org",
+ accounts: {
+ mnemonic: process.env.MNEMONIC ?? ""
+ },
+ verify: {
+ etherscan: {
+ apiUrl: "https://api-sepolia.basescan.org",
+ apiKey: process.env.ETHERSCAN_API_KEY
+ }
+ }
+}
+```
+
+Include in your `.env` file the following:
+
+```
+ETHERSCAN_API_KEY=
+```
+
+## Verifying
+
+You verify in base, and to do so, simply run the following command:
+
+```bash
+npx hardhat --network base_sepolia etherscan-verify
+```
+
+You should receive the following response:
+
+```
+verifying Lock ...
+waiting for result...
+ => contract Lock is now verified
+```
+
+You can now go to Basescan and search for your contract address, where you'll see the following:
+
+
+
+## Conclusion
+
+In this lesson, you've learned how to verify smart contracts using Hardhat and Hardhat-deploy. You learned how to configure Hardhat to support multiple networks and verify by using a simple command.
+
+---
+
+## See also
+
+[Solidity Docs]: https://docs.soliditylang.org/en/v0.8.17/
+[Remix Project]: https://remix-project.org/
+[Hardhat Deploy]: https://github.com/wighawag/hardhat-deploy
diff --git a/docs/learn/hardhat/hardhat-verify-vid.mdx b/docs/learn/hardhat/hardhat-verify-vid.mdx
new file mode 100644
index 00000000..8ab187e2
--- /dev/null
+++ b/docs/learn/hardhat/hardhat-verify-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Verifying Smart Contracts
+description: Verify your contracts with Hardhat.
+hide_table_of_contents: false
+---
+
+# Verifying Smart Contracts
+
+import {Video} from '/snippets/VideoPlayer.mdx'
+
+
diff --git a/docs/learn/hardhat/installing-deploy.mdx b/docs/learn/hardhat/installing-deploy.mdx
new file mode 100644
index 00000000..5ac6c722
--- /dev/null
+++ b/docs/learn/hardhat/installing-deploy.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Installing Deploy'
+---
+
diff --git a/docs/learn/hardhat/installing-hardhat-deploy-vid.mdx b/docs/learn/hardhat/installing-hardhat-deploy-vid.mdx
new file mode 100644
index 00000000..81675943
--- /dev/null
+++ b/docs/learn/hardhat/installing-hardhat-deploy-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Installing Hardhat Deploy
+description: Install a community plugin that makes deployments easier.
+hide_table_of_contents: false
+---
+
+# Installing Hardhat Deploy
+
+import {Video} from '/snippets/VideoPlayer.mdx'
+
+
diff --git a/docs/learn/hardhat/mainnet-forking-vid.mdx b/docs/learn/hardhat/mainnet-forking-vid.mdx
new file mode 100644
index 00000000..da6142e8
--- /dev/null
+++ b/docs/learn/hardhat/mainnet-forking-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Forking Mainnet
+description: Create a copy of the mainnet to run advanced tests.
+hide_table_of_contents: false
+---
+
+# Forking Mainnet
+
+import {Video} from '/snippets/VideoPlayer.mdx'
+
+
diff --git a/docs/learn/hardhat/network-configuration.mdx b/docs/learn/hardhat/network-configuration.mdx
new file mode 100644
index 00000000..9a0c33be
--- /dev/null
+++ b/docs/learn/hardhat/network-configuration.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Network Configuration'
+---
+
diff --git a/docs/learn/hardhat/optimizing-gas-usage.md b/docs/learn/hardhat/optimizing-gas-usage.md
new file mode 100644
index 00000000..ce17cfef
--- /dev/null
+++ b/docs/learn/hardhat/optimizing-gas-usage.md
@@ -0,0 +1,423 @@
+---
+title: 'Hardhat: Optimizing the gas usage of smart contracts'
+slug: /hardhat-profiling-gas
+description: A tutorial that teaches how to optimize the gas usage of your smart contracts using Hardhat.
+author: Edson Alcala and Brian Doyle
+---
+
+# Hardhat: Optimizing the gas usage of smart contracts
+
+In this tutorial, you'll learn how to profile and optimize your smart contract's gas usage with Hardhat and the [Hardhat Gas Reporter] plugin.
+
+
+
+## Objectives
+
+By the end of this tutorial you should be able to:
+
+- Use the Hardhat Gas Reporter plugin to profile gas usage
+- Describe common strategies for improving the gas usage of a contract
+
+
+
+## Overview
+
+In the world of smart contract development, optimizing the gas consumption of your smart contracts is important. Smaller contracts consume fewer gas resources during deployment and execution, resulting in significant cost savings for your users. In this tutorial, you will leverage the Hardhat Gas Reporter plugin to help you analyze and optimize your smart contract's gas usage.
+
+The following provides further information about smart contract profiling and gas optimization.
+
+## Setting up the Hardhat Gas Reporter plugin
+
+
+
+The Hardhat Gas Reporter plugin is an invaluable tool for profiling gas usage in your smart contracts. It allows you to gain insights into the gas consumption of various contract functions, making it easier to identify potential optimization opportunities. This tool is particularly useful during development when you want to ensure your contracts are as gas-efficient as possible.
+
+To install, run `npm install -D hardhat-gas-reporter`.
+
+Then, import `hardhat-gas-reporter` in `hardhat.config.ts`:
+
+```solidity
+import "hardhat-gas-reporter"
+```
+
+Configure the plugin in the `hardhat.config.ts` file:
+
+```tsx
+const config: HardhatUserConfig = {
+ // ....
+ gasReporter: {
+ enabled: true,
+ },
+};
+```
+
+When finished, you are ready to use the plugin.
+
+## Your first gas profiling
+
+Create a contract called `Store` with the following settings:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+contract Store {
+ address public owner;
+ uint256 public numberOfItems;
+
+ struct Item {
+ uint256 id;
+ string description;
+ uint256 price;
+ }
+
+ // id => item
+ mapping(uint256 => Item) public items;
+
+ constructor() {
+ owner = msg.sender;
+ }
+
+ function addItem(string memory description, uint256 price) external {
+ require(msg.sender == owner, "invalid owner");
+
+ numberOfItems++;
+
+ items[numberOfItems] = Item(
+ numberOfItems,
+ description,
+ price
+ );
+ }
+}
+
+```
+
+Add a test file called `Store.test.ts` in order to test the gas reporter plugin. The test file should contain the following:
+
+```tsx
+import { expect } from 'chai';
+import { ethers } from 'hardhat';
+import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers';
+
+import { Store, Store__factory } from '../typechain-types';
+
+describe('Store tests', function () {
+ let instance: Store;
+ let owner: HardhatEthersSigner;
+
+ before(async () => {
+ const signers = await ethers.getSigners();
+ owner = signers[0];
+ instance = await new Store__factory().connect(owner).deploy();
+ });
+
+ it('should add an item', async () => {
+ const description = 'TShirt';
+ const price = ethers.parseEther('1');
+
+ await instance.addItem(description, price);
+
+ expect(await instance.numberOfItems()).to.equal(1);
+ });
+});
+```
+
+Run `npx hardhat test`. The following report appears:
+
+```
+·------------------------|---------------------------|---------------|-----------------------------·
+| Solc version: 0.8.18 · Optimizer enabled: true · Runs: 10000 · Block limit: 30000000 gas │
+·························|···························|···············|······························
+| Methods │
+·············|···········|·············|·············|···············|···············|··············
+| Contract · Method · Min · Max · Avg · # calls · usd (avg) │
+·············|···········|·············|·············|···············|···············|··············
+| Store · addItem · - · - · 113601 · 1 · - │
+·············|···········|·············|·············|···············|···············|··············
+| Deployments · · % of limit · │
+·························|·············|·············|···············|···············|··············
+| Store · - · - · 428837 · 1.4 % · - │
+·------------------------|-------------|-------------|---------------|---------------|-------------·
+```
+
+The reporter provides a detailed overview of the gas costs for the function `addItem` and the deployment costs.
+
+## Common strategies to optimize contract sizes
+
+
+
+After performing the first gas profiling, you can start ideating strategies to improve the gas costs. These strategies are certainly vast and this tutorial only covers some basic examples.
+
+### Using the optimizer
+
+From the previous report, you can identify that the optimizer of the project has a value of 10000 runs. This means the deployment costs will be more expensive. However, if you modify that value to 200, you get:
+
+```
+·------------------------|---------------------------|-------------|-----------------------------·
+| Solc version: 0.8.18 · Optimizer enabled: true · Runs: 200 · Block limit: 30000000 gas │
+·························|···························|·············|······························
+| Methods │
+·············|···········|·············|·············|·············|···············|··············
+| Contract · Method · Min · Max · Avg · # calls · usd (avg) │
+·············|···········|·············|·············|·············|···············|··············
+| Store · addItem · - · - · 113619 · 1 · - │
+·············|···········|·············|·············|·············|···············|··············
+| Deployments · · % of limit · │
+·························|·············|·············|·············|···············|··············
+| Store · - · - · 357505 · 1.2 % · - │
+·------------------------|-------------|-------------|-------------|---------------|-------------·
+```
+
+This automatically gives you some improvements for deployment gas costs but slightly more for transaction executions.
+
+### Using immutable variables
+
+In our `Store` contract, you can identify certain variables that are only set during the creation of the contract. This means that an opportunity is possible to turn those variables into immutable, since immutable variables can still be assigned at construction time.
+
+If you modify the `Store` contract to:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+contract Store {
+ address immutable owner;
+ uint256 public numberOfItems;
+
+ struct Item {
+ uint256 id;
+ string description;
+ uint256 price;
+ }
+
+ // id => item
+ mapping(uint256 => Item) public items;
+
+ constructor() {
+ owner = msg.sender;
+ }
+
+ function addItem(string memory description, uint256 price) external {
+ require(msg.sender == owner, "invalid owner");
+
+ numberOfItems++;
+
+ items[numberOfItems] = Item(
+ numberOfItems,
+ description,
+ price
+ );
+ }
+}
+```
+
+Then, run the gas reporter. You should see:
+
+```
+·------------------------|---------------------------|-------------|-----------------------------·
+| Solc version: 0.8.18 · Optimizer enabled: true · Runs: 200 · Block limit: 30000000 gas │
+·························|···························|·············|······························
+| Methods │
+·············|···········|·············|·············|·············|···············|··············
+| Contract · Method · Min · Max · Avg · # calls · usd (avg) │
+·············|···········|·············|·············|·············|···············|··············
+| Store · addItem · - · - · 111525 · 1 · - │
+·············|···········|·············|·············|·············|···············|··············
+| Deployments · · % of limit · │
+·························|·············|·············|·············|···············|··············
+| Store · - · - · 329580 · 1.1 % · - │
+·------------------------|-------------|-------------|-------------|---------------|-------------·
+```
+
+Which already presents some improvements.
+
+### Avoid unnecessary data storage
+
+Storing data and not storing data in a smart contract is a design decision that has pros and cons. Some of the pros are certainly that all the information is stored in the smart contract and you don't necessarily need to rely on events or any other service to access the storage of a contract. However, the cons of storing all the information on the contract is the fact that it will be more expensive to perform actions against the smart contract.
+
+In the `Store` smart contract, you have the following:
+
+```solidity
+struct Item {
+ uint256 id;
+ string description;
+ uint256 price;
+ }
+
+// id => item
+mapping(uint256 => Item) public items;
+```
+
+Looking closely, you can see that the `Id` of the `Item` struct and the `id` used in the mapping are similar. You can avoid duplicating this information by removing the id of the `Item` struct.
+
+The contract looks like:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+contract Store {
+ address immutable owner;
+ uint256 public numberOfItems;
+
+ struct Item {
+ string description;
+ uint256 price;
+ }
+
+ // id => item
+ mapping(uint256 => Item) public items;
+
+ constructor() {
+ owner = msg.sender;
+ }
+
+ function addItem(string memory description, uint256 price) external {
+ require(msg.sender == owner, "invalid owner");
+
+ numberOfItems++;
+
+ items[numberOfItems] = Item(
+ description,
+ price
+ );
+ }
+}
+```
+
+If you run the gas reporter, you then get:
+
+```
+·------------------------|---------------------------|-------------|-----------------------------·
+| Solc version: 0.8.18 · Optimizer enabled: true · Runs: 200 · Block limit: 30000000 gas │
+·························|···························|·············|······························
+| Methods │
+·············|···········|·············|·············|·············|···············|··············
+| Contract · Method · Min · Max · Avg · # calls · usd (avg) │
+·············|···········|·············|·············|·············|···············|··············
+| Store · addItem · - · - · 89371 · 1 · - │
+·············|···········|·············|·············|·············|···············|··············
+| Deployments · · % of limit · │
+·························|·············|·············|·············|···············|··············
+| Store · - · - · 322251 · 1.1 % · - │
+·------------------------|-------------|-------------|-------------|---------------|-------------·
+```
+
+This presents another improvement in the gas consumption of the `Store` smart contract. However, you can go further and instead of storing the items in a `mapping`, you can simply emit `events` and use the events as a cheap form of storage.
+
+For instance, you can modify the contract to look like:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+contract Store {
+ address immutable owner;
+ uint256 public numberOfItems;
+
+ struct Item {
+ string description;
+ uint256 price;
+ }
+
+ event ItemCreated(uint256 id, Item item);
+
+ constructor() {
+ owner = msg.sender;
+ }
+
+ function addItem(string memory description, uint256 price) external {
+ require(msg.sender == owner, "invalid owner");
+
+ numberOfItems++;
+
+ emit ItemCreated(numberOfItems, Item(description, price));
+ }
+}
+```
+
+Notice how instead of storing the items, you emit an `ItemCreated` event, which reduces the gas costs for deployment and execution:
+
+```
+·------------------------|---------------------------|-------------|-----------------------------·
+| Solc version: 0.8.18 · Optimizer enabled: true · Runs: 200 · Block limit: 30000000 gas │
+·························|···························|·············|······························
+| Methods │
+·············|···········|·············|·············|·············|···············|··············
+| Contract · Method · Min · Max · Avg · # calls · usd (avg) │
+·············|···········|·············|·············|·············|···············|··············
+| Store · addItem · - · - · 47315 · 1 · - │
+·············|···········|·············|·············|·············|···············|··············
+| Deployments · · % of limit · │
+·························|·············|·············|·············|···············|··············
+| Store · - · - · 208252 · 0.7 % · - │
+·------------------------|-------------|-------------|-------------|---------------|-------------·
+```
+
+As you can see, the improvements in terms of gas consumption are significant. However, the draw back is that now in order to access all of the items, you must go through all of the `ItemCreated` events emitted by the contract.
+
+### Using custom errors
+
+Another common way to optimize gas costs is by removing `require`s and use custom errors. For instance, you can do the following:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+contract Store {
+ address immutable owner;
+ uint256 public numberOfItems;
+
+ error InvalidOwner();
+
+ struct Item {
+ string description;
+ uint256 price;
+ }
+
+ event ItemCreated(uint256 id, Item item);
+
+ constructor() {
+ owner = msg.sender;
+ }
+
+ function addItem(string memory description, uint256 price) external {
+ if(msg.sender != owner){
+ revert InvalidOwner();
+ }
+
+ numberOfItems++;
+
+ emit ItemCreated(numberOfItems, Item(description, price));
+ }
+}
+```
+
+Which gives you the following report:
+
+```
+·------------------------|---------------------------|-------------|-----------------------------·
+| Solc version: 0.8.18 · Optimizer enabled: true · Runs: 200 · Block limit: 30000000 gas │
+·························|···························|·············|······························
+| Methods │
+·············|···········|·············|·············|·············|···············|··············
+| Contract · Method · Min · Max · Avg · # calls · usd (avg) │
+·············|···········|·············|·············|·············|···············|··············
+| Store · addItem · - · - · 47315 · 1 · - │
+·············|···········|·············|·············|·············|···············|··············
+| Deployments · · % of limit · │
+·························|·············|·············|·············|···············|··············
+| Store · - · - · 200683 · 0.7 % · - │
+·------------------------|-------------|-------------|-------------|---------------|-------------·
+```
+
+Notice the improvement in deployment gas costs.
+
+## Conclusion
+
+In this tutorial, you've learned some common strategies to profile and optimize the gas usage of your smart contracts using the Hardhat development environment and the Hardhat Gas Reporter plugin. By implementing these strategies and leveraging the Hardhat Gas Reporter plugin, you can create more efficient and cost-effective smart contracts for the benefit of the users, since this means less gas costs.
+
+---
+
+[Hardhat Gas Reporter]: https://www.npmjs.com/package/hardhat-gas-reporter
diff --git a/docs/learn/hardhat/overview.mdx b/docs/learn/hardhat/overview.mdx
new file mode 100644
index 00000000..3c649f94
--- /dev/null
+++ b/docs/learn/hardhat/overview.mdx
@@ -0,0 +1,74 @@
+---
+title: 'Overview'
+slug: /hardhat-tools-and-testing/overview
+description: What's in this learning material.
+author: Brian Doyle
+keywords:
+ [
+ Hardhat Tools,
+ Smart Contract Development,
+ Gas Optimization,
+ Debugging,
+ Test Coverage,
+ Contract Size,
+ Solidity,
+ Base network,
+ Base blockchain,
+ blockchain development,
+ ]
+hide_table_of_contents: false
+displayed_sidebar: null
+---
+
+# Overview of Hardhat Tools and Testing
+
+This series of guides shows you how to use a number of [Hardhat plugins] that will help you more effectively build and test your smart contracts.
+
+Learn how to keep your contracts under the 24 kiB limit, improve gas costs for your users, make sure your unit tests fully cover your code, and practice debugging.
+
+---
+
+## Objectives
+
+By the end of these guides, you should be able to:
+
+### Profiling Size
+
+- Use Hardhat Contract Sizer plugin to profile contract size
+- Describe common strategies for managing the contract size limit
+- Describe the impact that inheritance has on the byte code size limit
+- Describe the impact that external contracts have on the byte code size limit
+- Describe the impact of using libraries has on the byte code size limit
+- Describe the impact of using the Solidity optimizer
+
+### Profiling Gas
+
+- Use the Hardhat Gas Reporter plugin to profile gas usage
+- Describe common strategies for improving the gas usage of a contract
+
+### Debugging
+
+- Use `console.log` to write debugging logs
+- List common errors and their resolutions
+- Determine if an error is a contract error or an error in the test
+
+### Test Coverage
+
+- Use the Solidity Coverage plugin to analyze the coverage of your test suite
+- Increase the coverage of your test suite
+
+---
+
+## Prerequisites
+
+### 1. Basic understanding of writing smart contracts
+
+These guides assume that you're reasonably comfortable writing basic smart contracts. If you're just getting started, jump over to our [Base Learn] guides and start learning!
+
+### 2. Familiarity with Hardhat
+
+We also assume that you've got Hardhat up and running, and can write unit tests for your smart contracts. If you're not there yet, but already know Solidity, you can [setup Hardhat here].
+
+[setup Hardhat here]: https://docs.base.org/base-learn/docs/hardhat-setup-overview/hardhat-setup-overview-sbs
+[Hardhat plugins]: https://hardhat.org/hardhat-runner/plugins
+[Base Learn]: https://base.org/learn
diff --git a/docs/learn/hardhat/reducing-contract-size.md b/docs/learn/hardhat/reducing-contract-size.md
new file mode 100644
index 00000000..876e0447
--- /dev/null
+++ b/docs/learn/hardhat/reducing-contract-size.md
@@ -0,0 +1,502 @@
+---
+title: 'Hardhat: Optimizing the size of smart contracts'
+slug: /hardhat-profiling-size
+description: A tutorial that teaches how to optimize the size of your smart contracts using Hardhat.
+author: Edson Alcala and Brian Doyle
+keywords:
+ [
+ Smart Contract Sizes,
+ Hardhat Contract Sizer,
+ Base network,
+ Base blockchain,
+ Blockchain,
+ Contract Optimization,
+ Inheritance,
+ External Contracts,
+ Solidity Optimizer,
+ Smart Contract Development,
+ ]
+tags: ['smart contracts']
+difficulty: beginner
+hide_table_of_contents: false
+displayed_sidebar: null
+---
+
+# Hardhat: Optimizing the size of smart contracts
+
+In this tutorial, you'll learn how to profile and optimize smart contract sizes with Hardhat and the [Hardhat Contract Sizer] plugin.
+
+
+
+## Objectives
+
+By the end of this tutorial you should be able to:
+
+- Use Hardhat Contract Sizer plugin to profile contract size
+- Describe common strategies for managing the contract size limit
+- Describe the impact that inheritance has on the byte code size limit
+- Describe the impact that external contracts have on the byte code size limit
+- Describe the impact of using libraries has on the byte code size limit
+- Describe the impact of using the Solidity optimizer
+
+
+
+## Overview
+
+In the world of blockchain and Ethereum, optimizing smart contract sizes is crucial. Smaller contracts consume less gas during deployment and execution, which is translated into gas costs savings for your users. Fortunately, you can use in Hardhat the `hardhat-contract-sizer` plugin that helps you analyze and optimize the size of your smart contracts.
+
+## Setting up the Hardhat Contract Sizer plugin
+
+
+
+Hardhat Contract Sizer is a community-developed plugin that enables the profiling of smart contract by printing the size of your smart contracts in the terminal. This is helpful during development since it allows you to immediately identify potential issues with the size of your smart contracts. Keep in mind that the [maximum size of a smart contract in Ethereum] is 24 KiB.
+
+To install, run `npm install -D hardhat-contract-sizer`.
+
+Then, import `hardhat-contract-sizer` in `hardhat.config.ts`:
+
+```solidity
+import "hardhat-contract-sizer"
+```
+
+When finished, you are ready to use the plugin.
+
+## Your first size profiling
+
+Similar to the previous tutorials, you begin by profiling the smart contract `Lock.sol`.
+
+Run `npx hardhat size-contracts`, which is a task added to Hardhat once you set up and configure the `hardhat-contract-sizer` plugin.
+
+You are then able to see:
+
+```
+ ·------------------------|--------------------------------|--------------------------------·
+ | Solc version: 0.8.18 · Optimizer enabled: false · Runs: 200 │
+ ·························|································|·································
+ | Contract Name · Deployed size (KiB) (change) · Initcode size (KiB) (change) │
+ ·························|································|·································
+ | BalanceReader · 0.612 () · 0.644 () │
+ ·························|································|·································
+ | Lock · 1.009 () · 1.461 () │
+```
+
+Although your contract is simple, you can see immediately the power of the `hardhat-contract-sizer` plugin, since it show you the size of your contracts.
+
+## Common strategies to optimize contract sizes
+
+
+
+In order to illustrate some of the strategies to optimize the size of your contracts, create two smart contracts, `Calculator.sol` and `ScientificCalculator.sol`, with the following:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+contract Calculator {
+ function add(uint256 a, uint256 b) external pure returns(uint256) {
+ require(a > 0 && b > 0, "Invalid values");
+ return a + b;
+ }
+
+ function sub(uint256 a, uint256 b) external pure returns(uint256) {
+ require(a > 0 && b > 0, "Invalid values");
+ return a - b;
+ }
+
+ function mul(uint256 a, uint256 b) external pure returns(uint256) {
+ require(a > 0 && b > 0, "Invalid values");
+ return a * b;
+ }
+
+ function div(uint256 a, uint256 b) external pure returns(uint256) {
+ require(a > 0 && b > 0, "Invalid values");
+ return a / b;
+ }
+}
+```
+
+```solidity
+contract ScientificCalculator is Calculator {
+ function power(uint256 base, uint256 exponent) public pure returns (uint256) {
+ require(base > 0 && exponent > 0, "Invalid values");
+
+ return base ** exponent;
+ }
+}
+```
+
+Then, run the command `npx hardhat size-contracts` again and you should be able to see:
+
+```
+ ·------------------------|--------------------------------|--------------------------------·
+ | Solc version: 0.8.18 · Optimizer enabled: false · Runs: 200 │
+ ·························|································|·································
+ | Contract Name · Deployed size (KiB) (change) · Initcode size (KiB) (change) │
+ ·························|································|·································
+ | BalanceReader · 0.612 (0.000) · 0.644 (0.000) │
+ ·························|································|·································
+ | Lock · 1.009 (0.000) · 1.461 (0.000) │
+ ·························|································|·································
+ | Calculator · 1.299 () · 1.330 () │
+ ·························|································|·································
+ | ScientificCalculator · 1.827 () · 1.858 () │
+ ·------------------------|--------------------------------|--------------------------------·
+```
+
+Notice how the size of `ScientificCalculator` is bigger than `Calculator`. This is because `ScientificCalculator` is inheriting the contract `Calculator`, which means all of its functionality and code is available in `ScientificCalculator` and that will influence its size.
+
+### Code abstraction and modifiers
+
+At this point as a smart contract developer, you can review your smart contract code and look for ways into you can optimize it.
+
+The first thing you notice in the source code is the extensive use of:
+
+```solidity
+require(a > 0 && b > 0, "Invalid values");
+```
+
+A possible optimization is to abstract repetitive code into [modifiers], such as the following:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+contract Calculator {
+ error InvalidInput();
+
+ function add(uint256 a, uint256 b) external pure onlyValidInputs(a,b) returns(uint256) {
+ return a + b;
+ }
+
+ function sub(uint256 a, uint256 b) external pure onlyValidInputs(a,b) returns(uint256) {
+ return a - b;
+ }
+
+ function mul(uint256 a, uint256 b) external pure onlyValidInputs(a,b) returns(uint256) {
+ return a * b;
+ }
+
+ function div(uint256 a, uint256 b) external pure onlyValidInputs(a,b) returns(uint256) {
+ return a / b;
+ }
+
+ modifier onlyValidInputs(uint256 a, uint256 b) {
+ if(a == 0 && b == 0){
+ revert InvalidInput();
+ }
+ _;
+ }
+}
+```
+
+And for `ScientificCalculator`:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "./Calculator.sol";
+
+contract ScientificCalculator is Calculator {
+ function power(uint256 base, uint256 exponent) public pure onlyValidInputs(base,exponent) returns (uint256) {
+ return base ** exponent;
+ }
+}
+```
+
+Notice the usage of the modifier and the replacement of the require to use a custom error.
+
+When you run the `npx hardhat size-contracts` command, you should be able to see:
+
+```
+ ·------------------------|--------------------------------|--------------------------------·
+ | Solc version: 0.8.18 · Optimizer enabled: false · Runs: 200 │
+ ·························|································|·································
+ | Contract Name · Deployed size (KiB) (change) · Initcode size (KiB) (change) │
+ ·························|································|·································
+ | BalanceReader · 0.612 (0.000) · 0.644 (0.000) │
+ ·························|································|·································
+ | Lock · 1.009 (0.000) · 1.461 (0.000) │
+ ·························|································|·································
+ | Calculator · 1.165 (0.000) · 1.196 (0.000) │
+ ·························|································|·································
+ | ScientificCalculator · 1.690 (0.000) · 1.722 (0.000) │
+ ·------------------------|--------------------------------|--------------------------------·
+```
+
+Although the optimization is small, you can see that there are some improvements.
+
+You can continue this process until you feel comfortable with the size of the contract.
+
+### Split into multiple contracts
+
+It is common to split your smart contracts into multiple contracts, not only because of the size limitations but to create better abstractions, to improve readability, and to avoid repetition.
+
+From a contract size perspective, having multiple independent contracts will reduce the size of each contract. For example, the original size of a smart contract was 30 KiB: by splitting into 2, you will end up with 2 smart contracts of ~15 KiB that are within the limits of Solidity. Keep in mind that this will influence gas costs during the execution of the contract because it will require it to call an external contract.
+
+In order to explain this example, create a contract called `Computer` that contains a function called `executeProcess`:
+
+```tsx
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "hardhat/console.sol";
+
+contract Computer {
+ function executeProcess() external view {
+ // ...logic to be implemented
+ }
+}
+```
+
+In this example, the `executeProcess` function of `Computer` requires certain functionality of `Calculator` and a new contract called `Printer`:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "hardhat/console.sol";
+
+contract Printer {
+ function print(string memory _content) external view {
+ require(bytes(_content).length > 0, "invalid length");
+ console.log(_content);
+ }
+}
+```
+
+The easiest way for `Computer` to access both functionalities is to inherit; however, as all of these contracts continue adding functionality, the size of the code will also increase. You will reach the contract size issue at some point, since you are copying the entire functionality into your contract. You can better allow that functionality to be kept with their specific contracts and if the `Computer` requires to access that functionality, you could call the `Calculator` and `Printer` contracts.
+
+But in this example, there is a process that must call both `Calculator` and `Printer`:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "hardhat/console.sol";
+
+import "./Calculator.sol";
+import "./Printer.sol";
+
+contract Computer {
+ Calculator calculator;
+ Printer printer;
+
+ constructor(address _calculator, address _printer) {
+ calculator = Calculator(_calculator);
+ printer = Printer(_printer);
+ }
+
+ function executeProcess() external view {
+ // call Calculator contract, i.e calculator.add(a, b);
+ // call Printer contract, i.e printer.print("value to print");
+ }
+}
+```
+
+If you run the contract sizer plugin, you get:
+
+```
+ ·------------------------|--------------------------------|--------------------------------·
+ | Solc version: 0.8.18 · Optimizer enabled: true · Runs: 10000 │
+ ·························|································|·································
+ | Contract Name · Deployed size (KiB) (change) · Initcode size (KiB) (change) │
+ ·························|································|·································
+ | console · 0.084 (0.000) · 0.138 (0.000) │
+ ·························|································|·································
+ | Computer · 0.099 (0.000) · 0.283 (0.000) │
+ ·························|································|·································
+ | Calculator · 0.751 (0.000) · 0.782 (0.000) │
+ ·························|································|·································
+ | Printer · 0.761 (0.000) · 0.792 (0.000) │
+ ·························|································|·································
+ | ScientificCalculator · 1.175 (0.000) · 1.206 (0.000) │
+ ·------------------------|--------------------------------|--------------------------------·
+```
+
+Notice how your `Computer` contract is very small but still has the capability to access all the functionality of `Printer` and `Calculator`.
+
+Although this will reduce the size of each contract, the costs of this are discussed more deeply in the [Gas Optimization] article.
+
+### Using libraries
+
+Libraries are another common way to encapsulate and abstract common functionality that can be shared across multiple contracts. This can significantly impact the bytecode size of the smart contracts. Remember that in Solidity, libraries can be external and internal.
+
+The way internal libraries affect the contract size is very similar to the way inherited contracts affects a contract's size; this is because the internal functions of the library is included within the final bytecode.
+
+But when the libraries are external, the behavior is different: the way Solidity calls external libraries is by using a special function called [delegate call].
+
+External libraries are commonly deployed independently and can be reused my multiple contracts. Since libraries don't keep a state, they behave like pure functions in the Blockchain.
+
+In this example, your computer will use the `Calculator` library only. Then, you would have the following:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+library Calculator {
+ error InvalidInput();
+
+ function add(uint256 a, uint256 b) external pure onlyValidInputs(a,b) returns(uint256) {
+ return a + b;
+ }
+
+ function sub(uint256 a, uint256 b) external pure onlyValidInputs(a,b) returns(uint256) {
+ return a - b;
+ }
+
+ function mul(uint256 a, uint256 b) external pure onlyValidInputs(a,b) returns(uint256) {
+ return a * b;
+ }
+
+ function div(uint256 a, uint256 b) external pure onlyValidInputs(a,b) returns(uint256) {
+ return a / b;
+ }
+
+ modifier onlyValidInputs(uint256 a, uint256 b) {
+ if(a == 0 && b == 0){
+ revert InvalidInput();
+ }
+ _;
+ }
+}
+```
+
+Then, `Computer` is:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "hardhat/console.sol";
+
+import "./Calculator.sol";
+import "./Printer.sol";
+
+contract Computer {
+ using Calculator for uint256;
+
+ function executeProcess() external view {
+ uint256 a = 1;
+ uint256 b = 2;
+ uint256 result = a.add(b);
+ // ... logic to be implemented
+ }
+}
+```
+
+Notice how you instructing the smart contract to use the `Calculator` library for `uint256` and how in the `executeProcess` function, you can now use the `add` function from the `Calculator` library in all of the `uint256`.
+
+If you run the `npx hardhat size-contracts` command, you then get:
+
+```
+ ·------------------------|--------------------------------|--------------------------------·
+ | Solc version: 0.8.18 · Optimizer enabled: true · Runs: 10000 │
+ ·························|································|·································
+ | Contract Name · Deployed size (KiB) (change) · Initcode size (KiB) (change) │
+ ·························|································|·································
+ | Calculator · 0.761 · 0.817 │
+ ·························|································|·································
+ | Printer · 0.771 · 0.827 │
+ ·························|································|·································
+ | Computer · 0.961 · 0.992 │
+ ·------------------------|--------------------------------|--------------------------------·
+```
+
+In order to compare the impact, you can modify the external modifier from all of the `Calculator` library functions and you will then have:
+
+```
+ ·------------------------|--------------------------------|--------------------------------·
+ | Solc version: 0.8.18 · Optimizer enabled: true · Runs: 10000 │
+ ·························|································|·································
+ | Contract Name · Deployed size (KiB) (change) · Initcode size (KiB) (change) │
+ ·························|································|·································
+ | Calculator · 0.084 · 0.138 │
+ ·························|································|·································
+ | Printer · 0.084 · 0.138 │
+ ·························|································|·································
+ | Computer · 1.139 · 1.170 │
+ ·------------------------|--------------------------------|--------------------------------·
+```
+
+Which demonstrates why using external libraries can be a good option in order to optimize the size of your contracts.
+
+### Using the Solidity compiler optimizer
+
+
+
+Another way to optimize the size of the smart contracts is to simply use the Solidity optimizer.
+
+From the [Solidity official docs]:
+
+> Overall, the optimizer tries to simplify complicated expressions, which reduces both code size and execution cost.
+
+You can enable the solidity optimizer in hardhat by simply adding the following to the `hardhat.config.ts` file:
+
+```solidity
+const config: HardhatUserConfig = {
+ solidity: {
+ version: "0.8.18",
+ settings: {
+ optimizer: {
+ enabled: true,
+ runs: 200
+ }
+ }
+ },
+ ...
+}
+```
+
+Notice the optimizer is enabled and has a parameter `runs`. If you run the contract sizer command again, you will see the following:
+
+```
+ ·------------------------|--------------------------------|--------------------------------·
+ | Solc version: 0.8.18 · Optimizer enabled: true · Runs: 200 │
+ ·························|································|·································
+ | Contract Name · Deployed size (KiB) (change) · Initcode size (KiB) (change) │
+ ·························|································|·································
+ | BalanceReader · 0.351 (-0.262) · 0.382 (-0.262) │
+ ·························|································|·································
+ | Lock · 0.471 (-0.538) · 0.661 (-0.800) │
+ ·························|································|·································
+ | Calculator · 0.604 (-0.561) · 0.636 (-0.561) │
+ ·························|································|·································
+ | ScientificCalculator · 0.930 (-0.761) · 0.961 (-0.761) │
+ ·------------------------|--------------------------------|--------------------------------·
+```
+
+Notice the bigger improvement, but see what happens if you increase the `runs` parameter value to 1000:
+
+```
+ ·------------------------|--------------------------------|--------------------------------·
+ | Solc version: 0.8.18 · Optimizer enabled: true · Runs: 1000 │
+ ·························|································|·································
+ | Contract Name · Deployed size (KiB) (change) · Initcode size (KiB) (change) │
+ ·························|································|·································
+ | BalanceReader · 0.400 (+0.050) · 0.432 (+0.050) │
+ ·························|································|·································
+ | Lock · 0.537 (+0.066) · 0.728 (+0.066) │
+ ·························|································|·································
+ | Calculator · 0.604 (0.000) · 0.636 (0.000) │
+ ·························|································|·································
+ | ScientificCalculator · 0.945 (+0.016) · 0.977 (+0.016) │
+ ·------------------------|--------------------------------|--------------------------------·
+```
+
+The size of the contract increased, however this means your code will be more efficient across the lifetime of the contract because the higher the `runs` value the more efficient during execution but more expensive during deployment. You can read more in the [Solidity documentation].
+
+## Conclusion
+
+In this tutorial, you've learned how to profile and optimise smart contracts using the Hardhat development environment and the Hardhat Contract Sizer plugin. By focusing on the critical aspect of contract size, we've equipped ourselves with tools and strategies to create more efficient Solidity code.
+
+As you continue your journey in smart contract development, keep in mind that optimizing contract sizes is a continuous process that requires careful consideration of trade-offs between size, readability, and gas efficiency.
+
+
+[Hardhat Contract Sizer]: https://github.com/ItsNickBarry/hardhat-contract-sizer
+[maximum size of a smart contract in Ethereum]: https://ethereum.org/en/developers/tutorials/downsizing-contracts-to-fight-the-contract-size-limit/#why-is-there-a-limit
+[modifiers]: https://docs.base.org/learn/advanced-functions/function-modifiers
+[Solidity official docs]: https://docs.soliditylang.org/en/v0.8.20/internals/optimizer.html
+[Delegate call]: https://solidity-by-example.org/delegatecall/
+[Gas Optimization]: ./hardhat-profiling-gas
+[Solidity documentation]: https://docs.soliditylang.org/en/v0.8.20/internals/optimizer.html#optimizer-parameter-runs
diff --git a/docs/learn/hardhat/setup-deploy-script-vid.mdx b/docs/learn/hardhat/setup-deploy-script-vid.mdx
new file mode 100644
index 00000000..20cfd86a
--- /dev/null
+++ b/docs/learn/hardhat/setup-deploy-script-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Setting up the Deploy Script
+description: Prepare a script to deploy your contract.
+hide_table_of_contents: false
+---
+
+# Setting up the Deploy Script
+
+import {Video} from '/snippets/VideoPlayer.mdx'
+
+
diff --git a/docs/learn/hardhat/setup-deploy-script.mdx b/docs/learn/hardhat/setup-deploy-script.mdx
new file mode 100644
index 00000000..d65b424b
--- /dev/null
+++ b/docs/learn/hardhat/setup-deploy-script.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Setup Deploy Script'
+---
+
diff --git a/docs/learn/hardhat/setup.mdx b/docs/learn/hardhat/setup.mdx
new file mode 100644
index 00000000..8aa8d467
--- /dev/null
+++ b/docs/learn/hardhat/setup.mdx
@@ -0,0 +1,5 @@
+---
+title: 'Setup'
+---
+
+[Content about Hardhat setup overview goes here]
\ No newline at end of file
diff --git a/docs/learn/hardhat/test-network-configuration-vid.mdx b/docs/learn/hardhat/test-network-configuration-vid.mdx
new file mode 100644
index 00000000..f0d701ed
--- /dev/null
+++ b/docs/learn/hardhat/test-network-configuration-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Test Network Configuration
+description: Configure test networks.
+hide_table_of_contents: false
+---
+
+# Test Network Configuration
+
+import {Video} from '/snippets/VideoPlayer.mdx'
+
+
diff --git a/docs/learn/hardhat/testing-deployment.mdx b/docs/learn/hardhat/testing-deployment.mdx
new file mode 100644
index 00000000..ae991b8d
--- /dev/null
+++ b/docs/learn/hardhat/testing-deployment.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Testing Deployment'
+---
+
diff --git a/docs/learn/hardhat/testing-guide.mdx b/docs/learn/hardhat/testing-guide.mdx
new file mode 100644
index 00000000..17889a5b
--- /dev/null
+++ b/docs/learn/hardhat/testing-guide.mdx
@@ -0,0 +1,3 @@
+---
+title: 'Testing Guide'
+---
\ No newline at end of file
diff --git a/docs/learn/hardhat/testing-our-deployment-vid.mdx b/docs/learn/hardhat/testing-our-deployment-vid.mdx
new file mode 100644
index 00000000..7bae1304
--- /dev/null
+++ b/docs/learn/hardhat/testing-our-deployment-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Testing Our Deployment
+description: Test the newly created deploy script.
+hide_table_of_contents: false
+---
+
+# Testing Our Deployment
+
+import {Video} from '/snippets/VideoPlayer.mdx'
+
+
diff --git a/docs/learn/hardhat/testing-overview-vid.mdx b/docs/learn/hardhat/testing-overview-vid.mdx
new file mode 100644
index 00000000..a1fe7978
--- /dev/null
+++ b/docs/learn/hardhat/testing-overview-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Testing Overview
+description: An overview of writing tests in Hardhat.
+hide_table_of_contents: false
+---
+
+# Testing Overview
+
+import {Video} from '/snippets/VideoPlayer.mdx'
+
+
diff --git a/docs/learn/hardhat/testing.mdx b/docs/learn/hardhat/testing.mdx
new file mode 100644
index 00000000..01b40073
--- /dev/null
+++ b/docs/learn/hardhat/testing.mdx
@@ -0,0 +1,5 @@
+---
+title: 'Testing'
+---
+
+[Content about testing overview goes here]
\ No newline at end of file
diff --git a/docs/learn/hardhat/verify-guide.mdx b/docs/learn/hardhat/verify-guide.mdx
new file mode 100644
index 00000000..c287ec99
--- /dev/null
+++ b/docs/learn/hardhat/verify-guide.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Verify Guide'
+---
+
diff --git a/docs/learn/hardhat/verify-video.mdx b/docs/learn/hardhat/verify-video.mdx
new file mode 100644
index 00000000..990017e3
--- /dev/null
+++ b/docs/learn/hardhat/verify-video.mdx
@@ -0,0 +1,4 @@
+---
+title: 'Verify Video'
+---
+
diff --git a/docs/learn/hardhat/writing-tests-vid.mdx b/docs/learn/hardhat/writing-tests-vid.mdx
new file mode 100644
index 00000000..1b5b94ba
--- /dev/null
+++ b/docs/learn/hardhat/writing-tests-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Writing Tests
+description: An introduction to writing tests.
+hide_table_of_contents: false
+---
+
+# Writing Tests
+
+import {Video} from '/snippets/VideoPlayer.mdx'
+
+
diff --git a/docs/learn/hardhat/writing-tests.mdx b/docs/learn/hardhat/writing-tests.mdx
new file mode 100644
index 00000000..de75370a
--- /dev/null
+++ b/docs/learn/hardhat/writing-tests.mdx
@@ -0,0 +1,5 @@
+---
+title: 'Writing Tests'
+---
+
+[Content about writing tests in Hardhat goes here]
\ No newline at end of file
diff --git a/docs/learn/imports/imports-exercise.mdx b/docs/learn/imports/imports-exercise.mdx
new file mode 100644
index 00000000..9140a8b7
--- /dev/null
+++ b/docs/learn/imports/imports-exercise.mdx
@@ -0,0 +1,91 @@
+---
+title: Imports Exercise
+description: Exercise - Demonstrate your knowledge of imports.
+sidebarTitle: Exercise
+hide_table_of_contents: false
+---
+
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Create a contract called `ImportsExercise`. It should `import` a copy of `SillyStringUtils`
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.17;
+
+library SillyStringUtils {
+
+ struct Haiku {
+ string line1;
+ string line2;
+ string line3;
+ }
+
+ function shruggie(string memory _input) internal pure returns (string memory) {
+ return string.concat(_input, unicode" 🤷");
+ }
+}
+```
+
+Add a public instance of `Haiku` called `haiku`.
+
+Add the following two functions.
+
+### Save Haiku
+
+`saveHaiku` should accept three strings and save them as the lines of `haiku`.
+
+### Get Haiku
+
+`getHaiku` should return the haiku as a `Haiku` type.
+
+
+Remember, the compiler will automatically create a getter for `public` `struct`s, but these return each member individually. Create your own getters to return the type.
+
+
+
+### Shruggie Haiku
+
+`shruggieHaiku` should use the library to add 🤷 to the end of `line3`. It must **not** modify the original haiku. It should return the modified `Haiku`.
+
+---
+
+## Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Contract Verification Best Practices
+
+To simplify the verification of your contract on a blockchain explorer like BaseScan.org, consider these two common strategies:
+
+1. **Flattening**: This method involves combining your main contract and all of its imported dependencies into a single file. This makes it easier for explorers to verify the code since they only have to process one file.
+
+2. **Modular Deployment**: Alternatively, you can deploy each imported contract separately and then reference them in your main contract via their deployed addresses. This approach maintains the modularity and readability of your code. Each contract is deployed and verified independently, which can facilitate easier updates and reusability.
+
+3. **Use Desktop Tools**: Forge and Hardhat both have tools to write scripts that both deploy and verify your contracts.
+
+
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+{/* */}
+
+
\ No newline at end of file
diff --git a/docs/learn/imports/imports-sbs.mdx b/docs/learn/imports/imports-sbs.mdx
new file mode 100644
index 00000000..af05a444
--- /dev/null
+++ b/docs/learn/imports/imports-sbs.mdx
@@ -0,0 +1,98 @@
+---
+title: Imports
+sidebarTitle: Step by Step Guide
+description: Learn to import code into your contract.
+hide_table_of_contents: false
+---
+
+In this lesson, we'll learn how to import code written by others into your contracts. We'll also explore the [OpenZeppelin] library of smart contracts.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Import and use code from another file
+- Utilize OpenZeppelin contracts within Remix
+
+---
+
+## OpenZeppelin
+
+[OpenZeppelin] has a robust [library] of well-[documented] smart contracts. These include a number of standard-compliant token implementations and a suite of utilities. All the contracts are audited and are therefore safer to use than random code you might find on the internet (you should still do your own audits before releasing to production).
+
+### Docs
+
+The [docs] start with installation instructions, which we'll return to when we switch over to local development. You do **not** need to install anything to use these contracts in Remix.
+
+Find the documentation for the `EnumerableSet` under _Utils_. This library will allow you to create [sets] of `bytes32`, `address`, and `uint256`. Since they're enumerated, you can iterate through them. Neat!
+
+### Implementing the OpenZeppelin EnumerableSet
+
+Create a new file to work in and add the `pragma` and license identifier.
+
+In Remix, you can import libraries directly from GitHub!
+
+```solidity
+import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/structs/EnumerableSet.sol";
+```
+
+You should see `EnumerableSet.sol` pop into your workspace files, nested deeply in a bunch of folders.
+
+### Trying It Out
+
+Add a contract called `SetExploration`. Review the extensive comments within the contract itself.
+
+To use the `EnumerableSet`, you need to use the [`using`] keyword. This directive attaches all of the library methods to the type. Doing so allows you to call the method on the variable with dot notation, and the variable itself will be supplied as the first argument.
+
+Follow the pattern in the example in the comments, but name the variable `visitors`:
+
+```
+using EnumerableSet for EnumerableSet.AddressSet;
+
+EnumerableSet.AddressSet private visitors;
+```
+
+Add a function called `registerVisitor` that makes use of the library's `add` function to add the sender of the message to the `visitors` set.
+
+
+There's also an `_add` function, which is private.
+
+
+
+
+
+```solidity
+function registerVisitor() public {
+ visitors.add(msg.sender);
+}
+```
+
+
+
+Add another function to return the `numberOfVisitors`. Thanks to `using`, this can cleanly call the `length` function:
+
+
+
+```solidity
+function numberOfVisitors() public view returns (uint) {
+ return visitors.length();
+}
+```
+
+
+---
+
+## Conclusion
+
+In this lesson, you imported a library from [OpenZeppelin] and implemented some of its functions. You also learned how to use the `using` keyword.
+
+---
+
+[OpenZeppelin]: https://www.openzeppelin.com/
+[library]: https://github.com/OpenZeppelin/openzeppelin-contracts
+[documented]: https://docs.openzeppelin.com/contracts/4.x/
+[docs]: https://docs.openzeppelin.com/contracts/4.x/
+[sets]: https://en.wikipedia.org/wiki/Set_(abstract_data_type)
+[`using`]: https://docs.soliditylang.org/en/v0.8.17/contracts.html#using-for
diff --git a/docs/learn/imports/imports-vid.mdx b/docs/learn/imports/imports-vid.mdx
new file mode 100644
index 00000000..11e17655
--- /dev/null
+++ b/docs/learn/imports/imports-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Imports
+sidebarTitle: Imports Overview
+description: Import libraries and contracts into your own contracts.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/inheritance/abstract-contracts-sbs.mdx b/docs/learn/inheritance/abstract-contracts-sbs.mdx
new file mode 100644
index 00000000..70d61de8
--- /dev/null
+++ b/docs/learn/inheritance/abstract-contracts-sbs.mdx
@@ -0,0 +1,77 @@
+---
+title: Abstract Contracts
+sidebarTitle: Abstract Contracts Guide
+description: Learn how to make contracts that must be inherited by another contract.
+hide_table_of_contents: false
+---
+
+[Abstract] contracts can't exist on their own. Their functionality can only be utilized by a contract that inherits from them. In this lesson, you'll learn how to create an abstract contract.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Use the virtual, override, and abstract keywords to create and use an abstract contract
+
+---
+
+## Abstract Contracts
+
+Continue with your `Inheritance.sol` file. Add `ContractD` as an `abstract contract`. Add a `virtual` function called `whoAreYou` function, but do **not** add any implementation for that function.
+
+
+
+```solidity
+abstract contract ContractD {
+ function whoAreYou() public virtual view returns (string memory);
+}
+```
+
+
+
+### Inheriting from an Abstract Function
+
+Update `ContractA` to inherit from `ContractD`.
+
+You'll get a slightly confusing error that `ContractA` needs to be marked as `abstract`. Doing so is **not** the correct fix.
+
+```text
+from solidity:
+TypeError: Contract "ContractA" should be marked as abstract.
+ --> contracts/Inheritance.sol:25:1:
+ |
+25 | contract ContractA is ContractB, ContractC, ContractD {
+ | ^ (Relevant source part starts here and spans across multiple lines).
+Note: Missing implementation:
+ --> contracts/Inheritance.sol:6:5:
+ |
+6 | function whoAreYou() public virtual view returns (string memory);
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+```
+
+The clue for the correct solution is further down: `Note: Missing implementation:`
+
+Only `abstract` contracts can declare functions that are not implemented. To fix this, provide an `override` implementation for `whoAreYou` in `ContractA`:
+
+
+
+```solidity
+function whoAreYou() public override pure returns (string memory) {
+ return "I'm a person!";
+}
+```
+
+
+
+
+---
+
+## Conclusion
+
+In this lesson, you've learned how to implement and inherit from an abstract contract.
+
+---
+
+[Abstract]: https://docs.soliditylang.org/en/v0.8.17/contracts.html?#abstract-contracts
diff --git a/docs/learn/inheritance/abstract-contracts-vid.mdx b/docs/learn/inheritance/abstract-contracts-vid.mdx
new file mode 100644
index 00000000..b51f3100
--- /dev/null
+++ b/docs/learn/inheritance/abstract-contracts-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Abstract Contracts
+description: Create contracts that exist only to be inherited from.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/inheritance/inheritance-exercise.mdx b/docs/learn/inheritance/inheritance-exercise.mdx
new file mode 100644
index 00000000..c4a91cdb
--- /dev/null
+++ b/docs/learn/inheritance/inheritance-exercise.mdx
@@ -0,0 +1,123 @@
+---
+title: Inheritance Exercise
+description: Exercise - Demonstrate your knowledge of inheritance.
+hide_table_of_contents: false
+sidebarTitle: Exercise
+---
+
+Create contracts that adhere to the following specifications.
+
+---
+
+## Contracts
+
+### Employee
+
+Create an `abstract` contract called `Employee`. It should have:
+
+- A public variable storing `idNumber`
+- A public variable storing `managerId`
+- A constructor that accepts arguments for and sets both of these variables
+- A `virtual` function called `getAnnualCost` that returns a `uint`
+
+### Salaried
+
+A contract called `Salaried`. It should:
+
+- Inherit from `Employee`
+- Have a public variable for `annualSalary`
+- Implement an `override` function for `getAnnualCost` that returns `annualSalary`
+- An appropriate constructor that performs any setup, including setting `annualSalary`
+
+### Hourly
+
+Implement a contract called `Hourly`. It should:
+
+- Inherit from `Employee`
+- Have a public variable storing `hourlyRate`
+- Include any other necessary setup and implementation
+
+
+The annual cost of an hourly employee is their hourly rate \* 2080 hours.
+
+
+
+### Manager
+
+Implement a contract called `Manager`. It should:
+
+- Have a public array storing employee Ids
+- Include a function called `addReport` that can add id numbers to that array
+- Include a function called `resetReports` that can reset that array to empty
+
+### Salesperson
+
+Implement a contract called `Salesperson` that inherits from `Hourly`.
+
+### Engineering Manager
+
+Implement a contract called `EngineeringManager` that inherits from `Salaried` and `Manager`.
+
+## Deployments
+
+You'll have to do a more complicated set of deployments for this exercise.
+
+Deploy your `Salesperson` and `EngineeringManager` contracts. You don't need to separately deploy the other contracts.
+
+Use the following values:
+
+### Salesperson
+
+- Hourly rate is 20 dollars an hour
+- Id number is 55555
+- Manager Id number is 12345
+
+### Manager
+
+- Annual salary is 200,000
+- Id number is 54321
+- Manager Id is 11111
+
+## Inheritance Submission
+
+Copy the below contract and deploy it using the addresses of your `Salesperson` and `EngineeringManager` contracts.
+
+```solidity
+contract InheritanceSubmission {
+ address public salesPerson;
+ address public engineeringManager;
+
+ constructor(address _salesPerson, address _engineeringManager) {
+ salesPerson = _salesPerson;
+ engineeringManager = _engineeringManager;
+ }
+}
+```
+
+---
+
+## Submit your Contracts and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+
+Submit your address for your copy of the `InheritanceSubmission` contract that contains your other contract addresses.
+
+
+
+{/* */}
+
+
diff --git a/docs/learn/inheritance/inheritance-sbs.mdx b/docs/learn/inheritance/inheritance-sbs.mdx
new file mode 100644
index 00000000..fb6b953e
--- /dev/null
+++ b/docs/learn/inheritance/inheritance-sbs.mdx
@@ -0,0 +1,183 @@
+---
+title: Inheritance
+description: Learn how to use inheritance to bring functionality from one contract into another.
+hide_table_of_contents: false
+sidebarTitle: Step by Step Guide
+---
+
+Solidity is an object-oriented language. Contracts can inherit from one another, allowing efficient reuse of code.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Write a smart contract that inherits from another contract
+- Describe the impact inheritance has on the byte code size limit
+
+---
+
+## Inheritance
+
+Create a new contract file in Remix called `Inheritance.sol` and add two simple contracts, each with a function identifying which contract called it:
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.17;
+
+contract ContractB {
+ function whoAmI() external pure returns (string memory) {
+ return "contract B";
+ }
+}
+
+contract ContractA {
+ function whoAmI() external pure returns (string memory) {
+ return "contract A";
+ }
+}
+```
+
+`ContractA` says that it is "contract A" and `ContractB` says that it is "contract B".
+
+### Inheriting from Another Contract
+
+[Inheritance] between contracts is indicated by the `is` keyword in the contract declaration. Update `ContractA` so that it `is` `ContractB`, and delete the `whoAmI` function from `ContractA`.
+
+
+
+```solidity
+contract ContractB {
+ function whoAmI() external pure returns (string memory) {
+ return "contract B";
+ }
+}
+
+contract ContractA is ContractB {
+
+}
+```
+
+
+
+Deploy and test again. Even though `ContractA` doesn't have any functions in it, the deployment still shows the button to call `whoAmI`. Call it. `ContractA` now reports that it is "contract B", due to the inheritance of the function from `Contract B`.
+
+### Internal Functions and Inheritance
+
+Contracts can call the `internal` functions from contracts they inherit from. Add an `internal` function to `ContractB` called `whoAmIInternal` that returns "contract B".
+
+Add an external function called `whoAmIExternal` that returns the results of a call to `whoAmIInternal`.
+
+
+
+```solidity
+contract ContractB {
+ function whoAmI() external pure returns (string memory) {
+ return "contract B";
+ }
+
+ function whoAmIInternal() internal pure returns (string memory) {
+ return "contract B";
+ }
+}
+
+contract ContractA is ContractB {
+ function whoAmExternal() external pure returns (string memory) {
+ return whoAmIInternal();
+ }
+}
+```
+
+
+
+Deploy and test. Note that in the deployment for `ContractB`, the `whoAmIInternal` function is **not** available, as it is `internal`. However, calling `whoAmIExternal` can call the `internal` function and return the expected result of "contract B".
+
+### Internal vs. Private
+
+You cannot call a `private` function from a contract that inherits from the contract containing that function.
+
+```solidity
+// Bad code example, do not use
+contract ContractB {
+ function whoAmIPrivate() private pure returns (string memory) {
+ return "contract B";
+ }
+}
+
+contract ContractA is ContractB {
+ function whoAmExternal() external pure returns (string memory) {
+ return whoAmIPrivate();
+ }
+}
+```
+
+The compiler will raise an error:
+
+```text
+from solidity:
+DeclarationError: Undeclared identifier.
+ --> contracts/Inheritance.sol:17:16:
+ |
+17 | return whoAmIPrivate();
+ | ^^^^^^^^^^^^^
+```
+
+### Inheritance and Contract Size
+
+A contract that inherits from another contract will have that contract's bytecode included within its own. You can view this by opening settings in Remix and turning _Artifact Generation_ back on. The bytecode for each compiled contract will be present in the JSON file matching that contract's name within the `artifacts` folder.
+
+Any empty contract:
+
+```solidity
+contract EmptyContract {
+
+}
+```
+
+Will compile into something similar to this:
+
+```text
+6080604052600080fdfea2646970667358221220df894b82f904e22617d7e40150306e2d2e8cb2ca5dcacb666a0c3d40f5f988c464736f6c63430008110033
+```
+
+A slightly more complex contract:
+
+```solidity
+contract notEmptyContract {
+ function sayHello() public pure returns (string memory) {
+ return "To whom it may concern, I write you after a long period of silence to alert you that after much reflection, it occurs to me that I don't think you have fully considered...";
+ }
+}
+```
+
+Will have more complex bytecode. In this case, mostly to store the long string present in the return:
+
+```text
+608060405234801561001057600080fd5b50610201806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063ef5fb05b14610030575b600080fd5b61003861004e565b60405161004591906100fe565b60405180910390f35b60606040518060e0016040528060ab815260200161012160ab9139905090565b600081519050919050565b600082825260208201905092915050565b60005b838110156100a857808201518184015260208101905061008d565b60008484015250505050565b6000601f19601f8301169050919050565b60006100d08261006e565b6100da8185610079565b93506100ea81856020860161008a565b6100f3816100b4565b840191505092915050565b6000602082019050818103600083015261011881846100c5565b90509291505056fe546f2077686f6d206974206d617920636f6e6365726e2c204920777269746520796f752061667465722061206c6f6e6720706572696f64206f662073696c656e636520746f20616c65727420796f752074686174206166746572206d756368207265666c656374696f6e2c206974206f636375727320746f206d652074686174204920646f6e2774207468696e6b20796f7520686176652066756c6c7920636f6e736964657265642e2e2ea264697066735822122058d68a2853aaa473c9a5ff4dba0cc94657cb2a5a87ce3a986090a7ab991055a464736f6c63430008110033
+```
+
+However, if the empty contract inherits from the not empty contract:
+
+```solidity
+contract EmptyContract is notEmptyContract {
+
+}
+```
+
+The resulting bytecode will include that of the contract inherited from:
+
+```text
+608060405234801561001057600080fd5b50610201806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063ef5fb05b14610030575b600080fd5b61003861004e565b60405161004591906100fe565b60405180910390f35b60606040518060e0016040528060ab815260200161012160ab9139905090565b600081519050919050565b600082825260208201905092915050565b60005b838110156100a857808201518184015260208101905061008d565b60008484015250505050565b6000601f19601f8301169050919050565b60006100d08261006e565b6100da8185610079565b93506100ea81856020860161008a565b6100f3816100b4565b840191505092915050565b6000602082019050818103600083015261011881846100c5565b90509291505056fe546f2077686f6d206974206d617920636f6e6365726e2c204920777269746520796f752061667465722061206c6f6e6720706572696f64206f662073696c656e636520746f20616c65727420796f752074686174206166746572206d756368207265666c656374696f6e2c206974206f636375727320746f206d652074686174204920646f6e2774207468696e6b20796f7520686176652066756c6c7920636f6e736964657265642e2e2ea264697066735822122088e486b0a77cd3e2ce809e0a086052815913daec73ebd731e30496d650784f7664736f6c63430008110033
+```
+
+---
+
+## Conclusion
+
+In this lesson, you've learned how to use inheritance to include the functionality of one contract in another. You've also learned that inheriting contracts can call `internal` functions, but they cannot call `private` functions. You've also learned that inheriting from a contract adds the size of that contract's bytecode to the total deployed size.
+
+---
+
+[Inheritance]: https://docs.soliditylang.org/en/v0.8.17/contracts.html
diff --git a/docs/learn/inheritance/inheritance-vid.mdx b/docs/learn/inheritance/inheritance-vid.mdx
new file mode 100644
index 00000000..f0644d6b
--- /dev/null
+++ b/docs/learn/inheritance/inheritance-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Inheritance
+sidebarTitle: Inheritance Overview
+description: Create contracts that inherit from other contracts.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/inheritance/multiple-inheritance-vid.mdx b/docs/learn/inheritance/multiple-inheritance-vid.mdx
new file mode 100644
index 00000000..1a9be621
--- /dev/null
+++ b/docs/learn/inheritance/multiple-inheritance-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Multiple Inheritance
+description: Create contracts that inherit from multiple contracts.
+hide_table_of_contents: false
+---
+
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/inheritance/multiple-inheritance.mdx b/docs/learn/inheritance/multiple-inheritance.mdx
new file mode 100644
index 00000000..a3d80af0
--- /dev/null
+++ b/docs/learn/inheritance/multiple-inheritance.mdx
@@ -0,0 +1,267 @@
+---
+title: Multiple Inheritance
+sidebarTitle: Multiple Inheritance Guide
+description: Learn how to have a contract inherit from multiple contracts.
+hide_table_of_contents: false
+---
+
+Contracts can inherit from more than one contract. In this lesson, we'll explore how multiple inheritance works in Solidity.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Write a smart contract that inherits from multiple contracts
+
+---
+
+## Multiple Inheritance
+
+Continue working with your contracts in `Inheritance.sol`. Add a new contract called `ContractC` with another `whoAmI` function:
+
+
+
+```solidity
+contract ContractC {
+ function whoAmI() external pure returns (string memory) {
+ return "contract C";
+ }
+}
+```
+
+
+
+### Inheriting from Two Contracts
+
+You can inherit from additional contracts by simply adding a comma and that contract's name after the first. Add inheritance from `ContractC` (an error is expected):
+
+
+
+```solidity
+// bad code example, do not use
+contract ContractA is ContractB, ContractC {
+ function whoAmExternal() external pure returns (string memory) {
+ return whoAmIInternal();
+ }
+}
+```
+
+
+
+The error is because both `ContractB` and `ContractC` contain a function called `whoAmI`. As a result, the compiler needs instruction on which to use.
+
+```text
+from solidity:
+TypeError: Derived contract must override function "whoAmI". Two or more base classes define function with same name and parameter types.
+ --> contracts/Inheritance.sol:21:1:
+ |
+21 | contract ContractA is ContractB, ContractC {
+ | ^ (Relevant source part starts here and spans across multiple lines).
+Note: Definition in "ContractC":
+ --> contracts/Inheritance.sol:6:5:
+ |
+6 | function whoAmI() external pure returns (string memory) {
+ | ^ (Relevant source part starts here and spans across multiple lines).
+Note: Definition in "ContractB":
+ --> contracts/Inheritance.sol:12:5:
+ |
+12 | function whoAmI() external pure returns (string memory) {
+ | ^ (Relevant source part starts here and spans across multiple lines).
+```
+
+### Using Virtual and Override
+
+One method to resolve this conflict is to use the [`virtual` and `override`] keywords to enable you to add functionality to choose which to call.
+
+Add the `virtual` keyword to the `whoAmI` function in both `ContractC` and `ContractB`.
+
+They must also be made `public` instead of `external`, because `external` functions cannot be called within the contract.
+
+```solidity
+contract ContractC {
+ function whoAmI() public virtual pure returns (string memory) {
+ return "contract C";
+ }
+}
+
+contract ContractB {
+ function whoAmI() public virtual pure returns (string memory) {
+ return "contract B";
+ }
+
+ // ... additional code
+}
+```
+
+Add an `override` function called `whoAmI` to `ContractA`:
+
+```solidity
+// Bad code example, do not use
+function whoAmI() public override pure returns (string memory) {
+ return ContractB.whoAmI();
+}
+```
+
+You'll get another error, telling you to specify which contracts this function should override.
+
+```text
+from solidity:
+TypeError: Function needs to specify overridden contracts "ContractB" and "ContractC".
+ --> contracts/Inheritance.sol:22:32:
+ |
+22 | function whoAmI() public override pure returns (string memory) {
+ | ^^^^^^^^
+```
+
+Add them both:
+
+```solidity
+function whoAmI() external override(ContractB, ContractC) pure returns (string memory) {
+ return ContractB.whoAmI();
+}
+```
+
+Deploy and test. The call will now be back to reporting "contract B".
+
+### Changing Types Dynamically
+
+Add an `enum` at the contract level in `ContractA` with members for `None`, `ContractBType`, and `ContractCType`, and an instance of it called `contractType`.
+
+
+
+```solidity
+enum Type { None, ContractBType, ContractCType }
+
+Type contractType;
+```
+
+
+
+Add a `constructor` to `ContractA` that accepts a `Type` and sets `initialType`.
+
+
+
+```solidity
+constructor (Type _initialType) {
+ contractType = _initialType;
+}
+```
+
+
+
+Update `whoAmI` in `ContractA` to call the appropriate `virtual` function based on its `currentType`.
+
+
+
+```solidity
+// Bad code example, do not use
+function whoAmI() public override(ContractB, ContractC) pure returns (string memory) {
+ if(contractType == Type.ContractBType) {
+ return ContractB.whoAmI();
+ }
+ if(contractType == Type.ContractCType) {
+ return ContractC.whoAmI();
+ }
+ return "contract A";
+}
+```
+
+
+
+You'll get errors because the function now reads from state, so it is no longer `pure`. Update it to `view`. You'll also have to update the `whoAmI` `virtual` functions to `view` to match.
+
+
+
+```solidity
+function whoAmI() public override(ContractB, ContractC) view returns (string memory) {
+ if(contractType == Type.ContractBType) {
+ return ContractB.whoAmI();
+ }
+ if(contractType == Type.ContractCType) {
+ return ContractC.whoAmI();
+ }
+ return "contract A";
+}
+```
+
+
+
+Finally, add a function that allows you to switch `currentType`:
+
+
+
+```solidity
+function changeType(Type _newType) external {
+ contractType = _newType;
+}
+```
+
+
+
+Deploy and test. You'll need to use **0**, **1**, and **2** as values to set `contractType`, because Remix won't know about your `enum`.
+
+## Final Code
+
+After completing this exercise, you should have something similar to:
+
+```solidity
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.17;
+
+contract ContractC {
+ function whoAmI() public virtual view returns (string memory) {
+ return "contract C";
+ }
+}
+
+contract ContractB {
+ function whoAmI() public virtual view returns (string memory) {
+ return "contract B";
+ }
+
+ function whoAmIInternal() internal pure returns (string memory) {
+ return "contract B";
+ }
+}
+
+contract ContractA is ContractB, ContractC {
+ enum Type { None, ContractBType, ContractCType }
+
+ Type contractType;
+
+ constructor (Type _initialType) {
+ contractType = _initialType;
+ }
+
+ function changeType(Type _newType) external {
+ contractType = _newType;
+ }
+
+ function whoAmI() public override(ContractB, ContractC) view returns (string memory) {
+ if(contractType == Type.ContractBType) {
+ return ContractB.whoAmI();
+ }
+ if(contractType == Type.ContractCType) {
+ return ContractC.whoAmI();
+ }
+ return "contract A";
+ }
+
+ function whoAmExternal() external pure returns (string memory) {
+ return whoAmIInternal();
+ }
+}
+```
+
+---
+
+## Conclusion
+
+In this lesson, you've explored how to use multiple inheritance to import additional functionality into a contract. You've also implemented one approach to resolving name conflicts between those contracts.
+
+---
+
+[`virtual` and `override`]: https://docs.soliditylang.org/en/v0.8.17/contracts.html?#function-overriding
diff --git a/docs/learn/interfaces/calling-another-contract-vid.mdx b/docs/learn/interfaces/calling-another-contract-vid.mdx
new file mode 100644
index 00000000..a8c524a9
--- /dev/null
+++ b/docs/learn/interfaces/calling-another-contract-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Calling Another Contract
+description: Call the functions in another contract from your own contract.
+hide_table_of_contents: false
+---
+
+This tutorial has been moved as part of a reorganization! It assumes you are using Hardhat. Everything in this lesson will work with minor adjustments if you are working in Foundry or Remix.
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/interfaces/contract-to-contract-interaction.mdx b/docs/learn/interfaces/contract-to-contract-interaction.mdx
new file mode 100644
index 00000000..a0a99491
--- /dev/null
+++ b/docs/learn/interfaces/contract-to-contract-interaction.mdx
@@ -0,0 +1,266 @@
+---
+title: 'Contract to Contract Interaction'
+sidebarTitle: Step by Step Guide
+description: Interact with other smart contracts
+hide_table_of_contents: false
+---
+
+In this article, you'll learn how to interact with other smart contracts using interfaces and the `.call()` function, which allows you to interact with other smart contracts without using an interface.
+
+
+This tutorial has been moved as part of a reorganization! It assumes you are using Hardhat. Everything in this lesson will work with minor adjustments if you are working in Foundry or Remix.
+
+
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Use interfaces to allow a smart contract to call functions in another smart contract
+- Use the `call()` function to interact with another contract without using an interface
+
+---
+
+## Overview
+
+Interacting with external smart contracts is a very common task in the life of a smart contract developer. This includes interacting with contracts that are already deployed to a particular network.
+
+Usually the creators of certain smart contracts document their functionality and expose their functions by providing interfaces that can be used to integrate those particular contracts into your own.
+
+For instance, [Uniswap] provides documentation on how to interact with their smart contracts and also some packages to easily integrate their protocol.
+
+In this example, you interact with the [Uniswap protocol] to create a custom pool for a custom pair of tokens.
+
+Since the Uniswap protocol is already deployed, you will use [Hardhat forking] to test your contract.
+
+You will also use the following two approaches in the example:
+
+- Using interfaces
+- Using the `.call()` function
+
+## Interacting with deployed contracts using interfaces
+
+You must first install the [Uniswap V3 core package] by running:
+
+```bash
+npm install @uniswap/v3-core
+```
+
+This package provides access to the Uniswap interfaces of the Core protocol.
+
+Then, write a custom contract called `PoolCreator` with the following code:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol";
+
+contract PoolCreator {
+ IUniswapV3Factory public uniswapFactory;
+
+ constructor(address _factoryAddress) {
+ uniswapFactory = IUniswapV3Factory(_factoryAddress);
+ }
+
+ function createPool(
+ address tokenA,
+ address tokenB,
+ uint24 fee
+ ) external returns (address poolAddress) {
+ // Check if a pool with the given tokens and fee already exists
+ poolAddress = uniswapFactory.getPool(tokenA, tokenB, fee);
+ if (poolAddress == address(0)) {
+ // If the pool doesn't exist, create a new one
+ poolAddress = uniswapFactory.createPool(tokenA, tokenB, fee);
+ }
+
+ return poolAddress;
+ }
+}
+```
+
+Notice the following:
+
+- You are importing a `IUniswapV3Factory` interface. The interface contains function declarations that include `getPool` and `createPool`:
+
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity >=0.5.0;
+
+/// @title The interface for the Uniswap V3 Factory
+/// @notice The Uniswap V3 Factory facilitates creation of Uniswap V3 pools and control over the protocol fees
+interface IUniswapV3Factory {
+ // ...
+ // ...other function declarations
+
+ /// @notice Returns the pool address for a given pair of tokens and a fee, or address 0 if it does not exist
+ /// @dev tokenA and tokenB may be passed in either token0/token1 or token1/token0 order
+ /// @param tokenA The contract address of either token0 or token1
+ /// @param tokenB The contract address of the other token
+ /// @param fee The fee collected upon every swap in the pool, denominated in hundredths of a bip
+ /// @return pool The pool address
+ function getPool(
+ address tokenA,
+ address tokenB,
+ uint24 fee
+ ) external view returns (address pool);
+
+ /// @notice Creates a pool for the given two tokens and fee
+ /// @param tokenA One of the two tokens in the desired pool
+ /// @param tokenB The other of the two tokens in the desired pool
+ /// @param fee The desired fee for the pool
+ /// @dev tokenA and tokenB may be passed in either order: token0/token1 or token1/token0. tickSpacing is retrieved
+ /// from the fee. The call will revert if the pool already exists, the fee is invalid, or the token arguments
+ /// are invalid.
+ /// @return pool The address of the newly created pool
+ function createPool(
+ address tokenA,
+ address tokenB,
+ uint24 fee
+ ) external returns (address pool);
+```
+
+- The constructor receives the address of the pool factory and creates an instance of `IUniswapV3Factory`.
+- The `createPool` function includes a validation to ensure the pool doesn't exist.
+- The `createPool` function creates a new pool.
+
+Then, create a test file called `PoolCreator.test.ts` with the following content:
+
+```tsx
+import { ethers } from 'hardhat';
+import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers';
+
+import { Token, Token__factory, PoolCreator, PoolCreator__factory } from '../typechain-types';
+
+describe('PoolCreator tests', function () {
+ const UNISWAP_FACTORY_ADDRESS = '0x1F98431c8aD98523631AE4a59f267346ea31F984';
+ let tokenA: Token;
+ let tokenB: Token;
+ let poolCreator: PoolCreator;
+ let owner: HardhatEthersSigner;
+
+ before(async () => {
+ const signers = await ethers.getSigners();
+ owner = signers[0];
+ tokenA = await new Token__factory().connect(owner).deploy('TokenA', 'TokenA');
+ tokenB = await new Token__factory().connect(owner).deploy('TokenB', 'TokenB');
+ poolCreator = await new PoolCreator__factory().connect(owner).deploy(UNISWAP_FACTORY_ADDRESS);
+ });
+
+ it('should create a pool', async () => {
+ const contractAddress = await poolCreator.createPool.staticCall(tokenA, tokenB, 500);
+ console.log('Contract Address', contractAddress);
+ await poolCreator.createPool(tokenA, tokenB, 500);
+ });
+});
+```
+
+Notice the following:
+
+- The address `0x1F98431c8aD98523631AE4a59f267346ea31F984` is the address of the Uniswap pool factory deployed to the Ethereum mainnet. This can be verified by looking at the Uniswap documentation that includes the [Deployment addresses of the contracts].
+- You created two tokens, TokenA and TokenB, by using a `Token` contract.
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
+
+contract Token is ERC20 {
+ constructor(string memory name, string memory symbol) ERC20(name, symbol){
+ _mint(msg.sender, 1000 ether);
+ }
+}
+```
+
+Finally, run `npx hardhat test` and you should get a result similar to the following:
+
+```
+PoolCreator tests
+Contract Address 0xa76662f79A5bC06e459d0a841190C7a4e093b04d
+ ✔ should create a pool (1284ms)
+
+ 1 passing (5s)
+```
+
+## Interacting with external contracts using `.call()`
+
+In the previous example, you accessed the Uniswap V3 Factory interface, however if you don't have access to the contract interface, you can use a special function called `call`.
+
+Using `call`, you can call any contract as long as you know minimal information of the function signature. In this case, you should at least know that `createPool` requires three parameters:
+
+- tokenA
+- tokenB
+- fee
+
+The newly modified smart contract code looks as follows:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.19;
+
+contract PoolCreator {
+ address public uniswapFactory;
+
+ constructor(address _factoryAddress) {
+ uniswapFactory = _factoryAddress;
+ }
+
+ function createPool(
+ address tokenA,
+ address tokenB,
+ uint24 fee
+ ) external returns (address poolAddress) {
+ bytes memory payload = abi.encodeWithSignature(
+ "createPool(address,address,uint24)",
+ tokenA,
+ tokenB,
+ fee
+ );
+
+ (bool success, bytes memory data) = uniswapFactory.call(payload);
+ require(success, "Uniswap factory call failed");
+
+ // The pool address should be returned as the first 32 bytes of the data
+ assembly {
+ poolAddress := mload(add(data, 32))
+ }
+
+ require(poolAddress != address(0), "Pool creation failed");
+ return poolAddress;
+ }
+}
+```
+
+Notice the following:
+
+- By using `abi.encodeWithSignature`, you encode the payload required to make a smart contract call using the `.call()` function.
+- Using `.call()` doesn't require you to import the interface.
+- You load the pool address by using a special assembly operation called `mload`.
+
+Try to run again the command `npx hardhat test` and you should expect the same result:
+
+```
+PoolCreator tests
+Contract Address 0xa76662f79A5bC06e459d0a841190C7a4e093b04d
+ ✔ should create a pool (1284ms)
+
+ 1 passing (5s)
+```
+
+## Conclusion
+
+Interfaces or the `.call` function are two ways to interact with external contracts. Using interfaces provides several advantages, including type safety, code readability, and compiler error checking. When interacting with well-documented contracts like Uniswap, using interfaces is often the preferred and safest approach.
+
+On the other hand, the `.call` function offers more flexibility but comes with greater responsibility. It allows developers to call functions on contracts even without prior knowledge of their interfaces. However, it lacks the type safety and error checking provided by interfaces, making it more error-prone.
+
+---
+
+[Uniswap]: https://docs.uniswap.org/contracts/v3/reference/core/UniswapV3Factory
+[Uniswap protocol]: https://uniswap.org
+[Hardhat forking]: https://hardhat.org/hardhat-network/docs/guides/forking-other-networks
+[Uniswap V3 core package]: https://www.npmjs.com/package/@uniswap/v3-core
+[Deployment addresses of the contracts]: https://docs.uniswap.org/contracts/v3/reference/deployments
diff --git a/docs/learn/interfaces/intro-to-interfaces-vid.mdx b/docs/learn/interfaces/intro-to-interfaces-vid.mdx
new file mode 100644
index 00000000..c53bbc64
--- /dev/null
+++ b/docs/learn/interfaces/intro-to-interfaces-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Intro to Interfaces
+description: Use interfaces to tell your contract how another works.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+This tutorial has been moved as part of a reorganization! It assumes you are using Hardhat. Everything in this lesson will work with minor adjustments if you are working in Foundry or Remix.
+
+
diff --git a/docs/learn/interfaces/testing-the-interface-vid.mdx b/docs/learn/interfaces/testing-the-interface-vid.mdx
new file mode 100644
index 00000000..54257979
--- /dev/null
+++ b/docs/learn/interfaces/testing-the-interface-vid.mdx
@@ -0,0 +1,11 @@
+---
+title: Testing the Interface
+description: Start writing tests for interfaces.
+hide_table_of_contents: false
+---
+
+This tutorial has been moved as part of a reorganization! It assumes you are using Hardhat. Everything in this lesson will work with minor adjustments if you are working in Foundry or Remix.
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/intro-to-tokens/intro-to-tokens-vid.mdx b/docs/learn/intro-to-tokens/intro-to-tokens-vid.mdx
new file mode 100644
index 00000000..a70e3015
--- /dev/null
+++ b/docs/learn/intro-to-tokens/intro-to-tokens-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Introduction
+sidebarTitle: Tokens Overview
+description: Welcome to the wonderful world of tokens!
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/intro-to-tokens/misconceptions-about-tokens-vid.mdx b/docs/learn/intro-to-tokens/misconceptions-about-tokens-vid.mdx
new file mode 100644
index 00000000..d822039e
--- /dev/null
+++ b/docs/learn/intro-to-tokens/misconceptions-about-tokens-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Common Misconceptions
+description: Review some common misconceptions before starting.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/intro-to-tokens/tokens-overview.mdx b/docs/learn/intro-to-tokens/tokens-overview.mdx
new file mode 100644
index 00000000..b73813fe
--- /dev/null
+++ b/docs/learn/intro-to-tokens/tokens-overview.mdx
@@ -0,0 +1,100 @@
+---
+title: Overview
+description: An overview of tokens on Ethereum
+hide_table_of_contents: false
+sidebarTitle: Overview Guide
+---
+
+
+This article will provide an overview of the most popular token standards on Ethereum, including ERC-20, ERC-721, ERC-1155, and a discussion on their properties and various use cases.
+
+---
+
+## Objectives:
+
+By the end of this lesson you should be able to:
+
+- Describe the properties of ERC-20 and ERC-721 tokens
+- List popular ERC-721 tokens
+- List the uses for ERC-20, ERC-721, and ERC-1155 tokens
+
+---
+
+## ERC Token Standards
+
+Ethereum Request for Comments (or, ERC) is a term used to describe technical proposals and standards for Ethereum. An ERC is authored by developers and members of the Ethereum community to suggest improvements, new features, or guidelines for creating and managing tokens and smart contracts. Once an ERC is submitted, it undergoes review and discussion by the community. If it gains consensus, it can then be implemented or adopted as a standard in the ecosystem.
+
+Token standards on Ethereum form the backbone of the digital asset ecosystem. They are a set of predefined rules and guidelines that govern the creation, management, and interaction of tokens on the network. These standards ensure that tokens are compatible with various apps, wallets, and other tokens within the Ethereum ecosystem. Token standards allow developers to create tokens with consistent behavior, facilitating seamless interaction and interoperability within the network.
+
+---
+
+## ERC-20
+
+ERC-20 tokens, the most widely-used token standard on Ethereum, possess several key properties that make them versatile and flexible for various applications. One of the defining characteristics of these tokens is their fungibility. Each unit of an ERC-20 token is interchangeable and holds equal value to another unit of the same token, rendering them indistinguishable from one another. In other words, one USDC token is always equal in value and interchangeable with another USDC token.
+
+Another aspect of ERC-20 tokens is their standardized interface, which includes a set of six mandatory functions: `totalSupply()`, `balanceOf(address)`, `transfer(address, uint256)`, `transferFrom(address, address, uint256)`, `approve(address, uint256)`, and `allowance(address, address)`. This standardization ensures consistency when interacting with these tokens, irrespective of their specific implementation or use case. For example, a user can easily check their token balance or transfer tokens using the same set of functions, whether they are interacting with a governance token like UNI or a stablecoin like DAI.
+
+Some notable applications of ERC-20 tokens include utility tokens (FIL, BAT, MANA), governance tokens (UNI, AAVE, COMP), and stablecoins (USDC, USDT, DAI).
+
+
+
+
+
+---
+
+## ERC-721
+
+ERC-721 is a prominent token standard specifically designed for NFTs, allowing for the creation and management of unique, indivisible digital assets that each have their own special properties.
+
+In contrast to ERC-20 tokens, which are fungible and can be easily exchanged, ERC-721 tokens are non-fungible and can't be swapped on a one-to-one basis. Every token has its own attributes that set it apart from the rest. This one-of-a-kind nature enables the representation of a wide range of digital assets, including digital art, virtual real estate, and collectibles. For example, an artist could mint a one-of-a-kind digital painting, a virtual land parcel could be tokenized in a metaverse, or a rare sports card could be digitized as a collectible NFT.
+
+ERC-721 tokens, like ERC-20 tokens, follow a standardized interface but employ a unique set of functions designed for non-fungible tokens, which allow developers to interact with NFTs across multiple platforms. For instance, a developer would use the same set of functions to interact with a digital artwork NFT listed on OpenSea as they would with a virtual land parcel NFT in Decentraland.
+
+Besides their unique qualities, ERC-721 tokens come with metadata properties that offer information about the token's specific features, such as the artwork's title, the artist, and an image preview. This metadata helps users better understand and appreciate the distinct aspects of each NFT, and it is consistent across platforms.
+
+Some notable applications of ERC-721 tokens include digital art by Beeple, virtual collectibles by NBA Top Shot, virtual real estate in Decentraland, and Ethereum-based domain names like vitalik.eth on the Ethereum Name Service (ENS).
+
+
+
+
+
+---
+
+## ERC-1155
+
+ERC-1155 is an innovative hybrid token standard that merges the best aspects of both fungible and non-fungible tokens, enabling developers to create and manage diverse token types using a single smart contract. This combination of features allows ERC-1155 tokens to provide greater versatility while representing a wide array of assets with different levels of fungibility.
+
+For example, a video game might use both fungible and non-fungible tokens within its ecosystem. Fungible tokens could represent in-game currencies, consumables, or resources, while non-fungible tokens could represent exclusive and unique items like character skins, weapons, or collectible cards.
+
+Digital artists can also benefit from ERC-1155, as it allows them to mint limited edition series of their artwork, with each piece in the series having unique attributes. At the same time, they can create fungible tokens that represent ownership of a specific edition number within the series.
+
+Similar to other token standards, ERC-1155 tokens adhere to a standardized interface with a set of functions that ensure consistency and compatibility across platforms and services. Furthermore, this standard enables efficient batch transfers, simplifying the process and reducing the cost of managing multiple tokens within a single application. For instance, a user who has collected various in-game items in a virtual world can leverage ERC-1155's batch transfer feature to send multiple fungible and non-fungible tokens to another user or marketplace simultaneously. This efficient approach minimizes transaction costs and the complexity typically involved in transferring numerous tokens one by one.
+
+
+
+
+
+---
+
+## Other Token Standards
+
+In addition to the three most prominent token standards that we covered, it is worth mentioning that other standards like ERC-777 and ERC-4626 have been introduced to address specific use cases or challenges. ERC-777 offers enhanced security and functionality over the fungible ERC-20 standard, while ERC-4626 streamlines yield-bearing vault integration by optimizing and unifying technical parameters. These lesser-known standards highlight the ongoing innovation and adaptability of the Ethereum token ecosystem as it continues to grow and evolve.
+
+---
+
+## Conclusion
+
+Ethereum's ERC token standards have played a pivotal role in shaping the digital asset ecosystem by providing clear guidelines and rules for the creation, management, and interaction of tokens on the network. From the widely-used ERC-20 standard for fungible tokens to the distinct ERC-721 standard for non-fungible tokens and the versatile hybrid ERC-1155 standard, these token standards empower developers to craft diverse tokens tailored to various use cases and applications. The standardized interfaces ensure seamless interoperability within the Ethereum ecosystem, facilitating token transfers and interactions across different platforms and services. Additional token standards, such as ERC-777 and ERC-4626, address specific challenges and further demonstrate the continuous innovation and adaptability of the Ethereum token ecosystem.
+
+---
+
+## See Also
+
+- [EIP-20](https://eips.ethereum.org/EIPS/eip-20)
+- [EIP-721](https://eips.ethereum.org/EIPS/eip-721)
+- [EIP-1155](https://eips.ethereum.org/EIPS/eip-1155)
+- [EIP-777](https://eips.ethereum.org/EIPS/eip-777)
+- [EIP-4626](https://eips.ethereum.org/EIPS/eip-4626)
+
+
+[token standards]: https://ethereum.org/en/developers/docs/standards/tokens/
diff --git a/docs/learn/introduction-to-ethereum.mdx b/docs/learn/introduction-to-ethereum.mdx
new file mode 100644
index 00000000..c1557c99
--- /dev/null
+++ b/docs/learn/introduction-to-ethereum.mdx
@@ -0,0 +1,9 @@
+---
+title: Introduction
+description: Welcome to the world of blockchain development!
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/introduction-to-solidity/anatomy-of-a-smart-contract-vid.mdx b/docs/learn/introduction-to-solidity/anatomy-of-a-smart-contract-vid.mdx
new file mode 100644
index 00000000..cad61066
--- /dev/null
+++ b/docs/learn/introduction-to-solidity/anatomy-of-a-smart-contract-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Anatomy of a Smart Contract
+description: Review how smart contracts are organized.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/introduction-to-solidity/deployment-in-remix-vid.mdx b/docs/learn/introduction-to-solidity/deployment-in-remix-vid.mdx
new file mode 100644
index 00000000..40e1bb49
--- /dev/null
+++ b/docs/learn/introduction-to-solidity/deployment-in-remix-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Deployment in Remix
+description: Learn to deploy your contracts to the Remix VM.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/introduction-to-solidity/deployment-in-remix.mdx b/docs/learn/introduction-to-solidity/deployment-in-remix.mdx
new file mode 100644
index 00000000..922b256c
--- /dev/null
+++ b/docs/learn/introduction-to-solidity/deployment-in-remix.mdx
@@ -0,0 +1,110 @@
+---
+sidebarTitle: Step by Step Guide
+title: Deployment in Remix
+description: Use Remix to deploy and interact with a contract.
+hide_table_of_contents: false
+---
+
+Remix contains a simulation of the blockchain that allows you to easily and safely deploy and interact with contracts, for free.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Deploy and test the Storage.sol demo contract in Remix
+
+---
+
+## Deploy the `Storage` Contract
+
+Deploying a contract is easy, but remember that if the contract doesn't compile, the deploy button will instead deploy the last good compiled version of your contract. Verify that you see a green checkmark on the icon for the _Compiler_ contract on the left side of the editor, then select the _Deploy & Run Transactions_ plugin.
+
+If you've already deployed any contracts, press the trash can button to the right of the _Deployed Contracts_ label. Then, press the orange button to deploy the `Storage` contract.
+
+
+
+
+
+### Contract Addresses
+
+After your contract deploys, it will appear in the _Deployed_ contracts section as _STORAGE AT_ followed by an address. Addresses are used for both contracts and wallets in EVM-compatible blockchains and serve a similar purpose to an IP address. You can copy the address to see what it looks like. It's 20 characters of hexadecimal, similar to `0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8`.
+
+The address is what you will use to find your contract with tools such as _Etherscan_, or to connect to it with a front end.
+
+However, when you deploy using the Remix VM simulation, it will only exist in your browser.
+
+### Deployments and Test Accounts
+
+The result of any transactions, including deployments, will appear in the Remix terminal. Click the chevron next to the blue _Debug_ button to expand the log.
+
+
+
+
+
+Doing so will show the full transaction log, which contains all of the details of the transaction, such as its amount, the address to and from, and the inputs and outputs provided to the transaction.
+
+In this case, the sender (from) matches the first listed account in the panel, which has spent a small amount of simulated Ether to deploy the contract.
+
+You can access a list of 15 test wallets here, each with 100 Ether to spend. Among other uses, you can use these accounts to compare behavior between wallets that are and are not the owner of a deployed contract.
+
+### Interacting with the Contract
+
+Click the chevron to expand your contract in the Deployed Contracts section of the left panel. You'll see two buttons, one for each `public` function in the `Storage` contract. Notice how the _Store_ button also has a field to pass a _uint256_, matching the parameter for `uint256 num`.
+
+
+
+
+
+Let's click the retrieve button first. Before clicking, make a prediction: given that the `number` variable was instantiated without a value, what do you think the return will be?
+
+Go ahead and click – the result will appear below the button as:
+
+```text
+0: uint256: 0
+```
+
+
+
+
+
+Outputs from the EVM are in the form of an array, so in this case, the only return is in the 0th element and it is a `uint256` of 0. Were you expecting `undefined` or an error?
+
+Unlike many languages, variables in Solidity have a [default value] if not assigned. For `uint` and `int`, that value is 0.
+
+You can also review the results of your transaction in the console.
+
+
+
+
+
+The screenshot above is from a newer version of Remix than the video. Outputs are now often decoded for you!
+
+### Storing and Retrieving a Value
+
+Use the input to store and retrieve a value. Which costs more gas? Storing or retrieving? This isn't a trick question, but it is a bit nuanced. Both cost about 23500 gas, but there is only a gas cost for the retrieve function if it is called by another contract. Calling it from the web is free, because you're only reading data that is on the blockchain and are not asking the EVM to perform a computational task.
+
+---
+
+## Disabling Artifact Generation
+
+Return to the _File Explorer_ by clicking the double document icon in the upper left. You should now see a folder called _artifacts_ that has been added to your project. This folder contains a number of build artifacts, such as the [_ABI_] for your contract, that will be useful to you later, but currently just cause clutter.
+
+You can disable artifact generation by clicking the settings gear in the bottom left corner, then deselecting the first checkbox to _Generate contract metadata..._
+
+
+
+
+
+---
+
+## Conclusion
+
+Remix makes it easy to write, deploy, and test contracts. Contracts are deployed by a wallet address to their own address. These addresses are similar to how IP addresses work, in that they enable connections across the network. You can test deployed contracts directly in Remix and use the console to see detailed information about each transaction.
+
+---
+
+
+[default value]: https://docs.soliditylang.org/en/v0.8.17/control-structures.html#scoping-and-declarations
+[_ABI_]: https://docs.soliditylang.org/en/v0.8.13/abi-spec.html
diff --git a/docs/learn/introduction-to-solidity/introduction-to-remix-vid.mdx b/docs/learn/introduction-to-solidity/introduction-to-remix-vid.mdx
new file mode 100644
index 00000000..753d77ed
--- /dev/null
+++ b/docs/learn/introduction-to-solidity/introduction-to-remix-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Introduction to Remix
+description: Learn about the Remix online IDE.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/introduction-to-solidity/introduction-to-remix.mdx b/docs/learn/introduction-to-solidity/introduction-to-remix.mdx
new file mode 100644
index 00000000..30f4c974
--- /dev/null
+++ b/docs/learn/introduction-to-solidity/introduction-to-remix.mdx
@@ -0,0 +1,93 @@
+---
+title: 'Introduction to Remix'
+description: An introduction to the Remix online IDE.
+sidebarTitle: Remix Guide
+hide_table_of_contents: false
+---
+
+In this lesson, you'll be introduced to an online Solidity IDE called Remix. You'll tour the workspace and explore a sample smart contract.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- List the features, pros, and cons of using Remix as an IDE
+- Deploy and test the Storage.sol demo contract in Remix
+
+---
+
+## Remix Window Overview
+
+Begin by opening a browser window and navigating to [remix.ethereum.org]. Open the project you created and cleaned up at the end of the last reading, and open `1_Storage.sol`. The editor should be organized in a way that is familiar to you. It is divided into three areas:
+
+- Editor Pane
+- Terminal/Output
+- Left Panel
+
+### Editor Pane
+
+The editor pane loads with the Remix home screen, which contains news, helpful links, and warnings about common scams. Double-click on `1_Storage.sol` to open it in the editor. You can close the home tab if you'd like.
+
+
+
+
+
+You'll edit your code in the editor pane. It also has most of the features you're expecting, such as syntax and error highlighting. Note that in Remix, errors are not underlined. Instead, you'll see an❗to the left of the line number where the error is present.
+
+At the top, you'll see a large green arrow similar to the _Run_ button in other editors. In Solidity, this compiles your code, but it does not run it because you must first deploy your code to the simulated blockchain.
+
+### Terminal/Output
+
+Below the editor pane, you'll find the terminal:
+
+
+
+
+
+You'll primarily use this panel to observe transaction logs from your smart contracts. It's also one way to access Remix's very powerful debugging tools.
+
+### Left Panel
+
+As with many other editors, the left panel in Remix has a number of vertical tabs that allow you to switch between different tools and functions. You can explore the files in your current workspace, create and switch between workspaces, search your code, and access a number of plugins.
+
+---
+
+## Plugins
+
+Most of the features in Remix are plugins and the ones you'll use the most are active by default. You can view and manage plugins by clicking the plug button in the lower-left corner, right above the settings gear. You can turn them off and on by clicking activate/deactivate, and some, such as the _Debug_ plugin will be automatically activated through other parts of the editor.
+
+### Solidity Compiler
+
+The first default plugin (after the search function) is the _Solidity Compiler_. Be sure to check the `Auto compile` option. Smart contracts are almost always very small files, so this shouldn't ever cause a performance problem while editing code.
+
+The `Compile and Run script` button in this plugin is a little misleading. This is **not** how you will usually run your contract through testing. You can click the `I` button for more information on this feature.
+
+Finally, if you have errors in your contracts, the complete text for each error will appear at the bottom of the pane. Try it out by introducing some typos to `1_Storage.sol`.
+
+### Deploy & Run Transactions
+
+The _Deploy & Run Transactions_ plugin is what you'll use to deploy your contracts and then interact with them. At the top are controls to select which virtual machine to use, mock user wallets with test Ether, and a drop-down menu to select the contract you wish to deploy and test.
+
+Fix any errors you introduced to `1_Storage.sol` and then click the orange `Deploy` button. You'll see your contract appear below as _STORAGE AT \_.
+
+
+There are two common gotchas that can be very confusing when deploying contracts in Remix.
+
+1. Each time you hit the Deploy button, a new copy of your contract is deployed but the previous deployments remain. Unless you are comparing or debugging between different versions of a contract, or deploying multiple contracts at once, you should click the `Trash` button to erase old deployments before deploying again.
+1. If your code will not compile, **clicking the deploy button will not generate an error!** Instead, the last compiled version will be deployed. Visually check and confirm that there are no errors indicated by a number in a red circle on top of the Compiler plugin.
+
+
+
+---
+
+## Conclusion
+
+Remix is a robust editor with many features and one or two gotchas. It is an excellent tool to use at the beginning of your journey because you can jump right in and start writing code for smart contracts.
+
+## See also
+
+[Remix](https://remix.ethereum.org)
+
+[remix.ethereum.org]: https://remix.ethereum.org
diff --git a/docs/learn/introduction-to-solidity/introduction-to-solidity-overview.mdx b/docs/learn/introduction-to-solidity/introduction-to-solidity-overview.mdx
new file mode 100644
index 00000000..7eee3161
--- /dev/null
+++ b/docs/learn/introduction-to-solidity/introduction-to-solidity-overview.mdx
@@ -0,0 +1,77 @@
+---
+title: 'Overview'
+description: An overview of this module.
+hide_table_of_contents: false
+---
+
+The course you are about to begin is designed to rapidly and thoroughly teach web3 concepts and language to web2 developers. It specifically highlights similarities and differences found in web3 vs. web2 and contains background information, guided coding practices, and independent exercises.
+
+This program is **not** suitable for people who are new to programming in general. While the explanations are thorough, they often rely on an expectation that you are familiar with the underlying concepts. We will not teach you what arrays are and how they are used, but we will show you how they work in this environment.
+
+## Prerequisites
+
+Before these lessons, you should:
+
+- Have several years of experience as a programmer in an object-oriented language
+- Be familiar with the uses and properties of the Ethereum blockchain and the EVM
+- Ideally, be familiar with at least one [curly-bracket] programming language
+
+---
+
+## Objectives
+
+By the end of this module, you should be able to:
+
+- **Introduction to Solidity**
+ - Describe why languages like Solidity are used to write smart contracts
+ - Relate an overview of the history (and pace of change) of Solidity and its strengths and weaknesses
+ - Deploy and test the Storage.sol demo contract in Remix
+- **Contracts and Basic Functions**
+ - Construct a simple ""Hello World"" contract
+ - Categorize basic data types
+ - List the major differences between data types in Solidity as compared to other languages
+ - Compare and contrast signed and unsigned integers
+ - Write a pure function that accepts argument and returns a value
+- **Deploying Smart Contracts to a Testnet**
+ - Describe the uses and properties of the Ethereum testnet
+ - Compare and contrast Ropsten, Rinkeby, Goerli, and Sepolia
+ - Deploy a contract to the Sepolia testnet and interact with it in Etherscan
+- **Control Structures**
+ - Control code flow with if, else, while, and for
+ - List the unique constraints for control flow in Solidity
+- **Storage in Solidity**
+ - Diagram how a contract's data is stored on the blockchain (Contract -> Blockchain)
+ - Order variable declarations to use storage efficiently
+ - Diagram how variables in a contract are stored (Variable -> Contract)
+- **Arrays in Solidity**
+ - Construct then store and retrieve values in storage and memory arrays
+ - Describe the difference between storage and memory arrays
+ - Diagram how arrays are stored
+ - Write a function that can return a filtered subset of an array
+- **The Mapping Type**
+ - Construct a Map (dictionary) data type
+ - Diagram the storage of the Mapping data type
+ - Recall that assignment of the Map data type is not as flexible as for other data types/in other languages
+ - Restrict function calls with the `msg.sender` global variable
+ - Recall that there is no collision protection in the EVM and why this (probably) ok
+- **Advanced Functions**
+ - Describe how pure and view functions are different than functions that modify storage
+ - Categorize functions as public, private, internal, or external based on their usage
+ - Use modifiers to efficiently add functionality to multiple functions
+ - Utilize require to write a function that can only be used when a variable is set to 'True'
+- **Structs**
+ - Construct a struct (user-defined type) that contains several different data types
+ - Declare members of the struct to maximize storage efficiency
+ - Describe constraints related to assignment of structs depending on the types they contain
+- **Inheritance**
+ - Write a smart contract that inherits from another contract
+- **Imports**
+ - Import and use code from another file
+- **Errors**
+ - Debug common solidity errors including execution reverted, out of gas, stack overflow, value overflow/underflow, index out of range, and so on
+- **New Keyword**
+ - Write a contract that creates a new contract with the new keyword
+
+---
+
+[curly-bracket]: https://en.wikipedia.org/wiki/List_of_programming_languages_by_type#Curly-bracket_languages
diff --git a/docs/learn/introduction-to-solidity/introduction-to-solidity-vid.mdx b/docs/learn/introduction-to-solidity/introduction-to-solidity-vid.mdx
new file mode 100644
index 00000000..9778d5bc
--- /dev/null
+++ b/docs/learn/introduction-to-solidity/introduction-to-solidity-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Introduction
+sidebarTitle: Video Tutorial
+description: Learn about the Solidity programming language.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/introduction-to-solidity/solidity-overview.mdx b/docs/learn/introduction-to-solidity/solidity-overview.mdx
new file mode 100644
index 00000000..2dc5cff5
--- /dev/null
+++ b/docs/learn/introduction-to-solidity/solidity-overview.mdx
@@ -0,0 +1,168 @@
+---
+sidebarTitle: Overview
+title: 'Solidity Overview'
+description: An overview of the Solidity programming language.
+hide_table_of_contents: false
+---
+
+import { Danger } from "/snippets/danger.mdx";
+
+In this article, you'll learn about the origins and history of Solidity, where to find the docs, and review some of the considerations that make programming in Solidity relatively unique. You'll also learn about how to get started with development!
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Describe why languages like Solidity are used to write smart contracts
+- Relate an overview of the history (and pace of change) of Solidity and its strengths and weaknesses
+
+---
+
+## Introduction to Solidity
+
+Solidity is a high-level language used to develop smart contracts compatible with the Ethereum Virtual Machine. It is object-oriented, strongly typed, and allows variadic (more than one argument) returns. Solidity was [inspired] by a number of languages, particularly C++. Compared to other languages, Solidity changes very rapidly. Review the [releases] to see just how rapid!
+
+### The Docs
+
+The [Solidity Docs] are thorough and helpful. This guide will regularly reference them and they should be your first source for specific information related to any of the components in the language. As with any versioned doc source, always double-check that the version you're referencing matches the version you are developing with.
+
+### Origins TL;DR
+
+Solidity was developed by the Ethereum Project's Solidity team and was first previewed in 2014 at DevCon0. The original goal was to create an easy-to-use language for smart contract development. A great [history overview] can be found in the team's blog.
+
+### What it Actually Does
+
+Solidity is very similar to the programming languages you are familiar with in that it's a high-level language that is relatively human-readable, which is then compiled into byte-code that can be read by the EVM. For example, this:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.9;
+
+contract Hello {
+ function HelloWorld() public pure returns (string memory) {
+ return "Hello World!";
+ }
+}
+```
+
+compiles into this:
+
+```text
+0x608060405234801561001057600080fd5b50610173806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80637fffb7bd14610030575b600080fd5b61003861004e565b604051610045919061011b565b60405180910390f35b60606040518060400160405280600c81526020017f48656c6c6f20576f726c64210000000000000000000000000000000000000000815250905090565b600081519050919050565b600082825260208201905092915050565b60005b838110156100c55780820151818401526020810190506100aa565b60008484015250505050565b6000601f19601f8301169050919050565b60006100ed8261008b565b6100f78185610096565b93506101078185602086016100a7565b610110816100d1565b840191505092915050565b6000602082019050818103600083015261013581846100e2565b90509291505056fea2646970667358221220575a1ec2ade1712a7a3a4e91cc5d83212207e4a5c70f5b2bc50079ee65ad29b364736f6c63430008110033
+```
+
+As you can see, the first example is a little easier to read!
+
+---
+
+## Programming for Ethereum with Solidity
+
+On the surface, writing code for the EVM using Solidity isn't particularly different from other programming languages. You write code organized into functions, and those functions get executed when called, often accepting arguments and returning values. However, there are a number of unusual traits that will require you to think a little differently. Additionally, the EVM is a much smaller, slower, and less-powerful computer than a desktop, or even a mobile device.
+
+### Gas Fees
+
+Every single [operation] your code performs costs gas, which your users pay for. You're probably already well-versed in _[time complexity]_ and know how to get an operation down to _O(log(n))_, when you have no choice but to run something that is _O(2^n)_, and that sometimes, nested for-loops go brrrrr. These constraints and practices still apply, but in Solidity, every inefficiency directly costs your users money, which can make your app more expensive, and less appealing, than needed.
+
+When you were learning about _time complexity_, you probably heard the term _space complexity_ once, and then it was never mentioned again. This is because normally, computation is expensive, and storage is practically free. The opposite is true on the EVM. It costs a minimum of **20,000** gas to initialize a variable, and a minimum of **5,000** to change it. Meanwhile, the cost to add two numbers together is **3** gas. This means it is often much cheaper to repeatedly derive a value that is calculated from other values than it is to calculate it once and save it.
+
+You also have to be careful to write code with predictable execution paths. Each transaction is sent with a gas limit and which various frameworks, such as _ethers.js_, in order to do their best to estimate. If this estimate is wrong, the transaction will fail, but **it will still cost the gas used up until the point it failed!**
+
+### Contract Size Limit
+
+[EIP-170] introduced a compiled byte-code size limit of **24 KiB** (24,576 B) to Ethereum Smart Contracts. Read that sentence again, as you're probably not used to thinking in this small of a number!
+
+While there isn't an exact ratio of lines of code to compiled byte-code size, you're limited to deploying contracts that are approximately 300-500 lines of Solidity.
+
+Luckily, there are a few ways around this limitation. Contracts can expose their functions to be called by other contracts, although there is an additional cost. Using this, you can write a suite of contracts designed to work together, or even make use of contracts already deployed by others. You can also use more advanced solutions, such as [EIP-2535].
+
+### Stack Limit
+
+Programs written for computers or mobile devices often work with hundreds of variables at the same time. The EVM operates with a stack that can hold 1,024 values, but it can only access the top 16.
+
+There are many implications of this limit, but the one you'll run into most commonly is the "Stack too Deep" error because you're trying to work with too many variables at once.
+
+In Solidity/EVM, your functions are limited to a total of 16 variables that are input, output, or initialized by the function.
+
+### Permanence
+
+Once deployed, smart contracts are permanent and cannot be changed by anyone, **even their creator(s)!** It is literally not possible to edit them. If the creators of a contract discover a vulnerability, they can't do anything about it except withdraw the funds - if the contract allows them to!
+
+As a result, standard practice is to have a smart contract audited by an expert, before deployment.
+
+### Pace of Change
+
+Solidity files always start with a license and a version:
+
+```solidity
+// SPDX-License-Identifier: UNLICENSED
+pragma solidity ^0.8.17;
+```
+
+One of the reasons for this is that the pace of development for Solidity is **very** fast, and changes are not always backwards-compatible. As a result, the compiler needs to know which version to use when converting the Solidity code to byte-code.
+
+Review the [changelog] to see some of the recent additions and fixes.
+
+---
+
+## Development Environments
+
+We'll be covering two tools that can be used to develop in Solidity.
+
+### Remix
+
+We'll start with [Remix], an online IDE similar to Codepen, Replit, or CodeSandbox. Remix is a great place to get started because it works out of the box, has a number of demo contracts, and has great debugging tools. More information can be found at the [Remix Project] website.
+
+
+
+
+
+
+**BE VERY CAREFUL** while using Remix, as it can also be used by scammers. Remix itself will warn you about this, so take heed! One common scam is for the scammer to convince you to paste and deploy code that is allegedly some sort of automated moneymaker, such as a staking tool, or a bot.
+
+If you paste and run code that you don't understand, you may lose all assets from your currently connected wallet. You should also be careful to always navigate directly to `remix.ethereum.org`. More experienced developers prefer to use static versions of Remix deployed to [IPFS], but be careful. There are also deployments that are compromised and used as a part of a scam!
+
+
+
+### Hardhat
+
+[Hardhat] is a development environment that allows you to develop and test Solidity on your local machine. It includes debugging and unit testing tools, and has an ecosystem of third-party-developed plugins that ease development and deployment. Among other things, these plugins can help you deploy contracts, see the size of your compiled byte-code, and even see unit test coverage.
+
+We'll introduce Hardhat and local development after the basics.
+
+## Remix Setup
+
+The next lesson will explore one of the demo contracts within [Remix]. Open it up and review the quickstart information if this is your first time on the site. Then, open or create a new workspace using the `Default` template.
+
+**Delete** everything except the contracts folder and the `1_Storage.sol` contract within that folder. You can also leave `.prettierrc.json` if you'd like.
+
+
+
+
+
+---
+
+## Conclusion
+
+On the surface, Solidity is very similar to other programming languages; most developers won't struggle to write familiar operations. However, there are some critically important properties to keep in mind. Operations are much more expensive than in other environments, particularly storage. You can use most of the practices you are accustomed to, but you are limited to very small contract sizes and by the size of the stack. Finally, remember that you should always use a separate wallet for development. If you make a mistake, you could lose anything in it!
+
+---
+
+## See also
+
+[Solidity Docs](https://docs.soliditylang.org/en/v0.8.17/)
+
+[inspired]: https://docs.soliditylang.org/en/v0.8.17/language-influences.html
+[releases]: https://github.com/ethereum/solidity/releases
+[Solidity Docs]: https://docs.soliditylang.org/en/v0.8.17/
+[history overview]: https://blog.soliditylang.org/2020/07/08/solidity-turns-5/
+[operation]: https://ethereum.org/en/developers/docs/evm/opcodes/
+[time complexity]: https://en.wikipedia.org/wiki/Time_complexity
+[EIP-170]: https://eips.ethereum.org/EIPS/eip-170
+[EIP-2535]: https://eips.ethereum.org/EIPS/eip-2535
+[changelog]: https://github.com/ethereum/solidity/blob/develop/Changelog.md
+[Remix]: https://remix.ethereum.org/
+[IPFS]: https://ipfs.tech/
+[Remix Project]: https://remix-project.org/
+[Hardhat]: https://hardhat.org/
diff --git a/docs/learn/mappings/how-mappings-are-stored-vid.mdx b/docs/learn/mappings/how-mappings-are-stored-vid.mdx
new file mode 100644
index 00000000..fb4f9da6
--- /dev/null
+++ b/docs/learn/mappings/how-mappings-are-stored-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: How Mappings are Stored
+description: Learn about `msg.sender`.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/mappings/mappings-exercise.mdx b/docs/learn/mappings/mappings-exercise.mdx
new file mode 100644
index 00000000..85849359
--- /dev/null
+++ b/docs/learn/mappings/mappings-exercise.mdx
@@ -0,0 +1,74 @@
+---
+title: Mappings Exercise
+sidebarTitle: Exercise
+description: Exercise - Demonstrate your knowledge of mappings.
+hide_table_of_contents: false
+---
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Create a single contract called `FavoriteRecords`. It should not inherit from any other contracts. It should have the following properties:
+
+### State Variables
+
+The contract should have the following state variables. It is **up to you** to decide if any supporting variables are useful.
+
+- A public mapping `approvedRecords`, which returns `true` if an album name has been added as described below, and `false` if it has not
+- A mapping called `userFavorites` that indexes user addresses to a mapping of `string` record names which returns `true` or `false`, depending if the user has marked that album as a favorite
+
+### Loading Approved Albums
+
+Using the method of your choice, load `approvedRecords` with the following:
+
+- Thriller
+- Back in Black
+- The Bodyguard
+- The Dark Side of the Moon
+- Their Greatest Hits (1971-1975)
+- Hotel California
+- Come On Over
+- Rumours
+- Saturday Night Fever
+
+### Get Approved Records
+
+Add a function called `getApprovedRecords`. This function should return a list of all of the names currently indexed in `approvedRecords`.
+
+### Add Record to Favorites
+
+Create a function called `addRecord` that accepts an album name as a parameter. **If** the album is on the approved list, add it to the list under the address of the sender. Otherwise, reject it with a custom error of `NotApproved` with the submitted name as an argument.
+
+### Users' Lists
+
+Write a function called `getUserFavorites` that retrieves the list of favorites for a provided `address memory`.
+
+### Reset My Favorites
+
+Add a function called `resetUserFavorites` that resets `userFavorites` for the sender.
+
+---
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+{/* */}
+
+
diff --git a/docs/learn/mappings/mappings-sbs.mdx b/docs/learn/mappings/mappings-sbs.mdx
new file mode 100644
index 00000000..cbb34938
--- /dev/null
+++ b/docs/learn/mappings/mappings-sbs.mdx
@@ -0,0 +1,212 @@
+---
+title: Mappings
+sidebarTitle: Step by Step Guide
+description: Use the mapping data type to store key-value pairs.
+hide_table_of_contents: false
+---
+
+In Solidity, the hashtable/hashmap/dictionary-comparable type used to store key-value pairs is called a `mapping`. `mapping`s are a powerful tool with many uses, but they also have some unexpected limitations. They also **aren't** actually hash tables!
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Construct a Map (dictionary) data type
+- Recall that assignment of the Map data type is not as flexible as for other data types/in other languages
+- Restrict function calls with the `msg.sender` global variable
+- Recall that there is no collision protection in the EVM and why this is (probably) ok
+
+---
+
+## Mappings in Solidity vs. Hash Tables
+
+On the surface, the [`mapping`] data type appears to be just another hash table implementation that stores pairs of any hashable type as a key, to any other type as a value. The difference is in implementation.
+
+In a more traditional implementation, the data is stored in memory as an array, with a hash-to-index _(hashmod)_ function used to determine which spot in the array to store a given value, based on the key. Sometimes, the _hashmod_ function for two different keys results in the same index, causing a _collision_.
+
+Collisions are resolved via additional solutions, such as linked list chaining; when the underlying array starts to get full, a bigger one is created, all the keys are re-hash-modded, and all the values moved over to the new array.
+
+In the EVM, `mappings` do **not** have an array as the underlying data structure. Instead, the `keccak256` hash of the key plus the storage slot for the mapping itself is used to determine which storage slot out of all 2\*\*256 will be used for the value.
+
+There is no collision-handling, for the same reason that makes wallets work at all - 2\*\*256 is an unimaginably large number. One of the biggest numbers you might encounter regularly is the number of possible configurations for a [shuffled deck of cards], which is:
+
+80658175170943878571660636856403766975289505440883277824000000000000
+
+Meanwhile, the number of variations of a `keccak256` hash are:
+
+115792089237316195423570985008687907853269984665640564039457584007913129639935
+
+Collisions are very unlikely.
+
+As a result, there are a few special characteristics and limitations to keep in mind with the `mapping` data type:
+
+- Mappings can only have a data location of `storage`
+- They can't be used as parameters or returns of public functions
+- They are not iterable and you cannot retrieve a list of keys
+- All possible keys will return the default value, unless another value has been stored
+
+### Creating a Mapping
+
+Create a contract called `Mappings`. In it, add a `mapping` from an `address` to a `uint` called `favoriteNumbers`.
+
+
+
+```solidity
+contract Mappings {
+ mapping (address => uint) favoriteNumbers;
+}
+```
+
+
+
+### Writing to the Mapping
+
+Add a function called `saveFavoriteNumber` that takes an `address` and `uint`, then saves the `uint` in the mapping, with the `address` as the key.
+
+
+
+```solidity
+function saveFavoriteNumber(address _address, uint _favorite) public {
+ favoriteNumbers[_address] = _favorite;
+}
+```
+
+
+
+Deploy and test it out. Does it work? Probably...
+
+You don't have a way to read the data in `favoriteNumber`, but this problem is easy to correct. Similar to arrays, if you mark a `mapping` as public, the Solidity compiler will automatically create a getter for values in that `mapping`.
+
+Update the declaration of `favoriteNumbers` and deploy to test again.
+
+### Utilizing msg.sender
+
+Another issue with this contract is that a `public` function can be called by anyone and everyone with a wallet and funds to pay gas fees. As a result, anyone could go in after you and change your favorite number from lucky number **13** to anything, even **7**!
+
+That won't do at all!
+
+Luckily, you can make use of a [global variable] called `msg.sender` to access the `address` of the wallet that sent the transaction. Use this to make it so that only the owner of an `address` can set their favorite number.
+
+
+
+```solidity
+function saveFavoriteNumber(uint _favorite) public {
+ favoriteNumbers[msg.sender] = _favorite;
+}
+```
+
+
+
+Deploy and test again. Success!
+
+---
+
+## Retrieving All Favorite Numbers
+
+One challenging limitation of the `mapping` data type is that it is **not** iterable - you cannot loop through and manipulate or return **all** values in the `mapping`.
+
+At least not with any built-in features, but you can solve this on your own. A common practice in Solidity with this and similar problems is to use multiple variables or data types to store the right combination needed to address the issue.
+
+### Using a Helper Array
+
+For this problem, you can use a helper array to store a list of all the keys present in `favoriteNumbers`. Simply add the array, and push new keys to it when saving a new favorite number.
+
+
+
+```solidity
+contract Mappings {
+ mapping (address => uint) public favoriteNumbers;
+ address[] addressesOfFavs;
+
+ function saveFavoriteNumber(uint _favorite) public {
+ favoriteNumbers[msg.sender] = _favorite;
+ // Imperfect solution, see below
+ addressesOfFavs.push(msg.sender);
+ }
+}
+```
+
+
+
+To return all of the favorite numbers, you can then iterate through `addressesOfFavs`, look up that addresses' favorite number, add it to a return array, and then return the array when you're done.
+
+
+
+
+```solidity
+function returnAllFavorites() public view returns (uint[] memory) {
+ uint[] memory allFavorites = new uint[](addressesOfFavs.length);
+
+ for(uint i = 0; i < allFavorites.length; i++) {
+ allFavorites[i] = favoriteNumbers[addressesOfFavs[i]];
+ }
+
+ return allFavorites;
+}
+```
+
+
+
+On the surface, this solution works, but there is a problem: What happens if a user **updates** their favorite number? Their address will end up in the list twice, resulting in a doubled entry in the return.
+
+A solution here would be to check first if the `address` already has a number as a value in `favoriteNumbers`, and only push it to the array if not.
+
+
+
+```solidity
+function saveFavoriteNumber(uint _favorite) public {
+ if(favoriteNumbers[msg.sender] == 0) {
+ addressesOfFavs.push(msg.sender);
+ }
+ favoriteNumbers[msg.sender] = _favorite;
+}
+```
+
+
+
+You should end up with a contract similar to this:
+
+
+
+```solidity
+pragma solidity 0.8.17;
+
+contract Mappings {
+ mapping (address => uint) public favoriteNumbers;
+ address[] addressesOfFavs;
+
+ function saveFavoriteNumber(uint _favorite) public {
+ if(favoriteNumbers[msg.sender] == 0) {
+ addressesOfFavs.push(msg.sender);
+ }
+ favoriteNumbers[msg.sender] = _favorite;
+ }
+
+ function returnAllFavorites() public view returns (uint[] memory) {
+ uint[] memory allFavorites = new uint[](addressesOfFavs.length);
+
+ for(uint i = 0; i < allFavorites.length; i++) {
+ allFavorites[i] = favoriteNumbers[addressesOfFavs[i]];
+ }
+
+ return allFavorites;
+ }
+}
+```
+
+
+
+---
+
+## Conclusion
+
+In this lesson, you've learned how to use the `mapping` data type to store key-value pairs in Solidity. You've also explored one strategy for solving some of the limitations found in the `mapping` type when compared to similar types in other languages.
+
+---
+
+[`mapping`]: https://docs.soliditylang.org/en/v0.8.17/types.html#mapping-types
+[hash table]: https://en.wikipedia.org/wiki/Hash_table
+[shuffled deck of cards]: https://czep.net/weblog/52cards.html
+[global variable]: https://docs.soliditylang.org/en/v0.8.17/units-and-global-variables.html
diff --git a/docs/learn/mappings/mappings-vid.mdx b/docs/learn/mappings/mappings-vid.mdx
new file mode 100644
index 00000000..238edede
--- /dev/null
+++ b/docs/learn/mappings/mappings-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Mappings
+sidebarTitle: Mappings Overview
+description: Learn about mappings.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/mappings/using-msg-sender-vid.mdx b/docs/learn/mappings/using-msg-sender-vid.mdx
new file mode 100644
index 00000000..63e627fa
--- /dev/null
+++ b/docs/learn/mappings/using-msg-sender-vid.mdx
@@ -0,0 +1,9 @@
+---
+title: Using msg.sender
+description: Learn about `msg.sender`.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/minimal-tokens/creating-a-minimal-token-vid.mdx b/docs/learn/minimal-tokens/creating-a-minimal-token-vid.mdx
new file mode 100644
index 00000000..42a57368
--- /dev/null
+++ b/docs/learn/minimal-tokens/creating-a-minimal-token-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Create a Minimal Token
+description: Learn to build a very simple token.
+sidebarTitle: Creatiing a Minimal Token
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/minimal-tokens/minimal-token-sbs.mdx b/docs/learn/minimal-tokens/minimal-token-sbs.mdx
new file mode 100644
index 00000000..6b1381d8
--- /dev/null
+++ b/docs/learn/minimal-tokens/minimal-token-sbs.mdx
@@ -0,0 +1,174 @@
+---
+title: Minimal Token
+description: Build your own minimal token.
+hide_table_of_contents: false
+sidebarTitle: Step by Step Guide
+---
+
+At their core, tokens are very simple. The technology powering famous NFT collections and fungible tokens worth vast amounts of money simply uses the EVM to keep track of who owns what, and provides a permissionless way for the owner to transfer what they own to someone new.
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Construct a minimal token and deploy to testnet
+- Identify the properties that make a token a token
+
+---
+
+## Implementing a Token
+
+The minimal elements needed for a token are pretty basic. Start by creating a contract called `MinimalToken`. Add a `mapping` to relate user addresses to the number of tokens they possess. Finally, add a variable to track `totalSupply`:
+
+
+
+```solidity
+contract MinimalToken {
+ mapping (address => uint) public balances;
+ uint public totalSupply;
+}
+```
+
+
+
+Add a `constructor` that initializes the `totalSupply` at 3000 and assigns ownership to the contract creator:
+
+
+
+```solidity
+constructor() {
+ totalSupply = 3000;
+
+ balances[msg.sender] = totalSupply;
+}
+```
+
+
+
+Deploy and test to confirm that the total supply is 3000, and the balance of the first account is as well.
+
+
+
+
+
+Update the constructor and hardcode a distribution of the tokens to be evenly split between the first three test accounts:
+
+
+
+```solidity
+constructor() {
+ totalSupply = 3000;
+
+ balances[msg.sender] = totalSupply / 3;
+ balances[0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2] = totalSupply / 3;
+ balances[0x4B20993Bc481177ec7E8f571ceCaE8A9e22C02db] = totalSupply / 3;
+}
+```
+
+
+
+Redeploy and test again. Now, each of the first three accounts should have 1000 tokens.
+
+
+
+
+
+---
+
+## Transferring Tokens
+
+We can set an initial distribution of tokens and we can see balances, but we're still missing a way to allow the owners of these tokens to share them or spend them.
+
+To remediate this, all we need to do is add a function that can update the balances of each party in the transfer.
+
+Add a `function` called `transfer` that accepts an `address` of `_to` and a `uint` for the `_amount`. You don't need to add anything for `_from`, because that should only be `msg.sender`. The function should subtract the `_amount` from the `msg.sender` and add it to `_to`:
+
+
+
+```solidity
+function transfer(address _to, uint _amount) public {
+ balances[msg.sender] -= _amount;
+ balances[_to] += _amount;
+}
+```
+
+
+
+Double-check that you've switched back to the first address and redeploy. Then, try sending 500 tokens to the second address.
+
+
+
+
+
+What happens if you try to transfer more tokens than an account has? Give it a try!
+
+```text
+transact to MinimalToken.transfer pending ...
+transact to MinimalToken.transfer errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Note: The called function should be payable if you send value and the value you send should be less than your current balance.
+Debug the transaction to get more information.
+```
+
+You won't be able to do it, though the `Note:` here is **misleading**. In the EVM, `payable` **only** refers to transfers of the primary token used to pay gas fees: ETH, Base ETH, Sepolia ETH, Matic, etc. It does **not** refer to the balance of our simple token.
+
+Instead, the transaction is reverting because of the built-in overflow/underflow protection. It's not a great programming practice to depend on this, so add an error for `InsufficientTokens` that returns the `newSenderBalance`.
+
+```Solidity
+function transfer(address _to, uint _amount) public {
+ int newSenderBalance = int(balances[msg.sender] - _amount);
+ if (newSenderBalance < 0) {
+ revert InsufficientTokens(newSenderBalance);
+ }
+
+ balances[msg.sender] = uint(newSenderBalance);
+ balances[_to] += _amount;
+}
+```
+
+Try spending too much again. You'll get the same error in Remix:
+
+```text
+transact to MinimalToken.transfer pending ...
+transact to MinimalToken.transfer errored: VM error: revert.
+
+revert
+ The transaction has been reverted to the initial state.
+Note: The called function should be payable if you send value and the value you send should be less than your current balance.
+Debug the transaction to get more information.
+```
+
+However, you can use the debug tool to review the error in memory to see that it now matches your custom `error`.
+
+## Destroying Tokens
+
+Tokens can be effectively destroyed by accident, or on purpose. Accidental destruction happens when someone sends a token to an unowned wallet address. While it's possible that some day, some lucky person will create a new wallet and find a pleasant surprise, the most likely outcome is that any given randomly chosen address will never be used, thus no one will ever have the ability to use or transfer those tokens.
+
+Luckily, there are some protections here. Similar to credit card numbers, addresses have a built-in checksum that helps protect against typos. Try it out by trying to transfer tokens to the second Remix address, but change the first character in the address from `A` to `B`. You'll get an error:
+
+```text
+transact to MinimalToken.transfer errored: Error encoding arguments: Error: bad address checksum (argument="address", value="0xBb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", code=INVALID_ARGUMENT, version=address/5.5.0) (argument=null, value="0xBb8483F64d9C6d1EcF9b849Ae677dD3315835cb2", code=INVALID_ARGUMENT, version=abi/5.5.0)
+```
+
+A more guaranteed way to destroy, or _burn_ a token, is to transfer it to the default address `0x0000000000000000000000000000000000000000`. This address is unowned and unownable, making it mathematically impossible to retrieve any tokens that are sent to it. Redeploy and try it out by sending 1000 tokens to the zero address.
+
+The `totalSupply` remains unchanged, and the balance of the zero address is visible, but those tokens are stuck there forever.
+
+
+The [zero address] currently has a balance of more than 11,000 ETH, worth over **20 million dollars**! Its total holding of burned assets is estimated to be worth more than **200 million dollars**!!!
+
+
+
+---
+
+## Conclusion
+
+In this lesson, you've learned to implement a simple token, which is really just a system to store the balance of each address, and a mechanism to transfer them from one wallet to another. You've also learned how to permanently destroy tokens, whether by accident, or on purpose.
+
+---
+
+[zero address]: https://etherscan.io/address/0x0000000000000000000000000000000000000000
diff --git a/docs/learn/minimal-tokens/minimal-tokens-exercise.mdx b/docs/learn/minimal-tokens/minimal-tokens-exercise.mdx
new file mode 100644
index 00000000..aef56e6f
--- /dev/null
+++ b/docs/learn/minimal-tokens/minimal-tokens-exercise.mdx
@@ -0,0 +1,72 @@
+---
+title: Minimal Tokens Exercise
+description: Exercise - Create your own token!
+hide_table_of_contents: false
+sidebarTitle: Exercise
+---
+
+Create a contract that adheres to the following specifications.
+
+---
+
+## Contract
+
+Create a contract called `UnburnableToken`. Add the following in storage:
+
+- A public mapping called `balances` to store how many tokens are owned by each address
+- A `public uint` to hold `totalSupply`
+- A `public uint` to hold `totalClaimed`
+- Other variables as necessary to complete the task
+
+Add the following functions.
+
+### Constructor
+
+Add a constructor that sets the total supply of tokens to 100,000,000.
+
+### Claim
+
+Add a `public` function called `claim`. When called, so long as a number of tokens equalling the `totalSupply` have not yet been distributed, any wallet _that has not made a claim previously_ should be able to claim 1000 tokens. If a wallet tries to claim a second time, it should revert with `TokensClaimed`.
+
+The `totalClaimed` should be incremented by the claim amount.
+
+Once all tokens have been claimed, this function should revert with the error `AllTokensClaimed`. (We won't be able to test this, but you'll know if it's there!)
+
+### Safe Transfer
+
+Implement a `public` function called `safeTransfer` that accepts an address `_to` and an `_amount`. It should transfer tokens from the sender to the `_to` address, **only if**:
+
+- That address is not the zero address
+- That address has a balance of greater than zero Base Sepolia Eth
+
+A failure of either of these checks should result in a revert with an `UnsafeTransfer` error, containing the address.
+
+---
+
+### Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+
+
+The contract specification contains actions that can only be performed once by a given address. As a result, the unit tests for a passing contract will only be successful the **first** time you test.
+
+**You may need to submit a fresh deployment to pass**
+
+
+
+{/* */}
+
+
\ No newline at end of file
diff --git a/docs/learn/minimal-tokens/transferring-a-minimal-token-vid.mdx b/docs/learn/minimal-tokens/transferring-a-minimal-token-vid.mdx
new file mode 100644
index 00000000..b730558f
--- /dev/null
+++ b/docs/learn/minimal-tokens/transferring-a-minimal-token-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Transferring a Minimal Token
+description: Explore how tokens are given from one owner to another.
+hide_table_of_contents: false
+sidebarTitle: Tranferring Tokens
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/new-keyword/creating-a-new-contract-vid.mdx b/docs/learn/new-keyword/creating-a-new-contract-vid.mdx
new file mode 100644
index 00000000..e30fdc84
--- /dev/null
+++ b/docs/learn/new-keyword/creating-a-new-contract-vid.mdx
@@ -0,0 +1,10 @@
+---
+title: Creating a new Contract
+sidebarTitle: Creating New Contracts
+description: Use the `new` keyword to create a contract that can create contracts.
+hide_table_of_contents: false
+---
+
+import { Video } from '/snippets/VideoPlayer.mdx';
+
+
diff --git a/docs/learn/new-keyword/new-keyword-exercise.mdx b/docs/learn/new-keyword/new-keyword-exercise.mdx
new file mode 100644
index 00000000..a73fb5ba
--- /dev/null
+++ b/docs/learn/new-keyword/new-keyword-exercise.mdx
@@ -0,0 +1,92 @@
+---
+title: New Exercise
+description: Exercise - Demonstrate your knowledge of the `new` keyword.
+sidebarTitle: Exercise
+hide_table_of_contents: false
+---
+
+For this exercise, we're challenging you to build a solution requiring you to use a number of the concepts you've learned so far. Have fun and enjoy!
+
+---
+
+## Contracts
+
+Build a contract that can deploy copies of an address book contract on demand, which allows users to add, remove, and view their contacts.
+
+You'll need to develop two contracts for this exercise and import **at least** one additional contract.
+
+## Imported Contracts
+
+Review the [Ownable](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol) contract from OpenZeppelin. You'll need to use it to solve this exercise.
+
+You may wish to use another familiar contract to help with this challenge.
+
+## AddressBook
+
+Create an `Ownable` contract called `AddressBook`. It includes:
+
+- A `struct` called `Contact` with properties for:
+ - `id`
+ - `firstName`
+ - `lastName`
+ - a `uint` array of `phoneNumbers`
+- Additional storage for `contacts`
+- Any other necessary state variables
+
+It should include the following functions:
+
+### Add Contact
+
+The `addContact` function should be usable only by the owner of the contract. It should take in the necessary arguments to add a given contact's information to `contacts`.
+
+### Delete Contact
+
+The `deleteContact` function should be usable only by the owner and should delete the contact under the supplied `_id` number.
+
+If the `_id` is not found, it should revert with an error called `ContactNotFound` with the supplied id number.
+
+### Get Contact
+
+The `getContact` function returns the contact information of the supplied `_id` number. It reverts to `ContactNotFound` if the contact isn't present.
+
+
+**Question**
+
+For bonus points (that only you will know about), explain why we can't just use the automatically generated getter for `contacts`?
+
+
+
+### Get All Contacts
+
+The `getAllContacts` function returns an array with all of the user's current, non-deleted contacts.
+
+
+You shouldn't use `onlyOwner` for the two _get_ functions. Doing so won't prevent a third party from accessing the information, because all information on the blockchain is public. However, it may give the mistaken impression that information is hidden, which could lead to a security incident.
+
+
+
+## AddressBookFactory
+
+The `AddressBookFactory` contains one function, `deploy`. It creates an instance of `AddressBook` and assigns the caller as the owner of that instance. It then returns the `address` of the newly-created contract.
+
+---
+
+## Submit your Contract and Earn an NFT Badge! (BETA)
+
+
+#### Hey, where'd my NFT go!?
+
+[Testnets](/learn/deployment-to-testnet/test-networks) are not permanent! Base Goerli [will soon be sunset](https://base.mirror.xyz/kkz1-KFdUwl0n23PdyBRtnFewvO48_m-fZNzPMJehM4), in favor of Base Sepolia.
+
+As these are separate networks with separate data, your NFTs **will not** transfer over.
+
+**Don't worry!** We've captured the addresses of all NFT owners on Base Goerli and will include them when we release the mechanism to transfer these NFTs to mainnet later this year! You can also redeploy on Sepolia and resubmit if you'd like!
+
+
+{/* */}
+
+
diff --git a/docs/learn/new-keyword/new-keyword-sbs.mdx b/docs/learn/new-keyword/new-keyword-sbs.mdx
new file mode 100644
index 00000000..ee0cd972
--- /dev/null
+++ b/docs/learn/new-keyword/new-keyword-sbs.mdx
@@ -0,0 +1,116 @@
+---
+title: The New Keyword
+sidebarTitle: Step by Step Guide
+description: Learn to create a contract that creates other contracts.
+hide_table_of_contents: false
+---
+
+You've seen the `new` keyword and used it to instantiate `memory` arrays with a size based on a variable. You can also use it to write a contract that [creates other contracts].
+
+---
+
+## Objectives
+
+By the end of this lesson you should be able to:
+
+- Write a contract that creates a new contract with the new keyword
+
+---
+
+## Creating a Simple Contract Factory
+
+A contract factory is a contract that creates other contracts. To start, let's create and interact with a very simple one. Create a new project in Remix and add a file called `ContractFactory.sol`.
+
+### Adding the Template
+
+Imagine you want to create a contract that can store its owner's name and complement it upon request. You can create this contract fairly easily.
+
+
+
+```solidity
+contract Complimenter {
+ string public name;
+
+ constructor(string memory _name) {
+ name = _name;
+ }
+
+ function compliment() public view returns(string memory) {
+ return string.concat("You look great today, ", name);
+ }
+}
+```
+
+
+
+Deploy and test.
+
+### Creating a Factory
+
+The `Complimenter` contract is a huge success! People love how it makes them feel and you've got customers banging on the doors and windows. Awesome!
+
+The only problem is that it takes time and effort to manually deploy a new version of the contract for each customer. Luckily, there's a way to write a contract that will act as a self-service portal for your customers.
+
+Start by adding a contract called `ComplimenterFactory`. The Remix interface makes things easier if you leave the factory in the same file as `Complimenter`.
+
+Add a function called `CreateComplimenter` that is public, accepts a `string` called `_name`, and returns an `address`.
+
+Creating a new contract is simple: `new Complimenter(_name)`
+
+You can also save the return from that instantiation into a variable. This reference can be used to call public functions in the deployed contract, and can be cast to an address. We can use it to get an easy reference to find the copies made by the factory. The end result should look similar to:
+
+
+
+```solidity
+contract ComplimenterFactory {
+ function CreateComplimenter(string memory _name) public returns (address) {
+ Complimenter newContract = new Complimenter(_name);
+ return address(newContract);
+ }
+}
+```
+
+
+
+### Testing
+
+Clear the environment if you haven't already, then start by deploying `ComplimenterFactory`. You've been working hard and deserve nice things, so call `CreateComplimenter` with your name.
+
+In the terminal, the _decoded output_ will be the address of the new contract.
+
+```text
+{
+ "0": "address: 0x9e0BC6DB02E5aF99b8868f0b732eb45c956B92dD"
+}
+```
+
+Copy **only** the address.
+
+Switch the _CONTRACT_ to be deployed to `Complimenter`, then paste the address you copied in the field next to the _At Address_ button which is below the _Deploy_ button.
+
+
+
+
+
+Click _At Address_ and the instance of `Complimenter` should appear below `ComplimenterFactory`. Test to confirm it works, then try deploying more instances with the factory.
+
+
+
+
+
+
+If the deployed contract appears, but is instead a broken copy of the factory, it's because you didn't change the contract in the _CONTRACT_ dropdown above the deploy button.
+
+Remix is trying to interact with `Complimenter` using the _ABI_ from the factory contract, which won't work.
+
+
+
+---
+
+## Conclusion
+
+In this lesson, you learned how to deploy contracts from another contract by using the `new` keyword. You also learned that you look great today!
+
+---
+
+[creates other contracts]: https://docs.soliditylang.org/en/v0.8.17/control-structures.html?#creating-contracts-via-new
diff --git a/docs/learn/onchain-app-development/account-abstraction.mdx b/docs/learn/onchain-app-development/account-abstraction.mdx
new file mode 100644
index 00000000..21c26ce0
--- /dev/null
+++ b/docs/learn/onchain-app-development/account-abstraction.mdx
@@ -0,0 +1,3 @@
+---
+title: Account Abstraction
+---
\ No newline at end of file
diff --git a/docs/pages/cookbook/account-abstraction/account-abstraction-on-base-using-biconomy.mdx b/docs/learn/onchain-app-development/account-abstraction/account-abstraction-on-base-using-biconomy.mdx
similarity index 100%
rename from docs/pages/cookbook/account-abstraction/account-abstraction-on-base-using-biconomy.mdx
rename to docs/learn/onchain-app-development/account-abstraction/account-abstraction-on-base-using-biconomy.mdx
diff --git a/docs/pages/cookbook/account-abstraction/account-abstraction-on-base-using-particle-network.mdx b/docs/learn/onchain-app-development/account-abstraction/account-abstraction-on-base-using-particle-network.mdx
similarity index 100%
rename from docs/pages/cookbook/account-abstraction/account-abstraction-on-base-using-particle-network.mdx
rename to docs/learn/onchain-app-development/account-abstraction/account-abstraction-on-base-using-particle-network.mdx
diff --git a/docs/pages/cookbook/account-abstraction/account-abstraction-on-base-using-privy-and-the-base-paymaster.mdx b/docs/learn/onchain-app-development/account-abstraction/account-abstraction-on-base-using-privy-and-the-base-paymaster.mdx
similarity index 100%
rename from docs/pages/cookbook/account-abstraction/account-abstraction-on-base-using-privy-and-the-base-paymaster.mdx
rename to docs/learn/onchain-app-development/account-abstraction/account-abstraction-on-base-using-privy-and-the-base-paymaster.mdx
diff --git a/docs/pages/cookbook/account-abstraction/gasless-transactions-with-paymaster.mdx b/docs/learn/onchain-app-development/account-abstraction/gasless-transactions-with-paymaster.mdx
similarity index 100%
rename from docs/pages/cookbook/account-abstraction/gasless-transactions-with-paymaster.mdx
rename to docs/learn/onchain-app-development/account-abstraction/gasless-transactions-with-paymaster.mdx
diff --git a/docs/pages/cookbook/client-side-development/introduction-to-providers.mdx b/docs/learn/onchain-app-development/client-side-development/introduction-to-providers.mdx
similarity index 100%
rename from docs/pages/cookbook/client-side-development/introduction-to-providers.mdx
rename to docs/learn/onchain-app-development/client-side-development/introduction-to-providers.mdx
diff --git a/docs/learn/onchain-app-development/client-side-development/viem.mdx b/docs/learn/onchain-app-development/client-side-development/viem.mdx
new file mode 100644
index 00000000..e88af757
--- /dev/null
+++ b/docs/learn/onchain-app-development/client-side-development/viem.mdx
@@ -0,0 +1,116 @@
+---
+title: viem
+slug: /tools/viem
+description: Documentation for using Viem, a TypeScript interface for EVM-compatible blockchains. This page covers installation, setup, and various functionalities such as reading and writing blockchain data and interacting with smart contracts on Base.
+---
+
+# viem
+
+:::info
+
+Viem is currently only available on Base Sepolia testnet.
+
+:::
+
+[viem](https://viem.sh/) a TypeScript interface for Ethereum that provides low-level stateless primitives for interacting with Ethereum.
+
+You can use viem to interact with smart contracts deployed on Base.
+
+## Install
+
+To install viem run the following command:
+
+```bash
+npm install --save viem
+```
+
+## Setup
+
+Before you can start using viem, you need to setup a [Client](https://viem.sh/docs/clients/intro.html) with a desired [Transport](https://viem.sh/docs/clients/intro.html) and [Chain](https://viem.sh/docs/chains/introduction).
+
+```javascript
+import { createPublicClient, http } from 'viem';
+import { base } from 'viem/chains';
+
+const client = createPublicClient({
+ chain: base,
+ transport: http(),
+});
+```
+
+:::info
+
+To use Base, you must specify `base` as the chain when creating a Client.
+
+To use Base Sepolia (testnet), replace `base` with `baseSepolia`.
+
+:::
+
+## Reading data from the blockchain
+
+Once you have created a client, you can use it to read and access data from Base using [Public Actions](https://viem.sh/docs/actions/public/introduction.html)
+
+Public Actions are client methods that map one-to-one with a "public" Ethereum RPC method (`eth_blockNumber`, `eth_getBalance`, etc.)
+
+For example, you can use the `getBlockNumber` client method to get the latest block:
+
+```javascript
+const blockNumber = await client.getBlockNumber();
+```
+
+## Writing data to the blockchain
+
+In order to write data to Base, you need to create a Wallet client (`createWalletClient`) and specify an [`Account`](https://ethereum.org/en/developers/docs/accounts/) to use.
+
+```javascript
+import { createWalletClient, custom } from 'viem'
+import { base } from 'viem/chains'
+
+//highlight-start
+const [account] = await window.ethereum.request({ method: 'eth_requestAccounts' })
+//highlight-end
+
+const client = createWalletClient({
+ //highlight-next-line
+ account,
+ chain: base,
+ transport: custom(window.ethereum)
+})
+
+client.sendTransaction({ ... })
+```
+
+:::info
+
+In addition to making a JSON-RPC request (`eth_requestAccounts`) to get an Account, viem provides various helper methods for creating an `Account`, including: [`privateKeyToAccount`](https://viem.sh/docs/accounts/local/privateKeyToAccount), [`mnemonicToAccount`](https://viem.sh/docs/accounts/local/mnemonicToAccount), and [`hdKeyToAccount`](https://viem.sh/docs/accounts/local/hdKeyToAccount).
+
+To use Base Sepolia (testnet), replace `base` with `baseSepolia`.
+
+:::
+
+## Interacting with smart contracts
+
+You can use viem to interact with a smart contract on Base by creating a `Contract` instance using [`getContract`](https://viem.sh/docs/contract/getContract.html) and passing it the contract ABI, contract address, and [Public](https://viem.sh/docs/clients/public.html) and/or [Wallet](https://viem.sh/docs/clients/wallet.html) Client:
+
+```javascript
+import { getContract } from 'viem';
+import { wagmiAbi } from './abi';
+import { publicClient } from './client';
+
+// 1. Create contract instance
+const contract = getContract({
+ address: 'CONTRACT_ADDRESS',
+ abi: wagmiAbi,
+ publicClient,
+});
+
+// 2. Call contract methods, listen to events, etc.
+const result = await contract.read.totalSupply();
+```
+
+:::info
+
+`CONTRACT_ADDRESS` is the address of the deployed contract.
+
+:::
+
diff --git a/docs/learn/onchain-app-development/client-side-development/web3.mdx b/docs/learn/onchain-app-development/client-side-development/web3.mdx
new file mode 100644
index 00000000..d153cd39
--- /dev/null
+++ b/docs/learn/onchain-app-development/client-side-development/web3.mdx
@@ -0,0 +1,115 @@
+---
+title: web3.js
+description: Documentation for using web3.js, a JavaScript library for interacting with EVM-compatible blockchains. This page covers installation, setup, connecting to the Base network and interacting with smart contracts.
+---
+
+# web3.js
+
+[web3.js](https://web3js.org/) is a JavaScript library that allows developers to interact with EVM-compatible blockchain networks.
+
+You can use web3.js to interact with smart contracts deployed on the Base network.
+
+## Install
+
+To install web3.js run the following command:
+
+```bash
+npm install web3
+```
+
+## Setup
+
+Before you can start using web3.js, you need to import it into your project.
+
+Add the following line of code to the top of your file to import web3.js:
+
+```javascript
+//web3.js v1
+const Web3 = require('web3');
+
+//web3.js v4
+const { Web3 } = require('web3');
+```
+
+## Connecting to Base
+
+You can connect to Base by instantiating a new web3.js `Web3` object with a RPC URL of the Base network:
+
+```javascript
+const { Web3 } = require('web3');
+
+const web3 = new Web3('https://mainnet.base.org');
+```
+
+:::info
+
+To alternatively connect to Base Sepolia (testnet), change the above URL from `https://mainnet.base.org` to `https://sepolia.base.org`.
+
+:::
+
+## Accessing data
+
+Once you have created a provider, you can use it to read data from the Base network.
+
+For example, you can use the `getBlockNumber` method to get the latest block:
+
+```javascript
+async function getLatestBlock(address) {
+ const latestBlock = await web3.eth.getBlockNumber();
+ console.log(latestBlock.toString());
+}
+```
+
+## Deploying contracts
+
+Before you can deploy a contract to the Base network using web3.js, you must first create an account.
+
+You can create an account by using `web3.eth.accounts`:
+
+```javascript
+const privateKey = "PRIVATE_KEY";
+const account = web3.eth.accounts.privateKeyToAccount(privateKey);
+```
+
+:::info
+
+`PRIVATE_KEY` is the private key of the wallet to use when creating the account.
+
+:::
+
+## Interacting with smart contracts
+
+You can use web3.js to interact with a smart contract on Base by instantiating a `Contract` object using the ABI and address of a deployed contract:
+
+```javascript
+const abi = [
+... // ABI of deployed contract
+];
+
+const contractAddress = "CONTRACT_ADDRESS"
+
+const contract = new web3.eth.Contract(abi, contractAddress);
+```
+
+Once you have created a `Contract` object, you can use it to call desired methods on the smart contract:
+
+```javascript
+async function setValue(value) {
+ // write query
+ const tx = await contract.methods.set(value).send();
+ console.log(tx.transactionHash);
+}
+
+async function getValue() {
+ // read query
+ const value = await contract.methods.get().call();
+ console.log(value.toString());
+}
+```
+
+:::info
+
+For more information on deploying contracts on Base, see [Deploying a Smart Contract](../smart-contract-development/hardhat/deploy-with-hardhat.md).
+
+:::
+
diff --git a/docs/learn/onchain-app-development/cross-chain/bridge-tokens-with-layerzero.mdx b/docs/learn/onchain-app-development/cross-chain/bridge-tokens-with-layerzero.mdx
new file mode 100644
index 00000000..f3681ce1
--- /dev/null
+++ b/docs/learn/onchain-app-development/cross-chain/bridge-tokens-with-layerzero.mdx
@@ -0,0 +1,646 @@
+---
+title: Sending messages from Base to other chains using LayerZero V2
+description: A tutorial that teaches how to use LayerZero V2 to perform cross-chain messaging from Base Goerli testnet to Optimism Goerli testnet.
+authors:
+ - taycaldwell
+---
+
+# Sending messages from Base to other chains using LayerZero V2
+
+This tutorial will guide you through the process of sending cross-chain message data from a Base smart contract to another smart contract on a different chain using LayerZero V2.
+
+## Objectives
+
+By the end of this tutorial you should be able to do the following:
+
+- Set up a smart contract project for Base using Foundry
+- Install the LayerZero smart contracts as a dependency
+- Use LayerZero to send messages and from smart contracts on Base to smart contracts on different chains
+- Deploy and test your smart contracts on Base testnet
+
+## Prerequisites
+
+### Foundry
+
+This tutorial requires you to have Foundry installed.
+
+- From the command-line (terminal), run: `curl -L https://foundry.paradigm.xyz | bash`
+- Then run `foundryup`, to install the latest (nightly) build of Foundry
+
+For more information, see the Foundry Book [installation guide](https://book.getfoundry.sh/getting-started/installation).
+
+### Coinbase Wallet
+
+In order to deploy a smart contract, you will first need a wallet. You can create a wallet by downloading the Coinbase Wallet browser extension.
+
+- Download [Coinbase Wallet](https://chrome.google.com/webstore/detail/coinbase-wallet-extension/hnfanknocfeofbddgcijnmhnfnkdnaad?hl=en)
+
+### Wallet funds
+
+To complete this tutorial, you will need to fund a wallet with ETH on Base Goerli and Optimism Goerli.
+
+The ETH is required for covering gas fees associated with deploying smart contracts to each network.
+
+- To fund your wallet with ETH on Base Goerli, visit a faucet listed on the [Base Faucets](/chain/network-faucets) page.
+- To fund your wallet with ETH on Optimism Goerli, visit a faucet listed on the [Optimism Faucets](https://docs.optimism.io/builders/tools/faucets) page.
+
+## What is LayerZero?
+
+LayerZero is an interoperability protocol that allows developers to build applications (and tokens) that can connect to multiple blockchains. LayerZero defines these types of applications as "omnichain" applications.
+
+The LayerZero protocol is made up of immutable on-chain [Endpoints](https://docs.layerzero.network/explore/layerzero-endpoint), a configurable [Security Stack](https://docs.layerzero.network/explore/decentralized-verifier-networks), and a permissionless set of [Executors](https://docs.layerzero.network/explore/executors) that transfer messages between chains.
+
+### High-level concepts
+
+#### Endpoints
+
+Endpoints are immutable LayerZero smart contracts that implement a standardized interface for your own smart contracts to use and in order to manage security configurations and send and receive messages.
+
+#### Security Stack (DVNs)
+
+The [Security Stack](https://docs.layerzero.network/explore/decentralized-verifier-networks) is a configurable set of required and optional Decentralized Verifier Networks (DVNs). The DVNs are used to verify message payloads to ensure integrity of your application's messages.
+
+#### Executors
+
+[Executors](https://docs.layerzero.network/explore/executors) are responsible for initiating message delivery. They will automatically execute the `lzReceive` function of the endpoint on the destination chain once a message has been verified by the Security Stack.
+
+## Creating a project
+
+Before you begin, you need to set up your smart contract development environment by creating a Foundry project.
+
+To create a new Foundry project, first create a new directory:
+
+```bash
+mkdir myproject
+```
+
+Then run:
+
+```bash
+cd myproject
+forge init
+```
+
+This will create a Foundry project with the following basic layout:
+
+```bash
+.
+├── foundry.toml
+├── script
+├── src
+└── test
+```
+
+:::info
+
+You can delete the `src/Counter.sol`, `test/Counter.t.sol`, and `script/Counter.s.sol` boilerplate files that were generated with the project, as you will not be needing them.
+
+:::
+
+## Installing the LayerZero smart contracts
+
+To use LayerZero within your Foundry project, you need to install the LayerZero smart contracts and their dependencies using `forge install`.
+
+To install LayerZero smart contracts and their dependencies, run the following commands:
+
+```bash
+forge install GNSPS/solidity-bytes-utils --no-commit
+forge install OpenZeppelin/openzeppelin-contracts@v4.9.4 --no-commit
+forge install LayerZero-Labs/LayerZero-v2 --no-commit
+```
+
+Once installed, update your `foundry.toml` file by appending the following lines:
+
+```bash
+remappings = [
+ '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts',
+ 'solidity-bytes-utils/=lib/solidity-bytes-utils',
+ '@layerzerolabs/lz-evm-oapp-v2/=lib/LayerZero-v2/oapp',
+ '@layerzerolabs/lz-evm-protocol-v2/=lib/LayerZero-v2/protocol',
+ '@layerzerolabs/lz-evm-messagelib-v2/=lib/LayerZero-v2/messagelib',
+]
+
+```
+
+## Getting started with LayerZero
+
+LayerZero provides a smart contract standard called [OApp](https://docs.layerzero.network/v2/developers/evm/oapp/overview) that is intended for omnichain messaging and configuration.
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { OAppSender } from "./OAppSender.sol";
+import { OAppReceiver, Origin } from "./OAppReceiver.sol";
+import { OAppCore } from "./OAppCore.sol";
+
+abstract contract OApp is OAppSender, OAppReceiver {
+ constructor(address _endpoint) OAppCore(_endpoint, msg.sender) {}
+
+ function oAppVersion() public pure virtual returns (uint64 senderVersion, uint64 receiverVersion) {
+ senderVersion = SENDER_VERSION;
+ receiverVersion = RECEIVER_VERSION;
+ }
+}
+```
+
+:::info
+
+You can view the source code for this contract on [GitHub](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oapp/OApp.sol).
+
+:::
+
+To get started using LayerZero, developers simply need to inherit from the [OApp](https://github.com/LayerZero-Labs/LayerZero-v2/blob/main/packages/layerzero-v2/evm/oapp/contracts/oapp/OApp.sol) contract, and implement the following two inherited functions:
+
+- `_lzSend`: A function used to send an omnichain message
+- `_lzReceive`: A function used to receive an omnichain message
+
+In this tutorial, you will be implementing the [OApp](https://docs.layerzero.network/v2/developers/evm/oapp/overview) standard into your own project to add the capability to send messages from a smart contract on Base to a smart contract on Optimism.
+
+:::info
+
+An extension of the [OApp](https://docs.layerzero.network/v2/developers/evm/oapp/overview) contract standard known as [OFT](https://docs.layerzero.network/contracts/oft) is also available for supporting omnichain fungible token transfers.
+
+:::
+
+:::info
+
+For more information on transferring tokens across chains using LayerZero, visit the [LayerZero documentation](https://docs.layerzero.network/contracts/oft).
+
+:::
+
+## Writing the smart contract
+
+To get started, create a new Solidity smart contract file in your project's `src/` directory named `ExampleContract.sol`, and add the following content:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { OApp, Origin, MessagingFee } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol";
+
+contract ExampleContract is OApp {
+ constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) {}
+}
+```
+
+The code snippet above defines a new smart contract named `ExampleContract` that extends the `OApp` contract standard.
+
+The contract's constructor expects two arguments:
+
+- `_endpoint`: The [LayerZero Endpoint](https://docs.layerzero.network/explore/layerzero-endpoint) `address` for the chain the smart contract is deployed to.
+- `_owner`: The `address` of the owner of the smart contract.
+
+:::info
+
+[LayerZero Endpoints](https://docs.layerzero.network/explore/layerzero-endpoint) are smart contracts that expose an interface for OApp contracts to manage security configurations and send and receive messages via the LayerZero protocol.
+
+:::
+
+### Implementing message sending (`_lzSend`)
+
+To send messages to another chain, your smart contract must call the `_lzSend` function inherited from the [OApp](https://docs.layerzero.network/v2/developers/evm/oapp/overview) contract.
+
+Add a new custom function named `sendMessage` to your smart contract that has the following content:
+
+```solidity
+/// @notice Sends a message from the source chain to the destination chain.
+/// @param _dstEid The endpoint ID of the destination chain.
+/// @param _message The message to be sent.
+/// @param _options The message execution options (e.g. gas to use on destination).
+function sendMessage(uint32 _dstEid, string memory _message, bytes calldata _options) external payable {
+ bytes memory _payload = abi.encode(_message); // Encode the message as bytes
+ _lzSend(
+ _dstEid,
+ _payload,
+ _options,
+ MessagingFee(msg.value, 0), // Fee for the message (nativeFee, lzTokenFee)
+ payable(msg.sender) // The refund address in case the send call reverts
+ );
+}
+```
+
+The `sendMessage` function above calls the inherited `_lzSend` function, while passing in the following expected data:
+
+| Name | Type | Description |
+| :--------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `_dstEid` | `uint32` | The [endpoint ID](https://docs.layerzero.network/contracts/endpoint-addresses) of the destination chain to send the message to. |
+| `_payload` | `bytes` | The message (encoded) to send. |
+| `_options` | `bytes` | [Additional options](https://docs.layerzero.network/contracts/options) when sending the message, such as how much gas should be used when receiving the message. |
+| `_fee` | [`MessagingFee`](https://github.com/LayerZero-Labs/LayerZero-v2/blob/c3213200dfe8fabbf7d92c685590d34e6e70da43/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol#L24) | The calculated fee for sending the message. |
+| `_refundAddress` | `address` | The `address` that will receive any excess fee values sent to the endpoint in case the `_lzSend` execution reverts. |
+
+### Implementing gas fee estimation (`_quote`)
+
+As shown in the table provided in the last section, the `_lzSend` function expects an estimated gas [fee](https://github.com/LayerZero-Labs/LayerZero-v2/blob/c3213200dfe8fabbf7d92c685590d34e6e70da43/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol#L24) to be provided when sending a message (`_fee`).
+
+Therefore, sending a message using the `sendMessage` function of your contract, you first need to estimate the associated gas fees.
+
+There are multiple fees incurred when sending a message across chains using LayerZero, including: paying for gas on the source chain, fees paid to DVNs validating the message, and gas on the destination chain. Luckily, LayerZero bundles all of these fees together into a single fee to be paid by the `msg.sender`, and LayerZero Endpoints expose a `_quote` function to estimate this fee.
+
+Add a new function to your `ExampleContract` contract called `estimateFee` that calls the `_quote` function, as shown below:
+
+```solidity
+/// @notice Estimates the gas associated with sending a message.
+/// @param _dstEid The endpoint ID of the destination chain.
+/// @param _message The message to be sent.
+/// @param _options The message execution options (e.g. gas to use on destination).
+/// @return nativeFee Estimated gas fee in native gas.
+/// @return lzTokenFee Estimated gas fee in ZRO token.
+function estimateFee(
+ uint32 _dstEid,
+ string memory _message,
+ bytes calldata _options
+) public view returns (uint256 nativeFee, uint256 lzTokenFee) {
+ bytes memory _payload = abi.encode(_message);
+ MessagingFee memory fee = _quote(_dstEid, _payload, _options, false);
+ return (fee.nativeFee, fee.lzTokenFee);
+}
+```
+
+The `estimateFee` function above calls the inherited `_quote` function, while passing in the following expected data:
+
+| Name | Type | Description |
+| :-------------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `_dstEid` | `uint32` | The [endpoint ID](https://docs.layerzero.network/contracts/endpoint-addresses) of the destination chain the message will be sent to. |
+| `_payload` | `bytes` | The message (encoded) that will be sent. |
+| `_options` | `bytes` | [Additional options](https://docs.layerzero.network/contracts/options) when sending the message, such as how much gas should be used when receiving the message. |
+| `_payInLzToken` | `bool` | Boolean flag for which token to use when returning the fee (native or ZRO token). |
+
+:::info
+
+Your contract's `estimateFee` function should always be called immediately before calling `sendMessage` to accurately estimate associated gas fees.
+
+:::
+
+### Implementing message receiving (`_lzReceive`)
+
+To receive messages on the destination chain, your smart contract must override the `_lzReceive` function inherited from the [OApp](https://docs.layerzero.network/v2/developers/evm/oapp/overview) contract.
+
+Add the following code snippet to your `ExampleContract` contract to override the `_lzReceive` function:
+
+```solidity
+/// @notice Entry point for receiving messages.
+/// @param _origin The origin information containing the source endpoint and sender address.
+/// - srcEid: The source chain endpoint ID.
+/// - sender: The sender address on the src chain.
+/// - nonce: The nonce of the message.
+/// @param _guid The unique identifier for the received LayerZero message.
+/// @param _message The payload of the received message.
+/// @param _executor The address of the executor for the received message.
+/// @param _extraData Additional arbitrary data provided by the corresponding executor.
+function _lzReceive(
+ Origin calldata _origin,
+ bytes32 _guid,
+ bytes calldata payload,
+ address _executor,
+ bytes calldata _extraData
+ ) internal override {
+ data = abi.decode(payload, (string));
+ // other logic
+}
+```
+
+The overridden `_lzReceive` function receives the following arguments when receiving a message:
+
+| Name | Type | Description |
+| :------------ | :-------- | :-------------------------------------------------------------------------------------------------------------------- |
+| `_origin` | `Origin` | The origin information containing the source endpoint and sender address. |
+| `_guid` | `bytes32` | The unique identifier for the received LayerZero message. |
+| `payload` | `bytes` | The payload of the received message (encoded). |
+| `_executor` | `address` | The `address` of the [Executor](https://docs.layerzero.network/explore/executors) for the received message. |
+| `_extraData ` | `bytes` | Additional arbitrary data provided by the corresponding [Executor](https://docs.layerzero.network/explore/executors). |
+
+Note that the overridden method decodes the message payload, and stores the string into a variable named `data` that you can read from later to fetch the latest message.
+
+Add the `data` field as a member variable to your contract:
+
+```solidity
+contract ExampleContract is OApp {
+
+ // highlight-next-line
+ string public data;
+
+ constructor(address _endpoint) OApp(_endpoint, msg.sender) {}
+}
+```
+
+:::info
+
+Overriding the `_lzReceive` function allows you to provide any custom logic you wish when receiving messages, including making a call back to the source chain by invoking `_lzSend`. Visit the LayerZero [Message Design Patterns](https://docs.layerzero.network/contracts/message-design-patterns) for common messaging flows.
+
+:::
+
+### Final code
+
+Once you complete all of the steps above, your contract should look like this:
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import { OApp, Origin, MessagingFee } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol";
+
+contract ExampleContract is OApp {
+
+ string public data;
+
+ constructor(address _endpoint) OApp(_endpoint, msg.sender) {}
+
+ /// @notice Sends a message from the source chain to the destination chain.
+ /// @param _dstEid The endpoint ID of the destination chain.
+ /// @param _message The message to be sent.
+ /// @param _options The message execution options (e.g. gas to use on destination).
+ function sendMessage(uint32 _dstEid, string memory _message, bytes calldata _options) external payable {
+ bytes memory _payload = abi.encode(_message); // Encode the message as bytes
+ _lzSend(
+ _dstEid,
+ _payload,
+ _options,
+ MessagingFee(msg.value, 0), // Fee for the message (nativeFee, lzTokenFee)
+ payable(msg.sender) // The refund address in case the send call reverts
+ );
+ }
+
+ /// @notice Estimates the gas associated with sending a message.
+ /// @param _dstEid The endpoint ID of the destination chain.
+ /// @param _message The message to be sent.
+ /// @param _options The message execution options (e.g. gas to use on destination).
+ /// @return nativeFee Estimated gas fee in native gas.
+ /// @return lzTokenFee Estimated gas fee in ZRO token.
+ function estimateFee(
+ uint32 _dstEid,
+ string memory _message,
+ bytes calldata _options
+ ) public view returns (uint256 nativeFee, uint256 lzTokenFee) {
+ bytes memory _payload = abi.encode(_message);
+ MessagingFee memory fee = _quote(_dstEid, _payload, _options, false);
+ return (fee.nativeFee, fee.lzTokenFee);
+ }
+
+ /// @notice Entry point for receiving messages.
+ /// @param _origin The origin information containing the source endpoint and sender address.
+ /// - srcEid: The source chain endpoint ID.
+ /// - sender: The sender address on the src chain.
+ /// - nonce: The nonce of the message.
+ /// @param _guid The unique identifier for the received LayerZero message.
+ /// @param _message The payload of the received message.
+ /// @param _executor The address of the executor for the received message.
+ /// @param _extraData Additional arbitrary data provided by the corresponding executor.
+ function _lzReceive(
+ Origin calldata _origin,
+ bytes32 _guid,
+ bytes calldata payload,
+ address _executor,
+ bytes calldata _extraData
+ ) internal override {
+ data = abi.decode(payload, (string));
+ }
+}
+```
+
+## Compiling the smart contract
+
+Compile the smart contract to ensure it builds without any errors.
+
+To compile your smart contract, run:
+
+```bash
+forge build
+```
+
+## Deploying the smart contract
+
+### Setting up your wallet as the deployer
+
+Before you can deploy your smart contract to various chains you will need to set up a wallet to be used as the deployer.
+
+To do so, you can use the [`cast wallet import`](https://book.getfoundry.sh/reference/cast/cast-wallet-import) command to import the private key of the wallet into Foundry's securely encrypted keystore:
+
+```bash
+cast wallet import deployer --interactive
+```
+
+After running the command above, you will be prompted to enter your private key, as well as a password for signing transactions.
+
+:::caution
+
+For instructions on how to get your private key from Coinbase Wallet, visit the [Coinbase Wallet documentation](https://docs.cloud.coinbase.com/wallet-sdk/docs/developer-settings#show-private-key). **It is critical that you do NOT commit this to a public repo**.
+
+:::
+
+To confirm that the wallet was imported as the `deployer` account in your Foundry project, run:
+
+```bash
+cast wallet list
+```
+
+### Setting up environment variables
+
+To setup your environment, create an `.env` file in the home directory of your project, and add the RPC URLs and [LayerZero Endpoint](https://docs.layerzero.network/contracts/endpoint-addresses) information for both Base Goerli and Optimism Goerli testnets:
+
+```bash
+BASE_GOERLI_RPC="https://goerli.base.org"
+BASE_GOERLI_LZ_ENDPOINT=0x464570adA09869d8741132183721B4f0769a0287
+BASE_GOERLI_LZ_ENDPOINT_ID=40184
+
+OPTIMISM_GOERLI_RPC="https://goerli.optimism.io"
+OPTIMISM_GOERLI_LZ_ENDPOINT=0x464570adA09869d8741132183721B4f0769a0287
+OPTIMISM_GOERLI_LZ_ENDPOINT_ID=40132
+```
+
+Once the `.env` file has been created, run the following command to load the environment variables in the current command line session:
+
+```
+source .env
+```
+
+With your contract compiled and environment setup, you are now ready to deploy the smart contract to different networks.
+
+### Deploying the smart contract to Base Goerli
+
+To deploy a smart contract using Foundry, you can use the `forge create` command. The command requires you to specify the smart contract you want to deploy, an RPC URL of the network you want to deploy to, and the account you want to deploy with.
+
+:::info
+
+Your wallet must be funded with ETH on the Base Goerli and Optimism Goerli to cover the gas fees associated with the smart contract deployment. Otherwise, the deployment will fail.
+
+To get testnet ETH, see the [prerequisites](#prerequisites).
+
+:::
+
+To deploy the `ExampleContract` smart contract to the Base Goerli testnet, run the following command:
+
+```bash
+forge create ./src/ExampleContract.sol:ExampleContract --rpc-url $BASE_GOERLI_RPC --constructor-args $BASE_GOERLI_LZ_ENDPOINT --account deployer
+```
+
+When prompted, enter the password that you set earlier, when you imported your wallet's private key.
+
+After running the command above, the contract will be deployed on the Base Goerli test network. You can view the deployment status and contract by using a [block explorer](/chain/block-explorers).
+
+### Deploying the smart contract to Optimism Goerli
+
+To deploy the `ExampleContract` smart contract to the Optimism Goerli testnet, run the following command:
+
+```bash
+forge create ./src/ExampleContract.sol:ExampleContract --rpc-url $OPTIMISM_GOERLI_RPC --constructor-args $OPTIMISM_GOERLI_LZ_ENDPOINT --account deployer
+```
+
+When prompted, enter the password that you set earlier, when you imported your wallet's private key.
+
+After running the command above, the contract will be deployed on the Optimism Goerli test network. You can view the deployment status and contract by using the [OP Goerli block explorer](https://goerli-optimism.etherscan.io/).
+
+## Opening the messaging channels
+
+Once your contract has been deployed to Base Goerli and Optimism Goerli, you will need to open the messaging channels between the two contracts so that they can send and receive messages from one another. This is done by calling the `setPeer` function on the contract.
+
+The `setPeer` function expects the following arguments:
+
+| Name | Type | Description |
+| :------ | :-------- | :------------------------------------------------------------------------------------------------------- |
+| `_eid` | `uint32` | The [endpoint ID](https://docs.layerzero.network/contracts/endpoint-addresses) of the destination chain. |
+| `_peer` | `bytes32` | The contract address of the OApp contract on the destination chain. |
+
+### Setting the peers
+
+Foundry provides the `cast` command-line tool that can be used to interact with deployed smart contracts and call their functions.
+
+To set the peer of your `ExampleContract` contracts, you can use `cast` to call the `setPeer` function while providing the [endpoint ID](https://docs.layerzero.network/contracts/endpoint-addresses) and address (in bytes) of the deployed contract on the respective destination chain.
+
+To set the peer of the Base Goerli contract to the Optimism Goerli contract, run the following command:
+
+```bash
+cast send --rpc-url $BASE_GOERLI_RPC "setPeer(uint32, bytes32)" $OPTIMISM_GOERLI_LZ_ENDPOINT_ID --account deployer
+```
+
+:::info
+
+Replace `` with the contract address of your deployed `ExampleContract` contract on Base Goerli, and`` with the contract address (as bytes) of your deployed `ExampleContract` contract on Optimism Goerli before running the provided `cast` command.
+
+:::
+
+To set the peer of the Optimism Goerli contract to the Base Goerli contract, run the following command:
+
+```bash
+cast send --rpc-url $OPTIMISM_GOERLI_RPC "setPeer(uint32, bytes32)" $BASE_GOERLI_LZ_ENDPOINT_ID --account deployer
+```
+
+:::info
+
+Replace `` with the contract address of your deployed `ExampleContract` contract on Optimism Goerli, and`` with the contract address (as bytes) of your deployed `ExampleContract` contract on Base Goerli before running the provided `cast` command.
+
+:::
+
+## Sending messages
+
+Once peers have been set on each contract, they are now able to send and receive messages from one another.
+
+Sending a message using the newly created `ExampleContract` contract can be done in three steps:
+
+1. Build [message options](https://docs.layerzero.network/contracts/options) to specify logic associated with the message transaction
+2. Call the `estimateFee` function to estimate the gas fee for sending a message
+3. Call the `sendMessage` function to send a message
+
+### Building message options
+
+The `estimateFee` and `sendMessage` custom functions of the `ExampleContract` contract both require a [message options](https://docs.layerzero.network/contracts/options) (`_options`) argument to be provided.
+
+Message options allow you to specify arbitrary logic as part of the message transaction, such as the gas amount the [Executor](https://docs.layerzero.network/explore/executors) pays for message delivery, the order of message execution, or dropping an amount of gas to a destination address.
+
+LayerZero provides a [Solidity](https://github.com/LayerZero-Labs/LayerZero-v2/blob/ccfd0d38f83ca8103b14ab9ca77f32e0419510ff/oapp/contracts/oapp/libs/OptionsBuilder.sol#L12) library and [TypeScript SDK](https://docs.layerzero.network/contracts/options) for building these message options.
+
+As an example, below is a Foundry script that uses OptionsBuilder from the Solidity library to generate message options (as `bytes`) that set the gas amount that the Executor will pay upon message delivery to `200000` wei:
+
+```solidity
+pragma solidity ^0.8.0;
+
+import {Script, console2} from "forge-std/Script.sol";
+import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol";
+
+contract OptionsScript is Script {
+ using OptionsBuilder for bytes;
+
+ function setUp() public {}
+
+ function run() public {
+ bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0);
+ console2.logBytes(options);
+ }
+}
+```
+
+The output of this script results in:
+
+```bash
+0x00030100110100000000000000000000000000030d40
+```
+
+For this tutorial, rather than building and generating your own message options, you can use the bytes output provided above.
+
+:::info
+
+Covering all of the different message options in detail is out of scope for this tutorial. If you are interested in learning more about the different message options and how to build them, visit the [LayerZero developer documentation](https://docs.layerzero.network/contracts/options).
+
+:::
+
+### Estimating the gas fee
+
+Before you can send a message from your contract on Base Goerli, you need to estimate the fee associated with sending the message. You can use the `cast` command to call the `estimateFee()` function of the `ExampleContract` contract.
+
+To estimate the gas fee for sending a message from Base Goerli to Optimism Goerli, run the following command:
+
+```bash
+cast send --rpc-url $BASE_GOERLI_RPC "estimateFee(uint32, string, bytes)" $OPTIMISM_GOERLI_LZ_ENDPOINT_ID "Hello World" 0x00030100110100000000000000000000000000030d40 --account deployer
+```
+
+:::info
+
+Replace `` with the contract address of your deployed `ExampleContract` contract on Base Goerli before running the provided `cast` command.
+
+:::
+
+The command above calls `estimateFee(uint32, string, bytes, bool)`, while providing the required arguments, including: the endpoint ID of the destination chain, the text to send, and the message options (generated in the last section).
+
+### Sending the message
+
+Once you have fetched the estimated gas for sending your message, you can now call `sendMessage` and provide the value returned as the `msg.value`.
+
+For example, to send a message from Base Goerli to Optimism Goerli with an estimated gas fee, run the following command:
+
+```bash
+cast send --rpc-url $BASE_GOERLI_RPC --value