TxProxy Service
TxProxy is the centralized transaction signing and broadcasting gatekeeper for the Neo N3 Mini-App Platform. It holds the platform's TEE signing keys and enforces strict allowlist policies before signing any transaction.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ TxProxy Service │
├─────────────────────────────────────────────────────────────┤
│ Security Layer │
│ ├── Contract Allowlist - Only approved contracts │
│ ├── Method Allowlist - Only approved methods per contract │
│ ├── Intent Policy - Asset constraints (GAS/NEO) │
│ └── Replay Protection - Request ID deduplication │
├─────────────────────────────────────────────────────────────┤
│ Signing Layer │
│ ├── GlobalSigner Client - Preferred (centralized TEE key) │
│ └── Local TEE Signer - Fallback (enclave-held key) │
└─────────────────────────────────────────────────────────────┘
File Structure
services/txproxy/marble/
├── service.go # Main service, initialization, replay cache
├── handlers.go # HTTP request handler
├── allowlist.go # Contract/method allowlist logic
└── types.go # Type definitions
API Endpoint
| Method |
Endpoint |
Auth |
Description |
| POST |
/invoke |
mTLS |
Sign and broadcast a contract invocation |
Allowlist Configuration
The allowlist defines which contracts and methods TxProxy will sign transactions for.
{
"contracts": {
"<contract_address>": ["method1", "method2"],
"<contract_address>": ["*"]
}
}
Example (allow platform contracts):
{
"contracts": {
"<gas_address>": ["transfer"],
"<paymenthub_address>": ["configureApp", "withdraw"],
"<governance_address>": ["stake", "unstake", "vote"],
"<randomnesslog_address>": ["record"],
"<pricefeed_address>": ["update"],
"<automationanchor_address>": ["markExecuted"],
"<servicegateway_address>": ["fulfillRequest"]
}
}
Rules
- Contract addresses are normalized to lowercase without
0x prefix (40 hex chars)
- Method names are canonicalized by lowercasing the first character (to match Neo C# devpack manifest names like
getLatest, setUpdater, transfer)
"*" allows all methods on a contract (not recommended in production)
- Empty array
[] blocks all methods
Loading Priority
TXPROXY_ALLOWLIST secret (MarbleRun injected)
TXPROXY_ALLOWLIST environment variable
- Empty allowlist (blocks all)
Intent Policy Gating
Request field intent enables stricter checks for platform user flows:
| Intent |
Asset Constraint |
Contract |
Allowed Methods |
payments |
GAS only |
GAS |
transfer to PaymentHub |
governance |
NEO only |
Governance |
stake, unstake, vote |
Requires corresponding contract address environment variables:
CONTRACT_PAYMENT_HUB_ADDRESS for payments intent
CONTRACT_GAS_ADDRESS (optional override; defaults to native GAS address)
CONTRACT_GOVERNANCE_ADDRESS for governance intent
Note: the allowlist must still permit GAS transfer when using the payments intent.
Replay Protection
- Each request includes a unique
request_id
- TxProxy maintains an in-memory cache of seen request IDs
- Duplicate requests within the replay window (10 min) are rejected
- Cache cleanup runs periodically to remove expired entries
Configuration
| Environment Variable |
Description |
Required |
NEO_RPC_URL |
Neo N3 RPC endpoint |
Yes |
TXPROXY_ALLOWLIST |
JSON allowlist config |
Yes (strict mode) |
GLOBALSIGNER_SERVICE_URL |
GlobalSigner URL |
Recommended |
TEE_PRIVATE_KEY |
Fallback signing key |
If no GlobalSigner |
CONTRACT_PAYMENT_HUB_ADDRESS |
PaymentHub contract |
For payments intent |
CONTRACT_GAS_ADDRESS |
GAS contract address |
Optional override |
CONTRACT_GOVERNANCE_ADDRESS |
Governance contract |
For governance intent |
Security
4-Layer Defense
- SDK Layer: Type signatures prevent invalid asset parameters
- Edge Layer: Validates JWT, enforces rate limits
- TxProxy Layer: Allowlist + intent policy + replay protection
- Contract Layer: Hardcoded asset checks (
if asset != GAS throw)
Key Management
- GlobalSigner (Preferred): Signing keys held in dedicated TEE service
- Local Key (Fallback): Key injected via MarbleRun manifest
- Keys never leave the enclave boundary