canopy-network/canopy · error · ErrInvalidERC20Tx
ErrInvalidERC20Tx
ErrInvalidERC20Tx
Error message
data too short
What it means
When the RLP tx targets one of the pseudo-contract addresses, its calldata must at least contain the 4-byte ABI selector. rlpToMessage returns ErrInvalidERC20Tx('data too short') when the data is shorter than 4 bytes.
Source
Thrown at fsm/ethereum.go:178
return tx.GasPrice(), nil
}
}
// rlpToMessage() converts an ethereum RLP transaction to a message
func rlpToMessage(publicKey crypto.PublicKeyI, transaction *lib.Transaction, tx ethTypes.Transaction) (msg lib.MessageI, e lib.ErrorI) {
// get the relevant tx fields
to, from, data := tx.To(), publicKey.Address().Bytes(), tx.Data()
// ensure non-nil to
if to == nil {
return nil, ErrRecipientAddressEmpty()
}
// switch on the 'recipient'
switch tx.To().Hex() {
// if the recipient is a pseudo-contract call
case CNPYContractAddress, StakedCNPYContractAddress, SwapCNPYContractAddress:
// ensure enough data for a selector
if len(data) < 4 {
return nil, ErrInvalidERC20Tx(fmt.Errorf("data too short"))
}
// switch on the selector
switch selector := lib.BytesToString(data[:4]); selector {
case SendSelector:
msg, e = ethDataToMsgSend(from, data)
case StakeSelector:
m := new(MessageStake)
msg, e = ethDataToMsg(MessageStakeName, transaction, m, data, func() {
// allow the omission of the public key because it may be difficult to get the public key from the wallet
if len(m.PublicKey) == 0 {
m.PublicKey = publicKey.Bytes()
}
})
case EditStakeSelector:
m := new(MessageEditStake)
msg, e = ethDataToMsg(MessageEditStakeName, transaction, m, data, nil)
case UnstakeSelector:
m := new(MessageUnstake)View on GitHub (pinned to ee8197d91d)
Solutions
- ABI-encode the intended call (selector + parameters) into the tx data field before signing
- Do not send bare value transfers to the pseudo-contract addresses; use the proper transfer/selector call
- Fix the bridging tool so it forwards the full calldata
Example fix
// before
data := []byte{} // bare send to pseudo-contract
// after
selector := crypto.Keccak256("transfer(address,uint256)")[:4]
data := append(selector, abiEncodePacked(recipient, amount)...) Defensive patterns
Strategy: validation
Validate before calling
if tx.To() != nil && isPseudoContract(tx.To()) && len(tx.Data()) < 4 {
return fmt.Errorf("pseudo-contract calls need >= 4 byte selector; got %d", len(tx.Data()))
} Type guard
func hasSelector(data []byte) bool { return len(data) >= 4 } Try / catch
msg, err := fsm.RLPToCanopyTransaction(bz)
if err != nil && strings.Contains(err.Error(), "data too short") {
return nil, fmt.Errorf("ABI-encode the pseudo-contract call before signing")
} Prevention
- Never send bare value to pseudo-contract addresses
- Always ABI-encode selector + args
- Test payloads with an ABI encoder before signing
When it happens
Trigger: Sending an RLP transaction to CNPYContractAddress, StakedCNPYContractAddress, or SwapCNPYContractAddress with empty or <4-byte tx.Data() — e.g. a plain value transfer addressed to the pseudo-contract.
Common situations: A wallet/user sending ETH-style value directly to the CNG contract address instead of calling transfer() with ABI-encoded calldata, or a bridge truncating the calldata.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Item too long: {len(item)} bytes (max 255)
- Invalid uint64 value: {value}
- Invalid chain_id: {self.chain_id}
- account-auth multisig requires threshold > 0
- invalid public key
AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06).
Data as JSON: /api/errors/46aff50477cd0402.
Report an issue: GitHub.