@aztec/cli-wallet 0.0.1-commit.b9865e97 → 0.0.1-commit.be03c316

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.
Files changed (45) hide show
  1. package/dest/cmds/check_tx.js +8 -6
  2. package/dest/cmds/create_account.d.ts +4 -2
  3. package/dest/cmds/create_account.d.ts.map +1 -1
  4. package/dest/cmds/create_account.js +26 -10
  5. package/dest/cmds/deploy.js +3 -6
  6. package/dest/cmds/deploy_account.js +4 -7
  7. package/dest/cmds/import_test_accounts.d.ts +1 -1
  8. package/dest/cmds/import_test_accounts.d.ts.map +1 -1
  9. package/dest/cmds/import_test_accounts.js +2 -1
  10. package/dest/cmds/index.d.ts +1 -1
  11. package/dest/cmds/index.d.ts.map +1 -1
  12. package/dest/cmds/index.js +8 -7
  13. package/dest/cmds/send.js +3 -6
  14. package/dest/cmds/simulate.d.ts +1 -1
  15. package/dest/cmds/simulate.d.ts.map +1 -1
  16. package/dest/cmds/simulate.js +2 -1
  17. package/dest/storage/wallet_db.d.ts +7 -4
  18. package/dest/storage/wallet_db.d.ts.map +1 -1
  19. package/dest/storage/wallet_db.js +15 -1
  20. package/dest/utils/constants.d.ts +2 -2
  21. package/dest/utils/constants.d.ts.map +1 -1
  22. package/dest/utils/constants.js +1 -0
  23. package/dest/utils/options/fees.d.ts +1 -1
  24. package/dest/utils/options/fees.d.ts.map +1 -1
  25. package/dest/utils/options/fees.js +4 -1
  26. package/dest/utils/options/options.d.ts +2 -2
  27. package/dest/utils/options/options.d.ts.map +1 -1
  28. package/dest/utils/options/options.js +5 -5
  29. package/dest/utils/wallet.d.ts +25 -5
  30. package/dest/utils/wallet.d.ts.map +1 -1
  31. package/dest/utils/wallet.js +89 -67
  32. package/package.json +15 -15
  33. package/src/cmds/check_tx.ts +11 -10
  34. package/src/cmds/create_account.ts +27 -15
  35. package/src/cmds/deploy.ts +3 -3
  36. package/src/cmds/deploy_account.ts +4 -4
  37. package/src/cmds/import_test_accounts.ts +12 -1
  38. package/src/cmds/index.ts +11 -9
  39. package/src/cmds/send.ts +3 -3
  40. package/src/cmds/simulate.ts +2 -1
  41. package/src/storage/wallet_db.ts +24 -2
  42. package/src/utils/constants.ts +7 -1
  43. package/src/utils/options/fees.ts +4 -2
  44. package/src/utils/options/options.ts +5 -5
  45. package/src/utils/wallet.ts +132 -85
