@aomi-labs/client 0.1.17 → 0.1.19

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/README.md CHANGED
@@ -307,7 +307,7 @@ All config can be passed as flags (which take priority over env vars):
307
307
 
308
308
  | Flag | Env Variable | Default | Description |
309
309
  | ----------------------- | ----------------- | ---------------------- | -------------------------------------------- |
310
- | `--backend-url` | `AOMI_BASE_URL` | `https://api.aomi.dev` | Backend URL |
310
+ | `--backend-url` | `AOMI_BACKEND_URL` | `https://api.aomi.dev` | Backend URL |
311
311
  | `--api-key` | `AOMI_API_KEY` | — | API key for non-default apps |
312
312
  | `--app` | `AOMI_APP` | `default` | App |
313
313
  | `--model` | `AOMI_MODEL` | — | Model rig to apply before chat |
package/dist/cli.js CHANGED
@@ -22,7 +22,7 @@ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
22
22
  // package.json
23
23
  var package_default = {
24
24
  name: "@aomi-labs/client",
25
- version: "0.1.17",
25
+ version: "0.1.19",
26
26
  description: "Platform-agnostic TypeScript client for the Aomi backend API",
27
27
  type: "module",
28
28
  main: "./dist/index.cjs",
@@ -53,9 +53,10 @@ var package_default = {
53
53
  "clean:dist": "rm -rf dist"
54
54
  },
55
55
  dependencies: {
56
- "@getpara/aa-alchemy": "2.18.0",
57
- "@getpara/aa-pimlico": "2.18.0",
58
- viem: "^2.40.3"
56
+ "@alchemy/wallet-apis": "5.0.0-beta.22",
57
+ "@getpara/aa-alchemy": "2.21.0",
58
+ "@getpara/aa-pimlico": "2.21.0",
59
+ viem: "^2.47.11"
59
60
  }
60
61
  };
61
62
 
@@ -68,17 +69,17 @@ var CliExit = class extends Error {
68
69
  };
69
70
  function fatal(message) {
70
71
  const RED = "\x1B[31m";
71
- const DIM3 = "\x1B[2m";
72
- const RESET3 = "\x1B[0m";
72
+ const DIM2 = "\x1B[2m";
73
+ const RESET2 = "\x1B[0m";
73
74
  const lines = message.split("\n");
74
75
  const [headline, ...details] = lines;
75
- console.error(`${RED}\u274C ${headline}${RESET3}`);
76
+ console.error(`${RED}\u274C ${headline}${RESET2}`);
76
77
  for (const detail of details) {
77
78
  if (!detail.trim()) {
78
79
  console.error("");
79
80
  continue;
80
81
  }
81
- console.error(`${DIM3}${detail}${RESET3}`);
82
+ console.error(`${DIM2}${detail}${RESET2}`);
82
83
  }
83
84
  throw new CliExit(1);
84
85
  }
@@ -202,7 +203,7 @@ function getConfig(parsed) {
202
203
  fatal("`--aa-provider` and `--aa-mode` cannot be used with `--eoa`.");
203
204
  }
204
205
  return {
205
- baseUrl: (_d = (_c = parsed.flags["backend-url"]) != null ? _c : process.env.AOMI_BASE_URL) != null ? _d : "https://api.aomi.dev",
206
+ baseUrl: (_d = (_c = parsed.flags["backend-url"]) != null ? _c : process.env.AOMI_BACKEND_URL) != null ? _d : "https://api.aomi.dev",
206
207
  apiKey: (_e = parsed.flags["api-key"]) != null ? _e : process.env.AOMI_API_KEY,
207
208
  app: (_g = (_f = parsed.flags["app"]) != null ? _f : process.env.AOMI_APP) != null ? _g : "default",
208
209
  model: (_h = parsed.flags["model"]) != null ? _h : process.env.AOMI_MODEL,
@@ -1157,6 +1158,39 @@ var AomiClient = class {
1157
1158
  }
1158
1159
  return postState(this.baseUrl, "/api/control/model", payload, sessionId, apiKey);
1159
1160
  }
