Ethereum Abi


ABI stands for application binary interface, it is the standard way to interact with contracts in the Ethereum ecosystem, both from outside the blockchain and for contract-to-contract interaction.

Data is encoded or decoded according to its type.


Abi Encoder

<?php 
use kornrunner\Keccak;

include_once "../libraries/vendor/autoload.php";

class BtcschoolsABI {
	private $abi;
	
	public function __construct(array $abi)
    {
		$this->abi = $abi;
	}
	
	public function hexDec(string $hex): string
    {
        $dec = 0;
        $len = strlen($hex);
        for ($i = 1; $i <= $len; $i++) {
            $dec = bcadd($dec, bcmul(strval(hexdec($hex[$i - 1])), bcpow('16', strval($len - $i))));
        }
        return $dec;
    }

    public function decHex($dec): string
    {
        $last = bcmod($dec, 16);
        $remain = bcdiv(bcsub($dec, $last), 16);

        if ($remain == 0) {
            return dechex($last);
        } else {
            return self::DecHex($remain) . dechex($last);
        }
    }
	
	public function str2Hex(string $str): string
    {
        $hex = "";
        for ($i = 0; $i < strlen($str); $i++) {
            $hex .= dechex(ord($str[$i]));
        }
		
        return $hex;
    }

    public function hex2Str(string $hex): string
    {
        $str = "";
        for ($i = 0; $i < strlen($hex) - 1; $i += 2) {
            $str .= chr(hexdec($hex[$i] . $hex[$i + 1]));
        }

        return $str;
    }
	
	public function encodeSimpleType(string $type, $value) {
		
		$simpleType = preg_replace('/[^a-z]/', '', $type);
		
		if (in_array($simpleType, ["address", "uint", "int", "bool"])) {
			switch ($simpleType) {
				case "address":
					if (substr($value, 0, 2) === "0x") {
						$value = substr($value, 2);
					}
				break;
				
				case "uint":
				case "int":
					$value = $this->decHex($value);
				break;
				
				case "bool":
					$value = $value === true ? 1 : 0;
				break;
			}
			
			return substr(str_pad(strval($value), 64, "0", STR_PAD_LEFT), 0, 64);
			
		} else if (in_array($simpleType, ["bytes"])) {
			
			if (substr($value, 0, 2) === "0x") {
				$value = substr($value, 2);
			}
			
			switch($simpleType) {
				case "bytes";
					return substr(str_pad(strval($value), 64, "0", STR_PAD_RIGHT), 0, 64);
				break;
			}
		}
		
		throw new Exception(sprintf('Cannot encode value of type "%s"', $type));
	}
	