package/src/cmds/send.ts CHANGED
@@ -42,10 +42,10 @@ export async function send(
42
42
  const localStart = performance.now();
43
43
  const sim = await call.simulate({
44
44
  ...sendOptions,
45
- fee: { ...sendOptions.fee, estimateGas: true },
45
+ includeMetadata: true,
46
46
  });
47
- // estimateGas: true guarantees these fields are present
48
- const estimatedGas = sim.estimatedGas!;
47
+ // includeMetadata: true guarantees these fields are present
48
+ const estimatedGas = await wallet.estimateGasLimits(sim.gasUsed!);
49
49
  const stats = sim.stats!;
50
50
 
51
51
  if (feeOpts.estimateOnly) {
@@ -44,7 +44,8 @@ export async function simulate(
44
44
  if (!metadata.instance) {
45
45
  return undefined;
46
46
  }
47
- const artifact = await wallet.getContractArtifact(metadata.instance.currentContractClassId);
47
+ const classId = metadata.updatedContractClassId ?? metadata.instance.originalContractClassId;
48
+ const artifact = await wallet.getContractArtifact(classId);
48
49
  return artifact;
49
50
  },
50
51
  log,
@@ -1,4 +1,5 @@
1
1
  import { Fr } from '@aztec/foundation/curves/bn254';
2
+ import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin';
2
3
  import type { LogFn } from '@aztec/foundation/log';
3
4
  import type { AztecAsyncKVStore, AztecAsyncMap } from '@aztec/kv-store';
4
5
  import type { AuthWitness } from '@aztec/stdlib/auth-witness';
@@ -88,13 +89,28 @@ export class WalletDB {
88
89
  address: AztecAddress,
89
90
  {
90
91
  type,
92
+ signingKey,
91
93
  secretKey,
92
94
  salt,
93
95
  alias,
94
96
  publicKey,
95
- }: { type: AccountType; secretKey: Fr; salt: Fr; alias: string | undefined; publicKey: string | undefined },
97
+ }: {
98
+ type: AccountType;
99
+ signingKey?: GrumpkinScalar;
100
+ secretKey?: Fr;
101
+ salt: Fr;
102
+ alias: string | undefined;
103
+ publicKey: string | undefined;
104
+ },
96
105
  log: LogFn,
97
106
  ) {
107
+ // Even though the privacy secret key is sometimes derived from the signing key, we store it in the database
108
+ // regardless as that is not always the case. Local-key accounts also store their signing key, while external-key
109
+ // accounts such as SSH store only the privacy secret.
110
+ if (!secretKey) {
111
+ throw new Error('Cannot store account without a secret key');
112
+ }
113
+
98
114
  let publicSigningKey: Buffer | undefined;
99
115
  if (type === 'ecdsasecp256r1ssh' && publicKey) {
100
116
  publicSigningKey = extractECDSAPublicKeyFromBase64String(publicKey);
@@ -106,6 +122,9 @@ export class WalletDB {
106
122
  }
107
123
  await this.#accounts.set(`${address.toString()}:type`, Buffer.from(type));
108
124
  await this.#accounts.set(`${address.toString()}:sk`, secretKey.toBuffer());
125
+ if (signingKey) {
126
+ await this.#accounts.set(`${address.toString()}:signing`, signingKey.toBuffer());
127
+ }
109
128
  await this.#accounts.set(`${address.toString()}:salt`, salt.toBuffer());
110
129
  if (publicSigningKey) {
111
130
  await this.#accounts.set(`${address.toString()}:publicSigningKey`, publicSigningKey);
@@ -226,10 +245,13 @@ export class WalletDB {
226
245
  if (!secretKeyBuffer) {
227
246
  throw new Error(`Could not find ${address}:sk. Account "${address.toString}" does not exist on this wallet.`);
228
247
  }
248
+ const signingKeyBuffer = await this.#accounts.getAsync(`${address.toString()}:signing`);
229
249
  const secretKey = Fr.fromBuffer(secretKeyBuffer);
250
+ // External-key accounts (e.g. SSH) store no signing key.
251
+ const signingKey = signingKeyBuffer ? GrumpkinScalar.fromBuffer(signingKeyBuffer) : undefined;
230
252
  const salt = Fr.fromBuffer((await this.#accounts.getAsync(`${address.toString()}:salt`))!);
231
253
  const type = (await this.#accounts.getAsync(`${address.toString()}:type`))!.toString('utf8') as AccountType;
232
- return { address, secretKey, salt, type };
254
+ return { address, signingKey, secretKey, salt, type };
233
255
  }
234
256
 
235
257
  async storeAlias(type: AliasType, key: string, value: Buffer, log: LogFn) {
@@ -1,4 +1,10 @@
1
1
  export const MIN_FEE_PADDING = 0.5;
2
2
 
3
- export const AccountTypes = ['schnorr', 'ecdsasecp256r1', 'ecdsasecp256r1ssh', 'ecdsasecp256k1'] as const;
3
+ export const AccountTypes = [
4
+ 'schnorr',
5
+ 'schnorr_initializerless',
6
+ 'ecdsasecp256r1',
7
+ 'ecdsasecp256r1ssh',
8
+ 'ecdsasecp256k1',
9
+ ] as const;
4
10
  export type AccountType = (typeof AccountTypes)[number];
@@ -137,7 +137,7 @@ export function parsePaymentMethod(
137
137
  if (!parsed.asset) {
138
138
  throw new Error('Missing "asset" in payment option');
139
139
  }
140
- return AztecAddress.fromString(parsed.asset);
140
+ return AztecAddress.fromStringUnsafe(parsed.asset);
141
141
  };
142
142
 
143
143
  return async (wallet: Wallet, from: AztecAddress, gasSettings: GasSettings) => {
@@ -259,7 +259,9 @@ export class CLIFeeArgs {
259
259
  async toUserFeeOptions(node: AztecNode, wallet: Wallet, from: AztecAddress): Promise<ParsedFeeOptions> {
260
260
  const minFees = await this.getMinFees(node);
261
261
  const maxFeesPerGas = minFees.mul(1 + MIN_FEE_PADDING);
262
- const gasSettings = GasSettings.fallback({ ...this.gasSettings, maxFeesPerGas });
262
+ const { txsLimits } = await node.getNodeInfo();
263
+ const gasLimits = this.gasSettings.gasLimits ?? Gas.from(txsLimits.gas);
264
+ const gasSettings = GasSettings.fallback({ ...this.gasSettings, gasLimits, maxFeesPerGas });
263
265
  const paymentMethod = await this.paymentMethod(wallet, from, gasSettings);
264
266
  return {
265
267
  paymentMethod,
@@ -1,5 +1,5 @@
1
1
  import { TxHash } from '@aztec/aztec.js/tx';
2
- import { parseAztecAddress, parseSecretKey, parseTxHash } from '@aztec/cli/utils';
2
+ import { parseAztecAddress, parseSigningKey, parseTxHash } from '@aztec/cli/utils';
3
3
  import { AuthWitness } from '@aztec/stdlib/auth-witness';
4
4
  import type { AztecAddress } from '@aztec/stdlib/aztec-address';
5
5
 
@@ -64,13 +64,13 @@ export function aliasedAddressParser(defaultPrefix: AliasType, address: string,
64
64
  }
65
65
  }
66
66
 
67
- export function aliasedSecretKeyParser(sk: string, db?: WalletDB) {
67
+ export function aliasedSigningKeyParser(sk: string, db?: WalletDB) {
68
68
  if (sk.startsWith('0x')) {
69
- return parseSecretKey(sk);
69
+ return parseSigningKey(sk);
70
70
  } else {
71
- const prefixed = `${sk.startsWith('accounts') ? '' : 'accounts'}:${sk.endsWith(':sk') ? sk : `${sk}:sk`}`;
71
+ const prefixed = `${sk.startsWith('accounts') ? '' : 'accounts'}:${sk.endsWith(':signing') ? sk : `${sk}:signing`}`;
72
72
  const rawSk = db ? db.tryRetrieveAlias(prefixed) : sk;
73
- return parseSecretKey(rawSk);
73
+ return parseSigningKey(rawSk);
74
74
  }
75
75
  }
76
76
 
@@ -1,16 +1,21 @@
1
- import { EcdsaRAccountContract, EcdsaRSSHAccountContract } from '@aztec/accounts/ecdsa';
2
- import { SchnorrAccountContract } from '@aztec/accounts/schnorr';
3
- import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/stub/ecdsa';
4
- import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/stub/schnorr';
1
+ import { EcdsaKAccountContract, EcdsaRAccountContract, EcdsaRSSHAccountContract } from '@aztec/accounts/ecdsa';
2
+ import { StubEcdsaAccountContractArtifact, createStubEcdsaAccount } from '@aztec/accounts/ecdsa/stub';
3
+ import { SchnorrAccountContract, SchnorrInitializerlessAccountContract } from '@aztec/accounts/schnorr';
4
+ import { StubSchnorrAccountContractArtifact, createStubSchnorrAccount } from '@aztec/accounts/schnorr/stub';
5
5
  import { getIdentities } from '@aztec/accounts/utils';
6
6
  import { type Account, type AccountContract, NO_FROM } from '@aztec/aztec.js/account';
7
- import { type InteractionFeeOptions, getContractClassFromArtifact, getGasLimits } from '@aztec/aztec.js/contracts';
7
+ import {
8
+ ContractFunctionInteraction,
9
+ type InteractionFeeOptions,
10
+ getContractClassFromArtifact,
11
+ } from '@aztec/aztec.js/contracts';
8
12
  import type { AztecNode } from '@aztec/aztec.js/node';
9
- import { AccountManager, type Aliased, type SimulateOptions } from '@aztec/aztec.js/wallet';
13
+ import { AccountManager, type Aliased } from '@aztec/aztec.js/wallet';
10
14
  import { TxSimulationResultWithAppOffset } from '@aztec/aztec.js/wallet';
11
15
  import type { DefaultAccountEntrypointOptions } from '@aztec/entrypoints/account';
12
16
  import { DefaultEntrypoint } from '@aztec/entrypoints/default';
13
17
  import { Fr } from '@aztec/foundation/curves/bn254';
18
+ import { GrumpkinScalar } from '@aztec/foundation/curves/grumpkin';
14
19
  import type { LogFn } from '@aztec/foundation/log';
15
20
  import type { NotesFilter } from '@aztec/pxe/client/lazy';
16
21
  import type { PXEConfig } from '@aztec/pxe/config';
@@ -18,16 +23,18 @@ import type { PXE } from '@aztec/pxe/server';
18
23
  import { createPXE, getPXEConfig } from '@aztec/pxe/server';
19
24
  import { getStandardAuthRegistry } from '@aztec/standard-contracts/auth-registry';
20
25
  import { AztecAddress } from '@aztec/stdlib/aztec-address';
21
- import { deriveSigningKey } from '@aztec/stdlib/keys';
26
+ import type { Gas, GasUsed } from '@aztec/stdlib/gas';
22
27
  import { NoteDao } from '@aztec/stdlib/note';
23
28
  import type { SimulationOverrides, TxExecutionRequest, TxProvingResult } from '@aztec/stdlib/tx';
24
29
  import { ExecutionPayload, mergeExecutionPayloads } from '@aztec/stdlib/tx';
25
- import { BaseWallet, type SimulateViaEntrypointOptions } from '@aztec/wallet-sdk/base-wallet';
30
+ import { BaseWallet, type SimulateViaEntrypointOptions, getGasLimits } from '@aztec/wallet-sdk/base-wallet';
26
31
 
27
32
  import type { WalletDB } from '../storage/wallet_db.js';
28
33
  import type { AccountType } from './constants.js';
29
34
  import { extractECDSAPublicKeyFromBase64String } from './ecdsa.js';
30
- import { printGasEstimates } from './options/fees.js';
35
+
36
+ /** Padding the CLI wallet applies to simulated gas usage when deriving declared gas limits. */
37
+ const DEFAULT_ESTIMATED_GAS_PADDING = 0.1;
31
38
 
32
39
  export class CLIWallet extends BaseWallet {
33
40
  private accountCache = new Map<string, Account>();
@@ -61,7 +68,8 @@ export class CLIWallet extends BaseWallet {
61
68
 
62
69
  private async registerAuthRegistry(): Promise<void> {
63
70
  const { instance, artifact } = await getStandardAuthRegistry();
64
- await this.pxe.registerContract({ instance, artifact });
71
+ await this.pxe.registerContractClass(artifact);
72
+ await this.pxe.registerContract(instance);
65
73
  }
66
74
 
67
75
  /**
@@ -77,6 +85,7 @@ export class CLIWallet extends BaseWallet {
77
85
  await this.pxe.registerContractClass(StubEcdsaAccountContractArtifact);
78
86
 
79
87
  this.stubClassIds.set('schnorr', schnorrClassId);
88
+ this.stubClassIds.set('schnorr_initializerless', schnorrClassId);
80
89
  this.stubClassIds.set('ecdsasecp256k1', ecdsaClassId);
81
90
  this.stubClassIds.set('ecdsasecp256r1', ecdsaClassId);
82
91
  this.stubClassIds.set('ecdsasecp256r1ssh', ecdsaClassId);
@@ -87,11 +96,21 @@ export class CLIWallet extends BaseWallet {
87
96
  return Promise.resolve(
88
97
  accounts.map(({ key, value }) => {
89
98
  const alias = key.includes(':') ? key.slice(key.indexOf(':') + 1) : key;
90
- return { alias, item: AztecAddress.fromString(value) };
99
+ return { alias, item: AztecAddress.fromStringUnsafe(value) };
91
100
  }),
92
101
  );
93
102
  }
94
103
 
104
+ /**
105
+ * Derives suggested total and teardown gas limits from simulated gas usage, padded and clamped to the
106
+ * network's per-tx admission limits.
107
+ * @param gasUsed - The gas consumed during simulation (from a `simulate({ includeMetadata: true })` result).
108
+ */
109
+ async estimateGasLimits(gasUsed: GasUsed): Promise<{ gasLimits: Gas; teardownGasLimits: Gas }> {
110
+ const maxTxGasLimits = await this.getMaxTxGasLimits();
111
+ return getGasLimits(gasUsed, maxTxGasLimits, DEFAULT_ESTIMATED_GAS_PADDING);
112
+ }
113
+
95
114
  private async createCancellationTxExecutionRequest(
96
115
  from: AztecAddress,
97
116
  txNonce: Fr,
@@ -126,7 +145,10 @@ export class CLIWallet extends BaseWallet {
126
145
  increasedFee: InteractionFeeOptions,
127
146
  ): Promise<TxProvingResult> {
128
147
  const cancellationTxRequest = await this.createCancellationTxExecutionRequest(from, txNonce, increasedFee);
129
- return await this.pxe.proveTx(cancellationTxRequest, { scopes: this.scopesFrom(from), senderForTags: from });
148
+ return await this.pxe.proveTx(cancellationTxRequest, {
149
+ scopes: this.scopesFrom(from, [], undefined),
150
+ senderForTags: from,
151
+ });
130
152
  }
131
153
 
132
154
  override async getAccountFromAddress(address: AztecAddress) {
@@ -134,7 +156,7 @@ export class CLIWallet extends BaseWallet {
134
156
  if (this.accountCache.has(address.toString())) {
135
157
  return this.accountCache.get(address.toString())!;
136
158
  } else {
137
- const accountManager = await this.createOrRetrieveAccount(address);
159
+ const accountManager = await this.retrieveAccount(address);
138
160
  account = await accountManager.getAccount();
139
161
  }
140
162
 
@@ -144,74 +166,115 @@ export class CLIWallet extends BaseWallet {
144
166
  return account;
145
167
  }
146
168
 
147
- private async createAccount(secret: Fr, salt: Fr, contract: AccountContract): Promise<AccountManager> {
148
- const accountManager = await AccountManager.create(this, secret, contract, { salt });
149
-
150
- const instance = accountManager.getInstance();
151
- const artifact = await contract.getContractArtifact();
152
-
153
- await this.registerContract(instance, artifact, secret);
154
- this.accountCache.set(accountManager.address.toString(), await accountManager.getAccount());
155
- return accountManager;
156
- }
157
-
158
- async createOrRetrieveAccount(
159
- address?: AztecAddress,
160
- secretKey?: Fr,
161
- type: AccountType = 'schnorr',
162
- salt?: Fr,
169
+ /**
170
+ * Creates an account from freshly supplied keys. SSH accounts sign with a key held in the agent (resolved from
171
+ * `publicKey`), every other type is rooted on `signingKey`, with `secretKey` as its privacy secret.
172
+ */
173
+ async createAccount(
174
+ type: AccountType,
175
+ signingKey: GrumpkinScalar | undefined,
176
+ secretKey: Fr | undefined,
177
+ salt: Fr = Fr.ZERO,
163
178
  publicKey?: string,
164
179
  ): Promise<AccountManager> {
165
- let account;
166
-
167
- salt ??= Fr.ZERO;
180
+ const publicSigningKey =
181
+ type === 'ecdsasecp256r1ssh' ? await this.resolveSshPublicSigningKey(publicKey) : undefined;
182
+ return this.buildAccount(type, salt, signingKey, secretKey, publicSigningKey);
183
+ }
168
184
 
169
- if (this.db && address) {
170
- ({ type, secretKey, salt } = await this.db.retrieveAccount(address));
185
+ /**
186
+ * Retrieves a previously stored account by address, loading its keys, type and salt from the wallet database.
187
+ */
188
+ async retrieveAccount(address: AztecAddress): Promise<AccountManager> {
189
+ if (!this.db) {
190
+ throw new Error('Cannot retrieve an account without a wallet database');
171
191
  }
172
-
173
- if (!secretKey) {
174
- throw new Error('Cannot retrieve/create wallet without secret key');
192
+ const { type, signingKey, secretKey, salt } = await this.db.retrieveAccount(address);
193
+ if (type !== 'ecdsasecp256r1ssh' && !signingKey) {
194
+ throw new Error(
195
+ `Account ${address} has no stored signing key: it was created with an older version of aztec-wallet and is ` +
196
+ `incompatible with this one. Create a new account to continue.`,
197
+ );
175
198
  }
199
+ const publicSigningKey =
200
+ type === 'ecdsasecp256r1ssh' ? await this.db.retrieveAccountMetadata(address, 'publicSigningKey') : undefined;
201
+ return this.buildAccount(type, salt, signingKey, secretKey, publicSigningKey);
202
+ }
176
203
 
204
+ private async buildAccount(
205
+ type: AccountType,
206
+ salt: Fr,
207
+ signingKey: GrumpkinScalar | undefined,
208
+ secretKey: Fr | undefined,
209
+ publicSigningKey: Buffer | undefined,
210
+ ): Promise<AccountManager> {
177
211
  switch (type) {
178
- case 'schnorr': {
179
- account = await this.createAccount(secretKey, salt, new SchnorrAccountContract(deriveSigningKey(secretKey)));
180
- break;
181
- }
182
- case 'ecdsasecp256r1': {
183
- account = await this.createAccount(
184
- secretKey,
185
- salt,
186
- new EcdsaRAccountContract(deriveSigningKey(secretKey).toBuffer()),
187
- );
188
- break;
212
+ case 'schnorr':
213
+ case 'schnorr_initializerless':
214
+ case 'ecdsasecp256r1':
215
+ case 'ecdsasecp256k1': {
216
+ if (!signingKey || !secretKey) {
217
+ throw new Error('Cannot build account without signing key and secret key');
218
+ }
219
+ const contract =
220
+ type === 'schnorr'
221
+ ? new SchnorrAccountContract(signingKey)
222
+ : type === 'schnorr_initializerless'
223
+ ? new SchnorrInitializerlessAccountContract(signingKey)
224
+ : type === 'ecdsasecp256r1'
225
+ ? new EcdsaRAccountContract(signingKey.toBuffer())
226
+ : new EcdsaKAccountContract(signingKey.toBuffer());
227
+ return await this.materializeAccount(secretKey, salt, contract);
189
228
  }
190
229
  case 'ecdsasecp256r1ssh': {
191
- let publicSigningKey;
192
- if (this.db && address) {
193
- publicSigningKey = await this.db.retrieveAccountMetadata(address, 'publicSigningKey');
194
- } else if (publicKey) {
195
- const identities = await getIdentities();
196
- const foundIdentity = identities.find(
197
- identity => identity.type === 'ecdsa-sha2-nistp256' && identity.publicKey === publicKey,
198
- );
199
- if (!foundIdentity) {
200
- throw new Error(`Identity for public key ${publicKey} not found in the SSH agent`);
201
- }
202
- publicSigningKey = extractECDSAPublicKeyFromBase64String(foundIdentity.publicKey);
203
- } else {
204
- throw new Error('Public key must be provided for ECDSA SSH account');
230
+ if (!secretKey || !publicSigningKey) {
231
+ throw new Error('Cannot build SSH account without secret key and public signing key');
205
232
  }
206
- account = await this.createAccount(secretKey, salt, new EcdsaRSSHAccountContract(publicSigningKey));
207
- break;
233
+ return await this.materializeAccount(secretKey, salt, new EcdsaRSSHAccountContract(publicSigningKey));
208
234
  }
209
235
  default: {
210
236
  throw new Error(`Unsupported account type: ${type}`);
211
237
  }
212
238
  }
239
+ }
213
240
 
214
- return account;
241
+ private async resolveSshPublicSigningKey(publicKey: string | undefined): Promise<Buffer> {
242
+ if (!publicKey) {
243
+ throw new Error('Public key must be provided for ECDSA SSH account');
244
+ }
245
+ const identities = await getIdentities();
246
+ const foundIdentity = identities.find(
247
+ identity => identity.type === 'ecdsa-sha2-nistp256' && identity.publicKey === publicKey,
248
+ );
249
+ if (!foundIdentity) {
250
+ throw new Error(`Identity for public key ${publicKey} not found in the SSH agent`);
251
+ }
252
+ return extractECDSAPublicKeyFromBase64String(foundIdentity.publicKey);
253
+ }
254
+
255
+ private async materializeAccount(secret: Fr, salt: Fr, contract: AccountContract): Promise<AccountManager> {
256
+ const accountManager = await AccountManager.create(this, secret, contract, { salt });
257
+
258
+ const instance = accountManager.getInstance();
259
+ const artifact = await contract.getContractArtifact();
260
+
261
+ await this.registerContract(instance, artifact, secret);
262
+ this.accountCache.set(accountManager.address.toString(), await accountManager.getAccount());
263
+
264
+ // Initializerless accounts have no deployment tx; their address commits to the signing public key
265
+ // (via the contract's immutablesHash, resolved by AccountManager.create) and the constructor's
266
+ // storage writes are materialized locally via a simulated "store" call here.
267
+ if (contract instanceof SchnorrInitializerlessAccountContract) {
268
+ const constructorAbi = artifact.functions.find(f => f.name === 'constructor');
269
+ if (!constructorAbi) {
270
+ throw new Error('Could not create SchnorrInitializerlessAccount: constructor ABI not found');
271
+ }
272
+ const { x, y } = await contract.getSigningPublicKey();
273
+ const storeCall = new ContractFunctionInteraction(this, instance.address, constructorAbi, [x, y]);
274
+ await storeCall.simulate({ from: instance.address });
275
+ }
276
+
277
+ return accountManager;
215
278
  }
216
279
 
217
280
  /**
@@ -229,7 +292,9 @@ export class CLIWallet extends BaseWallet {
229
292
  }
230
293
  const { type } = await this.db!.retrieveAccount(address);
231
294
  const stubAccount =
232
- type === 'schnorr' ? createStubSchnorrAccount(originalAddress) : createStubEcdsaAccount(originalAddress);
295
+ type === 'schnorr' || type === 'schnorr_initializerless'
296
+ ? createStubSchnorrAccount(originalAddress)
297
+ : createStubEcdsaAccount(originalAddress);
233
298
  const stubClassId = this.stubClassIds.get(type);
234
299
  if (!stubClassId) {
235
300
  throw new Error(
@@ -240,24 +305,6 @@ export class CLIWallet extends BaseWallet {
240
305
  return { account: stubAccount, instance };
241
306
  }
242
307
 
243
- override async simulateTx(
244
- executionPayload: ExecutionPayload,
245
- opts: SimulateOptions,
246
- ): Promise<TxSimulationResultWithAppOffset> {
247
- const simulationResults = await super.simulateTx(executionPayload, opts);
248
-
249
- if (opts.fee?.estimateGas) {
250
- const feeOptions = await this.completeFeeOptions({
251
- from: opts.from,
252
- feePayer: executionPayload.feePayer,
253
- gasSettings: opts.fee?.gasSettings,
254
- });
255
- const limits = getGasLimits(simulationResults, opts.fee?.estimatedGasPadding);
256
- printGasEstimates(feeOptions, limits, this.userLog);
257
- }
258
- return simulationResults;
259
- }
260
-
261
308
  /**
262
309
  * Uses a stub account for kernelless simulation, bypassing real account authorization.
263
310
  * Uses DefaultEntrypoint directly for NO_FROM transactions.
@@ -267,7 +314,7 @@ export class CLIWallet extends BaseWallet {
267
314
  opts: SimulateViaEntrypointOptions,
268
315
  ): Promise<TxSimulationResultWithAppOffset> {
269
316
  const { from, feeOptions, additionalScopes, sendMessagesAs } = opts;
270
- const scopes = this.scopesFrom(from, additionalScopes);
317
+ const scopes = this.scopesFrom(from, additionalScopes ?? [], sendMessagesAs);
271
318
  const feeExecutionPayload = await feeOptions.walletFeePaymentMethod?.getExecutionPayload();
272
319
  const finalExecutionPayload = feeExecutionPayload
273
320
  ? mergeExecutionPayloads([feeExecutionPayload, executionPayload])