> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nyx.money/llms.txt
> Use this file to discover all available pages before exploring further.

# Transaction authorization

> How transactions are authorized from Nyx private accounts.

## Overview

Your Nyx private account follows one simple rule:

<Info>
  The only way to authorize anything from your Nyx private account is to sign a
  message with your connected Ethereum wallet, and **what you sign is exactly
  what gets executed**.
</Info>

No one, including the Nyx team, the Nyx app, or a malicious website, can get you to sign one thing and then run something else from your account.
This page explains how that works and how to read the signing requests your wallet shows you.

## Your account is a smart contract

On Aztec, every account is a smart contract (see [Account overview](/technical-docs/account-overview#account-governance)).
That contract decides what counts as a valid authorization.
Nyx private accounts run a contract called `Eip712SignedMessageAccount`. It has three key properties:

* **One key, set at creation.** When your private account is created, your Ethereum wallet's public key is written into the contract as an *immutable* private note.
  No function exists to change it, add another key, or bypass it.
* **No admin, no backdoor.** The contract has no owner, guardian, or recovery key.
  The Nyx backend has no way to authorize a transaction for you.
* **Your wallet does the signing.** Authorization uses standard [EIP-712](https://eips.ethereum.org/EIPS/eip-712) typed data signatures, the same kind used across Ethereum for permits and off-chain orders.
  Any Ethereum EOA wallet (non-smart contract wallet) works, including hardware wallets, and your private key never leaves it.

Your passkey and account secret, covered in [Account overview](/technical-docs/account-overview), let you *see* your private account.
Only your Ethereum wallet can *act* on it.

## What you sign

Each time you send a private transaction, your wallet shows a structured EIP-712 message instead of an unreadable hex blob.
Here is a simplified example of a private send of 1 USDC:

```json theme={null}
{
  "domain": { "name": "Nyx", "version": "1", "chainId": "..." },
  "primaryType": "AztecTransactionRequest",
  "message": {
    "caution": "Confirm the app domain is nyx.money before proceeding.",
    "description": "Privately send 1 USDC to Alice.",
    "accountAddress": "0x1234…your private account",
    "functionCalls": [
      {
        "targetAddress": "0x0abc…JuiceBar fee sponsor",
        "functionSignature": "sponsor()",
        "args": [],
        "argsHash": "0x…",
        "isPublic": false,
        "isStatic": false,
        "hideMsgSender": false
      },
      {
        "targetAddress": "0x2e38…USDC token contract",
        "functionSignature": "transfer_private_to_private((Field),(Field),u128,Field)",
        "args": [
          "0x1234…your private account",
          "0x5678…Alice's private account",
          "1000000",
          "0x0000000000000000000000000000000000000000000000000000000000000000"
        ],
        "argsHash": "0x…",
        "isPublic": false,
        "isStatic": false,
        "hideMsgSender": false
      }
    ],
    "technicalDetails": {
      "rollupVersion": "…",
      "outerHash": "0x…",
      "nonce": "0x…"
    }
  }
}
```

The fields mean the following:

| Field | What it tells you |
| - | - |
| `domain` | The message is meant for Nyx (`name: "Nyx"`) on a specific network (`chainId`). |
| `caution` | A fixed warning chosen by the contract, not the app. See [Reading the caution line](#reading-the-caution-line). |
| `description` | A plain-language summary written by the app. Helpful, but **not enforced**. See [The description is a non-verified comment](#the-description-is-a-non-verified-comment). |
| `accountAddress` | The private account this signature authorizes. |
| `functionCalls` | **The actual calls your account will make.** This is the part that is enforced. |
| `targetAddress` | The contract being called. You can look it up on [Aztecscan](https://aztecscan.xyz) or in [Nyx smart contracts](/technical-docs/smart-contracts). |
| `functionSignature` | The function name and parameter types, for example `transfer_private_to_private(...)`. |
| `args` | The arguments, as text. Addresses and other field values are shown as hex. Numbers are shown in decimal. |
| `argsHash` | A hash of the arguments. The contract checks it against `args`. |
| `isPublic` | Whether the call runs as a *public* function. Public calls and their arguments are visible to everyone, just like on Ethereum. Private calls are not. |
| `isStatic` | Whether the call is read-only. |
| `hideMsgSender` | For public calls only: whether your account address is hidden from the called contract. |
| `technicalDetails` | Values that tie the signature to this one transaction on this one network. |

<Tip>
  Token amounts in `args` are in the token's smallest unit, with no decimal
  point. USDC has 6 decimals, so `1000000` means 1 USDC. WETH has 18 decimals,
  so `1000000000000000000` means 1 WETH.
</Tip>

## Why what you sign is what gets executed

A typical Ethereum transaction signature covers raw calldata, and your wallet has to *decode* it to show you something readable.
If the wallet decodes it wrong, or a site tricks it, what you see can differ from what you sign.

Nyx works the other way around.
The human-readable message *is* what gets signed, and the account contract checks it against the transaction it is about to run.

When your transaction executes, the account contract:

1. Receives the list of function calls to execute.
2. Rebuilds the full EIP-712 message from those exact calls: their target addresses, function signatures, and arguments as text.
3. Verifies your wallet's signature against that rebuilt message, using the public key stored at account creation.
4. Executes the calls only if the signature is valid. Otherwise the transaction fails.

The contract doesn't take any displayed value on trust. It ties each one to what runs:

* **Function signature → function.** The function that gets called is computed from the signature text you saw.
  You can't sign `transfer(...)` and have the account call `withdraw_everything(...)`.
* **Args → argsHash → execution.** The contract re-encodes the displayed arguments and checks that they hash to `argsHash`, which is the exact input passed to the call.
  You can't sign "send 1 USDC" and have the account send 1,000.
  Numeric text such as `1000000` is parsed back into a number inside the contract, so the text can't be faked either.
* **Target address → target.** The call goes to exactly the `targetAddress` you saw.
* **Account, network, and transaction binding.** `outerHash` combines your account address, the network's chain ID and rollup version, and a hash of the whole payload including a random `nonce`.
  A signature for one transaction can't be reused for a different account, a different network, or different calls.

```mermaid theme={null}
flowchart TD
    Wallet["Your Ethereum wallet"]

    subgraph App ["Nyx app"]
        Transaction["Transaction to execute"]
        Message["EIP-712 message\ncalls + args as text"]
    end

    subgraph Contract ["Nyx account contract"]
        Entrypoint
        Rebuilt["Rebuilt message"]
        SignatureMatch{"Signature verified against stored public key?"}
        Execute["Execute exactly those calls"]
        Reject["Reject"]
    end

    Wallet -- signs --> Message
    Transaction --> Entrypoint
    Message -- signature --> Entrypoint
    Entrypoint -- rebuild message from the actual calls --> Rebuilt
    Rebuilt --> SignatureMatch
    SignatureMatch -- yes --> Execute
    SignatureMatch -- no --> Reject
```

All of this happens inside the account contract's private function, so it's covered by the zero-knowledge proof that every Aztec transaction carries.
The network accepts the transaction only if the proof shows these checks passed.

## The description is a non-verified comment

The `description` field is a plain-language summary written by the app, such as *"Privately send 1 USDC to Alice."*
It's part of the signed message, so it can't be changed after you sign, but the contract **does not check that it matches the function calls**.
That's impossible in general: the description is free text.

Treat the description as a helpful comment and the `functionCalls` as the truth.
If the two disagree, trust `functionCalls`, and don't sign.

## Reading the caution line

The `caution` line comes from a fixed set built into the account contract.
An app can't choose its own caution text, and the contract checks that the right one was shown.

| Caution | When you see it |
| - | - |
| `Review the transaction carefully.` | Clear signing, with the contract's built-in description. |
| `Confirm the app domain is nyx.money before proceeding.` | Clear signing, with a custom description from the app. Because anyone could write a custom description, check that you are actually on [`nyx.money`](https://nyx.money). |
| `Blind signing. Review the transaction carefully.` | Blind signing ([learn more below](#blind-signing)), with the built-in description. |
| `Blind signing. Confirm the app domain is nyx.money before proceeding.` | Blind signing, with a custom description from the app. |

## Letting a contract act for you

Some actions need another contract to do something *on your behalf*.
For example, when you send funds from your private account to Ethereum, the token bridge contract has to burn tokens from your private balance.
In Aztec, that permission is called an [*authentication witness*](https://docs.aztec.network/developers/docs/foundational-topics/advanced/authwit). Nyx shows it as a separate signing request with primary type `AztecCallIntent`:

```json theme={null}
{
  "primaryType": "AztecCallIntent",
  "message": {
    "caution": "Confirm the app domain is nyx.money before proceeding.",
    "description": "Authorize the withdrawal of 1 USDC from your private account to send it to 0xabcd…. (Step 1 of 2)",
    "accountAddress": "0x1234…your private account",
    "caller": "0x2bb7…USDC token bridge",
    "functionCall": {
      "targetAddress": "0x2e38…USDC token contract",
      "functionSignature": "burn_private((Field),u128,Field)",
      "args": ["0x1234…your private account", "1000000", "0x…"],
      "...": "..."
    },
    "technicalDetails": { "rollupVersion": "…", "outerHash": "0x…" }
  }
}
```

The same guarantees apply, with one more field:

* `caller` is the **only** contract allowed to use this permission.
* `functionCall` is the **only** call it may make with it, with exactly these arguments.

When the token contract asks your account "did you authorize this?", your account rebuilds the call intent from the actual call and verifies your signature against it.
A different caller, function, or amount doesn't pass verification.

Clear-signed call intents are limited to private calls.

## Blind signing

Clear signing has fixed size limits, because every displayed value must be processed inside a zero-knowledge circuit.
Currently, a transaction is clear-signed when:

* It has at most 2 function calls.
* Each call has at most 6 arguments (call intents: at most 6).
* Each function signature fits in 93 bytes, and each text argument fits in 31 bytes.
* All arguments are field elements, unsigned integers, booleans, or short strings.

If a transaction exceeds these limits, Nyx falls back to *blind signing*.
The message type becomes `AztecOpaqueTransactionRequest` (or `AztecOpaqueCallIntent`). It shows the caution, description, account address, and network, plus an `outerHash` in place of the readable calls.

Blind signatures are still bound to one account, one network, and one exact set of calls. The `outerHash` commits to all of that, so the signature can't be reused for anything else.
What you lose is the ability to *see* the calls in your wallet. You're relying on the app to have built the transaction honestly.

<Warning>
  A blind signing request always starts with `Blind signing.` in its caution
  line. Only sign one if you started the action yourself and you trust the app that prepared the
  transaction. All Nyx actions such as sending, receiving, depositing, and
  redeeming are designed to be clear-signed.
</Warning>

## Checklist before you sign

1. **Make sure you started the action.** Only sign a request that comes right after something you did in the app, such as clicking Send. If a signing request appears out of nowhere, reject it.
2. **Check the site.** Make sure you're on [`nyx.money`](https://nyx.money), especially if the caution line asks you to.
3. **Check the caution line.** If it starts with `Blind signing.`, take extra care.
4. **Check the account.** `accountAddress` should be your own private account.
5. **Check the calls, not just the description.** Look at `targetAddress`, `functionSignature`, and `args` for each call.
   Expect a small `sponsor()` call to Nyx's JuiceBar contract, which pays your network fees.
6. **Check the amounts** in smallest units, and check recipient addresses.
7. **For call intents, check `caller`.** It should be the contract you expect, such as the token bridge for a withdrawal.

If anything looks off, reject the request in your wallet.
Nothing can happen in your private account without your signature.

## Source code

Nyx is still under active development, and the details on this page may change as it evolves.
The behavior described here is implemented in two places:

* The on-chain account contract, which verifies signatures and executes calls.
* The client-side code in the Nyx app, which builds the EIP-712 messages your wallet shows.

Both, along with the rest of Nyx's smart contracts, will be open-sourced and publicly auditable when Nyx reaches GA (general availability).
See [Nyx smart contracts](/technical-docs/smart-contracts) for more on our plans.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.