一个跑在 Uptick Chain CosWasm VM 上的极简商城合约(Rust + CosmWasm 1.5),内置资金托管(escrow)与退款闭环。
- 上架商品
ListProduct { name, price, stock }—— 调用者自动成为卖家,合约分配自增id - 购买
BuyProduct { product_id, quantity }—— 必须随交易附上精确的price × quantity原生币;合约先把钱托管在合约里(不下发给卖家),并生成一笔Order{status: created},同时扣减库存 - 确认收货
ConfirmReceipt { order_id }—— 仅买家可调用,把托管款打给卖家,订单置completed - 申请退款
RequestRefund { order_id }—— 仅买家可调用,把托管款退回买家,并恢复库存,订单置refunded - 查询
Config/Product{id}/Products{分页}/ProductsBySeller{seller}/Order{id}/OrdersByBuyer{buyer}
买家 BuyProduct ──付款──▶ 合约托管 (Order.created, 库存-)
│
├─▶ 买家 ConfirmReceipt ──▶ 放款给卖家 (Order.completed)
└─▶ 买家 RequestRefund ──▶ 退款给买家 + 库存恢复 (Order.refunded)
解决了“先钱后货”的信任问题:卖家不发货,买家退款即可拿回本金;买家确认收货,卖家才能拿到钱。
说明:为求最小,仍没有管理员改价/下架、没有纠纷仲裁、没有平台抽成。这些见文末「可扩展点」。
wasm-store/
├── README.md # 本文件:总览 + 构建 / 部署 / 前端说明
├── Makefile # make test / build / deploy 标准工作流
├── Cargo.toml # CosmWasm 合约(crate: wasm-mall,惯例放在仓库根)
├── .gitignore
├── src/ # 合约源码
│ ├── lib.rs # 模块声明
│ ├── state.rs # Config / Product / Order 数据结构 + 存储键
│ ├── msg.rs # Instantiate / Execute / Query 消息与响应
│ ├── error.rs # ContractError
│ ├── contract.rs # instantiate / execute / query 实现
│ └── tests.rs # cw-multi-test 单元测试(含托管 / 退款 / 越权用例)
├── scripts/ # 部署与交互脚本
│ ├── deploy.sh # uptickd CLI 一键 store + instantiate
│ ├── mall-client.mjs # CosmJS node 交互脚本
│ └── package.json # CosmJS 依赖(npm install 在此目录)
├── frontend/ # 店铺式 Keplr dApp(纯静态单文件)
│ └── index.html
├── docs/ # 文档
│ ├── WORK_LOG.md # 工作日志(chronological)
│ └── HANDOVER.md # 交接文档(给下一开发者)
└── artifacts/ # (gitignored) make build 产物 wasm_mall.wasm
合约按 CosmWasm 惯例放在仓库根(
Cargo.toml在根),而不是contracts/子目录——这样make build/deploy.sh的相对路径最简单。前端与脚本各自成目录。更完整的架构说明、构建 / 部署 / 前端运行步骤、网络参数、关键坑与验收清单,见
docs/HANDOVER.md;开发历程见docs/WORK_LOG.md。
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknowncargo testdocker run --rm \
-v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \
--name cosmwasm-optimizer cosmwasm/optimizer:0.16.0产物:artifacts/wasm_mall.wasm
CosmWasm 2.x 链可改用
cosmwasm/optimizer:0.17并把Cargo.toml里cosmwasm-std升到2.0。Uptick 当前 1.x 运行时用 0.16.0 即可。 注意:缓存卷不要加readonly—— optimizer 需要往/target写编译缓存,readonly 会让构建直接失败。
也可以用 Makefile 一键跑:make test / make build / make deploy(deploy 依赖 KEY / CHAIN_ID / DENOM / NODE 环境变量)。
若节点拒绝
--gas-prices 0auoc,把 gas price 调成最小值即可(例如--gas-prices 0.025auoc)。Uptick 官方示例多用 0,照抄通常能通过。
| 网络 | chain-id | 原生 base denom | RPC |
|---|---|---|---|
| 主网 | uptick_117-1 |
auptick |
(你的节点 / 公共 RPC) |
| Origin 测试网 | origin_1170-3 |
auoc(显示 UOC) |
https://rpc.origin.uptick.network |
下面以 Origin 测试网 为例,主网把 chain-id / denom / RPC 替换即可。
- 装 uptickd(版本必须与目标网络一致):测试网
origin_1170-3当前为 v0.2.x 系,主网uptick_117-1为 v0.3.3 / 升级目标 v0.5.0。从源码装最稳:详见官方文档(upticknft.gitbook.io / docs.uptick.network 的 Installation / Joining a Testnet)。git clone https://github.com/UptickNetwork/uptick.git cd uptick && git checkout <对应tag> && make install uptickd version
- 建/导入钱包:
uptickd keys add mykey --keyring-backend test(或--recover导入已有助记词),记下地址。 - 领测试币 auoc:进 Uptick Discord
#faucet频道 发$faucet <你的地址>,或用 Keplr 水龙头 点 Request Funds。主网需真实auptick。 - 确认余额:
uptickd query bank balances <地址> --node https://rpc.origin.uptick.network。
不需要在你 EC2 跑全节点——“上链”只是把交易通过公开 RPC 广播到 Uptick 网络;
deploy.sh默认就连https://rpc.origin.uptick.network。
KEY=mykey ./deploy.sh
# 脚本会 store + instantiate,并输出:
# export CODE_ID=...
# export CONTRACT=...
# 复制这两行到 shell 即可供 mall-client.mjs / 后续命令使用。所有变量都可前置覆盖:CHAIN_ID=uptick_117-1 DENOM=auptick NODE=https://... KEY=mykey KEYRING=test ./deploy.sh
export KEY=<你的key名>
export NODE=https://rpc.origin.uptick.network
export CHAIN=origin_1170-3
# 1. 存储代码
uptickd tx wasm store artifacts/wasm_mall.wasm \
--from $KEY --chain-id $CHAIN --node $NODE --keyring-backend test \
--gas auto --gas-prices 0auoc -y -b block
uptickd query wasm list-code --node $NODE --output json # 记下 code_id
# 2. 实例化(指定接受的支付 denom)
uptickd tx wasm instantiate <CODE_ID> '{"denom":"auoc"}' \
--from $KEY --label "wasm-mall" --chain-id $CHAIN --node $NODE --keyring-backend test \
--gas auto --gas-prices 0auoc -y -b block --no-admin# 上架(卖家)
uptickd tx wasm execute <CONTRACT_ADDR> \
'{"list_product":{"name":"Uptick T-Shirt","price":"1000000000000000000","stock":10}}' \
--from $SELLER --chain-id $CHAIN --node $NODE \
--gas auto --gas-prices 0auoc -y -b block
# price 是 base 单位:auptick/auoc 都是 18 位小数,1 = 1000000000000000000
# 购买(买家,必须带精确金额;返回事件里含 order_id)
uptickd tx wasm execute <CONTRACT_ADDR> \
'{"buy_product":{"product_id":1,"quantity":1}}' \
--from $BUYER --chain-id $CHAIN --node $NODE \
--amount 1000000000000000000auoc \
--gas auto --gas-prices 0auoc -y -b block
# 确认收货 → 放款给卖家(买家)
uptickd tx wasm execute <CONTRACT_ADDR> \
'{"confirm_receipt":{"order_id":1}}' \
--from $BUYER --chain-id $CHAIN --node $NODE \
--gas auto --gas-prices 0auoc -y -b block
# 申请退款 → 退款给买家并恢复库存(买家)
uptickd tx wasm execute <CONTRACT_ADDR> \
'{"request_refund":{"order_id":1}}' \
--from $BUYER --chain-id $CHAIN --node $NODE \
--gas auto --gas-prices 0auoc -y -b blockuptickd query wasm contract-state smart <CONTRACT_ADDR> '{"product":{"id":1}}' --node $NODE
uptickd query wasm contract-state smart <CONTRACT_ADDR> '{"products":{"limit":10}}' --node $NODE
uptickd query wasm contract-state smart <CONTRACT_ADDR> '{"products_by_seller":{"seller":"uptick1xxxx","limit":10}}' --node $NODE
uptickd query wasm contract-state smart <CONTRACT_ADDR> '{"order":{"id":1}}' --node $NODE
uptickd query wasm contract-state smart <CONTRACT_ADDR> '{"orders_by_buyer":{"buyer":"uptick1yyyy","limit":10}}' --node $NODEscripts/mall-client.mjs 用 @cosmjs/cosmwasm-stargate 封装了全部读写操作,并带 CLI。
cd scripts # 依赖声明在 scripts/package.json
npm install # 安装 CosmJS 依赖
export MNEMONIC="你的助记词"
export CONTRACT=<实例化得到的合约地址> # 或 CODE_ID 用于 instantiate
node mall-client.mjs instantiate <CODE_ID> # 部署实例,输出合约地址
node mall-client.mjs list "T-Shirt" 1000000000000000000 10
node mall-client.mjs buy 1 1 1000000000000000000 # 自动带 --amount
node mall-client.mjs confirm 1
node mall-client.mjs refund 1
node mall-client.mjs product 1
node mall-client.mjs orders uptick1yyyy环境变量:
RPC/CHAIN_ID/DENOM/PREFIX/MNEMONIC/CONTRACT/CODE_ID均可覆盖。 前端 / Keplr 集成:把makeClient()里的DirectSecp256k1HdWallet.fromMnemonic(...)换成const signer = await window.keplr.getOfflineSignerOnlyAmino(CHAIN_ID);,其余 execute/query 调用完全一致。
frontend/index.html 是一个纯静态、无后端的店铺式 Web 前端,让你在浏览器里用 Keplr 钱包直接签名、调用链上合约——和 EVM + MetaMask 的前端是同一个套路。合约部署在链上(见上方「部署到 Uptick Chain」),前端只负责签名与广播,可托管在任意静态空间(test01 的 nginx / GitHub Pages / 本地直接打开)。
界面构成(已重做为真正店铺,非调试面板)
- 顶栏:品牌 Logo + 网络切换(测试网/主网)+ 连接钱包(连接后显示地址与余额)。
- Hero 横幅:链上商城卖点。
- 商品网格:卡片含缩略图、名称、价格、库存徽章(有货/仅剩 N/售罄)、卖家、购买按钮。
- 购买弹窗:数量步进器 + 自动计算应付总额(托管)。
- 上架弹窗(+ 上架商品):卖家发布商品,价格按显示单位填写(如
12.5 UOC),前端自动转成最小单位整数发给合约。 - 我的订单抽屉:买家订单列表,状态标签 + 确认收货(放款)/ 申请退款(退钱+补库存)按钮。
- 开发者设置(底部折叠):合约地址填写、部署 wasm、查配置、调试日志——把原来的调试面板收起,不干扰购物体验。
工作流程
- 浏览器打开
frontend/index.html(本地双击或静态托管均可)。 - 点「连接 Keplr 钱包」——首次会先把 Uptick 网络加入 Keplr(自动
experimentalSuggestChain)。 - 部署(可选):在「开发者设置」选本地
artifacts/wasm_mall.wasm→ 点「上传 + 实例化」,Keplr 弹窗签名两笔交易(store + instantiate),合约地址自动填入;也可直接用上方deploy.sh部署后再把地址粘贴进来。 - 购物:点商品「购买」→ 选数量 → 确认(托管付款);卖家点「+ 上架商品」发布;买家在「我的订单」里确认/退款。
要点
- 价格输入框已改为显示单位(UOC / uptick),前端用
parseDisplayToMin精确转最小单位整数(避免浮点误差),合约仍按 base 单位存储。 - 主网部署请把「开发者设置」里 Gas Price 改为实值(如
0.002auptick)并切换网络;小数位若与主网不一致,改CHAINS里的decimals。 - 依赖经 esm.sh CDN 加载
@cosmjs/cosmwasm-stargate,无需npm install;需浏览器支持 ES Modules + 已装 Keplr 扩展。 - Keplr 仅在 https / localhost / file:// 安全上下文响应——本地可用
python3 -m http.server起静态服务,或部署到 test01 的 nginx。
- 纠纷仲裁:引入仲裁员/管理员,在
created状态下可强制放款或退款。 - 管理员改价 / 下架:用
Config.admin做权限校验。 - 多卖家目录索引:用
(Addr, u64) -> u64的 Map 给卖家建索引,避免全表扫描。 - 平台手续费抽成:
ConfirmReceipt放款时把一部分打给平台地址。 - 自动确认/退款超时:结合
created_height设超时,到期自动 confirm 或 refund。 - NFT 商品:结合 Uptick 的 CW721,购买即转移 NFT。
Uptick Chain · CosWasm VM · CosmWasm 1.5