1161
+ // ===========================================================================
1162
+ // Batch Simulation
1163
+ // ===========================================================================
1164
+ /**
1165
+ * Simulate transactions as an atomic batch.
1166
+ * Each tx sees state changes from previous txs (e.g., approve → swap).
1167
+ * Sends full tx payloads — the backend does not look up by ID.
1168
+ */
1169
+ async simulateBatch(sessionId, transactions, options) {
1170
+ const url = joinApiPath(this.baseUrl, "/api/simulate");
1171
+ const headers = new Headers(
1172
+ withSessionHeader(sessionId, { "Content-Type": "application/json" })
1173
+ );
1174
+ if (this.apiKey) {
1175
+ headers.set(API_KEY_HEADER, this.apiKey);
1176
+ }
1177
+ const payload = {
1178
+ transactions,
1179
+ from: options == null ? void 0 : options.from,
1180
+ chain_id: options == null ? void 0 : options.chainId
1181
+ };
1182
+ const response = await fetch(url, {
1183
+ method: "POST",
1184
+ headers,
1185
+ body: JSON.stringify(payload)
1186
+ });
1187
+ if (!response.ok) {
1188
+ const body = await response.text().catch(() => "");
1189
+ throw new Error(`HTTP ${response.status}: ${response.statusText}${body ? `
1190
+ ${body}` : ""}`);
1191
+ }
1192
+ return await response.json();
1193
+ }
1160
1194
  };
1161
1195
 
1162
1196
  // src/types.ts
@@ -2940,6 +2974,92 @@ async function sessionCommand(runtime) {
2940
2974
  );
2941
2975
  }
2942
2976
 
2977
+ // src/cli/commands/simulate.ts
2978
+ function requirePendingTx(state, txId) {
2979
+ var _a3;
2980
+ const pendingTx = ((_a3 = state.pendingTxs) != null ? _a3 : []).find((tx) => tx.id === txId);
2981
+ if (!pendingTx) {
2982
+ fatal(
2983
+ `No pending transaction with id "${txId}".
2984
+ Run \`aomi tx\` to see available IDs.`
2985
+ );
2986
+ }
2987
+ return pendingTx;
2988
+ }
2989
+ async function simulateCommand(runtime) {
2990
+ var _a3, _b, _c;
2991
+ const state = readState();
2992
+ if (!state) {
2993
+ fatal("No active session. Run `aomi chat` first.");
2994
+ }
2995
+ const txIds = runtime.parsed.positional;
2996
+ if (txIds.length === 0) {
2997
+ fatal("Usage: aomi simulate <tx-id> [<tx-id> ...]\nRun `aomi tx` to see available IDs.");
2998
+ }
2999
+ const pendingTxs = txIds.map((txId) => requirePendingTx(state, txId));
3000
+ console.log(
3001
+ `${DIM}Simulating ${txIds.length} transaction(s) as atomic batch...${RESET}`
3002
+ );
3003
+ const client = new AomiClient({
3004
+ baseUrl: state.baseUrl,
3005
+ apiKey: state.apiKey
3006
+ });
3007
+ const transactions = pendingTxs.map((tx) => {
3008
+ var _a4;
3009
+ return {
3010
+ to: tx.to,
3011
+ value: tx.value,
3012
+ data: tx.data,
3013
+ label: (_a4 = tx.description) != null ? _a4 : tx.id
3014
+ };
3015
+ });
3016
+ const response = await client.simulateBatch(
3017
+ state.sessionId,
3018
+ transactions,
3019
+ {
3020
+ from: (_a3 = state.publicKey) != null ? _a3 : void 0,
3021
+ chainId: (_b = state.chainId) != null ? _b : void 0
3022
+ }
3023
+ );
3024
+ const { result } = response;
3025
+ const modeLabel = result.stateful ? "stateful (Anvil snapshot)" : "stateless (independent eth_call)";
3026
+ console.log(`
3027
+ Batch simulation (${modeLabel}):`);
3028
+ console.log(`From: ${result.from} | Network: ${result.network}
3029
+ `);
3030
+ for (const step of result.steps) {
3031
+ const icon = step.success ? `${GREEN}\u2713${RESET}` : `\x1B[31m\u2717${RESET}`;
3032
+ const label = step.label || `Step ${step.step}`;
3033
+ const gasInfo = step.gas_used ? ` | gas: ${step.gas_used.toLocaleString()}` : "";
3034
+ console.log(` ${icon} ${step.step}. ${label}`);
3035
+ console.log(` ${DIM}to: ${step.tx.to} | value: ${step.tx.value_eth} ETH${gasInfo}${RESET}`);
3036
+ if (!step.success && step.revert_reason) {
3037
+ console.log(` \x1B[31mRevert: ${step.revert_reason}${RESET}`);
3038
+ }
3039
+ }
3040
+ if (result.total_gas) {
3041
+ console.log(`
3042
+ ${DIM}Total gas: ${result.total_gas.toLocaleString()}${RESET}`);
3043
+ }
3044
+ if (result.fee) {
3045
+ const feeEth = (Number(result.fee.amount_wei) / 1e18).toFixed(6);
3046
+ console.log(
3047
+ `Service fee: ${feeEth} ETH \u2192 ${result.fee.recipient}`
3048
+ );
3049
+ }
3050
+ console.log();
3051
+ if (result.batch_success) {
3052
+ console.log(
3053
+ `${GREEN}All steps passed.${RESET} Run \`aomi sign ${txIds.join(" ")}\` to execute.`
3054
+ );
3055
+ } else {
3056
+ const failed = result.steps.find((s) => !s.success);
3057
+ console.log(
3058
+ `\x1B[31mBatch failed at step ${(_c = failed == null ? void 0 : failed.step) != null ? _c : "?"}.${RESET} Fix the issue and re-queue, or run \`aomi sign\` on the successful prefix.`
3059
+ );
3060
+ }
3061
+ }
3062
+
2943
3063
  // src/cli/commands/wallet.ts
