Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ time-interface = { path = "./pkg/time-interface" }
time-system = { path = "./pkg/time-system" }
email-interface = { path = "./pkg/email-interface" }
email-memory = { path = "./pkg/email-memory" }
email-postmark = { path = "./pkg/email-postmark" }
evm-json-rpc-interface = { path = "./pkg/evm-json-rpc-interface" }
evm-json-rpc-reqwest = { path = "./pkg/evm-json-rpc-reqwest" }
serde_yaml = { path = "./pkg/serde_yaml" }
Expand Down Expand Up @@ -199,6 +200,8 @@ noir-abi-inputs-macro = { path = "./pkg/noir-abi-inputs-macro" }
bungee-interface = { path = "./pkg/bungee-interface" }
bungee-client-http = { path = "./pkg/bungee-client-http" }
bungee = { path = "./pkg/bungee" }
postmark-interface = { path = "./pkg/postmark-interface" }
postmark-client-http = { path = "./pkg/postmark-client-http" }
slack-client-interface = { path = "./pkg/slack-client-interface" }
slack-client-http = { path = "./pkg/slack-client-http" }
swap-pricer-price-cache = { path = "./pkg/swap-pricer-price-cache" }
Expand Down Expand Up @@ -340,6 +343,7 @@ insta = { version = "1", features = ["json"] }
itertools = "0.14.0"
jsonwebtoken = { version = "10", features = ["rust_crypto"] }
lazy_static = "1.5"
libc = "0.2"
libp2p = { version = "0.51", default-features = false, features = [
"ping",
"request-response",
Expand Down
17 changes: 13 additions & 4 deletions docker/docker-compose.payy-auth-phala.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ services:
# allowlisted Origin header (curl smokes included). The hostname is the
# dstack gateway name for this CVM: <app-id>-<port>.<gateway-domain>.
PAYY_AUTH_PUBLIC_ORIGIN: https://3964805fd025d1d17e4bf900849d6eadf892ab0f-8787.dstack-pha-prod9.phala.network
# Confirmed Postmark sender signature. This address is non-secret and is
# safe in the attested compose text.
PAYY_AUTH_POSTMARK_FROM: auth@polybase.xyz
# The server token is a secret: it is configured as an encrypted dstack env
# var in the Phala dashboard (like the DSTACK_DOCKER_* pull credentials) and
# referenced here by name, so the value never appears in this attested
# compose text. Required whenever PAYY_AUTH_POSTMARK_FROM is set, or the bin
# fails closed at startup.
PAYY_AUTH_POSTMARK_SERVER_TOKEN: ${PAYY_AUTH_POSTMARK_SERVER_TOKEN}
# EVM eth_sendTransaction broadcast endpoints, one entry per chain the
# deployment can send on. The Alchemy URLs embed the same kind of
# publicly-shipped API key the app bundle already ships to browsers, so
Expand All @@ -46,10 +55,10 @@ services:
{"app_id": "app-local", "caip2": "eip155:10", "endpoint_id": "optimism-mainnet", "base_url": "https://opt-mainnet.g.alchemy.com/v2/tM35lzH9DscymagwCy2GQ"},
{"app_id": "app-local", "caip2": "eip155:43114", "endpoint_id": "avalanche-c-chain", "base_url": "https://avax-mainnet.g.alchemy.com/v2/tM35lzH9DscymagwCy2GQ"}
]
# Smoke-box only: re-exposes plaintext login codes at
# GET /_local/passwordless/emails, which the public profile hides by
# default. Remove as soon as real email delivery lands or this CVM
# serves anyone but us.
# Smoke-box only: the dev inbox intentionally coexists with real Postmark
# delivery via the recording transport, so smokes can still read
# GET /_local/passwordless/emails. It exposes plaintext login codes; keep
# it off anywhere this CVM serves anyone but us.
PAYY_AUTH_DEV_INBOX: "1"
restart: unless-stopped

Expand Down
3 changes: 2 additions & 1 deletion pkg/beam-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "beam-cli"
version = "0.2.4"
version = "0.3.0"
edition = "2024"
publish = false

Expand All @@ -23,6 +23,7 @@ eth-util = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
json-store = { workspace = true }
libc = { workspace = true }
num-bigint = { workspace = true }
payy-evm-client = { workspace = true }
rand = { workspace = true }
Expand Down
106 changes: 106 additions & 0 deletions pkg/beam-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,107 @@ path; confirmed receipts include the reported transaction status.
is preparing a continuation. Removing an app keeps app-local data by default;
pass `--purge-data` to delete `~/.beam/apps/data/<app>` as well.

## Profiles

Beam profiles delegate bounded public signing authority to an agent without
giving that agent the wallet password or raw private key. A profile is bound to
one stored wallet, has explicit command/app grants, and can be unlocked into a
short-lived local daemon session. The daemon holds the decrypted key only in
memory and checks profile policy immediately before signing the final
transaction.

Profile lifecycle commands:

```bash
beam profiles create agent --from alice
beam profiles list
beam profiles show agent
beam profiles revoke agent <grant-id>
beam profiles ledger agent
beam profiles remove agent
```

Creating, modifying, removing, and unlocking a profile prompts for the wallet
password. Profiles require a non-empty wallet password so profile integrity
cannot be keyed from an empty secret. Use `beam wallets change-password [wallet]`
to set a password on an existing empty-password wallet before creating a
profile.

Grant direct public signing commands:

```bash
beam profiles grant agent command native-transfer \
--chain base \
--recipient 0x1111111111111111111111111111111111111111 \
--max-native 10000000000000000 \
--max-gas 50000 \
--budget 100000000000000000

beam profiles grant agent command erc20-transfer \
--chain base \
--token 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 \
--recipient 0x1111111111111111111111111111111111111111 \
--max-token 1000000 \
--budget 10000000

beam profiles grant agent command erc20-approval \
--chain base \
--token 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913 \
--spender 0x2222222222222222222222222222222222222222 \
--max-token 1000000

beam profiles grant agent command contract-transaction \
--chain base \
--target 0x3333333333333333333333333333333333333333 \
--selector 0xa9059cbb \
--max-native 0
```

Amounts and budgets are base-unit integers. Durations use seconds by default
and also accept `s`, `m`, `h`, and `d` suffixes.

Grant app action plans from an existing approval continuation or a saved action
plan JSON file:

```bash
beam --chain base --from alice x uniswap swap USDC ETH 100 --prepare --format json
beam profiles grant agent app uniswap --approval-id <approval-id> --budget 10000000
beam profiles grant agent app uniswap --plan-json ./plan.json
```

App profile grants bind the app identity, active artifact digests, command,
wallet, chain, action-plan hash, bindings, constraints, step metadata, and plan
expiry. An app update or changed action plan requires a new grant.

Unlock a profile for agent use:

```bash
beam profiles unlock agent --ttl 1h --print-env
export BEAM_PROFILE='agent'
export BEAM_PROFILE_TOKEN='...'
```

Subsequent commands can select the profile with `--profile`:

```bash
beam --profile agent --chain base transfer 0x1111111111111111111111111111111111111111 0.001
beam --profile agent --chain base erc20 transfer USDC 0x1111111111111111111111111111111111111111 1
beam --profile agent --chain base x uniswap swap USDC ETH 100 --no-prompt
beam profiles sessions
beam profiles lock agent
```

If a selected profile is missing, expired, locked, or does not match the final
transaction, Beam fails closed instead of prompting for the password. Wallet,
profile, chain/RPC, token, app install/update/remove, and privacy management
commands are not authorized by profile sessions.

Profiles v1 cover public EVM signing only: native transfers, ERC20 transfers,
ERC20 approvals, public contract transactions, public fetch payments, and
public Beam app action plans. Privacy actions continue to prompt or fail closed
under profiles v1. Future privacy profile capabilities must keep privacy key
derivation, note access, proof generation, and spend authorization inside Beam.

## Privacy

Beam privacy support is configured per chain. Built-in Payy privacy-capable chains include a
Expand Down Expand Up @@ -381,6 +482,7 @@ beam wallets export-private-key [wallet]
beam wallets export-recovery-phrase [wallet]
beam wallets import-recovery-phrase [--name <name>] [--expected-address <address>] [--phrase-stdin | --phrase-fd <fd>]
beam wallets list
beam wallets change-password [wallet]
beam wallets rename <name|address|ens> <new-name>
beam wallets address [--private-key-stdin | --private-key-fd <fd>]
beam wallets use <name|address|ens>
Expand Down Expand Up @@ -418,6 +520,9 @@ Notes:
- `beam wallets create` prompts for a wallet name when you omit `[name]`, suggesting the next available `wallet-N` alias and accepting it when you press Enter.
- `beam wallets import` uses a verified ENS reverse record as the default wallet name when one resolves back to the imported address; otherwise it falls back to the next `wallet-N` alias.
- The CLI prompts for a password when creating/importing a wallet. Press Enter at the password prompt to create a wallet with no password; whitespace-only passwords are rejected.
- `beam wallets change-password [wallet]` re-encrypts the selected wallet after prompting for the
current password and a new password. Press Enter at the current password prompt for wallets that
were created with no password.
- Beam trims surrounding whitespace and sanitizes terminal control characters in wallet names, rejecting aliases that become empty after normalization.
- Commands that need signing prompt for the keystore password again before decrypting.
- `beam privacy address` uses the same password prompt and keystore integrity checks before
Expand All @@ -439,6 +544,7 @@ beam --format compact wallets export-private-key alice
beam wallets import --private-key-fd 3 --name alice 3< ~/.config/beam/private-key.txt
beam wallets address --private-key-fd 3 3< ~/.config/beam/private-key.txt
beam wallets export-recovery-phrase alice
beam wallets change-password alice
beam wallets import-recovery-phrase --name alice
beam wallets import-recovery-phrase --expected-address 0x1111111111111111111111111111111111111111 --name alice
pass show beam/alice/recovery-phrase | beam wallets import-recovery-phrase --phrase-stdin --name alice
Expand Down
13 changes: 13 additions & 0 deletions pkg/beam-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod fetch;
mod gas;
mod normalize;
mod privacy;
mod profiles;
mod wallet;

pub mod util;
Expand All @@ -21,6 +22,7 @@ pub use fetch::FetchArgs;
pub use gas::*;
pub(crate) use normalize::normalize_cli_args;
pub use privacy::*;
pub use profiles::*;
use util::UtilAction;
pub use wallet::*;

Expand All @@ -39,6 +41,9 @@ pub struct Cli {
#[arg(long, global = true)]
pub chain: Option<String>,

#[arg(long, global = true)]
pub profile: Option<String>,

#[arg(long = "format", global = true, value_enum, default_value_t = OutputMode::Default)]
pub output: OutputMode,

Expand Down Expand Up @@ -89,6 +94,11 @@ pub enum Command {
#[command(subcommand)]
action: AppsAction,
},
/// Manage delegated signing profiles
Profiles {
#[command(subcommand)]
action: ProfilesAction,
},
/// Run a Beam app
X(AppRunArgs),
/// Work with private balances and transfers
Expand Down Expand Up @@ -129,6 +139,8 @@ pub enum Command {
Fetch(FetchArgs),
/// Check for beam updates
Update,
#[command(name = "__profile-daemon", hide = true)]
ProfileDaemon(ProfileDaemonArgs),
#[command(name = "__refresh-update-status", hide = true)]
RefreshUpdateStatus,
}
Expand Down Expand Up @@ -231,6 +243,7 @@ impl Cli {
InvocationOverrides {
chain: self.chain.clone(),
from: self.from.clone(),
profile: self.profile.clone(),
rpc: self.rpc.clone(),
}
}
Expand Down
Loading
Loading