关于 basket-bet
basket-bet 是一项技能,指导使用 vara-wallet 在 Vara Network 上领取 CHIP 并投注 PolyBaskets 篮子。内容涵盖 CHIP 与原生 VARA 路径,包括报价处理和仓位查询。
主要功能
- 01按小时领取带连签奖励的 CHIP
- 02浏览活跃篮子并检查资产类型
- 03批准 CHIP 并使用签名报价投注
- 04在符合条件的篮子上投注 VARA
- 05查询仓位并处理常见错误
适用场景
- 01投注篮子前领取 CHIP
- 02使用有效签名报价进行 CHIP 投注
- 03篮子支持时使用 VARA 通道
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ adityaakr/ polybaskets --skill basket-bet安装后在 Claude Code 中输入/basket-bet确认已加载。
详细介绍
Basket Bet
通过 vara-wallet 领取 CHIP 代币,并在 PolyBaskets 篮子上下注。
设置
仅限主网。 务必先运行 vara-wallet config set network mainnet。切勿切换到测试网——那里没有合约。如果调用失败,请排查错误,不要回退到测试网。
需要 vara-wallet 0.10+ 才能自动将十六进制转换为字节。使用以下命令更新:npm install -g vara-wallet@latest
# Ensure mainnet (default RPC)
vara-wallet config set network mainnet
BASKET_MARKET="0xea8373e8b4441ef6e95325c1044d23ebf615b43fdef60a48623836a15ca7a25a"
BET_TOKEN="0x186f6cda18fea13d9fc5969eec5a379220d6726f64c1d5f4b346e89271f917bc"
BET_LANE="0x35848dea0ab64f283497deaff93b12fe4d17649624b2cd5149f253ef372b29dc"
_PB="${POLYBASKETS_SKILLS_DIR:-skills}"
IDL="$_PB/idl/polymarket-mirror.idl"
BET_TOKEN_IDL="$_PB/idl/bet_token_client.idl"
BET_LANE_IDL="$_PB/idl/bet_lane_client.idl"
BET_QUOTE_URL="https://bet-quote-service-production.up.railway.app"
MY_ADDR=$(vara-wallet balance --account agent | jq -r .address)
VOUCHER_URL="https://voucher-backend-production-5a1b.up.railway.app/voucher"
检查 / 刷新 Gas Voucher(按小时分段模型)
第 2 季 Voucher 模型:每个 Agent 每小时分段可获得 500 VARA。一次批量 POST 会注册所需的 CHIP 程序,并为 Voucher 充值 500 VARA。不要仅仅因为小时窗口已开启就充值:先 GET Voucher 状态;只要已知链上余额至少为 10 VARA,就复用当前 Voucher;仅当 Voucher 不存在、程序覆盖不完整,或已知余额低于 10 VARA 且 canTopUpNow=true 时,才再次 POST。
速率限制:
- 每个钱包:每小时最多 1 次已充值的 POST。在 1 小时窗口内进行第 2 次 POST 会返回带有
Retry-After和retryAfterSec的429——复用现有的voucherId,不要中止。 - 每个 IP:每天 UTC 时间 40 个分段(滥用防护限制)。达到上限会返回相同格式的
429,其中Retry-After设置为距下一个 UTC 午夜的秒数。
GET 免费且仅用于读取——POST 前务必先检查状态。
if [ -z "$MY_ADDR" ] || [ "$MY_ADDR" = "null" ]; then
echo "Failed to resolve wallet address; aborting before voucher request."
exit 1
fi
VOUCHER_STATE_URL="$VOUCHER_URL/$MY_ADDR"
# GET current voucher state — free, never rate-limited
VOUCHER_STATE=$(curl -s "$VOUCHER_STATE_URL")
VOUCHER_ID=$(echo "$VOUCHER_STATE" | jq -r .voucherId)
CAN_TOP_UP=$(echo "$VOUCHER_STATE" | jq -r .canTopUpNow)
HAS_ALL_PROGRAMS=$(echo "$VOUCHER_STATE" | jq -r \
--arg bm "$BASKET_MARKET" --arg bt "$BET_TOKEN" --arg bl "$BET_LANE" \
'($bm | ascii_downcase) as $bm | ($bt | ascii_downcase) as $bt | ($bl | ascii_downcase) as $bl | ((.programs // []) | map(ascii_downcase)) as $p | (($p | index($bm)) != null and ($p | index($bt)) != null and ($p | index($bl)) != null)')
VARA_BALANCE=$(echo "$VOUCHER_STATE" | jq -r .varaBalance)
BALANCE_KNOWN=$(echo "$VOUCHER_STATE" | jq -r .balanceKnown)
NEXT_ELIGIBLE=$(echo "$VOUCHER_STATE" | jq -r .nextTopUpEligibleAt)
LOW_VOUCHER_BALANCE="10000000000000" # 10 VARA in planck
NEED_TOP_UP=false
if [ "$BALANCE_KNOWN" = "true" ] && [ "$VARA_BALANCE" -lt "$LOW_VOUCHER_BALANCE" ]; then
NEED_TOP_UP=true
fi
# POST a single batched request. Trigger when:
# (a) no voucher yet (null), OR
# (b) known balance is below 10 VARA and canTopUpNow=true, OR
# (c) voucher is missing one of the required CHIP programs
if [ "$VOUCHER_ID" = "null" ] || [ "$HAS_ALL_PROGRAMS" != "true" ] || { [ "$NEED_TOP_UP" = "true" ] && [ "$CAN_TOP_UP" = "true" ]; }; then
# ⚠ "programs" is a JSON ARRAY of contract IDs (NOT your agent address, NOT a single string).
RESP=$(curl -s -w "\n%{http_code}" -X POST "$VOUCHER_URL" \
-H 'Content-Type: application/json' \
-d '{"account":"'"$MY_ADDR"'","programs":["'"$BASKET_MARKET"'","'"$BET_TOKEN"'","'"$BET_LANE"'"]}')
HTTP_CODE=$(echo "$RESP" | tail -n1)
BODY=$(echo "$RESP" | sed '$d')
case "$HTTP_CODE" in
200)
VOUCHER_ID=$(echo "$BODY" | jq -r .voucherId)
;;
429)
RETRY_SEC=$(echo "$BODY" | jq -r .retryAfterSec)
echo "Voucher rate-limited (next top-up in $RETRY_SEC s). Reusing existing voucherId — continue."
# VOUCHER_ID from the initial GET remains valid; do not abort.
;;
*)
echo "Voucher POST failed: HTTP $HTTP_CODE — $BODY"
exit 1
;;
esac
fi
echo "Voucher: $VOUCHER_ID (canTopUpNow=$CAN_TOP_UP, balance=$VARA_BALANCE, known=$BALANCE_KNOWN, nextEligible=$NEXT_ELIGIBLE)"
Voucher 耗尽时的停止规则:只有在 BALANCE_KNOWN=true 时,才可相信 $VARA_BALANCE。如果 BALANCE_KNOWN=false,说明 Voucher 后端无法连接 Vara 节点——继续使用当前 Voucher,不要仅根据 CAN_TOP_UP 充值。当 BALANCE_KNOWN=true 且 $VARA_BALANCE < 10000000000000(10 VARA,单位为 planck)时:
- 如果
CAN_TOP_UP=true→ POST 以充值 500 VARA,然后继续。 - 如果
CAN_TOP_UP=false→ 停止,并等待到$NEXT_ELIGIBLE(下一个可充值时段)。1 小时窗口是最短间隔;更频繁地尝试只会返回 429。
迁移说明:如果后端返回的错误指出 program 字段(单数)有问题,说明你使用的是旧版 skills 副本。API 现在接受 programs: string[](数组)。重新拉取 skill 包:npx skills add Adityaakr/polybaskets -g --all。
CHIP 通道(主要路径)
大多数篮子使用 asset_kind: "Bet"(CHIP 代币)。这是 Agent 的默认工作流。
步骤 1:领取每小时 CHIP
第 2 季经济机制:Agent 每小时可免费领取 CHIP 代币一次。每次领取的奖励 = 500 + 10 × (streak_days − 1) CHIP,最高 600。在新的 UTC 日历日领取时,连续天数计数器会递增——在同一个 UTC 日内多次按小时领取不会增加连续天数。错过一个完整的 UTC 日 → 连续天数重置为 1。
因此,第 1 天每次领取 = 500,第 2 天每次 = 510,……第 11 天及以后每次 = 600。
# Get your hex address (required for actor_id args — SS58 won't work)
MY_ADDR=$(vara-wallet balance | jq -r .address)
if [ -z "$MY_ADDR" ] || [ "$MY_ADDR" = "null" ]; then
echo "Failed to resolve wallet address; aborting before voucher request."
exit 1
fi
VOUCHER_STATE_URL="$VOUCHER_URL/$MY_ADDR"
# Get your voucher ID (check with GET first — see Quick Start in SKILL.md)
VOUCHER_ID=$(curl -s "$VOUCHER_STATE_URL" | jq -r .voucherId)
# Check if claim is available and how much you'll get
vara-wallet call $BET_TOKEN BetToken/GetClaimPreview \
--args '["'$MY_ADDR'"]' --idl $BET_TOKEN_IDL
# Claim hourly CHIP (do this once per hour; streak advances per UTC day)
# NOTE: --voucher is required on ALL write calls (agent has no VARA for gas)
vara-wallet --account agent call $BET_TOKEN BetToken/Claim \
--args '[]' --voucher $VOUCHER_ID --idl $BET_TOKEN_IDL
响应中包含你的 streak_days 和 total_claimed。连续天数越多,每次领取的 CHIP 就越多,最高为第 11 天的上限。
步骤 2:检查 CHIP 余额
vara-wallet call $BET_TOKEN BetToken/BalanceOf \
--args '["'$MY_ADDR'"]' --idl $BET_TOKEN_IDL
步骤 3:选择一个篮子
浏览有效的篮子并选择一个下注:
# How many baskets exist
vara-wallet call $BASKET_MARKET BasketMarket/GetBasketCount --args '[]' --idl $IDL
# View a specific basket
vara-wallet call $BASKET_MARKET BasketMarket/GetBasket --args '[0]' --idl $IDL
# ⚠ Response is nested under .result.ok — NOT .ok!
# Example: {"result":{"ok":{"id":0,"name":"...","status":"Active","asset_kind":"Bet",...}}}
# Use jq: | jq '.result.ok'
# To get just name and status: | jq '.result.ok | {name, status}'
确认 status 为 "Active",且 asset_kind 为 "Bet"。篮子数据位于 JSON 响应的 .result.ok 中。
重要:PlaceBet 的 basket_id 是普通整数(例如 0、1、2),不是十六进制程序 ID。
步骤 4:批准 CHIP 支出
允许 BetLane 合约使用你的 CHIP:
vara-wallet --account agent call $BET_TOKEN BetToken/Approve \
--args '["'$BET_LANE'", <amount>]' --voucher $VOUCHER_ID --idl $BET_TOKEN_IDL
**注意:**Approve 会返回 "result":false——这是正常情况,它表示之前的批准状态(不是错误)。如有需要,可通过 BetToken/Allowance 验证。
第 5 步:获取签名报价 + 下注
下注需要来自 bet-quote-service 的签名报价。报价服务会获取 Polymarket 实时价格、计算指数并对载荷签名。合约会在链上验证签名。
推荐命令(获取报价 + 估算 gas + 下注——请一并执行,以免超过报价的 30 秒有效期):
# Replace <BASKET_ID> and <AMOUNT_RAW> with real values
QUOTE=$(curl -s -X POST "$BET_QUOTE_URL/api/bet-lane/quote" \
-H 'Content-Type: application/json' \
-d '{"user":"'"$MY_ADDR"'","basketId":<BASKET_ID>,"amount":"<AMOUNT_RAW>","targetProgramId":"'"$BET_LANE"'"}') && \
echo "$QUOTE" | jq -e '.payload' >/dev/null 2>&1 || { echo "Quote failed: $QUOTE"; exit 1; } && \
EST=$(vara-wallet --account agent call $BET_LANE BetLane/PlaceBet \
--args "[<BASKET_ID>, \"<AMOUNT_RAW>\", $QUOTE]" \
--voucher $VOUCHER_ID --idl $BET_LANE_IDL --estimate) && \
GAS_LIMIT=$(node -e 'const x=JSON.parse(process.argv[1]); const used=BigInt(x.min_limit??x.minLimit??x.gas_for_reply??x.gasForReply??0); const withBuffer=used + used/5n + 5000000000n; console.log(withBuffer.toString())' "$EST") && \
vara-wallet --account agent call $BET_LANE BetLane/PlaceBet \
--args "[<BASKET_ID>, \"<AMOUNT_RAW>\", $QUOTE]" \
--voucher $VOUCHER_ID --gas-limit $GAS_LIMIT --idl $BET_LANE_IDL
工作原理: vara-wallet 0.10+ 会自动将十六进制签名("0x...")转换为 vec u8 字段所需的字节数组。无需手动转换——只需将 curl 返回的原始报价 JSON 直接传入 --args。
下注的关键规则:
- 不要手动重建报价对象。 报价采用
{"payload": {...}, "signature": "0x..."}结构——如果重建时省略了payload包装层,合约会以InvalidIndexAtCreation错误拒绝。 - 需要 vara-wallet 0.10+。 较旧版本需要手动进行十六进制→字节转换。使用
vara-wallet --version检查版本。 - 发送
PlaceBet时务必显式指定--gas-limit。 推荐的做法是在发送前,使用完全相同的PlaceBet参数估算 gas,然后将--gas-limit设置为估算值再加上缓冲。建议基准值:estimate * 1.2 + 5_000_000_000。 - 不要盲目地从同一账户批量发送
PlaceBet交易。 每次只下注一笔,等待结果后再进行下一笔。连续写入可能会触发OperationInProgress。 - **如果看到
Message ran out of gas while executing或Failed to reserve gas for system signal: Ext(Execution(NotEnoughGas)),**请先查询该篮子的BetLane/GetPosition,确认状态是否已改变。只有在状态未改变时,才应获取新报价、增加 gas 缓冲并重试一次。 - **如果同一
(user, basket_id)的OperationInProgress持续出现,**请停止反复尝试该篮子。先重新检查仓位和报价是否仍然有效;如果这对参数仍然卡住,请报告问题,不要循环重试。
报价有效期为 30 秒。过期后,请重新请求报价。每个报价都有唯一的 nonce,且只能使用一次。
返回 u256 ——收到的份额数。
完整的 CHIP 通道示例
# 0. Vars are set in the Setup block above. If starting fresh:
# MY_ADDR=$(vara-wallet balance --account agent | jq -r .address)
# VOUCHER_ID=$(curl -s "$VOUCHER_URL/$MY_ADDR" | jq -r .voucherId)
# 1. Claim hourly CHIP
vara-wallet --account agent call $BET_TOKEN BetToken/Claim \
--args '[]' --voucher $VOUCHER_ID --idl $BET_TOKEN_IDL
# 2. Approve BetLane to spend 100 CHIP
vara-wallet --account agent call $BET_TOKEN BetToken/Approve \
--args '["'$BET_LANE'", "100000000000000"]' --voucher $VOUCHER_ID --idl $BET_TOKEN_IDL
# 3. Get quote + estimate gas + place bet (30s expiry — run together!)
# ⚠ Do NOT manually reconstruct the quote. Pass the raw curl response directly.
QUOTE=$(curl -s -X POST "$BET_QUOTE_URL/api/bet-lane/quote" \
-H 'Content-Type: application/json' \
-d '{"user":"'"$MY_ADDR"'","basketId":0,"amount":"100000000000000","targetProgramId":"'"$BET_LANE"'"}') && \
EST=$(vara-wallet --account agent call $BET_LANE BetLane/PlaceBet \
--args "[0, \"100000000000000\", $QUOTE]" \
--voucher $VOUCHER_ID --idl $BET_LANE_IDL --estimate) && \
GAS_LIMIT=$(node -e 'const x=JSON.parse(process.argv[1]); const used=BigInt(x.min_limit??x.minLimit??x.gas_for_reply??x.gasForReply??0); const withBuffer=used + used/5n + 5000000000n; console.log(withBuffer.toString())' "$EST") && \
vara-wallet --account agent call $BET_LANE BetLane/PlaceBet \
--args "[0, \"100000000000000\", $QUOTE]" \
--voucher $VOUCHER_ID --gas-limit $GAS_LIMIT --idl $BET_LANE_IDL
# 5. Verify position
vara-wallet call $BET_LANE BetLane/GetPosition \
--args '["'$MY_ADDR'", 0]' --idl $BET_LANE_IDL
重要: CHIP 有 12 位小数。100 CHIP = 100000000000000(100 * 10^12)个原始单位。
报价的工作原理
Agent 不再手动计算 index_at_creation_bps。bet-quote-service 会:
- 从链上读取篮子(验证其处于启用状态且类型为 Bet)
- 获取每个结果对应的 Polymarket 实时价格
- 计算加权后的
quoted_index_bps - 使用 SR25519 对载荷签名(包含 user、basket_id、amount、deadline、nonce)
- 返回签名报价
BetLane 合约会在链上验证签名,以防止价格操纵。
报价属性:
- 有效期为 30 秒(
deadline_ms) - 只能使用一次(nonce 可防止重放)
- 绑定到特定的用户、篮子和金额
赔付公式请参阅 ../references/index-math.md:payout = shares * (settlement_index / entry_index)。
VARA 通道(asset_kind: Vara)
部分篮子接受原生 VARA,而不是 CHIP。请检查篮子的 asset_kind。
# Bet 100 VARA on basket 0 at index 6120
vara-wallet --account agent call $BASKET_MARKET BasketMarket/BetOnBasket \
--args '[0, 6120]' \
--value 100 \
--idl $IDL
返回 u128 ——收到的份额数(等于以最小单位表示的 VARA 发送量)。
注意:某些部署可能会禁用 VARA 通道。请使用以下命令检查:
vara-wallet call $BASKET_MARKET BasketMarket/IsVaraEnabled --args '[]' --idl $IDL
下注后
检查你的仓位(使用 BetLane/GetPosition,不要使用不存在的 GetUserPositions):
vara-wallet call $BET_LANE BetLane/GetPosition \
--args '["'$MY_ADDR'", <BASKET_ID>]' --idl $BET_LANE_IDL
- 等待结算,然后领取赔付:
../basket-claim/SKILL.md - 明天再来获取更多 CHIP:重复第 1 步
常见错误
| 错误 | 原因 | 修复方法 |
|---|---|---|
InvalidIndexAtCreation | 报价结构格式错误(缺少 payload 包装) | 请勿手动重建报价——将原始 curl 响应通过管道传给 python3 |
InvalidQuoteSignature | 报价未由已配置的签名者签名 | 检查 bet-quote-service 配置 |
QuoteExpired | 报价已超过 30 秒 | 请求新报价 |
QuoteNonceAlreadyUsed | 同一报价提交了两次 | 每次投注都请求新报价 |
QuoteTargetMismatch | 报价对应的是另一个 BetLane | 检查 targetProgramId 是否与 $BET_LANE 匹配 |
InvalidBetAmount | 未附加 --value(VARA 通道) | 添加 --value <amount> |
BasketNotActive | 篮子正在结算或已结算 | 无法投注非活跃篮子 |
BasketAssetMismatch | 篮子使用了错误的通道 | 检查篮子的 asset_kind |
VaraDisabled | VARA 投注已关闭 | 改用 CHIP 通道 |
AmountBelowMinBet | CHIP 金额过低 | 检查 BetLane 配置中的 min_bet |
AmountAboveMaxBet | CHIP 金额过高 | 检查 BetLane 配置中的 max_bet |
BetTokenTransferFromFailed | CHIP 余额或授权额度不足 | 领取更多代币或增加授权额度 |