Aptos Dapp Journey - Part 1

From create-aptos-dapp to Next.js with Surf Integration

·

18 min read

First Steps

Hey there! Welcome to the first part of my six-part series on diving into the Aptos blockchain ecosystem. As a Next.js developer stepping into the Aptos blockchain world, I want to share my journey, challenges, and cool discoveries with Aptos.

In the first part of this series, you’ll see:

  1. Setting up a development environment and creating a Aptos DApp with create-aptos-dapp.

  2. Moving from Vite to Next.js for a better developer experience (personal preference)

  3. Using Surf for type-safe interactions.

  4. Building handy features like a UI faucet for testing.

  5. Reflecting on the journey and discussing future possibilities in the Aptos ecosystem.

Introduction

When I first decided to dive into Aptos blockchain development, I felt pretty lost. There's so much to learn, and I wasn't sure where to start. That's when I stumbled upon create-aptos-dapp, and it was a game-changer for me.

Using a template seemed like the smart way to go. Instead of staring at a blank screen, wondering how to set everything up, I had a working project right from the get-go. It was like having training wheels while learning to ride a bike – it gave me the confidence to start pedaling.

What I love about these templates is that they're not just empty shells. They come packed with good practices that experienced developers use. It's like having a mentor guiding you, showing you the ropes of how things should be done in the Aptos world.

