nat-cli 0.0.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/i18n/zh.json ADDED
@@ -0,0 +1,283 @@
1
+ {
2
+ "chain.aDevDeploymentMockagentidNeeds": "开发部署(mockAgentId)必须设置 BNAT_RPC 指向本地链",
3
+ "chain.bidDoesnTExistIn": "买单 #{id} 不存在(共 {n} 个)",
4
+ "chain.cannotReadTheDeploymentFile": "读不到部署文件 {path}:",
5
+ "chain.noDefaultRpcForChainid": "chainId {chainId} 没有默认 RPC,请设置 BNAT_RPC",
6
+ "chain.noSiteAddressSetBnat": "没有站点地址:设置 BNAT_SITE(例如 https://bnat.live)",
7
+ "chain.revertNameSuffix": "({name})",
8
+ "chain.theDeploymentFileLacks": "部署文件缺少 {k}",
9
+ "chain.theSignatureIsNotFor": "签名不是给这个合约的",
10
+ "chain.theSignerDiffersFromThe": "签名者和合约里的不一致,这个签名会被拒绝",
11
+ "chain.unexpectedReplyFromTheSigning": "签名服务返回格式不对",
12
+ "cli.addressMustBeAFull": "--address 要是 0x 开头的完整地址",
13
+ "cli.amountTooSmallWorthLess": "数量太小,按这个单价换不到 1 wei",
14
+ "cli.approveTheMarketToUse": "授权市场合约使用 {amount} BNAT(精确额度,不是无限)",
15
+ "cli.atMostPerSplitIt": "一次最多 {verb} {BATCH_MAX} 张,分几次",
16
+ "cli.belongsToNotThisWallet": "#{blk} 属于 {owner},不是这个钱包",
17
+ "cli.bid": "买单编号 #{id}",
18
+ "cli.bidHasOnlyBnatLeft": "买单 #{id} 只剩 {remaining} BNAT,要卖 {amount}",
19
+ "cli.bidIsAlreadyFilledOr": "买单 #{id} 已经成交完或撤销了",
20
+ "cli.bidWasPlacedByNot": "买单 #{id} 是 {buyer} 挂的,不是这个钱包",
21
+ "cli.bidBnatAtBnbLocking": "挂买单:{amount} BNAT,单价 {price} BNB,锁定 {cost}",
22
+ "cli.bidsShowing": "共 {total} 个买单,显示第 {v0}–{v1} 个",
23
+ "cli.block": "区块 ",
24
+ "cli.blockBurnGwei": "区块 销毁 gwei",
25
+ "cli.blockBurnGweiUnwrapCost": "区块 销毁 gwei unwrap 要付 BNAT",
26
+ "cli.blockListsABlockTwice": "--block 里有重复的区块号",
27
+ "cli.natInTheVault": "金库里有 {total} 张 NAT{range}",
28
+ "cli.bscRecomputesWhichDiffersFrom": "(销毁索引为 {bscBurnGwei},与铸造记录不一致)",
29
+ "cli.burn": "销毁 ",
30
+ "cli.cancelBidRefunding": "撤买单 #{id},退回 {escrow}",
31
+ "cli.chainNatBnatMarket": "链 {chainId} · NAT {nat}\nBNAT {bnat} · 市场 {market}",
32
+ "cli.configSaved": "已保存:{key} = {value}({file})",
33
+ "cli.configShow": "语言 {lang} · 铸造接收地址 {mintTo}({file})",
34
+ "cli.configUsage": "用法:bnat config [mintTo <地址|self>]",
35
+ "cli.contractSendsGasAbout": " 合约 {address} · {functionName} · 附带 {value} · gas 约 {v0}",
36
+ "cli.countUnit": " 张",
37
+ "cli.doneBlock": "完成:{hash}(区块 {blockNumber})",
38
+ "cli.error": "出错",
39
+ "cli.executeErrorPrefix": "({message})",
40
+ "cli.filterMustBeUnmintedMinted": "--filter 只能是 unminted、minted 或 all",
41
+ "cli.final": "终局 ",
42
+ "cli.fromTheSiteIndexChecked": "(来自站点索引,铸造前会再上链确认)",
43
+ "cli.giveTheAmountWithAmount": "用 --amount <BNAT> 指定数量,或 --all 卖到买单或余额用完",
44
+ "cli.giveTheTransactionHashWith": "用 --hash 0x… 指定交易哈希",
45
+ "cli.hasNoHolderNotMinted": "#{blk} 没有持有人(还没铸):",
46
+ "cli.heldBy": "(持有人是 {value})",
47
+ "cli.help": "bnat {VERSION} — 通过币安 Agentic Wallet 铸造 NAT、wrap / unwrap 成 BNAT、交易 BNAT\n\n直接运行 bnat(在终端里)打开交互界面;bnat help 显示本帮助。\n\n只读\n bnat status 部署、铸造进度、市场概况\n bnat wallet 钱包地址、.ai(Agent ID)、BNB、NAT、BNAT\n bnat blocks [--size 20] 最近终局、还没铸的区块,销毁高的在前\n bnat top [--size 20] [--filter unminted|minted|all] [--offset 0]\n 全部历史里销毁最高的区块(默认未铸;每次最多 100,排行到前 10,000 名)\n bnat block --block <N> 一个区块:销毁、是否终局、是否已铸、持有人\n bnat holdings [--address <地址>] [--size 100] [--offset 0]\n 一个地址持有的 NAT(默认这个钱包)\n bnat vault [--sort burn|recent] [--size 20] [--offset 0]\n BNAT 金库里的 NAT 和各自的 unwrap 价格\n bnat bids [--mine] [--size 100] [--offset 0]\n 买单簿(单价从高到低);--mine 只看这个钱包挂着的\n bnat history 本机发过的交易记录\n bnat lang [zh|en] 查看或保存界面语言(第一次在终端运行时会问)\n bnat config [mintTo <地址|self>] 查看设置;设置铸造默认接收地址(self = 本钱包)\n\n写(不加 --yes 只做检查和模拟,不发交易)\n bnat mint --block <N> | --best [--to <地址>] 铸一个区块(每笔一个);接收地址用 --to,否则用设置里的 mintTo,都没有就是本钱包\n bnat wrap --block <N[,N2,…]> 把 NAT wrap 成 BNAT 给这个钱包,一次最多 {BATCH_MAX} 张(1% 手续费留在 BNAT 合约)\n bnat unwrap --block <N[,N2,…]> 付 销毁量 + 1% 的 BNAT,从金库 unwrap 出这些 NAT 给这个钱包(任何人都可以),一次最多 {BATCH_MAX} 张\n bnat bid --price <BNB> --amount <BNAT> 挂买单,锁定 单价×数量 的 BNB\n bnat sell --bid <id> --amount <BNAT> | --all 把 BNAT 卖进一个买单(必要时先精确授权)\n bnat cancel --bid <id> 撤自己的买单,退回剩余 BNB\n bnat resume --hash 0x… 结清一笔中断的交易(从不重发)\n bnat resume --abandon <记录id> 确认没有发出后,作废一条中断的记录\n\n选项\n --yes 真正执行(先得到用户同意)\n --preview-only 走到钱包预览为止,不执行\n --json 机器可读输出\n --lang zh|en 只这一次用这个语言(保存的设置在 ~/.config/bnat/config.json,环境变量 BNAT_LANG 优先于它)\n\n环境变量:BNAT_SITE(站点)、BNAT_RPC(合约链 RPC)",
48
+ "cli.holdsNat": "{who} 持有 {heldTotal} 张 NAT{range}",
49
+ "cli.idPriceBnbBnatLeft": "id 单价 BNB / BNAT 剩余 BNAT 锁定 BNB 买家",
50
+ "cli.inTheBnatVaultCan": "(在 BNAT 金库,可 unwrap)",
51
+ "cli.isAlreadyInTheBnat": "#{blk} 已经在 BNAT 金库里了(要取出用 bnat unwrap)",
52
+ "cli.isAlreadyMintedFirstCome": "#{blk} 已被铸造,换一块",
53
+ "cli.isBeforeTheBep95": "#{blk} 早于 BEP-95 生效块 {ELEMENT_ACTIVATION_BLOCK},没有销毁记录",
54
+ "cli.langMustBeZhOr": "--lang 只能是 zh 或 en",
55
+ "cli.langSaved": "已保存:以后用中文({file})",
56
+ "cli.languageEnglishSettingsFile": "当前语言:中文(设置文件 {v0})",
57
+ "cli.marketBidsCurrentPriceBnb": "市场:{openBids} 个买单,当前价格 {price} BNB / BNAT(最高买单),累计成交 {vol} BNB",
58
+ "cli.mintSummary": "铸造 #{blk}(销毁 {burn} gwei)→ {dest}",
59
+ "cli.mintToBad": "配置里的 mintTo 不是有效地址:{v}(bnat config mintTo self 改回本钱包)",
60
+ "cli.minted": "已铸",
61
+ "cli.minted2": "铸造 ",
62
+ "cli.mintedHeldBy": "已铸,持有人 {owner}{where}",
63
+ "cli.mintedOfThemInThe": "已铸 {minted} 张(其中 {wrapped} 张在 BNAT 金库),封存销毁 {sealed} BNB,持有地址 {holders}",
64
+ "cli.mustBeAnIntegerFrom": "--{name} 要是 {lo}–{hi} 的整数",
65
+ "cli.nameTheBidToCancel": "用 --bid <买单编号> 指定要撤的买单",
66
+ "cli.nameTheBidWithBid": "用 --bid <买单编号> 指定买单(`bnat bids` 查看)",
67
+ "cli.nameTheBlockWithBlock": "用 --block <区块号> 指定区块",
68
+ "cli.nameTheBlockWithBlock2": "用 --block <区块号> 指定区块,或 --best 选最近销毁最高的一块",
69
+ "cli.nameTheNatToWith": "用 --block <区块号> 指定要 {verb} 的 NAT,多张用逗号分隔:--block 1,2,3",
70
+ "cli.no": "否",
71
+ "cli.no2": "未铸",
72
+ "cli.noEntriesYet": "还没有记录。",
73
+ "cli.noHash": "无哈希",
74
+ "cli.noMatchingBlocks": "没有符合的区块。",
75
+ "cli.noOpenEntries": "没有未结清的记录",
76
+ "cli.noOpenEntry": "没有未结清的记录 {abandon}",
77
+ "cli.noneClaimOneInThe": "没有 —— 铸造前先在币安 App 领取(`baw wallet agent-id` 查看)",
78
+ "cli.notEnoughBnbNeedIncluding": "BNB 不足:需要 {need}(含 gas 约 {v0}),有 {bal},差 {short}",
79
+ "cli.notEnoughBnatNeedHave": "BNAT 不足:需要 {amount},有 {bal},差 {short}",
80
+ "cli.notEnoughBnatNeedIncluding": "BNAT 不足:需要 {total}(含手续费 {fee}),有 {bal},差 {v0}",
81
+ "cli.notMintedYet": "(还没铸:{reason})",
82
+ "cli.nothingLeftToMintIn": "最近 96 个终局块里没有可铸的了,稍后再试。",
83
+ "cli.onChainSimulationFailed": "链上模拟失败:",
84
+ "cli.onChainSimulationPassed": " 链上模拟:通过",
85
+ "cli.openEntriesPickOneWith": "有 {length} 条未结清记录,用 --id 指定:",
86
+ "cli.outcomeUnknownDoNotResend": "执行结果未知。不要重发;用 `baw wallet tx-history` 找到哈希后运行 bnat resume --hash 0x…",
87
+ "cli.plan": "计划:",
88
+ "cli.priceAndAmountMustBoth": "单价和数量都要大于 0",
89
+ "cli.priceBnbPerBnatAnd": "--price(BNB / 每个 BNAT)和 --amount(BNAT)都要是最多 18 位小数的数字",
90
+ "cli.rankBlockBurnGweiStatus": "名次 区块 销毁 gwei 状态",
91
+ "cli.sellBnatToBidReceive": "卖 {amount} BNAT 给买单 #{id}:收到 {v0}(手续费 {fee})",
92
+ "cli.sentWaitingForTheReceipt": " 已发出 {hash},等待上链…",
93
+ "cli.showing": "(显示第 {v0}–{v1} 张)",
94
+ "cli.sortMustBeBurnOr": "--sort 只能是 burn 或 recent",
95
+ "cli.stopped": "已停止",
96
+ "cli.theApprovalHasNotRun": "(授权还没执行,卖出这一步要等授权上链后才能模拟。)",
97
+ "cli.theInteractiveScreenNeedsA": "交互界面要在终端里运行",
98
+ "cli.theLast96FinalizedBlocks": "最近 96 个终局块都已铸或没有销毁。",
99
+ "cli.theMarketSBnatAllowance": "市场合约的 BNAT 授权是 {allowance},先精确授权 {amount}(一笔单独的交易)。",
100
+ "cli.theReceiptLacksTheExpected": "回执里没有预期的 {name} 事件,请人工核对",
101
+ "cli.theSellAmountMustBe": "卖出数量要大于 0",
102
+ "cli.theWalletAcceptedItBut": "钱包已受理但没给哈希(order {v0},{status})。",
103
+ "cli.thereAreNoBidsRight": "现在没有买单。",
104
+ "cli.thisEntryHasAHash": "这条记录有哈希,用 --hash 结清,不能作废",
105
+ "cli.thisIsADevDeployment": "这是开发部署(本地链):真的币安钱包会把交易发到 BSC 主网,已停止。开发链上发交易要用假 baw(BNAT_BAW_BIN),并且 BNAT_RPC 和 FAKE_BAW_RPC 都指向本机",
106
+ "cli.thisTransactionDoesNotMatch": "这笔交易和记录里的调用对不上(发送方 / 合约 / calldata / 附带 BNB),没有结清",
107
+ "cli.thisWallet": "本钱包",
108
+ "cli.thisWalletHasNoOpen": "这个钱包没有挂着的买单。",
109
+ "cli.toIsNotAValid": "--to 不是有效地址",
110
+ "cli.transactionIsOnChainBut": "交易 {hash} 上链但执行失败(区块 {blockNumber})",
111
+ "cli.transactionSWithAnUnknown": "有 {length} 笔交易结果未知({list})。先用 `bnat resume --hash 0x…` 结清;确认没有发出才可 `bnat resume --abandon <id>`。",
112
+ "cli.transactionWasSentButHas": "交易 {hash} 已发出但还没拿到回执({why}),执行结果未知。不要重发;稍后运行 bnat resume --hash {hash}",
113
+ "cli.unexpectedReplyFromAddr": "/addr 返回格式不对",
114
+ "cli.unexpectedReplyFromMarket": "/market 返回格式不对",
115
+ "cli.unexpectedReplyFromMints": "/mints 返回格式不对",
116
+ "cli.unexpectedReplyFromRecent": "/recent 返回格式不对",
117
+ "cli.unexpectedReplyFromTop": "/top 返回格式不对",
118
+ "cli.unknownCommand": "未知命令",
119
+ "cli.unminted": "未铸",
120
+ "cli.unwrapHasNoToThe": "unwrap 没有 --to:NFT 只进发起 unwrap 的钱包,要给别人就 unwrap 完再转",
121
+ "cli.unwrapPayBnatBurnFee": "unwrap {names}:付 {total} BNAT(销毁 {amount} + 手续费 {fee} 留在 BNAT 合约)→ {length} 张 NAT 给自己",
122
+ "cli.usageBnatLangZhEn": "用法:bnat lang zh|en",
123
+ "cli.voided": "已作废 {action} [{id}]",
124
+ "cli.wallet": "钱包 ",
125
+ "cli.walletHasNoAiAgent": "钱包 {from} 没有 .ai(Agent ID),合约会拒绝。先在币安 App 领取(`baw wallet agent-id` 查看)。",
126
+ "cli.walletPreviewEveryFieldChecked": " 钱包预览:逐项核对通过",
127
+ "cli.withoutYesOnlyCheckedNothing": "没有加 --yes:只做了检查,没有发交易。",
128
+ "cli.wrapHasNoToBnat": "wrap 没有 --to:BNAT 只进发起 wrap 的钱包,要给别人就 wrap 完再转",
129
+ "cli.wrapBnatToThisWallet": "wrap {names} → {toHolder} BNAT 给自己(手续费 {fee} 留在 BNAT 合约;任何人付 销毁量 + 1% 可 unwrap 取出)",
130
+ "cli.yes": "有",
131
+ "cli.yes2": "是",
132
+ "common.colon": ":",
133
+ "common.listSep": ";",
134
+ "tui.16TabsMoveR": "1–5 切换 · ↑↓ 选 · {v0} · r 刷新",
135
+ "tui.aTransactionIsExecutingCannot": "交易执行中,完成前不能退出",
136
+ "tui.abandon": "作废 {action}",
137
+ "tui.action": "动作",
138
+ "tui.amountBnatEscCancels": "数量(BNAT,Esc 取消):",
139
+ "tui.atMostPerTransaction": "一笔最多 {BATCH} 张",
140
+ "tui.atMostPerTransactionPicked": "一笔最多 {BATCH} 张,只选了前 {BATCH} 张",
141
+ "tui.badAddress": "不是有效地址:{v}",
142
+ "tui.batchTitle": "{verb} {length} 张",
143
+ "tui.bawAuthSigninReturnedAn": "baw auth signin 返回格式不对",
144
+ "tui.bawCheckingLogin": "baw 检查登录…",
145
+ "tui.bawIsCreatingTheWallet": "baw 正在创建钱包:用币安 App 扫码继续",
146
+ "tui.bawIsNotLoggedIn": "baw 未登录:用币安 App 扫码",
147
+ "tui.bawLoggedIn": "baw 登录成功",
148
+ "tui.bawLoggedIn2": "baw 已登录",
149
+ "tui.bawLogin": "登录 baw",
150
+ "tui.bawNotLoggedInL": "baw 未登录 · L 扫码",
151
+ "tui.bawUnavailable": "baw 不可用",
152
+ "tui.bidBnat": "挂买单 {amount} BNAT @ {price}",
153
+ "tui.block": "区块",
154
+ "tui.bnatStarted": "bnat 已启动",
155
+ "tui.burnGwei": "销毁 gwei",
156
+ "tui.buyer": "买家",
157
+ "tui.byBurn": "按销毁量",
158
+ "tui.cancel": " 取消",
159
+ "tui.cancelBid": "撤买单 #{id}",
160
+ "tui.cancelledNothingSent": "已取消,没有发交易",
161
+ "tui.cannotQuitUntilItFinishes": "(完成前不能退出)",
162
+ "tui.checking": "检查 {title}…",
163
+ "tui.checkingAndSimulatingOnChain": "检查并在链上模拟…",
164
+ "tui.checksAndSimulationPassedNothing": " 检查和模拟已通过,还没有发交易 · ",
165
+ "tui.confirmedHandedToBawTo": "已确认,交给 baw 签名发送",
166
+ "tui.couldNotCreateTheLogin": "生成登录二维码失败",
167
+ "tui.creatingTheLoginQrCode": "正在生成登录二维码…",
168
+ "tui.done": "完成",
169
+ "tui.done2": "完成",
170
+ "tui.enterMintTMintTo2": "回车 铸造 · t 设置接收地址 · n/p 翻页",
171
+ "tui.enterSellBPlaceBid": "回车 卖出 · b 挂买单 · c 撤单 · m 只看我的 · n/p 翻页",
172
+ "tui.errorsCountReason": "错误汇总(次数 · 原因)",
173
+ "tui.executing": "执行 {title}…",
174
+ "tui.expiresInLNewCode": "{mmss} 后过期 · L 重新生成 · Esc 先不登录",
175
+ "tui.hSettleByHashX": "h 用哈希结清 · x 作废",
176
+ "tui.history": "记录",
177
+ "tui.holdings": "持仓",
178
+ "tui.howMuchBnatToSell": "卖多少 BNAT 给 #{id}(直接回车 = 卖到买单或余额用完,Esc 取消):",
179
+ "tui.id": "编号",
180
+ "tui.lLogIn": "L 登录 · ",
181
+ "tui.lNewQrCodeEsc": "L 重新生成二维码 · Esc 先不登录",
182
+ "tui.langSwitched": "已切换到中文(保存在 {file})",
183
+ "tui.leftBnat": "剩余 BNAT",
184
+ "tui.lockedBnb": "锁定 BNB",
185
+ "tui.log": "日志",
186
+ "tui.loginFailed": "登录失败",
187
+ "tui.loginQrCodeReadyWaiting": "登录二维码已生成,等待扫码…",
188
+ "tui.market": "市场",
189
+ "tui.mine": " 我的",
190
+ "tui.mineOnly": "只看我的",
191
+ "tui.mint": "铸造 #{blk}",
192
+ "tui.mint2": "铸造 #{blk} → {to}",
193
+ "tui.mintToCleared": "铸造接收地址改回本钱包(保存在 {file})",
194
+ "tui.mintToHeader": "铸到 {to}",
195
+ "tui.mintToSet": "铸造接收地址已设为 {to}(保存在 {file})",
196
+ "tui.mintToWhichAddress0x": "铸造接收地址(0x…;留空 = 本钱包;Esc 取消):",
197
+ "tui.mintable": "可铸",
198
+ "tui.newest": "按最新",
199
+ "tui.noOutput": "没有输出",
200
+ "tui.none": "✗ 没有",
201
+ "tui.notLoggedInPressL": "baw 未登录:按 L 扫码登录",
202
+ "tui.nothingHere": "(没有记录)",
203
+ "tui.pageRange": "第 {range}",
204
+ "tui.pairingCodeCheckItMatches": "配对码 {v0}(和 App 里显示的核对)",
205
+ "tui.pgupPgdnScrollLogQ": "PgUp/PgDn 滚动日志 · {loginKey}i English · q 退出",
206
+ "tui.pickedGetBnat": "已选 {size} 张 · 约得到 {amt} BNAT",
207
+ "tui.pickedPayBnat": "已选 {size} 张 · 约要付 {amt} BNAT",
208
+ "tui.priceBnbBnat": "单价 BNB / BNAT",
209
+ "tui.priceBnbBnatEscCancels": "单价(BNB / BNAT,Esc 取消):",
210
+ "tui.qrCodeExpiredPressL": "二维码已过期:按 L 重新生成",
211
+ "tui.rank": "名次",
212
+ "tui.rankRange": "名次 {from}–{to}",
213
+ "tui.readFailed": "读取失败",
214
+ "tui.readingBids": "读取买单…",
215
+ "tui.readingHistory": "读取记录…",
216
+ "tui.readingHoldings": "读取持仓…",
217
+ "tui.readingMintableBlocks": "读取可铸区块…",
218
+ "tui.readingVault": "读取金库…",
219
+ "tui.readingWallet": "读取钱包…",
220
+ "tui.ready": "就绪",
221
+ "tui.scanResult": "扫码结果 {status}",
222
+ "tui.scanWithTheBinanceApp": "用币安 App 扫码登录 Agentic Wallet",
223
+ "tui.sellToBid": "卖给买单 #{id}",
224
+ "tui.send": " 发送 ",
225
+ "tui.settle": "结清 {action}",
226
+ "tui.skippedLoginYouCanBrowse": "先不登录:可以浏览,发交易前按 L 登录",
227
+ "tui.spacePickAPickPage": "空格 勾选 · a 选本页 · 回车 wrap · Esc 清空 · n/p 翻页",
228
+ "tui.spacePickAPickPage2": "空格 勾选 · a 选本页 · 回车 unwrap · s 排序 · Esc 清空 · n/p 翻页",
229
+ "tui.status": "状态",
230
+ "tui.summary": "摘要",
231
+ "tui.sureItWasNeverSent": "确认这笔没有发出、要作废?输入 yes:",
232
+ "tui.thisEntryIsAlreadySettled": "这条记录已经结清了",
233
+ "tui.time": "时间",
234
+ "tui.toConfirm": "待确认 ▸ ",
235
+ "tui.transactionHash0xFindIt": "交易哈希(0x…,用 baw wallet tx-history 查,Esc 取消):",
236
+ "tui.unexpectedReply": "返回格式不对(bnat 命令输出的形状和界面预期的不一致)",
237
+ "tui.unwrapCostsBnat": "unwrap 要付 BNAT",
238
+ "tui.vault": "金库",
239
+ "tui.walletReadFailed": "钱包读取失败",
240
+ "tui.windowTooSmallForThe": "窗口太小放不下二维码:放大终端,或在手机浏览器打开这个地址",
241
+ "tui.withoutYes": "没有加 --yes",
242
+ "tui.wrapGetsBnat": "wrap 得到 BNAT",
243
+ "tui.youCanOnlyCancelYour": "只能撤自己的买单(m 只看我的)",
244
+ "wallet.balanceChange": "余额变化",
245
+ "wallet.bawDidNotReturnJson": "baw 没有返回 JSON:",
246
+ "wallet.bawIsNotLoggedIn": ":baw 未登录或会话已过期。直接运行 bnat 扫码登录(或 baw auth signin)",
247
+ "wallet.bawIsNotLoggedIn2": "baw 未登录:直接运行 bnat 扫码登录(或 baw auth signin)",
248
+ "wallet.binanceRiskControlRefusedThis": "币安风控拒绝了这笔调用(预览阶段,没有上链、没花 gas)",
249
+ "wallet.cannotStartBawRunNpm": "启动不了 baw(用 npm install -g nat-cli 重新安装):",
250
+ "wallet.contractCallsNeedDeveloperMode": "合约调用要先在币安 App 的 Agentic Wallet 设置里打开开发者模式,打开后重跑",
251
+ "wallet.failed": " 失败:",
252
+ "wallet.heldByATemporaryBinance": "这笔被币安安全校验临时拦下,没有发出;过几分钟重跑",
253
+ "wallet.messageSuffix": "({message})",
254
+ "wallet.theBinanceAgenticWalletHas": "币安 Agentic Wallet 里没有 BSC 地址",
255
+ "wallet.thePreviewApprovesADifferent": "预览授权给了别的地址",
256
+ "wallet.thePreviewApprovesMoreThan": "预览的授权额度高于计划",
257
+ "wallet.thePreviewCalldataDiffersFrom": "预览的 calldata 和计划不一致",
258
+ "wallet.thePreviewChangesAnAllowance": "预览改动了授权,计划里没有",
259
+ "wallet.thePreviewChangesAnotherAccount": "预览改动了别的账户的余额",
260
+ "wallet.thePreviewChangesAnotherToken": "预览改动了别的代币的授权",
261
+ "wallet.thePreviewFunctionSelectorDiffers": "预览的函数选择器和计划不一致",
262
+ "wallet.thePreviewHasExpired": "预览已过期",
263
+ "wallet.thePreviewHasNoValid": "预览没有有效的 requestId",
264
+ "wallet.thePreviewMovesAnUnplanned": "预览动用了计划外的资产 {contractAddress}",
265
+ "wallet.thePreviewSendsFromA": "预览的发送地址和计划不一致",
266
+ "wallet.thePreviewSendsWeiThe": "预览附带 {value} wei,计划是 {value2}",
267
+ "wallet.thePreviewShowsAnAuthority": "预览显示有权限变更;已停止",
268
+ "wallet.thePreviewSpendsMoreThan": "预览花出 {v0}({token}),超过计划的 {max}",
269
+ "wallet.thePreviewTargetsADifferent": "预览的目标合约和计划不一致",
270
+ "wallet.theWalletReportsRisks": "钱包报告风险:",
271
+ "wallet.theWalletWantsThisCall": "钱包要求在币安 App 里确认这笔调用;已停止(在 App 里调整设置后重跑)",
272
+ "wallet.unexpectedDecreaseOfInThe": "预览里 {v0} 意外减少",
273
+ "wallet.unexpectedIncreaseOfInThe": "预览里 {v0} 意外增加",
274
+ "wallet.unexpectedShapeFromBawWallet": "baw wallet address 返回形状不对:",
275
+ "wallet.unrecognizedInTheWalletPreview": "钱包预览里有无法识别的{what}:",
276
+ "wallet.walletSimulationFailed": "钱包模拟失败:",
277
+ "wallet.unexpectedPreviewShape": "钱包预览格式不对:缺少或无法识别 {field},已停止",
278
+ "wallet.bawTimedOut": "baw {cmd} 超过 {s} 秒没有响应,已放弃",
279
+ "cli.stillWaitingForTheWallet": "还在等钱包执行(记录 {id})。不要重发;中断后用 bnat resume 查结果",
280
+ "tui.drawFailed": "界面绘制出错",
281
+ "chain.indexSyncing": "索引同步中",
282
+ "journal.corruptLine": "记账文件 {path} 第 {n} 行损坏,修好之前不会执行任何写操作。改动前先备份这个文件,并用 baw wallet tx-history 核对钱包的交易记录。"
283
+ }
package/lib/baw.mjs ADDED
@@ -0,0 +1,191 @@
1
+ // Binance Agentic Wallet (baw CLI) runner and strict preview checks.
2
+ // The wallet holds the keys; this module previews one exact call, checks the preview
3
+ // against the transaction we built, and executes that requestId once.
4
+ //
5
+ // Preview field names (simulationResult, parsedTx, balanceChanges, allowanceChanges, risks,
6
+ // requireConfirmation) are those of baw 1.12's output (the pinned dependency). A missing or
7
+ // mistyped field stops the run rather than passing: an absent check must never read as "nothing moves".
8
+ import { spawn } from 'node:child_process';
9
+ import { createRequire } from 'node:module';
10
+ import { dirname, join } from 'node:path';
11
+ import { t } from './i18n.mjs';
12
+
13
+ export const NATIVE = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee';
14
+ const lower = (v) => (typeof v === 'string' ? v.toLowerCase() : v);
15
+
16
+ /** Wallet amounts arrive as integer strings, hex, or JSON numbers (large ones as floats). */
17
+ function amount(v, what) {
18
+ if (typeof v === 'number' && Number.isFinite(v)) return { num: v, big: Number.isSafeInteger(v) ? BigInt(v) : null };
19
+ if (typeof v === 'string' && /^-?(0x[0-9a-f]+|[0-9]+)$/i.test(v)) {
20
+ const big = v.startsWith('-') ? -BigInt(v.slice(1)) : BigInt(v);
21
+ return { num: Number(big), big };
22
+ }
23
+ if (typeof v === 'string' && /^-?[0-9]+(\.[0-9]+)?(e[+-]?[0-9]+)?$/i.test(v)) return { num: Number(v), big: null };
24
+ throw Error(t('wallet.unrecognizedInTheWalletPreview', { what }) + JSON.stringify(v));
25
+ }
26
+ const empty = (v) => v == null || v === '' || (Array.isArray(v) && v.length === 0) || (typeof v === 'object' && Object.values(v).every(empty));
27
+
28
+ /** A refused precondition (not logged in, wallet setting, risk control …): exit 2, nothing was sent. */
29
+ export class Stop extends Error {}
30
+
31
+ // baw is a pinned dependency of this package, run with this same node (no global install needed).
32
+ // BNAT_BAW_BIN overrides the executable (tests use a fake). Only one wallet: baw keeps its login
33
+ // in its default place (~/.baw), shared with a `baw` you run yourself.
34
+ const require = createRequire(import.meta.url);
35
+ function bawCommand(args) {
36
+ if (process.env.BNAT_BAW_BIN) return [process.env.BNAT_BAW_BIN, args];
37
+ try {
38
+ const pkg = require.resolve('@binance/agentic-wallet/package.json');
39
+ return [process.execPath, [join(dirname(pkg), require(pkg).bin.baw), ...args]];
40
+ } catch { return ['baw', args]; }
41
+ }
42
+
43
+ // Reads and previews are safe to abandon; `execute` is never aborted (it may already be on chain),
44
+ // it only reports that it is still waiting.
45
+ const READ_TIMEOUT_MS = Number(process.env.BNAT_BAW_TIMEOUT_MS ?? 60_000);
46
+ const SLOW_MS = Number(process.env.BNAT_BAW_SLOW_MS ?? 30_000);
47
+
48
+ /**
49
+ * Run `baw <args> --json` and return the parsed reply.
50
+ * `signal` stops a long-running call (e.g. `auth verify`, which polls until the QR is scanned);
51
+ * `timeoutMs` gives up after that long; `onSlow` is called every SLOW_MS while it runs.
52
+ */
53
+ export function baw(args, { signal, timeoutMs, onSlow } = {}) {
54
+ return new Promise((resolve, reject) => {
55
+ const [cmd, argv] = bawCommand([...args, '--json']);
56
+ if (timeoutMs) signal = signal ? AbortSignal.any([signal, AbortSignal.timeout(timeoutMs)]) : AbortSignal.timeout(timeoutMs);
57
+ const child = spawn(cmd, argv, { shell: false, stdio: ['ignore', 'pipe', 'pipe'], signal });
58
+ const slow = onSlow && setInterval(onSlow, SLOW_MS);
59
+ child.on('close', () => clearInterval(slow));
60
+ let out = '', err = '';
61
+ child.stdout.on('data', (d) => { out += d; if (out.length > 4e6) child.kill(); });
62
+ child.stderr.on('data', (d) => { err += d; });
63
+ child.on('error', (e) => {
64
+ if (e.name === 'AbortError' && signal?.reason?.name === 'TimeoutError') reject(Error(t('wallet.bawTimedOut', { cmd: args.slice(0, 2).join(' '), s: timeoutMs / 1000 })));
65
+ else reject(e.name === 'AbortError' ? e : Error(t('wallet.cannotStartBawRunNpm') + e.message));
66
+ });
67
+ child.on('close', () => {
68
+ try { resolve(JSON.parse(out.slice(out.indexOf('{')))); } catch { reject(Error(t('wallet.bawDidNotReturnJson') + (out + err).trim().slice(-400))); }
69
+ });
70
+ });
71
+ }
72
+
73
+ // Wallet error codes seen in practice: each is an expected stop with what to do about it.
74
+ const BAW_STOP = {
75
+ 351801: t('wallet.contractCallsNeedDeveloperMode'),
76
+ 351724: t('wallet.heldByATemporaryBinance'),
77
+ 351803: t('wallet.binanceRiskControlRefusedThis'),
78
+ };
79
+
80
+ /** The reply's data; anything but `success: true` throws (a Stop for the known, expected cases). */
81
+ export function data(res, what) {
82
+ if (res && typeof res === 'object' && res.success === true) return res.data;
83
+ const e = res?.error ?? {};
84
+ const msg = `${e.name ?? ''} ${e.message ?? ''}`.trim();
85
+ if (BAW_STOP[e.code]) throw new Stop(`${what}${t('common.colon')}${BAW_STOP[e.code]}${e.message ? t('wallet.messageSuffix', { message: e.message }) : ''}`);
86
+ if (e.name === 'SESSION_EXPIRED' || e.name === 'NOT_LOGGED_IN') throw new Stop(`${what}${t('wallet.bawIsNotLoggedIn')}`);
87
+ throw Error(`${what}${t('wallet.failed')}${msg || JSON.stringify(res).slice(0, 300)}`);
88
+ }
89
+
90
+ /** The logged-in wallet's BSC address (not its Solana one). */
91
+ export async function walletAddress() {
92
+ const { status } = data(await baw(['wallet', 'status'], { timeoutMs: READ_TIMEOUT_MS }), 'baw wallet status');
93
+ if (status !== 'CONNECTED') throw new Stop(t('wallet.bawIsNotLoggedIn2'));
94
+ const { addresses } = data(await baw(['wallet', 'address'], { timeoutMs: READ_TIMEOUT_MS }), 'baw wallet address');
95
+ if (!Array.isArray(addresses)) throw Error(t('wallet.unexpectedShapeFromBawWallet') + JSON.stringify(addresses));
96
+ const bsc = addresses.find((a) => String(a.binanceChainId) === '56');
97
+ if (!/^0x[0-9a-fA-F]{40}$/.test(bsc?.address ?? '')) throw Error(t('wallet.theBinanceAgenticWalletHas'));
98
+ return bsc.address;
99
+ }
100
+
101
+ export async function preview(chainId, tx) {
102
+ return baw(['contract-call', 'preview', '--binanceChainId', String(chainId), '--from', tx.from, '--to', tx.to,
103
+ '--value', tx.value.toString(), '--inputData', tx.data], { timeoutMs: READ_TIMEOUT_MS });
104
+ }
105
+
106
+ /**
107
+ * Check a preview against the call we built. `expect`:
108
+ * { from, to, data, value: bigint,
109
+ * assets: { [token]: { in?: true, maxOut?: bigint } }, // every token allowed to move
110
+ * native: { in?: true, maxOut?: bigint }, // BNB beyond gas
111
+ * approve?: { token, spender, max: bigint } } // the one allowed allowance change
112
+ * Anything unknown, risky or different throws.
113
+ */
114
+ export function validatePreview(res, expect, now = Date.now()) {
115
+ const d = data(res, 'baw contract-call preview');
116
+ const shape = (ok, field) => { if (!ok) throw Error(t('wallet.unexpectedPreviewShape', { field })); };
117
+ const isObj = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
118
+ shape(isObj(d), 'data');
119
+ const s = d.simulationResult, ptx = d.parsedTx;
120
+ shape(isObj(s), 'simulationResult');
121
+ shape(isObj(ptx), 'parsedTx');
122
+ shape(Array.isArray(s.balanceChanges), 'simulationResult.balanceChanges');
123
+ shape(Array.isArray(s.allowanceChanges), 'simulationResult.allowanceChanges');
124
+ // Observed preview shape: risks is an object of lists;
125
+ // parsedTx carries neither value nor calldata, only the target and sometimes the selector
126
+ // (`action`); the value comes back as simulationResult.gasTokenFromData.
127
+ shape(isObj(d.risks) || Array.isArray(d.risks), 'risks');
128
+ const value = ptx.value ?? s.gasTokenFromData;
129
+ shape(value !== undefined && value !== null, 'simulationResult.gasTokenFromData');
130
+ shape(d.expiresAt === undefined || Number.isFinite(Number(d.expiresAt)), 'expiresAt');
131
+ if (typeof d.requestId !== 'string' || !/^[A-Za-z0-9_-]{8,128}$/.test(d.requestId)) throw Error(t('wallet.thePreviewHasNoValid'));
132
+ if (d.expiresAt !== undefined && Number(d.expiresAt) <= now) throw Error(t('wallet.thePreviewHasExpired'));
133
+ if (d.requireConfirmation !== false) throw Error(t('wallet.theWalletWantsThisCall'));
134
+ if (s.simulationCode !== '000000000' || !empty(s.simulationErrorDetail) || !(s.preCheckCode === '' || s.preCheckCode === '000000000' || s.preCheckCode == null)) {
135
+ throw Error(t('wallet.walletSimulationFailed') + (s.simulationErrorDetail || s.simulationCode || 'unknown'));
136
+ }
137
+ if (!empty(s.authorityChanges)) throw Error(t('wallet.thePreviewShowsAnAuthority'));
138
+ if (!empty(d.risks)) throw Error(t('wallet.theWalletReportsRisks') + JSON.stringify(d.risks).slice(0, 300));
139
+ if (lower(ptx.contractAddress ?? ptx.to) !== lower(expect.to)) throw Error(t('wallet.thePreviewTargetsADifferent'));
140
+ if (ptx.from !== undefined && lower(ptx.from) !== lower(expect.from)) throw Error(t('wallet.thePreviewSendsFromA'));
141
+ for (const k of ['inputData', 'data', 'input']) if (ptx[k] !== undefined && lower(ptx[k]) !== lower(expect.data)) throw Error(t('wallet.thePreviewCalldataDiffersFrom'));
142
+ if (ptx.action !== undefined && lower(ptx.action) !== expect.data.slice(0, 10).toLowerCase()) throw Error(t('wallet.thePreviewFunctionSelectorDiffers'));
143
+ const v = amount(value, 'value');
144
+ if (v.big !== null ? v.big !== expect.value : Math.abs(v.num - Number(expect.value)) > Number(expect.value) * 1e-9) throw Error(t('wallet.thePreviewSendsWeiThe', { value, value2: expect.value }));
145
+
146
+ const outs = new Map(); // token -> { big, num, exact }
147
+ const assets = Object.fromEntries(Object.entries(expect.assets ?? {}).map(([k, v]) => [lower(k), v]));
148
+ for (const b of s.balanceChanges) {
149
+ if (b.owner !== undefined && lower(b.owner) !== lower(expect.from)) throw Error(t('wallet.thePreviewChangesAnotherAccount'));
150
+ const token = lower(b.contractAddress), change = amount(b.change, t('wallet.balanceChange'));
151
+ // A listed change of zero (or a field that reads as zero, e.g. an NFT entry carrying only its
152
+ // tokenId) would pass every rule below unchecked.
153
+ shape(change.num !== 0, 'simulationResult.balanceChanges');
154
+ const rule = token === NATIVE ? expect.native ?? {} : assets[token];
155
+ if (!rule) throw Error(t('wallet.thePreviewMovesAnUnplanned', { contractAddress: b.contractAddress }));
156
+ if (change.num > 0 && !rule.in) throw Error(t('wallet.unexpectedIncreaseOfInThe', { v0: token === NATIVE ? 'BNB' : b.contractAddress }));
157
+ if (change.num < 0) {
158
+ if (rule.maxOut === undefined) throw Error(t('wallet.unexpectedDecreaseOfInThe', { v0: token === NATIVE ? 'BNB' : b.contractAddress }));
159
+ const o = outs.get(token) ?? { big: 0n, num: 0, exact: true };
160
+ o.num += -change.num; if (change.big === null) o.exact = false; else o.big += -change.big;
161
+ outs.set(token, o);
162
+ }
163
+ }
164
+ for (const [token, o] of outs) {
165
+ const max = (token === NATIVE ? expect.native : assets[token]).maxOut;
166
+ if (o.exact ? o.big > max : o.num > Number(max) * (1 + 1e-9)) throw Error(t('wallet.thePreviewSpendsMoreThan', { v0: o.exact ? o.big : o.num, token, max }));
167
+ }
168
+ for (const a of s.allowanceChanges) {
169
+ if (!expect.approve) throw Error(t('wallet.thePreviewChangesAnAllowance'));
170
+ // Real field names are tokenAddress and postAmount; an entry whose token, spender or new
171
+ // amount cannot be found stops instead of passing unchecked.
172
+ const token = a.tokenAddress ?? a.contractAddress;
173
+ const spender = a.spender ?? a.spenderAddress ?? a.approvedAddress;
174
+ const amounts = ['postAmount', 'amount', 'allowance', 'newAllowance', 'after'].filter((k) => a[k] != null);
175
+ shape(token != null && spender != null && amounts.length > 0, 'simulationResult.allowanceChanges');
176
+ if (a.owner != null && lower(a.owner) !== lower(expect.from)) throw Error(t('wallet.thePreviewChangesAnotherAccount'));
177
+ if (lower(token) !== lower(expect.approve.token)) throw Error(t('wallet.thePreviewChangesAnotherToken'));
178
+ if (lower(spender) !== lower(expect.approve.spender)) throw Error(t('wallet.thePreviewApprovesADifferent'));
179
+ for (const k of amounts) {
180
+ const n = amount(a[k], k);
181
+ if (n.big !== null ? n.big > expect.approve.max : n.num > Number(expect.approve.max) * (1 + 1e-9)) throw Error(t('wallet.thePreviewApprovesMoreThan'));
182
+ }
183
+ }
184
+ return { requestId: d.requestId, expiresAt: d.expiresAt ?? null };
185
+ }
186
+
187
+ /** Execute a validated requestId once. Never retried, never aborted; `onSlow` hears that it is still running. */
188
+ export async function execute(requestId, onSlow) {
189
+ const d = data(await baw(['contract-call', 'execute', '--requestId', requestId], { onSlow }), 'baw contract-call execute');
190
+ return { hash: d.txHash ?? d.hash ?? null, orderId: d.orderId ?? null, status: d.status ?? 'UNKNOWN' };
191
+ }