scaffold-aptos 脚手架 | Move dApp 极速入门

本文将阐述一个由  NonceGeekDAO 创建的 Aptos dApp 的脚手架 —— Scaffold-Aptos。

https://github.com/NonceGeek/scaffold-move

所用到的示例合约见:

https://github.com/NonceGeek/MoveDID

其对如下项目有所借鉴:

https://github.com/Amovane/aptos-NFT-marketplace

项目主要技术栈:Next.jsTailwind

0x00 运行程序

$ git clone https://github.com/NonceGeek/scaffold-move.git
$ cd scaffold-aptos & yarn
$ direnv allow # 如使用 direnv
$ export [...things in .envrc...] # 如直接设置环境变量
$ yarn dev

0x01 项目结构

.
├── README.md
├── next-env.d.ts
├── next.config.js
├── node_modules
├── package.json
├── postcss.config.js
├── public
├── scripts
├── src
    ├── components    /* important */
    │   ├── AptosConnect.tsx
    │   ├── ModalContext.tsx
    │   ├── NavBar.tsx
    │   ├── NavItem.tsx
    │   ├── TooltipSection.tsx
    │   └── WalletModal.tsx
    ├── config        /* important */
    │   └── constants.ts
    ├── hooks
    │   ├── index.ts
    │   ├── useOffers.ts
    │   └── useTokens.ts
    ├── pages        /* important */
    │   ├── _app.tsx
    │   ├── api
    │   ├── did_querier.tsx
    │   ├── endpoint.tsx
    │   └── index.tsx
    ├── styles
    │   ├── globals.css
    │   └── loading.css
    ├── types
    │   ├── Offer.ts
    │   └── index.ts
    └── utils
        ├── aptos.ts
        ├── nftstorage.ts
        └── supabase.ts
├── tailwind.config.js
├── .envrc
├── tsconfig.json
└── yarn.lock

我们主要要关注的三个文件夹是componentsconfig 和 pages

0x02 Config — 环境变量管理

环境变量被写在了.envrc文件里,推荐通过direnv这个环境变量管理工具进行环境变量的管理。

不过直接在bash中执行设置环境变量的命令也是可以的:

export NEXT_PUBLIC_DAPP_ADDRESS=0x0c78cfc8cf31129444f22da3e527a48732f5b6e4e09ffbe82ffb900f84b22a17
export NEXT_PUBLIC_DAPP_NAME=DID
export NEXT_PUBLIC_MARKET_COIN_TYPE=0x1::aptos_coin::AptosCoin
export NEXT_PUBLIC_APTOS_NODE_URL=https://fullnode.testnet.aptoslabs.com/v1/
export NEXT_PUBLIC_APTOS_FAUCET_URL=https://faucet.devnet.aptoslabs.com/v1/
export NEXT_PUBLIC_APTOS_NETWORK=testnet

在  constants.ts 文件中,我们通过这样的命令将环境变量转化为一个全局变量:

export const DAPP_NAME = process.env.NEXT_PUBLIC_DAPP_NAME!; // changed here.

如果我们要添加新的环境变量,则可以依葫芦画瓢。例如添加一个指向浏览器的 URL:

export const NETWORK=process.env.NEXT_PUBLIC_APTOS_NETWORK!;
export const MODULE_URL="https://explorer.aptoslabs.com/account/" + DAPP_ADDRESS + "/modules?network=" + NETWORK

0x03 Components — 页面组件

主要包含如下组件套装 ——

  • 钱包连接组件:由AptosConnect.tsxModalContext.tsxWalletModal.tsx组成。
  • 导航组件:由NavBar.tsxNavItem.tsx组成。

3.1 钱包连接组件

AptosConnect.tsx 的源码如下:

import { useWallet } from "@manahippo/aptos-wallet-adapter";
import { useContext } from "react";
import { ModalContext } from "./ModalContext";
import { WalletModal } from "./WalletModal";

export function AptosConnect() {
  const { account } = useWallet();
  const { modalState, setModalState } = useContext(ModalContext);

  return (
    <>
      {account?.address ? (
        <button
          className="btn btn-primary w-48"
          onClick={() => setModalState({ ...modalState, walletModal: true })}
          style={{
            textOverflow: "ellipsis",
            overflow: "hidden",
            display: "inline",
          }}
        >
          {account!.address!.toString()!}
        </button>
      ) : (
        <button
          className="btn"
          onClick={() => setModalState({ ...modalState, walletModal: true })}
        >
          Connect wallet
        </button>
      )}
      <WalletModal />
    </>
  );
}

useContext:

接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定。

https://zh-hans.reactjs.org/docs/hooks-reference.html#usecontext

所以在点击Connect Wallet按钮后,会调用scaffold-aptos/src/pages/_app.tsx中的Provider

...
	return (
    <WalletProvider wallets={wallets} autoConnect={false}>
      <ModalContext.Provider value={modals}>
        <div className="px-8">
          <NavBar />
          <Component {...pageProps} className="bg-base-300" />
        </div>
      </ModalContext.Provider>
    </WalletProvider>
  );
...

通过useMemo我们可以调整能登入的钱包,自然也可以参考 Adapter 的写法支持新的钱包。

