Skip to content

Repository files navigation

Java4Ever

JDK versionSDK versionLicenseChannel on Telegramjavadoc

Logo

Java4Ever is a feature-rich framework for smart-contracts development, testing & accessing TVM-compatible blockchains like Everscale, Venom, GOSH and so on. Closest alternative is a TypeScript-based locklift framework by Broxus.

Framework internally uses JSON-RPC connection to wrapped native EVER-SDK library (java4ever-binding).

Features

  • Auto-generation of Java smart contract wrappers to create, deploy, transact with and call smart contracts from native Java code
  • Auto-conversion of ABI types to Java native types for any input/output
  • Easy work with TIP 3.2 Fungible Tokens
  • Easy work with Multisig Wallets
  • Easy work with EverNode-SE Givers (with easy polymorph to giving from wallet on production)
  • Access to transaction tree of complex calls
  • Complete typed implementation of latest EVER-SDK JSON-RPC client API
  • Pluggable EVER-SDK library support (no rebuild needed, just plug-in SDK lib you like with special Loaders)

Java4Ever only runtime dependencies are its own binding and utils libs and Jackson Core for fast JSON serialisation/deserialization It also uses JavaPoet for generating smart contract wrappers.

Contents

Quick start

Prerequisites

Prerequisites for Kotlin

  • Install Kotlin 2.0.0
  • Install Gradle 8.8
  • Make sure to setup use of Gradle 8.8 and JDK 22 for compilation and runtime

Note: EverNode-SE needed only for quick start example, it's not a requirement

Add java4ever to your Maven/Gradle setup:

  • Gradle
dependencies {
implementation 'tech.deplant.java4ever:java4ever-framework:3.2.0'
}
  • Maven
<dependency>
<groupId>tech.deplant.java4ever</groupId>
<artifactId>java4ever-framework</artifactId>
<version>3.2.0</version>
</dependency>

Deploy your first contract

// initialize EVER-SDK libraryEverSdk.load(); // create config context, save its idintcontextId = EverSdk.createWithEndpoint("http://localhost/graphql").orElseThrow();
// creates random pair of keysvarkeys = Credentials.ofRandom(contextId); // use it to deploy a new contractvarcontract = newSafeMultisigWalletTemplate()
.prepareDeploy(contextId,0, keys, newBigInteger[]{keys.publicKeyBigInt()}, 1)
.deployWithGiver(EverOSGiver.V2(contextId), EVER_ONE);
// get the contract infoSystem.out.println(contract.account().id() + " is active: " + contract.account().isActive());

Examples and Guides

Setting up SDK

SDK setup consists of two steps:

  1. Loading EVER-SDK library (should be done once)
  2. Creating context/session with certain config (should be done for every new endpoint or config change)

Both steps are described below.

Loading EVER-SDK library

To load EVER-SDK connection to JVM, use EverSdk.load() static method. Loaded EVER-SDK is a singleton, you can't use other version of library simultaneously. Java4Ever stores wrapped copy of actual EVER-SDK libraries in its resources. To load wrapped library, run:

EverSdk.load();

Note: We found problems with loading library from resources using Spring's fatJar bundles. Please, use alternative loaders if you use fatJar too.

If you want to use custom binaries or version, you should use other loaders. All loaders are just ways to reach library, so you should get/build ton_client library first. You can find EverX precompiled EVER-SDK files here. Here are the examples of loader options:

// loads library from path saved in environment variableEverSdk.load(AbsolutePathLoader.ofSystemEnv("TON_CLIENT_LIB")); // loads library from ~ (user home)EverSdk.load(AbsolutePathLoader.ofUserDir("libton_client.so")); // loads from any absolute pathEverSdk.load(newAbsolutePathLoader(Path.of("/home/ton/lib/libton_client.so"))); // loads library from java.library.path JVM argumentEverSdk.load(newJavaLibraryPathLoader("ton_client")); 

Creating config context and specifying endpoints

Context configuration is needed to provide EVER-SDK library with your endpoints, timeouts and other settings. You can find a list of endpoints here: https://docs.evercloud.dev/products/evercloud/networks-endpoints If you're working with Everscale mainnet, here you can register your app and receive "ProjectID" part of the URL: https://dashboard.evercloud.dev/

// creates default EVER-SDK context without specifying endpointsintcontextId1 = EverSdk.createDefault(); // creates default EVER-SDK with specified endpointintcontextId2 = EverSdk.createWithEndpoint("http://localhost/graphql"); // creates EVER-SDK context from ready JSON stringintcontextId4 = EverSdk.createWithJson(configJsonString);

