2026-04-17 19:45:42 -03:00

10 KiB
Raw Blame History

This tutorial walks through native token transfers between public and private accounts using the Authenticated-Transfers program. You will create and initialize accounts, fund them with the Pinata program, and run transfers across different privacy combinations. By the end, you will have practiced:

  1. Public account creation and initialization.
  2. Account funding through the Pinata program.
  3. Native token transfers between public accounts.
  4. Private account creation.
  5. Native token transfer from a public account to a private account.
  6. Native token transfer from a public account to a private account owned by someone else.

The CLI provides commands to manage accounts. Run wallet account to see the options available:

Commands:
  get           Get account data
  new           Create a new public account or private accounts key
  sync-private  Sync private accounts
  help  Print this message or the help of the given subcommand(s)

1. Public account creation and initialization

Important

Public accounts live on-chain and are identified by a 32-byte Account ID. Running wallet account new public generates a fresh keypair for the signature scheme used in LEZ. The account ID is derived from the public key, and the private key signs transactions and authorizes program executions. The CLI can create both public and private accounts.

a. New public account creation

wallet account new public

# Output:
Generated new account with account_id Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ

Tip

Save this account ID. You will use it in later commands.

b. Account initialization

To query the accounts current status, run:

# Replace the id with yours
wallet account get --account-id Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ

# Output:
Account is Uninitialized

In this example, we initialize the account for the authenticated-transfer program, which manages native token transfers and enforces authenticated debits.

  1. Initialize the account:
# This command submits a public transaction executing the `init` function of the
# authenticated-transfer program. The wallet polls the sequencer until the
# transaction is included in a block, which may take several seconds.
wallet auth-transfer init --account-id Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ
  1. Check the updated account status:
wallet account get --account-id Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ

# Output:
Account owned by authenticated-transfer program
{"balance":0}

Note

New accounts start uninitialized, meaning no program owns them yet. Any program may claim an uninitialized account; once claimed, that program owns it. Owned accounts can only be modified through executions of the owning program. The only exception is native-token credits: any program may credit native tokens to any account. Debiting native tokens must always be performed by the owning program.

2. Account funding through the Piñata program

Now that the account is initialized under the authenticated-tansfer program, fund it using the testnet Piñata program.

# Replace with your id
wallet pinata claim --to Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ

After the claim succeeds, the account is funded:

wallet account get --account-id Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ

# Output:
Account owned by authenticated-transfer program
{"balance":150}

3. Native token transfers between public accounts

LEZ includes a program for managing native tokens. Run wallet auth-transfer to see the available commands:

Commands:
  init  Initialize account under the authenticated-transfer program
  send  Send native tokens from one account to another with variable privacy
  help  Print this message or the help of the given subcommand(s)

We already used init. Now use send to execute a transfer.

a. Create a recipient account

wallet account new public

# Output:
Generated new account with account_id Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS

Note

The new account is uninitialized. The authenticated-transfer program will claim any uninitialized account used in a transfer, so manual initialization isnt required.

b. Send 37 tokens to the new account

wallet auth-transfer send \
    --from Public/9ypzv6GGr3fwsgxY7EZezg5rz6zj52DPCkmf1vVujEiJ \
    --to Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \
    --amount 37

c. Check both accounts

# Sender account (use your sender ID)
wallet account get --account-id Public/HrA8TVjBS8UVf9akV7LRhyh6k4c7F6PS7PvqgtPmKAT8

# Output:
Account owned by authenticated-transfer program
{"balance":113}
# Recipient account
wallet account get --account-id Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS

# Output:
Account owned by authenticated-transfer program
{"balance":37}

4. Private accounts key creation

Important

