diff --git a/protobufs/client.proto b/protobufs/client.proto index c23ed24..f548f69 100644 --- a/protobufs/client.proto +++ b/protobufs/client.proto @@ -2,6 +2,8 @@ syntax = "proto3"; package arbiter.client; +import "evm.proto"; + message AuthChallengeRequest { bytes pubkey = 1; } @@ -21,6 +23,8 @@ message ClientRequest { oneof payload { AuthChallengeRequest auth_challenge_request = 1; AuthChallengeSolution auth_challenge_solution = 2; + arbiter.evm.EvmSignTransactionRequest evm_sign_transaction = 3; + arbiter.evm.EvmAnalyzeTransactionRequest evm_analyze_transaction = 4; } } @@ -28,5 +32,7 @@ message ClientResponse { oneof payload { AuthChallenge auth_challenge = 1; AuthOk auth_ok = 2; + arbiter.evm.EvmSignTransactionResponse evm_sign_transaction = 3; + arbiter.evm.EvmAnalyzeTransactionResponse evm_analyze_transaction = 4; } } diff --git a/protobufs/evm.proto b/protobufs/evm.proto index 18d22f8..0f8a0ee 100644 --- a/protobufs/evm.proto +++ b/protobufs/evm.proto @@ -2,6 +2,9 @@ syntax = "proto3"; package arbiter.evm; +import "google/protobuf/empty.proto"; +import "google/protobuf/timestamp.proto"; + enum EvmError { EVM_ERROR_UNSPECIFIED = 0; EVM_ERROR_VAULT_SEALED = 1; @@ -29,3 +32,184 @@ message WalletListResponse { EvmError error = 2; } } + +// --- Grant types --- + +message TransactionRateLimit { + uint32 count = 1; + int64 window_secs = 2; +} + +message VolumeRateLimit { + bytes max_volume = 1; // U256 as big-endian bytes + int64 window_secs = 2; +} + +message SharedSettings { + int32 wallet_id = 1; + uint64 chain_id = 2; + optional google.protobuf.Timestamp valid_from = 3; + optional google.protobuf.Timestamp valid_until = 4; + optional bytes max_gas_fee_per_gas = 5; // U256 as big-endian bytes + optional bytes max_priority_fee_per_gas = 6; // U256 as big-endian bytes + optional TransactionRateLimit rate_limit = 7; +} + +message EtherTransferSettings { + repeated bytes targets = 1; // list of 20-byte Ethereum addresses + VolumeRateLimit limit = 2; +} + +message TokenTransferSettings { + bytes token_contract = 1; // 20-byte Ethereum address + optional bytes target = 2; // 20-byte Ethereum address; absent means any recipient allowed + repeated VolumeRateLimit volume_limits = 3; +} + +message SpecificGrant { + oneof grant { + EtherTransferSettings ether_transfer = 1; + TokenTransferSettings token_transfer = 2; + } +} + +message EtherTransferMeaning { + bytes to = 1; // 20-byte Ethereum address + bytes value = 2; // U256 as big-endian bytes +} + +message TokenInfo { + string symbol = 1; + bytes address = 2; // 20-byte Ethereum address + uint64 chain_id = 3; +} + +// Mirror of token_transfers::Meaning +message TokenTransferMeaning { + TokenInfo token = 1; + bytes to = 2; // 20-byte Ethereum address + bytes value = 3; // U256 as big-endian bytes +} + +// Mirror of policies::SpecificMeaning +message SpecificMeaning { + oneof meaning { + EtherTransferMeaning ether_transfer = 1; + TokenTransferMeaning token_transfer = 2; + } +} + +// --- Eval error types --- +message GasLimitExceededViolation { + optional bytes max_gas_fee_per_gas = 1; // U256 as big-endian bytes + optional bytes max_priority_fee_per_gas = 2; // U256 as big-endian bytes +} + +message EvalViolation { + oneof kind { + bytes invalid_target = 1; // 20-byte Ethereum address + GasLimitExceededViolation gas_limit_exceeded = 2; + google.protobuf.Empty rate_limit_exceeded = 3; + google.protobuf.Empty volumetric_limit_exceeded = 4; + google.protobuf.Empty invalid_time = 5; + google.protobuf.Empty invalid_transaction_type = 6; + } +} + +// Transaction was classified but no grant covers it +message NoMatchingGrantError { + SpecificMeaning meaning = 1; +} + +// Transaction was classified and a grant was found, but constraints were violated +message PolicyViolationsError { + SpecificMeaning meaning = 1; + repeated EvalViolation violations = 2; +} + +// top-level error returned when transaction evaluation fails +message TransactionEvalError { + oneof kind { + google.protobuf.Empty contract_creation_not_supported = 1; + google.protobuf.Empty unsupported_transaction_type = 2; + NoMatchingGrantError no_matching_grant = 3; + PolicyViolationsError policy_violations = 4; + } +} + +// --- UserAgent grant management --- +message EvmGrantCreateRequest { + int32 client_id = 1; + SharedSettings shared = 2; + SpecificGrant specific = 3; +} + +message EvmGrantCreateResponse { + oneof result { + int32 grant_id = 1; + EvmError error = 2; + } +} + +message EvmGrantDeleteRequest { + int32 grant_id = 1; +} + +message EvmGrantDeleteResponse { + oneof result { + google.protobuf.Empty ok = 1; + EvmError error = 2; + } +} + +// Basic grant info returned in grant listings +message GrantEntry { + int32 id = 1; + int32 client_id = 2; + SharedSettings shared = 3; +} + +message EvmGrantListRequest { + optional int32 wallet_id = 1; +} + +message EvmGrantListResponse { + oneof result { + EvmGrantList grants = 1; + EvmError error = 2; + } +} + +message EvmGrantList { + repeated GrantEntry grants = 1; +} + +// --- Client transaction operations --- + +message EvmSignTransactionRequest { + bytes wallet_address = 1; // 20-byte Ethereum address + bytes rlp_transaction = 2; // RLP-encoded EIP-1559 transaction (unsigned) +} + +// oneof because signing and evaluation happen atomically — a signing failure +// is always either an eval error or an internal error, never a partial success +message EvmSignTransactionResponse { + oneof result { + bytes signature = 1; // 65-byte signature: r[32] || s[32] || v[1] + TransactionEvalError eval_error = 2; + EvmError error = 3; + } +} + +message EvmAnalyzeTransactionRequest { + bytes wallet_address = 1; // 20-byte Ethereum address + bytes rlp_transaction = 2; // RLP-encoded EIP-1559 transaction +} + +message EvmAnalyzeTransactionResponse { + oneof result { + SpecificMeaning meaning = 1; + TransactionEvalError eval_error = 2; + EvmError error = 3; + } +} diff --git a/protobufs/user_agent.proto b/protobufs/user_agent.proto index b468e31..09a2c2a 100644 --- a/protobufs/user_agent.proto +++ b/protobufs/user_agent.proto @@ -58,6 +58,9 @@ message UserAgentRequest { google.protobuf.Empty query_vault_state = 5; google.protobuf.Empty evm_wallet_create = 6; google.protobuf.Empty evm_wallet_list = 7; + arbiter.evm.EvmGrantCreateRequest evm_grant_create = 8; + arbiter.evm.EvmGrantDeleteRequest evm_grant_delete = 9; + arbiter.evm.EvmGrantListRequest evm_grant_list = 10; } } message UserAgentResponse { @@ -69,5 +72,8 @@ message UserAgentResponse { VaultState vault_state = 5; arbiter.evm.WalletCreateResponse evm_wallet_create = 6; arbiter.evm.WalletListResponse evm_wallet_list = 7; + arbiter.evm.EvmGrantCreateResponse evm_grant_create = 8; + arbiter.evm.EvmGrantDeleteResponse evm_grant_delete = 9; + arbiter.evm.EvmGrantListResponse evm_grant_list = 10; } }