Save your contextId, you will use this id to call EVER-SDK methods.

Configuring SDK with Builder

Alternatively, you can call EverSdk.builder() that provides builder methods for all config values of EVER-SDK. Thus you can easily configure only needed parts of library.

intcontextId3 = EverSdk.builder()
.networkEndpoints("http://localhost/graphql")
.networkQueryTimeout(300_000L)
.build();

Adding ABI, TVC & other artifacts

Java4Ever includes easy API to work with files and java resources (both json-based and byte[]-based). Here are simple examples of getting contract ABIs and TVCs artifacts:

  • ContractAbi.ofFile("/path/to/your.abi.json") - reads abi from file (can be relative)

  • ContractAbi.ofResource("yourresource.abi.json") - reads abi from resources of your project

  • ContractAbi.ofString("") -reads abi from JSON string

  • ContractAbi.ofJsonNode(node) - reads abi from Jackson framework's JsonNode object

  • Tvc.ofFile("/path/to/your.tvc") - reads tvc from file (can be relative)

  • Tvc.ofResource("yourresource.tvc") - reads tvc from resources of your project

  • Tvc.ofBase64String("") -reads tvc from base64 encoded string

  • new Tvc(bytes) - reads tvc from byte array

Also, you can check JsonFile, JsonResource, ByteFile, ByteResource helpers for custom artifacts.

Crypto

Java4Ever includes basic helpers to create your seeds, key pairs and signatures. If you want some specific EVER-SDK functions, just use them firectly as all EVER-SDK API is available from Java4Ever.

Creating a random keypair

varkeys = Credentials.ofRandom(contextId);
Stringsk = keys.secretKey();
Stringpk = keys.publicKey();

Creating a random seed

varseed = Seed.ofRandom(contextId);
StringwordsPhrase = seed.phrase();
intwordsCount = seed.words();

Deriving keys from seed

varkeys = Credentials.ofSeed(contextId,seed);
Stringsk = keys.secretKey();
Stringpk = keys.publicKey();

Deriving keys from private

varkeys = Credentials.ofSecret("17011c9157f3cf9e3c75ce8778be6b1adc42cd7abc1aebc0d288d2c338d2d93b");
Stringsk = keys.secretKey();
Stringpk = keys.publicKey();

Using existing keys & seeds

varseed = newSeed("your seed phrase with 12 words or 24 with second constructor param");
varkeys = newCredentials("publickey_string","secretkey_string");

Using Signing Box

intcontext = EverSdk.createDefault();
varkeys = Env.RNG_KEYS();
// create implementation of your handlevarboxHandle = EverSdk.await(Crypto.registerSigningBox(context, newAppSigningBox() {
@OverridepublicStringgetPublicKey() {
return"";
}
@OverridepublicStringsign(Stringunsigned) {
return"";
}
})).handle();
// use this handle when creating contract objectvarcontract = newEverWalletContract(context, newAddress("0:9400ec4b8629b5293bb6798bbcf3dd25d72e4f114226b5547777d0fc98fe53fa"), newAbi.Signer.SigningBox(boxHandle));
// now contract calls will use your signing boxcontract.sendTransaction(dest, value, bounce).call();

Smart-contracts

Using already deployed contract

// to use already deployed contract, you should know its ABI and its addressvardeployedContractAbi = ContractAbi.ofResource("artifacts/giver/GiverV2.abi.json");
vardeployedContractAddress = newAddress("0:ece57bcc6c530283becbbd8a3b24d3c5987cdddc3c8b7b33be6e4a6312490415");
// if you don't know contract credentials, use Credentials.NONEvardeployedContractCredentials = newCredentials("2ada2e65ab8eeab09490e3521415f45b6e42df9c760a639bcf53957550b25a16",
"172af540e43a524763dd53b26a066d472a97c4de37d5498170564510608250c3");
// instantiate your contractvargiverContract = newAbstractContract(contextId,
deployedContractAddress,
deployedContractAbi,
deployedContractCredentials);
// make a call by function namevarfunctionCallPrepare = giverContract.functionCallBuilder()
.setFunctionName("getMessages")
.setFunctionInputs(Map.of()) // provide a map of params
.setReturnClass(Map.class)
.build();
// if you didn't provide return class, use callAsMap() and getAsMap() to receive plain JSONSystem.out.println(functionCallPrepare.getAsMap().toPrettyString());

If you don't want to write function calls by hand, use Contract Generation tips below to generate your own Contract classes.

Accessing contract account metadata

To access account metadata of certain smart-contract, get Account object from any type of Contract

Accountacc = contract.account();

Alternatively, you can create Account object from any address

