Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
4aa7c01
chore: drop dead legacy oracle mappings
benesjan Mar 26, 2026
37d79a0
cherry-pick PR #21829: fix: sync dateProvider from anvil stdout on ev…
PhilWindle Mar 20, 2026
ed394e8
cherry-pick PR #21853: fix(sequencer): remove l1 block timestamp chec…
PhilWindle Mar 20, 2026
7343882
cherry-pick PR #21869: fix(e2e): set anvilSlotsInAnEpoch in slashing …
spalladino Mar 26, 2026
9820222
cherry-pick PR #22023: fix(sequencer): use last L1 slot of L2 slot as…
spalladino Mar 26, 2026
22fbcfa
fix: resolve all cherry-pick conflicts
spalladino Mar 27, 2026
4e3ba00
merge origin/backport-to-v4-next-staging (with conflicts)
spalladino Mar 27, 2026
ef51060
fix: resolve merge conflicts with base branch
spalladino Mar 27, 2026
f818201
fix: build errors from cherry-pick backports
spalladino Mar 27, 2026
d7ceefa
fix: add aztecSlotDuration to test configs
spalladino Mar 27, 2026
4d8a102
chore: backport - drop dead legacy oracle mappings (#22035)
benesjan Mar 30, 2026
af8c132
init
sklppy88 Mar 30, 2026
9caf4c0
fix: reorder spread to avoid duplicate property error
spalladino Mar 30, 2026
37243f3
fix(e2e): deflake epochs_mbps deploy-then-call test
spalladino Mar 30, 2026
5528388
fix: backport timestamp and anvil fixes (#22110)
spalladino Mar 30, 2026
a0d9a82
fix(archiver): swallow error when rollup contract not yet finalized o…
spalladino Mar 30, 2026
2223482
feat(docs): public setup allowlist backport (#22141)
sklppy88 Mar 30, 2026
5a99963
cherry-pick: fix: restrict access to scoped capsules (#22113) (with c…
mverzilli Mar 30, 2026
314078e
fix: resolve cherry-pick conflicts
AztecBot Mar 30, 2026
19daacc
feat!: remove ALL_SCOPES (#22136)
mverzilli Mar 30, 2026
0939861
fix: restrict access to scoped capsules (#22113) (#22135)
mverzilli Mar 30, 2026
d79af8a
feat!: remove ALL_SCOPES (backport #22136) (#22161)
mverzilli Mar 30, 2026
459e929
cherry-pick: 49e2563fc3 fix(docs): update CLI commands, ABI fields, a…
critesjosh Mar 30, 2026
1e2e745
fix: resolve cherry-pick conflicts
AztecBot Mar 30, 2026
20899ce
fix(docs): backport #22160 — update CLI commands, ABI fields, and tut…
critesjosh Mar 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 12 additions & 45 deletions docs/docs-developers/docs/aztec-js/how_to_pay_fees.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@ This guide walks you through paying transaction fees on Aztec using various paym
| Method | Use Case | Privacy | Requirements |
| ------------------- | ----------------------------- | ------- | -------------------------- |
| Fee Juice (default) | Account already has Fee Juice | Public | Funded account |
#if(testnet)
| Sponsored FPC | Testing, free transactions | Public | None (not on testnet) |
#else
#if(devnet)
| Sponsored FPC | Testing, free transactions | Public | None |
#else
| Sponsored FPC | Testing, free transactions | Public | None (devnet and local only) |
#endif
| Private FPC | Pay with tokens privately | Private | Token balance, FPC address |
| Public FPC | Pay with tokens publicly | Public | Token balance, FPC address |
| Bridge + Claim | Bootstrap from L1 | Public | L1 ETH for gas |

## Mana and Fee Juice
Expand Down Expand Up @@ -118,20 +116,22 @@ console.log("Transaction fee:", receipt.transactionFee);

## Use Fee Payment Contracts

Fee Payment Contracts (FPC) pay fees on your behalf, typically accepting a different token than Fee Juice. Since Fee Juice is non-transferable on L2, FPCs are the most common fee payment method.
Fee Payment Contracts (FPCs) pay Fee Juice on your behalf. FPCs must use Fee Juice exclusively on L2 during the setup phase; custom token contract functions cannot be called during setup on public networks. An FPC that accepts other tokens on L1 and bridges Fee Juice works on any network.

### Sponsored Fee Payment Contracts

#if(testnet)
:::warning
The Sponsored FPC is **not** deployed on testnet. To pay fees, you must either [bridge Fee Juice from L1](#bridge-fee-juice-from-l1) or deploy your own fee-paying contract.
:::note
The Sponsored FPC is not available on testnet or mainnet. It is only available on devnet and local network.
:::
#elif(mainnet)
:::note
The Sponsored FPC is not available on mainnet. It is only available on devnet and local network.
:::

The Sponsored FPC pays for fees unconditionally without requiring payment in return. It is available on the local network and devnet (deployed by Aztec Labs), but **not on testnet**.
#else
The Sponsored FPC pays for fees unconditionally without requiring payment in return. It is available on both the local network and devnet (deployed by Aztec Labs).
#endif

The Sponsored FPC pays fees unconditionally. It is only available on devnet and local network.

You can derive the Sponsored FPC address from its deployment parameters, register it with your wallet, and use it to pay for transactions:

#include_code deploy_sponsored_fpc_contract /docs/examples/ts/aztecjs_advanced/index.ts typescript
Expand All @@ -140,39 +140,6 @@ Here's a simpler example from the test suite:

#include_code sponsored_fpc_simple yarn-project/end-to-end/src/e2e_fees/sponsored_payments.test.ts typescript

### Use other Fee Paying Contracts

Third-party FPCs can pay for your fees using custom logic, such as accepting different tokens instead of Fee Juice.

#### Set gas settings

```typescript
import { GasSettings } from "@aztec/stdlib/gas";

// node is from createAztecNodeClient() in the connection guide (see prerequisites)
const maxFeesPerGas = (await node.getCurrentMinFees()).mul(1.5); //adjust this to your needs
const gasSettings = GasSettings.default({ maxFeesPerGas });
```

Private FPCs enable fee payments without revealing the payer's identity onchain:

#include_code private_fpc_payment yarn-project/end-to-end/src/composed/e2e_local_network_example.test.ts typescript

Public FPCs can be used in the same way:

```typescript
import { PublicFeePaymentMethod } from "@aztec/aztec.js/fee";

// wallet is from the connection guide; fpcAddress is the FPC contract address
// senderAddress is the account paying; gasSettings is from the step above
const paymentMethod = new PublicFeePaymentMethod(
fpcAddress,
senderAddress,
wallet,
gasSettings,
);
```

## Bridge Fee Juice from L1

Fee Juice is non-transferable on L2, but you can bridge it from L1, claim it on L2, and use it. This involves a few components that are part of a running network's infrastructure:
Expand Down
7 changes: 2 additions & 5 deletions docs/docs-developers/docs/foundational-topics/fees.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,9 @@ Fee Juice uses an enshrined `FeeJuicePortal` contract on Ethereum for bridging,

An account with Fee Juice can pay for its transactions directly. A new account can even pay for its own deployment transaction, provided Fee Juice was bridged to its address before deployment.

Alternatively, accounts can use [fee-paying contracts (FPCs)](../aztec-js/how_to_pay_fees.md#use-fee-payment-contracts) to pay for transactions. FPCs accept tokens and pay fees in Fee Juice on behalf of users. Common patterns include:
Alternatively, accounts can use [fee-paying contracts (FPCs)](../aztec-js/how_to_pay_fees.md#use-fee-payment-contracts) to pay for transactions. FPCs must use Fee Juice exclusively on L2 during the setup phase, but can accept other tokens on L1 and bridge Fee Juice.

- **Sponsored FPCs**: Pay fees unconditionally, enabling free transactions for users
- **Token-accepting FPCs**: Accept a specific token in exchange for paying fees

FPCs can contain arbitrary logic to authorize fee payments and can operate privately or publicly.
The **Sponsored FPC** pays fees unconditionally, enabling free transactions. It is only available on devnet and local network.

### Teardown phase

Expand Down
67 changes: 67 additions & 0 deletions docs/docs-developers/docs/resources/migration_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,71 @@ Aztec is in active development. Each version may introduce breaking changes that

## TBD

### [PXE] `simulateTx`, `executeUtility`, `profileTx`, and `proveTx` no longer accept `scopes: 'ALL_SCOPES'`

The `AccessScopes` type (`'ALL_SCOPES' | AztecAddress[]`) has been removed. The `scopes` field in `SimulateTxOpts`,
`ExecuteUtilityOpts`, and `ProfileTxOpts` now requires an explicit `AztecAddress[]`. Callers that previously passed
`'ALL_SCOPES'` must now specify which addresses will be in scope for the call.

**Migration:**

```diff
+ const accounts = await pxe.getRegisteredAccounts();
+ const scopes = accounts.map(a => a.address);

// simulateTx
- await pxe.simulateTx(txRequest, { simulatePublic: true, scopes: 'ALL_SCOPES' });
+ await pxe.simulateTx(txRequest, { simulatePublic: true, scopes });

// executeUtility
- await pxe.executeUtility(call, { scopes: 'ALL_SCOPES' });
+ await pxe.executeUtility(call, { scopes });

// profileTx
- await pxe.profileTx(txRequest, { profileMode: 'full', scopes: 'ALL_SCOPES' });
+ await pxe.profileTx(txRequest, { profileMode: 'full', scopes });

// proveTx
- await pxe.proveTx(txRequest, 'ALL_SCOPES');
+ await pxe.proveTx(txRequest, scopes);
```

**Impact**: Any code passing `'ALL_SCOPES'` to `simulateTx`, `executeUtility`, `profileTx`, or `proveTx` will fail to compile. Replace with an explicit array of account addresses.

### [PXE] Capsule operations are now scope-enforced at the PXE level

The PXE now enforces that capsule operations can only access scopes that were authorized for the current execution. If a contract attempts to access a capsule scope that is not in its allowed scopes list, the PXE will throw an error:

```
Scope 0x1234... is not in the allowed scopes list: [0xabcd...].
```

The zero address (`AztecAddress::zero()`) is always allowed regardless of the scopes list, preserving backwards compatibility for contracts using the global scope.

**Impact**: Contracts that access capsules scoped to addresses not included in the transaction's authorized scopes will now fail at runtime. Ensure the correct scopes are passed when executing transactions.

## 4.2.0-aztecnr-rc.2

### Custom token FPCs removed from default public setup allowlist

Token contract functions (like `transfer_in_public` and `_increase_public_balance`) have been removed from the default public setup allowlist. FPCs that accept custom tokens (like the reference `FPC` contract) will not work on public networks, because their setup-phase calls to these functions will be rejected. Token class IDs change with each aztec-nr release, making it impractical to maintain them in the allowlist.

FPCs that use only Fee Juice still work on all networks, since FeeJuice is a protocol contract with a fixed address in the allowlist. Custom FPCs should only call protocol contract functions (AuthRegistry, FeeJuice) during setup.

`PublicFeePaymentMethod` and `PrivateFeePaymentMethod` in aztec.js are affected, since they use the reference `FPC` contract which calls Token functions during setup. Switch to `FeeJuicePaymentMethodWithClaim` (after [bridging Fee Juice from L1](../aztec-js/how_to_pay_fees.md#bridge-fee-juice-from-l1)) or write an FPC that uses Fee Juice natively.

**Migration:**

```diff
- import { PublicFeePaymentMethod } from '@aztec/aztec.js/fee';
- const paymentMethod = new PublicFeePaymentMethod(fpcAddress, senderAddress, wallet, gasSettings);
+ import { FeeJuicePaymentMethodWithClaim } from '@aztec/aztec.js/fee';
+ const paymentMethod = new FeeJuicePaymentMethodWithClaim(senderAddress, claim);
```

Similarly, the `fpc-public` and `fpc-private` CLI wallet payment methods use the reference Token-based FPC and will not work on public networks. Use `fee_juice` for direct Fee Juice payment, or `fpc-sponsored` on devnet and local network.


### [Aztec.nr] Domain-separated tags on log emission

All logs emitted through the Aztec.nr framework now include a domain-separated tag at `fields[0]`. Each log category uses its own domain separator via `compute_log_tag(raw_tag, dom_sep)`:
Expand Down Expand Up @@ -118,6 +183,8 @@ The `DeployTxReceipt` and `DeployWaitOptions` types have been removed.
+ from: address,
+ });
```


### [aztec.js] `isContractInitialized` is now `initializationStatus` tri-state enum

`ContractMetadata.isContractInitialized` has been renamed to `ContractMetadata.initializationStatus` and changed from `boolean | undefined` to a `ContractInitializationStatus` enum with values `INITIALIZED`, `UNINITIALIZED`, and `UNKNOWN`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ Your structure should look like this:
| |-Nargo.toml
```

The file `main.nr` will soon turn into our smart contract!
The `aztec new` command creates a contract project with `Nargo.toml` and `src/main.nr`. The file `src/main.nr` will soon turn into our smart contract!

Add the following dependencies to `Nargo.toml` under the autogenerated content:
Add the following dependency to `Nargo.toml` under the existing `aztec` dependency:

```toml
[dependencies]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,10 @@ The proof generation script executes the circuit offchain and produces the proof

Create `scripts/generate_data.ts`:

#include_code generate_data /docs/examples/ts/recursive_verification/scripts/generate_data.ts typescript
```js
import circuitJson from "../circuit/target/hello_circuit.json" with { type: "json" };
#include_code generate_data /docs/examples/ts/recursive_verification/scripts/generate_data.ts raw
```

### Understanding the Proof Generation Pipeline

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,13 @@ cd bob_token
yarn init
# This is to ensure yarn uses node_modules instead of pnp for dependency installation
yarn config set nodeLinker node-modules
yarn add @aztec/aztec.js@#include_aztec_version @aztec/accounts@#include_aztec_version @aztec/test-wallet@#include_aztec_version @aztec/kv-store@#include_aztec_version
yarn add @aztec/aztec.js@#include_aztec_version @aztec/accounts@#include_aztec_version @aztec/kv-store@#include_aztec_version
aztec init
```

## Contract structure

The `aztec init` command created a workspace with two crates: a `bob_token_contract` crate for your smart contract code and a `bob_token_test` crate for Noir tests. In `bob_token_contract/src/main.nr` we even have a proto-contract. Let's replace it with a simple starting point:
The `aztec init` command created a contract project with `Nargo.toml` and `src/main.nr`. Let's replace the boilerplate in `src/main.nr` with a simple starting point:

```rust
#include_code start /docs/examples/contracts/bob_token_contract/src/main.nr raw
Expand Down
5 changes: 1 addition & 4 deletions docs/docs-participate/basics/fees.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,7 @@ Aztec offers flexible fee payment:
If you have $AZTEC, pay for your own transactions directly from your account.

### Sponsored Transactions
Some applications pay fees on behalf of their users, enabling "free" transactions. The application covers the cost, not you.

### Fee-Paying Contracts
Specialized contracts can accept other tokens and pay fees in $AZTEC for you. This is useful if you only hold other tokens.
Fee-paying contracts can pay fees on your behalf. For example, on devnet and local network, a sponsored fee-paying contract covers transaction costs for free. FPCs can also accept other tokens on L1 and bridge $AZTEC to pay fees.

## Understanding Your Fee

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import circuitJson from "../../../../target/hello_circuit.json" with { type: "json" };
// docs:start:generate_data
import { Noir } from "@aztec/noir-noir_js";
import circuitJson from "../../../../target/hello_circuit.json" with { type: "json" };
import { Barretenberg, UltraHonkBackend, deflattenFields } from "@aztec/bb.js";
import fs from "fs";
import { exit } from "process";
Expand Down Expand Up @@ -56,14 +56,14 @@ if (proofAsFields.length === 0) {
const vkAsFields = recursiveArtifacts.vkAsFields;

console.log(`VK size: ${vkAsFields.length}`); // Should be 115
console.log(`Proof size: ${proofAsFields.length}`); // Should be 508
console.log(`Proof size: ${proofAsFields.length}`); // Should be ~500
console.log(`Public inputs: ${mainProofData.publicInputs.length}`); // Should be 1

// Step 9: Save all data to JSON for contract interaction
const data = {
vkAsFields: vkAsFields, // 115 field elements - the verification key
vkHash: recursiveArtifacts.vkHash, // Hash of VK - stored in contract
proofAsFields: proofAsFields, // 508 field elements - the proof
proofAsFields: proofAsFields, // ~500 field elements - the proof
publicInputs: mainProofData.publicInputs.map((p: string) => p.toString()),
};

Expand Down
2 changes: 1 addition & 1 deletion docs/examples/ts/token_bridge/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ const INBOX_ABI = [
type: "event",
name: "MessageSent",
inputs: [
{ name: "l2BlockNumber", type: "uint256", indexed: true },
{ name: "checkpointNumber", type: "uint256", indexed: true },
{ name: "index", type: "uint256", indexed: false },
{ name: "hash", type: "bytes32", indexed: true },
{ name: "rollingHash", type: "bytes16", indexed: false },
Expand Down
10 changes: 10 additions & 0 deletions docs/netlify.toml
Original file line number Diff line number Diff line change
Expand Up @@ -805,3 +805,13 @@
# PXE: incompatible oracle version between contract and PXE
from = "/errors/8"
to = "/developers/docs/foundational-topics/pxe"

[[redirects]]
# CLI: aztec dep version in Nargo.toml does not match the CLI version
from = "/errors/9"
to = "/developers/docs/aztec-nr/framework-description/dependencies#updating-your-aztec-dependencies"

[[redirects]]
# PXE: capsule operation attempted with a scope not in the allowed scopes list
from = "/errors/10"
to = "/developers/docs/aztec-nr/framework-description/advanced/how_to_use_capsules"
7 changes: 5 additions & 2 deletions yarn-project/archiver/src/modules/l1_synchronizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,8 +256,11 @@ export class ArchiverL1Synchronizer implements Traceable {
},
);
}
} catch (err) {
this.log.warn(`Failed to update finalized checkpoint: ${err}`);
} catch (err: any) {
// The rollup contract may not exist at the finalized L1 block right after deployment.
if (!err?.message?.includes('returned no data')) {
this.log.warn(`Failed to update finalized checkpoint: ${err}`);
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion yarn-project/aztec/src/testing/anvil_test_watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class AnvilTestWatcher {
return;
}

const l1Time = (await this.cheatcodes.timestamp()) * 1000;
const l1Time = (await this.cheatcodes.lastBlockTimestamp()) * 1000;
const wallTime = this.dateProvider.now();
if (l1Time > wallTime) {
this.logger.warn(`L1 is ahead of wall time. Syncing wall time to L1 time`);
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/aztec/src/testing/cheat_codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class CheatCodes {
* @param duration - The duration to advance time by (in seconds)
*/
async warpL2TimeAtLeastBy(sequencerClient: SequencerClient, node: AztecNode, duration: bigint | number) {
const currentTimestamp = await this.eth.timestamp();
const currentTimestamp = await this.eth.lastBlockTimestamp();
const targetTimestamp = BigInt(currentTimestamp) + BigInt(duration);
await this.warpL2TimeAtLeastTo(sequencerClient, node, targetTimestamp);
}
Expand Down
5 changes: 3 additions & 2 deletions yarn-project/cli-wallet/src/cmds/check_tx.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ContractArtifact } from '@aztec/aztec.js/abi';
import type { AztecAddress } from '@aztec/aztec.js/addresses';
import { AztecAddress } from '@aztec/aztec.js/addresses';
import { Fr } from '@aztec/aztec.js/fields';
import type { AztecNode } from '@aztec/aztec.js/node';
import { ProtocolContractAddress } from '@aztec/aztec.js/protocol';
Expand Down Expand Up @@ -87,12 +87,13 @@ async function inspectTx(wallet: CLIWallet, aztecNode: AztecNode, txHash: TxHash
// Nullifiers
const nullifierCount = effects.nullifiers.length;
const { deployNullifiers, initNullifiers, classNullifiers } = await getKnownNullifiers(wallet, artifactMap);
const accounts = (await wallet.getAccounts()).map(a => a.item);
if (nullifierCount > 0) {
log(' Nullifiers:');
for (const nullifier of effects.nullifiers) {
const deployed = deployNullifiers[nullifier.toString()];
const note = deployed
? (await wallet.getNotes({ siloedNullifier: nullifier, contractAddress: deployed, scopes: 'ALL_SCOPES' }))[0]
? (await wallet.getNotes({ siloedNullifier: nullifier, contractAddress: deployed, scopes: accounts }))[0]
: undefined;
const initialized = initNullifiers[nullifier.toString()];
const registered = classNullifiers[nullifier.toString()];
Expand Down
7 changes: 6 additions & 1 deletion yarn-project/cli-wallet/src/utils/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ export class CLIWallet extends BaseWallet {

override async getAccounts(): Promise<Aliased<AztecAddress>[]> {
const accounts = (await this.db?.listAliases('accounts')) ?? [];
return Promise.resolve(accounts.map(({ key, value }) => ({ alias: value, item: AztecAddress.fromString(key) })));
return Promise.resolve(
accounts.map(({ key, value }) => {
const alias = key.includes(':') ? key.slice(key.indexOf(':') + 1) : key;
return { alias, item: AztecAddress.fromString(value) };
}),
);
}

private async createCancellationTxExecutionRequest(
Expand Down
8 changes: 4 additions & 4 deletions yarn-project/end-to-end/src/e2e_cheat_codes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,19 @@ describe('e2e_cheat_codes', () => {

it.each([100, 42, 99])(`setNextBlockTimestamp by %i`, async increment => {
const blockNumber = await ethCheatCodes.blockNumber();
const timestamp = await ethCheatCodes.timestamp();
const timestamp = await ethCheatCodes.lastBlockTimestamp();
await ethCheatCodes.setNextBlockTimestamp(timestamp + increment);

expect(await ethCheatCodes.timestamp()).toBe(timestamp);
expect(await ethCheatCodes.lastBlockTimestamp()).toBe(timestamp);

await ethCheatCodes.mine();

expect(await ethCheatCodes.blockNumber()).toBe(blockNumber + 1);
expect(await ethCheatCodes.timestamp()).toBe(timestamp + increment);
expect(await ethCheatCodes.lastBlockTimestamp()).toBe(timestamp + increment);
});

it('setNextBlockTimestamp to a past timestamp throws', async () => {
const timestamp = await ethCheatCodes.timestamp();
const timestamp = await ethCheatCodes.lastBlockTimestamp();
const pastTimestamp = timestamp - 1000;
await expect(async () => await ethCheatCodes.setNextBlockTimestamp(pastTimestamp)).rejects.toThrow(
'Timestamp error',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ describe('e2e_crowdfunding_and_claim', () => {
} = await setup(3));

// We set the deadline to a week from now
deadline = (await cheatCodes.eth.timestamp()) + 7 * 24 * 60 * 60;
deadline = (await cheatCodes.eth.lastBlockTimestamp()) + 7 * 24 * 60 * 60;

({ contract: donationToken } = await TokenContract.deploy(
wallet,
Expand Down
Loading
Loading