The best part? I got to see a full dApp in action. From the smart contract written in Move (Aptos' programming language) to the user interface, everything was there. It was like getting a peek behind the curtain of how real Aptos apps work.

Tinkering with this working example helped me understand things so much faster. Instead of just reading about concepts, I could see them in action and play around with the code. It's amazing how much you can learn by breaking things and fixing them!

I was also pleasantly surprised to find that the template used tools I was already familiar with – React, Vite, and Tailwind CSS. It made me feel a bit more at home while venturing into new blockchain territory.

One thing that really helped was the built-in commands for tasks like compiling and publishing smart contracts. When you're just starting out, having these complex processes simplified is a huge relief.

So, if you're new to Aptos like I was, don't be afraid to use a template. It's not cheating, it's smart learning. You'll get up and running faster, and you'll pick up good habits along the way.

Prerequisites

Setting up for Aptos development was quite a ride. Let me tell you about it, especially the part with the VS Code extension for Aptos Move.

The Quest for Better Developer Experience

I'm pretty picky about my coding setup. So when I heard about this VS Code extension for .move files, I was excited. The aptos-move-analyzer sounded great - formatting help, hints, the works. I couldn't wait to try it.

Installing the Aptos Move Extensions for VSCode

I went to the VS Code marketplace, thinking it'd be a breeze. The instructions looked simple enough. But boy, was I wrong!

I followed every step exactly. But it just wouldn't work. It was so frustrating. But you know what? That's how you learn sometimes.

Mac User Problems

Being a Mac user, I hit a little snag. The file wasn't executable out of the box. No worries though, I've dealt with this before. A quick chmod command came to the rescue:

chmod 755 aptos-move-analyzer

Finding a Home for the Analyzer

After making it executable, I needed to find the right place for it. I decided to move it to /usr/local/bin/aptos-move-analyzer. It felt like finding the perfect spot for a new piece of furniture! Then came the final touch – adding the path in VS Code settings. It was like the last piece of a puzzle falling into place.

Victory at Last

When it finally worked, I felt like doing a little victory dance. It's amazing how such a small thing can make coding so much more enjoyable. So, if you're setting up your Aptos development environment, don't skip the tools that can make your life easier. It might take a bit of tinkering, but trust me, it's worth it.

Creating a New Project

As a newcomer to Aptos, I wanted a smooth start. create-aptos-dapp promised to set up a project with all the essentials, saving me from the headache of configuring everything from scratch. It seemed like the perfect launchpad for my Aptos adventure.

Taking the Leap

With a mix of excitement and nervousness, I opened my terminal and typed:

npx create-aptos-dapp@latest

As I hit enter, I felt like I was entering a new world. The terminal displayed instructions, guiding me through the setup process like a friendly tour guide showing me around a new city.

Exploring Advanced Territory

Feeling a bit adventurous, I decided to try out a more advanced example. I used:

npx create-aptos-dapp --example aptos-friend

This command set up a more complex project. It was like jumping into the deep end, but with a life jacket on. I knew I'd learn a lot from examining this more sophisticated setup.

As I started working on my project, I realized I wanted to use Git for version control. But wait, what files should I ignore? After some research and a bit of trial and error, I created a .gitignore file with these entries:

.aptos
*/build
node_modules
.env

Running the Tests

After setting up my first Aptos dApp, I was eager to see it in action. Time to put on my tester hat and dive into the world of Move testing!

The First Test Run

With a mix of excitement and nervousness, I typed the magic command:

npm run move:test

As I hit enter, I held my breath. The terminal sprang to life, and to my delight, the tests passed!

The Unexpected Hurdle

Feeling confident, I decided to run the tests again. But this time, I hit a snag:

{ "Error": "Unexpected error: Failed to run tests: Unable to resolve packages for package 'AptosFriend': While resolving dependency 'AptosFramework' in package 'AptosFriend': Failed to reset to latest Git state 'mainnet' for package 'AptosFramework', to skip set --skip-fetch-latest-git-deps | Exit status: exit status: 128" }

My heart sank. What went wrong? I felt like I'd just hit a brick wall in my coding journey.

The Quest for a Solution

Determined to overcome this obstacle, I put on my detective hat. After some digging and a fair bit of head-scratching, I discovered a workaround. I could clear the local cache with: bash rm -rf ~/.move

But this solution felt like using a sledgehammer to crack a nut. Sure, it worked, but it meant waiting ages for the tests to run again. Not ideal when you're trying to iterate quickly!

The Elegant Fix

Then, I stumbled upon a more elegant solution. The error message had been trying to tell me all along! I just needed to add an extra argument to my scripts/move/test.js file:

await move.test({
    packageDirectoryPath: "move",
    namedAddresses: {
        aptos_friend_addr: "0x100",
    },
    extraArguments: ["--skip-fetch-latest-git-deps"], // This Line
});

Adding this felt like finding the secret passage in a video game. It was smooth sailing from there on out!

Publishing and Upgrading

After the rollercoaster ride of testing, I was ready for the next big step: publishing my Aptos smart contract on-chain.

The Big Moment

To publish, I typed the command:

bun move:publish

As I hit enter, it felt like launching a rocket. My heart raced as I watched the terminal, waiting to see if my contract would successfully deploy. And then... success! The terminal displayed an object address.

I copied it into my .env file because we are going to use it later in the frontend.

VITE_MODULE_ADDRESS=

The Art of the Upgrade

Here's a truth bomb for you: your first deployment won't be perfect. And that's okay. In fact, it's beautiful. Because in this space, iteration is everything.When I needed to upgrade my contract, I ran:

npm run move:upgrade

With these extra arguments in the scripts/move/upgrade.js file to skip pulling the latest git dependencies and automatically answer yes to all prompts:

  move.upgradeObjectPackage({
    packageDirectoryPath: "contract",
    objectAddress: process.env.VITE_MODULE_ADDRESS,
    namedAddresses: {
      launchpad_addr: process.env.VITE_MODULE_ADDRESS,
      initial_creator_addr: process.env.VITE_COLLECTION_CREATOR_ADDRESS,
      minter: "0x3c41ff6b5845e0094e19888cba63773591be9de59cafa9e582386f6af15dd490",
    },
      profile: `${process.env.PROJECT_NAME}-${process.env.VITE_APP_NETWORK}`,
      extraArguments: ["--skip-fetch-latest-git-deps", "--assume-yes"], // Adding this line
  });

Upgrading Limits

I hit a roadblock when I tried to upgrade my Aptos contract. There were all these rules I didn't know about. I had to learn them the hard way.

First, I found out about structs. Once I put a struct in my contract, I couldn't change it at all. No new fields, no removing fields. I couldn't even change things like whether it could be copied or dropped. It felt like my hands were tied.

Then there were the functions. If I made a function public or an entry function, I couldn't change what went in or came out of it. The only thing I could change was the names of the inputs. At first, this felt really limiting.

But then I learned about public(friend) functions. These were different. I could change them however I wanted because only certain parts of my code could use them. That gave me a bit more flexibility.

Adding new things was easier. I could add new structs and new functions without problems. I could even add whole new modules to my package. That was a relief.

I had to be careful about storage, though. I needed to make sure my changes wouldn't mess up the data already on the blockchain. That could have been a big problem.

There were also rules about what I could make public or private. I couldn't take something public and make it private, but I could do the opposite. It made sense when I thought about it.

After all this, I realized sometimes it's just easier to start a new contract instead of trying to upgrade the old one. It depends on what changes I need to make. Upgrading isn't always the best choice, and that's okay.

💡
Sidenote: Like Solana, Aptos lets you batch multiple function calls in one transaction, similar to how Solana allows multiple instructions in one transaction.

Frontend Journey

When I started working on the frontend for my Aptos project, I felt a bit lost. I knew I had to make some choices about how to set things up.

I decided to try out the Vite templates that came with create-aptos-dapp. There were four of them, and they all seemed pretty good for getting started. I spent some time playing around with each one, trying to get a feel for how they worked.

But here's the thing - I've been using Next.js for a long time. It's like an old friend to me. I know how it works, and I'm comfortable with it. So when I saw that all the create-aptos-dapp options were Vite templates, I felt a bit out of my element.

I thought about it for a while. Should I learn a new way of doing things, or stick with what I know? In the end, I decided to go with the base boilerplate. It seemed like the easiest option to move over to my usual tech stack.

It wasn't the fanciest choice, but it felt right for me. Sometimes, when you're learning something new like Aptos, it's good to have some familiar tools around you. It's like having a comfortable pair of shoes when you're exploring a new city.

So that's what I did. I took the base boilerplate and started to make it my own. It might take a bit more work, but I think it'll be worth it in the long run. After all, the best tools are the ones you know how to use well.

Migrating to Next.js

I made the move to Next.js for my project. It was a bit of a rollercoaster, but I learned a lot along the way.

First, I had to say goodbye to some old friends. I uninstalled Vite and its plugins, which felt a bit sad. Then I had to delete a bunch of files:

  • main.tsx - gone

  • index.html - bye-bye

  • vite-env.d.ts - see ya

  • tsconfig.node.json - farewell

  • vite.config.ts - adios

It was like cleaning out my closet. A bit painful, but necessary.

Then came the fun part - adding new stuff! I installed next@latest. It felt like getting a shiny new toy. I created a next.config.mjs file, which was like setting up my new playground.

The app folder was next. I made layout.tsx and page.tsx files. It was like drawing the blueprint for my new house.

Updating tsconfig.json was tricky. It felt like I was rewiring my brain to think in Next.js terms.

I also had to update my package.json. Out with the old Vite scripts, in with the new Next.js ones. It was like learning a new language.

After all this, I sat back and looked at my work. It wasn't perfect, but it was a start. I realized this minimal Next.js setup could be useful for future projects, not just for me but maybe for others too.

It wasn't easy, but it felt good to have taken this step. Now I'm ready to explore all the cool features Next.js has to offer. Onwards and upwards!

My Adventure with Surf

When I started working with Aptos smart contracts, I felt like I was walking on eggshells. One typo in a function name, and everything would break. It was frustrating.

Then I discovered Surf. It's this TypeScript library made by Thala Labs. At first, I was skeptical. Another library to learn? But I decided to give it a shot.

Using Surf felt like putting on glasses for the first time. Suddenly, everything was clearer. I could see my mistakes before I even made them. No more typos in function names. No more guessing about what arguments a function needed.

What I really liked was how Surf worked with my editor. As I typed, it would suggest the right things. It was like having a smart friend looking over my shoulder, gently nudging me in the right direction.

Before Surf, I was always worried about using return values wrong. But with Surf, I felt more confident. I knew I was handling the data correctly.

I didn't have to generate any extra code. Surf just worked, right out of the box. It was like magic.

Looking back, I wish I had found Surf earlier. It would have saved me so many headaches. Now, working with Aptos smart contracts feels smoother, easier. I'm not afraid of making silly mistakes anymore.

If you're working with Aptos, give Surf a try. It might just make your life a whole lot easier, like it did for me.

Surf vs Aptos TS-SDK

When I first started working with Aptos, I used the TS-SDK and wallet-adapter-react. It seemed okay at first, but I soon realized it had some quirks.

In the vite template the function looks like this:

import { InputTransactionData } from "@aptos-labs/wallet-adapter-react";

export const writeMessage = (args: WriteMessageArguments): InputTransactionData => {
  const { content } = args;
  return {
    data: {
      function: `${MODULE_ADDRESS}::message_board::post_message`,
      functionArguments: [content],
    },
  };
};

It worked, but something felt off. The function call and arguments weren't typed. I could easily make a typo and not realize it until runtime. Then I discovered Surf. It was like a breath of fresh air. Here's how the post_message looks like with Surf:

import { useWalletClient } from '@thalalabs/surf/hooks'
const { client } = useWalletClient()

const result = await client.useABI(ABI).post_message({
  arguments: [message],
  type_arguments: []
});

With Surf, everything was typed. My editor could catch mistakes before I even ran the code. I noticed the same improvement with view functions.

Without Surf, I had to write:

const balance = await aptos.view<[number]>({
  payload: {
    function: "0x1::coin::balance",
    typeArguments: ["0x1::aptos_coin::AptosCoin"],
    functionArguments: [accountAddress],
  },
});

But with Surf, it became:

const [balance] = await surf.useABI(COIN_ABI).view.balance({
    functionArguments: [accountAddress!],
    typeArguments: ['0x1::aptos_coin::AptosCoin']
  });

Again, everything was typed and clear. I felt more confident in my code.The main difference I noticed was that for entry functions, Surf wrapped the wallet from wallet-adapter-react. For view functions, it used the Aptos client directly.After using both, I can say Surf made my life easier. It caught errors early and made my code cleaner.

Potential issues and concerns

Surf is provided by Thala Labs, an Aptos ecosystem project. Its maintainability is uncertain, we don't know how long it will be supported, and it is not the standard method. The community and available examples are smaller, making it harder to find resources and examples.

Adding ABI and Testnet Faucet

To use Surf I needed the ABI (Application Binary Interface) for my smart contract, but I wasn't sure how to get it. That's when I stumbled upon a script in the create-aptos-dapp advanced example.

I copied the script into my project and ran it. It was like magic! The script fetched the ABI and saved it right in my frontend utils folder. Here's a snippet of what it did:

const url = `https://fullnode.${process.env.NEXT_PUBLIC_APP_NETWORK}.aptoslabs.com/v1/accounts/${process.env.NEXT_PUBLIC_MODULE_ADDRESS}/module/${moduleName}`

async function getAbi() {
  axios.get(url)
    .then((response) => {
      const abi = response.data.abi
      const abiString = `export const ABI = ${JSON.stringify(abi)} as const;`
      fs.writeFileSync(ABI_FILE_PATH, abiString)
      console.log(`ABI saved to ${ABI_FILE_PATH}`)
    })
    .catch((error) => {
      console.error('Error fetching ABI:', error)
    })
}

It felt good to have the ABI ready to use. But then I realized I needed to use the Aptos testnet. That's when things got interesting. I decided to build a UI faucet to test transferring Aptos. It was going well until I started running out of tokens.

That's when I had an idea, why not add a top-up button? I wrote a function to call the faucet in src/utils/aptosClient.ts:

export async function callFaucet(
  amount: number,
  address: string
): Promise<string[]> {
  const faucetClient = new AptosFaucetClient({
    BASE: `https://faucet.${process.env.NEXT_PUBLIC_APP_NETWORK}.aptoslabs.com`
  });
  const request: FundRequest = {
    amount,
    address
  };
  const response = await faucetClient.fund.fund({ requestBody: request });
  return response.txn_hashes;
}

And then I used it like this in my components:

const hashes = await callFaucet(parseAptos('1'), account!.address);
const executedTransaction = await aptosClient().waitForTransaction({
  transactionHash: hashes as unknown as string
});

It worked! I could now top up my testnet tokens whenever I needed. It felt like I had unlocked a new superpower.

Propose a New Template

After all the work I'd done with Aptos and Next.js, I had an idea. Why not share this setup with others? I realized that all the existing templates in create-aptos-dapp were Vite client-side ones. There wasn't a Next.js option, which meant no server-side rendering out of the box.

I thought about how helpful a Next.js template would be. It would give developers the option to use server-side rendering, which can be great for SEO and initial load times. Plus, it would offer all the other benefits of Next.js that I'd come to appreciate.

So, I decided to take action. I forked the create-aptos-dapp repository and added my Next.js template to the CLI. It felt good to contribute something that could help other developers.

I submitted a pull request (https://github.com/aptos-labs/create-aptos-dapp/pull/197) and waited. I was excited about the possibility of others being able to use this template just by running create-aptos-dapp and choosing "Server-side (NextJS)".

But the process wasn't as smooth as I'd hoped. There was a lot of back-and-forth with the template maintainers. They had their own vision for how the templates should look and function. I had to remove some things I'd added to make it fit better with the existing vite boilerplate template.

Because this was the first Next.js template being added to create-aptos-dapp, we had to make some extra tweaks. We had to update the framework select in the prompts and adjust some environment variables.

It was a bit frustrating at times, having to change things I thought were improvements. But I also understood the importance of consistency across templates. It was a learning experience in collaboration and compromise.

Throughout this process, I kept reminding myself why I was doing this. I wanted to make it easier for other developers to get started with Aptos using Next.js. Even if the final template wasn't exactly what I'd envisioned, it would still be a valuable addition to the create-aptos-dapp options.

As I waited for the final decision on my pull request, I felt a mix of nervousness and pride. Regardless of the outcome, I knew I'd learned a lot and hopefully made a positive contribution to the Aptos development community.

Looking Back and Moving Forward

As I wrap up this chapter of my Aptos journey, I can't help but reflect on all I've learned. It's been quite a ride!

Summary

Wow, what a journey it's been! Let me give you a quick rundown of what I've been up to in the Aptos world.

  • Setting Up: Getting VS Code Move extension working on Mac. Tricky but worth it for better coding

  • Create-Aptos-Dapp: First steps in Aptos development. Tried basic and advanced examples

  • Testing: Running tests on Move contracts. New experience in blockchain coding

  • Going Live: Publishing and upgrading contracts. Exciting to be part of Aptos ecosystem

  • New Framework: Switching from Vite to Next.js. Challenging but opened new possibilities

  • Surf: Using Surf for safer smart contract interactions. Made coding clearer and safer

What I've Learned

Starting out was easier than I expected, thanks to create-aptos-dapp. It gave me a solid foundation to build on, with best practices already in place. I didn't have to reinvent the wheel, which was a huge relief.

One of the biggest game-changers for me was discovering Surf. The type safety it provided when interacting with smart contracts was like having a safety net. It caught so many potential errors before they could cause problems. My code quality improved dramatically.

But it wasn't all smooth sailing. I faced some unique challenges that come with blockchain development. Testing, publishing, and upgrading smart contracts each had their own quirks. It really drove home the point that blockchain development is a whole different ball game.

As I progressed, I started creating additional tools to simplify my work. Switching to NextJS introduced new opportunities for server-side rendering.

What's Next

I'm excited to see how my NextJS template PR on GitHub turns out. Will it be accepted? Will other developers find it useful? Only time will tell.

Looking ahead, I'm eager to dive into Aptos Standards and Digital Assets. I have a feeling there's still so much to learn and explore in the Aptos ecosystem.

This journey has taught me that in blockchain development, every challenge is an opportunity to create something useful. Whether it's a new tool, a template, or just a better way of doing things, there's always room for innovation.

So here's to the next chapter of this Aptos adventure. I can't wait to see what new challenges and discoveries it brings!