const wallets = useMemo(
  () => [
  new AptosWalletAdapter(),
  new MartianWalletAdapter(),
  new PontemWalletAdapter(),
  new FewchaWalletAdapter(),
  ],
  []
);

3.2 导航组件

NavBar.tsx源码如下:

import Image from "next/image";
import { NavItem } from "./NavItem";
import { AptosConnect } from "./AptosConnect";
import {
  MODULE_URL
} from "../config/constants";

export function NavBar() {
  return (
    <nav className="navbar py-4 px-4 bg-base-100">
      <div className="flex-1">
        <a href="http://movedid.build" target="_blank">
          <Image src="/logo.png" width={64} height={64} alt="logo" />
        </a>
        <ul className="menu menu-horizontal p-0 ml-5">
          <NavItem href="/" title="AddrAggregatorManager" />
          <NavItem href="/endpoint" title="EndpointManager" />
          <NavItem href="/did_querier" title="DIDQuerier" />
          <li className="font-sans font-semibold text-lg">
            <a href="https://github.com/NonceGeek/MoveDID/" target="_blank">Source Code</a>
            <a href={MODULE_URL} target="_blank">Contract on Explorer</a>
          </li>
        </ul>
      </div>
      <AptosConnect />
    </nav>
  );

通过NavBar,我们能设置导航栏中的 URL。

如果是外链,可以像代码中所示的那样,使用<li>标签来标记。

需要注意的是,在Next框架下,页面的路由是根据pages中添加的文件自动生成的:

...
    ├── pages        /* important */
    │   ├── _app.tsx
    │   ├── api
    │   ├── did_querier.tsx
    │   ├── endpoint.tsx
    │   └── index.tsx
...

0x04 Pages 和链交互

我们以index.tsx模块为例。该模块中涉及的功能点归纳如下:

  • 页面相关:
    • 通过输入框获取值
    • 将值返回到页面上
  • 链交互相关:
    • 从链上拿到某个账户下的全量资源(Resources)
    • 从链上拿到某个账户下的特定资源
    • 调用某个模块(合约)中的方法

4.1 页面相关逻辑

这里用了比较懒的处理方式,通过一次性的useState将所有页面上的传入值归集到formInput下。


  const [formInput, updateFormInput] = useState<{
    description: string;
    resource_path: string;
    addr_type: number;
    addr: string;
    addr_description: string;
    chains: Array<string>;
    
  }>({
    description: "",
    resource_path: "",
    addr_type: 1,
    addr: "",
    addr_description: "",
    chains: [],
  });

然后只要在 Input 或其它任何类型的组件中赋值即可:

<input
  placeholder="Chains"
  className="mt-8 p-4 input input-bordered input-primary w-full"
  onChange={(e) =>
  updateFormInput({ ...formInput, chains: JSON.parse(e.target.value) })
  }
/>

在使用时通过如下代码拿到变量:

const { description, resource_path, addr_type, addr, addr_description, chains } = formInput;

将值传入页面的方法见如下代码:

<p>{JSON.stringify(resource)}</p>

4.2 链交互逻辑

(1)获取全部资源

通过如下代码,调用 aptosClient 封装好的方法,我们可以获取全部资源:

import {
  DAPP_ADDRESS,
  APTOS_FAUCET_URL,
  APTOS_NODE_URL
} from "../config/constants";
import {
  WalletClient
} from "@martiandao/aptos-web3-bip44.js";
const { account, signAndSubmitTransaction } = useWallet();
const client = new WalletClient(APTOS_NODE_URL, APTOS_FAUCET_URL);
async function get_resources() {
  client.aptosClient.getAccountResources(account!.address!.toString()).then(value =>
		// do something to value!
	);
}

(2)根据资源路径地址获取资源

import { MoveResource } from "@martiandao/aptos-web3-bip44.js/dist/generated";
const [resource, setResource] = React.useState<MoveResource>();
async function get_did_resource() {
  client.aptosClient.getAccountResource(account!.address!.toString(),  DAPP_ADDRESS + "::addr_aggregator::AddrAggregator").then(
    setResource
  );
}

setResource 之后,我们即可以通过resource变量对该资源进行访问。

(3)调用合约方法

以 MoveDID 中的添加地址方法为例:

async function create_addr() {
  await signAndSubmitTransaction(
    add_addr(),
    { gas_unit_price: 100 }
  );
}
...
function add_addr() {
  const { description, resource_path, addr_type, addr, addr_description, chains } = formInput;
  return {
  type: "entry_function_payload",
  function: DAPP_ADDRESS + "::addr_aggregator::add_addr",
  type_arguments: [],
  arguments: [
      addr_type,
      addr,
      chains,
      addr_description,
    ],
  };
}

关于type_argumentsarguments的讲解见这里:

https://aptos.dev/guides/interacting-with-the-aptos-blockchain/

在使用这个方法的时候,会调用浏览器钱包插件发起交易:

以上内容均转载自互联网,不代表AptosNews立场,不是投资建议,投资有风险,入市需谨慎,如遇侵权请联系管理员删除。

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023年7月21日 下午12:37
下一篇 2023年7月21日 下午1:12

相关文章

发表回复

登录后才能评论
微信扫一扫
百度扫一扫

订阅AptosNews

订阅AptosNews,掌握Aptos一手资讯。


窗口将关闭与 0

本站没有投资建议,投资有风险,入市需谨慎。