Skip to content

Commit

Permalink
feat!: cli wallet with fee opts + private transfer flow (#7856)
Browse files Browse the repository at this point in the history
Further improvements to `aztec-wallet`, supporting new commands:

- **bridge-fee-juice**: Same as old version, but supports alias for the
recipient
```bash
aztec-wallet bridge-fee-juice 100000000000000000 accounts:main --mint
```
- **add-secret** : generates aliased secrets (and their hashes) that can
be later used as `secrets:my_secret(:hash)`
- **add-note**: Adds notes to PXE, reading from the contract interface
so no "magic numbers" are required
```bash
aztec-wallet add-secret -a shield
aztec-wallet send mint_private -ca contracts:last --args 42 secrets:shield:hash -f main

# Note that the contract interface doesn't have to be specified since the wallet nows about the last contract that 
# was deployed. Also transactions now store the `txHash` in `transactions:last`
# (and the `send` method supports a `-a` param for custom aliases)
aztec-wallet add-note TransparentNote pending_shields -ca contracts:last -h transactions:last -a accounts:main -b 42 secrets:shield:hash
```

Also, fee options now accept aliases for the things that make sense:

`--payment method=fpc-public,fpc=contracts:my_fpc`

It is also possible to claim bridged fee juice without passing secrets
around, using a stack:

```bash
# Pushes the claim to the stack, with sender, amount and secret
aztec-wallet bridge-fee-juice 100000000000000000 accounts:main --mint

# Retrieves the last one on the stack, only the claim keyword is needed
aztec-wallet deploy-account -f main --payment method=native,claim
```

## Testing

In order not to break things constantly, the most interesting flows
should be added as e2e tests (using a sandbox as target) inside
`./test/flows`. A few examples are provided showcasing old and new
features.

![Screenshot 2024-08-09 at 16 36
31](https://github.com/user-attachments/assets/d8188da2-63a4-4f13-bf83-fcb71a8cdee4)

## Breaking changes

- `-ac, --account` param has been renamed to `-f, --from` for improved
readability
  • Loading branch information
Thunkar authored Aug 12, 2024
1 parent b7efd07 commit 1459360
Show file tree
Hide file tree
Showing 33 changed files with 671 additions and 314 deletions.
1 change: 1 addition & 0 deletions noir-projects/noir-contracts/aztec
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// deploy-protocol-contracts //
1 change: 1 addition & 0 deletions yarn-project/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ noir-contracts.js/src
noir-contracts.js/artifacts/
noir-contracts.js/codegenCache.json
types/fixtures/*.json
cli-wallet/test/data

.yarn/*
!.yarn/patches
Expand Down
52 changes: 20 additions & 32 deletions yarn-project/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,18 @@ build-dev:
FROM +build
SAVE ARTIFACT /usr/src /usr/src

bb-cli:
cli-base:
FROM +build
# Remove a bunch of stuff that we don't need that takes up space.
RUN rm -rf \
../noir-projects \
../l1-contracts \
../barretenberg/ts/src \
../barretenberg/ts/dest/node-cjs \
../barretenberg/ts/dest/browser

bb-cli:
FROM +cli-base

ENV BB_WORKING_DIRECTORY=/usr/src/bb
ENV BB_BINARY_PATH=/usr/src/barretenberg/cpp/build/bin/bb
Expand All @@ -66,11 +76,6 @@ bb-cli:

RUN yarn workspaces focus @aztec/bb-prover --production && yarn cache clean
RUN rm -rf \
../noir-projects \
../l1-contracts \
../barretenberg/ts/src \
../barretenberg/ts/dest/node-cjs \
../barretenberg/ts/dest/browser \
aztec.js/dest/main.js \
end-to-end \
**/src
Expand Down Expand Up @@ -114,28 +119,23 @@ rollup-verifier-contract:
RUN --entrypoint write-contract -c RootRollupArtifact -n UltraVerifier.sol
SAVE ARTIFACT /usr/src/bb /usr/src/bb

cli-base:
FROM +build
# Remove a bunch of stuff that we don't need that takes up space.
RUN rm -rf \
../noir-projects \
../l1-contracts \
../barretenberg/ts/src \
../barretenberg/ts/dest/node-cjs \
../barretenberg/ts/dest/browser

txe:
FROM +cli-base
RUN yarn workspaces focus @aztec/txe --production && yarn cache clean
ENTRYPOINT ["node", "--no-warnings", "/usr/src/yarn-project/txe/dest/bin/index.js"]
SAVE ARTIFACT /usr/src /usr/src

cli-wallet:
cli-wallet-build:
FROM +cli-base
RUN yarn workspaces focus @aztec/cli-wallet --production && yarn cache clean
ENTRYPOINT ["node", "--no-warnings", "/usr/src/yarn-project/cli-wallet/dest/bin/index.js"]
SAVE ARTIFACT /usr/src /usr/src

cli-wallet:
FROM ubuntu:noble
RUN apt update && apt install nodejs curl -y && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
COPY +cli-wallet-build/usr/src /usr/src
ENTRYPOINT ["node", "--no-warnings", "/usr/src/yarn-project/cli-wallet/dest/bin/index.js"]

export-cli-wallet:
FROM +cli-wallet
ARG DIST_TAG="latest"
Expand Down Expand Up @@ -165,14 +165,9 @@ aztec:
EXPOSE $port

aztec-faucet-build:
FROM +build
FROM +cli-base
RUN yarn workspaces focus @aztec/aztec-faucet --production && yarn cache clean
RUN rm -rf \
../noir-projects \
../l1-contracts \
../barretenberg/ts/src \
../barretenberg/ts/dest/node-cjs \
../barretenberg/ts/dest/browser \
aztec.js/dest/main.js \
end-to-end \
**/src
Expand All @@ -193,15 +188,8 @@ export-aztec-faucet:

# We care about creating a slimmed down e2e image because we have to serialize it from earthly to docker for running.
end-to-end-prod:
FROM +build
FROM +cli-base
RUN yarn workspaces focus @aztec/end-to-end --production && yarn cache clean
# Remove a bunch of stuff that we don't need that takes up space.
RUN rm -rf \
../noir-projects \
../l1-contracts \
../barretenberg/ts/src \
../barretenberg/ts/dest/node-cjs \
../barretenberg/ts/dest/browser
COPY --dir +rollup-verifier-contract/usr/src/bb /usr/src
SAVE ARTIFACT /usr/src /usr/src

Expand Down
2 changes: 2 additions & 0 deletions yarn-project/cli-wallet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,10 @@
"dependencies": {
"@aztec/accounts": "workspace:^",
"@aztec/aztec.js": "workspace:^",
"@aztec/circuit-types": "workspace:^",
"@aztec/circuits.js": "workspace:^",
"@aztec/cli": "workspace:^",
"@aztec/ethereum": "workspace:^",
"@aztec/foundation": "workspace:^",
"@aztec/kv-store": "workspace:^",
"commander": "^12.1.0",
Expand Down
39 changes: 35 additions & 4 deletions yarn-project/cli-wallet/src/bin/index.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,49 @@
import { fileURLToPath } from '@aztec/aztec.js';
import { createConsoleLogger, createDebugLogger } from '@aztec/foundation/log';
import { Fr, computeSecretHash, fileURLToPath } from '@aztec/aztec.js';
import { type LogFn, createConsoleLogger, createDebugLogger } from '@aztec/foundation/log';
import { AztecLmdbStore } from '@aztec/kv-store/lmdb';

import { Command } from 'commander';
import { Argument, Command } from 'commander';
import { readFileSync } from 'fs';
import { dirname, resolve } from 'path';

import { injectCommands } from '../cmds/index.js';
import { WalletDB } from '../storage/wallet_db.js';
import { Aliases, WalletDB } from '../storage/wallet_db.js';
import { createAliasOption } from '../utils/options/index.js';

const userLog = createConsoleLogger();
const debugLogger = createDebugLogger('aztec:wallet');

const { WALLET_DATA_DIRECTORY } = process.env;

function injectInternalCommands(program: Command, log: LogFn, db: WalletDB) {
program
.command('alias')
.description('Aliases information for easy reference.')
.addArgument(new Argument('<type>', 'Type of alias to create').choices(Aliases))
.argument('<key>', 'Key to alias.')
.argument('<value>', 'Value to assign to the alias.')
.action(async (type, key, value) => {
value = db.tryRetrieveAlias(value) || value;
await db.storeAlias(type, key, value, log);
});

program
.command('add-secret')
.description('Creates an aliased secret to use in other commands')
.addOption(createAliasOption('Key to alias the secret with', false).makeOptionMandatory(true))
.action(async (_options, command) => {
const options = command.optsWithGlobals();
const { alias } = options;
const value = Fr.random();
const hash = computeSecretHash(value);

await db.storeAlias('secrets', alias, Buffer.from(value.toString()), log);
await db.storeAlias('secrets', `${alias}:hash`, Buffer.from(hash.toString()), log);
});

return program;
}

/** CLI wallet main entrypoint */
async function main() {
const packageJsonPath = resolve(dirname(fileURLToPath(import.meta.url)), '../../package.json');
Expand All @@ -32,6 +62,7 @@ async function main() {
});

injectCommands(program, userLog, debugLogger, db);
injectInternalCommands(program, userLog, db);
await program.parseAsync(process.argv);
}

Expand Down
30 changes: 30 additions & 0 deletions yarn-project/cli-wallet/src/cmds/add_note.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { type AccountWalletWithSecretKey, type AztecAddress } from '@aztec/aztec.js';
import { ExtendedNote, Note, type TxHash } from '@aztec/circuit-types';
import { getContractArtifact, parseFields } from '@aztec/cli/utils';
import { type LogFn } from '@aztec/foundation/log';

export async function addNote(
wallet: AccountWalletWithSecretKey,
address: AztecAddress,
contractAddress: AztecAddress,
noteName: string,
storageFieldName: string,
artifactPath: string,
txHash: TxHash,
noteFields: string[],
log: LogFn,
) {
const fields = parseFields(noteFields);
const note = new Note(fields);
const contractArtifact = await getContractArtifact(artifactPath, log);

const contractNote = contractArtifact.notes[noteName];
const storageField = contractArtifact.storageLayout[storageFieldName];

if (!contractNote) {
throw new Error(`Note ${noteName} not found in contract ${contractArtifact.name}`);
}

const extendedNote = new ExtendedNote(note, address, contractAddress, storageField.slot, contractNote.id, txHash);
await wallet.addNote(extendedNote);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { createCompatibleClient } from '@aztec/aztec.js';
import { type AztecAddress } from '@aztec/circuits.js';
import { FeeJuicePortalManager, prettyPrintJSON } from '@aztec/cli/utils';
import { createEthereumChain, createL1Clients } from '@aztec/ethereum';
import { type DebugLogger, type LogFn } from '@aztec/foundation/log';

import { FeeJuicePortalManager } from '../../portal_manager.js';
import { prettyPrintJSON } from '../../utils/commands.js';

export async function bridgeL1Gas(
export async function bridgeL1FeeJuice(
amount: bigint,
recipient: AztecAddress,
rpcUrl: string,
Expand Down Expand Up @@ -45,4 +43,5 @@ export async function bridgeL1Gas(
log(`claimAmount=${amount},claimSecret=${secret}\n`);
log(`Note: You need to wait for two L2 blocks before pulling them from the L2 side`);
}
return secret;
}
14 changes: 11 additions & 3 deletions yarn-project/cli-wallet/src/cmds/create_account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { prettyPrintJSON } from '@aztec/cli/cli-utils';
import { Fr } from '@aztec/foundation/fields';
import { type DebugLogger, type LogFn } from '@aztec/foundation/log';

import { type AccountType, createAndStoreAccount } from '../utils/accounts.js';
import { type AccountType, createOrRetrieveAccount } from '../utils/accounts.js';
import { type IFeeOpts, printGasEstimates } from '../utils/options/fees.js';

export async function createAccount(
Expand All @@ -21,10 +21,18 @@ export async function createAccount(
debugLogger: DebugLogger,
log: LogFn,
) {
const salt = Fr.ZERO;
secretKey ??= Fr.random();

const account = await createAndStoreAccount(client, accountType, secretKey, publicKey, salt, alias);
const account = await createOrRetrieveAccount(
client,
undefined /* address, we don't have it yet */,
undefined /* db, as we want to create from scratch */,
accountType,
secretKey,
Fr.ZERO,
publicKey,
);
const salt = account.getInstance().salt;
const { address, publicKeys, partialAddress } = account.getCompleteAddress();

const out: Record<string, any> = {};
Expand Down
92 changes: 92 additions & 0 deletions yarn-project/cli-wallet/src/cmds/deploy_account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { type AccountManager, type DeployAccountOptions } from '@aztec/aztec.js';
import { prettyPrintJSON } from '@aztec/cli/cli-utils';
import { type DebugLogger, type LogFn } from '@aztec/foundation/log';

import { type IFeeOpts, printGasEstimates } from '../utils/options/fees.js';

export async function deployAccount(
account: AccountManager,
wait: boolean,
feeOpts: IFeeOpts,
json: boolean,
debugLogger: DebugLogger,
log: LogFn,
) {
const out: Record<string, any> = {};
const { address, partialAddress, publicKeys } = account.getCompleteAddress();
const { initializationHash, deployer, salt } = account.getInstance();
const wallet = await account.getWallet();
const secretKey = wallet.getSecretKey();

if (json) {
out.address = address;
out.partialAddress = partialAddress;
out.salt = salt;
out.initHash = initializationHash;
out.deployer = deployer;
} else {
log(`\nNew account:\n`);
log(`Address: ${address.toString()}`);
log(`Public key: 0x${publicKeys.toString()}`);
if (secretKey) {
log(`Secret key: ${secretKey.toString()}`);
}
log(`Partial address: ${partialAddress.toString()}`);
log(`Salt: ${salt.toString()}`);
log(`Init hash: ${initializationHash.toString()}`);
log(`Deployer: ${deployer.toString()}`);
}

let tx;
let txReceipt;

const sendOpts: DeployAccountOptions = {
...feeOpts.toSendOpts(wallet),
skipInitialization: false,
};
if (feeOpts.estimateOnly) {
const gas = await (await account.getDeployMethod()).estimateGas({ ...sendOpts });
if (json) {
out.fee = {
gasLimits: {
da: gas.gasLimits.daGas,
l2: gas.gasLimits.l2Gas,
},
teardownGasLimits: {
da: gas.teardownGasLimits.daGas,
l2: gas.teardownGasLimits,
},
};
} else {
printGasEstimates(feeOpts, gas, log);
}
} else {
tx = account.deploy({ ...sendOpts });
const txHash = await tx.getTxHash();
debugLogger.debug(`Account contract tx sent with hash ${txHash}`);
out.txHash = txHash;
if (wait) {
if (!json) {
log(`\nWaiting for account contract deployment...`);
}
txReceipt = await tx.wait();
out.txReceipt = {
status: txReceipt.status,
transactionFee: txReceipt.transactionFee,
};
}
}

if (json) {
log(prettyPrintJSON(out));
} else {
if (tx) {
log(`Deploy tx hash: ${await tx.getTxHash()}`);
}
if (txReceipt) {
log(`Deploy tx fee: ${txReceipt.transactionFee}`);
}
}

return { address, secretKey, salt };
}
Loading

0 comments on commit 1459360

Please sign in to comment.