# 第一个本地 Warp 合约 **Published by:** [Wang Defa](https://paragraph.com/@westernjournalist/) **Published on:** 2023-12-28 **URL:** https://paragraph.com/@westernjournalist/warp ## Content 1. 安装运行 arlocalnpm 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,创建一个 javascriptconst run = async () => { // } run(); 创建 warp 实例,forLocal 表示 local 环境,如果是主网就改成 forMainnetimport { DeployPlugin } from "warp-contracts-plugin-deploy"; const warp = WarpSdk.WarpFactory.forLocal().use(new DeployPlugin()); 读取 wallet1 和 wallet2import 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 和 initStateconst contractSrc = fs.readFileSync( path.join(__dirname, "./contracts/token.js"), "utf8" ); const initialState = fs.readFileSync( path.join(__dirname, "./contracts/token-initState.json"), "utf8" ); 部署合约,交易 id 就是合约 idconst { 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 ## Publication Information - [Wang Defa](https://paragraph.com/@westernjournalist/): Publication homepage - [All Posts](https://paragraph.com/@westernjournalist/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@westernjournalist): Subscribe to updates