	public function encodeCall(string $name, ?array $args): string
    {
		
		$blocks = $this->abi;
		$encoded = "";
			
		foreach($blocks as $block) {
			if ($block['type'] == 'function' and $block['name'] == $name) {
				$dataParts = [];
				$methodParamsTypes = [];
	
				if(count($block['inputs']) > 0) {
					
					foreach($block['inputs'] as $inputIdx=>$input) {
						$thisEncoded = "";
						$methodParamsTypes[] = $input['type'];
						$arg = $args[$inputIdx];
						
						if (preg_match('/^((uint|int){1}(8|16|32|64|128|256)?|bool|address|(bytes[0-9]{1,2}))$/', $input['type'])) {
							
							$encoded .= $this->encodeSimpleType($input['type'], $arg);
						//specially handle string array type
						} else if ($input['type'] == "string") {
							
							$thisEncoded = $this->str2Hex($arg);
							$thisEncoded = $this->encodeSimpleType("uint", strlen($thisEncoded)/2).$thisEncoded;
							
							$lengthUpTo = ceil(strlen($thisEncoded) / 64) * 64;
							
							$dataParts[] = substr(str_pad(strval($thisEncoded), $lengthUpTo, "0", STR_PAD_RIGHT), 0, $lengthUpTo);
							
							$idx = count($dataParts);

							$encoded .= "[" . str_pad($idx, 62, "0", STR_PAD_LEFT) . "]";
						//specially handle byte array type
						} else if ($input['type'] == "bytes") {
							
							$thisEncoded = $arg;
							if (substr($thisEncoded, 0, 2) === "0x") {
								$thisEncoded = substr($thisEncoded, 2);
							}
							
							$thisEncoded = $this->encodeSimpleType("uint", strlen($thisEncoded)/2).$thisEncoded;
							
							$lengthUpTo = ceil(strlen($thisEncoded) / 64) * 64;
							
							$dataParts[] = substr(str_pad(strval($thisEncoded), $lengthUpTo, "0", STR_PAD_RIGHT), 0, $lengthUpTo);
							
							$idx = count($dataParts);

							$encoded .= "[" . str_pad($idx, 62, "0", STR_PAD_LEFT) . "]";
						//specially handle other array type
						} else if (preg_match('/^((uint|int){1}(8|16|32|64|128|256)?|bool|address|bytes[0-9]{1,2})\[[0-9]*\]$/', $input['type'])) {
							$items = json_decode($arg,true);
							
							if (!in_array(substr($arg, 0, 1), array('[')) OR !in_array(substr($arg, -1), array("]"))) {
								throw new Exception(
									sprintf('Invalid array-json input for param "%s" with type "%s" (1)', $block['name'], $input['type'])
								); 
							}
								
							if (json_last_error() != JSON_ERROR_NONE) {
								throw new Exception(
									sprintf('Invalid array-json input for param "%s" with type "%s" (2)', $block['name'], $input['type'])
								); 
							}
							
							$thisEncoded .= $this->encodeSimpleType("uint", count($items));
							
							foreach($items as $itemIdx=>$item) {
								$thisEncoded .= $this->encodeSimpleType($input['type'], $item);
							}
							
							$dataParts[] = $thisEncoded;
							
							$idx = count($dataParts);

							$encoded .= "[" . str_pad($idx, 62, "0", STR_PAD_LEFT) . "]";
						} else {
							throw new Exception(sprintf('Cannot encode value of type "%s"', $type));
						}
					}
				}
				
				foreach($dataParts as $k=>$dataPart) {
					$find = "[" . str_pad($k + 1, 62, "0", STR_PAD_LEFT) . "]";
					$replaceWith = $this->encodeSimpleType("uint", strlen($encoded) / 2);
					
					$encoded = str_replace($find, $replaceWith, $encoded);
					$encoded .= $dataPart;
				}

				$encodedMethodCall = Keccak::hash($plain = sprintf('%s(%s)', $name, implode(",", $methodParamsTypes)), 256);
				
				return '0x' . substr($encodedMethodCall, 0, 8) . $encoded;
				
			}
		}
		return "";
    }
}

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
	$ajaxData = [];
	if ($_GET['action'] == 'parse_abi' OR ($_GET['action'] == 'submit' AND isset($_POST['abi']))) {
		$ajaxData['functions'] = [];
		$blocks = json_decode($_POST['abi'], true);
		if (@count($blocks) > 0) {
			foreach($blocks as $block) {
				if ($block['type'] == 'function') {
					$ajaxData['functions'][] = $block['name'];
				}
			}
		} else {
			$errmsg .= "Invalid ABI.";
		}
		
		if ($_GET['ajax'] == '1') {
			if ($errmsg) {
				$ajaxData['error'] = $errmsg;
			} 
			
			die(json_encode($ajaxData));
		}
	} 
	
	if ($_GET['action'] == 'get_args' OR ($_GET['action'] == 'submit' AND isset($_POST['abi']) AND isset($_POST['function'])) ) {
		$ajaxData['args'] = [];
		$blocks = json_decode($_POST['abi'], true);
		
		if (@count($blocks) > 0) {
			foreach($blocks as $block) {
				
				if ($block['type'] == 'function' and $block['name'] == $_POST['function']) {					
					if ($block['inputs']) {
						foreach($block['inputs'] as $input) {
							$ajaxData['args'][] = $input['name'] .  " ({$input['type']})";
						}
					}
				}
			}
		}
		
		if ($_GET['ajax'] == '1') {
			if ($errmsg) {
				$ajaxData['error'] = $errmsg;
			} 
			
			die(json_encode($ajaxData));
		}
	}
}

