Describe the bug
The data decoder returns incorrect parameter names for proxy contracts whose implementation is verified on Etherscan.
When decoding mint(address,uint256,uint256) on a TransparentUpgradeableProxy whose implementation (ComplianceToken) is verified and proxy-linked on Etherscan, the decoder falls back to the 4-byte signature database instead of using the implementation contract's ABI.
Root cause: Etherscan's getabi API for a proxy address returns the proxy contract's own ABI (TransparentUpgradeableProxy — only constructor/events/errors, no application functions), not the implementation's ABI. The decoder does not follow up by checking getsourcecode for Proxy: 1 + Implementation and fetching the implementation's ABI.
To Reproduce
1. Call the safe-decoder-service:
curl -X POST "https://safe-decoder.safe.global/api/v1/data-decoder" \
-H "Content-Type: application/json" \
-d '{
"data": "0x156e29f60000000000000000000000009ace6120ded418589593c5d953e8f6778ed94b6c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
"to": "0x9ace6120DED418589593C5D953e8F6778Ed94b6c",
"chain_id": 11155111
}'
Response (incorrect):
{
"method": "mint",
"parameters": [
{ "name": "_to", "type": "address", "value": "0x9ace6120DED418589593C5D953e8F6778Ed94b6c", "valueDecoded": null },
{ "name": "_tokenId", "type": "uint256", "value": "1", "valueDecoded": null },
{ "name": "_amount", "type": "uint256", "value": "1", "valueDecoded": null }
],
"accuracy": "ONLY_FUNCTION_MATCH"
}
2. Call the safe-transaction-service:
curl -X POST "https://api.safe.global/tx-service/sep/api/v1/data-decoder/" \
-H "Content-Type: application/json" \
-d '{
"data": "0x156e29f60000000000000000000000009ace6120ded418589593c5d953e8f6778ed94b6c00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
"to": "0x9ace6120DED418589593C5D953e8F6778Ed94b6c"
}'
Response (also incorrect, different fallback match):
{
"method": "mint",
"parameters": [
{ "name": "to", "type": "address", "value": "0x9ace6120DED418589593C5D953e8F6778Ed94b6c" },
{ "name": "payAmount", "type": "uint256", "value": "1" },
{ "name": "minReceive", "type": "uint256", "value": "1" }
]
}
3. Verify that Etherscan has the correct proxy relationship:
curl "https://api.etherscan.io/v2/api?chainid=11155111&module=contract&action=getsourcecode&address=0x9ace6120DED418589593C5D953e8F6778Ed94b6c&apikey=YOUR_KEY"
Returns:
ContractName: TransparentUpgradeableProxy
Proxy: 1
Implementation: 0xdc220588726d033e9a662ec7c29641f725c67254
4. Verify that the implementation's ABI on Etherscan has the correct mint function:
curl "https://api.etherscan.io/v2/api?chainid=11155111&module=contract&action=getabi&address=0xDc220588726d033E9a662ec7c29641F725c67254&apikey=YOUR_KEY"
Returns ComplianceToken ABI which includes:
{
"name": "mint",
"inputs": [
{ "name": "depositAddr", "type": "address" },
{ "name": "amount", "type": "uint256" },
{ "name": "validUntil", "type": "uint256" }
]
}
Expected behavior
The decoder should return the implementation contract's parameter names:
{
"method": "mint",
"parameters": [
{ "name": "depositAddr", "type": "address", "value": "0x9ace6120DED418589593C5D953e8F6778Ed94b6c" },
{ "name": "amount", "type": "uint256", "value": "1" },
{ "name": "validUntil", "type": "uint256", "value": "1" }
],
"accuracy": "FULL_MATCH"
}
The expected flow:
- Fetch ABI for proxy address via
getabi → gets TransparentUpgradeableProxy ABI (no application functions)
- Detect that this is a proxy via
getsourcecode → Proxy: 1, Implementation: 0xdc22...7254
- Fetch the implementation's ABI via
getabi for 0xdc22...7254 → gets ComplianceToken ABI with the correct mint function
- Use the implementation ABI to decode the transaction
Environment
Additional context
Why 4-byte fallback gives wrong results
The function selector 0x156e29f6 = mint(address,uint256,uint256) exists in multiple contracts with different parameter names:
| Source |
Parameter names |
| ComplianceToken (correct) |
depositAddr, amount, validUntil |
| ERC1155 (4-byte DB match) |
_to, _tokenId, _amount |
| Other (4-byte DB match) |
to, payAmount, minReceive |
Because the decoder doesn't resolve the proxy → implementation ABI chain, it falls back to the 4-byte signature database and picks a wrong match.
Etherscan getabi vs getsourcecode behavior for proxies
| API |
Returns for proxy address |
getabi |
TransparentUpgradeableProxy ABI only (constructor, events, errors — no application functions) |
getsourcecode |
Proxy: 1, Implementation: 0xdc22...7254 — contains the link to the implementation |
The decoder currently appears to only use getabi, which misses the implementation ABI entirely.
Impact
This affects all TransparentUpgradeableProxy contracts on Safe Wallet. Users see wrong parameter names in the transaction confirmation screen, which is a security/UX concern — users cannot verify they are signing the correct parameters.
Suggested fix
When fetching contract metadata, if getsourcecode returns Proxy: 1 with a non-empty Implementation address:
- Fetch the implementation's ABI via
getabi(implementation_address)
- Use the implementation ABI (or merge it with the proxy ABI) for decoding transactions targeting the proxy address
Describe the bug
The data decoder returns incorrect parameter names for proxy contracts whose implementation is verified on Etherscan.
When decoding
mint(address,uint256,uint256)on aTransparentUpgradeableProxywhose implementation (ComplianceToken) is verified and proxy-linked on Etherscan, the decoder falls back to the 4-byte signature database instead of using the implementation contract's ABI.Root cause: Etherscan's
getabiAPI for a proxy address returns the proxy contract's own ABI (TransparentUpgradeableProxy — only constructor/events/errors, no application functions), not the implementation's ABI. The decoder does not follow up by checkinggetsourcecodeforProxy: 1+Implementationand fetching the implementation's ABI.To Reproduce
1. Call the safe-decoder-service:
Response (incorrect):
{ "method": "mint", "parameters": [ { "name": "_to", "type": "address", "value": "0x9ace6120DED418589593C5D953e8F6778Ed94b6c", "valueDecoded": null }, { "name": "_tokenId", "type": "uint256", "value": "1", "valueDecoded": null }, { "name": "_amount", "type": "uint256", "value": "1", "valueDecoded": null } ], "accuracy": "ONLY_FUNCTION_MATCH" }2. Call the safe-transaction-service:
Response (also incorrect, different fallback match):
{ "method": "mint", "parameters": [ { "name": "to", "type": "address", "value": "0x9ace6120DED418589593C5D953e8F6778Ed94b6c" }, { "name": "payAmount", "type": "uint256", "value": "1" }, { "name": "minReceive", "type": "uint256", "value": "1" } ] }3. Verify that Etherscan has the correct proxy relationship:
curl "https://api.etherscan.io/v2/api?chainid=11155111&module=contract&action=getsourcecode&address=0x9ace6120DED418589593C5D953e8F6778Ed94b6c&apikey=YOUR_KEY"Returns:
ContractName: TransparentUpgradeableProxyProxy: 1Implementation: 0xdc220588726d033e9a662ec7c29641f725c672544. Verify that the implementation's ABI on Etherscan has the correct
mintfunction:curl "https://api.etherscan.io/v2/api?chainid=11155111&module=contract&action=getabi&address=0xDc220588726d033E9a662ec7c29641F725c67254&apikey=YOUR_KEY"Returns
ComplianceTokenABI which includes:{ "name": "mint", "inputs": [ { "name": "depositAddr", "type": "address" }, { "name": "amount", "type": "uint256" }, { "name": "validUntil", "type": "uint256" } ] }Expected behavior
The decoder should return the implementation contract's parameter names:
{ "method": "mint", "parameters": [ { "name": "depositAddr", "type": "address", "value": "0x9ace6120DED418589593C5D953e8F6778Ed94b6c" }, { "name": "amount", "type": "uint256", "value": "1" }, { "name": "validUntil", "type": "uint256", "value": "1" } ], "accuracy": "FULL_MATCH" }The expected flow:
getabi→ gets TransparentUpgradeableProxy ABI (no application functions)getsourcecode→Proxy: 1,Implementation: 0xdc22...7254getabifor0xdc22...7254→ gets ComplianceToken ABI with the correctmintfunctionEnvironment
safe-decoder.safe.globalandapi.safe.global/tx-service/sep/0x9ace6120DED418589593C5D953e8F6778Ed94b6c0xDc220588726d033E9a662ec7c29641F725c67254(ComplianceToken, verified)Proxy: 1, Implementation correctly linked)Additional context
Why 4-byte fallback gives wrong results
The function selector
0x156e29f6=mint(address,uint256,uint256)exists in multiple contracts with different parameter names:depositAddr,amount,validUntil_to,_tokenId,_amountto,payAmount,minReceiveBecause the decoder doesn't resolve the proxy → implementation ABI chain, it falls back to the 4-byte signature database and picks a wrong match.
Etherscan
getabivsgetsourcecodebehavior for proxiesgetabigetsourcecodeProxy: 1,Implementation: 0xdc22...7254— contains the link to the implementationThe decoder currently appears to only use
getabi, which misses the implementation ABI entirely.Impact
This affects all TransparentUpgradeableProxy contracts on Safe Wallet. Users see wrong parameter names in the transaction confirmation screen, which is a security/UX concern — users cannot verify they are signing the correct parameters.
Suggested fix
When fetching contract metadata, if
getsourcecodereturnsProxy: 1with a non-emptyImplementationaddress:getabi(implementation_address)