Private accounts are structurally identical to public accounts, but their values are stored off-chain. On-chain, only a 32-byte commitment is recorded. Transactions include encrypted private values so the owner can recover them, and the decryption keys are never shared. Private accounts use two keypairs: nullifier keys for privacy-preserving executions and viewing keys for encrypting and decrypting values. A private account ID is derived from the nullifier public key and an identifier chosen by the sender at the time of the first transfer. Private accounts can be initialized by anyone, but once initialized they can only be modified by the owners keys. Updates include a new commitment and a nullifier for the old state, which prevents linkage between versions.

a. Create a private accounts key

wallet account new private-accounts-key

# Output:
Generated new private accounts key at path /0
With npk e6366f79d026c8bd64ae6b3d601f0506832ec682ab54897f205fffe64ec0d951
With vpk 02ddc96d0eb56e00ce14994cfdaec5ae1f76244180a919545983156e3519940a17

Tip

Share npk and vpk with anyone who wants to send you tokens. The account ID for a given payment is determined by the sender when they create the transaction. Run wallet account sync-private after receiving a transfer to discover new account IDs under your key.

Important

Private account data is never visible to the network. It exists only in your local wallet storage.

5. Native token transfer from a public account to a private account

Important

Sending tokens to an uninitialized private account causes the authenticated-transfer program to claim it, just like with public accounts. Program logic is the same regardless of account type. When sending to a private account, use the recipients npk and vpk. The sender chooses an identifier for the payment; the recipients account ID is derived from (npk, identifier) and is only known after the recipient syncs.

a. Send 17 tokens to the private account

Note

The syntax matches public-to-public transfers, but the recipient is identified by npk and vpk. This runs locally, generates a proof, and submits it to the sequencer. It may take 30 seconds to 4 minutes.

wallet auth-transfer send \
    --from Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \
    --to-npk e6366f79d026c8bd64ae6b3d601f0506832ec682ab54897f205fffe64ec0d951 \
    --to-vpk 02ddc96d0eb56e00ce14994cfdaec5ae1f76244180a919545983156e3519940a17 \
    --amount 17

b. Sync to discover the private account ID

wallet account sync-private
wallet account list

# Output (private account entry):
/0 Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL

Tip

Save this account ID. You will use it in later commands.

c. Check both accounts

# Public sender account
wallet account get --account-id Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS

# Output:
Account owned by authenticated-transfer program
{"balance":20}
# Private recipient account (use the ID discovered after sync)
wallet account get --account-id Private/HacPU3hakLYzWtSqUPw6TUr8fqoMieVWovsUR6sJf7cL

# Output:
Account owned by authenticated-transfer program
{"balance":17}

Note

The last command does not query the network. It works offline because private account data is stored locally. Other users cannot read your private balances.

Caution

Private accounts can only be modified by their owners keys. The exception is initialization: any user can initialize an uninitialized private account. This enables transfers to a private account owned by someone else, as long as that account is uninitialized.

6. Native token transfer from a public account to a private account owned by someone else

Important

When the recipient is someone else, you only have their npk and vpk — never their account ID. The flow is identical to section 5.

a. Create a new private accounts key to simulate a foreign recipient

wallet account new private-accounts-key

# Output:
Generated new private accounts key at path /1
With npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e
With vpk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72

b. Send 3 tokens using the recipients npk and vpk

wallet auth-transfer send \
    --from Public/Ev1JprP9BmhbFVQyBcbznU8bAXcwrzwRoPTetXdQPAWS \
    --to-npk 0c95ebc4b3830f53da77bb0b80a276a776cdcf6410932acc718dcdb3f788a00e \
    --to-vpk 039fd12a3674a880d3e917804129141e4170d419d1f9e28a3dcf979c1f2369cb72 \
    --amount 3

Warning

This command creates a privacy-preserving transaction, which may take a few minutes. The updated values are encrypted and included in the transaction. Once accepted, the recipient must run wallet account sync-private to scan the chain for their encrypted updates and refresh local state.

Note

You have seen transfers between two public accounts and from a public sender to a private recipient. Transfers from a private sender, whether to a public account or to another private account, follow the same pattern.