include_once("html_iframe_header.php");
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
	try {
		
		if (!$_POST['function']) {
			throw new Exception("Function should not be blank.");
		}
		
		$_POST['args'] = is_array($_POST['args']) ? $_POST['args'] : [];
		
		$decoded = json_decode($_POST['abi'], true);
		$abi = new BtcschoolsABI($decoded);
		$data = $abi->encodeCall($_POST['function'], $_POST['args']);
		
		
	?>
	   <div class="alert alert-success">
			<h6 class="mt-3">Data (Hex)</h6>
			<textarea class="form-control" rows="5" id="comment" readonly><?php echo $data;?></textarea>
			
		</div>
<?php 
	} catch (Exception $e) {
		$errmsg .= "Problem found. " . $e->getMessage();
	}
} 

if ($errmsg) {
?>
    <div class="alert alert-danger">
        <strong>Error!</strong> <?php echo $errmsg?>
    </div>
<?php
}

?>

<form id='this_form' action='?action=submit' method='post'>

	<div class="form-group">
        <label for="function">Function:</label>
        <select class="form-control" type='text' name='function' id='function' value='<?php echo $_POST['function']?>' onchange="
				var self = $(this);
				var form = self.closest('form');
				$('p#args_panel',form).empty();
				$('p#args_panel',form).html('');
				$.ajax({
					url: '?ajax=1&action=get_args', 
					type: 'post',
					data: $('#this_form :input'),
					success:function(result){
						
						try {
							j = eval('(' + result + ')');
							
							if ('error' in j && j.error.length>0) {
								var error = true;
							} else {
								var error = false;
							}
							
							if (!error) {
								
								var args = j.args;
								
								if (args.length > 0) {
									
									var x;    
									for (x in args) {
										$('p#args_panel',form).append('<div class=\'form-group\'>'+ args[x] +':<input class=\'form-control\' type=\'text\' name=\'args[]\'/></div>');
										
									}
									
								} else {
									$('p#args_panel',form).html('&nbsp;&nbsp;&nbsp;&nbsp;No arguments');
								}
							} else {
								alert(j.error);
							}
						} catch(e) {
							alert('Invalid Json Format.');
						}
					},
					complete:function() {
					}
				});
				">
			
			
			<?php
			
			if (@count($ajaxData['functions']) > 0) {
			?>
				<option value=''>Please select ...</option>
				<?php
				foreach($ajaxData['functions'] as $function_name) {
					
					$optionSelected = $function_name == $_POST['function'] ? " selected='selected'" : "";
				?>
					<option value='<?php echo $function_name?>'<?php echo $optionSelected?>><?php echo $function_name?></option>
				<?php
				}
			} else {
			?>
				<option value="">Please load from ABI ...</option>
			<?php
			}
			?>
		</select>
    </div>
	
	<div class="form-group">
        <label for="args">Function Arguments:</label>
		<p id='args_panel'>
			
		</p>
    </div>
	
	<div class="form-group">
		<label for="gas_price">ABI:</label>
		
		<div class="input-group mb-3">
			<textarea class="form-control" rows="10" name='abi' id='abi'><?php echo $_POST['abi']?></textarea>
			<div class="input-group-append">
			 <input class="btn btn-success" type="button" value="Load" onclick="
				var self = $(this);
				self.val('...'); 
				
				var form = self.closest('form');
				
				$('select#function',form).empty();
				
				$.ajax({
					url: '?ajax=1&action=parse_abi', 
					type: 'post',
					data: $('#this_form :input'),
					success:function(result){
						
						try {
							j = eval('(' + result + ')');
							
							if ('error' in j && j.error.length>0) {
								var error = true;
							} else {
								var error = false;
							}
							
							if (!error) {
								
								var functions = j.functions;
								
								if (functions.length > 0) {
									
									
									$('select#function',form).append('<option value=\'\' selected=\'selected\'>Please select ...</option>');
									
									
									var x;    
									for (x in functions) {
										$('select#function',form).append('<option value=\''+functions[x]+'\'>'+functions[x]+'</option>');
									}
									
									
								}
							} else {
								
								$('select#function',form).prepend('<option value=\'\' selected=\'selected\'>Please load from ABI ...</option>');
								alert(j.error);
							}
						} catch(e) {
							
							$('select#function',form).prepend('<option value=\'\' selected=\'selected\'>Please load from ABI ...</option>');
							alert('Invalid Json Format.');
						}
					},
					complete:function() {
						self.val('Load');
					}
				});
				"/>
			</div>
		</div>
	</div>
	
    <input type='submit' class="btn btn-success btn-block"/>
