npm i -g arlocal
npx arlocal start
Arlocal 模拟了一个本地 arweave gateway server,默认运行在1984端口。
Arocal 重启后状态就没了。
可以用 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": "*******************************"
}
写一个简单的 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 };
}
创建一个 token-initState.json 文件,定义初始状态,主要是设置 balances.
{
"owner": "-aVC26iRLJmi7uq4iKtRllLA2DJ5EoABSZV5EwNkwdU",
"canEvolve": true,
"balances": {
"-aVC26iRLJmi7uq4iKtRllLA2DJ5EoABSZV5EwNkwdU": 1000
}
}
创建一个 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);
用 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":{}}
