@kashdao/cli 0.1.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/CHANGELOG.md +78 -0
- package/CONTRIBUTING.md +159 -0
- package/LICENSE +21 -0
- package/README.md +868 -0
- package/SECURITY.md +110 -0
- package/dist/account-AVFLEM5D.js +90 -0
- package/dist/account-AVFLEM5D.js.map +1 -0
- package/dist/auth-GUJCVKTD.js +239 -0
- package/dist/auth-GUJCVKTD.js.map +1 -0
- package/dist/chunk-BN2CUM42.js +9 -0
- package/dist/chunk-BN2CUM42.js.map +1 -0
- package/dist/chunk-BRK7KJ4O.js +154 -0
- package/dist/chunk-BRK7KJ4O.js.map +1 -0
- package/dist/chunk-KMBMQIZ7.js +393 -0
- package/dist/chunk-KMBMQIZ7.js.map +1 -0
- package/dist/chunk-LBIRQHX5.js +133 -0
- package/dist/chunk-LBIRQHX5.js.map +1 -0
- package/dist/chunk-MIXOZU2S.js +366 -0
- package/dist/chunk-MIXOZU2S.js.map +1 -0
- package/dist/chunk-QJMF73M5.js +129 -0
- package/dist/chunk-QJMF73M5.js.map +1 -0
- package/dist/chunk-UZNSYATZ.js +680 -0
- package/dist/chunk-UZNSYATZ.js.map +1 -0
- package/dist/chunk-VIADBYFY.js +94 -0
- package/dist/chunk-VIADBYFY.js.map +1 -0
- package/dist/chunk-YHCG2SUC.js +159 -0
- package/dist/chunk-YHCG2SUC.js.map +1 -0
- package/dist/chunk-YJX3JJ4M.js +174 -0
- package/dist/chunk-YJX3JJ4M.js.map +1 -0
- package/dist/client-IOM55ZCS.js +14 -0
- package/dist/client-IOM55ZCS.js.map +1 -0
- package/dist/completion-ZZGX5BQC.js +100 -0
- package/dist/completion-ZZGX5BQC.js.map +1 -0
- package/dist/config-XJL5TSYK.js +690 -0
- package/dist/config-XJL5TSYK.js.map +1 -0
- package/dist/config-store-3ZSYGXMQ.js +44 -0
- package/dist/config-store-3ZSYGXMQ.js.map +1 -0
- package/dist/docs-MDKKSJDC.js +102 -0
- package/dist/docs-MDKKSJDC.js.map +1 -0
- package/dist/eoa-IQ72EIHR.js +551 -0
- package/dist/eoa-IQ72EIHR.js.map +1 -0
- package/dist/errors-WMZIEGQI.js +18 -0
- package/dist/errors-WMZIEGQI.js.map +1 -0
- package/dist/explain-H4EH3KH3.js +165 -0
- package/dist/explain-H4EH3KH3.js.map +1 -0
- package/dist/global-options-XOLJUPTT.js +18 -0
- package/dist/global-options-XOLJUPTT.js.map +1 -0
- package/dist/health-VZEIII74.js +98 -0
- package/dist/health-VZEIII74.js.map +1 -0
- package/dist/help-footer-GTANVDNP.js +43 -0
- package/dist/help-footer-GTANVDNP.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +251 -0
- package/dist/index.js.map +1 -0
- package/dist/intro-PQQTYWR7.js +41 -0
- package/dist/intro-PQQTYWR7.js.map +1 -0
- package/dist/markets-C37JZDPE.js +295 -0
- package/dist/markets-C37JZDPE.js.map +1 -0
- package/dist/output-VCBZ3FM7.js +22 -0
- package/dist/output-VCBZ3FM7.js.map +1 -0
- package/dist/portfolio-2AEPIJIG.js +115 -0
- package/dist/portfolio-2AEPIJIG.js.map +1 -0
- package/dist/protocol-XEQXF2GW.js +1572 -0
- package/dist/protocol-XEQXF2GW.js.map +1 -0
- package/dist/quote-I4DVJ7ZB.js +144 -0
- package/dist/quote-I4DVJ7ZB.js.map +1 -0
- package/dist/schema-PIFQ65TS.js +410 -0
- package/dist/schema-PIFQ65TS.js.map +1 -0
- package/dist/setup-USZ6IODD.js +260 -0
- package/dist/setup-USZ6IODD.js.map +1 -0
- package/dist/stdin-YW2CEQXU.js +28 -0
- package/dist/stdin-YW2CEQXU.js.map +1 -0
- package/dist/trace-IZBYTUFO.js +103 -0
- package/dist/trace-IZBYTUFO.js.map +1 -0
- package/dist/trade-IIZSEXEI.js +586 -0
- package/dist/trade-IIZSEXEI.js.map +1 -0
- package/dist/version-JDK3PEP5.js +117 -0
- package/dist/version-JDK3PEP5.js.map +1 -0
- package/dist/version-check-TDCION37.js +138 -0
- package/dist/version-check-TDCION37.js.map +1 -0
- package/dist/webhooks-PTVRKICZ.js +680 -0
- package/dist/webhooks-PTVRKICZ.js.map +1 -0
- package/dist/with-retry-4FIZG3A7.js +223 -0
- package/dist/with-retry-4FIZG3A7.js.map +1 -0
- package/package.json +99 -0
|
@@ -0,0 +1,1572 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
loadRawPrivateKey,
|
|
4
|
+
resolveCliCustomChain
|
|
5
|
+
} from "./chunk-YJX3JJ4M.js";
|
|
6
|
+
import {
|
|
7
|
+
formatAddress,
|
|
8
|
+
formatBigDecimal,
|
|
9
|
+
formatGwei,
|
|
10
|
+
formatUsdcAtomic,
|
|
11
|
+
formatWad
|
|
12
|
+
} from "./chunk-BRK7KJ4O.js";
|
|
13
|
+
import {
|
|
14
|
+
readConfig
|
|
15
|
+
} from "./chunk-KMBMQIZ7.js";
|
|
16
|
+
import {
|
|
17
|
+
DEFAULT_SLIPPAGE_BPS,
|
|
18
|
+
HEX_HASH_REGEX,
|
|
19
|
+
MAX_UINT256,
|
|
20
|
+
decimalToAtomicUsdc,
|
|
21
|
+
decimalToAtomicWad,
|
|
22
|
+
extractPartialHash,
|
|
23
|
+
parseDeadlineSec,
|
|
24
|
+
parseOutcomeIndex,
|
|
25
|
+
parseSlippageBps,
|
|
26
|
+
parseUppercaseSide,
|
|
27
|
+
validateAddress,
|
|
28
|
+
validateAddressOptional
|
|
29
|
+
} from "./chunk-YHCG2SUC.js";
|
|
30
|
+
import {
|
|
31
|
+
parseOptionalPositiveFloat,
|
|
32
|
+
parseOptionalPositiveInt,
|
|
33
|
+
parsePositiveInt,
|
|
34
|
+
readGlobals
|
|
35
|
+
} from "./chunk-LBIRQHX5.js";
|
|
36
|
+
import {
|
|
37
|
+
isQuiet,
|
|
38
|
+
log,
|
|
39
|
+
print,
|
|
40
|
+
printJson,
|
|
41
|
+
style,
|
|
42
|
+
writeNdjson
|
|
43
|
+
} from "./chunk-VIADBYFY.js";
|
|
44
|
+
import "./chunk-MIXOZU2S.js";
|
|
45
|
+
import {
|
|
46
|
+
CliError,
|
|
47
|
+
CliValidationError,
|
|
48
|
+
EXIT_CODES,
|
|
49
|
+
toCliError
|
|
50
|
+
} from "./chunk-UZNSYATZ.js";
|
|
51
|
+
|
|
52
|
+
// src/commands/protocol/index.ts
|
|
53
|
+
import { Command as Command13 } from "commander";
|
|
54
|
+
|
|
55
|
+
// src/commands/protocol/allowance.ts
|
|
56
|
+
import { Command } from "commander";
|
|
57
|
+
|
|
58
|
+
// src/utils/direct-client.ts
|
|
59
|
+
async function buildDirectClient(opts) {
|
|
60
|
+
const globals = opts.globals;
|
|
61
|
+
const config = await readConfig({
|
|
62
|
+
...globals?.profile === void 0 ? {} : { profile: globals.profile },
|
|
63
|
+
...globals?.configPath === void 0 ? {} : { configPath: globals.configPath }
|
|
64
|
+
});
|
|
65
|
+
const rpc = globals?.baseUrl ?? config.rpcUrl;
|
|
66
|
+
if (!rpc) {
|
|
67
|
+
throw missingDirectConfig("rpcUrl", "--rpc-url or `kash config set rpcUrl <url>`");
|
|
68
|
+
}
|
|
69
|
+
if (!config.smartAccount) {
|
|
70
|
+
throw missingDirectConfig(
|
|
71
|
+
"smartAccount",
|
|
72
|
+
"`kash config set smartAccount 0x\u2026` (the ERC-4337 account address whose funds you trade with)"
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const { createSmartAccountClient } = await import("@kashdao/protocol-sdk");
|
|
76
|
+
const signer = opts.requireSigner ? await loadSigner(config.signerKeyRef) : noopSigner();
|
|
77
|
+
const bundlerConfig = resolveBundlerConfig(config);
|
|
78
|
+
const customChain = await maybeBuildCustomChain(config, rpc);
|
|
79
|
+
if (customChain !== void 0 && customChain.smartAccount === void 0) {
|
|
80
|
+
throw missingDirectConfig(
|
|
81
|
+
"customChain.smartAccount",
|
|
82
|
+
"all three: `kash config set customChain.smartAccount.factoryAddress 0x\u2026`, `customChain.smartAccount.implementationAddress 0x\u2026`, `customChain.smartAccount.entryPointAddress 0x\u2026` (SA mode requires the local SimpleAccount factory + EntryPoint addresses on a custom chain \u2014 Anvil deploys these to addresses that differ from the canonical permissionless.js defaults)"
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
const client = createSmartAccountClient({
|
|
86
|
+
chainId: config.defaultChainId,
|
|
87
|
+
rpc,
|
|
88
|
+
signer,
|
|
89
|
+
...bundlerConfig === void 0 ? {} : { bundler: bundlerConfig },
|
|
90
|
+
...customChain === void 0 ? {} : { customChain }
|
|
91
|
+
});
|
|
92
|
+
return {
|
|
93
|
+
client,
|
|
94
|
+
chainId: config.defaultChainId,
|
|
95
|
+
smartAccount: config.smartAccount
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function noopSigner() {
|
|
99
|
+
return {
|
|
100
|
+
ownerAddress: "0x0000000000000000000000000000000000000000",
|
|
101
|
+
async signUserOpHash() {
|
|
102
|
+
throw new CliError("CLI bug: read-only direct-mode command attempted to sign a UserOp.", {
|
|
103
|
+
code: "UNEXPECTED",
|
|
104
|
+
suggestion: "File an issue at https://github.com/KashDAO/cli/issues \u2014 this should not happen."
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async function loadSigner(ref) {
|
|
110
|
+
const rawKey = await loadRawPrivateKey(ref);
|
|
111
|
+
const [{ privateKeyToAccount }, { viemAccountSigner }] = await Promise.all([
|
|
112
|
+
import("viem/accounts"),
|
|
113
|
+
import("@kashdao/protocol-sdk")
|
|
114
|
+
]);
|
|
115
|
+
return viemAccountSigner(privateKeyToAccount(rawKey));
|
|
116
|
+
}
|
|
117
|
+
function resolveBundlerConfig(config) {
|
|
118
|
+
if (config.bundlerProvider === void 0 && config.bundlerUrl === void 0) {
|
|
119
|
+
return void 0;
|
|
120
|
+
}
|
|
121
|
+
if (config.bundlerProvider === void 0) {
|
|
122
|
+
return config.bundlerUrl;
|
|
123
|
+
}
|
|
124
|
+
if (config.bundlerUrl === void 0) {
|
|
125
|
+
throw new CliError(
|
|
126
|
+
`Direct-mode config: bundlerProvider="${config.bundlerProvider}" requires a bundlerUrl.`,
|
|
127
|
+
{
|
|
128
|
+
code: "DIRECT_CONFIG_MISSING",
|
|
129
|
+
suggestion: "`kash config set bundlerUrl <provider-rpc-url>` (e.g. https://api.pimlico.io/v2/8453/rpc?apikey=\u2026)"
|
|
130
|
+
}
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
provider: config.bundlerProvider,
|
|
135
|
+
url: config.bundlerUrl
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
async function maybeBuildCustomChain(config, rpc) {
|
|
139
|
+
if (config.customChain === void 0) return void 0;
|
|
140
|
+
if (config.defaultChainId === void 0) {
|
|
141
|
+
throw missingDirectConfig(
|
|
142
|
+
"defaultChainId",
|
|
143
|
+
"`kash config set defaultChainId <id>` (required when customChain is set so the CLI can construct the viem chain)"
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return resolveCliCustomChain({
|
|
147
|
+
customChain: config.customChain,
|
|
148
|
+
chainId: config.defaultChainId,
|
|
149
|
+
rpc
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function missingDirectConfig(field, hint) {
|
|
153
|
+
return new CliError(`Direct-mode config missing: ${field}.`, {
|
|
154
|
+
code: "DIRECT_CONFIG_MISSING",
|
|
155
|
+
exitCode: EXIT_CODES.GENERIC,
|
|
156
|
+
suggestion: `Set ${field} via ${hint}.`,
|
|
157
|
+
actions: [
|
|
158
|
+
{
|
|
159
|
+
type: "check_input",
|
|
160
|
+
field,
|
|
161
|
+
description: hint
|
|
162
|
+
}
|
|
163
|
+
]
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/commands/protocol/allowance.ts
|
|
168
|
+
var allowanceCommand = new Command("allowance").description("Read the on-chain USDC allowance from `account` to `spender`.").argument("<spender>", "spender contract address (e.g. a market) \u2014 0x-prefixed").argument("[account]", "owner address (default: profile's smartAccount)").addHelpText(
|
|
169
|
+
"after",
|
|
170
|
+
`
|
|
171
|
+
Examples:
|
|
172
|
+
$ kash protocol allowance 0xMarket...
|
|
173
|
+
$ kash protocol allowance 0xMarket... 0xOwner...
|
|
174
|
+
$ kash protocol allowance 0xMarket... --json --quiet | jq -r '.allowanceAtomic'
|
|
175
|
+
|
|
176
|
+
Notes:
|
|
177
|
+
- The allowance is returned in USDC atomic units (6 decimals). A
|
|
178
|
+
value larger than the trade's atomic-USDC input means \`approve\`
|
|
179
|
+
can be skipped on the next trade.
|
|
180
|
+
`
|
|
181
|
+
).action(async (spender, account, _opts, cmd) => {
|
|
182
|
+
const globals = readGlobals(cmd);
|
|
183
|
+
const spenderAddress = validateAddress(spender, "spender");
|
|
184
|
+
const accountAddress = validateAddressOptional(account, "account");
|
|
185
|
+
try {
|
|
186
|
+
const resolved = await buildDirectClient({ globals });
|
|
187
|
+
const owner = accountAddress ?? resolved.smartAccount;
|
|
188
|
+
const allowance = await resolved.client.account.usdcAllowance(owner, spenderAddress);
|
|
189
|
+
const payload = {
|
|
190
|
+
owner,
|
|
191
|
+
spender: spenderAddress,
|
|
192
|
+
chainId: resolved.chainId,
|
|
193
|
+
allowanceAtomic: allowance.toString()
|
|
194
|
+
};
|
|
195
|
+
if (globals.json) {
|
|
196
|
+
printJson(payload);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
print("");
|
|
200
|
+
print(` ${style.dim("Owner ")} ${formatAddress(owner, 10, 8)}`);
|
|
201
|
+
print(` ${style.dim("Spender ")} ${formatAddress(spenderAddress, 10, 8)}`);
|
|
202
|
+
print(
|
|
203
|
+
` ${style.dim("Allowance")} ${formatUsdcAtomic(allowance.toString())} ${style.dim(`(${allowance.toString()} atomic)`)}`
|
|
204
|
+
);
|
|
205
|
+
} catch (cause) {
|
|
206
|
+
throw toCliError(cause);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// src/commands/protocol/balance.ts
|
|
211
|
+
import { Command as Command2 } from "commander";
|
|
212
|
+
var balanceCommand = new Command2("balance").description("Read on-chain USDC + gas balance for a smart account (defaults to the profile's).").argument("[account]", "smart-account address (defaults to the active profile's smartAccount)").addHelpText(
|
|
213
|
+
"after",
|
|
214
|
+
`
|
|
215
|
+
Examples:
|
|
216
|
+
$ kash protocol balance
|
|
217
|
+
$ kash protocol balance 0xabc...
|
|
218
|
+
$ kash protocol balance --json --quiet | jq '.usdcAtomic'
|
|
219
|
+
|
|
220
|
+
Direct-mode requires rpcUrl + smartAccount + signerKeyRef configured
|
|
221
|
+
on the active profile (signer is loaded but never invoked for reads).
|
|
222
|
+
`
|
|
223
|
+
).action(async (account, _opts, cmd) => {
|
|
224
|
+
const globals = readGlobals(cmd);
|
|
225
|
+
const accountOverride = validateAddressOptional(account, "account");
|
|
226
|
+
let resolved;
|
|
227
|
+
try {
|
|
228
|
+
resolved = await buildDirectClient({ globals });
|
|
229
|
+
} catch (cause) {
|
|
230
|
+
throw toCliError(cause);
|
|
231
|
+
}
|
|
232
|
+
const target = accountOverride ?? resolved.smartAccount;
|
|
233
|
+
let usdcAtomic;
|
|
234
|
+
let gasWei;
|
|
235
|
+
try {
|
|
236
|
+
[usdcAtomic, gasWei] = await Promise.all([
|
|
237
|
+
resolved.client.account.usdcBalance(target),
|
|
238
|
+
resolved.client.account.gasBalance(target)
|
|
239
|
+
]);
|
|
240
|
+
} catch (cause) {
|
|
241
|
+
throw toCliError(cause);
|
|
242
|
+
}
|
|
243
|
+
const payload = {
|
|
244
|
+
account: target,
|
|
245
|
+
chainId: resolved.chainId,
|
|
246
|
+
usdcAtomic: usdcAtomic.toString(),
|
|
247
|
+
gasWei: gasWei.toString()
|
|
248
|
+
};
|
|
249
|
+
if (globals.json) {
|
|
250
|
+
printJson(payload);
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
print("");
|
|
254
|
+
print(` ${style.dim("Account ")} ${formatAddress(target, 10, 8)}`);
|
|
255
|
+
print(` ${style.dim("Chain ")} ${String(resolved.chainId)}`);
|
|
256
|
+
print(` ${style.dim("USDC ")} ${formatUsdcAtomic(usdcAtomic.toString())}`);
|
|
257
|
+
print(
|
|
258
|
+
` ${style.dim("ETH (gas) ")} ${formatBigDecimal(gasWei, { baseDecimals: 18, displayDecimals: 6 })} ETH`
|
|
259
|
+
);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// src/commands/protocol/decode-revert.ts
|
|
263
|
+
import { Command as Command3 } from "commander";
|
|
264
|
+
var HEX_DATA_RE = /^0x[0-9a-fA-F]+$/;
|
|
265
|
+
var decodeRevertCommand = new Command3("decode-revert").description("Decode raw revert data into (name, args) using the Market + EntryPoint ABIs.").argument("<data>", "raw revert data (0x-prefixed hex)").addHelpText(
|
|
266
|
+
"after",
|
|
267
|
+
`
|
|
268
|
+
Examples:
|
|
269
|
+
$ kash protocol decode-revert 0x4e487b710000000000000000000000000000000000000000000000000000000000000011
|
|
270
|
+
$ kash protocol decode-revert 0x... --json --quiet | jq -r '.name'
|
|
271
|
+
|
|
272
|
+
Notes:
|
|
273
|
+
- Returns \`null\` if the selector doesn't match any known error
|
|
274
|
+
(the data may be raw \`Error(string)\` or vendor-specific).
|
|
275
|
+
- The \`args\` array stringifies bigint values to preserve precision.
|
|
276
|
+
`
|
|
277
|
+
).action(async (data, _opts, cmd) => {
|
|
278
|
+
const globals = readGlobals(cmd);
|
|
279
|
+
if (!HEX_DATA_RE.test(data) || data.length < 10) {
|
|
280
|
+
throw new CliValidationError(
|
|
281
|
+
"<data> must be 0x-prefixed hex with at least a 4-byte selector.",
|
|
282
|
+
`Got "${data}".`,
|
|
283
|
+
"data"
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
try {
|
|
287
|
+
const { decodeMarketRevert } = await import("@kashdao/protocol-sdk");
|
|
288
|
+
const decoded = decodeMarketRevert(data);
|
|
289
|
+
const args = decoded?.args.map((a) => typeof a === "bigint" ? a.toString() : a) ?? null;
|
|
290
|
+
const payload = decoded === null ? null : { name: decoded.name, args };
|
|
291
|
+
if (globals.json) {
|
|
292
|
+
printJson(payload);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (decoded === null) {
|
|
296
|
+
print("");
|
|
297
|
+
print(` ${style.dim("No match \u2014 selector not in Market or EntryPoint ABIs.")}`);
|
|
298
|
+
print(` ${style.dim("Data: ")}${data.slice(0, 10)}${data.length > 10 ? "\u2026" : ""}`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
print("");
|
|
302
|
+
print(` ${style.dim("Error ")} ${style.bold(decoded.name)}`);
|
|
303
|
+
if (args && args.length > 0) {
|
|
304
|
+
print(` ${style.dim("Args ")}`);
|
|
305
|
+
for (const [i, arg] of args.entries()) {
|
|
306
|
+
print(` [${String(i)}] ${String(arg)}`);
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
print(` ${style.dim("Args ")} (none)`);
|
|
310
|
+
}
|
|
311
|
+
} catch (cause) {
|
|
312
|
+
throw toCliError(cause);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// src/commands/protocol/fees.ts
|
|
317
|
+
import { Command as Command4 } from "commander";
|
|
318
|
+
var feesCommand = new Command4("fees").description("EIP-1559 fee estimate for the configured chain (uses eth_feeHistory).").option("--blocks <n>", "number of recent blocks to sample (default 4)").option(
|
|
319
|
+
"--percentile <n>",
|
|
320
|
+
"priority-fee percentile to take from each sampled block (1-99, default 50)"
|
|
321
|
+
).option(
|
|
322
|
+
"--base-multiplier <n>",
|
|
323
|
+
"multiplier applied to predicted next-block base fee (default 2.0; raise for congested chains)"
|
|
324
|
+
).option("--priority-floor-gwei <n>", "floor for maxPriorityFeePerGas in gwei (default 1)").addHelpText(
|
|
325
|
+
"after",
|
|
326
|
+
`
|
|
327
|
+
Examples:
|
|
328
|
+
$ kash protocol fees
|
|
329
|
+
$ kash protocol fees --base-multiplier 3 --json --quiet | jq -r '.maxFeePerGas'
|
|
330
|
+
$ kash protocol fees --blocks 16 --percentile 75 # smoother, more conservative
|
|
331
|
+
`
|
|
332
|
+
).action(async (options, cmd) => {
|
|
333
|
+
const globals = readGlobals(cmd);
|
|
334
|
+
const blocks = parseOptionalPositiveInt(options.blocks, "blocks");
|
|
335
|
+
const percentile = parseOptionalPositiveInt(options.percentile, "percentile");
|
|
336
|
+
if (percentile !== void 0 && (percentile < 1 || percentile > 99)) {
|
|
337
|
+
throw new CliValidationError(
|
|
338
|
+
"--percentile must be between 1 and 99.",
|
|
339
|
+
void 0,
|
|
340
|
+
"percentile"
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
const baseMultiplier = parseOptionalPositiveFloat(options.baseMultiplier, "base-multiplier");
|
|
344
|
+
const priorityFloorGwei = parseOptionalPositiveInt(
|
|
345
|
+
options.priorityFloorGwei,
|
|
346
|
+
"priority-floor-gwei"
|
|
347
|
+
);
|
|
348
|
+
try {
|
|
349
|
+
const resolved = await buildDirectClient({ globals });
|
|
350
|
+
const estimate = await resolved.client.bundler.estimateFees({
|
|
351
|
+
...blocks === void 0 ? {} : { blockCount: blocks },
|
|
352
|
+
...percentile === void 0 ? {} : { rewardPercentile: percentile },
|
|
353
|
+
...baseMultiplier === void 0 ? {} : { baseMultiplier },
|
|
354
|
+
...priorityFloorGwei === void 0 ? {} : { priorityFeeFloorWei: BigInt(priorityFloorGwei) * 10n ** 9n }
|
|
355
|
+
});
|
|
356
|
+
const payload = {
|
|
357
|
+
chainId: resolved.chainId,
|
|
358
|
+
maxFeePerGas: estimate.maxFeePerGas.toString(),
|
|
359
|
+
maxPriorityFeePerGas: estimate.maxPriorityFeePerGas.toString()
|
|
360
|
+
};
|
|
361
|
+
if (globals.json) {
|
|
362
|
+
printJson(payload);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
print("");
|
|
366
|
+
print(` ${style.dim("Chain ")} ${String(resolved.chainId)}`);
|
|
367
|
+
print(
|
|
368
|
+
` ${style.dim("maxFee ")} ${formatGwei(estimate.maxFeePerGas)} gwei ${style.dim(`(${estimate.maxFeePerGas.toString()} wei)`)}`
|
|
369
|
+
);
|
|
370
|
+
print(
|
|
371
|
+
` ${style.dim("maxPrio ")} ${formatGwei(estimate.maxPriorityFeePerGas)} gwei ${style.dim(`(${estimate.maxPriorityFeePerGas.toString()} wei)`)}`
|
|
372
|
+
);
|
|
373
|
+
} catch (cause) {
|
|
374
|
+
throw toCliError(cause);
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// src/commands/protocol/market.ts
|
|
379
|
+
import { Command as Command5 } from "commander";
|
|
380
|
+
var marketCommand = new Command5("market").description("Read on-chain market state (reserves, supplies, weights, derived probabilities).").argument("<address>", "market contract address (0x-prefixed)").addHelpText(
|
|
381
|
+
"after",
|
|
382
|
+
`
|
|
383
|
+
Examples:
|
|
384
|
+
$ kash protocol market 0xabc...
|
|
385
|
+
$ kash protocol market 0xabc... --json --quiet | jq '.outcomes'
|
|
386
|
+
`
|
|
387
|
+
).action(async (address, _opts, cmd) => {
|
|
388
|
+
const globals = readGlobals(cmd);
|
|
389
|
+
validateAddress(address, "address");
|
|
390
|
+
let resolved;
|
|
391
|
+
let state;
|
|
392
|
+
try {
|
|
393
|
+
resolved = await buildDirectClient({ globals });
|
|
394
|
+
state = await resolved.client.markets.state(address);
|
|
395
|
+
} catch (cause) {
|
|
396
|
+
throw toCliError(cause);
|
|
397
|
+
}
|
|
398
|
+
const payload = {
|
|
399
|
+
marketAddress: state.marketAddress,
|
|
400
|
+
chainId: resolved.chainId,
|
|
401
|
+
status: state.status,
|
|
402
|
+
readAt: state.readAt.toString(),
|
|
403
|
+
reserveWad: state.reserveWad.toString(),
|
|
404
|
+
outcomes: state.outcomes.map((o) => ({
|
|
405
|
+
index: o.index,
|
|
406
|
+
outstandingTokensWad: o.outstandingTokensWad.toString(),
|
|
407
|
+
weightWad: o.weightWad.toString(),
|
|
408
|
+
probability: o.probability
|
|
409
|
+
}))
|
|
410
|
+
};
|
|
411
|
+
if (globals.json) {
|
|
412
|
+
printJson(payload);
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
print("");
|
|
416
|
+
print(` ${style.dim("Market ")} ${formatAddress(state.marketAddress, 10, 8)}`);
|
|
417
|
+
print(` ${style.dim("Status ")} ${state.status}`);
|
|
418
|
+
print(` ${style.dim("Outcomes ")} ${String(state.outcomes.length)}`);
|
|
419
|
+
print("");
|
|
420
|
+
for (const outcome of state.outcomes) {
|
|
421
|
+
const pct = (outcome.probability * 100).toFixed(2);
|
|
422
|
+
print(
|
|
423
|
+
` [${String(outcome.index)}] p=${pct}% outstanding=${formatWad(outcome.outstandingTokensWad)} weight=${formatWad(outcome.weightWad)}`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// src/commands/protocol/position.ts
|
|
429
|
+
import { Command as Command6 } from "commander";
|
|
430
|
+
var positionCommand = new Command6("position").description("Read on-chain outcome-token holdings (ERC-1155) for a market.").argument("<market>", "market contract address (0x-prefixed)").argument(
|
|
431
|
+
"[account]",
|
|
432
|
+
"EOA or smart-account address whose holdings to read (default: profile's smartAccount)"
|
|
433
|
+
).addHelpText(
|
|
434
|
+
"after",
|
|
435
|
+
`
|
|
436
|
+
Examples:
|
|
437
|
+
$ kash protocol position 0xMarket...
|
|
438
|
+
$ kash protocol position 0xMarket... 0xOther...
|
|
439
|
+
$ kash protocol position 0xMarket... --json --quiet | jq '.holdings[].balanceWad'
|
|
440
|
+
`
|
|
441
|
+
).action(async (market, account, _opts, cmd) => {
|
|
442
|
+
const globals = readGlobals(cmd);
|
|
443
|
+
const marketAddr = validateAddress(market, "market");
|
|
444
|
+
const accountAddr = validateAddressOptional(account, "account");
|
|
445
|
+
let resolved;
|
|
446
|
+
let position;
|
|
447
|
+
try {
|
|
448
|
+
resolved = await buildDirectClient({ globals });
|
|
449
|
+
const target = accountAddr ?? resolved.smartAccount;
|
|
450
|
+
position = await resolved.client.account.position(target, marketAddr);
|
|
451
|
+
const payload = {
|
|
452
|
+
account: target,
|
|
453
|
+
marketAddress: position.marketAddress,
|
|
454
|
+
chainId: resolved.chainId,
|
|
455
|
+
numOutcomes: position.numOutcomes,
|
|
456
|
+
holdings: position.holdings.map((h) => ({
|
|
457
|
+
outcomeIndex: h.outcomeIndex,
|
|
458
|
+
balanceWad: h.balanceWad.toString()
|
|
459
|
+
}))
|
|
460
|
+
};
|
|
461
|
+
if (globals.json) {
|
|
462
|
+
printJson(payload);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
print("");
|
|
466
|
+
print(` ${style.dim("Account ")} ${formatAddress(target, 10, 8)}`);
|
|
467
|
+
print(` ${style.dim("Market ")} ${formatAddress(position.marketAddress, 10, 8)}`);
|
|
468
|
+
print(` ${style.dim("Outcomes ")} ${String(position.numOutcomes)}`);
|
|
469
|
+
print("");
|
|
470
|
+
for (const h of position.holdings) {
|
|
471
|
+
const balanceLabel = formatWad(h.balanceWad);
|
|
472
|
+
print(
|
|
473
|
+
` [${String(h.outcomeIndex)}] balance=${balanceLabel} ${style.dim(`(${h.balanceWad.toString()} WAD)`)}`
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
} catch (cause) {
|
|
477
|
+
throw toCliError(cause);
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// src/commands/protocol/quote.ts
|
|
482
|
+
import { Command as Command7 } from "commander";
|
|
483
|
+
var quoteCommand = new Command7("quote").description("On-chain price quote for buying or selling an outcome.").argument("<address>", "market contract address (0x-prefixed)").requiredOption("-s, --side <buy|sell>", "trade side: buy or sell").requiredOption("-o, --outcome <index>", "outcome index (0-based)").requiredOption(
|
|
484
|
+
"-a, --amount <decimal>",
|
|
485
|
+
'amount: USDC decimal for buy (e.g. "10"), outcome-token decimal for sell (e.g. "1.5")'
|
|
486
|
+
).addHelpText(
|
|
487
|
+
"after",
|
|
488
|
+
`
|
|
489
|
+
Examples:
|
|
490
|
+
$ kash protocol quote 0xabc... --side buy --outcome 0 --amount 10
|
|
491
|
+
$ kash protocol quote 0xabc... --side sell --outcome 1 --tokens 1.5 --json
|
|
492
|
+
`
|
|
493
|
+
).action(async (address, options, cmd) => {
|
|
494
|
+
const globals = readGlobals(cmd);
|
|
495
|
+
const marketAddress = validateAddress(address, "address");
|
|
496
|
+
const side = parseUppercaseSide(options.side, "side");
|
|
497
|
+
const outcome = parseOutcomeIndex(options.outcome);
|
|
498
|
+
const amount = side === "BUY" ? decimalToAtomicUsdc(options.amount, "amount") : decimalToAtomicWad(options.amount, "amount");
|
|
499
|
+
let resolved;
|
|
500
|
+
let quote;
|
|
501
|
+
try {
|
|
502
|
+
resolved = await buildDirectClient({ globals });
|
|
503
|
+
quote = await resolved.client.markets.quote(marketAddress, {
|
|
504
|
+
side,
|
|
505
|
+
outcome,
|
|
506
|
+
amount
|
|
507
|
+
});
|
|
508
|
+
} catch (cause) {
|
|
509
|
+
throw toCliError(cause);
|
|
510
|
+
}
|
|
511
|
+
const payload = {
|
|
512
|
+
side: quote.side,
|
|
513
|
+
outcomeIndex: quote.outcomeIndex,
|
|
514
|
+
amountIn: quote.amountIn.toString(),
|
|
515
|
+
amountOut: quote.amountOut.toString(),
|
|
516
|
+
reserveAfterWad: quote.reserveAfterWad.toString(),
|
|
517
|
+
pricesAfterWad: quote.pricesAfterWad.map((p) => p.toString())
|
|
518
|
+
};
|
|
519
|
+
if (globals.json) {
|
|
520
|
+
printJson(payload);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
print("");
|
|
524
|
+
print(` ${style.dim("Market ")} ${formatAddress(address, 10, 8)}`);
|
|
525
|
+
print(` ${style.dim("Action ")} ${quote.side} outcome ${String(quote.outcomeIndex)}`);
|
|
526
|
+
if (quote.side === "BUY") {
|
|
527
|
+
print(` ${style.dim("Spend ")} ${formatUsdcAtomic(quote.amountIn)}`);
|
|
528
|
+
print(` ${style.dim("Tokens out")} ${formatWad(quote.amountOut)}`);
|
|
529
|
+
} else {
|
|
530
|
+
print(` ${style.dim("Tokens in ")} ${formatWad(quote.amountIn)}`);
|
|
531
|
+
print(` ${style.dim("USDC out ")} ${formatUsdcAtomic(quote.amountOut)}`);
|
|
532
|
+
}
|
|
533
|
+
print(
|
|
534
|
+
` ${style.dim("Probs after")} [${quote.pricesAfterWad.map((p) => `${(Number(p) / 1e18 * 100).toFixed(2)}%`).join(", ")}]`
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
// src/commands/protocol/smart-account.ts
|
|
539
|
+
import { Command as Command8 } from "commander";
|
|
540
|
+
var computeCommand = new Command8("compute").description("Derive the deterministic smart-account address for an EOA owner.").requiredOption("-o, --owner <address>", "EOA owner address (0x-prefixed)").option("-s, --salt <n>", "optional salt as a non-negative integer (default 0)", "0").addHelpText(
|
|
541
|
+
"after",
|
|
542
|
+
`
|
|
543
|
+
Examples:
|
|
544
|
+
$ kash protocol smart-account compute --owner 0xEoa...
|
|
545
|
+
$ kash protocol smart-account compute --owner 0xEoa... --salt 1
|
|
546
|
+
$ kash protocol smart-account compute --owner 0xEoa... --json --quiet | jq -r '.address'
|
|
547
|
+
`
|
|
548
|
+
).action(async (options, cmd) => {
|
|
549
|
+
const globals = readGlobals(cmd);
|
|
550
|
+
const ownerAddress = validateAddress(options.owner, "owner");
|
|
551
|
+
let salt;
|
|
552
|
+
if (options.salt !== void 0) {
|
|
553
|
+
let parsed;
|
|
554
|
+
try {
|
|
555
|
+
parsed = BigInt(options.salt);
|
|
556
|
+
} catch {
|
|
557
|
+
throw new CliValidationError(
|
|
558
|
+
"--salt must be a non-negative integer (decimal or 0x-hex).",
|
|
559
|
+
`Got "${options.salt}".`,
|
|
560
|
+
"salt"
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
if (parsed < 0n) {
|
|
564
|
+
throw new CliValidationError(
|
|
565
|
+
"--salt must be non-negative.",
|
|
566
|
+
`Got "${options.salt}".`,
|
|
567
|
+
"salt"
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
salt = parsed === 0n ? void 0 : parsed;
|
|
571
|
+
}
|
|
572
|
+
try {
|
|
573
|
+
const resolved = await buildDirectClient({ globals });
|
|
574
|
+
const address = await resolved.client.account.computeAddress(ownerAddress, salt);
|
|
575
|
+
const payload = {
|
|
576
|
+
owner: ownerAddress,
|
|
577
|
+
salt: (salt ?? 0n).toString(),
|
|
578
|
+
chainId: resolved.chainId,
|
|
579
|
+
address
|
|
580
|
+
};
|
|
581
|
+
if (globals.json) {
|
|
582
|
+
printJson(payload);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
print("");
|
|
586
|
+
print(` ${style.dim("Owner ")} ${formatAddress(ownerAddress, 10, 8)}`);
|
|
587
|
+
print(` ${style.dim("Salt ")} ${(salt ?? 0n).toString()}`);
|
|
588
|
+
print(` ${style.dim("Address")} ${address}`);
|
|
589
|
+
} catch (cause) {
|
|
590
|
+
throw toCliError(cause);
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
var isDeployedCommand = new Command8("is-deployed").description("Check whether a smart account has bytecode (i.e. has been deployed on-chain).").argument("[address]", "smart account address (default: profile's smartAccount)").addHelpText(
|
|
594
|
+
"after",
|
|
595
|
+
`
|
|
596
|
+
Examples:
|
|
597
|
+
$ kash protocol smart-account is-deployed
|
|
598
|
+
$ kash protocol smart-account is-deployed 0xSa...
|
|
599
|
+
$ kash protocol smart-account is-deployed --json --quiet | jq -r '.deployed'
|
|
600
|
+
`
|
|
601
|
+
).action(async (address, _opts, cmd) => {
|
|
602
|
+
const globals = readGlobals(cmd);
|
|
603
|
+
const addressOverride = validateAddressOptional(address, "address");
|
|
604
|
+
try {
|
|
605
|
+
const resolved = await buildDirectClient({ globals });
|
|
606
|
+
const target = addressOverride ?? resolved.smartAccount;
|
|
607
|
+
const deployed = await resolved.client.account.isDeployed(target);
|
|
608
|
+
const payload = {
|
|
609
|
+
address: target,
|
|
610
|
+
chainId: resolved.chainId,
|
|
611
|
+
deployed
|
|
612
|
+
};
|
|
613
|
+
if (globals.json) {
|
|
614
|
+
printJson(payload);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
print("");
|
|
618
|
+
print(` ${style.dim("Address ")} ${formatAddress(target, 10, 8)}`);
|
|
619
|
+
print(
|
|
620
|
+
` ${style.dim("Deployed")} ${deployed ? style.success("yes") : style.dim("no \u2014 first UserOp will deploy")}`
|
|
621
|
+
);
|
|
622
|
+
} catch (cause) {
|
|
623
|
+
throw toCliError(cause);
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
var smartAccountCommand = new Command8("smart-account").description("Smart-account address derivation and deployment-status checks.").addCommand(computeCommand).addCommand(isDeployedCommand);
|
|
627
|
+
|
|
628
|
+
// src/commands/protocol/token-id.ts
|
|
629
|
+
import { Command as Command9 } from "commander";
|
|
630
|
+
var tokenIdCommand = new Command9("token-id").description(
|
|
631
|
+
"Compute the ERC-1155 token id for a (marketId, outcomeIndex) pair (offline; no RPC)."
|
|
632
|
+
).requiredOption(
|
|
633
|
+
"-m, --market-id <bigint>",
|
|
634
|
+
"on-chain numeric market id (decimal or 0x-prefixed hex)"
|
|
635
|
+
).requiredOption("-o, --outcome <index>", "outcome index (0-255)").addHelpText(
|
|
636
|
+
"after",
|
|
637
|
+
`
|
|
638
|
+
Examples:
|
|
639
|
+
$ kash protocol token-id --market-id 42 --outcome 0
|
|
640
|
+
$ kash protocol token-id --market-id 0x2a --outcome 1 --json --quiet | jq -r '.tokenId'
|
|
641
|
+
|
|
642
|
+
Notes:
|
|
643
|
+
- The numeric \`marketId\` is the on-chain id read from
|
|
644
|
+
\`IMarket(<addr>).marketId()\`. It's NOT the same as the market
|
|
645
|
+
contract address.
|
|
646
|
+
- The token id encoding is \`(marketId << 8) | outcomeIndex\`,
|
|
647
|
+
matching the on-chain \`OutcomeTokens1155\` contract.
|
|
648
|
+
`
|
|
649
|
+
).action(async (options, cmd) => {
|
|
650
|
+
const globals = readGlobals(cmd);
|
|
651
|
+
let marketId;
|
|
652
|
+
try {
|
|
653
|
+
marketId = BigInt(options.marketId);
|
|
654
|
+
} catch {
|
|
655
|
+
throw new CliValidationError(
|
|
656
|
+
"--market-id must be a non-negative integer (decimal or 0x-hex).",
|
|
657
|
+
`Got "${options.marketId}".`,
|
|
658
|
+
"market-id"
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
if (marketId < 0n) {
|
|
662
|
+
throw new CliValidationError(
|
|
663
|
+
"--market-id must be non-negative.",
|
|
664
|
+
`Got "${options.marketId}".`,
|
|
665
|
+
"market-id"
|
|
666
|
+
);
|
|
667
|
+
}
|
|
668
|
+
const outcomeIndex = Number.parseInt(options.outcome, 10);
|
|
669
|
+
if (!Number.isFinite(outcomeIndex) || outcomeIndex < 0 || outcomeIndex > 255) {
|
|
670
|
+
throw new CliValidationError(
|
|
671
|
+
"--outcome must be an integer between 0 and 255.",
|
|
672
|
+
`Got "${options.outcome}".`,
|
|
673
|
+
"outcome"
|
|
674
|
+
);
|
|
675
|
+
}
|
|
676
|
+
try {
|
|
677
|
+
const { tokenIdFor } = await import("@kashdao/protocol-sdk");
|
|
678
|
+
const tokenId = tokenIdFor(marketId, outcomeIndex);
|
|
679
|
+
const payload = {
|
|
680
|
+
marketId: marketId.toString(),
|
|
681
|
+
outcomeIndex,
|
|
682
|
+
tokenId: tokenId.toString(),
|
|
683
|
+
tokenIdHex: `0x${tokenId.toString(16)}`
|
|
684
|
+
};
|
|
685
|
+
if (globals.json) {
|
|
686
|
+
printJson(payload);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
print("");
|
|
690
|
+
print(` ${style.dim("Market id ")} ${marketId.toString()}`);
|
|
691
|
+
print(` ${style.dim("Outcome ")} ${String(outcomeIndex)}`);
|
|
692
|
+
print(` ${style.dim("Token id ")} ${tokenId.toString()}`);
|
|
693
|
+
print(` ${style.dim(" (hex) ")} 0x${tokenId.toString(16)}`);
|
|
694
|
+
} catch (cause) {
|
|
695
|
+
throw toCliError(cause);
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
|
|
699
|
+
// src/commands/protocol/trade.ts
|
|
700
|
+
import { Command as Command10 } from "commander";
|
|
701
|
+
import ora from "ora";
|
|
702
|
+
|
|
703
|
+
// src/utils/userop-json.ts
|
|
704
|
+
function serializeUserOp(userOp) {
|
|
705
|
+
const out = {};
|
|
706
|
+
for (const [k, v] of Object.entries(userOp)) {
|
|
707
|
+
out[k] = typeof v === "bigint" ? v.toString() : v;
|
|
708
|
+
}
|
|
709
|
+
return out;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// src/commands/protocol/trade.ts
|
|
713
|
+
var buyCommand = new Command10("buy").description("One-shot BUY: prepare \u2192 simulate \u2192 sign \u2192 submit (and wait by default).").argument("<market>", "market contract address (0x-prefixed)").requiredOption("-o, --outcome <index>", "outcome index (0-based)").requiredOption("-a, --amount <usdc>", 'USDC to spend (decimal \u2014 e.g. "10" or "12.50")').option("--slippage-bps <n>", "slippage tolerance in basis points (default 50 = 0.5%)").option("--deadline-sec <n>", "unix-seconds deadline (default now + 5min)").option("--dry-run", "prepare + simulate but DO NOT sign or submit; print the UserOp").option("--no-simulate", "skip the eth_call preflight (faster, riskier)").option("--no-wait", "fire-and-forget: return userOpHash without waiting for inclusion").option("--wait-timeout-ms <n>", "cap on the receipt wait (default 60000)").option("--wait-interval-ms <n>", "receipt poll interval (default 1500)").addHelpText(
|
|
714
|
+
"after",
|
|
715
|
+
`
|
|
716
|
+
Examples:
|
|
717
|
+
$ kash protocol trade buy 0xMarket... -o 0 -a 10
|
|
718
|
+
$ kash protocol trade buy 0xMarket... -o 0 -a 10 --slippage-bps 100 # 1% slippage
|
|
719
|
+
$ kash protocol trade buy 0xMarket... -o 0 -a 10 --dry-run --json
|
|
720
|
+
$ kash protocol trade buy 0xMarket... -o 0 -a 10 --no-wait --json --quiet
|
|
721
|
+
`
|
|
722
|
+
).action(async (market, options, cmd) => {
|
|
723
|
+
await runTrade("buy", market, options, cmd);
|
|
724
|
+
});
|
|
725
|
+
var sellCommand = new Command10("sell").description("One-shot SELL: prepare \u2192 simulate \u2192 sign \u2192 submit (and wait by default).").argument("<market>", "market contract address (0x-prefixed)").requiredOption("-o, --outcome <index>", "outcome index (0-based)").requiredOption("-t, --tokens <amount>", "outcome tokens to sell (decimal \u2014 WAD precision)").option("--slippage-bps <n>", "slippage tolerance in basis points (default 50 = 0.5%)").option("--deadline-sec <n>", "unix-seconds deadline (default now + 5min)").option("--dry-run", "prepare + simulate but DO NOT sign or submit; print the UserOp").option("--no-simulate", "skip the eth_call preflight (faster, riskier)").option("--no-wait", "fire-and-forget: return userOpHash without waiting for inclusion").option("--wait-timeout-ms <n>", "cap on the receipt wait (default 60000)").option("--wait-interval-ms <n>", "receipt poll interval (default 1500)").addHelpText(
|
|
726
|
+
"after",
|
|
727
|
+
`
|
|
728
|
+
Examples:
|
|
729
|
+
$ kash protocol trade sell 0xMarket... -o 0 -t 1.5
|
|
730
|
+
$ kash protocol trade sell 0xMarket... -o 1 -t 0.5 --json --quiet
|
|
731
|
+
`
|
|
732
|
+
).action(async (market, options, cmd) => {
|
|
733
|
+
await runTrade("sell", market, options, cmd);
|
|
734
|
+
});
|
|
735
|
+
var closeCommand = new Command10("close").description("Sell the entire SA balance for an outcome (one-shot prepare \u2192 submit).").argument("<market>", "market contract address (0x-prefixed)").requiredOption("-o, --outcome <index>", "outcome index (0-based)").option("--slippage-bps <n>", "slippage tolerance in basis points (default 50 = 0.5%)").option("--deadline-sec <n>", "unix-seconds deadline (default now + 5min)").option("--dry-run", "prepare + simulate but DO NOT sign or submit; print the UserOp").option("--no-simulate", "skip the eth_call preflight (faster, riskier)").option("--no-wait", "fire-and-forget: return userOpHash without waiting for inclusion").option("--wait-timeout-ms <n>", "cap on the receipt wait (default 60000)").option("--wait-interval-ms <n>", "receipt poll interval (default 1500)").addHelpText(
|
|
736
|
+
"after",
|
|
737
|
+
`
|
|
738
|
+
Examples:
|
|
739
|
+
$ kash protocol trade close 0xMarket... -o 0
|
|
740
|
+
$ kash protocol trade close 0xMarket... -o 1 --slippage-bps 200 --json
|
|
741
|
+
`
|
|
742
|
+
).action(async (market, options, cmd) => {
|
|
743
|
+
await runTrade("close", market, options, cmd);
|
|
744
|
+
});
|
|
745
|
+
var approveCommand = new Command10("approve").description("USDC approval \u2014 required once before the first BUY.").argument("<spender>", "spender contract address (typically a Market) \u2014 0x-prefixed").option(
|
|
746
|
+
"-a, --amount <usdc>",
|
|
747
|
+
"atomic-USDC amount to approve (decimal); default is unlimited (MAX_UINT256)"
|
|
748
|
+
).option("--dry-run", "prepare + simulate but DO NOT sign or submit; print the UserOp").option("--no-simulate", "skip the eth_call preflight (faster, riskier)").option("--no-wait", "fire-and-forget: return userOpHash without waiting for inclusion").option("--wait-timeout-ms <n>", "cap on the receipt wait (default 60000)").option("--wait-interval-ms <n>", "receipt poll interval (default 1500)").addHelpText(
|
|
749
|
+
"after",
|
|
750
|
+
`
|
|
751
|
+
Examples:
|
|
752
|
+
$ kash protocol trade approve 0xMarket... # unlimited approval
|
|
753
|
+
$ kash protocol trade approve 0xMarket... --amount 100 # cap at 100 USDC
|
|
754
|
+
$ kash protocol trade approve 0xMarket... --dry-run --json # preview only
|
|
755
|
+
`
|
|
756
|
+
).action(async (spender, options, cmd) => {
|
|
757
|
+
await runApprove(spender, options, cmd);
|
|
758
|
+
});
|
|
759
|
+
var tradeCommand = new Command10("trade").description("Direct-mode trade execution (UserOp signed locally, submitted via bundler).").addCommand(buyCommand).addCommand(sellCommand).addCommand(closeCommand).addCommand(approveCommand);
|
|
760
|
+
async function runTrade(side, market, options, cmd) {
|
|
761
|
+
const globals = readGlobals(cmd);
|
|
762
|
+
const marketAddress = validateAddress(market, "market");
|
|
763
|
+
const outcome = parseOutcomeIndex(options.outcome);
|
|
764
|
+
const slippageBps = options.slippageBps ? parseSlippageBps(options.slippageBps) : DEFAULT_SLIPPAGE_BPS;
|
|
765
|
+
const deadline = options.deadlineSec ? parseDeadlineSec(options.deadlineSec) : void 0;
|
|
766
|
+
const waitTimeoutMs = options.waitTimeoutMs ? parsePositiveInt(options.waitTimeoutMs, "wait-timeout-ms") : void 0;
|
|
767
|
+
const waitIntervalMs = options.waitIntervalMs ? parsePositiveInt(options.waitIntervalMs, "wait-interval-ms") : void 0;
|
|
768
|
+
let amountUsdc;
|
|
769
|
+
let amountTokens;
|
|
770
|
+
if (side === "buy") {
|
|
771
|
+
const buyOpts = options;
|
|
772
|
+
amountUsdc = decimalToAtomicUsdc(buyOpts.amount, "amount");
|
|
773
|
+
} else if (side === "sell") {
|
|
774
|
+
const sellOpts = options;
|
|
775
|
+
amountTokens = decimalToAtomicWad(sellOpts.tokens, "tokens");
|
|
776
|
+
}
|
|
777
|
+
try {
|
|
778
|
+
const resolved = await buildDirectClient({ globals, requireSigner: true });
|
|
779
|
+
if (options.dryRun === true) {
|
|
780
|
+
const built = await prepareForSide(resolved.client, side, {
|
|
781
|
+
market: marketAddress,
|
|
782
|
+
smartAccount: resolved.smartAccount,
|
|
783
|
+
outcome,
|
|
784
|
+
slippageBps,
|
|
785
|
+
...deadline === void 0 ? {} : { deadline },
|
|
786
|
+
...amountUsdc === void 0 ? {} : { amountUsdc },
|
|
787
|
+
...amountTokens === void 0 ? {} : { amountTokens },
|
|
788
|
+
simulate: options.simulate !== false
|
|
789
|
+
});
|
|
790
|
+
emitDryRun(built, side, market, globals.json);
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const sendOptions = {
|
|
794
|
+
simulate: options.simulate !== false,
|
|
795
|
+
wait: options.wait !== false,
|
|
796
|
+
...waitTimeoutMs === void 0 ? {} : { waitTimeoutMs },
|
|
797
|
+
...waitIntervalMs === void 0 ? {} : { waitIntervalMs }
|
|
798
|
+
};
|
|
799
|
+
const spinner = !globals.json && !isQuiet() ? ora({
|
|
800
|
+
text: `Preparing ${side} on ${formatAddress(market, 8, 6)}\u2026`,
|
|
801
|
+
stream: process.stderr
|
|
802
|
+
}).start() : void 0;
|
|
803
|
+
let result;
|
|
804
|
+
try {
|
|
805
|
+
if (side === "buy") {
|
|
806
|
+
result = await resolved.client.trades.send.buy(
|
|
807
|
+
marketAddress,
|
|
808
|
+
{
|
|
809
|
+
account: resolved.smartAccount,
|
|
810
|
+
outcome,
|
|
811
|
+
amountUsdc,
|
|
812
|
+
maxSlippageBps: slippageBps,
|
|
813
|
+
...deadline === void 0 ? {} : { deadline }
|
|
814
|
+
},
|
|
815
|
+
sendOptions
|
|
816
|
+
);
|
|
817
|
+
} else if (side === "sell") {
|
|
818
|
+
result = await resolved.client.trades.send.sell(
|
|
819
|
+
marketAddress,
|
|
820
|
+
{
|
|
821
|
+
account: resolved.smartAccount,
|
|
822
|
+
outcome,
|
|
823
|
+
amountTokens,
|
|
824
|
+
maxSlippageBps: slippageBps,
|
|
825
|
+
...deadline === void 0 ? {} : { deadline }
|
|
826
|
+
},
|
|
827
|
+
sendOptions
|
|
828
|
+
);
|
|
829
|
+
} else {
|
|
830
|
+
result = await resolved.client.trades.send.closePosition(
|
|
831
|
+
marketAddress,
|
|
832
|
+
{
|
|
833
|
+
account: resolved.smartAccount,
|
|
834
|
+
outcome,
|
|
835
|
+
maxSlippageBps: slippageBps,
|
|
836
|
+
...deadline === void 0 ? {} : { deadline }
|
|
837
|
+
},
|
|
838
|
+
sendOptions
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
} catch (cause) {
|
|
842
|
+
spinner?.fail(`${side} failed.`);
|
|
843
|
+
const partialHash = extractPartialUserOpHash(cause);
|
|
844
|
+
if (partialHash !== void 0) {
|
|
845
|
+
if (globals.json) {
|
|
846
|
+
printJson({ userOpHash: partialHash, waited: false, partial: true });
|
|
847
|
+
} else {
|
|
848
|
+
log.warn(
|
|
849
|
+
`Submit succeeded but wait timed out. UserOp is on the bundler queue \u2014 resume with: kash protocol userop wait ${partialHash}`
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
throw cause;
|
|
854
|
+
}
|
|
855
|
+
spinner?.succeed(`${side} submitted.`);
|
|
856
|
+
emitSendResult(result, side, market, globals.json);
|
|
857
|
+
} catch (cause) {
|
|
858
|
+
throw toCliError(cause);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
async function runApprove(spender, options, cmd) {
|
|
862
|
+
const globals = readGlobals(cmd);
|
|
863
|
+
const spenderAddress = validateAddress(spender, "spender");
|
|
864
|
+
const amount = options.amount ? decimalToAtomicUsdc(options.amount, "amount") : MAX_UINT256;
|
|
865
|
+
const waitTimeoutMs = options.waitTimeoutMs ? parsePositiveInt(options.waitTimeoutMs, "wait-timeout-ms") : void 0;
|
|
866
|
+
const waitIntervalMs = options.waitIntervalMs ? parsePositiveInt(options.waitIntervalMs, "wait-interval-ms") : void 0;
|
|
867
|
+
try {
|
|
868
|
+
const resolved = await buildDirectClient({ globals, requireSigner: true });
|
|
869
|
+
const params = {
|
|
870
|
+
account: resolved.smartAccount,
|
|
871
|
+
spender: spenderAddress,
|
|
872
|
+
amount
|
|
873
|
+
};
|
|
874
|
+
if (options.dryRun === true) {
|
|
875
|
+
const built = await resolved.client.trades.prepareApprove(params, {
|
|
876
|
+
simulate: options.simulate !== false
|
|
877
|
+
});
|
|
878
|
+
emitDryRun(built, "approve", spender, globals.json);
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const sendOptions = {
|
|
882
|
+
simulate: options.simulate !== false,
|
|
883
|
+
wait: options.wait !== false,
|
|
884
|
+
...waitTimeoutMs === void 0 ? {} : { waitTimeoutMs },
|
|
885
|
+
...waitIntervalMs === void 0 ? {} : { waitIntervalMs }
|
|
886
|
+
};
|
|
887
|
+
const spinner = !globals.json && !isQuiet() ? ora({
|
|
888
|
+
text: `Approving ${formatAddress(spender, 8, 6)}\u2026`,
|
|
889
|
+
stream: process.stderr
|
|
890
|
+
}).start() : void 0;
|
|
891
|
+
let result;
|
|
892
|
+
try {
|
|
893
|
+
result = await resolved.client.trades.send.approve(params, sendOptions);
|
|
894
|
+
} catch (cause) {
|
|
895
|
+
spinner?.fail("approve failed.");
|
|
896
|
+
const partialHash = extractPartialUserOpHash(cause);
|
|
897
|
+
if (partialHash !== void 0) {
|
|
898
|
+
if (globals.json) {
|
|
899
|
+
printJson({ userOpHash: partialHash, waited: false, partial: true });
|
|
900
|
+
} else {
|
|
901
|
+
log.warn(
|
|
902
|
+
`Submit succeeded but wait timed out. The approve UserOp is on the bundler queue \u2014 resume with: kash protocol userop wait ${partialHash}`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
throw cause;
|
|
907
|
+
}
|
|
908
|
+
spinner?.succeed("approve submitted.");
|
|
909
|
+
emitSendResult(result, "approve", spender, globals.json);
|
|
910
|
+
} catch (cause) {
|
|
911
|
+
throw toCliError(cause);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
async function prepareForSide(client, side, args) {
|
|
915
|
+
const opts = { simulate: args.simulate };
|
|
916
|
+
const baseParams = {
|
|
917
|
+
account: args.smartAccount,
|
|
918
|
+
outcome: args.outcome,
|
|
919
|
+
maxSlippageBps: args.slippageBps,
|
|
920
|
+
...args.deadline === void 0 ? {} : { deadline: args.deadline }
|
|
921
|
+
};
|
|
922
|
+
if (side === "buy") {
|
|
923
|
+
return await client.trades.prepareBuy(
|
|
924
|
+
args.market,
|
|
925
|
+
{ ...baseParams, amountUsdc: args.amountUsdc },
|
|
926
|
+
opts
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
if (side === "sell") {
|
|
930
|
+
return await client.trades.prepareSell(
|
|
931
|
+
args.market,
|
|
932
|
+
{ ...baseParams, amountTokens: args.amountTokens },
|
|
933
|
+
opts
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
return await client.trades.prepareClosePosition(args.market, baseParams, opts);
|
|
937
|
+
}
|
|
938
|
+
function emitDryRun(built, side, target, json) {
|
|
939
|
+
const userOp = serializeUserOp(built.userOp);
|
|
940
|
+
if (json) {
|
|
941
|
+
printJson({
|
|
942
|
+
side,
|
|
943
|
+
target,
|
|
944
|
+
userOp,
|
|
945
|
+
userOpHash: built.userOpHash,
|
|
946
|
+
typedData: built.typedData,
|
|
947
|
+
dryRun: true
|
|
948
|
+
});
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
print("");
|
|
952
|
+
print(` ${style.bold("Dry run")} \u2014 UserOp prepared but NOT signed/submitted.`);
|
|
953
|
+
print(` ${style.dim("Side ")} ${side}`);
|
|
954
|
+
print(` ${style.dim("Target ")} ${formatAddress(target, 10, 8)}`);
|
|
955
|
+
const senderRaw = userOp["sender"];
|
|
956
|
+
const sender = typeof senderRaw === "string" ? senderRaw : "";
|
|
957
|
+
print(` ${style.dim("Sender ")} ${formatAddress(sender, 10, 8)}`);
|
|
958
|
+
print(` ${style.dim("Nonce ")} ${String(userOp["nonce"])}`);
|
|
959
|
+
print(` ${style.dim("Hash ")} ${built.userOpHash}`);
|
|
960
|
+
print("");
|
|
961
|
+
print(style.dim("Re-run without --dry-run to sign and submit."));
|
|
962
|
+
}
|
|
963
|
+
function emitSendResult(result, side, target, json) {
|
|
964
|
+
const waited = "transactionHash" in result;
|
|
965
|
+
const payload = waited ? {
|
|
966
|
+
side,
|
|
967
|
+
target,
|
|
968
|
+
userOpHash: result.userOpHash,
|
|
969
|
+
transactionHash: result.transactionHash,
|
|
970
|
+
blockNumber: result.blockNumber.toString(),
|
|
971
|
+
success: result.success,
|
|
972
|
+
actualGasUsed: result.gasUsed.toString()
|
|
973
|
+
} : {
|
|
974
|
+
side,
|
|
975
|
+
target,
|
|
976
|
+
userOpHash: result.userOpHash,
|
|
977
|
+
waited: false
|
|
978
|
+
};
|
|
979
|
+
if (json) {
|
|
980
|
+
printJson(payload);
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
print("");
|
|
984
|
+
if (waited) {
|
|
985
|
+
if (result.success) {
|
|
986
|
+
log.success(`${side} included in block ${result.blockNumber.toString()}.`);
|
|
987
|
+
} else {
|
|
988
|
+
log.error(`${side} included but reverted on-chain.`);
|
|
989
|
+
}
|
|
990
|
+
print(` ${style.dim("UserOp ")} ${result.userOpHash}`);
|
|
991
|
+
print(` ${style.dim("Tx ")} ${result.transactionHash}`);
|
|
992
|
+
print(` ${style.dim("Block ")} ${result.blockNumber.toString()}`);
|
|
993
|
+
print(` ${style.dim("Gas used ")} ${result.gasUsed.toString()}`);
|
|
994
|
+
} else {
|
|
995
|
+
log.success(`${side} submitted (fire-and-forget).`);
|
|
996
|
+
print(` ${style.dim("UserOp ")} ${result.userOpHash}`);
|
|
997
|
+
log.info(`Use \`kash protocol userop wait ${result.userOpHash}\` to poll for inclusion.`);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
function extractPartialUserOpHash(cause) {
|
|
1001
|
+
return extractPartialHash(cause, {
|
|
1002
|
+
code: "BUNDLER_RECEIPT_TIMEOUT",
|
|
1003
|
+
contextKey: "userOpHash"
|
|
1004
|
+
});
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// src/commands/protocol/userop.ts
|
|
1008
|
+
import { readFile, writeFile } from "fs/promises";
|
|
1009
|
+
import { Command as Command11 } from "commander";
|
|
1010
|
+
var buildBuyCommand = new Command11("buy").description("Build a fully-populated unsigned BUY UserOp ready for offline signing.").argument("<market>", "market contract address (0x-prefixed)").requiredOption("-o, --outcome <index>", "outcome index (0-based)").requiredOption("-a, --amount <usdc>", "USDC to spend (decimal)").option("--slippage-bps <n>", "slippage tolerance in bps (default 50 = 0.5%)").option("--deadline-sec <n>", "unix-seconds deadline (default now + 5min)").option("--out <path>", "write the UserOp envelope to a file (default stdout)").option("--no-simulate", "skip eth_call preflight in `prepare`").action(
|
|
1011
|
+
async (market, options, cmd) => {
|
|
1012
|
+
await runBuild("buy", market, options, cmd);
|
|
1013
|
+
}
|
|
1014
|
+
);
|
|
1015
|
+
var buildSellCommand = new Command11("sell").description("Build a fully-populated unsigned SELL UserOp ready for offline signing.").argument("<market>", "market contract address (0x-prefixed)").requiredOption("-o, --outcome <index>", "outcome index (0-based)").requiredOption("-t, --tokens <amount>", "outcome tokens to sell (WAD decimal)").option("--slippage-bps <n>", "slippage tolerance in bps (default 50 = 0.5%)").option("--deadline-sec <n>", "unix-seconds deadline (default now + 5min)").option("--out <path>", "write the UserOp envelope to a file (default stdout)").option("--no-simulate", "skip eth_call preflight in `prepare`").action(
|
|
1016
|
+
async (market, options, cmd) => {
|
|
1017
|
+
await runBuild("sell", market, options, cmd);
|
|
1018
|
+
}
|
|
1019
|
+
);
|
|
1020
|
+
var buildCloseCommand = new Command11("close").description("Build a fully-populated unsigned UserOp that closes a position (full balance).").argument("<market>", "market contract address (0x-prefixed)").requiredOption("-o, --outcome <index>", "outcome index (0-based)").option("--slippage-bps <n>", "slippage tolerance in bps (default 50 = 0.5%)").option("--deadline-sec <n>", "unix-seconds deadline (default now + 5min)").option("--out <path>", "write the UserOp envelope to a file (default stdout)").option("--no-simulate", "skip eth_call preflight in `prepare`").action(async (market, options, cmd) => {
|
|
1021
|
+
await runBuild("close", market, options, cmd);
|
|
1022
|
+
});
|
|
1023
|
+
var buildApproveCommand = new Command11("approve").description("Build a fully-populated unsigned approve UserOp ready for offline signing.").argument("<spender>", "spender contract address (typically a Market) \u2014 0x-prefixed").option("-a, --amount <usdc>", "atomic-USDC amount (decimal); default unlimited (MAX_UINT256)").option("--out <path>", "write the UserOp envelope to a file (default stdout)").option("--no-simulate", "skip eth_call preflight in `prepare`").action(
|
|
1024
|
+
async (spender, options, cmd) => {
|
|
1025
|
+
await runBuildApprove(spender, options, cmd);
|
|
1026
|
+
}
|
|
1027
|
+
);
|
|
1028
|
+
var buildCommand = new Command11("build").description("Build (prepare) a fully-populated unsigned UserOp ready for offline signing.").addCommand(buildBuyCommand).addCommand(buildSellCommand).addCommand(buildCloseCommand).addCommand(buildApproveCommand);
|
|
1029
|
+
async function runBuild(side, market, options, cmd) {
|
|
1030
|
+
const globals = readGlobals(cmd);
|
|
1031
|
+
const marketAddress = validateAddress(market, "market");
|
|
1032
|
+
const outcome = parseOutcomeIndex(options.outcome);
|
|
1033
|
+
const slippageBps = options.slippageBps ? parseSlippageBps(options.slippageBps) : DEFAULT_SLIPPAGE_BPS;
|
|
1034
|
+
const deadline = options.deadlineSec ? parseDeadlineSec(options.deadlineSec) : void 0;
|
|
1035
|
+
let amountUsdc;
|
|
1036
|
+
let amountTokens;
|
|
1037
|
+
if (side === "buy") {
|
|
1038
|
+
amountUsdc = decimalToAtomicUsdc(options.amount, "amount");
|
|
1039
|
+
} else if (side === "sell") {
|
|
1040
|
+
amountTokens = decimalToAtomicWad(options.tokens, "tokens");
|
|
1041
|
+
}
|
|
1042
|
+
try {
|
|
1043
|
+
const resolved = await buildDirectClient({ globals });
|
|
1044
|
+
const baseParams = {
|
|
1045
|
+
account: resolved.smartAccount,
|
|
1046
|
+
outcome,
|
|
1047
|
+
maxSlippageBps: slippageBps,
|
|
1048
|
+
...deadline === void 0 ? {} : { deadline }
|
|
1049
|
+
};
|
|
1050
|
+
const opts = { simulate: options.simulate !== false };
|
|
1051
|
+
let built;
|
|
1052
|
+
if (side === "buy") {
|
|
1053
|
+
built = await resolved.client.trades.prepareBuy(
|
|
1054
|
+
marketAddress,
|
|
1055
|
+
{ ...baseParams, amountUsdc },
|
|
1056
|
+
opts
|
|
1057
|
+
);
|
|
1058
|
+
} else if (side === "sell") {
|
|
1059
|
+
built = await resolved.client.trades.prepareSell(
|
|
1060
|
+
marketAddress,
|
|
1061
|
+
{ ...baseParams, amountTokens },
|
|
1062
|
+
opts
|
|
1063
|
+
);
|
|
1064
|
+
} else {
|
|
1065
|
+
built = await resolved.client.trades.prepareClosePosition(marketAddress, baseParams, opts);
|
|
1066
|
+
}
|
|
1067
|
+
await emitBuildResult(built, side, market, options.out, globals.json);
|
|
1068
|
+
} catch (cause) {
|
|
1069
|
+
throw toCliError(cause);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
async function runBuildApprove(spender, options, cmd) {
|
|
1073
|
+
const globals = readGlobals(cmd);
|
|
1074
|
+
const spenderAddress = validateAddress(spender, "spender");
|
|
1075
|
+
const amount = options.amount ? decimalToAtomicUsdc(options.amount, "amount") : MAX_UINT256;
|
|
1076
|
+
try {
|
|
1077
|
+
const resolved = await buildDirectClient({ globals });
|
|
1078
|
+
const built = await resolved.client.trades.prepareApprove(
|
|
1079
|
+
{
|
|
1080
|
+
account: resolved.smartAccount,
|
|
1081
|
+
spender: spenderAddress,
|
|
1082
|
+
amount
|
|
1083
|
+
},
|
|
1084
|
+
{ simulate: options.simulate !== false }
|
|
1085
|
+
);
|
|
1086
|
+
await emitBuildResult(built, "approve", spender, options.out, globals.json);
|
|
1087
|
+
} catch (cause) {
|
|
1088
|
+
throw toCliError(cause);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
var simulateCommand = new Command11("simulate").description("Simulate (eth_call) an UnsignedUserOp from a file or stdin.").argument("[file]", 'path to a UserOp JSON file (omit or pass "-" for stdin)').addHelpText(
|
|
1092
|
+
"after",
|
|
1093
|
+
`
|
|
1094
|
+
Examples:
|
|
1095
|
+
$ kash protocol userop build buy 0xMarket... -o 0 -a 10 --out trade.json
|
|
1096
|
+
$ kash protocol userop simulate trade.json
|
|
1097
|
+
$ cat trade.json | kash protocol userop simulate
|
|
1098
|
+
`
|
|
1099
|
+
).action(async (file, _opts, cmd) => {
|
|
1100
|
+
const globals = readGlobals(cmd);
|
|
1101
|
+
try {
|
|
1102
|
+
const userOp = await readUnsignedUserOp(file);
|
|
1103
|
+
const resolved = await buildDirectClient({ globals });
|
|
1104
|
+
const result = await resolved.client.trades.simulate(userOp);
|
|
1105
|
+
if (globals.json) {
|
|
1106
|
+
printJson(serializeSimulationResult(result));
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
print("");
|
|
1110
|
+
if (result.willSucceed) {
|
|
1111
|
+
log.success("Simulation: would succeed.");
|
|
1112
|
+
if (result.gasEstimate !== void 0) {
|
|
1113
|
+
print(` ${style.dim("Gas estimate ")} ${result.gasEstimate.toString()}`);
|
|
1114
|
+
}
|
|
1115
|
+
} else {
|
|
1116
|
+
log.error("Simulation: would revert.");
|
|
1117
|
+
print(` ${style.dim("Reason ")} ${result.revertReason}`);
|
|
1118
|
+
if (result.decodedError) {
|
|
1119
|
+
print(` ${style.dim("Custom err")} ${result.decodedError.name}`);
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
} catch (cause) {
|
|
1123
|
+
throw toCliError(cause);
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
var submitCommand = new Command11("submit").description("Submit a SignedUserOp from a file or stdin to the configured bundler.").argument("[file]", 'path to a SignedUserOp JSON file (omit or pass "-" for stdin)').option("--skip-staleness-check", "bypass the EIP-191 staleness check (for typed-data signers)").option("--wait", "wait for receipt after submitting").option("--wait-timeout-ms <n>", "cap on the receipt wait (default 60000)").option("--wait-interval-ms <n>", "receipt poll interval (default 1500)").addHelpText(
|
|
1127
|
+
"after",
|
|
1128
|
+
`
|
|
1129
|
+
Examples:
|
|
1130
|
+
$ kash protocol userop submit signed.json --json
|
|
1131
|
+
$ cat signed.json | kash protocol userop submit --wait
|
|
1132
|
+
`
|
|
1133
|
+
).action(async (file, options, cmd) => {
|
|
1134
|
+
const globals = readGlobals(cmd);
|
|
1135
|
+
const waitTimeoutMs = options.waitTimeoutMs ? parsePositiveInt(options.waitTimeoutMs, "wait-timeout-ms") : void 0;
|
|
1136
|
+
const waitIntervalMs = options.waitIntervalMs ? parsePositiveInt(options.waitIntervalMs, "wait-interval-ms") : void 0;
|
|
1137
|
+
try {
|
|
1138
|
+
const signed = await readSignedUserOp(file);
|
|
1139
|
+
const resolved = await buildDirectClient({ globals });
|
|
1140
|
+
const result = await resolved.client.trades.submit(signed, {
|
|
1141
|
+
skipStalenessCheck: options.skipStalenessCheck === true
|
|
1142
|
+
});
|
|
1143
|
+
if (options.wait !== true) {
|
|
1144
|
+
if (globals.json) {
|
|
1145
|
+
printJson({ userOpHash: result.userOpHash, waited: false });
|
|
1146
|
+
return;
|
|
1147
|
+
}
|
|
1148
|
+
log.success("UserOp submitted.");
|
|
1149
|
+
print(` ${style.dim("Hash")} ${result.userOpHash}`);
|
|
1150
|
+
log.info(`Use \`kash protocol userop wait ${result.userOpHash}\` to poll for inclusion.`);
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
const waitOpts = {
|
|
1154
|
+
...waitTimeoutMs === void 0 ? {} : { timeoutMs: waitTimeoutMs },
|
|
1155
|
+
...waitIntervalMs === void 0 ? {} : { intervalMs: waitIntervalMs }
|
|
1156
|
+
};
|
|
1157
|
+
let receipt;
|
|
1158
|
+
try {
|
|
1159
|
+
receipt = await resolved.client.bundler.waitForReceipt(result.userOpHash, waitOpts);
|
|
1160
|
+
} catch (waitCause) {
|
|
1161
|
+
if (globals.json) {
|
|
1162
|
+
printJson({ userOpHash: result.userOpHash, waited: false, partial: true });
|
|
1163
|
+
} else {
|
|
1164
|
+
log.warn(
|
|
1165
|
+
`Submit succeeded but wait failed. UserOp is on the bundler queue \u2014 resume with: kash protocol userop wait ${result.userOpHash}`
|
|
1166
|
+
);
|
|
1167
|
+
}
|
|
1168
|
+
throw toCliError(waitCause);
|
|
1169
|
+
}
|
|
1170
|
+
if (globals.json) {
|
|
1171
|
+
printJson(serializeReceipt(receipt));
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
emitReceiptHuman(receipt);
|
|
1175
|
+
} catch (cause) {
|
|
1176
|
+
throw toCliError(cause);
|
|
1177
|
+
}
|
|
1178
|
+
});
|
|
1179
|
+
var hashCommand = new Command11("hash").description("Recompute the canonical EIP-4337 v0.7 hash for a UnsignedUserOp.").argument("[file]", 'path to a UserOp JSON file (omit or pass "-" for stdin)').addHelpText(
|
|
1180
|
+
"after",
|
|
1181
|
+
`
|
|
1182
|
+
Examples:
|
|
1183
|
+
$ kash protocol userop hash trade.json
|
|
1184
|
+
$ cat trade.json | kash protocol userop hash --json --quiet | jq -r '.userOpHash'
|
|
1185
|
+
|
|
1186
|
+
Notes:
|
|
1187
|
+
- The hash baked into a freshly-built UserOp goes stale after gas
|
|
1188
|
+
fields are populated. Always recompute via this command (or
|
|
1189
|
+
\`hashOf\` in the SDK) before signing.
|
|
1190
|
+
`
|
|
1191
|
+
).action(async (file, _opts, cmd) => {
|
|
1192
|
+
const globals = readGlobals(cmd);
|
|
1193
|
+
try {
|
|
1194
|
+
const userOp = await readUnsignedUserOp(file);
|
|
1195
|
+
const resolved = await buildDirectClient({ globals });
|
|
1196
|
+
const userOpHash = resolved.client.trades.hashOf(userOp);
|
|
1197
|
+
if (globals.json) {
|
|
1198
|
+
printJson({ userOpHash });
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
print(userOpHash);
|
|
1202
|
+
} catch (cause) {
|
|
1203
|
+
throw toCliError(cause);
|
|
1204
|
+
}
|
|
1205
|
+
});
|
|
1206
|
+
var receiptCommand = new Command11("receipt").description("Fetch the bundler receipt for a UserOp hash (null if not yet included).").argument("<hash>", "UserOp hash (0x-prefixed, 32 bytes)").addHelpText(
|
|
1207
|
+
"after",
|
|
1208
|
+
`
|
|
1209
|
+
Examples:
|
|
1210
|
+
$ kash protocol userop receipt 0xabc...
|
|
1211
|
+
$ kash protocol userop receipt 0xabc... --json --quiet | jq -r '.success'
|
|
1212
|
+
`
|
|
1213
|
+
).action(async (hash, _opts, cmd) => {
|
|
1214
|
+
const globals = readGlobals(cmd);
|
|
1215
|
+
if (!HEX_HASH_REGEX.test(hash)) {
|
|
1216
|
+
throw new CliValidationError(
|
|
1217
|
+
"<hash> must be a 0x-prefixed 32-byte hex hash.",
|
|
1218
|
+
`Got "${hash}".`,
|
|
1219
|
+
"hash"
|
|
1220
|
+
);
|
|
1221
|
+
}
|
|
1222
|
+
try {
|
|
1223
|
+
const resolved = await buildDirectClient({ globals });
|
|
1224
|
+
const receipt = await resolved.client.bundler.getReceipt(hash);
|
|
1225
|
+
if (globals.json) {
|
|
1226
|
+
printJson(receipt === null ? null : serializeReceipt(receipt));
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
if (receipt === null) {
|
|
1230
|
+
log.info("Not yet included (receipt is null).");
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
emitReceiptHuman(receipt);
|
|
1234
|
+
} catch (cause) {
|
|
1235
|
+
throw toCliError(cause);
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
var waitCommand = new Command11("wait").description("Wait for a UserOp to be included; polls with exponential backoff.").argument("<hash>", "UserOp hash (0x-prefixed, 32 bytes)").option(
|
|
1239
|
+
"--wait-timeout-ms, --timeout-ms <n>",
|
|
1240
|
+
"total time budget across polls (default 60000) \u2014 distinct from the global --timeout-ms (per-HTTP-request)"
|
|
1241
|
+
).option("--interval-ms <n>", "poll interval (default 1500)").addHelpText(
|
|
1242
|
+
"after",
|
|
1243
|
+
`
|
|
1244
|
+
Examples:
|
|
1245
|
+
$ kash protocol userop wait 0xabc...
|
|
1246
|
+
$ kash protocol userop wait 0xabc... --wait-timeout-ms 300000 # 5min budget
|
|
1247
|
+
`
|
|
1248
|
+
).action(
|
|
1249
|
+
async (hash, options, cmd) => {
|
|
1250
|
+
const globals = readGlobals(cmd);
|
|
1251
|
+
if (!HEX_HASH_REGEX.test(hash)) {
|
|
1252
|
+
throw new CliValidationError(
|
|
1253
|
+
"<hash> must be a 0x-prefixed 32-byte hex hash.",
|
|
1254
|
+
`Got "${hash}".`,
|
|
1255
|
+
"hash"
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
const timeoutMs = options.timeoutMs ? parsePositiveInt(options.timeoutMs, "timeout-ms") : void 0;
|
|
1259
|
+
const intervalMs = options.intervalMs ? parsePositiveInt(options.intervalMs, "interval-ms") : void 0;
|
|
1260
|
+
try {
|
|
1261
|
+
const resolved = await buildDirectClient({ globals });
|
|
1262
|
+
const waitOpts = {
|
|
1263
|
+
...timeoutMs === void 0 ? {} : { timeoutMs },
|
|
1264
|
+
...intervalMs === void 0 ? {} : { intervalMs }
|
|
1265
|
+
};
|
|
1266
|
+
const receipt = await resolved.client.bundler.waitForReceipt(
|
|
1267
|
+
hash,
|
|
1268
|
+
waitOpts
|
|
1269
|
+
);
|
|
1270
|
+
if (globals.json) {
|
|
1271
|
+
printJson(serializeReceipt(receipt));
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
emitReceiptHuman(receipt);
|
|
1275
|
+
} catch (cause) {
|
|
1276
|
+
throw toCliError(cause);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
);
|
|
1280
|
+
var useropCommand = new Command11("userop").description("Granular UserOp lifecycle: build, simulate, submit, hash, receipt, wait.").addCommand(buildCommand).addCommand(simulateCommand).addCommand(submitCommand).addCommand(hashCommand).addCommand(receiptCommand).addCommand(waitCommand);
|
|
1281
|
+
async function emitBuildResult(built, side, target, outPath, json) {
|
|
1282
|
+
const payload = {
|
|
1283
|
+
side,
|
|
1284
|
+
target,
|
|
1285
|
+
userOp: serializeUserOp(built.userOp),
|
|
1286
|
+
userOpHash: built.userOpHash,
|
|
1287
|
+
typedData: built.typedData
|
|
1288
|
+
};
|
|
1289
|
+
const serialized = JSON.stringify(payload, null, 2);
|
|
1290
|
+
if (outPath) {
|
|
1291
|
+
try {
|
|
1292
|
+
await writeFile(outPath, `${serialized}
|
|
1293
|
+
`, { encoding: "utf8", mode: 384 });
|
|
1294
|
+
} catch (cause) {
|
|
1295
|
+
throw new CliError(`Failed to write ${outPath}.`, {
|
|
1296
|
+
code: "CONFIGURATION",
|
|
1297
|
+
recoverable: true,
|
|
1298
|
+
suggestion: "Verify the path is writable and the parent directory exists.",
|
|
1299
|
+
cause
|
|
1300
|
+
});
|
|
1301
|
+
}
|
|
1302
|
+
if (!json) {
|
|
1303
|
+
log.success(`Wrote ${outPath} (mode 0600).`);
|
|
1304
|
+
print(` ${style.dim("Side ")} ${side}`);
|
|
1305
|
+
print(` ${style.dim("Target")} ${formatAddress(target, 10, 8)}`);
|
|
1306
|
+
print(` ${style.dim("Hash ")} ${built.userOpHash}`);
|
|
1307
|
+
}
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
if (json) {
|
|
1311
|
+
printJson(payload);
|
|
1312
|
+
return;
|
|
1313
|
+
}
|
|
1314
|
+
log.success(`Built ${side} UserOp.`);
|
|
1315
|
+
log.detail("Hash", built.userOpHash);
|
|
1316
|
+
print(serialized);
|
|
1317
|
+
}
|
|
1318
|
+
async function readUnsignedUserOp(file) {
|
|
1319
|
+
const raw = await readSource(file);
|
|
1320
|
+
const parsed = parseEnvelope(raw);
|
|
1321
|
+
return deserializeUserOp(parsed.userOp);
|
|
1322
|
+
}
|
|
1323
|
+
var SIGNED_USEROP_SIG_REGEX = /^0x([0-9a-fA-F]{2})+$/;
|
|
1324
|
+
async function readSignedUserOp(file) {
|
|
1325
|
+
const raw = await readSource(file);
|
|
1326
|
+
const parsed = parseEnvelope(raw);
|
|
1327
|
+
const userOp = deserializeUserOp(parsed.userOp);
|
|
1328
|
+
const signature = userOp.signature;
|
|
1329
|
+
if (typeof signature !== "string" || signature === "0x") {
|
|
1330
|
+
throw new CliValidationError(
|
|
1331
|
+
"UserOp envelope is missing a non-empty `signature` field.",
|
|
1332
|
+
"Sign the userOp externally and write the resulting signature into the `userOp.signature` field."
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
1335
|
+
if (!SIGNED_USEROP_SIG_REGEX.test(signature)) {
|
|
1336
|
+
throw new CliValidationError(
|
|
1337
|
+
"UserOp `signature` must be a 0x-prefixed hex string with even-length payload.",
|
|
1338
|
+
`Got ${String(signature.length)} characters. Expected at least 132 (0x + 130 hex chars for a 65-byte ECDSA signature).`
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
if (signature.length < 132) {
|
|
1342
|
+
throw new CliValidationError(
|
|
1343
|
+
"UserOp `signature` is too short to be a valid ECDSA signature.",
|
|
1344
|
+
`Got ${String(signature.length)} hex chars; canonical ECDSA signatures are 132 (0x + 130). Did the signer truncate output?`
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
return userOp;
|
|
1348
|
+
}
|
|
1349
|
+
async function readSource(file) {
|
|
1350
|
+
if (file !== void 0 && file !== "-") {
|
|
1351
|
+
try {
|
|
1352
|
+
return await readFile(file, "utf8");
|
|
1353
|
+
} catch (cause) {
|
|
1354
|
+
throw new CliError(`Failed to read ${file}.`, {
|
|
1355
|
+
code: "CONFIGURATION",
|
|
1356
|
+
recoverable: true,
|
|
1357
|
+
suggestion: "Verify the path exists and is readable.",
|
|
1358
|
+
cause
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
const chunks = [];
|
|
1363
|
+
for await (const chunk of process.stdin) {
|
|
1364
|
+
if (typeof chunk === "string") chunks.push(Buffer.from(chunk));
|
|
1365
|
+
else if (chunk instanceof Buffer) chunks.push(chunk);
|
|
1366
|
+
else chunks.push(Buffer.from(chunk));
|
|
1367
|
+
}
|
|
1368
|
+
if (chunks.length === 0) {
|
|
1369
|
+
throw new CliValidationError(
|
|
1370
|
+
"No input received on stdin.",
|
|
1371
|
+
"Pass a file path or pipe a UserOp JSON envelope into stdin."
|
|
1372
|
+
);
|
|
1373
|
+
}
|
|
1374
|
+
const { stripBom } = await import("./stdin-YW2CEQXU.js");
|
|
1375
|
+
return stripBom(Buffer.concat(chunks).toString("utf8"));
|
|
1376
|
+
}
|
|
1377
|
+
function parseEnvelope(raw) {
|
|
1378
|
+
let parsed;
|
|
1379
|
+
try {
|
|
1380
|
+
parsed = JSON.parse(raw);
|
|
1381
|
+
} catch {
|
|
1382
|
+
throw new CliValidationError(
|
|
1383
|
+
"Input is not valid JSON.",
|
|
1384
|
+
"Expected the JSON output of `kash protocol userop build`."
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.userOp !== "object" || parsed.userOp === null) {
|
|
1388
|
+
throw new CliValidationError(
|
|
1389
|
+
"Input does not contain a `userOp` object.",
|
|
1390
|
+
'The envelope must look like { userOp: {...}, userOpHash: "0x\u2026", typedData: {...} }.'
|
|
1391
|
+
);
|
|
1392
|
+
}
|
|
1393
|
+
return parsed;
|
|
1394
|
+
}
|
|
1395
|
+
function deserializeUserOp(input) {
|
|
1396
|
+
const BIGINT_FIELDS = /* @__PURE__ */ new Set([
|
|
1397
|
+
"nonce",
|
|
1398
|
+
"callGasLimit",
|
|
1399
|
+
"verificationGasLimit",
|
|
1400
|
+
"preVerificationGas",
|
|
1401
|
+
"maxFeePerGas",
|
|
1402
|
+
"maxPriorityFeePerGas",
|
|
1403
|
+
"paymasterVerificationGasLimit",
|
|
1404
|
+
"paymasterPostOpGasLimit"
|
|
1405
|
+
]);
|
|
1406
|
+
const out = {};
|
|
1407
|
+
for (const [k, v] of Object.entries(input)) {
|
|
1408
|
+
if (BIGINT_FIELDS.has(k) && typeof v === "string") {
|
|
1409
|
+
try {
|
|
1410
|
+
out[k] = BigInt(v);
|
|
1411
|
+
} catch {
|
|
1412
|
+
throw new CliValidationError(
|
|
1413
|
+
`UserOp field "${k}" is not a valid bigint string.`,
|
|
1414
|
+
`Got ${typeof v === "string" ? `"${v}"` : String(v)}.`
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
} else {
|
|
1418
|
+
out[k] = v;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
return out;
|
|
1422
|
+
}
|
|
1423
|
+
function serializeReceipt(receipt) {
|
|
1424
|
+
return {
|
|
1425
|
+
userOpHash: receipt.userOpHash,
|
|
1426
|
+
sender: receipt.sender,
|
|
1427
|
+
nonce: receipt.nonce,
|
|
1428
|
+
success: receipt.success,
|
|
1429
|
+
actualGasCost: receipt.actualGasCost,
|
|
1430
|
+
actualGasUsed: receipt.actualGasUsed,
|
|
1431
|
+
receipt: {
|
|
1432
|
+
transactionHash: receipt.receipt.transactionHash,
|
|
1433
|
+
blockNumber: receipt.receipt.blockNumber,
|
|
1434
|
+
status: receipt.receipt.status
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
function emitReceiptHuman(receipt) {
|
|
1439
|
+
print("");
|
|
1440
|
+
if (receipt.success) {
|
|
1441
|
+
log.success("UserOp included successfully.");
|
|
1442
|
+
} else {
|
|
1443
|
+
log.error("UserOp included but reverted on-chain.");
|
|
1444
|
+
}
|
|
1445
|
+
print(` ${style.dim("UserOp ")} ${receipt.userOpHash}`);
|
|
1446
|
+
print(` ${style.dim("Tx ")} ${receipt.receipt.transactionHash}`);
|
|
1447
|
+
print(` ${style.dim("Block ")} ${receipt.receipt.blockNumber}`);
|
|
1448
|
+
print(` ${style.dim("Gas ")} ${receipt.actualGasUsed}`);
|
|
1449
|
+
}
|
|
1450
|
+
function serializeSimulationResult(result) {
|
|
1451
|
+
if (result.willSucceed) {
|
|
1452
|
+
return {
|
|
1453
|
+
willSucceed: true,
|
|
1454
|
+
...result.gasEstimate === void 0 ? {} : { gasEstimate: result.gasEstimate.toString() }
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
return {
|
|
1458
|
+
willSucceed: false,
|
|
1459
|
+
revertReason: result.revertReason,
|
|
1460
|
+
...result.decodedError === void 0 ? {} : {
|
|
1461
|
+
decodedError: {
|
|
1462
|
+
name: result.decodedError.name,
|
|
1463
|
+
args: result.decodedError.args.map((a) => typeof a === "bigint" ? a.toString() : a)
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// src/commands/protocol/watch.ts
|
|
1470
|
+
import { Command as Command12 } from "commander";
|
|
1471
|
+
var watchCommand = new Command12("watch").description("Subscribe to on-chain trade events for a market (NDJSON stream).").argument("<market>", "market contract address (0x-prefixed)").option("--max-events <n>", "exit cleanly after observing N events").option("--timeout-ms <n>", "exit cleanly after this wall-clock budget (ms)").addHelpText(
|
|
1472
|
+
"after",
|
|
1473
|
+
`
|
|
1474
|
+
Examples:
|
|
1475
|
+
$ kash protocol watch 0xMarket... --json
|
|
1476
|
+
$ kash protocol watch 0xMarket... --json --quiet | jq -r '.transactionHash'
|
|
1477
|
+
$ kash protocol watch 0xMarket... --max-events 100 --json
|
|
1478
|
+
|
|
1479
|
+
Notes:
|
|
1480
|
+
- Best-effort: on RPC reconnect, missed events are NOT replayed. For
|
|
1481
|
+
gap-free history, use \`kash markets predictions <id>\` (indexer-backed).
|
|
1482
|
+
- Press Ctrl-C to terminate cleanly. The subscription unsubscribes
|
|
1483
|
+
before the process exits.
|
|
1484
|
+
`
|
|
1485
|
+
).action(async (market, options, cmd) => {
|
|
1486
|
+
const globals = readGlobals(cmd);
|
|
1487
|
+
const marketAddress = validateAddress(market, "market");
|
|
1488
|
+
const maxEvents = options.maxEvents ? parsePositiveInt(options.maxEvents, "max-events") : void 0;
|
|
1489
|
+
const timeoutMs = options.timeoutMs ? parsePositiveInt(options.timeoutMs, "timeout-ms") : void 0;
|
|
1490
|
+
try {
|
|
1491
|
+
const resolved = await buildDirectClient({ globals });
|
|
1492
|
+
let resolveDone;
|
|
1493
|
+
let count = 0;
|
|
1494
|
+
const done = new Promise((resolve) => {
|
|
1495
|
+
resolveDone = resolve;
|
|
1496
|
+
});
|
|
1497
|
+
const subscriptionHolder = { current: void 0 };
|
|
1498
|
+
let teardownTriggered = false;
|
|
1499
|
+
const teardown = (reason2) => {
|
|
1500
|
+
if (teardownTriggered) return;
|
|
1501
|
+
teardownTriggered = true;
|
|
1502
|
+
try {
|
|
1503
|
+
subscriptionHolder.current?.unsubscribe();
|
|
1504
|
+
} catch {
|
|
1505
|
+
}
|
|
1506
|
+
resolveDone?.(reason2);
|
|
1507
|
+
};
|
|
1508
|
+
subscriptionHolder.current = resolved.client.markets.watch(marketAddress, {
|
|
1509
|
+
onEvent: (event) => {
|
|
1510
|
+
if (globals.json) {
|
|
1511
|
+
writeNdjson(event);
|
|
1512
|
+
} else {
|
|
1513
|
+
print(formatEventHuman(event));
|
|
1514
|
+
}
|
|
1515
|
+
count += 1;
|
|
1516
|
+
if (maxEvents !== void 0 && count >= maxEvents) {
|
|
1517
|
+
teardown(`reached --max-events ${String(maxEvents)}`);
|
|
1518
|
+
}
|
|
1519
|
+
},
|
|
1520
|
+
onReconnect: () => {
|
|
1521
|
+
if (!globals.json) {
|
|
1522
|
+
log.warn("Watcher reconnecting \u2014 missed events will NOT be replayed.");
|
|
1523
|
+
}
|
|
1524
|
+
},
|
|
1525
|
+
onError: (err) => {
|
|
1526
|
+
if (!globals.json) {
|
|
1527
|
+
log.warn(`Watcher error: ${err.message}`);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
const onSigint = () => teardown("SIGINT");
|
|
1532
|
+
const onSigterm = () => teardown("SIGTERM");
|
|
1533
|
+
process.on("SIGINT", onSigint);
|
|
1534
|
+
process.on("SIGTERM", onSigterm);
|
|
1535
|
+
const timeoutHandle = timeoutMs !== void 0 ? setTimeout(() => teardown(`reached --timeout-ms ${String(timeoutMs)}`), timeoutMs) : void 0;
|
|
1536
|
+
timeoutHandle?.unref();
|
|
1537
|
+
if (!globals.json) {
|
|
1538
|
+
log.info(`Watching market ${marketAddress}\u2026`);
|
|
1539
|
+
log.detail("Stop", "Ctrl-C");
|
|
1540
|
+
if (maxEvents !== void 0) log.detail("Max events", String(maxEvents));
|
|
1541
|
+
if (timeoutMs !== void 0) log.detail("Timeout (ms)", String(timeoutMs));
|
|
1542
|
+
}
|
|
1543
|
+
const reason = await done;
|
|
1544
|
+
process.off("SIGINT", onSigint);
|
|
1545
|
+
process.off("SIGTERM", onSigterm);
|
|
1546
|
+
if (timeoutHandle !== void 0) clearTimeout(timeoutHandle);
|
|
1547
|
+
if (!globals.json) {
|
|
1548
|
+
log.info(`Watcher stopped (${reason}).`);
|
|
1549
|
+
}
|
|
1550
|
+
} catch (cause) {
|
|
1551
|
+
throw toCliError(cause);
|
|
1552
|
+
}
|
|
1553
|
+
});
|
|
1554
|
+
function formatEventHuman(event) {
|
|
1555
|
+
const time = (/* @__PURE__ */ new Date()).toISOString().slice(11, 19);
|
|
1556
|
+
if (event.type === "TRADE") {
|
|
1557
|
+
return `${style.dim(time)} ${style.bold("TRADE")} side=${event.side} out=${String(event.outcome)} usdc=${event.assetsUsdc.toString()} tokens=${event.tokensWad.toString()} tx=${event.transactionHash.slice(0, 10)}\u2026`;
|
|
1558
|
+
}
|
|
1559
|
+
if (event.type === "RESOLVED") {
|
|
1560
|
+
return `${style.dim(time)} ${style.bold("RESOLVED")} winner=${String(event.winningOutcome)} tx=${event.transactionHash.slice(0, 10)}\u2026`;
|
|
1561
|
+
}
|
|
1562
|
+
return `${style.dim(time)} ${style.bold("FROZEN")} observedAt=${event.observedAt.toString()} tx=${event.transactionHash.slice(0, 10)}\u2026`;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
// src/commands/protocol/index.ts
|
|
1566
|
+
var protocolCommand = new Command13("protocol").description(
|
|
1567
|
+
"Self-orchestrated direct-to-chain operations (uses @kashdao/protocol-sdk). Like the default Kash-orchestrated mode, this path is non-custodial \u2014 the difference is who builds and submits trades."
|
|
1568
|
+
).addCommand(balanceCommand).addCommand(marketCommand).addCommand(quoteCommand).addCommand(positionCommand).addCommand(allowanceCommand).addCommand(smartAccountCommand).addCommand(feesCommand).addCommand(tokenIdCommand).addCommand(decodeRevertCommand).addCommand(tradeCommand).addCommand(useropCommand).addCommand(watchCommand);
|
|
1569
|
+
export {
|
|
1570
|
+
protocolCommand
|
|
1571
|
+
};
|
|
1572
|
+
//# sourceMappingURL=protocol-XEQXF2GW.js.map
|