</form>
<script>
<?php

if ($_GET['action'] == 'submit') {
	if (@count($ajaxData['args']) > 0) {
		foreach($ajaxData['args'] as $k=>$arg) {
		?>
			$('p#args_panel').append('<div class=\'form-group\'><?php echo $arg?>:<input class=\'form-control\' type=\'text\' name=\'args[]\' value=\'<?php echo $_POST['args'][$k]?>\'/></div>');
		<?php
		}
	} else {
	?>
		$('p#args_panel').html('&nbsp;&nbsp;&nbsp;&nbsp;No arguments');
	<?php
	}
} 
?>
</script>
<?php
include_once("html_iframe_footer.php");

Abi Decoder

<?php 
use Web3\Contracts\Ethabi;
use Web3\Contracts\Types\{Address, Boolean, Bytes, DynamicBytes, Integer, Str, Uinteger};

include_once "../libraries/vendor/autoload.php";

$ethAbi = new Ethabi(['address' => new Address,'bool' => new Boolean,'bytes' => new Bytes,'dynamicBytes' => new DynamicBytes,'int' => new Integer,'string' => new Str,'uint' => new Uinteger,]);

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
	$ajaxData = [];
	if ($_GET['action'] == 'parse_abi' OR ($_GET['action'] == 'submit' AND isset($_POST['abi']))) {
		$ajaxData['functions'] = [];
		$abiBlocks = json_decode($_POST['abi'], true);
		if (@count($abiBlocks) > 0) {
			foreach($abiBlocks as $abiBlock) {
				if ($abiBlock['type'] == 'function') {
					if (count($abiBlock['outputs']) > 0) {
						$ajaxData['functions'][] = $abiBlock['name'];
					}
				}
			}
		} else {
			$errmsg .= "Invalid ABI.";
		}
		
		if ($_GET['ajax'] == '1') {
			if ($errmsg) {
				$ajaxData['error'] = $errmsg;
			} 
			
			die(json_encode($ajaxData));
		}
	} 
}

include_once("html_iframe_header.php");
$results = [];
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
	try {
		
		if (!$_POST['function'] OR !$_POST['abi']) {
			throw new Exception("Abi with selected function is needed.");
		}

		$abiBlocks = json_decode($_POST['abi'], true);
		
		if (@count($abiBlocks) > 0) {
			foreach($abiBlocks as $abiBlock) {
				
				if ($abiBlock['type'] == 'function' and $abiBlock['name'] == $_POST['function']) {		
					$totalOutputs = count($abiBlock['outputs']);
					
					if ($totalOutputs) {
						$encoded = $_POST['response'];
						
						// Remove 0x prefix
						if (substr($encoded, 0, 2) === '0x') {
							$encoded = substr($encoded, 2);
						}
						
						if (strlen($encoded) != 64 * $totalOutputs) {
							throw new Exception("Response length is not valid.");
						}
						
						$decodeds = str_split($encoded, 64);
						foreach($decodeds as $k=> $decoded) {
							
							$result['name']  = $abiBlock['outputs'][$k]['name'];
							$result['type']  = $abiBlock['outputs'][$k]['type'];
							$result['hex']   = "0x". $decoded;
							$result['value'] = $ethAbi->decodeParameter($abiBlock['outputs'][$k]['type'], $decoded);
							
							$results[] = $result;
						}
					}
				}
			}
		}
	?>
    <div class="alert alert-success">
		<?php echo $totalOutputs?> outputs have been decoded.
	</div>
	<div class="table-responsive">
		<table class="table table-bordered">
			<tr><th>Name</th><th>Type</th><th>Hex</th><th>Decoded Value</th></tr>
			<?php
			foreach($results as $result) {
			?>
				<tr>
					<td><?Php echo $result['name']?></td>
					<td><?Php echo $result['type']?></td>
					<td><?Php echo $result['hex']?></td>
					<td><?Php echo $result['value']?></td>
				</tr>
			<?Php
			}
			?>
		</table>
	</div>
<?php 
	} catch (Exception $e) {
		$errmsg .= "Problem found. " . $e->getMessage();
	}
} 