2944
3064
  import { createWalletClient, http } from "viem";
2945
3065
  import { privateKeyToAccount as privateKeyToAccount2 } from "viem/accounts";
@@ -3277,6 +3397,25 @@ async function createAlchemyAAState(options) {
3277
3397
  if (ownerParams.kind === "unsupported_adapter") {
3278
3398
  return getUnsupportedAdapterState(plan, ownerParams.adapter);
3279
3399
  }
3400
+ if (owner.kind === "direct") {
3401
+ try {
3402
+ return await createAlchemyWalletApisState({
3403
+ plan,
3404
+ chain,
3405
+ privateKey: owner.privateKey,
3406
+ apiKey,
3407
+ gasPolicyId,
3408
+ mode: plan.mode
3409
+ });
3410
+ } catch (error) {
3411
+ return {
3412
+ plan,
3413
+ AA: null,
3414
+ isPending: false,
3415
+ error: error instanceof Error ? error : new Error(String(error))
3416
+ };
3417
+ }
3418
+ }
3280
3419
  try {
3281
3420
  const smartAccount = await createAlchemySmartAccount(__spreadProps(__spreadValues({}, ownerParams.ownerParams), {
3282
3421
  apiKey,
@@ -3308,6 +3447,46 @@ async function createAlchemyAAState(options) {
3308
3447
  };
3309
3448
  }
3310
3449
  }
3450
+ async function createAlchemyWalletApisState(params) {
3451
+ const { createSmartWalletClient, alchemyWalletTransport } = await import("@alchemy/wallet-apis");
3452
+ const signer = privateKeyToAccount(params.privateKey);
3453
+ const walletClient = createSmartWalletClient(__spreadValues({
3454
+ transport: alchemyWalletTransport({ apiKey: params.apiKey }),
3455
+ chain: params.chain,
3456
+ signer
3457
+ }, params.gasPolicyId ? { paymaster: { policyId: params.gasPolicyId } } : {}));
3458
+ let accountAddress = signer.address;
3459
+ if (params.mode === "4337") {
3460
+ const account = await walletClient.requestAccount();
3461
+ accountAddress = account.address;
3462
+ }
3463
+ const sendCalls = async (calls) => {
3464
+ var _a3, _b;
3465
+ const result = await walletClient.sendCalls(__spreadProps(__spreadValues({}, params.mode === "4337" ? { account: accountAddress } : {}), {
3466
+ calls
3467
+ }));
3468
+ const status = await walletClient.waitForCallsStatus({ id: result.id });
3469
+ const transactionHash = (_b = (_a3 = status.receipts) == null ? void 0 : _a3[0]) == null ? void 0 : _b.transactionHash;
3470
+ if (!transactionHash) {
3471
+ throw new Error("Alchemy Wallets API did not return a transaction hash.");
3472
+ }
3473
+ return { transactionHash };
3474
+ };
3475
+ const AA = {
3476
+ provider: "alchemy",
3477
+ mode: params.mode,
3478
+ AAAddress: accountAddress,
3479
+ delegationAddress: params.mode === "7702" ? signer.address : void 0,
3480
+ sendTransaction: async (call) => sendCalls([call]),
3481
+ sendBatchTransaction: async (calls) => sendCalls(calls)
3482
+ };
3483
+ return {
3484
+ plan: params.plan,
3485
+ AA,
3486
+ isPending: false,
3487
+ error: null
3488
+ };
3489
+ }
3311
3490
  async function createPimlicoAAState(options) {
3312
3491
  var _a3;
3313
3492
  const {
@@ -3491,7 +3670,7 @@ function txCommand() {
3491
3670
  }
3492
3671
  printDataFileLocation();
3493
3672
  }
3494
- function requirePendingTx(state, txId) {
3673
+ function requirePendingTx2(state, txId) {
3495
3674
  var _a3;
3496
3675
  const pendingTx = ((_a3 = state.pendingTxs) != null ? _a3 : []).find((tx) => tx.id === txId);
3497
3676
  if (!pendingTx) {
@@ -3507,7 +3686,7 @@ function requirePendingTxs(state, txIds) {
3507
3686
  if (uniqueIds.length !== txIds.length) {
3508
3687
  fatal("Duplicate transaction IDs are not allowed in a single `aomi sign` call.");
3509
3688
  }
3510
- return uniqueIds.map((txId) => requirePendingTx(state, txId));
3689
+ return uniqueIds.map((txId) => requirePendingTx2(state, txId));
3511
3690
  }
3512
3691
  function rewriteSessionState(runtime, state) {
3513
3692
  let changed = false;
@@ -3669,7 +3848,7 @@ async function executeTransactionWithFallback(params) {
3669
3848
  }
3670
3849
  }
3671
3850
  async function signCommand(runtime) {
3672
- var _a3, _b;
3851
+ var _a3, _b, _c, _d;
3673
3852
  const txIds = runtime.parsed.positional;
3674
3853
  if (txIds.length === 0) {
3675
3854
  fatal(
@@ -3739,6 +3918,46 @@ async function signCommand(runtime) {
3739
3918
  if (callList.length > 1 && rpcUrl && new Set(callList.map((call) => call.chainId)).size > 1) {
3740
3919
  fatal("A single `--rpc-url` override cannot be used for a mixed-chain multi-sign request.");
3741
3920
  }
3921
+ try {
3922
+ const simResponse = await session.client.simulateBatch(
3923
+ state.sessionId,
3924
+ pendingTxs.map((tx) => {
3925
+ var _a4;
3926
+ return {
3927
+ to: tx.to,
3928
+ value: tx.value,
3929
+ data: tx.data,
3930
+ label: (_a4 = tx.description) != null ? _a4 : tx.id
3931
+ };
3932
+ }),
3933
+ {
3934
+ from: account.address,
3935
+ chainId: primaryChainId
3936
+ }
3937
+ );
3938
+ const { result: sim } = simResponse;
3939
+ if (!sim.batch_success) {
3940
+ const failed = sim.steps.find((s) => !s.success);
3941
+ fatal(
3942
+ `Simulation failed at step ${(_c = failed == null ? void 0 : failed.step) != null ? _c : "?"}: ${(_d = failed == null ? void 0 : failed.revert_reason) != null ? _d : "unknown"}`
3943
+ );
3944
+ }
3945
+ if (sim.fee) {
3946
+ const feeEth = (Number(sim.fee.amount_wei) / 1e18).toFixed(6);
3947
+ console.log(
3948
+ `Fee: ${feeEth} ETH \u2192 ${sim.fee.recipient.slice(0, 10)}...`
3949
+ );
3950
+ callList.push({
3951
+ to: sim.fee.recipient,
3952
+ value: sim.fee.amount_wei,
3953
+ chainId: primaryChainId
3954
+ });
3955
+ }
3956
+ } catch (e) {
3957
+ console.log(
3958
+ `${DIM}Simulation unavailable, skipping fee injection.${RESET}`
3959
+ );
3960
+ }
3742
3961
  const decision = resolveCliExecutionDecision({
3743
3962
  config: runtime.config,
3744
3963
  chain,
@@ -3875,6 +4094,8 @@ Usage:
3875
4094
  Delete a local session file (session-id or session-N)
3876
4095
  aomi log Show full conversation history with tool results
3877
4096
  aomi tx List pending and signed transactions
4097
+ aomi simulate <tx-id> [<tx-id> ...]
4098
+ Batch-simulate pending txs atomically (approve \u2192 swap)
3878
4099
  aomi sign <tx-id> [<tx-id> ...] [--eoa | --aa] [--aa-provider <name>] [--aa-mode <mode>]
3879
4100
  Sign and submit a pending transaction
3880
4101
  aomi secret list List configured secrets for the active session
@@ -3914,7 +4135,7 @@ Default signing behavior:
3914
4135
  fall back to EOA automatically if AA is unavailable
3915
4136
 
3916
4137
  Environment (overridden by flags):
3917
- AOMI_BASE_URL Backend URL
4138
+ AOMI_BACKEND_URL Backend URL
3918
4139
  AOMI_API_KEY API key
3919
4140
  AOMI_APP App
3920
4141
  AOMI_MODEL Model rig
@@ -3959,6 +4180,9 @@ async function main(runtime) {
3959
4180
  case "sign":
3960
4181
  await signCommand(runtime);
3961
4182
  break;
4183
+ case "simulate":
4184
+ await simulateCommand(runtime);
4185
+ break;
3962
4186
  case "status":
3963
4187
  await statusCommand(runtime);
3964
4188
  break;
@@ -3996,7 +4220,7 @@ function isPnpmExecWrapper() {
3996
4220
  async function runCli(argv = process.argv) {
3997
4221
  const runtime = createRuntime(argv);
3998
4222
  const RED = "\x1B[31m";
3999
- const RESET3 = "\x1B[0m";
4223
+ const RESET2 = "\x1B[0m";
4000
4224
  const strictExit = process.env.AOMI_CLI_STRICT_EXIT === "1";
4001
4225
  try {
4002
4226
  await main(runtime);
@@ -4009,7 +4233,7 @@ async function runCli(argv = process.argv) {
4009
4233
  return;
4010
4234
  }
4011
4235
  const message = err instanceof Error ? err.message : String(err);
4012
- console.error(`${RED}\u274C ${message}${RESET3}`);
4236
+ console.error(`${RED}\u274C ${message}${RESET2}`);
4013
4237
  process.exit(1);
4014
4238
  }
4015
4239
  }
package/dist/index.cjs CHANGED
@@ -615,6 +615,39 @@ var AomiClient = class {
615
615
  }
616
616
  return postState(this.baseUrl, "/api/control/model", payload, sessionId, apiKey);
617
617
  }
618
+ // ===========================================================================
619
+ // Batch Simulation
620
+ // ===========================================================================
621
+ /**
622
+ * Simulate transactions as an atomic batch.
623
+ * Each tx sees state changes from previous txs (e.g., approve → swap).
624
+ * Sends full tx payloads — the backend does not look up by ID.
625
+ */
626
+ async simulateBatch(sessionId, transactions, options) {
627
+ const url = joinApiPath(this.baseUrl, "/api/simulate");
628
+ const headers = new Headers(
629
+ withSessionHeader(sessionId, { "Content-Type": "application/json" })
630
+ );
631
+ if (this.apiKey) {
632
+ headers.set(API_KEY_HEADER, this.apiKey);
633
+ }
634
+ const payload = {
635
+ transactions,
636
+ from: options == null ? void 0 : options.from,
637
+ chain_id: options == null ? void 0 : options.chainId
638
+ };
639
+ const response = await fetch(url, {
640
+ method: "POST",
641
+ headers,
642
+ body: JSON.stringify(payload)
643
+ });
644
+ if (!response.ok) {
645
+ const body = await response.text().catch(() => "");
646
+ throw new Error(`HTTP ${response.status}: ${response.statusText}${body ? `
647
+ ${body}` : ""}`);
648
+ }
649
+ return await response.json();
650
+ }
618
651
  };
619
652
 
620
653
  // src/types.ts
@@ -1956,6 +1989,25 @@ async function createAlchemyAAState(options) {
1956
1989
  if (ownerParams.kind === "unsupported_adapter") {
1957
1990
  return getUnsupportedAdapterState(plan, ownerParams.adapter);
1958
1991
  }
1992
+ if (owner.kind === "direct") {
1993
+ try {
1994
+ return await createAlchemyWalletApisState({
1995
+ plan,
1996
+ chain,
1997
+ privateKey: owner.privateKey,
1998
+ apiKey,
1999
+ gasPolicyId,
2000
+ mode: plan.mode
2001
+ });
2002
+ } catch (error) {
2003
+ return {
2004
+ plan,
2005
+ AA: null,
2006
+ isPending: false,
2007
+ error: error instanceof Error ? error : new Error(String(error))
2008
+ };
2009
+ }
2010
+ }
1959
2011
  try {
1960
2012
  const smartAccount = await (0, import_aa_alchemy.createAlchemySmartAccount)(__spreadProps(__spreadValues({}, ownerParams.ownerParams), {
1961
2013
  apiKey,
@@ -1987,6 +2039,46 @@ async function createAlchemyAAState(options) {
1987
2039
  };
1988
2040
  }
1989
2041
  }
2042
+ async function createAlchemyWalletApisState(params) {
2043
+ const { createSmartWalletClient, alchemyWalletTransport } = await import("@alchemy/wallet-apis");
2044
+ const signer = (0, import_accounts.privateKeyToAccount)(params.privateKey);
2045
+ const walletClient = createSmartWalletClient(__spreadValues({
2046
+ transport: alchemyWalletTransport({ apiKey: params.apiKey }),
2047
+ chain: params.chain,
2048
+ signer
2049
+ }, params.gasPolicyId ? { paymaster: { policyId: params.gasPolicyId } } : {}));
2050
+ let accountAddress = signer.address;
2051
+ if (params.mode === "4337") {
2052
+ const account = await walletClient.requestAccount();
2053
+ accountAddress = account.address;
2054
+ }
2055
+ const sendCalls = async (calls) => {
2056
+ var _a, _b;
2057
+ const result = await walletClient.sendCalls(__spreadProps(__spreadValues({}, params.mode === "4337" ? { account: accountAddress } : {}), {
2058
+ calls
2059
+ }));
2060
+ const status = await walletClient.waitForCallsStatus({ id: result.id });
2061
+ const transactionHash = (_b = (_a = status.receipts) == null ? void 0 : _a[0]) == null ? void 0 : _b.transactionHash;
2062
+ if (!transactionHash) {
2063
+ throw new Error("Alchemy Wallets API did not return a transaction hash.");
2064
+ }
2065
+ return { transactionHash };
2066
+ };
2067
+ const AA = {
2068
+ provider: "alchemy",
2069
+ mode: params.mode,
2070
+ AAAddress: accountAddress,
2071
+ delegationAddress: params.mode === "7702" ? signer.address : void 0,
2072
+ sendTransaction: async (call) => sendCalls([call]),
2073
+ sendBatchTransaction: async (calls) => sendCalls(calls)
2074
+ };
2075
+ return {
2076
+ plan: params.plan,
2077
+ AA,
2078
+ isPending: false,
2079
+ error: null
2080
+ };
2081
+ }
1990
2082
  async function createPimlicoAAState(options) {
1991
2083
  var _a;
1992
2084
  const {