Accountacc = AccountofAddress(contextId, "0:ece57bcc6c530283becbbd8a3b24d3c5987cdddc3c8b7b33be6e4a6312490415");

Then, get all needed info from account

acc.boc();
acc.code();
acc.codeHash();
acc.balance();
acc.accType();

Contract Generation

Calling generator for your artifacts

ContractWrapper class is a generator that will create java wrapper classes for all your contracts. You need only abi.json and .tvc artifacts of your contracts as a source for code generation.

Run the following, specifying your artifacts and where to place generated classes in params:

ContractWrapper.generate(ContractAbi.ofResource("mycontract.abi.json").abiContract(),
Tvc.ofResource("mycontract.tvc"),
Path.of("src/gen/java"),
"MyContract",
"org.example.contract",
"org.example.template",
newString[]{});

Contract and template wrappers will appear in packages that you specified.

If you're working with standard contracts, all wrappers are already generated (for Multisig Wallets, Givers, TIP3 and TIP4 contracts and so on - check javadoc)

Accessing your newly generated contract wrapper

To access contract account, create instance of your contract class by passing SDK Provider and address of deployed contract.

MyContractcontr = newMyContract(contextId, "0:your_contract_address");

Accessing generated function wrappers

Now, when your contract object is created, just get handle of one of functions by calling one of the contract methods.

FunctionHandlegetCustodiansFunctionHandle = contr.getCustodians();

Function in this example doesn't have params, but yours can have.

Calling Functions in various ways

With FunctionHandle you can make external calls, run get methods using remote or local boc and so on like this:

MyContract.ResultOfGetCustodianscustodians = getCustodiansFunctionHandle.get();
Map<String,Object> custodiansMap = getCustodiansFunctionHandle.getAsMap();
MyContract.ResultOfGetCustodianscustodians = getCustodiansFunctionHandle.call();
Map<String,Object> custodiansMap = getCustodiansFunctionHandle.callAsMap();
MyContract.ResultOfGetCustodianscustodians = getCustodiansFunctionHandle.getLocal(locallySavedBoc);

All the described functions, handles and return types are auto-generated when you generate contract wrapper

Variants of calls to function:

  • get() - runs getter method and returns auto-generated type
  • call() - makes external call (make sure you added credentials to contract object if contract checks signatures)
  • getLocal() - runs getter against provided boc
  • ...AsMap() - each method have AsMap variant that returns Jackson framework's JsonNode object instead of static type result

Deploying new contracts

Second class created by contract generator is MyContractTemplate.class. It's a companion class that stores ABI and TVC info for deployment.

Using wallets and other standard contract wrappers

Sending Internal Message from Multisig Wallet

varwalletContract = newSafeMultisigWallet(contextId,"", walletKeys);
getCustodiansFunctionHandle.sendFrom(walletContract, CurrencyUnit.VALUE(EVER,"1.25"), true, MessageFlag.FEE_EXTRA);

sendFrom() method also has sendFromAsMap() variant.

Calls and sends also has ...Tree() variants that can be used to monitor transaction tree execution and collect errors.

Encoding as Payload

You can encode FunctionHandle as a payload for internal call like this:

varpayload = getCustodiansFunctionHandle.toPayload();

Supported Multisig contracts

java4ever supports all popular types of Multisig contracts. You can check if it's your needed smart-contract by retrieving its code hash.

// multisig 1assertEquals("80d6c47c4a25543c9b397b71716f3fae1e2c5d247174c52e2c19bd896442b105",
SafeMultisigWalletTemplate.DEFAULT_TVC().codeHash(contextId));
assertEquals("e2b60b6b602c10ced7ea8ede4bdf96342c97570a3798066f3fb50a4b2b27a208",
SetcodeMultisigWalletTemplate.DEFAULT_TVC().codeHash(contextId));
assertEquals("207dc560c5956de1a2c1479356f8f3ee70a59767db2bf4788b1d61ad42cdad82",
SurfMultisigWalletTemplate.DEFAULT_TVC().codeHash(contextId));
// multisig 1 with modern compilerassertEquals("1974b06efa89ba22d1962d06efaef6d00751b7cdc3156c151bb0cc1c504e7e8c",
SafeMultisigSolc064WalletTemplate.DEFAULT_TVC().codeHash(contextId));
assertEquals("ad57a772ca0c56462e07a086e447abbb7605bd6ac1424cc4178dc9f4730093ff",
SetcodeMultisigSolc063WalletTemplate.DEFAULT_TVC().codeHash(contextId));
// multisig 2assertEquals("7377910a1b5d0c8073ba02523e139c7f42f9772fe0076a4d0b211ccec071eb7a",
SafeMultisigWallet2Template.DEFAULT_TVC().codeHash(contextId));
assertEquals("d66d198766abdbe1253f3415826c946c371f5112552408625aeb0b31e0ef2df3",
SetcodeMultisigWallet2Template.DEFAULT_TVC().codeHash(contextId));
// multisig 24assertEquals("7d0996943406f7d62a4ff291b1228bf06ebd3e048b58436c5b70fb77ff8b4bf2",
Safe24MultisigWalletTemplate.DEFAULT_TVC().codeHash(contextId));
assertEquals("a491804ca55dd5b28cffdff48cb34142930999621a54acee6be83c342051d884",
Setcode24MultisigWalletTemplate.DEFAULT_TVC().codeHash(contextId));
// specialsassertEquals("f3a07ae84fc343259d7fa4847b86335b3fdcfc8b31f1ba4b7a9499d5530f0b18",
BridgeMultisigWalletTemplate.DEFAULT_TVC().codeHash(contextId));

Supported TIP-3 Token contracts

java4ever supports TIP3TokenRootContract and TIP3TokenWalletContract. Also, TIP3Builder is available for specifying token parameters builder-style. Here is a quick example of working with TIP-3:

// creating token RootvartokenRoot = newTIP3Builder()
.setRootKeys(LOCAL_KEYS_ROOT)
.setOwnerAddress(LOCAL_MSIG_ROOT.address())
.setName("Test Token")
.setSymbol("TST")
.setDecimals(6)
.setRandomNonce(newRandom().nextInt())
.build(SDK_LOCAL, GIVER_LOCAL, CurrencyUnit.VALUE(EVER, "1.3"));
// deploying token wallettokenRoot.deployWallet(LOCAL_MSIG_WALLET1.address(),
CurrencyUnit.VALUE(EVER, "0.5"))
.sendFrom(LOCAL_MSIG_ROOT,
CurrencyUnit.VALUE(EVER, "1.5"));
// mint operationtokenRoot.mint(mintAmount, receiverAddress, EVER_ZERO, receiverAddress, false,
TvmCell.EMPTY)
.sendFromTree(LOCAL_MSIG_ROOT,
CurrencyUnit.VALUE(EVER, "0.3"),
true,
MessageFlag.FEE_EXTRA,
true,
TIP3TokenWalletContract.DEFAULT_ABI());
// gettersassertEquals(mintAmount, tokenRoot.totalSupply().get().value0());
assertEquals(mintAmount, tokenRoot.balance().get().value0());

Supported TIP-4 Nft contracts

java4ever provides TIP4Collection, TIP4IndexBasis, TIP4Index, TIP4Nft and TIP4Wallet templates and contract wrappers.

Deploying smart-contracts

Accessing Template

You can create template object with no additional params. If you didn't use generator, use AbstractTemplate class and pass ABI and TVC to it manually.

MyContractTemplatemyTemplate = newMyContractTemplate();
varabi = myTemplate.abi(); // getting ABI from templatevartvc = myTemplate.tvc(); // getting TVC from templatemyTemplate.tvc().code() // getting code cellmyTemplate.tvc().codeHash() // getting code hash

There are much more methods for TVC and ABI, including decoding and encoding initial data, various helpers for all sort of interactions.

Prepared deployment set

DeployHandle is a handle of prepared deployment set with all needed params. As with function handles, Template::prepareDeploy params may vary depending on your contract - your static variables and constructor params.

DeployHandledeployHandle = myTemplate.prepareDeploy(contextId, Credentials.NONE,"hello_world");

Variations of running deploy

// Deploys prepared contract deployment. Will work only when deploy target account address is already paid// You can check target account address by using deployHandle.toAddress() callMyContractmyContract = deployHandle.deploy();
// deploys prepared contract deployment by using funds from specified walletMyContractmyContract = deployHandle.deployWithGiver(walletContract, CurrencyUnit.VALUE(EVER,"1.25"));
// deploys prepared contract deployment by using funds from EverNode-SE giverMyContractmyContract = deployHandle.deployWithGiver(EverOSGiver.V2(contextId), CurrencyUnit.VALUE(EVER,"1.25"));

Each deployment returns a contract object after deploy is done. Also, you can use deployHandle.toAddress() if you need only address calculation.

Switching Givers

Deployment usually requires giving funds to target address of deployment. Here's the example of universal deployment that switches between evernode-se giver & msig wallet without any additional code:

Givergiver = null;
if (isEverOsNet()) {
giver = EverOSGiver.V2(contextId);
} else {
giver = newSafeMultisigWallet(contextId,"0:your_address");
}
deployHandle.deployWithGiver(giver, CurrencyUnit.VALUE(EVER,"1.25"));

This is possible as all Java4Ever wallet classes are implementing Giver interface. You can also generate wrappers that implements Giver interface.

Offline deployment

Example of creation and signing offline messages for later sending.

intofflineContext = EverSdk.builder()
.networkSignatureId(1L)
.networkQueryTimeout(300_000L)
.build();
inti = 0;
vartemplate = newEverWalletTemplate();
// let's generate 5 addresses that match certain condition and send them 1 everPredicate<Address> addressCondition = address -> address.makeAddrStd().contains("7777");
while (i < 5) {
varseed = Env.RNG_SEED(); // creates new seedvarkeys = seed.deriveCredentials(offlineContext); // derives keys from seed// let's calculate future EVER Wallet addressvarstateInit = template.getStateInit(offlineContext, keys.publicKey(), BigInteger.ZERO);
varaddress = template.getAddress(offlineContext, 0, stateInit);
// check conditions if we want to deploy contractif (addressCondition.test(address)) {
// let's create message bodies offline// here we send sendTransaction to ourselvesvarbody = newEverWalletContract(offlineContext, address, keys).sendTransaction(address, EVER_ONE, false)
.toPayload(false);
System.out.printf("Address: %s%n", address);
System.out.printf("Seed: %s, public: %s%n", seed.phrase(), keys.publicKey());
System.out.printf("Message body: %s%n", body);
System.out.printf("State Init: %s%n", stateInit);
intonlineContext = EverSdk.createWithEndpoint("https://gql.venom.foundation/graphql");
// let's send messages when we're onlineEverSdk.sendExternalMessage(onlineContext,
address.makeAddrStd(),
EverWalletTemplate.DEFAULT_ABI().ABI(),
stateInit.cellBoc(),
body.cellBoc(),
null);
i++;
}
}

Subsriptions

Unstable - Subscriptions were heavily changed in the last release, so there can be dragons.

Subscriptions consist of Subscription.Builder class where you describe your subscription details. After that, run the subscription with subscribe..() methods. Subscription supports sprcifying multiple GQL filters, multiple consumers, saving to queue, manual and auto-unsubscribing on condition.

// let's specify what will consume our event:Consumer<JsonNode> eventConsumer = jsonNode -> System.out.println(jsonNode.toPrettyString());
// describe our subscription in builder stylevarsubscriptionBuilder = Subscriptions
.onAccounts("acc_type", "id")
.addFilterOnSubscription("id: { eq: \"<your_address>\" }")
.addFilterOnSubscription("code_hash: { eq: \"<your_hash>\" }")
.addCallbackConsumer(eventConsumer)
.setCallbackToQueue(true); // if you don't want to specify consumer, you can switch on adding to internal queue// let's subsribevarsubscription1 = subscriptionBuilder.subscribeUntilCancel(1);
// let's unsubscribesubscription1.unsubscribe();
// perhaps some messages were pu in the queue?intsize = subscription1.callbackQueue().size();
// let's reuse builder, but subscribe until first event is firedvarsubscription2 = subscriptionBuilder.subscribeUntilFirst(1);
// another one, subscribed until certain conditionvarsubscription3 = subscriptionBuilder.subscribeUntilCondition(1, jsonNode -> !jsonNode.get("accounts").elements().hasNext());

Currency

All solidity currency constants are available from CurrencyUnit class. You can retrieve final bigint about like this

vareverAmount = CurrencyUnit.VALUE(EVER, "2"); // 2_000_000_000 nanoeversvareverAmount = CurrencyUnit.VALUE(MILLIEVER, "500.3"); // 500_300_000 nanoevers

If your token has custom decimals count, you can specify it like this

vartokenUnit = newCurrencyUnit.CustomToken(12); // my token has 12 decimals// then use your own tokenunits in all your callsvarnanoValue = CurrencyUnit.VALUE(tokenUnit, "2.2"); // 2_200_000_000_000 nanotokens

Logging

Java4Ever uses the JDK Platform Loggging (JEP 264: Platform Logging API and Service), so can be easily bridged to any logging framework. For example, to use log4j2, just add org.apache.logging.log4j:log4j-jpl to your Maven/Gradle build.

Getting Help

If you can't answer in this readme or have a bug/improvement to report:

About

Connects your JVM solutions to TON Virtual Machine smart-contracts. Develop, test & access Web 3.0 apps. Suitable for TVM family of blockchains: TON, AckiNacki, Everscale & Venom.

Topics

Resources

Stars

13 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages