This guide shows you how to add and use NeoSerializerLib in your existing Solidity contracts.
npm install @axlabs/neo-serializerCopy these files to your project:
contracts/libraries/NeoSerializerLib.solcontracts/libraries/VarInt.solcontracts/libraries/NeoTypes.sol
Make sure to preserve the directory structure (contracts/libraries/).
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "path/to/libraries/NeoSerializerLib.sol";
contract MyContract {
// Your contract code
}You can use the library in two ways:
contract MyContract {
function serializeData(uint256 value) public pure returns (bytes memory) {
return NeoSerializerLib.serialize(value);
}
function serializeString(string memory text) public pure returns (bytes memory) {
return NeoSerializerLib.serialize(text);
}
}contract MyContract {
using NeoSerializerLib for uint256;
using NeoSerializerLib for bytes;
using NeoSerializerLib for string;
function serializeData(uint256 value) public pure returns (bytes memory) {
return value.serialize(); // Cleaner syntax!
}
function serializeString(string memory text) public pure returns (bytes memory) {
return text.serialize();
}
}import "path/to/libraries/NeoSerializerLib.sol";
contract MyContract {
using NeoSerializerLib for uint256;
using NeoSerializerLib for bytes;
using NeoSerializerLib for string;
// Both bytes20 and address are supported for the target
function createNeoCall(
address target, // Can also use bytes20
string memory method,
uint256[] memory args
) public pure returns (bytes memory) {
// Serialize each argument
bytes[] memory serializedArgs = new bytes[](args.length);
for (uint256 i = 0; i < args.length; i++) {
serializedArgs[i] = args[i].serialize();
}
// Serialize the call with CallFlags.All
return NeoSerializerLib.serializeCall(
target, // address is automatically converted
method,
NeoSerializerLib.CALL_FLAGS_ALL, // Use library constants
serializedArgs
);
}
}contract MyContract {
// Both bytes20 and address are supported
function serializeNeoAddress(bytes20 contractHash) public pure returns (bytes memory) {
// Hash160 is serialized with reversed bytes (little-endian)
return NeoSerializerLib.serializeHash160(contractHash);
}
function serializeNeoAddressFromAddr(address contractAddr) public pure returns (bytes memory) {
// Address type is automatically converted to bytes20
return NeoSerializerLib.serializeHash160(contractAddr);
}
}contract MyContract {
using NeoSerializerLib for uint256;
using NeoSerializerLib for string;
using NeoSerializerLib for bytes;
function serializeMixedData(
uint256 id,
string memory name,
bytes memory data
) public pure returns (bytes memory) {
bytes[] memory items = new bytes[](3);
items[0] = id.serialize();
items[1] = name.serialize();
items[2] = data.serialize();
// Wrap in an array
return NeoSerializerLib.serialize(items);
}
}contract MyContract {
function deserializeInteger(bytes memory data) public pure returns (uint256) {
(uint256 value, ) = NeoSerializerLib.deserializeUint256(data, 0);
return value;
}
function deserializeString(bytes memory data) public pure returns (string memory) {
(bytes memory bytesValue, ) = NeoSerializerLib.deserializeBytes(data, 0);
return string(bytesValue);
}
function deserializeArray(bytes memory data) public pure returns (bytes[] memory) {
(bytes[] memory items, ) = NeoSerializerLib.deserializeArray(data, 0);
return items;
}
}serialize(bool value)→bytesserialize(uint256 value)→bytesserialize(bytes memory value)→bytesserialize(string memory value)→bytesserialize(uint256[] memory value)→bytesserialize(bytes[] memory value)→bytesserializeHash160(bytes20 value)→bytes(for Neo contract addresses)serializeHash160(address value)→bytes(convenience overload for address)serializeBuffer(bytes memory value)→bytes(type 0x30, for ByteArray params)serializeCall(bytes20 target, string memory method, uint256 callFlags, bytes[] memory args)→bytesserializeCall(address target, string memory method, uint256 callFlags, bytes[] memory args)→bytes(convenience overload)
deserializeBool(bytes memory data, uint256 offset)→(bool value, uint256 newOffset)deserializeUint256(bytes memory data, uint256 offset)→(uint256 value, uint256 newOffset)deserializeBytes(bytes memory data, uint256 offset)→(bytes memory value, uint256 newOffset)deserializeArray(bytes memory data, uint256 offset)→(bytes[] memory items, uint256 newOffset)
CALL_FLAGS_NONE(0)CALL_FLAGS_READ_STATES(1)CALL_FLAGS_WRITE_STATES(2)CALL_FLAGS_ALLOW_CALL(4)CALL_FLAGS_ALLOW_NOTIFY(8)CALL_FLAGS_STATES(3) - ReadStates | WriteStatesCALL_FLAGS_READ_ONLY(5) - ReadStates | AllowCallCALL_FLAGS_ALL(15) - All flags combined
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "path/to/libraries/NeoSerializerLib.sol";
contract OracleBridge {
using NeoSerializerLib for uint256;
using NeoSerializerLib for string;
using NeoSerializerLib for bytes;
event OracleCallSerialized(bytes indexed serializedCall);
function serializeOracleRequest(
bytes20 oracleContract,
string memory url,
bytes20 callbackContract,
string memory callbackMethod,
uint256 gasForResponse
) public pure returns (bytes memory) {
// Serialize arguments
bytes[] memory args = new bytes[](6);
args[0] = url.serialize(); // String
args[1] = "".serialize(); // Empty filter
args[2] = NeoSerializerLib.serializeHash160(callbackContract); // Hash160
args[3] = callbackMethod.serialize(); // String
args[4] = "".serialize(); // Empty userData
args[5] = gasForResponse.serialize(); // Integer
// Serialize the call
return NeoSerializerLib.serializeCall(
oracleContract,
"requestOracleData",
NeoSerializerLib.CALL_FLAGS_ALL,
args
);
}
function deserializeOracleResponse(bytes memory data)
public
pure
returns (string memory result)
{
(bytes memory bytesResult, ) = NeoSerializerLib.deserializeBytes(data, 0);
return string(bytesResult);
}
}If using Hardhat, add the library path to your hardhat.config.ts:
import { HardhatUserConfig } from "hardhat/config";
const config: HardhatUserConfig = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
paths: {
sources: "./contracts",
// If library is in node_modules:
// libraries: "./node_modules/@axlabs/neo-serializer/contracts",
},
};
export default config;- All serialization functions are
pure(no state changes) - Deserialization functions return both the value and the new offset (for parsing multiple items)
- Hash160 values are automatically reversed to little-endian (matching Neo's UInt160 format)
- Integer zero serializes as empty bytes (
21 00), not a single zero byte - Integers with MSB ≥ 0x80 get a sign extension byte (matching .NET BigInteger.ToByteArray())
If you get import errors, check:
- The library files are in the correct directory structure
- Your import path matches the actual file location
- Hardhat/compiler can find the files (check
hardhat.config.ts)
Make sure you're using the correct types:
bytes20oraddressfor Hash160 (both are supported)bytes memoryfor serialized data (notbytes calldata)bytes[] memoryfor arrays of serialized items
For gas optimization:
- Prefer
purefunctions when possible - Consider caching serialized data if it's reused
- Serialize arguments once and reuse them if calling
serializeCallmultiple times