Bitkub Chain SDK Compatible Smart Contract
Welcome to this tutorial on creating a Bitkub Chain SDK-compatible smart contract. In this guide, we'll walk you through building a smart contract designed for integration with our Bitkub Chain SDK. This tutorial will focus on outlining the essential structure of a compatible function, along with some additional practical examples.
Prerequisites
A basic understanding of blockchain technology and smart contracts.
Familiarity with the Solidity programming language.
A Solidity-compatible IDE. For this tutorial, we will be using Remix IDE to develop, compile, and deploy our smart contracts.
Bare Minimum Compatible Smart Contract
Let's start with a basic example to grasp the core concepts.
Explanation of the Contract Code
Let’s break down the smart contract code step by step, highlighting each part in detail.
1. SPDX Identifier
At the very top, we see the line:
This line is the SPDX License Identifier, which specifies the type of license under which this contract is distributed. In this case, it's the MIT license.
💡 Note: If you don’t want to specify a license, use UNLICENSED (not Unlicense) to ensure clarity that the contract is not licensed.
2. Pragma Solidity
This contract is built using Solidity version 0.8.x, specifically any version from 0.8.0 up to but not including 0.8.20.
💡 Note: It’s important to note that Bitkub Chain currently supports Solidity versions up to 0.8.19 (at the time of writing), so we specify this range.
3. State Variables
Here, we define two state variables:
myUint256Var
: An unsigned integer (uint256
) initialized with the value 7216. This will be updated when the function is called.myAddressVar
: Anaddress
type variable to store Ethereum-like addresses.
These variables will hold the data passed through the function.
💡 Note: If these state variables were uninitialized, their values would default to zero. In Solidity, uninitialized state variables take on zero-like values (e.g.,
uint256
is0
andaddress
is0x000...000
).
4. Function Declaration
This is the function where the actual logic occurs. Let's examine its components:
Function Parameters: The function accepts two parameters:
uint256 var_
: This parameter is inputable from the SDK and will be passed into the function when called.address bitkubNext_
: This parameter indicates the user interacting with the function. A compatible function on Bitkub Chain must have its last parameter as an address.
External Keyword:
The
external
keyword signifies that this function can only be called by a transaction. This means it can be triggered by an external account or even another contract.
💡 Important: It doesn’t necessarily have to be another contract— a contract can also call itself in a circular fashion. However, this function cannot be called internally by other functions in the same contract without using
this.mySDKMethod1()
5. Function Logic: Assigning Values to State Variables
In the function body, we simply assign the input values (from the parameters var_
and bitkubNext_
) to the corresponding global state variables myUint256Var
and myAddressVar
. This updates the state of the contract.
6. Observing State Variables
Since both variables are marked public, we can observe their values externally at any time. In Solidity, marking a state variable as public
automatically creates a getter function, allowing anyone to read its value.
💡 Note: If these variables were not marked as
public
, they would be private by default, meaning they could not be observed externally through normal means.
Key Points
SPDX License Identifier: Use
UNLICENSED
if you don’t want to declare a license.Solidity Version: Ensure compatibility with 0.8.x, and be mindful of the chain’s supported version limits (up to 0.8.19).
Two State Variables: Used to hold the values passed through the function.
Function with Two Parameters: Only the first parameter is inputable from the SDK, while the second must always be an address (indicating the interacting user).
External Functions: Can only be called by transactions and must be called with
this
internally.State Variables: Public state variables are observable externally, while private ones cannot be seen without getters.
This simple contract provides a bare minimum example of what a Bitkub Chain-compatible smart contract should look like, with a focus on SDK compatibility and proper state variable management.
Exploring More: Building a More Practical and Sophisticated Smart Contract
Explanation of the Contract Code
1. Addition of a Modifier
This modifier ensures that only the SDK CallHelper Router can call functions with this modifier. It is designed to prevent unauthorized users from interacting with specific functions, which is critical for securing the contract's operations.
2. SDK CallHelper Router Address
The SDK CallHelper Router address is stored in a constant variable in this example, which holds the router address for Bitkub Chain testnet. This ensures that only calls originating from this address are allowed to invoke functions protected by the onlySDKCallHelperRouter
modifier.
💡 Note: The SDK CallHelper Router address may change if the contract is upgraded or moved to a different address. In such cases, it would be better to store the address in a changeable state variable. However, for simplicity, we are using a constant in this example.
Method 1: Simulating Work and Event Emission
In this method, we simulate work being done by:
Writing to state variables such as
myAddrToUint8Map
andmyStringArrVar
.Emitting an event to signal that the function has executed successfully, with relevant data.
An SDK-compatible function can receive multiple input variables, as shown here. The function takes a combination of an address, a uint256 value, and a string, demonstrating the flexibility of inputs that SDK functions can handle.
Method 2: Handling Arrays and Compatibility with the SDK
Here, we have a function that accepts an array of addresses (address[] memory
). Unfortunately, the SDK system does not yet support arrays or structs as parameters, making this function incompatible with the SDK at the time of writing.
Workaround: Using bytes
and Decoding
bytes
and DecodingTo work around the current SDK limitations, we provide an overloaded version of this function:
This version accepts a bytes
type input, decodes it into an array, and then calls the original array-based function. This allows us to work within the SDK's constraints while still preparing the contract for future support of arrays. By overloading the function, we ensure that once array support is available, we can switch seamlessly to the array-based implementation.
Encoding Arrays into bytes
using JavaScript
bytes
using JavaScriptIf you're working with the SDK and need to encode an array of addresses into bytes
, you can use Ethers.js to achieve this. Here's the relevant JavaScript code to encode an array into bytes
format:
This script uses the AbiCoder from Ethers.js to encode the array of addresses into the
bytes
format.The resulting
encoded
value is what you would pass as theabiEncodedAddressArr_
parameter to the Solidity function.This approach can also be applied to functions that take structs, arrays of structs, or even nested structs as inputs (--structs are essentially tuples, and nested structs are tuples within tuples!)
Method 3: Interacting with Another Contract
In this third method, we demonstrate how to call another contract from within an SDK-compatible function. The parameter a_
is passed to the setA
function of the target contract, and the target contract returns a_ + 1
.
This shows how contracts can interact with each other in Solidity, which is a common use case in more complex decentralized applications. The ability to make external calls is key for creating sophisticated systems that involve multiple contracts working together.
Method 4 and 5: transferFrom
Using SDK Transfer Router
transferFrom
Using SDK Transfer RouterIn the fourth method, we demonstrate how to replicate the transferFrom
functionality of ERC20 tokens within our SDK system by calling the transferKAP20
method on the SDK Transfer Router.
💡 Note: The SDK Transfer Router address should not be constant in a production environment, as it may change if the contract is upgraded or moved to a new address. In such cases, it would be better to store the address in a modifiable state variable to allow flexibility. However, for simplicity, we are using a constant in this example.
Approval Requirement
Before this function can execute successfully, you must first approve the contract to spend the tokens on behalf of the user (bitkubNext_
) using the SDK. This is similar to how an ERC20 token requires approval before calling transferFrom
. If the token is not approved, the function will revert, just like with ERC20 token transfers.
As for the fifth method, it is the same as the fourth but for KAP721 tokens.
Key Points
Modifier for SDK CallHelper Router: Ensures only the SDK CallHelper Router can call certain functions.
Related Addresses: Stored in constant variables for simplicity in this example, though they can be made flexible in production contracts.
Event Emission: Helps in simulating work and provides a way to track function execution.
Handling Arrays: While arrays are not yet supported in the SDK, we provide a workaround using
bytes
and decoding, with JavaScript code to handle the encoding.Contract Interactions: Demonstrates how to call another contract from within an SDK-compatible function, a key feature in complex smart contracts.
Last updated