# 第一个本地 Warp 合约

By [Wang Defa](https://paragraph.com/@westernjournalist) · 2023-12-28

---

### 1\. 安装运行 arlocal

    npm i -g arlocal
    

    npx arlocal start
    

Arlocal 模拟了一个本地 arweave gateway server，默认运行在1984端口。

Arocal 重启后状态就没了。

### 2\. 创建 wallet

可以用 arconnect 生成一个地址，然后导出私钥，保存到本地目录，

或者用 arweave 节点程序生成密钥对，

或者用 arweave js 库生成密钥对。

    const Arweave = require("arweave");
    
    // 创建 Arweave 实例
    const arweave = Arweave.init();
    
    // 生成一个新的 JWK（JSON Web Key）钥匙对
    const key = await arweave.wallets.generate();
    
    // 从 JWK 获取 Arweave 地址
    const address = await arweave.wallets.jwkToAddress(key);
    
    console.log("Private Key:", key);
    console.log("Address:", address);
    

Arweave 用的是 RSA 密钥对，私钥长这样：

    {
        "kty": "RSA",
        "n": "*******************************",
        "e": "AQAB",
        "d": "*******************************",
        "p": "*******************************",
        "q": "*******************************",
        "dp": "*******************************",
        "dq": "*******************************",
        "qi": "*******************************"
    }
    

### 3\. 编写合约

写一个简单的 token 合约，功能就是记录余额和执行转账。合约本体就是 handle 函数，state 是合约状态，action.input 是调用合约的参数。

Arweave 合约的 state 和 code 是分开的，对 state 求值需要把 code，initial state，interactions 全部攒起来，warp 框架会把这些都搞定。（此处理解不深，可能不准确）

    // file: token.js
    export async function handle(state, action) {
        if (action.input.function === "transfer") {
            const { target, qty } = action.input;
    
            if (!target || !qty) {
                throw new ContractError("Invalid input");
            }
    
            const balance = state.balances[action.caller] || 0;
    
            if (balance < qty) {
                throw new ContractError("Insufficient balance");
            }
    
            // 执行转账
            state.balances[action.caller] -= qty;
            state.balances[target] = (state.balances[target] || 0) + qty;
        }
    
        return { state };
    }
    

### 4\. 设定初始状态

创建一个 token-initState.json 文件，定义初始状态，主要是设置 balances.

    {
        "owner": "-aVC26iRLJmi7uq4iKtRllLA2DJ5EoABSZV5EwNkwdU",
        "canEvolve": true,
        "balances": {
            "-aVC26iRLJmi7uq4iKtRllLA2DJ5EoABSZV5EwNkwdU": 1000
        }
    }
    

### 5\. 部署脚本

创建一个 ES6 module，创建一个 javascript

    const run = async () => {
     //
    }
    
    run();
    

创建 warp 实例，forLocal 表示 local 环境，如果是主网就改成 forMainnet

    import { DeployPlugin } from "warp-contracts-plugin-deploy";
    const warp = WarpSdk.WarpFactory.forLocal().use(new DeployPlugin());
    

读取 wallet1 和 wallet2

    import fs from "fs";
    import * as path from "path";
    
    import { fileURLToPath } from "url";
    
    const __filename = fileURLToPath(import.meta.url);
    const __dirname = path.dirname(__filename);
    
    const wallet1 = JSON.parse(
        fs.readFileSync(
            path.join(__dirname, "../jwk.json").toString(),
            "utf-8"
        )
    );
    
    const wallet2 = JSON.parse(
        fs.readFileSync(
            path.join(__dirname, "../jwk-2.json").toString(),
            "utf-8"
        )
    );
    
    const walletAddress1 = await warp.arweave.wallets.jwkToAddress(wallet1);
    const walletAddress2 = await warp.arweave.wallets.jwkToAddress(wallet2);
    
    console.log("address 1 " + walletAddress1);
    console.log("address 2 " + walletAddress2);
    

引入合约 code 和 initState

    const contractSrc = fs.readFileSync(
        path.join(__dirname, "./contracts/token.js"),
        "utf8"
    );
    const initialState = fs.readFileSync(
        path.join(__dirname, "./contracts/token-initState.json"),
        "utf8"
    );
    

部署合约，交易 id 就是合约 id

    const { contractTxId } = await warp.deploy({
        wallet: wallet1,
        initState: initialState,
        src: contractSrc,
    });
    console.log(`contractTxId: `, contractTxId);
    

如果是主网，要这样写

    // for mainnet
    import { ArweaveSigner } from "warp-contracts-plugin-deploy";
    const { contractTxId } = await warp.deploy({
        wallet: new ArweaveSigner(wallet1),
        initState: initialState,
        src: contractSrc,
    });
    console.log(`contractTxId: `, contractTxId);
    

### 6\. 合约交互脚本

用 contractTxId 作参数，把 contract 实例创建出来。本地调试可以把 mineArLocalBlocks 和 waitForConfirmation 开启，以便于直接观察状态变化。

    let contract = warp
        .contract(contractTxId)
        .connect(wallet1)
        .setEvaluationOptions({
            mineArLocalBlocks: true,
            waitForConfirmation: true,
        });
    

发起一笔交易，写入合约状态

    let res = await contract.writeInteraction(
        {
            function: "transfer",
            target: walletAddress2,
            qty: 10,
        },
        { strict: true }
    );
    console.log(`res: %s`, res);
    

查询最新区块高度

    let block = await warp.arweave.blocks.getCurrent();
    console.log(`current block: %s`, block.height);
    

如果是主网，这样查询

    // get current block of mainnet
    const arweaveWrapper = new WarpSdk.ArweaveWrapper(warp);
    let block = await arweaveWrapper.warpGwBlock();
    get current block from arlocal
    

查看合约在当前区块下的状态

    const { sortKey, cachedValue } = await contract.readState(block.height);
    console.log(`sortKey value: %s`, sortKey);
    console.log(`cached value: %s`, JSON.stringify(cachedValue));
    

把上面的脚本整合起来运行一下

    $ node writeContract.js 
    address 1 -aVC26iRLJmi7uq4iKtRllLA2DJ5EoABSZV5EwNkwdU
    address 2 xmq-uGvTmH-sl8ELVym0OizK8Ps5B3QQFjMNY8uEG5w
    contractTxId:  O5UElo74Nt70AdOhhgx0zvRtiUP5D2r3nKcX0Ngaf_w
    2023-12-28T18:22:34.382Z INFO [HandlerBasedContract] Write interaction [
      {
        input: {
          function: 'transfer',
          target: 'xmq-uGvTmH-sl8ELVym0OizK8Ps5B3QQFjMNY8uEG5w',
          qty: 10
        },
        options: { strict: true }
      }
    ]
    2023-12-28T18:22:34.430Z INFO [ContractDefinitionLoader] Contract definition loaded in: 40ms []
    2023-12-28T18:22:34.450Z INFO [HandlerBasedContract] Call contract input [
      {
        function: 'transfer',
        target: 'xmq-uGvTmH-sl8ELVym0OizK8Ps5B3QQFjMNY8uEG5w',
        qty: 10
      }
    ]
    2023-12-28T18:22:34.463Z INFO [ContractDefinitionLoader] Contract definition loaded in: 9ms []
    2023-12-28T18:22:34.471Z INFO [HandlerBasedContract] effectiveCaller [ '-aVC26iRLJmi7uq4iKtRllLA2DJ5EoABSZV5EwNkwdU' ]
    2023-12-28T18:22:34.471Z INFO [CacheableStateEvaluator] No missing interactions O5UElo74Nt70AdOhhgx0zvRtiUP5D2r3nKcX0Ngaf_w []
    2023-12-28T18:22:34.472Z INFO [HandlerBasedContract] Current state [
      {
        owner: '-aVC26iRLJmi7uq4iKtRllLA2DJ5EoABSZV5EwNkwdU',
        canEvolve: true,
        balances: { '-aVC26iRLJmi7uq4iKtRllLA2DJ5EoABSZV5EwNkwdU': 1000 }
      }
    ]
    2023-12-28T18:22:35.739Z INFO [HandlerBasedContract] Waiting for confirmation of [ '91WJeeSJPeAGtoy-Ebxrs284_fSFdeRbAoji7nICMAg' ]
    2023-12-28T18:22:35.754Z INFO [HandlerBasedContract] Transaction 91WJeeSJPeAGtoy-Ebxrs284_fSFdeRbAoji7nICMAg confirmed [ { status: 200, confirmed: 'Pending' } ]
    2023-12-28T18:22:35.755Z INFO [HandlerBasedContract] Transaction confirmed after [ '16ms' ]
    res: {
      originalTxId: '91WJeeSJPeAGtoy-Ebxrs284_fSFdeRbAoji7nICMAg',
      interactionTx: [Transaction]
    }
    writeTx block: {
      height: 7,
      block: 'bdgcd6ztl16ojp5zb13j98453e82wqn3vxrhrwna7hku0d8i9ce6sdku2i7xhp63'
    }
    current block: 7
    2023-12-28T18:22:36.167Z INFO [HandlerBasedContract] Read state for [
      {
        contractTxId: 'O5UElo74Nt70AdOhhgx0zvRtiUP5D2r3nKcX0Ngaf_w',
        sortKeyOrBlockHeight: 7
      }
    ]
    2023-12-28T18:22:36.182Z INFO [ContractDefinitionLoader] Contract definition loaded in: 12ms []
    2023-12-28T18:22:36.195Z INFO [HandlerBasedContract] Execution Context [
      {
        srcTxId: 'psZuy21rSCNyrBW1y4HMPaTjTyFms8DcHU-Mlk4YZrg',
        missingInteractions: 1,
        cachedSortKey: '000000000000,0000000000000,0000000000000000000000000000000000000000000000000000000000000000'
      }
    ]
    2023-12-28T18:22:36.197Z INFO [HandlerBasedContract] Benchmark [
      {
        'Gateway communication  ': '27ms',
        'Contract evaluation    ': '2ms',
        'Total:                 ': '29ms'
      }
    ]
    sortKey value: 000000000007,0000000000000,f4fbc9e57672bcb120c1486e597e5c465ad7fd43db656f9623be0e1261cc2011
    cached value: {"state":{"owner":"-aVC26iRLJmi7uq4iKtRllLA2DJ5EoABSZV5EwNkwdU","canEvolve":true,"balances":{"-aVC26iRLJmi7uq4iKtRllLA2DJ5EoABSZV5EwNkwdU":990,"xmq-uGvTmH-sl8ELVym0OizK8Ps5B3QQFjMNY8uEG5w":10}},"validity":{"91WJeeSJPeAGtoy-Ebxrs284_fSFdeRbAoji7nICMAg":true},"errorMessages":{}}
    

[https://academy.warp.cc/docs/docs-intro](https://academy.warp.cc/docs/docs-intro)

---

*Originally published on [Wang Defa](https://paragraph.com/@westernjournalist/warp)*
