# 使用 Warp 部署一个 PST 代币合约

By [W3.Hitchhiker](https://paragraph.com/@w3hitchhiker) · 2022-08-15

---

**_作者：_**[**_Dan_**](https://twitter.com/xiaoxinispotato)

**_English Version：「_** [**_Deploy a PST contract using Warp_**](https://w3hitchhiker.mirror.xyz/V2hBVrzJlg-2jGzkjVPpNFcZ2aW0kmouwSsGcjK6-Wg)**_」_**

在本教程中，我们将学习如何通过 Warp 建立一个简单的 PST 应用程序。为了实现这个目标，我们将编写一个 PST 合约，在用户本地测试网做一些测试后并把它部署到 Redstone 公共测试网。它将让我们铸造一些代币，以及在地址之间转移它们，并读取当前的余额。然后，我们将创建一个简单的 dApp，它将帮助我们以用户友好的方式与合约交互。

### _准备工作：_

*   _安装好 node.js 16.5 或者以上版本_
    
*   _安装好 yarn_
    
*   _安装好 visual studio code_
    
*   _安装好 python 3_
    
*   _安装好 git_
    

一、下载代码并启动 visual studio code
----------------------------

#### 1、clone 代码到本地机器，使用如下命令：

git clone [https://github.com/warp-contracts/academy.git](https://github.com/warp-contracts/academy.git)

![](https://storage.googleapis.com/papyrus_images/137ebc5a984518363e8bd7e849c515d9cdff0fe1fa46faadfbfe4926c0747179.png)

**2、切换到工作目录，使用命令：** `cd academy/warp-academy-pst/challenge`

![](https://storage.googleapis.com/papyrus_images/00e5efeee573cab585a6fe706b9821a862a8f778c139073f584b0ff86e92496b.png)

**3、用 visual studio code 打开源代码，使用命令 code . 启动 visual studio code**

![](https://storage.googleapis.com/papyrus_images/5cd398e7a6f3b6c4b908fcfd98202d189ce5223e8217fc723b26b7f1b903e92e.png)

**4、安装依赖，使用命令：** `yarn add arweave@1.11.4 arlocal@1.1.42 warp-contracts@1.0.1`

![](https://storage.googleapis.com/papyrus_images/987e092fda874b24fd2f6264c9847cd39cf4b2987e9dc95f6f532808d5e668ad.png)

二、编辑代码
------

#### 1、定义状态类型

打开文件 warp-academy-pst/challenge/src/contracts/types/types.ts，复制以下代码：

    export interface PstState {
    
      ticker: string;
    
      name: string;
    
      owner: string;
    
      balances: {
    
        [address: string]: number,
    
      };
    
    }
    

![](https://storage.googleapis.com/papyrus_images/3edd120c9b1002dc2bcc472f31b28ea75e513d3f6434a4ec8c966bb0f0eca7ad.png)

`PstState` 代表合约的当前状态。是由开发者来决定，在我们的实现中，它将由四个属性组成：

*   `ticker` —— token 名字的缩写
    
*   `name` —— token 的名字
    
*   `owner` —— token 拥有者
    
*   `balances` —— 定义地址的代币余额
    

#### 2、设置初始状态

打开文件，warp-academy-pst/challenge/src/contracts/initial-state.json

复制以下代码：

    {
    
      "ticker": "FC",
    
      "name": "Federation Credits",
    
      "owner": "GH2IY_3vtE2c0KfQve9_BHoIPjZCS8s5YmSFS_fppKI",
    
      "balances": {
    
        "GH2IY_3vtE2c0KfQve9_BHoIPjZCS8s5YmSFS_fppKI": 1000,
    
        "33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA": 230
    
      }
    
    }
    

**3、编辑合约源代码**

合约包括了一个 `handle` 函数，接受两个参数

第一个参数是 `state`，代表合约的当前状态，第二个参数是 `action`，包括两个属性，`caller` 代表合约的调用者，`inpu`t 代表用户的输入

`handle` 函数必须通过下面三种方式结束：

*   返回 `{ state: newState }` —— 当合约状态在特定的交互后发生变化时。
    
*   返回 `{ result: someResult }` —— 当合约状态在交互后没有改变时。
    
*   抛出 `ContractError` 异常
    

### **类型定义：**

打开文件 warp-academy-pst/challenge/src/contracts/types/types.ts，添加以下类型：

    export interface PstAction {
    
      input: PstInput;
    
      caller: string;
    
    }
    
    export interface PstInput {
    
      function: PstFunction;
    
      target: string;
    
      qty: number;
    
    }
    
    export interface PstResult {
    
      target: string;
    
      ticker: string;
    
      balance: number;
    
    }
    
    export type PstFunction = 'transfer' | 'mint' | 'balance';
    
    export type ContractResult = { state: PstState } | { result: PstResult };
    

`PstAction` 代表合约的交互。如前所述，它有两个属性 —— 调用者和输入。输入由三个属性构成：

*   `function` —— 交互类型 (在我们的例子中可以是转移代币，mint 代币，读取余额)
    
*   `target` —— 目标地址
    
*   `qty` —— 代币数量
    

`PstResult` 当合约状态在交互后没有改变时返回的对象，包含三个属性：

*   `target` ——目标地址.
    
*   `ticker` —— token名字的缩写.
    
*   `balance` —— 余额
    

### **余额函数：**

打开文件 warp-academy-pst/challenge/src/contracts/actions/read/balance.ts，添加以下代码：

    declare const ContractError;
    
    export const balance = async (
    
      state: PstState,
    
      { input: { target } }: PstAction
    
    ): Promise<ContractResult> => {
    
      const ticker = state.ticker;
    
      const balances = state.balances;
    
      if (typeof target !== 'string') {
    
        throw new ContractError('Must specify target to get balance for');
    
      }
    
      if (typeof balances[target] !== 'number') {
    
        throw new ContractError('Cannot get balance, target does not exist');
    
      }
    
      return { result: { target, ticker, balance: balances[target] } };
    
    };
    

> **上述函数将帮助我们读取指定目标地址的余额。**

### **铸造代币函数：**

打开文件 warp-academy-pst/challenge/src/contracts/actions/write/mintTokens.ts，添加以下代码：

    declare const ContractError;
    
    export const mintTokens = async (
    
      state: PstState,
    
      { caller, input: { qty } }: PstAction
    
    ): Promise<ContractResult> => {
    
      const balances = state.balances;
    
      if (qty <= 0) {
    
        throw new ContractError('Invalid token mint');
    
      }
    
      if (!Number.isInteger(qty)) {
    
        throw new ContractError('Invalid value for "qty". Must be an integer');
    
      }
    
      balances[caller] ? (balances[caller] += qty) : (balances[caller] = qty);
    
      return { state };
    
    };
    

> **这个函数将会 mint 代币到调用者地址。**

### 转移代币函数：

打开文件 warp-academy-pst/challenge/src/contracts/actions/write/transferTokens.ts，添加以下代码：

    declare const ContractError;
    
    export const transferTokens = async (
    
      state: PstState,
    
      { caller, input: { target, qty } }: PstAction
    
    ): Promise<ContractResult> => {
    
      const balances = state.balances;
    
      if (!Number.isInteger(qty)) {
    
        throw new ContractError('Invalid value for "qty". Must be an integer');
    
      }
    
      if (!target) {
    
        throw new ContractError('No target specified');
    
      }
    
      if (qty <= 0 || caller === target) {
    
        throw new ContractError('Invalid token transfer');
    
      }
    
      if (!balances[caller]) {
    
        throw new ContractError(`Caller balance is not defined!`);
    
      }
    
      if (balances[caller] < qty) {
    
        throw new ContractError(
    
          `Caller balance not high enough to send ${qty} token(s)!`
    
        );
    
      }
    
      balances[caller] -= qty;
    
      if (target in balances) {
    
        balances[target] += qty;
    
      } else {
    
        balances[target] = qty;
    
      }
    
      return { state };
    
    };
    

### 处理函数：

打开文件 warp-academy-pst/challenge/src/contracts/contract.ts，添加以下代码：

    import { balance } from './actions/read/balance';
    
    import { mintTokens } from './actions/write/mintTokens';
    
    import { transferTokens } from './actions/write/transferTokens';
    
    import { PstAction, PstResult, PstState } from './types/types';
    
    declare const ContractError;
    
    export async function handle(
    
      state: PstState,
    
      action: PstAction
    
    ): Promise<ContractResult> {
    
      const input = action.input;
    
      switch (input.function) {
    
        case 'mint':
    
          return await mintTokens(state, action);
    
        case 'transfer':
    
          return await transferTokens(state, action);
    
        case 'balance':
    
          return await balance(state, action);
    
        default:
    
          throw new ContractError(
    
            `No function supplied or function not recognised: "${input.function}"`
    
          );
    
      }
    
    }
    

`Handle` 函数是合约的核心，接受两个参数分别是 state 和 action，对 action 进行判断去调用对应的函数。

三、用 ESBuild 将 ts 文件转化为 js 文件
----------------------------

运行命令 `yarn build:contracts`：

![](https://storage.googleapis.com/papyrus_images/0a7eef49c24c9c0de4173376fd3815bab7e25f9621fb3c717daa2661ecd98331.png)

四、编写测试文件
--------

进入文件 warp-academy-pst/challenge/tests/contract.test.ts，复制以下代码：

    import fs from 'fs';
    
    import ArLocal from 'arlocal';
    
    import Arweave from 'arweave';
    
    import { JWKInterface } from 'arweave/node/lib/wallet';
    
    import path from 'path';
    
    import { addFunds, mineBlock } from '../utils/_helpers';
    
    import {
    
      PstContract,
    
      PstState,
    
      Warp,
    
      WarpNodeFactory,
    
      LoggerFactory,
    
      InteractionResult,
    
    } from 'warp-contracts';
    
    describe('Testing the Profit Sharing Token', () => {
    
      let contractSrc: string;
    
      let wallet: JWKInterface;
    
      let walletAddress: string;
    
      let initialState: PstState;
    
      let arweave: Arweave;
    
      let arlocal: ArLocal;
    
      let warp: Warp;
    
      let pst: PstContract;
    
      beforeAll(async () => {
    
      // ~~ Declare all variables ~~
    
      // ~~ Set up ArLocal and instantiate Arweave ~~
    
      arlocal = new ArLocal(1820);
    
      await arlocal.start();
    
      arweave = Arweave.init({
    
      host: 'localhost',
    
      port: 1820,
    
      protocol: 'http',
    
      });
    
      // ~~ Initialize 'LoggerFactory' ~~
    
      LoggerFactory.INST.logLevel('error');
    
      // ~~ Set up Warp ~~
    
      warp = WarpNodeFactory.forTesting(arweave);
    
      // ~~ Generate wallet and add funds ~~
    
      wallet = await arweave.wallets.generate();
    
      walletAddress = await arweave.wallets.jwkToAddress(wallet);
    
      await addFunds(arweave, wallet);
    
      // ~~ Read contract source and initial state files ~~
    
      contractSrc = fs.readFileSync(path.join(__dirname, '../dist/contract.js'), 'utf8');
    
      const stateFromFile: PstState = JSON.parse(
    
      fs.readFileSync(path.join(__dirname, '../dist/contracts/initial-state.json'), 'utf8')
    
      );
    
      // ~~ Update initial state ~~
    
      initialState = {
    
      ...stateFromFile,
    
      ...{
    
      owner: walletAddress,
    
      },
    
      };
    
      // ~~ Deploy contract ~~
    
      const contractTxId = await warp.createContract.deploy({
    
      wallet,
    
      initState: JSON.stringify(initialState),
    
      src: contractSrc,
    
      });
    
      // ~~ Connect to the pst contract ~~
    
      pst = warp.pst(contractTxId);
    
      pst.connect(wallet);
    
      // ~~ Mine block ~~
    
      await mineBlock(arweave);
    
      });
    
      afterAll(async () => {
    
      // ~~ Stop ArLocal ~~
    
      await arlocal.stop();
    
      });
    
      it('should read pst state and balance data', async () => {
    
      expect(await pst.currentState()).toEqual(initialState);
    
      expect(
    
      (await pst.currentBalance('GH2IY_3vtE2c0KfQve9_BHoIPjZCS8s5YmSFS_fppKI'))
    
      .balance
    
      ).toEqual(1000);
    
      expect(
    
      (await pst.currentBalance('33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA'))
    
      .balance
    
      ).toEqual(230);
    
      });
    
      it('should properly mint tokens', async () => {
    
      await pst.writeInteraction({
    
      function: 'mint',
    
      qty: 2000,
    
      });
    
      await mineBlock(arweave);
    
      expect((await pst.currentState()).balances[walletAddress]).toEqual(2000);
    
      });
    
      it('should properly add tokens for already existing balance', async () => {
    
      });
    
      it('should properly transfer tokens', async () => {
    
      await pst.transfer({
    
      target: 'GH2IY_3vtE2c0KfQve9_BHoIPjZCS8s5YmSFS_fppKI',
    
      qty: 555,
    
      });
    
      await mineBlock(arweave);
    
      expect((await pst.currentState()).balances[walletAddress]).toEqual(
    
      2000 - 555
    
      );
    
      expect(
    
      (await pst.currentState()).balances[
    
      'GH2IY_3vtE2c0KfQve9_BHoIPjZCS8s5YmSFS_fppKI'
    
      ]
    
      ).toEqual(1000 + 555);
    
      });
    
      it('should properly view contract state', async () => {});
    
      it('should properly perform dry write with overwritten caller', async () => {
    
      const newWallet = await arweave.wallets.generate();
    
      const overwrittenCaller = await arweave.wallets.jwkToAddress(newWallet);
    
      await pst.transfer({
    
      target: overwrittenCaller,
    
      qty: 1000,
    
      });
    
      await mineBlock(arweave);
    
      const result: InteractionResult<PstState, unknown> = await pst.dryWrite(
    
      {
    
      function: 'transfer',
    
      target: 'GH2IY_3vtE2c0KfQve9_BHoIPjZCS8s5YmSFS_fppKI',
    
      qty: 333,
    
      },
    
      overwrittenCaller
    
      );
    
      expect(result.state.balances[walletAddress]).toEqual(
    
      2000 - 555 - 1000
    
      );
    
      expect(
    
      result.state.balances['GH2IY_3vtE2c0KfQve9_BHoIPjZCS8s5YmSFS_fppKI']
    
      ).toEqual(1000 + 555 + 333);
    
      expect(result.state.balances[overwrittenCaller]).toEqual(1000 - 333);
    
      });
    
    });
    

> **以上代码会在本地启动一个 AR 的测试网，创建一个 AR 钱包，并将合约部署在本地的测试网上，并测试了铸造代币和转移代币等功能。**

进入 warp-academy-pst\\challenge\\utils\\\_helpers.ts，复制以下代码：

    import Arweave from 'arweave';
    
    import { JWKInterface } from 'arweave/node/lib/wallet';
    
    // ~~ Write function responsible for adding funds to the generated wallet ~~
    
    export async function addFunds(arweave: Arweave, wallet: JWKInterface) {
    
      const walletAddress = await arweave.wallets.getAddress(wallet);
    
      await arweave.api.get(`/mint/${walletAddress}/1000000000000000`);
    
      }
    
    // ~~ Write function responsible for mining block on the Arweave testnet ~~
    
    export async function mineBlock(arweave: Arweave) {
    
      await arweave.api.get('mine');
    
      }
    

五、在本地命令行部署合约并运行测试
-----------------

在命令行输入命令 `yarn test:node`，

![](https://storage.googleapis.com/papyrus_images/833c3420a056e370d1437a1694afd3a918e669857c5092ce8f69137436059a3f.png)

![](https://storage.googleapis.com/papyrus_images/1efaa80263231bd077f5b63f2e6813d15b434d940b1261ca074b18f9b5973352.png)

> **上图就是六个测试用例在本地测试网成功测试的结果。**

六、编辑用于部署合约到 redstone 测试网的源文件
----------------------------

进入文件 warp-academy-pst\\challenge\\src\\tools\\deploy-test-contract.ts，复制以下代码：

    import Arweave from 'arweave';
    
    import { JWKInterface } from 'arweave/node/lib/wallet';
    
    import { PstState } from '../contracts/types/types';
    
    import { LoggerFactory, PstContract, Warp, WarpNodeFactory } from 'warp-contracts';
    
    import fs from 'fs';
    
    import path from 'path';
    
    import { addFunds, mineBlock } from '../../utils/_helpers';
    
    let contractSrc: string;
    
    let wallet: JWKInterface;
    
    let walletAddress: string;
    
    let initialState: PstState;
    
    let arweave: Arweave;
    
    let warp: Warp;
    
    (async () => {
    
    // ~~ Declare variables ~~
    
    // ~~ Initialize Arweave ~~
    
    arweave = Arweave.init({
    
      host: 'testnet.redstone.tools',
    
      port: 443,
    
      protocol: 'https',
    
      });
    
    // ~~ Initialize `LoggerFactory` ~~
    
    LoggerFactory.INST.logLevel('error');
    
    // ~~ Initialize Warp ~~
    
    warp = WarpNodeFactory.memCached(arweave);
    
    // ~~ Generate wallet and add some funds ~~
    
    wallet = await arweave.wallets.generate();
    
    walletAddress = await arweave.wallets.jwkToAddress(wallet);
    
    await addFunds(arweave, wallet);
    
    // ~~ Read contract source and initial state files ~~
    
    contractSrc = fs.readFileSync(
    
      path.join(__dirname, '../../dist/contract.js'),
    
      'utf8'
    
      );
    
      const stateFromFile: PstState = JSON.parse(
    
      fs.readFileSync(
    
      path.join(__dirname, '../../dist/contracts/initial-state.json'),
    
      'utf8'
    
      )
    
      );
    
    // ~~ Override contract's owner address with the generated wallet address ~~
    
    initialState = {
    
      ...stateFromFile,
    
      ...{
    
      owner: walletAddress,
    
      },
    
      };
    
    // ~~ Deploy contract ~~
    
    const contractTxId = await warp.createContract.deploy({
    
      wallet,
    
      initState: JSON.stringify(initialState),
    
      src: contractSrc,
    
      });
    
    // ~~ Log contract id to the console ~~
    
    console.log(contractTxId);
    
    //Mine block
    
    await mineBlock(arweave);
    
    })();
    

这段代码主要是创建一个连接 redstone 测试网的实例，创建一个钱包，领取 AR 测试代币，部署合约到 redstone 测试网，部署成功以后会输出部署的合约地址。

七、部署合约到 redstone 测试网
--------------------

在命令行输入命令 `yarn ts-node src/tools/deploy-test-contract.ts`

![](https://storage.googleapis.com/papyrus_images/cec9f1780afcfac0693f8b00fc1d51485cf4fa5156b32f3ce1b4f7aeed8d5ef9.png)

上图说明合约已经成功部署到 redstone 测试网，合约地址为：**XeJiSHIkj0dVU7ddGtOIE8kZoQEIPcqvLS\_y5KPu62w**

八、编辑前端文件的源代码
------------

前端是使用 vue V2 开发的，以下是前端源代码的目录机构：

*   challenge/src/main.ts —— 是一个应用程序的入口。
    
*   challenge/src/pst-contract.ts —— 在这里我们定义 Arweave 和 SmartWeave 的实例并将其导出。
    
*   challenge/src/deployed-contracts.ts —— 在这里我们表明部署在 redstone 测试网上的合约ID。
    
*   challenge/src/constants.ts —— 所有的常量（包括 URL）。
    
*   challenge/src/assets —— 应用程序中使用的所有资产。
    
*   challenge/src/components —— 所有的组件，这些组件是 Vue 封装可重用代码的关键功能。
    
*   challenge/src/router —— 使用 vue-router 构建的应用程序的路由器。
    
*   challenge/src/store —— 使用 Vuex 构建应用程序的存储，Vuex 是一种状态管理模式和库。它可以作为应用程序中所有组件的集中存储。
    
*   challenge/src/views —— 应用程序的视图层。
    

进入 challenge/src/pst-contract.ts，复制以下代码：

    import Arweave from 'arweave';
    
    import {
    
      PstContract,
    
      PstState,
    
      Warp,
    
      WarpNodeFactory,
    
      LoggerFactory,
    
      InteractionResult,
    
      WarpWebFactory
    
      } from 'warp-contracts';
    
    export const arweave: Arweave = Arweave.init({
    
      host: 'testnet.redstone.tools',
    
      port: 443,
    
      protocol: 'https',
    
      });
    
      export const warp: Warp = WarpWebFactory.memCachedBased(arweave).useArweaveGateway().build();
    

进入文件 challenge\\src\\deployed-contracts.ts，添加之前成功部署的合约的 ID：

![](https://storage.googleapis.com/papyrus_images/2efe0d762e25dc1a4a68c423b573ba639d7022cf8338f7a97203b624d28d85d9.png)

> **这里的合约地址应该填入自己成功部署在 redstone 测试网的合约地址**

进入文件 challenge\\src\\constants.ts，复制以下代码：

    export const url = {
    
      warpGateway: '[https://gateway.redstone.finance](https://gateway.redstone.finance)',
    
    };
    

进入文件challenge\\src\\store\\index.ts,复制以下代码：

    import Vue from 'vue';
    
    import Vuex from 'vuex';
    
    import { arweave, warp } from '../pst-contract';
    
    import { deployedContracts } from '../deployed-contracts';
    
    import { PstState } from '@/contracts/types/types';
    
    import { Contract } from 'warp-contracts';
    
    Vue.use(Vuex);
    
    export default new Vuex.Store({
    
      state: {
    
      arweave,
    
      warp,
    
      state: {},
    
      validity: {},
    
      contract: null,
    
      walletAddress: null,
    
      },
    
      mutations: {
    
      setState(state, swState) {
    
      state.state = swState;
    
      },
    
      setValidity(state, validity) {
    
      state.validity = validity;
    
      },
    
      setContract(state, contract) {
    
      state.contract = contract;
    
      },
    
      setWalletAddress(state, walletAddress) {
    
      state.walletAddress = walletAddress;
    
      },
    
      },
    
      actions: {
    
      async loadState({ commit }) {
    
      // ~~ Generate arweave wallet ~~
    
      const wallet = await arweave.wallets.generate();
    
      // ~~ Get wallet address and mint some tokens ~~
    
      const walletAddress = await arweave.wallets.getAddress(wallet);
    
      await arweave.api.get(`/mint/${walletAddress}/1000000000000000`);
    
      // ~~ Connect deployed contract and wallet ~~
    
      const contract: Contract = warp
    
      .pst(deployedContracts.fc)
    
      .connect(wallet);
    
      commit('setContract', contract);
    
      // ~~ Set the state of the contract ~~
    
      const { state, validity } = await contract.readState();
    
      commit('setState', state);
    
      commit('setValidity', validity);
    
      commit('setWalletAddress', walletAddress);
    
      },
    
      },
    
      modules: {},
    
    });
    

进入文件 challenge\\src\\components\\Header\\Header.vue，插入以下代码：

    const txId = await this.contract.writeInteraction({
    
      function: 'mint',
    
      qty: parseInt(this.$refs.balanceMint.value),
    
      });
    
      await this.arweave.api.get('mine');
    
      // ~~ Set the balances by calling `currentState` method ~~
    
      const newResult = await this.contract.currentState();
    

具体插入代码的位置，请参考下图：

![](https://storage.googleapis.com/papyrus_images/c7986be9be4fee7c02f42b0b399c31c1dd992a1f60fead24d82f2cba6b18ff0c.png)

进入文件 challenge\\src\\components\\BalancesList\\BalancesList.vue，插入以下代码：

    const tx = await this.contract.transfer({
    
      target: address,
    
      qty: parseInt(qty),
    
      });
    
      // ~~ Mine a block ~~
    
      await this.arweave.api.get('mine');
    
      // ~~ Set new balances list by calling `currentState` method
    
      let newResult = await this.contract.currentState();
    

具体插入的代码位置，请参考下图：

![](https://storage.googleapis.com/papyrus_images/c331fcb474fa14346f5ed0e29c116e98c21fa3204fdf63993e1cf901cc593e00.png)

到现在为止，所有的源代码编辑工作完成。

九、通过前端与 redstone 测试网部署的合约进行交互
-----------------------------

在命令行输入 `yarn build` 来 build 整个项目，

![](https://storage.googleapis.com/papyrus_images/07256de4d5ebde6ec56515f91b04883ee38474021fa0044bf07f32458b2710a1.png)

下图是 build 成功的截图：

![](https://storage.googleapis.com/papyrus_images/1e487cad54fb39d1137f766e9ca5c7ed06f7d0fbc4e443164d0510db0ed44dae.png)

在命令行输入 `yarn serve` 来在开发环境中运行，下图是成功运行的截图：

![](https://storage.googleapis.com/papyrus_images/a0a227153c0a7232d6c34d04c83c0d5c5381af1dadb91615d9b0787e3de1401e.png)

打开浏览器，在地址栏输入网址 [http://localhost:8080/](http://localhost:8080/)，

打开后界面将会显示合约地址，钱包地址，以及代币余额等信息，这时候可以先 mint 一部分代币，然后再进行转账。

![](https://storage.googleapis.com/papyrus_images/75e11343a4d7fa5108088b13a778176d46015bf938f06bc26ebf7ee085d1113b.png)

进行 mint 代币以及转账后代币的余额会发生变化。

![](https://storage.googleapis.com/papyrus_images/42374a046b16362e38c12126a89dd72cfdc4fc0724e19de0abaa78961ad65a5c.png)

* * *

> _声明：本文内容仅供参考、交流，不构成任何投资建议。若存在明显的理解或数据的错误，欢迎反馈。_
> 
> _本文内容系 W3.Hitchhiker 原创，如需转载请标明出处。_
> 
> _商务合作：_[_hello@w3hitchhiker.com_](http://mailto:hello@w3hitchhiker.com/)
> 
> _官网：_[_https://w3hitchhiker.com/_](https://w3hitchhiker.com/)
> 
> _W3.Hitchhiker 官方推特：_[_https://twitter.com/HitchhikerW3_](https://twitter.com/HitchhikerW3)

---

*Originally published on [W3.Hitchhiker](https://paragraph.com/@w3hitchhiker/warp-pst)*
