This document describes all valid inbound transaction flows in the Push Chain gateway architecture. "Inbound" means: a user on an external EVM chain initiates a transaction that results in funds or payload delivery on Push Chain.
UniversalGateway on the external chain is the sole entry point. It infers the transaction type,
enforces rate limits, moves funds, and emits the UniversalTx event that TSS observes to mint
PRC20 or execute UEA payloads on Push Chain.
| Chain | Contract | Role |
|---|---|---|
| External Chain | UniversalGateway |
Inbound entry point; infers TX_TYPE; routes gas/funds; handles reverts |
| External Chain | Vault |
Holds ERC20 funds locked for bridging; receives ERC20 deposits via safeTransferFrom |
| External Chain | CEAFactory |
CEA identity registry; used for sendUniversalTxFromCEA anti-spoof |
| External Chain | CEA |
Per-UEA smart contract wallet; can call sendUniversalTxFromCEA back into the gateway |
| External Chain | Chainlink Feeds | ETH/USD price oracle for USD caps; optional L2 sequencer uptime feed |
| External Chain | Uniswap V3 | Token→native swap for the UniversalTokenTxRequest entry point |
| Push Chain | TSS (off-chain) | Observes UniversalTx events; mints PRC20; executes UEA payloads |
| Actor | Description |
|---|---|
| BOB | The end user on the external chain. Has a UEA on Push Chain and an associated CEA on the external chain. |
| UEA | BOB's Universal Execution Account on Push Chain — the address funds and payloads are credited to. |
| TSS | Off-chain Threshold Signature Scheme relayer operated by Push Chain validators. Observes UniversalTx events; mints PRC20 or executes payloads via UEA. |
| CEA | BOB's Chain Execution Account on the external chain. Can call sendUniversalTxFromCEA to bridge funds back to Push Chain. |
Inbound transactions result in PRC20 minting on Push Chain:
- Native ETH gas/funds: Forwarded directly to
TSS_ADDRESSby_handleDeposits(UniversalGateway.sol:732). TSS observes theUniversalTxevent and mints PRC20-ETH to the recipient UEA. - ERC20 funds: Pulled from the caller into
VaultviasafeTransferFrom(UniversalGateway.sol:737). TSS observes theUniversalTxevent and mints the corresponding PRC20 token to the recipient UEA. recipient == address(0)inUniversalTx: Push Chain attributes the inbound tx tosender's own UEA.recipient != address(0)(CEA path only): Push Chain routes to that explicit UEA address.
TX_TYPE is inferred automatically from four decision variables. Users never specify it.
hasPayload := req.payload.length > 0
hasFunds := req.amount > 0
fundsIsNative := req.token == address(0)
hasNativeValue := nativeValue > 0 // msg.value or swapped ETH from token-gas path
hasPayload |
hasFunds |
fundsIsNative |
hasNativeValue |
TX_TYPE | Route |
|---|---|---|---|---|---|
| false | false | — | true | GAS |
Instant |
| true | false | — | any | GAS_AND_PAYLOAD |
Instant |
| false | true | true | true | FUNDS |
Standard |
| false | true | false | any | FUNDS |
Standard |
| true | true | false | any | FUNDS_AND_PAYLOAD (Case 2.1 / 2.3) |
Standard |
| true | true | true | true | FUNDS_AND_PAYLOAD (Case 2.2) |
Standard |
| (any other) | — | — | — | ❌ reverts InvalidInput |
— |
Note: At the type-inference level (
_fetchTxType), Cases 2.1 and 2.3 are unified under the single!fundsIsNativecheck. The routing function_sendTxWithFundsstill distinguishes them: Case 2.1 fires when post-feenativeValue == 0, Case 2.3 when post-feenativeValue > 0.
Protocol Fee — see 2_UniversalGateway.md §4 Inbound Fees.
| System | Applies To | Mechanism |
|---|---|---|
| USD Caps (per-tx) | GAS, GAS_AND_PAYLOAD |
_checkUSDCaps: min/max USD per tx via Chainlink ETH/USD feed (line 718-722) |
| Block USD Cap | GAS, GAS_AND_PAYLOAD |
_checkBlockUSDCap: per-block rolling USD budget; resets each block (line 745) |
| Epoch Rate Limit | FUNDS, FUNDS_AND_PAYLOAD |
_consumeRateLimit: per-token quota; resets after epochDurationSec (line 770) |
USD caps apply only when nativeValue > 0. A GAS_AND_PAYLOAD tx with msg.value == 0
(payload-only) skips all USD cap checks.
| Function | Caller | CEA blocked? | Key extra step |
|---|---|---|---|
sendUniversalTx(UniversalTxRequest) |
Any EOA/contract | Yes | None |
sendUniversalTx(UniversalTokenTxRequest) |
Any EOA/contract | Yes | Swap gasToken → native via swapToNative |
sendUniversalTxFromCEA(UniversalTxRequest) |
CEA only | N/A | CEA identity check + anti-spoof check |
CEAs are blocked from calling sendUniversalTx directly (reverts InvalidInput,
UniversalGateway.sol:311). They must use sendUniversalTxFromCEA.
Pure native gas deposit. No funds, no payload. The deposited ETH tops up the caller's UEA gas balance on Push Chain.
Invariants: req.amount == 0, req.payload == "", nativeValue > 0.
Rate limits: USD caps + block USD cap applied to the full nativeValue.
ETH routing: Forwarded to TSS_ADDRESS via _handleDeposits(address(0), amount).
Event: UniversalTx(sender=BOB, recipient=address(0), token=address(0), amount=nativeValue, payload="", txType=GAS, fromCEA=false).
Scenario: BOB sends ETH directly to fund his UEA's gas on Push Chain.
Call:
UniversalTxRequest({
recipient: address(0), // Push Chain credits BOB's UEA
token: address(0),
amount: 0,
payload: bytes(""),
revertRecipient: BOB_ADDRESS,
signatureData: bytes("")
})
// msg.value = gasAmount + INBOUND_FEE (must be within USD caps after fee extraction)TX_TYPE: GAS (hasPayload=false, hasFunds=false, hasNativeValue=true).
sequenceDiagram
autonumber
participant BOB as BOB (External Chain)
participant GW as UniversalGateway (External Chain)
participant TSS as TSS_ADDRESS (External Chain)
participant PC as Push Chain
BOB->>GW: sendUniversalTx{value: gasAmount + INBOUND_FEE}(req)
GW->>GW: _fetchTxType → GAS
GW->>GW: _collectProtocolFee → forward INBOUND_FEE to TSS
GW->>GW: _checkUSDCaps(gasAmount)
GW->>GW: _checkBlockUSDCap(gasAmount)
GW->>TSS: forward gasAmount ETH
GW-->>PC: emit UniversalTx(sender=BOB, recipient=0, txType=GAS)
Note over PC: Mints gas to BOB's UEA
Scenario: BOB pays gas in an ERC20 token (e.g. USDC). The gateway swaps it to native ETH first, then processes it identically to 2.1.
Call: sendUniversalTx(UniversalTokenTxRequest) with gasToken=USDC, gasAmount=X,
amountOutMinETH=Y (slippage bound), deadline=T. The amount and token fields are 0/empty
for a pure gas top-up.
Swap path (swapToNative, UniversalGateway.sol:802-853):
- If
gasToken == WETH: pull WETH from BOB, unwrap to ETH (fast-path). - Otherwise: scan
v3FeeOrderfee tiers for agasToken/WETHpool, pullgasTokenfrom BOB, calluniV3Router.exactInputSingle, unwrap WETH to ETH. - Enforce
ethOut >= amountOutMinETH.
After swap: nativeValue = ethOut. TX_TYPE inference proceeds identically to 2.1.
Key validations (UniversalGateway.sol:321-324):
gasToken != address(0)gasAmount > 0amountOutMinETH > 0deadline == 0 OR deadline >= block.timestamp
sequenceDiagram
autonumber
participant BOB as BOB (External Chain)
participant GW as UniversalGateway (External Chain)
participant V3 as Uniswap V3 / WETH (External Chain)
participant TSS as TSS_ADDRESS (External Chain)
participant PC as Push Chain
BOB->>GW: sendUniversalTx(UniversalTokenTxRequest{gasToken=USDC, gasAmount=X})
GW->>V3: safeTransferFrom(BOB, gateway, X USDC)
GW->>V3: exactInputSingle(USDC→WETH) + WETH.withdraw → ethOut
GW->>GW: _checkUSDCaps(ethOut), _checkBlockUSDCap(ethOut)
GW->>TSS: forward ethOut ETH
GW-->>PC: emit UniversalTx(sender=BOB, recipient=0, txType=GAS)
Note over PC: Mints gas to BOB's UEA
req.payload.length > 0, req.amount == 0. USD caps applied only when nativeValue > 0.
TX_TYPE: GAS_AND_PAYLOAD (hasPayload=true, hasFunds=false).
The payload is forwarded to Push Chain for execution via BOB's UEA. No funds are bridged.
Scenario: BOB submits a payload to execute on Push Chain but provides no ETH. BOB's UEA must already have gas on Push Chain.
Call: sendUniversalTx(req) with payload=<pushChainCalldata>, msg.value=INBOUND_FEE
(or 0 if fee is disabled).
Rate limits: No USD cap or block cap check (skipped when post-fee nativeValue == 0,
UniversalGateway.sol:392).
Event: UniversalTx(sender=BOB, recipient=address(0), token=address(0), amount=0, payload=<calldata>, txType=GAS_AND_PAYLOAD, fromCEA=false).
Scenario: BOB submits a payload and also tops up his UEA's gas in one transaction.
Call: sendUniversalTx(req) with payload=<calldata>, msg.value=gasAmount + INBOUND_FEE.
Rate limits: USD caps + block cap checked on post-fee gasAmount.
Event: UniversalTx(amount=gasAmount, txType=GAS_AND_PAYLOAD).
Same as 3.1 or 3.2 but uses sendUniversalTx(UniversalTokenTxRequest). The gasToken is
swapped to native (ethOut). If ethOut > 0, acts as 3.2; the gas portion is forwarded to
TSS.
req.amount > 0, req.payload == "". Epoch rate-limit consumed via _consumeRateLimit.
Standard route: higher block confirmation requirement compared to GAS routes.
Scenario: BOB bridges ETH from the external chain to Push Chain. TSS mints PRC20-ETH to BOB's UEA.
Call:
UniversalTxRequest({
recipient: address(0),
token: address(0), // native
amount: 1 ether,
payload: bytes(""),
revertRecipient: BOB_ADDRESS,
signatureData: bytes("")
})
// msg.value must equal req.amount + INBOUND_FEETX_TYPE: FUNDS (hasFunds=true, hasPayload=false, fundsIsNative=true, hasNativeValue=true).
Validation (UniversalGateway.sol:419): After fee extraction, req.amount != nativeValue
→ revert InvalidAmount. Since nativeValue = msg.value - INBOUND_FEE, this means
msg.value must equal req.amount + INBOUND_FEE exactly.
Routing:
_consumeRateLimit(address(0), req.amount)— consume native epoch quota._handleDeposits(address(0), req.amount)— forward ETH toTSS_ADDRESS.- Emit
UniversalTx(txType=FUNDS).
sequenceDiagram
autonumber
participant BOB as BOB (External Chain)
participant GW as UniversalGateway (External Chain)
participant TSS as TSS_ADDRESS (External Chain)
participant PC as Push Chain
BOB->>GW: sendUniversalTx{value: 1 ETH + INBOUND_FEE}(req{token=0x0, amount=1ETH})
GW->>GW: _fetchTxType → FUNDS
GW->>GW: _collectProtocolFee → forward INBOUND_FEE to TSS
GW->>GW: _consumeRateLimit(address(0), 1ETH)
GW->>TSS: forward 1 ETH
GW-->>PC: emit UniversalTx(sender=BOB, recipient=0, token=0x0, amount=1ETH, txType=FUNDS)
Note over PC: Mints 1 PRC20-ETH to BOB's UEA
Scenario: BOB bridges ERC20 tokens (e.g. USDC) from the external chain to Push Chain. No gas top-up needed — BOB's UEA already has gas on Push Chain.
Call: req.token = USDC_ADDRESS, req.amount = 1000e6, msg.value = 0 (or INBOUND_FEE
if fee is enabled).
TX_TYPE: FUNDS (hasFunds=true, hasPayload=false, fundsIsNative=false).
Token support check (UniversalGateway.sol:736): tokenToLimitThreshold[token] == 0
→ revert NotSupported. Token must be in the allowlist.
Routing (Case 1.2, nativeValue == 0 post-fee):
_consumeRateLimit(USDC, 1000e6)— consume USDC epoch quota._handleDeposits(USDC, 1000e6)—safeTransferFrom(BOB, Vault, 1000e6).- Emit
UniversalTx(txType=FUNDS, token=USDC).
sequenceDiagram
autonumber
participant BOB as BOB (External Chain)
participant GW as UniversalGateway (External Chain)
participant V as Vault (External Chain)
participant USDC as USDC Token (External Chain)
participant PC as Push Chain
BOB->>GW: sendUniversalTx(req{token=USDC, amount=1000e6})
Note over BOB: BOB must have approved gateway for 1000e6 USDC
GW->>GW: _fetchTxType → FUNDS
GW->>GW: _consumeRateLimit(USDC, 1000e6)
GW->>USDC: safeTransferFrom(BOB, Vault, 1000e6)
USDC-->>V: +1000e6 USDC
GW-->>PC: emit UniversalTx(sender=BOB, recipient=0, token=USDC, amount=1000e6, txType=FUNDS)
Note over PC: Mints 1000 PRC20-USDC to BOB's UEA
Scenario: BOB bridges ERC20 tokens and also tops up his UEA's gas in a single transaction.
Call: req.token = USDC_ADDRESS, req.amount = 1000e6, msg.value = gasTopUp
(or gasTopUp + INBOUND_FEE if fee is enabled).
TX_TYPE: FUNDS (hasFunds=true, hasPayload=false, fundsIsNative=false).
Routing (Case 1.2, nativeValue > 0 post-fee):
_sendTxWithGas(GAS, gasTopUp)→ USD caps + block cap checked → forward gas ETH to TSS → emit firstUniversalTx(txType=GAS, amount=gasTopUp)._consumeRateLimit(USDC, 1000e6)— consume USDC epoch quota._handleDeposits(USDC, 1000e6)—safeTransferFrom(BOB, Vault, 1000e6).- Emit second
UniversalTx(txType=FUNDS, token=USDC, amount=1000e6).
Total events: 2 (gas leg + funds leg).
sequenceDiagram
autonumber
participant BOB as BOB (External Chain)
participant GW as UniversalGateway (External Chain)
participant V as Vault (External Chain)
participant USDC as USDC Token (External Chain)
participant TSS as TSS_ADDRESS (External Chain)
participant PC as Push Chain
BOB->>GW: sendUniversalTx{value: gasTopUp}(req{token=USDC, amount=1000e6})
GW->>GW: _fetchTxType → FUNDS
GW->>GW: _checkUSDCaps(gasTopUp), _checkBlockUSDCap(gasTopUp)
GW->>TSS: forward gasTopUp ETH (gas leg)
GW-->>PC: emit UniversalTx(txType=GAS, amount=gasTopUp) [gas leg]
GW->>GW: _consumeRateLimit(USDC, 1000e6)
GW->>USDC: safeTransferFrom(BOB, Vault, 1000e6)
USDC-->>V: +1000e6 USDC
GW-->>PC: emit UniversalTx(txType=FUNDS, token=USDC, amount=1000e6) [funds leg]
Note over PC: gasTopUp tops UEA gas; 1000 USDC minted as PRC20-USDC
Uses sendUniversalTx(UniversalTokenTxRequest). The gasToken is swapped to native ETH
(ethOut) via swapToNative. After protocol fee extraction, the remaining nativeValue
becomes a gas top-up (Section 4.3 pattern). The ERC20 token/amount fields identify the
funds being bridged.
Routing: identical to 4.3 — post-fee nativeValue is routed as gas via _sendTxWithGas,
ERC20 amount goes to Vault via _handleDeposits.
req.amount > 0, req.payload.length > 0. Epoch rate-limit consumed. Standard route.
Three sub-cases are determined inside _sendTxWithFunds (UniversalGateway.sol:459-525)
based on req.token and nativeValue.
Condition: req.token != address(0), nativeValue == 0 (no ETH sent).
Scenario: BOB bridges USDC and includes a Push Chain payload. BOB already has gas on Push Chain — no gas top-up needed.
Call: req.token=USDC, req.amount=500e6, req.payload=<calldata>, msg.value=0
(or INBOUND_FEE if fee is enabled).
TX_TYPE: FUNDS_AND_PAYLOAD Case 2.1 (hasPayload=true, hasFunds=true,
fundsIsNative=false, hasNativeValue=any).
Routing:
_consumeRateLimit(USDC, 500e6)._handleDeposits(USDC, 500e6)→safeTransferFrom(BOB, Vault, 500e6).- Emit one
UniversalTx(txType=FUNDS_AND_PAYLOAD, token=USDC, amount=500e6, payload=...).
sequenceDiagram
autonumber
participant BOB as BOB (External Chain)
participant GW as UniversalGateway (External Chain)
participant V as Vault (External Chain)
participant USDC as USDC Token (External Chain)
participant PC as Push Chain
BOB->>GW: sendUniversalTx(req{token=USDC, amount=500e6, payload=calldata})
GW->>GW: _fetchTxType → FUNDS_AND_PAYLOAD (Case 2.1)
GW->>GW: _consumeRateLimit(USDC, 500e6)
GW->>USDC: safeTransferFrom(BOB, Vault, 500e6)
USDC-->>V: +500e6 USDC
GW-->>PC: emit UniversalTx(txType=FUNDS_AND_PAYLOAD, token=USDC, amount=500e6)
Note over PC: Mints 500 PRC20-USDC to BOB's UEA + executes payload via UEA
Condition: req.token == address(0), nativeValue > 0.
Scenario: BOB sends 1.1 ETH — 1 ETH is bridged to Push Chain, 0.1 ETH tops up UEA gas. Both happen in a single transaction.
Call: req.token=address(0), req.amount=1 ether, msg.value=1.1 ether + INBOUND_FEE.
Invariant (UniversalGateway.sol:473): post-fee nativeValue >= req.amount. If
nativeValue < req.amount → revert InvalidAmount.
Routing:
gasAmount = nativeValue - req.amount= 0.1 ETH.- If
gasAmount > 0: call_sendTxWithGas(GAS, gasAmount)→ USD caps + block cap checked → forward gas ETH to TSS → emit firstUniversalTx(txType=GAS, amount=0.1ETH). _consumeRateLimit(address(0), 1 ETH)._handleDeposits(address(0), 1 ETH)→ forward 1 ETH to TSS.- Emit second
UniversalTx(txType=FUNDS_AND_PAYLOAD, amount=1ETH).
Total events: 2 (or 1 if gasAmount == 0 because msg.value == req.amount).
sequenceDiagram
autonumber
participant BOB as BOB (External Chain)
participant GW as UniversalGateway (External Chain)
participant TSS as TSS_ADDRESS (External Chain)
participant PC as Push Chain
BOB->>GW: sendUniversalTx{value: 1.1 ETH + INBOUND_FEE}(req{token=0x0, amount=1ETH, payload=calldata})
GW->>GW: _fetchTxType → FUNDS_AND_PAYLOAD (Case 2.2)
GW->>GW: _collectProtocolFee → forward INBOUND_FEE to TSS
GW->>GW: gasAmount = 1.1 - 1.0 = 0.1 ETH (post-fee nativeValue = 1.1)
GW->>GW: _checkUSDCaps(0.1 ETH), _checkBlockUSDCap(0.1 ETH)
GW->>TSS: forward 0.1 ETH (gas leg)
GW-->>PC: emit UniversalTx(txType=GAS, amount=0.1ETH) [gas leg]
GW->>GW: _consumeRateLimit(address(0), 1ETH)
GW->>TSS: forward 1 ETH (funds leg)
GW-->>PC: emit UniversalTx(txType=FUNDS_AND_PAYLOAD, amount=1ETH) [funds leg]
Note over PC: 0.1 ETH tops UEA gas; 1 ETH minted as PRC20-ETH + payload executed
Condition: req.token != address(0), nativeValue > 0.
Scenario: BOB sends 0.01 ETH for gas and bridges 500 USDC with a payload, all in one call.
Call: req.token=USDC, req.amount=500e6, req.payload=<calldata>,
msg.value=0.01 ether + INBOUND_FEE.
Routing:
gasAmount = nativeValue= 0.01 ETH (entire native value is the gas leg)._sendTxWithGas(GAS, 0.01 ETH)→ USD caps + block cap → forward to TSS → emit firstUniversalTx(txType=GAS, amount=0.01ETH)._consumeRateLimit(USDC, 500e6)._handleDeposits(USDC, 500e6)→safeTransferFrom(BOB, Vault, 500e6).- Emit second
UniversalTx(txType=FUNDS_AND_PAYLOAD, token=USDC, amount=500e6).
Total events: Always 2.
sequenceDiagram
autonumber
participant BOB as BOB (External Chain)
participant GW as UniversalGateway (External Chain)
participant V as Vault (External Chain)
participant USDC as USDC Token (External Chain)
participant TSS as TSS_ADDRESS (External Chain)
participant PC as Push Chain
BOB->>GW: sendUniversalTx{value: 0.01ETH + INBOUND_FEE}(req{token=USDC, amount=500e6, payload=calldata})
GW->>GW: _fetchTxType → FUNDS_AND_PAYLOAD (Case 2.3)
GW->>GW: _collectProtocolFee → forward INBOUND_FEE to TSS
GW->>GW: _checkUSDCaps(0.01ETH), _checkBlockUSDCap(0.01ETH)
GW->>TSS: forward 0.01 ETH (gas leg)
GW-->>PC: emit UniversalTx(txType=GAS, amount=0.01ETH) [gas leg]
GW->>GW: _consumeRateLimit(USDC, 500e6)
GW->>USDC: safeTransferFrom(BOB, Vault, 500e6)
USDC-->>V: +500e6 USDC
GW-->>PC: emit UniversalTx(txType=FUNDS_AND_PAYLOAD, token=USDC, amount=500e6) [funds leg]
Note over PC: 0.01 ETH tops UEA gas; 500 USDC minted as PRC20-USDC + payload executed
Any of the above sub-cases can use sendUniversalTx(UniversalTokenTxRequest). The gasToken
is swapped to native first, producing nativeValue = ethOut. TX_TYPE inference and routing
then proceeds identically to 5.1, 5.2, or 5.3 depending on req.token and ethOut.
A CEA calls back into the gateway to bridge funds or execute payloads toward Push Chain. This path is used when an outbound execution on the external chain needs to return funds or results to the user's UEA on Push Chain.
All four TX_TYPEs are supported. TX_TYPE inference is identical to the normal path. All events
emit fromCEA=true and recipient=mappedUEA.
Protocol fee is skipped for CEA calls (fromCEA=true). The fee is already paid on Push
Chain before the CEA executes on the external chain. CEAs send msg.value without adding
INBOUND_FEE on top.
1. CEA_FACTORY != address(0) — factory must be configured
2. ICEAFactory(CEA_FACTORY).isCEA(msg.sender) — caller must be a deployed CEA
3. mappedUEA = ICEAFactory.getUEAForCEA(msg.sender) — must not be address(0)
4. req.recipient == mappedUEA — anti-spoof: prevents crediting arbitrary UEAs
If any check fails → revert InvalidInput (checks 1-3) or InvalidRecipient (check 4).
In FUNDS_AND_PAYLOAD Cases 2.2 and 2.3, the gas leg is emitted as a separate UniversalTx
event with a recipient field.
- Normal path (
fromCEA=false):gasRecipient = address(0). Push Chain attributes gas tomsg.sender's UEA — which is the CEA's address, not BOB's UEA. This would cause Push Chain to deploy a new UEA for the CEA address rather than crediting BOB's existing UEA. - CEA path (
fromCEA=true):gasRecipient = req.recipient = mappedUEA. Push Chain routes the gas to BOB's correct UEA.
This logic is applied at UniversalGateway.sol:464 and UniversalGateway.sol:513.
Same as Section 2.1 but fromCEA=true, recipient=mappedUEA.
Same as Sections 3.1/3.2 but fromCEA=true, recipient=mappedUEA.
Scenario: CEA bridges USDC back to BOB's UEA on Push Chain.
sequenceDiagram
autonumber
participant CEA as CEA (External Chain)
participant GW as UniversalGateway (External Chain)
participant CF as CEAFactory (External Chain)
participant V as Vault (External Chain)
participant USDC as USDC Token (External Chain)
participant PC as Push Chain
CEA->>GW: sendUniversalTxFromCEA(req{recipient=BOB_UEA, token=USDC, amount=500e6})
Note over CEA: CEA must have approved gateway for 500e6 USDC
GW->>CF: isCEA(CEA) → true
GW->>CF: getUEAForCEA(CEA) → BOB_UEA
GW->>GW: req.recipient == BOB_UEA ✓ (anti-spoof)
GW->>GW: _fetchTxType → FUNDS
GW->>GW: _consumeRateLimit(USDC, 500e6)
GW->>USDC: safeTransferFrom(CEA, Vault, 500e6)
USDC-->>V: +500e6 USDC
GW-->>PC: emit UniversalTx(sender=CEA, recipient=BOB_UEA, token=USDC, amount=500e6, fromCEA=true)
Note over PC: Mints 500 PRC20-USDC to BOB_UEA
Same as Sections 5.1–5.3 with fromCEA=true propagated to both legs (gas leg and funds+payload
leg). Both emitted events carry recipient=mappedUEA and fromCEA=true.
Revert and rescue flows are documented in Revert_Handling.md.
Safety checks applied on every price read:
priceInUSD <= 0→ revertInvalidDataansweredInRound < roundId→ revertInvalidData(incomplete round)block.timestamp - updatedAt > chainlinkStalePeriod→ revertInvalidData(stale)- Price scaled from Chainlink decimals (typically 8) to 1e18
Output: price1e18 = USD(1e18) per 1 ETH. Example: ETH at $4,400 → returns 4_400 * 1e18.
If l2SequencerFeed is set, getEthUsdPrice also checks:
status == 1(DOWN) → revertInvalidDatablock.timestamp - sequencerUpdatedAt <= l2SequencerGracePeriodSec→ revertInvalidData
This gates all GAS/GAS_AND_PAYLOAD routes on sequencer liveness. Prevents USD cap exploitation during sequencer downtime recovery.
| Step | Action |
|---|---|
| 1 | If tokenIn == WETH: pull WETH, call WETH.withdraw, return ETH |
| 2 | Else: call _findV3PoolWithNative(tokenIn) — scans v3FeeOrder fee tiers |
| 3 | Pull tokenIn from caller, approve router, call exactInputSingle(tokenIn→WETH) |
| 4 | Reset router allowance to 0, call WETH.withdraw(wethOut) |
| 5 | Enforce ethOut >= amountOutMinETH (slippage bound) |
The receive() function (UniversalGateway.sol:980-983) only accepts ETH from WETH. All
other ETH transfers revert, preventing accidental deposits.
event UniversalTx(
address indexed sender, // caller on the external chain
address indexed recipient, // address(0) → Push Chain credits sender's UEA
// mappedUEA → explicit UEA routing (CEA path only)
address token, // address(0) for native; ERC20 address for token
uint256 amount, // wei for native; token units for ERC20
bytes payload, // Push Chain calldata (empty for funds-only)
address revertRecipient, // where funds go if Push Chain rejects the tx
TX_TYPE txType, // GAS | GAS_AND_PAYLOAD | FUNDS | FUNDS_AND_PAYLOAD
bytes signatureData, // optional signature for signed-verification flows
bool fromCEA // true only on sendUniversalTxFromCEA path
)event RevertUniversalTx(
bytes32 indexed subTxId,
bytes32 indexed universalTxId,
address indexed to, // revertRecipient
address token, // address(0) for native
uint256 amount,
RevertInstructions revertInstruction
)recipient == address(0) convention: When recipient is address(0) in UniversalTx,
Push Chain derives the target UEA from sender (i.e. BOB's UEA is looked up by his external
chain address). When recipient is non-zero (CEA path), Push Chain routes directly to that
address without derivation.
All msg.value amounts shown below are pre-fee (raw msg.value). When INBOUND_FEE > 0,
add INBOUND_FEE to each msg.value shown. CEA path (5.x) is exempt from the fee.
| # | TX_TYPE | Entry Point | req.token |
msg.value (pre-fee) |
Rate Limit | Events Emitted |
|---|---|---|---|---|---|---|
| 1.1 | GAS |
sendUniversalTx (native) |
address(0) |
> 0 | USD caps + block cap | 1× UniversalTx |
| 1.2 | GAS |
sendUniversalTx (token swap) |
any → native | 0 | USD caps + block cap on ethOut |
1× UniversalTx |
| 2.1 | GAS_AND_PAYLOAD |
sendUniversalTx (native) |
address(0) |
≥ 0 | USD caps if msg.value > 0 |
1× UniversalTx |
| 2.2 | GAS_AND_PAYLOAD |
sendUniversalTx (token swap) |
any → native | 0 | USD caps if ethOut > 0 |
1× UniversalTx |
| 3.1 | FUNDS |
sendUniversalTx (native) |
address(0) |
== amount | Epoch (native) | 1× UniversalTx |
| 3.2 | FUNDS |
sendUniversalTx (native) |
ERC20 | ≥ 0 | Epoch (token) + USD if gas > 0 | 1–2× UniversalTx |
| 4.1 | FUNDS_AND_PAYLOAD (2.1) |
sendUniversalTx |
ERC20 | 0 | Epoch (token) | 1× UniversalTx |
| 4.2 | FUNDS_AND_PAYLOAD (2.2) |
sendUniversalTx |
address(0) |
≥ amount | Epoch (native) + USD if gas > 0 | 1–2× UniversalTx |
| 4.3 | FUNDS_AND_PAYLOAD (2.3) |
sendUniversalTx |
ERC20 | > 0 | Epoch (token) + USD caps | 2× UniversalTx |
| 5.x | Any of above | sendUniversalTxFromCEA |
any | any (no fee) | Same as above | Same + fromCEA=true |
| 6.1 | — | revertUniversalTxToken (VAULT) |
ERC20 | 0 | None | 1× RevertUniversalTx |
| 6.2 | — | revertUniversalTx (TSS) |
address(0) |
== amount | None | 1× RevertUniversalTx |