Skip to content

Native token transfers incorrectly include ERC-20 call in multicall payload builder #246

Description

@omaidf

Security Disclosure: Native Token Transfers Incorrectly Include ERC-20 Call

Repository: pushchain/push-chain-sdk
Severity: Medium
Status: Unfixed as of 2026-03-28

Summary

In the multicall payload builder, the isNative flag is computed and logged correctly but is not used in the actual branching condition. When a native token (ETH/SOL bridged as native PC) is used in a non-array multicall, the code incorrectly encodes an ERC-20 transfer() call to the PRC-20 token address, which will either fail or produce unexpected behavior.

Affected Code

File: packages/core/src/lib/orchestrator/payload-builders.ts, lines 54-67

const isArrayMulticall = Array.isArray(execute.data);
const isNative = token.mechanism === 'native';
log('buildExecuteMulticall — Branch 2 (funds): ' + JSON.stringify({
  // ...
  willAddErc20Transfer: !isNative && !isArrayMulticall,  // ← Correct logic in log
  skippedReason: isNative ? 'native token — no PRC-20 transfer needed' : ...,
}, null, 2));
// Only add ERC-20 transfer for non-native tokens AND when NOT in array multicall mode
// - Native tokens (ETH/SOL) are bridged as native PC on Push Chain, not as PRC-20
// - When execute.data is an array (explicit multicall), user handles fund transfers
if (!isArrayMulticall) {  // ← BUG: should be `if (!isNative && !isArrayMulticall)`
  const erc20Transfer = encodeFunctionData({
    abi: ERC20_EVM,
    functionName: 'transfer',
    args: [execute.to, execute.funds?.amount],
  });

The comment on line 64 and the logging on line 61 both describe the correct condition (!isNative && !isArrayMulticall), but the actual if statement on line 67 only checks !isArrayMulticall.

Impact

When token.mechanism === 'native' and execute.data is not an array:

  • An ERC-20 transfer() call is encoded for a PRC-20 address
  • This address likely doesn't have a valid ERC-20 transfer implementation for native tokens
  • Result: transaction reverts (wasted gas) or, worse, if the PRC-20 address has a fallback that accepts the call, funds could be misrouted

Suggested Fix

--- a/packages/core/src/lib/orchestrator/payload-builders.ts
+++ b/packages/core/src/lib/orchestrator/payload-builders.ts
@@ -64,7 +64,7 @@
     // Only add ERC-20 transfer for non-native tokens AND when NOT in array multicall mode
     // - Native tokens (ETH/SOL) are bridged as native PC on Push Chain, not as PRC-20
     // - When execute.data is an array (explicit multicall), user handles fund transfers in their calls
-    if (!isArrayMulticall) {
+    if (!isNative && !isArrayMulticall) {
       const erc20Transfer = encodeFunctionData({

One character change — adds the !isNative && check that the comment and logging already describe.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions