-
Notifications
You must be signed in to change notification settings - Fork 27
Using Appkit from Python
To use Appkit from Python, you need to bridge to a JVM instance running the Appkit code. To do so, we use JPype for this guide. It is presumed that you've already set up a proper Python environment.
Install jpype in your Python environment by typing
pip install JPype1
(or pip3 when using Python3).
For Appkit to work on your system, you'll need a JDK8 or JDK11 installed and you need the appkit "fat jar", that means a jar containing all dependencies to add to your project. You can find this jar on the releases page or build it yourself. (How to build the fat jar)
The general wrapper for your Python code is then something like this:
# Enable Java imports
import jpype.imports
# Pull in types
from jpype.types import *
# Launch the JVM
jpype.startJVM()
# Add Appkit fat jar to JVM classpath
jpype.addClassPath("ergo-appkit-fat-jar.jar")
import java.lang
# ----> add your ergo related code here
jpype.shutdownJVM()
You have two options to implement your ergo related code: You can do everything in your Python code and call all necessary Appkit methods from Python, or you create an own Java/Kotlin/Scala project and implement your necessary logic in these languages providing some methods for simpler use from Python.
A general recommendation which approach is more useful can't be given - while Appkit is a bit more easier to use from JVM, you most likely are more comfortable coding Python if you read this guide, so you might prefer using Python.
A downside is that code completion is not working for Appkit methods within Python (or at least there was no way found so far). The good news is that most things work pretty straightforward so code completion is not a must have.
Let's take a look on how to construct an ergo transaction in Appkit. In Java this is done the following way:
// file header
import org.ergoplatform.appkit.Address;
import org.ergoplatform.appkit.BoxOperations;
import org.ergoplatform.appkit.NetworkType;
import org.ergoplatform.appkit.RestApiErgoClient;
// within a method
NetworkType networkType = NetworkType.TESTNET;
RestApiErgoClient.create(
"http://213.239.193.208:9052/", // use your node or a public node here
networkType,
"",
RestApiErgoClient.getDefaultExplorerUrl(networkType)
).execute(ctx -> {
ErgoTreeContract contract = recipient.toErgoContract();
UnsignedTransaction unsignedTransaction = BoxOperations.createForSender(sender)
.withAmountToSpend(amountToSend)
.putToContractTxUnsigned(ctx, contract);
// reduce the transaction for use with ErgoPay
return ctx.newProverBuilder().build().reduce(unsignedTransaction, 0);
});
How to translate this to Python?
At first we need to declare all used imports. These can be done straightforward by looking at the Java imports:
from org.ergoplatform.appkit import RestApiErgoClient, NetworkType, BoxOperations, Address
Constructing the ErgoClient
is also a straightforward copy from the Java code:
network_type = NetworkType.TESTNET
node_client = RestApiErgoClient.create("http://213.239.193.208:9052/", network_type, "",
RestApiErgoClient.getDefaultExplorerUrl(network_type))
To execute code within a BlockchainContext
, a lambda function is used within Java:
ergoClient.execute(ctx -> {
// ....
});
Internally, lambda functions in Java are nothing more than an anonymous class implementing a special interface. So to adapt this construct into Python, we need to define such a class implementing this interface. JPype provides special annotations for this:
@JImplements(java.util.function.Function)
class BlockchainContextExecutor(object):
@JOverride
def apply(self, blockchain_context):
# use blockchain_context here
return something
Putting this all together, we can use the following code to construct and a 1 ERG roundtrip transaction, convert it
into a ReducedTransaction
and use Python features for Base64 encoding. This way, we've built the juicy part of an
ErgoPay Request in Python.
import base64
from jpype import JImplements, JOverride
# Enable Java imports
import jpype.imports
# Pull in types
from jpype.types import *
# Launch the JVM
jpype.startJVM()
jpype.addClassPath("ergo-appkit-fat-jar.jar")
import java.lang
from org.ergoplatform.appkit import RestApiErgoClient, NetworkType, BoxOperations, Address
# this executor sends a 1 ERG roundtrip
@JImplements(java.util.function.Function)
class ReducedRoundTripExecutor(object):
def __init__(self, address):
self.address = address
@JOverride
def apply(self, blockchain_context):
round_trip_address = self.address
unsigned_tx = BoxOperations.createForSender(Address.create(round_trip_address),
blockchain_context).withAmountToSpend(
1000 * 1000 * 1000).putToContractTxUnsigned(Address.create(round_trip_address).toErgoContract())
return blockchain_context.newProverBuilder().build().reduce(unsigned_tx, 0)
def get_base64_reduced_tx(executor):
network_type = NetworkType.TESTNET
node_client = RestApiErgoClient.create("http://213.239.193.208:9052/", network_type, "",
RestApiErgoClient.getDefaultExplorerUrl(network_type))
reduced_tx = node_client.execute(executor)
return base64.urlsafe_b64encode(reduced_tx.toBytes()).decode('utf-8')
# print base64 url encoded reduced transaction - this is the most interesting part of an ErgoPay request
print(get_base64_reduced_tx(ReducedRoundTripExecutor("3Wwxnaem5ojTfp91qfLw3Y4Sr7ZWVcLPvYSzTsZ4LKGcoxujbxd3")))
jpype.shutdownJVM()
For minting a token, you can make use of your knowledge gained above and by using Boxoperations.mintTokenToContractTxUnsigned()
.
This method takes another lambda for building the actual token, so this time we need two helper classes.
Building the token itself is easy: Eip4TokenBuilder provides all necessary methods to hide away complexity.
from org.ergoplatform.appkit.impl import Eip4TokenBuilder
# helper class for next executor
@JImplements(java.util.function.Function)
class TokenBuilder(object):
@JOverride
def apply(self, token_id):
# other possible method: buildNftAudioToken with same parameters
return Eip4TokenBuilder.buildNftPictureToken(token_id,
1, # amount to issue
"Picture token", # name
"Description",
0, # decimals - should be 0 for NFT
bytearray(), # sha256 hash TODO fill yourself
"ipfs://..." # link to image content (can be https as well)
)
# this executor mints a token
@JImplements(java.util.function.Function)
class MintTokenExecutor(object):
def __init__(self, address):
self.address = address
@JOverride
def apply(self, blockchain_context):
round_trip_address = self.address
unsigned_tx = BoxOperations.createForSender(Address.create(round_trip_address),
blockchain_context).withAmountToSpend(
1000 * 1000).mintTokenToContractTxUnsigned(Address.create(round_trip_address).toErgoContract(),
TokenBuilder())
return blockchain_context.newProverBuilder().build().reduce(unsigned_tx, 0)
print(get_base64_reduced_tx(MintTokenExecutor("3Wwxnaem5ojTfp91qfLw3Y4Sr7ZWVcLPvYSzTsZ4LKGcoxujbxd3")))
You can find published Python artifacts here https://github.com/ergo-pad/ergo-python-appkit