本文将阐述一个由 NonceGeekDAO 创建的 Aptos dApp 的脚手架 —— Scaffold-Aptos。
https://github.com/NonceGeek/scaffold-move
所用到的示例合约见:
https://github.com/NonceGeek/MoveDID
其对如下项目有所借鉴:
https://github.com/Amovane/aptos-NFT-marketplace
项目主要技术栈:Next.js
、Tailwind
。
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
我们主要要关注的三个文件夹是components
、config
和 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.tsx
、ModalContext.tsx
和WalletModal.tsx
组成。 - 导航组件:由
NavBar.tsx
和NavItem.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_arguments
和arguments
的讲解见这里:
https://aptos.dev/guides/interacting-with-the-aptos-blockchain/
在使用这个方法的时候,会调用浏览器钱包插件发起交易:
以上内容均转载自互联网,不代表AptosNews立场,不是投资建议,投资有风险,入市需谨慎,如遇侵权请联系管理员删除。