Overview
The Universal Gateway Transaction Protocol (UGTP) gives every user a single blockchain address that works across every supported network. Built on EIP-7702 set-code delegation, UGTP transforms an ordinary EOA into a full smart account — same address, same key, six mainnets.
The protocol is organized as a monorepo with six packages:
| Package | Purpose |
|---|---|
| @ugtp/core | Shared constants, types, contract addresses, chain registry |
| @ugtp/sdk | Client-side helpers — delegation builder, intent builder, execution optimizer |
| @ugtp/waas | Server-side Wallet as a Service SDK — institutional key management |
| ugtp-api | REST API server — delegation, sponsorship, balances, capabilities |
| ugtp-portal | Web wallet UI — connect, delegate, transfer, privacy |
| ugtp-landing | Marketing site and documentation (this site) |
Quickstart
Clone the monorepo and install dependencies with pnpm:
# Install dependencies pnpm install # Build core packages (required before other packages) pnpm --filter @ugtp/core build pnpm --filter @ugtp/sdk build # Start the API server pnpm --filter ugtp-api dev # Start the portal pnpm --filter ugtp-portal dev
@ugtp/core and @ugtp/sdk packages must be built first — other packages import from them as workspace dependencies.Server-side (WaaS)
For institutional integrations, install the WaaS SDK directly:
pnpm add @ugtp/waas @ugtp/core @ugtp/sdk
Authentication
UGTP supports two authentication paths:
Wallet Connect (Portal)
End users connect via MetaMask, WalletConnect, or any injected wallet. The connected address becomes their Universal Crypto Account. No additional signup or key generation is required.
Service Account (WaaS)
Server-side integrations authenticate through the external signer (e.g., Dfns). Credentials are loaded from environment variables and never exposed to the client:
# .env.local (server-side only — never commit) DFNS_AUTH_TOKEN= # service-account token DFNS_ORG_ID= DFNS_CRED_ID= DFNS_PRIVATE_KEY_PATH=./.secrets/dfns-private.pem DFNS_BASE_URL=https://api.dfns.io
NEXT_PUBLIC_, PUBLIC_, or VITE_. The @ugtp/waas package is server-side only and must never be imported from a client component.Account Model
A Universal Crypto Account (UCA) is one persistent user identity across public, smart, and private execution. Your EOA address becomes your UCA — the same address on Ethereum, Base, Arbitrum, Optimism, Polygon, and Avalanche.
EOA = Smart = Private
On EIP-7702 chains, delegation upgrades the EOA to a full smart account — batch execution, session keys, sponsored gas — while preserving the original address. There is no separate smart-account contract to deploy; the delegation itself confers smart-account capabilities.
On Avalanche (the coordination chain), the account operates in standard EOA mode until ACP-209 enables 7702 support. Privacy features on Avalanche use a separate escrow pattern that does not require delegation.
Delegation Lifecycle
// 1. Build the authorization import { DelegationHelper } from '@ugtp/sdk'; const auth = DelegationHelper.buildAuthorization(chainId, nonce); // 2. Sign with the user's wallet const signed = await wallet.signAuthorization(auth); // 3. Submit to the API for on-chain execution await fetch('/api/delegate', { method: 'POST', body: JSON.stringify({ authorization: signed }), });
Operation Rails
UGTP selects the optimal execution path for each transaction based on chain capabilities and delegation status:
| Rail | When Used | Features |
|---|---|---|
| EIP-7702 | Delegated account on 7702-capable chain | Batch execution, session keys, sponsored gas, full smart-account UX |
| ERC-4337 | Chains with 4337 bundler support (fallback) | UserOp-based execution, paymaster support |
| EOA | Standard transfer, no delegation needed | Direct transaction, lowest gas cost |
import { ExecutionOptimizer } from '@ugtp/sdk'; const optimizer = new ExecutionOptimizer(); const mode = optimizer.selectMode(chainId, isDelegated); // Returns: '7702' | '4337' | 'eoa'
Avalanche C-Chain
Avalanche C-Chain (43114) is the coordination chain — the source of truth for account state, privacy registration, and cross-chain routing.
Privacy Layer
Avalanche hosts the privacy escrow contracts that enable confidential transfers using Encrypted ERC (eERC) tokens. Users register for privacy on the coordination chain, then shield and unshield assets through the escrow contract.
EIP-7702 Status
Avalanche C-Chain does not yet support EIP-7702 set-code transactions. The community proposal ACP-209 is in progress. Until then, accounts on Avalanche operate as standard EOAs. Once ACP-209 is activated, delegation will unlock smart-account features on the coordination chain as well.
WaaS SDK
The @ugtp/waas package is a server-side SDK for creating and operating UGTP wallets with institutional key management.
Initialize
import { createUgtpWaas, JsonFileWalletStore } from '@ugtp/waas'; import { dfnsFromEnv } from '@ugtp/waas/signer/dfns'; const waas = createUgtpWaas({ signer: dfnsFromEnv(), store: new JsonFileWalletStore('./data/waas.json'), allowMainnet: true, });
Create a Wallet
// Dfns MPC key → same address on all 6 chains const wallet = await waas.wallets.create({ name: 'treasury-1' });
Plan & Execute
// Plan a batch transaction const plan = await waas.batch.plan({ walletId: wallet.id, recipient: '0x...', target: { chainId: 1, symbol: 'ETH', amount: '500000000000000' }, sources: [{ chainId: 1, symbol: 'ETH' }], }); // Execute (signs with Dfns, broadcasts, tracks stages) const flow = await waas.batch.execute(plan.planId); // Poll status const status = await waas.batch.status(flow.flowId);
Full API
| Method | Returns | Description |
|---|---|---|
| waas.signers.list() | SignerProvider[] | All registered signer providers |
| waas.signers.health() | HealthResult[] | Connectivity check for each provider |
| waas.signers.get(id) | SignerProvider | Look up a provider by ID |
| waas.wallets.create(opts?) | WalletRecord | Create signer + resolve UCA + optional activation |
| waas.wallets.get(id) | WalletRecord | Fetch a wallet by ID |
| waas.wallets.list() | WalletRecord[] | All wallets in the store |
| waas.wallets.refresh(id) | WalletRecord | Re-read delegation state from chain |
| waas.wallets.activate(id, chainId) | string | EIP-7702 delegation (self-signed tx) |
| waas.wallets.activateSponsored(id, chainId) | object | Gas-free delegation via /api/delegate |
| waas.wallets.balances(id) | Balances | Multi-chain native + ERC-20 balances |
| waas.wallets.privacyStatus(id) | string | Avalanche privacy registration state |
| waas.batch.plan(intent) | BatchPlan | Build steps from an intent (quotes, fees) |
| waas.batch.execute(planId) | ExecutionFlow | Sign and broadcast each step |
| waas.batch.status(flowId) | ExecutionFlow | Poll execution progress |
| waas.batch.list(walletId?) | ExecutionFlow[] | Past flows, optionally filtered |
Error Codes
| Code | Meaning |
|---|---|
| WALLET_NOT_FOUND | No wallet with the given ID in the store |
| UNSUPPORTED_PROVIDER | No registered signer provider with the given ID |
| UNSUPPORTED_NETWORK | Chain does not support the requested operation |
| ACTIVATION_FAILED | EIP-7702 delegation transaction failed |
| PLANNING_FAILED | Batch plan could not be constructed |
| EXECUTION_FAILED | A step in the batch execution reverted or timed out |
| MAINNET_DISABLED | allowMainnet is false and step targets a mainnet chain |
| VALUE_CAP_EXCEEDED | Native value exceeds maxNativeValueWei safety cap |
Safety Rails
ALLOW_MAINNET_TRANSACTIONS— must be explicitlytrue; the executor rejects every mainnet step otherwise.MAX_NATIVE_VALUE_WEI— per-step native value cap (default 0.002 ETH). Any step exceeding it is rejected before signing.- Simulation —
eth_call/estimateGasbefore every broadcast. Reverts are caught before the transaction reaches the mempool. - Pending policy status — when Dfns policies require multi-party approval, the executor surfaces
Pendingas a stage status rather than failing.
External Signers
UGTP's Wallet as a Service architecture decouples key management from protocol logic through the SignerProvider interface. Any institutional key management system that can sign EVM transactions can be plugged in as an external signer.
SignerProvider Interface
Every external signer implements this interface. Four EVM signing kinds are supported: Transaction, Eip7702, Eip712, and Eip191.
interface SignerProvider { id: string; createSigner(params: CreateSignerParams): Promise<SignerRecord>; signTransaction(signerId: string, tx: Record<string, unknown>): Promise<string>; signAuthorization(signerId: string, auth: Record<string, unknown>): Promise<SignedAuthorization>; signTypedData(signerId: string, data: Record<string, unknown>): Promise<string>; signMessage(signerId: string, message: string): Promise<string>; healthCheck(): Promise<{ ok: boolean; latencyMs: number }>; }
The toProviderAccount() bridge wraps any SignerProvider as a viem Account so the full @ugtp/sdk works unchanged.
Dfns — Production Signer
Dfns is the production external signer for UGTP. It provides MPC-based key management with hardware-backed key shares, programmable policy engine, and multi-party approval workflows.
Architecture
- MPC key generation — private key shares are distributed across Dfns infrastructure. No single party (including Dfns) ever holds the complete key.
- Deterministic addressing — one MPC key derives the same Ethereum address on all six UGTP chains. The wallet is portable without bridging.
- Policy engine — configurable rules (spend limits, allowlists, time locks) enforced at the signing layer. Policies requiring multi-party approval surface as
Pendingstatus in the WaaS executor. - Service-account auth — the customer's institutional signer holds the key. Authentication uses asymmetric credentials (RSA key pair). The private key never leaves the server.
Configuration
# .env.local (server-side only) DFNS_AUTH_TOKEN=sa_... # service-account token DFNS_ORG_ID=or_... # organization ID DFNS_CRED_ID=cr_... # credential ID DFNS_PRIVATE_KEY_PATH=./.secrets/dfns-private.pem DFNS_BASE_URL=https://api.dfns.io DFNS_DEFAULT_NETWORK=Ethereum
Usage
import { dfnsFromEnv } from '@ugtp/waas/signer/dfns'; // Initialize from environment variables const signer = dfnsFromEnv(); // Or configure explicitly import { DfnsSignerProvider } from '@ugtp/waas/signer/dfns'; const signer = new DfnsSignerProvider({ authToken: process.env.DFNS_AUTH_TOKEN, orgId: process.env.DFNS_ORG_ID, credId: process.env.DFNS_CRED_ID, privateKeyPath: process.env.DFNS_PRIVATE_KEY_PATH, baseUrl: process.env.DFNS_BASE_URL, });
Supported Operations
| Operation | Dfns Method | UGTP Usage |
|---|---|---|
| Create wallet | POST /wallets | waas.wallets.create() — generates MPC key, returns address |
| Sign transaction | POST /wallets/:id/signatures | waas.batch.execute() — signs each step in a batch |
| Sign EIP-7702 auth | POST /wallets/:id/signatures | waas.wallets.activate() — delegation authorization |
| Sign typed data | POST /wallets/:id/signatures | Privacy registration (EIP-712 domain) |
| Sign message | POST /wallets/:id/signatures | Arbitrary message signing (EIP-191) |
| Health check | GET /health | waas.signers.health() — latency and connectivity |
Utila
Utila provides institutional-grade MPC wallet infrastructure with policy-based access controls, multi-chain support, and SOC 2 compliance. The Utila adapter will implement the SignerProvider interface, allowing UGTP wallets to use Utila as an alternative to Dfns for key management.
Cobo
Cobo offers a full-stack digital asset custody solution with MPC-TSS technology, risk management engine, and comprehensive API coverage. The Cobo adapter will follow the same SignerProvider pattern, enabling enterprises already using Cobo to integrate with UGTP without changing their custody infrastructure.
Adding a Custom Provider
To integrate a new key-management system, implement the SignerProvider interface in a single adapter file and register it:
import { createUgtpWaas } from '@ugtp/waas'; import { MyCustomProvider } from './my-provider'; const waas = createUgtpWaas({ signer: new MyCustomProvider({ /* config */ }), store: new JsonFileWalletStore('./data/waas.json'), });
Transactions
UGTP transactions are expressed as intents — declarative descriptions of what the user wants to accomplish. The SDK resolves intents into executable steps.
Intent Builder
import { IntentBuilder } from '@ugtp/sdk'; // Native transfer const intent = IntentBuilder.transfer({ from: address, to: recipient, chainId: 1, asset: 'ETH', amount: '1000000000000000000', // 1 ETH in wei }); // ERC-20 transfer const tokenIntent = IntentBuilder.transfer({ from: address, to: recipient, chainId: 43114, asset: 'USDt', amount: '2000000', // 2 USDt (6 decimals) });
Batch Execution
On delegated accounts, multiple operations can be batched into a single transaction. The batch planner quotes fees, selects source chains, and produces an ordered list of steps:
const plan = await waas.batch.plan({ walletId: wallet.id, recipient: '0x...', target: { chainId: 1, symbol: 'ETH', amount: '500000000000000' }, sources: [ { chainId: 1, symbol: 'ETH' }, { chainId: 8453, symbol: 'ETH' }, // fallback to Base ], }); // Each step: { chainId, type, to, value, data, gasEstimate } console.log(plan.steps);
Private Tokens
UGTP integrates with Encrypted ERC (eERC) tokens on Avalanche C-Chain for confidential transfers. Amounts and balances are encrypted on-chain; only the account holder (and authorized auditors) can decrypt them.
Privacy Lifecycle
- Register — derive a privacy key pair from the account, register the public key on the coordination chain.
- Shield — deposit tokens into the privacy escrow. The escrow mints an equivalent encrypted balance.
- Transfer — send encrypted tokens to another registered account. The transfer is visible on-chain but the amount is encrypted.
- Unshield — withdraw from the escrow back to a standard ERC-20 balance.
KEY_DERIVATION_PURPOSE = 'Derive UGTP private-account keys' — this value must not change.Swaps & Routing
Cross-chain routing selects the cheapest path to fulfill a transfer intent. When the source and destination chains differ, the router computes bridge fees, DEX swap quotes, and gas costs to find the optimal route.
The batch planner integrates routing automatically — specify multiple source chains in the sources array, and the planner picks the best combination:
const plan = await waas.batch.plan({ walletId: wallet.id, recipient: '0x...', target: { chainId: 1, symbol: 'ETH', amount: '1000000000000000' }, sources: [ { chainId: 1, symbol: 'ETH' }, { chainId: 8453, symbol: 'ETH' }, { chainId: 42161, symbol: 'ETH' }, ], }); // plan.route describes the chosen path and estimated costs
Network Support
UGTP supports six production mainnets. Each chain has a specific capability profile based on EIP support and contract deployment status:
| Network | Chain ID | 7702 | 4337 | Privacy | Role |
|---|---|---|---|---|---|
| Ethereum | 1 | ✓ | ✓ | — | Primary L1 |
| Base | 8453 | ✓ | ✓ | — | Preferred for first type-4 tx |
| Arbitrum One | 42161 | ✓ | ✓ | — | L2 Rollup |
| Optimism | 10 | ✓ | ✓ | — | L2 Rollup |
| Polygon PoS | 137 | ✓ | ✓ | — | Sidechain |
| Avalanche C-Chain | 43114 | — | — | ✓ | Coordination chain |
Fees & Lifecycle
UGTP applies a transparent fee model on every batch execution:
| Component | Value | Description |
|---|---|---|
| Proportional | 25 bps | Applied to the transfer amount (fee-from-amount) |
| Flat | 0.001 ETH | Per-batch flat fee in native currency |
| Recipient | 0xdC7C3AB388375FF0E83fcc66F61A2214C94B64db | System fee collector address |
Fees are calculated fee-from-amount — deducted from the transfer value, not added on top. The recipient receives the amount minus fees.
Execution Lifecycle
A batch flow moves through these stages:
- Plan — intent is resolved into steps with fee quotes
- Simulate — each step is dry-run via eth_call
- Sign — external signer signs each transaction
- Broadcast — signed transactions are submitted to the network
- Confirm — receipts are collected and the flow is marked complete
API Reference
The ugtp-api server exposes the following REST endpoints:
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/health | Health check and protocol status |
| GET | /api/uca/:address | UCA delegation status across all chains |
| GET | /api/balances/:address | Multi-chain asset balances |
| GET | /api/capabilities | Per-chain capability matrix and contract status |
| POST | /api/delegate | Submit a signed EIP-7702 delegation |
| POST | /api/sponsor | Submit a sponsored execution request |
| GET | /api/legacy/aw/:eoa | Check for legacy Advanced Wallet deployment |
WaaS Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/waas/signers | List all registered signer providers |
| GET | /api/waas/signers/health | Health check all signers |
| POST | /api/waas/wallets | Create a new WaaS wallet |
| GET | /api/waas/wallets | List all wallets |
| GET | /api/waas/wallets/:id | Get wallet details |
| POST | /api/waas/wallets/:id/activate | Activate (delegate) on a chain |
| GET | /api/waas/wallets/:id/balances | Multi-chain balances for a wallet |
| POST | /api/waas/batch/plan | Plan a batch transaction |
| POST | /api/waas/batch/execute | Execute a planned batch |
| GET | /api/waas/batch/:flowId | Check batch execution status |
Deployed Contracts
Canonical contract addresses — deployed once, reused across the protocol:
| Contract | Address | Chain |
|---|---|---|
| UGTPAccount | 0xa4B2befEE38792B931777df37FEaE679Ed8cfA17 | All EIP-7702 chains |
| PrivacyEscrow | See @ugtp/core | Avalanche (43114) |
Protocol Constants
| Constant | Value |
|---|---|
| UGTPAccount Address | 0xa4B2befEE38792B931777df37FEaE679Ed8cfA17 |
| Coordination Chain | Avalanche (43114) |
| Fee (Proportional) | 25 bps |
| Fee (Flat) | 0.001 ETH |
| Fee Recipient | 0xdC7C3AB388375FF0E83fcc66F61A2214C94B64db |
| Protocol Version | 0.1.0 |
| Status | beta |