if ($errmsg) {
?>
    <div class="alert alert-danger">
        <strong>Error!</strong> <?php echo $errmsg?>
    </div>
<?php
}
?>
<form id='this_form' action='?action=submit' method='post'>
	
	<div class="form-group">
        <label for="response">Response To Decode:</label>
        <input class="form-control" type='text' name='response' id='response' value='<?php echo $_POST['response']?>'>
		<small>You may get this response from <a href="eth_call.php" target='_blank'>Ethereum Eth Call</a> to decode.</small>
    </div>
	
	<div class="form-group">
        <label for="function">Function:</label>
        <select class="form-control" type='text' name='function' id='function' value='<?php echo $_POST['function']?>'>
			<?php
			
			if (@count($ajaxData['functions']) > 0) {
			?>
				<option value=''>Please select ...</option>
				<?php
				foreach($ajaxData['functions'] as $function_name) {
					
					$optionSelected = $function_name == $_POST['function'] ? " selected='selected'" : "";
				?>
					<option value='<?php echo $function_name?>'<?php echo $optionSelected?>><?php echo $function_name?></option>
				<?php
				}
			} else {
			?>
				<option value="">Please load from ABI ...</option>
			<?php
			}
			?>
		</select>
    </div>
	
	<div class="form-group">
		<label for="gas_price">ABI:</label>
		
		<div class="input-group mb-3">
			<textarea class="form-control" rows="10" name='abi' id='abi'><?php echo $_POST['abi']?></textarea>
			<div class="input-group-append">
			 <input class="btn btn-success" type="button" value="Load" onclick="
				var self = $(this);
				self.val('...'); 
				
				var form = self.closest('form');
				
				$('select#function',form).empty();
				
				$.ajax({
					url: '?ajax=1&action=parse_abi', 
					type: 'post',
					data: $('#this_form :input'),
					success:function(result){
						
						try {
							j = eval('(' + result + ')');
							
							if ('error' in j && j.error.length>0) {
								var error = true;
							} else {
								var error = false;
							}
							
							if (!error) {
								
								var functions = j.functions;
								
								if (functions.length > 0) {
									$('select#function',form).append('<option value=\'\' selected=\'selected\'>Please select ...</option>');
									
									var x;    
									for (x in functions) {
										$('select#function',form).append('<option value=\''+functions[x]+'\'>'+functions[x]+'</option>');
									}
									
									
								}
							} else {
								
								$('select#function',form).prepend('<option value=\'\' selected=\'selected\'>Please load from ABI ...</option>');
								alert(j.error);
							}
						} catch(e) {
							
							$('select#function',form).prepend('<option value=\'\' selected=\'selected\'>Please load from ABI ...</option>');
							alert('Invalid Json Format.');
						}
					},
					complete:function() {
						self.val('Load');
					}
				});
				"/>
			</div>
		</div>
	</div>
	
    <input type='submit' class="btn btn-success btn-block"/>
</form>

<?php
include_once("html_iframe_footer.php");








Tutorials
About Us
Contents have been open source in GITHUB. Please give me a ⭐ if you found this helpful :)
Community
Problem? Raise me a new issue.
Support Us
Buy me a coffee. so i can spend more nights for this :)

BTCSCHOOLS would like to present you with more pratical but little theory throughout our tutorials. Pages' content are constantly keep reviewed to avoid mistakes, but we cannot warrant correctness of all contents. While using this site, you agree to accept our terms of use, cookie & privacy policy. Copyright 2019 by BTCSCHOOLS. All Rights Reserved.