@aztec/cli-wallet 0.75.0-commit.c03ba01a2a4122e43e90d5133ba017e54b90e9d2
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 +2 -0
- package/dest/bin/index.js +75 -0
- package/dest/cmds/add_authwit.js +4 -0
- package/dest/cmds/add_note.js +14 -0
- package/dest/cmds/authorize_action.js +17 -0
- package/dest/cmds/bridge_fee_juice.js +52 -0
- package/dest/cmds/cancel_tx.js +38 -0
- package/dest/cmds/check_tx.js +11 -0
- package/dest/cmds/create_account.js +98 -0
- package/dest/cmds/create_authwit.js +16 -0
- package/dest/cmds/deploy.js +83 -0
- package/dest/cmds/deploy_account.js +84 -0
- package/dest/cmds/index.js +227 -0
- package/dest/cmds/register_contract.js +14 -0
- package/dest/cmds/register_sender.js +4 -0
- package/dest/cmds/send.js +49 -0
- package/dest/cmds/simulate.js +26 -0
- package/dest/storage/wallet_db.js +180 -0
- package/dest/utils/accounts.js +87 -0
- package/dest/utils/ecdsa.js +13 -0
- package/dest/utils/options/fees.js +189 -0
- package/dest/utils/options/index.js +2 -0
- package/dest/utils/options/options.js +122 -0
- package/dest/utils/pxe_wrapper.js +21 -0
- package/package.json +103 -0
- package/src/bin/index.ts +121 -0
- package/src/cmds/add_authwit.ts +13 -0
- package/src/cmds/add_note.ts +30 -0
- package/src/cmds/authorize_action.ts +36 -0
- package/src/cmds/bridge_fee_juice.ts +88 -0
- package/src/cmds/cancel_tx.ts +61 -0
- package/src/cmds/check_tx.ts +12 -0
- package/src/cmds/create_account.ts +120 -0
- package/src/cmds/create_authwit.ts +35 -0
- package/src/cmds/deploy.ts +113 -0
- package/src/cmds/deploy_account.ts +92 -0
- package/src/cmds/index.ts +674 -0
- package/src/cmds/register_contract.ts +20 -0
- package/src/cmds/register_sender.ts +7 -0
- package/src/cmds/send.ts +62 -0
- package/src/cmds/simulate.ts +42 -0
- package/src/storage/wallet_db.ts +205 -0
- package/src/utils/accounts.ts +102 -0
- package/src/utils/ecdsa.ts +15 -0
- package/src/utils/options/fees.ts +234 -0
- package/src/utils/options/index.ts +2 -0
- package/src/utils/options/options.ts +175 -0
- package/src/utils/pxe_wrapper.ts +26 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type AccountWalletWithSecretKey, type AztecAddress } from '@aztec/aztec.js';
|
|
2
|
+
import { type LogFn } from '@aztec/foundation/log';
|
|
3
|
+
|
|
4
|
+
export async function registerSender(wallet: AccountWalletWithSecretKey, address: AztecAddress, log: LogFn) {
|
|
5
|
+
await wallet.registerSender(address);
|
|
6
|
+
log(`Sender registered: ${address}`);
|
|
7
|
+
}
|
package/src/cmds/send.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type AccountWalletWithSecretKey, type AztecAddress, Contract, Fr } from '@aztec/aztec.js';
|
|
2
|
+
import { GasSettings } from '@aztec/circuits.js';
|
|
3
|
+
import { prepTx } from '@aztec/cli/utils';
|
|
4
|
+
import { type LogFn } from '@aztec/foundation/log';
|
|
5
|
+
|
|
6
|
+
import { type IFeeOpts, printGasEstimates } from '../utils/options/fees.js';
|
|
7
|
+
|
|
8
|
+
export async function send(
|
|
9
|
+
wallet: AccountWalletWithSecretKey,
|
|
10
|
+
functionName: string,
|
|
11
|
+
functionArgsIn: any[],
|
|
12
|
+
contractArtifactPath: string,
|
|
13
|
+
contractAddress: AztecAddress,
|
|
14
|
+
wait: boolean,
|
|
15
|
+
cancellable: boolean,
|
|
16
|
+
feeOpts: IFeeOpts,
|
|
17
|
+
log: LogFn,
|
|
18
|
+
) {
|
|
19
|
+
const { functionArgs, contractArtifact } = await prepTx(contractArtifactPath, functionName, functionArgsIn, log);
|
|
20
|
+
|
|
21
|
+
const contract = await Contract.at(contractAddress, contractArtifact, wallet);
|
|
22
|
+
const call = contract.methods[functionName](...functionArgs);
|
|
23
|
+
|
|
24
|
+
const gasLimits = await call.estimateGas({ ...(await feeOpts.toSendOpts(wallet)) });
|
|
25
|
+
printGasEstimates(feeOpts, gasLimits, log);
|
|
26
|
+
|
|
27
|
+
if (feeOpts.estimateOnly) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const nonce = Fr.random();
|
|
32
|
+
const tx = call.send({ ...(await feeOpts.toSendOpts(wallet)), nonce, cancellable });
|
|
33
|
+
const txHash = await tx.getTxHash();
|
|
34
|
+
log(`\nTransaction hash: ${txHash.toString()}`);
|
|
35
|
+
if (wait) {
|
|
36
|
+
try {
|
|
37
|
+
await tx.wait();
|
|
38
|
+
|
|
39
|
+
log('Transaction has been mined');
|
|
40
|
+
|
|
41
|
+
const receipt = await tx.getReceipt();
|
|
42
|
+
log(` Tx fee: ${receipt.transactionFee}`);
|
|
43
|
+
log(` Status: ${receipt.status}`);
|
|
44
|
+
log(` Block number: ${receipt.blockNumber}`);
|
|
45
|
+
log(` Block hash: ${receipt.blockHash?.toString()}`);
|
|
46
|
+
} catch (err: any) {
|
|
47
|
+
log(`Transaction failed\n ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
log('Transaction pending. Check status with check-tx');
|
|
51
|
+
}
|
|
52
|
+
const gasSettings = GasSettings.from({
|
|
53
|
+
...feeOpts.gasSettings,
|
|
54
|
+
...gasLimits,
|
|
55
|
+
});
|
|
56
|
+
return {
|
|
57
|
+
txHash,
|
|
58
|
+
nonce,
|
|
59
|
+
cancellable,
|
|
60
|
+
gasSettings,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type AccountWalletWithSecretKey, type AztecAddress, Contract, type ProfileResult } from '@aztec/aztec.js';
|
|
2
|
+
import { prepTx } from '@aztec/cli/utils';
|
|
3
|
+
import { type LogFn } from '@aztec/foundation/log';
|
|
4
|
+
|
|
5
|
+
import { format } from 'util';
|
|
6
|
+
|
|
7
|
+
function printProfileResult(result: ProfileResult, log: LogFn) {
|
|
8
|
+
log(format('\nSimulation result:'));
|
|
9
|
+
log(format('Return value:', JSON.stringify(result.returnValues, null, 2)));
|
|
10
|
+
|
|
11
|
+
log(format('\nGate count per circuit:'));
|
|
12
|
+
let acc = 0;
|
|
13
|
+
result.gateCounts.forEach(r => {
|
|
14
|
+
acc += r.gateCount;
|
|
15
|
+
log(format(' ', r.circuitName.padEnd(50), 'Gates:', r.gateCount.toLocaleString(), '\tAcc:', acc.toLocaleString()));
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
log(format('\nTotal gates:', acc.toLocaleString()));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function simulate(
|
|
22
|
+
wallet: AccountWalletWithSecretKey,
|
|
23
|
+
functionName: string,
|
|
24
|
+
functionArgsIn: any[],
|
|
25
|
+
contractArtifactPath: string,
|
|
26
|
+
contractAddress: AztecAddress,
|
|
27
|
+
profile: boolean,
|
|
28
|
+
log: LogFn,
|
|
29
|
+
) {
|
|
30
|
+
const { functionArgs, contractArtifact } = await prepTx(contractArtifactPath, functionName, functionArgsIn, log);
|
|
31
|
+
|
|
32
|
+
const contract = await Contract.at(contractAddress, contractArtifact, wallet);
|
|
33
|
+
const call = contract.methods[functionName](...functionArgs);
|
|
34
|
+
|
|
35
|
+
if (profile) {
|
|
36
|
+
const result = await call.simulateWithProfile();
|
|
37
|
+
printProfileResult(result, log);
|
|
38
|
+
} else {
|
|
39
|
+
const result = await call.simulate();
|
|
40
|
+
log(format('\nSimulation result: ', result, '\n'));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { type AuthWitness, type TxHash } from '@aztec/circuit-types';
|
|
2
|
+
import { type AztecAddress, Fr, GasSettings } from '@aztec/circuits.js';
|
|
3
|
+
import { type LogFn } from '@aztec/foundation/log';
|
|
4
|
+
import { type AztecKVStore, type AztecMap } from '@aztec/kv-store';
|
|
5
|
+
|
|
6
|
+
import { type AccountType } from '../utils/accounts.js';
|
|
7
|
+
import { extractECDSAPublicKeyFromBase64String } from '../utils/ecdsa.js';
|
|
8
|
+
|
|
9
|
+
export const Aliases = ['accounts', 'contracts', 'artifacts', 'secrets', 'transactions', 'authwits'] as const;
|
|
10
|
+
export type AliasType = (typeof Aliases)[number];
|
|
11
|
+
|
|
12
|
+
export class WalletDB {
|
|
13
|
+
#accounts!: AztecMap<string, Buffer>;
|
|
14
|
+
#aliases!: AztecMap<string, Buffer>;
|
|
15
|
+
#bridgedFeeJuice!: AztecMap<string, Buffer>;
|
|
16
|
+
#transactions!: AztecMap<string, Buffer>;
|
|
17
|
+
|
|
18
|
+
private static instance: WalletDB;
|
|
19
|
+
|
|
20
|
+
static getInstance() {
|
|
21
|
+
if (!WalletDB.instance) {
|
|
22
|
+
WalletDB.instance = new WalletDB();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return WalletDB.instance;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
init(store: AztecKVStore) {
|
|
29
|
+
this.#accounts = store.openMap('accounts');
|
|
30
|
+
this.#aliases = store.openMap('aliases');
|
|
31
|
+
this.#bridgedFeeJuice = store.openMap('bridgedFeeJuice');
|
|
32
|
+
this.#transactions = store.openMap('transactions');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async pushBridgedFeeJuice(recipient: AztecAddress, secret: Fr, amount: bigint, leafIndex: bigint, log: LogFn) {
|
|
36
|
+
let stackPointer = this.#bridgedFeeJuice.get(`${recipient.toString()}:stackPointer`)?.readInt8() || 0;
|
|
37
|
+
stackPointer++;
|
|
38
|
+
await this.#bridgedFeeJuice.set(
|
|
39
|
+
`${recipient.toString()}:${stackPointer}`,
|
|
40
|
+
Buffer.from(`${amount.toString()}:${secret.toString()}:${leafIndex.toString()}`),
|
|
41
|
+
);
|
|
42
|
+
await this.#bridgedFeeJuice.set(`${recipient.toString()}:stackPointer`, Buffer.from([stackPointer]));
|
|
43
|
+
log(`Pushed ${amount} fee juice for recipient ${recipient.toString()}. Stack pointer ${stackPointer}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async popBridgedFeeJuice(recipient: AztecAddress, log: LogFn) {
|
|
47
|
+
let stackPointer = this.#bridgedFeeJuice.get(`${recipient.toString()}:stackPointer`)?.readInt8() || 0;
|
|
48
|
+
const result = this.#bridgedFeeJuice.get(`${recipient.toString()}:${stackPointer}`);
|
|
49
|
+
if (!result) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`No stored fee juice available for recipient ${recipient.toString()}. Please provide claim amount and secret. Stack pointer ${stackPointer}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
const [amountStr, secretStr, leafIndexStr] = result.toString().split(':');
|
|
55
|
+
await this.#bridgedFeeJuice.set(`${recipient.toString()}:stackPointer`, Buffer.from([--stackPointer]));
|
|
56
|
+
log(`Retrieved ${amountStr} fee juice for recipient ${recipient.toString()}. Stack pointer ${stackPointer}`);
|
|
57
|
+
return { amount: BigInt(amountStr), secret: secretStr, leafIndex: BigInt(leafIndexStr) };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async storeAccount(
|
|
61
|
+
address: AztecAddress,
|
|
62
|
+
{
|
|
63
|
+
type,
|
|
64
|
+
secretKey,
|
|
65
|
+
salt,
|
|
66
|
+
alias,
|
|
67
|
+
publicKey,
|
|
68
|
+
}: { type: AccountType; secretKey: Fr; salt: Fr; alias: string | undefined; publicKey: string | undefined },
|
|
69
|
+
log: LogFn,
|
|
70
|
+
) {
|
|
71
|
+
if (alias) {
|
|
72
|
+
await this.#aliases.set(`accounts:${alias}`, Buffer.from(address.toString()));
|
|
73
|
+
}
|
|
74
|
+
await this.#accounts.set(`${address.toString()}:type`, Buffer.from(type));
|
|
75
|
+
await this.#accounts.set(`${address.toString()}:sk`, secretKey.toBuffer());
|
|
76
|
+
await this.#accounts.set(`${address.toString()}:salt`, salt.toBuffer());
|
|
77
|
+
if (type === 'ecdsasecp256r1ssh' && publicKey) {
|
|
78
|
+
const publicSigningKey = extractECDSAPublicKeyFromBase64String(publicKey);
|
|
79
|
+
await this.storeAccountMetadata(address, 'publicSigningKey', publicSigningKey);
|
|
80
|
+
}
|
|
81
|
+
await this.#aliases.set('accounts:last', Buffer.from(address.toString()));
|
|
82
|
+
log(`Account stored in database with alias${alias ? `es last & ${alias}` : ' last'}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async storeSender(address: AztecAddress, alias: string, log: LogFn) {
|
|
86
|
+
await this.#aliases.set(`accounts:${alias}`, Buffer.from(address.toString()));
|
|
87
|
+
log(`Account stored in database with alias ${alias} as a sender`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async storeContract(address: AztecAddress, artifactPath: string, log: LogFn, alias?: string) {
|
|
91
|
+
if (alias) {
|
|
92
|
+
await this.#aliases.set(`contracts:${alias}`, Buffer.from(address.toString()));
|
|
93
|
+
await this.#aliases.set(`artifacts:${alias}`, Buffer.from(artifactPath));
|
|
94
|
+
}
|
|
95
|
+
await this.#aliases.set(`contracts:last`, Buffer.from(address.toString()));
|
|
96
|
+
await this.#aliases.set(`artifacts:last`, Buffer.from(artifactPath));
|
|
97
|
+
await this.#aliases.set(`artifacts:${address.toString()}`, Buffer.from(artifactPath));
|
|
98
|
+
log(`Contract stored in database with alias${alias ? `es last & ${alias}` : ' last'}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async storeAuthwitness(authWit: AuthWitness, log: LogFn, alias?: string) {
|
|
102
|
+
if (alias) {
|
|
103
|
+
await this.#aliases.set(`authwits:${alias}`, Buffer.from(authWit.toString()));
|
|
104
|
+
}
|
|
105
|
+
await this.#aliases.set(`authwits:last`, Buffer.from(authWit.toString()));
|
|
106
|
+
log(`Authorization witness stored in database with alias${alias ? `es last & ${alias}` : ' last'}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async storeTx(
|
|
110
|
+
{
|
|
111
|
+
txHash,
|
|
112
|
+
nonce,
|
|
113
|
+
cancellable,
|
|
114
|
+
gasSettings,
|
|
115
|
+
}: { txHash: TxHash; nonce: Fr; cancellable: boolean; gasSettings: GasSettings },
|
|
116
|
+
log: LogFn,
|
|
117
|
+
alias?: string,
|
|
118
|
+
) {
|
|
119
|
+
if (alias) {
|
|
120
|
+
await this.#aliases.set(`transactions:${alias}`, Buffer.from(txHash.toString()));
|
|
121
|
+
}
|
|
122
|
+
await this.#transactions.set(`${txHash.toString()}:nonce`, nonce.toBuffer());
|
|
123
|
+
await this.#transactions.set(`${txHash.toString()}:cancellable`, Buffer.from(cancellable ? 'true' : 'false'));
|
|
124
|
+
await this.#transactions.set(`${txHash.toString()}:gasSettings`, gasSettings.toBuffer());
|
|
125
|
+
await this.#aliases.set(`transactions:last`, Buffer.from(txHash.toString()));
|
|
126
|
+
log(`Transaction hash stored in database with alias${alias ? `es last & ${alias}` : ' last'}`);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
retrieveTxData(txHash: TxHash) {
|
|
130
|
+
const nonceBuffer = this.#transactions.get(`${txHash.toString()}:nonce`);
|
|
131
|
+
if (!nonceBuffer) {
|
|
132
|
+
throw new Error(
|
|
133
|
+
`Could not find ${txHash.toString()}:nonce. Transaction with hash "${txHash.toString()}" does not exist on this wallet.`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
const nonce = Fr.fromBuffer(nonceBuffer);
|
|
137
|
+
const cancellable = this.#transactions.get(`${txHash.toString()}:cancellable`)!.toString() === 'true';
|
|
138
|
+
const gasBuffer = this.#transactions.get(`${txHash.toString()}:gasSettings`)!;
|
|
139
|
+
return { txHash, nonce, cancellable, gasSettings: GasSettings.fromBuffer(gasBuffer) };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
tryRetrieveAlias(arg: string) {
|
|
143
|
+
try {
|
|
144
|
+
return this.retrieveAlias(arg);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
return arg;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
retrieveAlias(arg: string) {
|
|
151
|
+
if (Aliases.find(alias => arg.startsWith(`${alias}:`))) {
|
|
152
|
+
const [type, ...alias] = arg.split(':');
|
|
153
|
+
const data = this.#aliases.get(`${type}:${alias.join(':') ?? 'last'}`);
|
|
154
|
+
if (!data) {
|
|
155
|
+
throw new Error(`Could not find alias ${arg}`);
|
|
156
|
+
}
|
|
157
|
+
return data.toString();
|
|
158
|
+
} else {
|
|
159
|
+
throw new Error(`Aliases must start with one of ${Aliases.join(', ')}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
listAliases(type?: AliasType) {
|
|
164
|
+
const result = [];
|
|
165
|
+
if (type && !Aliases.includes(type)) {
|
|
166
|
+
throw new Error(`Unknown alias type ${type}`);
|
|
167
|
+
}
|
|
168
|
+
for (const [key, value] of this.#aliases.entries()) {
|
|
169
|
+
if (!type || key.startsWith(`${type}:`)) {
|
|
170
|
+
result.push({ key, value: value.toString() });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async storeAccountMetadata(aliasOrAddress: AztecAddress | string, metadataKey: string, metadata: Buffer) {
|
|
177
|
+
const { address } = this.retrieveAccount(aliasOrAddress);
|
|
178
|
+
await this.#accounts.set(`${address.toString()}:${metadataKey}`, metadata);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
retrieveAccountMetadata(aliasOrAddress: AztecAddress | string, metadataKey: string) {
|
|
182
|
+
const { address } = this.retrieveAccount(aliasOrAddress);
|
|
183
|
+
const result = this.#accounts.get(`${address.toString()}:${metadataKey}`);
|
|
184
|
+
if (!result) {
|
|
185
|
+
throw new Error(`Could not find metadata with key ${metadataKey} for account ${aliasOrAddress}`);
|
|
186
|
+
}
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
retrieveAccount(address: AztecAddress | string) {
|
|
191
|
+
const secretKeyBuffer = this.#accounts.get(`${address.toString()}:sk`);
|
|
192
|
+
if (!secretKeyBuffer) {
|
|
193
|
+
throw new Error(`Could not find ${address}:sk. Account "${address.toString}" does not exist on this wallet.`);
|
|
194
|
+
}
|
|
195
|
+
const secretKey = Fr.fromBuffer(secretKeyBuffer);
|
|
196
|
+
const salt = Fr.fromBuffer(this.#accounts.get(`${address.toString()}:salt`)!);
|
|
197
|
+
const type = this.#accounts.get(`${address.toString()}:type`)!.toString('utf8') as AccountType;
|
|
198
|
+
return { address, secretKey, salt, type };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async storeAlias(type: AliasType, key: string, value: Buffer, log: LogFn) {
|
|
202
|
+
await this.#aliases.set(`${type}:${key}`, value);
|
|
203
|
+
log(`Data stored in database with alias ${type}:${key}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { getIdentities } from '@aztec/accounts/utils';
|
|
2
|
+
import { type AccountManager, type AccountWalletWithSecretKey } from '@aztec/aztec.js';
|
|
3
|
+
import { type PXE } from '@aztec/circuit-types/interfaces';
|
|
4
|
+
import { deriveSigningKey } from '@aztec/circuits.js/keys';
|
|
5
|
+
import { AztecAddress } from '@aztec/foundation/aztec-address';
|
|
6
|
+
import { Fr } from '@aztec/foundation/fields';
|
|
7
|
+
|
|
8
|
+
import { type WalletDB } from '../storage/wallet_db.js';
|
|
9
|
+
import { extractECDSAPublicKeyFromBase64String } from './ecdsa.js';
|
|
10
|
+
|
|
11
|
+
export const AccountTypes = ['schnorr', 'ecdsasecp256r1ssh', 'ecdsasecp256k1'] as const;
|
|
12
|
+
export type AccountType = (typeof AccountTypes)[number];
|
|
13
|
+
|
|
14
|
+
export async function createOrRetrieveAccount(
|
|
15
|
+
pxe: PXE,
|
|
16
|
+
address?: AztecAddress,
|
|
17
|
+
db?: WalletDB,
|
|
18
|
+
secretKey?: Fr,
|
|
19
|
+
type: AccountType = 'schnorr',
|
|
20
|
+
salt?: Fr,
|
|
21
|
+
publicKey?: string | undefined,
|
|
22
|
+
) {
|
|
23
|
+
let account;
|
|
24
|
+
|
|
25
|
+
salt ??= Fr.ZERO;
|
|
26
|
+
|
|
27
|
+
if (db && address) {
|
|
28
|
+
({ type, secretKey, salt } = db.retrieveAccount(address));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!salt) {
|
|
32
|
+
throw new Error('Cannot retrieve/create wallet without salt');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!secretKey) {
|
|
36
|
+
throw new Error('Cannot retrieve/create wallet without secret key');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
switch (type) {
|
|
40
|
+
case 'schnorr': {
|
|
41
|
+
const { getSchnorrAccount } = await import('@aztec/accounts/schnorr');
|
|
42
|
+
account = getSchnorrAccount(pxe, secretKey, deriveSigningKey(secretKey), salt);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case 'ecdsasecp256r1ssh': {
|
|
46
|
+
let publicSigningKey;
|
|
47
|
+
if (db && address) {
|
|
48
|
+
publicSigningKey = db.retrieveAccountMetadata(address, 'publicSigningKey');
|
|
49
|
+
} else if (publicKey) {
|
|
50
|
+
const identities = await getIdentities();
|
|
51
|
+
const foundIdentity = identities.find(
|
|
52
|
+
identity => identity.type === 'ecdsa-sha2-nistp256' && identity.publicKey === publicKey,
|
|
53
|
+
);
|
|
54
|
+
if (!foundIdentity) {
|
|
55
|
+
throw new Error(`Identity for public key ${publicKey} not found in the SSH agent`);
|
|
56
|
+
}
|
|
57
|
+
publicSigningKey = extractECDSAPublicKeyFromBase64String(foundIdentity.publicKey);
|
|
58
|
+
} else {
|
|
59
|
+
throw new Error('Public key must be provided for ECDSA SSH account');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const { getEcdsaRSSHAccount } = await import('@aztec/accounts/ecdsa');
|
|
63
|
+
account = getEcdsaRSSHAccount(pxe, secretKey, publicSigningKey, salt);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
default: {
|
|
67
|
+
throw new Error(`Unsupported account type: ${type}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return account;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function addScopeToWallet(wallet: AccountWalletWithSecretKey, scope: AztecAddress, db?: WalletDB) {
|
|
75
|
+
const address = wallet.getAddress().toString();
|
|
76
|
+
const currentScopes = wallet.getScopes() ?? [];
|
|
77
|
+
const deduplicatedScopes = Array.from(
|
|
78
|
+
new Set([address, ...currentScopes, scope].map(scope => scope.toString())).values(),
|
|
79
|
+
);
|
|
80
|
+
if (db) {
|
|
81
|
+
await db.storeAccountMetadata(wallet.getAddress(), 'scopes', Buffer.from(deduplicatedScopes.join(',')));
|
|
82
|
+
}
|
|
83
|
+
wallet.setScopes(deduplicatedScopes.map(scope => AztecAddress.fromString(scope)));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function getWalletWithScopes(account: AccountManager, db?: WalletDB) {
|
|
87
|
+
const wallet = await account.getWallet();
|
|
88
|
+
if (db) {
|
|
89
|
+
const address = wallet.getAddress().toString();
|
|
90
|
+
let storedScopes: string[] = [];
|
|
91
|
+
try {
|
|
92
|
+
storedScopes = (db.retrieveAccountMetadata(wallet.getAddress(), 'scopes') ?? '').toString().split(',');
|
|
93
|
+
// eslint-disable-next-line no-empty
|
|
94
|
+
} catch {}
|
|
95
|
+
const currentScopes = wallet.getScopes()?.map(scopes => scopes.toString()) ?? [];
|
|
96
|
+
const deduplicatedScopes = Array.from(new Set([address, ...currentScopes, ...storedScopes]).values()).map(scope =>
|
|
97
|
+
AztecAddress.fromString(scope),
|
|
98
|
+
);
|
|
99
|
+
wallet.setScopes(deduplicatedScopes);
|
|
100
|
+
}
|
|
101
|
+
return wallet;
|
|
102
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function extractECDSAPublicKeyFromBase64String(base64PublicKey: string): Buffer {
|
|
2
|
+
const buffer = Buffer.from(base64PublicKey, 'base64');
|
|
3
|
+
let keyOffset = 0;
|
|
4
|
+
const typeLen = buffer.readUInt32BE(keyOffset);
|
|
5
|
+
keyOffset += 4;
|
|
6
|
+
keyOffset += typeLen;
|
|
7
|
+
|
|
8
|
+
const curveLen = buffer.readUInt32BE(keyOffset);
|
|
9
|
+
keyOffset += 4;
|
|
10
|
+
keyOffset += curveLen;
|
|
11
|
+
|
|
12
|
+
const keyLen = buffer.readUInt32BE(keyOffset);
|
|
13
|
+
keyOffset += 5; // 4+1 to remove the prefix
|
|
14
|
+
return buffer.subarray(keyOffset, keyOffset + keyLen - 1);
|
|
15
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { type AccountWallet, type FeePaymentMethod, type PXE, type SendMethodOptions } from '@aztec/aztec.js';
|
|
2
|
+
import { AztecAddress, Fr, Gas, GasFees, GasSettings } from '@aztec/circuits.js';
|
|
3
|
+
import { type LogFn } from '@aztec/foundation/log';
|
|
4
|
+
|
|
5
|
+
import { Option } from 'commander';
|
|
6
|
+
|
|
7
|
+
import { type WalletDB } from '../../storage/wallet_db.js';
|
|
8
|
+
import { aliasedAddressParser } from './options.js';
|
|
9
|
+
|
|
10
|
+
export type CliFeeArgs = {
|
|
11
|
+
estimateGasOnly: boolean;
|
|
12
|
+
gasLimits?: string;
|
|
13
|
+
payment?: string;
|
|
14
|
+
maxFeesPerGas?: string;
|
|
15
|
+
maxPriorityFeesPerGas?: string;
|
|
16
|
+
estimateGas?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export interface IFeeOpts {
|
|
20
|
+
estimateOnly: boolean;
|
|
21
|
+
gasSettings: GasSettings;
|
|
22
|
+
toSendOpts(sender: AccountWallet): Promise<SendMethodOptions>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function printGasEstimates(
|
|
26
|
+
feeOpts: IFeeOpts,
|
|
27
|
+
gasEstimates: Pick<GasSettings, 'gasLimits' | 'teardownGasLimits'>,
|
|
28
|
+
log: LogFn,
|
|
29
|
+
) {
|
|
30
|
+
log(`Estimated gas usage: ${formatGasEstimate(gasEstimates)}`);
|
|
31
|
+
log(`Maximum total tx fee: ${getEstimatedCost(gasEstimates, feeOpts.gasSettings.maxFeesPerGas)}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function formatGasEstimate(estimate: Pick<GasSettings, 'gasLimits' | 'teardownGasLimits'>) {
|
|
35
|
+
return `da=${estimate.gasLimits.daGas},l2=${estimate.gasLimits.l2Gas},teardownDA=${estimate.teardownGasLimits.daGas},teardownL2=${estimate.teardownGasLimits.l2Gas}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getEstimatedCost(estimate: Pick<GasSettings, 'gasLimits' | 'teardownGasLimits'>, maxFeesPerGas: GasFees) {
|
|
39
|
+
return GasSettings.default({ ...estimate, maxFeesPerGas })
|
|
40
|
+
.getFeeLimit()
|
|
41
|
+
.toBigInt();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export class FeeOpts implements IFeeOpts {
|
|
45
|
+
constructor(
|
|
46
|
+
public estimateOnly: boolean,
|
|
47
|
+
public gasSettings: GasSettings,
|
|
48
|
+
private paymentMethodFactory: (sender: AccountWallet) => Promise<FeePaymentMethod>,
|
|
49
|
+
private estimateGas: boolean,
|
|
50
|
+
) {}
|
|
51
|
+
|
|
52
|
+
async toSendOpts(sender: AccountWallet): Promise<SendMethodOptions> {
|
|
53
|
+
return {
|
|
54
|
+
fee: {
|
|
55
|
+
estimateGas: this.estimateGas,
|
|
56
|
+
gasSettings: this.gasSettings,
|
|
57
|
+
paymentMethod: await this.paymentMethodFactory(sender),
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static paymentMethodOption() {
|
|
63
|
+
return new Option(
|
|
64
|
+
'--payment <method=name,asset=address,fpc=address,claimSecret=string,claimAmount=string,feeRecipient=string>',
|
|
65
|
+
'Fee payment method and arguments. Valid methods are: none, fee_juice, fpc-public, fpc-private.',
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static getOptions() {
|
|
70
|
+
return [
|
|
71
|
+
new Option('--gas-limits <da=100,l2=100,teardownDA=10,teardownL2=10>', 'Gas limits for the tx.'),
|
|
72
|
+
FeeOpts.paymentMethodOption(),
|
|
73
|
+
new Option('--max-fees-per-gas <da=100,l2=100>', 'Maximum fees per gas unit for DA and L2 computation.'),
|
|
74
|
+
new Option(
|
|
75
|
+
'--max-priority-fees-per-gas <da=0,l2=0>',
|
|
76
|
+
'Maximum priority fees per gas unit for DA and L2 computation.',
|
|
77
|
+
),
|
|
78
|
+
new Option('--no-estimate-gas', 'Whether to automatically estimate gas limits for the tx.'),
|
|
79
|
+
new Option('--estimate-gas-only', 'Only report gas estimation for the tx, do not send it.'),
|
|
80
|
+
];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static async fromCli(args: CliFeeArgs, pxe: PXE, log: LogFn, db?: WalletDB) {
|
|
84
|
+
const estimateOnly = args.estimateGasOnly;
|
|
85
|
+
const gasLimits = args.gasLimits ? parseGasLimits(args.gasLimits) : {};
|
|
86
|
+
const maxFeesPerGas = args.maxFeesPerGas ? parseGasFees(args.maxFeesPerGas) : await pxe.getCurrentBaseFees();
|
|
87
|
+
const maxPriorityFeesPerGas = args.maxPriorityFeesPerGas ? parseGasFees(args.maxPriorityFeesPerGas) : undefined;
|
|
88
|
+
const gasSettings = GasSettings.default({
|
|
89
|
+
...gasLimits,
|
|
90
|
+
maxFeesPerGas,
|
|
91
|
+
maxPriorityFeesPerGas,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
if (!args.gasLimits && !args.payment) {
|
|
95
|
+
return new NoFeeOpts(estimateOnly, gasSettings);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const defaultPaymentMethod = async () => {
|
|
99
|
+
const { NoFeePaymentMethod } = await import('@aztec/aztec.js/fee');
|
|
100
|
+
return new NoFeePaymentMethod();
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
return new FeeOpts(
|
|
104
|
+
estimateOnly,
|
|
105
|
+
gasSettings,
|
|
106
|
+
args.payment ? parsePaymentMethod(args.payment, log, db) : defaultPaymentMethod,
|
|
107
|
+
!!args.estimateGas,
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
class NoFeeOpts implements IFeeOpts {
|
|
113
|
+
constructor(public estimateOnly: boolean, public gasSettings: GasSettings) {}
|
|
114
|
+
|
|
115
|
+
toSendOpts(): Promise<SendMethodOptions> {
|
|
116
|
+
return Promise.resolve({});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function parsePaymentMethod(
|
|
121
|
+
payment: string,
|
|
122
|
+
log: LogFn,
|
|
123
|
+
db?: WalletDB,
|
|
124
|
+
): (sender: AccountWallet) => Promise<FeePaymentMethod> {
|
|
125
|
+
const parsed = payment.split(',').reduce((acc, item) => {
|
|
126
|
+
const [dimension, value] = item.split('=');
|
|
127
|
+
acc[dimension] = value ?? 1;
|
|
128
|
+
return acc;
|
|
129
|
+
}, {} as Record<string, string>);
|
|
130
|
+
|
|
131
|
+
const getFpcOpts = (parsed: Record<string, string>, db?: WalletDB) => {
|
|
132
|
+
if (!parsed.fpc) {
|
|
133
|
+
throw new Error('Missing "fpc" in payment option');
|
|
134
|
+
}
|
|
135
|
+
if (!parsed.asset) {
|
|
136
|
+
throw new Error('Missing "asset" in payment option');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const fpc = aliasedAddressParser('contracts', parsed.fpc, db);
|
|
140
|
+
|
|
141
|
+
return [AztecAddress.fromString(parsed.asset), fpc];
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
return async (sender: AccountWallet) => {
|
|
145
|
+
switch (parsed.method) {
|
|
146
|
+
case 'none': {
|
|
147
|
+
log('Using no fee payment');
|
|
148
|
+
const { NoFeePaymentMethod } = await import('@aztec/aztec.js/fee');
|
|
149
|
+
return new NoFeePaymentMethod();
|
|
150
|
+
}
|
|
151
|
+
case 'native': {
|
|
152
|
+
if (parsed.claim || (parsed.claimSecret && parsed.claimAmount && parsed.messageLeafIndex)) {
|
|
153
|
+
let claimAmount, claimSecret, messageLeafIndex;
|
|
154
|
+
if (parsed.claim && db) {
|
|
155
|
+
({
|
|
156
|
+
amount: claimAmount,
|
|
157
|
+
secret: claimSecret,
|
|
158
|
+
leafIndex: messageLeafIndex,
|
|
159
|
+
} = await db.popBridgedFeeJuice(sender.getAddress(), log));
|
|
160
|
+
} else {
|
|
161
|
+
({ claimAmount, claimSecret, messageLeafIndex } = parsed);
|
|
162
|
+
}
|
|
163
|
+
log(`Using Fee Juice for fee payments with claim for ${claimAmount} tokens`);
|
|
164
|
+
const { FeeJuicePaymentMethodWithClaim } = await import('@aztec/aztec.js/fee');
|
|
165
|
+
return new FeeJuicePaymentMethodWithClaim(sender.getAddress(), {
|
|
166
|
+
claimAmount: (typeof claimAmount === 'string'
|
|
167
|
+
? Fr.fromHexString(claimAmount)
|
|
168
|
+
: new Fr(claimAmount)
|
|
169
|
+
).toBigInt(),
|
|
170
|
+
claimSecret: Fr.fromHexString(claimSecret),
|
|
171
|
+
messageLeafIndex: BigInt(messageLeafIndex),
|
|
172
|
+
});
|
|
173
|
+
} else {
|
|
174
|
+
log(`Using Fee Juice for fee payment`);
|
|
175
|
+
const { FeeJuicePaymentMethod } = await import('@aztec/aztec.js/fee');
|
|
176
|
+
return new FeeJuicePaymentMethod(sender.getAddress());
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
case 'fpc-public': {
|
|
180
|
+
const [asset, fpc] = getFpcOpts(parsed, db);
|
|
181
|
+
log(`Using public fee payment with asset ${asset} via paymaster ${fpc}`);
|
|
182
|
+
const { PublicFeePaymentMethod } = await import('@aztec/aztec.js/fee');
|
|
183
|
+
return new PublicFeePaymentMethod(fpc, sender);
|
|
184
|
+
}
|
|
185
|
+
case 'fpc-private': {
|
|
186
|
+
const [asset, fpc] = getFpcOpts(parsed, db);
|
|
187
|
+
log(`Using private fee payment with asset ${asset} via paymaster ${fpc}`);
|
|
188
|
+
const { PrivateFeePaymentMethod } = await import('@aztec/aztec.js/fee');
|
|
189
|
+
return new PrivateFeePaymentMethod(fpc, sender);
|
|
190
|
+
}
|
|
191
|
+
case undefined:
|
|
192
|
+
throw new Error('Missing "method" in payment option');
|
|
193
|
+
default:
|
|
194
|
+
throw new Error(`Invalid fee payment method: ${payment}`);
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function parseGasLimits(gasLimits: string): { gasLimits: Gas; teardownGasLimits: Gas } {
|
|
200
|
+
const parsed = gasLimits.split(',').reduce((acc, limit) => {
|
|
201
|
+
const [dimension, value] = limit.split('=');
|
|
202
|
+
acc[dimension] = parseInt(value, 10);
|
|
203
|
+
return acc;
|
|
204
|
+
}, {} as Record<string, number>);
|
|
205
|
+
|
|
206
|
+
const expected = ['da', 'l2', 'teardownDA', 'teardownL2'];
|
|
207
|
+
for (const dimension of expected) {
|
|
208
|
+
if (!(dimension in parsed)) {
|
|
209
|
+
throw new Error(`Missing gas limit for ${dimension}`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
gasLimits: new Gas(parsed.da, parsed.l2),
|
|
215
|
+
teardownGasLimits: new Gas(parsed.teardownDA, parsed.teardownL2),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function parseGasFees(gasFees: string): GasFees {
|
|
220
|
+
const parsed = gasFees.split(',').reduce((acc, fee) => {
|
|
221
|
+
const [dimension, value] = fee.split('=');
|
|
222
|
+
acc[dimension] = parseInt(value, 10);
|
|
223
|
+
return acc;
|
|
224
|
+
}, {} as Record<string, number>);
|
|
225
|
+
|
|
226
|
+
const expected = ['da', 'l2'];
|
|
227
|
+
for (const dimension of expected) {
|
|
228
|
+
if (!(dimension in parsed)) {
|
|
229
|
+
throw new Error(`Missing gas fee for ${dimension}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return new GasFees(parsed.da, parsed.l2);
|
|
234
|
+
}
|