{"author":{"address":null,"user":"https://learnblockchain.cn/people/13838"},"content":{"body":"---\r\ntitle: sui-move基础（六）：letsmove-task5\r\ndate: 2024-11-24 17:27:00\r\ntags:\r\n---\r\n\r\n# sui-move基础（七）：letsmove-task6\r\n\r\n### 需求\r\n\r\n- 完成 dapp-kit 学习\r\n- 完成 Sui SDK 学习\r\n- 完成 PTB的概念学习\r\n- 完成 Navi Protocol 学习\r\n- 在 dapp-kit里面用chrome钱包签名提交\r\n- 完成 必须在同一个PTB完成 Navi SDK 1.[存入Navi Protocol 1 SUI]  2.[借出当前日期的USDC 0.[月][天][小时] 的 USDC ]\r\n  3.[然后在存入等额的USDC]\r\n\r\n### 写在前面\r\n\r\n在任务描述中，已经给了我们一些进行学习的相关资料。\r\n\r\n因为task6涉及到的内容比较多，所以还是建议都大致浏览一下，其中的NAVI book和dapp-kit部分更是需要着重关注，仔细阅读一下。\r\n\r\n其实，如果相关经验的话，例如将训练营task作为起点的程序员，想要完成本节任务是十分困难的。而在task之外，补充相关知识也是非常重要的。毕竟，学习才是主要的目的（在拥有足够赚取报酬的代码和知识水平之前），task只是一个引导我们学习的路径罢了。\r\n\r\n在这里，我仍然将任务指南贴出来：\r\n\r\n- [sui sdk](https://sdk.mystenlabs.com/typescript)\r\n- [ptb sdk](https://sdk.mystenlabs.com/typescript/transaction-building/basics)\r\n- [ptb concepts](https://docs.sui.io/concepts/transactions/prog-txn-blocks)\r\n- [navi-sdk](https://github.com/naviprotocol/navi-sdk)\r\n- [navi-docs](https://naviprotocol.gitbook.io/navi-protocol-developer-docs/how-to-interact-with-the-contract/navi-sdk/api-interface)\r\n\r\n另外，尤其推荐观看uvd老师的相关视频教程，这里我贴出与本节最相关的一个教程：\r\n\r\nhttps://www.bilibili.com/video/BV1DmmyYvEnC/?spm_id_from=333.999.0.0\u0026vd_source=e4ecd9395f6e61a139c766490968f125\r\n\r\n### 概述\r\n\r\n在最开始，对于如何进行一个dapp的开发，我们肯定是十分迷茫的。\r\n\r\n幸运的是，我们相对而言不用这么迷茫，sui已经有了dapp开发的脚手架，我们不用像最开始的冒险家那样面对一片陌生的新大陆。\r\n\r\n在上述uvd老师的视频里，介绍了dapp开发的脚手架：dapp-kit ， 我在这里对如何下载进行一个简要的介绍：\r\n\r\n#### dapp-kit\r\n\r\n1. 准备工作\r\n\r\n    - 安装必要的工具\r\n        确保你有以下开发环境：\r\n\r\n        Node.js (版本不宜过旧)\r\n        npm 或 yarn\r\n\r\n    - 安装 dapp-kit\r\n        在项目目录中运行以下命令：\r\n        npm i --save @mysten/dapp-kit @mysten/sui @tanstack/react-query\r\n\r\n2. 创建项目\r\n\r\n    在终端运行：\r\n\r\n    npm create @mysten/dapp\r\n\r\n    控制台会引导你创建一个新的 dApp 项目。\r\n\r\n    创建新的 dApp 时，你有以下模板可供选择：\r\n\r\n    - react-client-dapp：一个基本的 React dApp，用于获取连接的钱包拥有的对象列表。\r\n\r\n    - react-e2e-counter：一个端到端示例，其中包含 Move 代码和简单计数器应用程序的 UI。\r\n\r\n    就本task而言，选择react-client-dapp即可。\r\n\r\n3. 项目结构\r\n\r\n    创建项目后，你将看到：\r\n\r\n    ```bash\r\n    my-first-sui-dapp$ tree\r\n    .\r\n    ├── README.md\r\n    ├── index.html\r\n    ├── package.json\r\n    ├── prettier.config.cjs\r\n    ├── src\r\n    │   ├── App.tsx\r\n    │   ├── OwnedObjects.tsx\r\n    │   ├── WalletStatus.tsx\r\n    │   ├── main.tsx\r\n    │   ├── networkConfig.ts\r\n    │   └── vite-env.d.ts\r\n    ├── tsconfig.json\r\n    ├── tsconfig.node.json\r\n    └── vite.config.ts\r\n\r\n    1 directory, 13 files\r\n    ```\r\n\r\n    这是为我们自动创建的模板，我们可以在它的基础上进行修改。\r\n\r\n### PTB与NAVI\r\n\r\n那么在正式开始编码之前，我们先了解一下Programmable Transaction Block (PTB)和Sui上的NAVI Protocol。\r\n\r\n#### PTB\r\n\r\nProgrammable Transaction Block (PTB) 是 Sui 网络中的一种强大工具，允许开发者在单个交易中包含多个操作。PTB 通过将多个交易命令链接在一起，提供了一个灵活且高效的方式来生成交易。每个 PTB 可以执行多达 1024 个独特的操作，显著降低了 gas 费用并加快了执行速度。PTB 的设计保证了交易的原子性，如果其中一个命令失败，整个块将失败，且不会应用任何效果。\r\n\r\nPTB 的主要特性\r\n\r\n- 模块化设计:\r\n\r\n    PTB 是由一组操作和指令组成的事务单元，每个操作可以执行独立的功能，如调用 Move 合约、转账资产等。\r\n    通过组合多个操作，开发者可以实现复杂的逻辑，同时保持事务的原子性。\r\n\r\n- 原子性:\r\n\r\n    PTB 中的所有操作要么全部成功执行，要么全部回滚。这种设计保证了状态的一致性，非常适合构建需要强一致性保证的应用。\r\n\r\n- 灵活的组合性:\r\n\r\n    PTB 可以包含多个输入（如代币、对象）和多个输出（如新生成的对象或资产）。\r\n    支持跨模块调用，实现合约之间的互操作。\r\n    \r\n- 高性能:\r\n\r\n    PTB 的设计充分利用了 Sui 的并行执行引擎。通过对事务依赖的静态分析，PTB 能够最大化并行度，提升执行效率。\r\n\r\n#### NAVI Protocol\r\n\r\nNAVI Protocol 是构建于 Sui 区块链之上的一站式去中心化流动性协议，为用户提供高效的资产借贷和流动性管理服务。NAVI 的设计目标是通过创新的去中心化金融（DeFi）基础设施，为用户提供无中介的资产操作，同时优化安全性和资本效率。\r\n\r\n核心功能\r\n\r\n- 借贷市场：\r\n\r\n    用户可以通过 NAVI 提供或借入多种数字资产，收益来源于利息。\r\n    提供流动性时，用户会获得“Navi 收据代币”（例如 nSUI），作为其在流动性池中的份额证明，这些代币可以用作抵押品​。\r\n\r\n- 自动杠杆金库（Automatic Leverage Vaults）：\r\n\r\n    用户可以在 NAVI 中使用低利率资产借贷进行杠杆操作，优化资产收益，比如参与质押奖励或流动性挖矿​。\r\n\r\n- 隔离模式（Isolation Market）：\r\n\r\n    NAVI 支持在特定的隔离市场中列出新资产，设定债务上限（Debt Ceiling）以限制波动性资产的借贷风险。这种机制增强了平台的安全性​。\r\n\r\n- 灵活的治理机制：\r\n\r\n    持有原生治理代币 NAVX 的用户可以参与投票，决定如利率设定、新资产支持和抵押率调整等关键事项​。\r\n\r\n### 任务指南\r\n\r\n那么如何完成要求的任务呢？\r\n\r\n我们可以分两个模块来完成：\r\n\r\n- 第一个模块用于构建PTB，我们将PTB进行的操作都在这个模块定义。\r\n\r\n- 第二个模块用于调用执行PTB，在这个模块中，我们进行钱包连接和前端调用等操作。\r\n\r\n而对于第一个模块，可以分为以下几个步骤：\r\n\r\n- 定义 PTB\r\n\r\n    - Step 1: 存入 1 SUI 到 Navi Protocol\r\n\r\n    - Step 2: 借出 USDC\r\n\r\n    - Step 3: 再次存入等量的 USDC\r\n\r\n    - Step 4: 提交 PTB ，签名并执行交易\r\n\r\n任务要求我们进行日期计算，我们可以将计算放在PTB中，但为了模块化考虑，我们在另一个模块中完成它。\r\n\r\n对于这个模块，我们作此建构：\r\n\r\n- 动态金额计算: \r\n    \r\n    调用 calculateBorrowAmount 根据当前时间生成借贷金额，格式为 0.MMDDHH。\r\n\r\n- 连接钱包:\r\n\r\n    使用 useWallets 获取支持的钱包列表。\r\n    用户选择钱包后，通过 connect 方法完成连接。\r\n    在连接成功后显示用户的钱包地址。\r\n\r\n- 交易 UI:\r\n\r\n    展示用户需要完成的交易流程。\r\n    通过按钮点击触发 mainAction，完成 PTB 事务的签名与提交。\r\n\r\n- PTB 提交:\r\n\r\n    mainAction 是具体实现事务的核心逻辑，负责：\r\n    组合事务（如存入 SUI、借出 USDC 等）。\r\n    调用 signAndExecuteTransaction 提交事务。\r\n\r\n### 代码实现\r\n\r\n因为之前我们使用dap-kit进行了初步的预构建，\r\n这时我们只需要在其基础上进行修改即可。\r\n\r\n#### PTB实现\r\n\r\n我们新增一个文件，NAVI.ts：\r\n\r\n```typescript\r\nimport { Transaction } from '@mysten/sui/transactions';\r\nimport { depositCoin, borrowCoin, } from 'navi-sdk/dist/libs/PTB'\r\nimport { Sui, wUSDC } from \"navi-sdk/dist/address\";\r\nimport { Pool, PoolConfig } from \"navi-sdk/dist/types\";\r\nimport { pool } from 'navi-sdk/dist/address'\r\n\r\n\r\nexport const mainAction = async (account: any, { signAndExecute, usdcAmount, onSuccess = () =\u003e { } }: {\r\n    signAndExecute: any;\r\n    usdcAmount: number;\r\n    onSuccess?: (result: any) =\u003e void;\r\n    onError?: (result: any) =\u003e void;\r\n}) =\u003e {\r\n\r\n    if (!account) {\r\n        postMessage(\"Please connect your wallet first\");\r\n        return;\r\n    }\r\n\r\n\r\n    const tx = new Transaction();\r\n\r\n    tx.setSender(account.address);\r\n\r\n    // 获取池配置\r\n    const SuiPool: PoolConfig = pool[Sui.symbol as keyof Pool];\r\n    const wUSDCPool: PoolConfig = pool[wUSDC.symbol as keyof Pool];\r\n\r\n    if (!SuiPool || !wUSDCPool) {\r\n        throw new Error(\"Invalid pool configuration\");\r\n    }\r\n\r\n    // 设置gas\r\n    tx.setGasBudget(1e9);\r\n\r\n    try {\r\n        // 分离出1sui\r\n        const [sui] = tx.splitCoins(tx.gas, [1e9]);\r\n\r\n        console.log(\"Checking object existence...\");\r\n        const suiObject = await tx.object(sui);\r\n        if (!suiObject) {\r\n            throw new Error(`Object ${sui} does not exist`);\r\n        }\r\n\r\n        // 将其存入navi\r\n        await depositCoin(tx as any, SuiPool, sui, 1e9);\r\n\r\n        // 借出 usdc\r\n        const [usdc_coin] = await borrowCoin(tx as any, wUSDCPool, usdcAmount);\r\n\r\n        // 存入 usdc\r\n        await depositCoin(tx as any, wUSDCPool, usdc_coin, usdcAmount);\r\n\r\n        // 签名执行交易\r\n        await signAndExecute({\r\n            transaction: tx, chain: \"sui:mainnet\"\r\n        }, {\r\n            onSuccess,\r\n            onError: (error: any) =\u003e { // 为 error 参数指定类型\r\n                console.error(\"Transaction failed:\", error);\r\n                postMessage(error.message || \"Transaction failed\");\r\n            },\r\n        });\r\n    } catch (error) {\r\n        console.error(\"Transaction failed:\", error);\r\n        postMessage(error instanceof Error ? error.message : \"An unknown error occurred\");\r\n    }\r\n\r\n}\r\n\r\n```\r\n\r\n简而言之，在这个 `NAVI.ts` 中，我们构建了一个PTB，并在其中\r\n编写了所有我们需要进行的交易，并最终签名并执行。\r\n\r\n#### 前端实现\r\n\r\n前端调用模块我们可以直接改造App.tsx：\r\n\r\n```typescript\r\nimport { useCurrentAccount, useSignAndExecuteTransaction } from \"@mysten/dapp-kit\";\r\nimport { Container, Flex, Heading, Text, Button } from \"@radix-ui/themes\";\r\nimport { ConnectButton, useConnectWallet, useWallets } from '@mysten/dapp-kit';\r\nimport { wUSDC } from \"navi-sdk/dist/address\";\r\nimport { mainAction } from \"./NAVI.ts\";\r\n\r\nconst calculateBorrowAmount = (month: number, day: number, hour: number): number =\u003e {\r\n  const borrowAmountStr = `0.${month.toString().padStart(2, \"0\")}${day.toString().padStart(2, \"0\")}${hour.toString().padStart(2, \"0\")}`;\r\n  return parseFloat(borrowAmountStr) * 10 ** wUSDC.decimal;\r\n};\r\n\r\nconst Execute = () =\u003e {\r\n\r\n  const wallets = useWallets();\r\n  const { mutate: connect } = useConnectWallet();\r\n\r\n  const account = useCurrentAccount();\r\n  const { mutate: signAndExecute } = useSignAndExecuteTransaction();\r\n\r\n  const date = new Date();\r\n  const usdcAmount = calculateBorrowAmount(date.getMonth() + 1, date.getDate(), date.getHours());\r\n\r\n  return (\r\n\r\n\r\n    \u003cContainer my=\"2\"\u003e\r\n      \u003cHeading mb=\"2\"\u003eWallet Status\u003c/Heading\u003e\r\n\r\n      \u003cdiv style={{ padding: 20 }}\u003e\r\n        \u003cConnectButton /\u003e\r\n        \u003cul\u003e\r\n          {wallets.map((wallet) =\u003e (\r\n            \u003cli key={wallet.name}\u003e\r\n              \u003cbutton\r\n                onClick={() =\u003e {\r\n                  connect(\r\n                    { wallet },\r\n                    {\r\n                      onSuccess: () =\u003e console.log('connected'),\r\n                    },\r\n                  );\r\n                }}\r\n              \u003e\r\n                Connect to {wallet.name}\r\n              \u003c/button\u003e\r\n            \u003c/li\u003e\r\n          ))}\r\n        \u003c/ul\u003e\r\n      \u003c/div\u003e\r\n\r\n      {account ? (\r\n        \u003cFlex direction=\"column\"\u003e\r\n          \u003cText\u003eWallet connected\u003c/Text\u003e\r\n          \u003cText\u003eAddress: {account.address}\u003c/Text\u003e\r\n        \u003c/Flex\u003e\r\n      ) : (\r\n        \u003cText\u003eWallet not connected\u003c/Text\u003e\r\n      )}\r\n\r\n      \u003cHeading mb=\"2\"\u003eTransaction Action\u003c/Heading\u003e\r\n      {account ? (\r\n        \u003cFlex direction=\"column\"\u003e\r\n          \u003cHeading mb=\"2\"\u003e执行交易\u003c/Heading\u003e\r\n          \u003cFlex direction=\"column\"\u003e\r\n            \u003cText\u003e1. [存入Navi Protocol 1 SUI] \u003c/Text\u003e\r\n            \u003cText\u003e{`2.[借出当前日期的USDC ${usdcAmount} 的 USDC ]`}\u003c/Text\u003e\r\n            \u003cText\u003e3.[然后在存入等额的USDC]\u003c/Text\u003e\r\n          \u003c/Flex\u003e\r\n          \u003cButton\r\n            size=\"3\"\r\n            onClick={() =\u003e {\r\n              mainAction(account, {\r\n                signAndExecute, usdcAmount, onSuccess(result) {\r\n                  console.log(result, 'Transaction success!')\r\n                }, onError(error) {\r\n                  console.log(error, 'Something went wrong!')\r\n                },\r\n              });\r\n            }}\r\n            style={{ cursor: 'pointer' }}\r\n          \u003e\r\n            \u003cText\u003e点击执行此交易\u003c/Text\u003e\r\n          \u003c/Button\u003e\r\n        \u003c/Flex\u003e\r\n      ) : null}\r\n    \u003c/Container\u003e\r\n  );\r\n}\r\n\r\nexport default Execute;\r\n\r\n```\r\n\r\n我们定义一个 `calculateBorrowAmount` 用于计算需要存入的金额，注意需要乘以wUSDC的精度 `10 ** wUSDC.decimal`。\r\n\r\n由于需要使用chrome钱包进行交易，我们编写一个连接钱包的逻辑：\r\n\r\n```typescript\r\n\u003cdiv style={{ padding: 20 }}\u003e\r\n        \u003cConnectButton /\u003e\r\n        \u003cul\u003e\r\n          {wallets.map((wallet) =\u003e (\r\n            \u003cli key={wallet.name}\u003e\r\n              \u003cbutton\r\n                onClick={() =\u003e {\r\n                  connect(\r\n                    { wallet },\r\n                    {\r\n                      onSuccess: () =\u003e console.log('connected'),\r\n                    },\r\n                  );\r\n                }}\r\n              \u003e\r\n                Connect to {wallet.name}\r\n              \u003c/button\u003e\r\n            \u003c/li\u003e\r\n          ))}\r\n        \u003c/ul\u003e\r\n      \u003c/div\u003e\r\n```\r\n\r\n这一部分的代码可以直接在官方文档中找到:https://sdk.mystenlabs.com/dapp-kit/wallet-hooks/useConnectWallet。\r\n\r\n下面的Wallet Status代码可以直接从已经存在的WalletStatus.tsx中迁移过来。\r\n\r\n最后就是打印一些log，并执行调用`mainAction`的逻辑。\r\n\r\n至此，task6的编写便告完成。\r\n\r\n### 调用\r\n\r\n在相应路径下运行 `npm run`，然后来到前端页面进行调用即可。\r\n\r\n如果你发现调用失败的话，或许你需要注意main.tsx中的 defaultNetwork 定义。","title":"sui-move基础（七）：letsmove-task6"},"history":null,"timestamp":1733654334,"version":1}