Skip to content

Repository files navigation

Crypto Chief crypto-processing SDK for Java

Maven Central Java License: MIT

Pure Java SDK for the Crypto Chief crypto-processing API. No Kotlin runtime, no reactive bridges — straightforward synchronous API with records, builders, and OkHttp.

Installation

Maven

<dependency>
  <groupId>com.crypto-chief</groupId>
  <artifactId>cryptochief-crypto-processing-java</artifactId>
  <version>0.8.0</version>
</dependency>

Gradle (Kotlin DSL)

dependencies {
    implementation("com.crypto-chief:cryptochief-crypto-processing-java:0.8.0")
}

Gradle (Groovy)

dependencies {
    implementation 'com.crypto-chief:cryptochief-crypto-processing-java:0.8.0'
}

Requires Java 17+.

Quick start

Credentials come from the dashboard → Integration tab.

import com.cryptochief.processing.Chain;
import com.cryptochief.processing.CryptoChiefClient;
import com.cryptochief.processing.models.EstimatePayoutRequest;
import com.cryptochief.processing.models.ExecutePayoutRequest;

public class App {
    public static void main(String[] args) {
        try (CryptoChiefClient client = CryptoChiefClient.create("mer_...", "sk_...")) {
            var estimate = client.payouts().estimate(
                EstimatePayoutRequest.of(Chain.ETH_SEPOLIA, "ETH", "0.0001", "0x..."));
            System.out.println("recipient gets " + estimate.amountToReceive());

            var payout = client.payouts().execute(new ExecutePayoutRequest(
                "order-42",
                "user-42",
                Chain.ETH_SEPOLIA,
                "ETH",
                "0.0001",
                "0x...",
                "https://your.app/webhooks/payout",
                null, false, false, null, null, null));
            System.out.println("payout: " + payout.uuid() + " → " + payout.status());
        }
    }
}

Services

Service Endpoints
client.payouts() estimate, execute, info, history, batchEstimate, batchExecute
client.transactions() sign, execute, info, history + EVM/TRON/Solana/TON helpers
client.payIns() create, info, history, cancel, selectAsset, resetAsset
client.wallets() generate, list, info, history, freeze, rebindMaster, setCallbackUrl, clearCallbackUrl, setLabel, clearLabel, decryptPrivateKey
client.sweeps() force, history, walletHistory, settings, updateSettings, updateGasSource
client.withdrawals() info, history
client.staticDeposits() info, history
client.blockchain() contractsAvailable, contractsList, blockchains, walletBalance, transactionStatus
client.currencies() fiatToCrypto, cryptoToFiat, fiats, cryptos
client.credits() balance, topup

Invoices (PayIn)

FIAT mode — customer picks the coin at payment time:

import com.cryptochief.processing.models.CreatePayInRequest;
import com.cryptochief.processing.models.PayInMode;

var invoice = client.payIns().create(new CreatePayInRequest(
    "order-42", "user-42", PayInMode.FIAT,
    null, 3600, "https://your.app/webhooks/invoice", null, null, null, null,
    "19.99", "USD", null, null,
    null, null));
System.out.println(invoice.paymentLink());

CRYPTO mode — fix the coin and amount up front:

import com.cryptochief.processing.Asset;
import com.cryptochief.processing.Chain;

var invoice = client.payIns().create(new CreatePayInRequest(
    "order-42", "user-42", PayInMode.CRYPTO,
    null, null, "https://your.app/webhooks/invoice", null, null, null, null,
    null, null, null, null,
    "10", new Asset(Chain.TRON_MAINNET, "USDT")));
System.out.println("pay to " + invoice.toAddress());

Wallets

generate takes an optional label — a name of your own for the wallet, at most 255 characters, stored and never interpreted. It applies to every wallet type, a master wallet as much as a static one. Leave it null and the wallet stays unnamed; the field then does not go on the wire at all.

import com.cryptochief.processing.ChainFamily;
import com.cryptochief.processing.models.GenerateWalletRequest;
import com.cryptochief.processing.models.WalletType;

var wallet = client.wallets().generate(new GenerateWalletRequest(
    WalletType.STATIC, ChainFamily.EVM, masterAddress,
    "https://your.app/webhooks/deposit",
    "Acme Corp — EU customers"));

The name comes back on every response that describes a wallet — generation, info, the list, and the updates below — as label(), which reads null when the wallet has no name.

Three things can still be changed once the wallet exists.

rebindMaster re-points a transit or static wallet at another master wallet of the project:

var moved = client.wallets().rebindMaster(depositAddress, otherMasterAddress);
System.out.println(moved.masterWalletAddress());

It moves no money. It decides where the next sweep settles — including sweeps already queued but not yet sent — and everything swept before stays on the previous master. Calling it again with the same master returns 200 and changes nothing. Master wallets cannot be re-pointed, and the new master must be the same chain family and not frozen.

setCallbackUrl sets or clears the deposit webhook of a static wallet after creation:

client.wallets().setCallbackUrl(depositAddress, "https://your.app/webhooks/deposit");
client.wallets().clearCallbackUrl(depositAddress);   // sends "", stops the announcements

Static wallets only — master and transit answer 400. A deposit that was already announced is not announced again to the new URL.

setLabel renames a wallet, or takes the name off it:

client.wallets().setLabel(depositAddress, "Acme Corp — EU customers");
client.wallets().clearLabel(depositAddress);   // sends "", the wallet goes back to unnamed

Every wallet type, unlike the callback URL — a master wallet is named the same way a static one is. Over 255 characters answers 400 with LABEL_TOO_LONG.

All three calls return the wallet as it stands afterwards, so the new binding, URL or name is visible without a second request. masterWalletAddress(), callbackUrl() and label() read as null when the wallet has none.

history lists every pay-in that used one deposit address — useful when a payer says they sent funds and you have the address but not the order, since a deposit wallet can serve several orders over its lifetime:

import com.cryptochief.processing.models.WalletHistoryQuery;

var page = client.wallets().history(depositAddress);
for (var order : page.items()) {
    System.out.println(order.orderId() + " → " + order.status());
}

var window = client.wallets().history(new WalletHistoryQuery(
    depositAddress, "2026-08-01T00:00:00+00:00", "2026-08-31T23:59:59+00:00", 1, 50));

The same order and meta records as client.payIns().history() — this is the same list, narrowed to one wallet. The address is matched case-insensitively, so either spelling of an EVM address works, and an address your project does not own yields an empty page rather than an error: an empty result is not proof the address does not exist.

Auto-sweep settings

A deposit wallet is swept to your master wallet on a policy: as soon as funds arrive, once the balance reaches an amount, or never on its own (a force sweep still works).

import com.cryptochief.processing.models.SweepFieldWrite;
import com.cryptochief.processing.models.SweepPolicyMode;
import com.cryptochief.processing.models.SweepSettingsQuery;

var s = client.sweeps().updateSettings(depositAddress,
    SweepFieldWrite.set(SweepPolicyMode.THRESHOLD),
    SweepFieldWrite.set("250"),
    null);

System.out.println(s.effective().typeWork());  // what will actually happen
System.out.println(s.effective().source());    // which layer decided it

The read (client.sweeps().settings(SweepSettingsQuery.forAddress(address))) comes back in three layers — effective (what will happen), override (what this wallet decides for itself) and projectDefault (what it falls back to) — because only the three together say whether a value is yours or inherited.

Inheritance is per field: writing the mode leaves the fee mode inherited. A null argument leaves a field alone; SweepFieldWrite.inherit() stops overriding it.

fee_mode — who covers a gas shortfall

A deposit wallet that already holds enough of the chain's native coin pays for its own transfer, whatever the mode. fee_mode only decides where the missing gas comes from when it does not:

Value Where the shortfall comes from
SweepFeeMode.CLIENT Your own master wallet.
SweepFeeMode.SERVICE The platform — and the cost is billed to your API credits.
SweepFeeMode.MIX The default. Tries client first, falls back to service when the master wallet cannot cover it.

So service, and every mix sweep that falls back to it, spends API credits rather than on-chain balance — a cost that shows up on the credits ledger and nowhere in the wallet.

gas_source — who buys the energy on TRON

gas_source decides what is bought to move a sweep on TRON, where fee_mode decides who covers a gas shortfall. The two are independent, and the energy is billed to your API credits whatever the fee mode says.

Value What happens
SweepGasSource.NATIVE The wallet burns its own TRX for energy.
SweepGasSource.RENTED The platform supplies the energy, billed to your API credits. The default.

Not setting it is not the same as setting native. A wallet that never chose one gets the platform default, which is rented — so energy is supplied and billed to your credits without anyone having switched it on. To have the wallet burn its own TRX, send native explicitly.

import com.cryptochief.processing.models.SweepGasSource;

client.sweeps().updateGasSource(tronAddress,
    SweepFieldWrite.set(SweepGasSource.NATIVE));   // burn the wallet's own TRX

client.sweeps().updateGasSource(tronAddress,
    SweepFieldWrite.inherit());                    // drop the override and inherit again

inherit() names gas_source in the fields mask with no value, which is the only way to clear one field while keeping the others — and it inherits back to rented, not to "off". The mask accepts type_work, threshold_amount_usd, fee_mode and gas_source.

Read it back with effective().gasSource(), which is always a concrete value. A null in override().gasSource() means only that this layer does not decide — inherited, not switched off.

Carried and ignored on every chain other than TRON.

Sweep history

A sweep is broadcast first and confirmed after: SweepStatus.BROADCASTED means the transaction is out and not yet confirmed, SweepStatus.COMPLETED means confirmed, with sweepConfirmations() above zero. Earlier platform versions reported completed at broadcast, so a sweep could read as settled while its transaction was still unconfirmed; the confirmation count is what separates the two.

completedAt() is not proof the sweep settled. It is stamped when the task reached a terminal outcome, and failed and skipped are terminal too — so it is absent only while the sweep is in flight, and its presence says the sweep finished rather than that it succeeded. Check sweepConfirmations() is above zero, or take confirmedAt() from the sweep.confirmed webhook, which carries a separate field for exactly this reason.

Both history endpoints filter on status and search as well as mode:

import com.cryptochief.processing.models.SweepHistoryQuery;
import com.cryptochief.processing.models.SweepStatus;
import com.cryptochief.processing.models.SweepWalletHistoryQuery;

var failed = client.sweeps().history(SweepHistoryQuery.empty()
    .withStatus(SweepStatus.FAILED)
    .withSearch("0x77EDde3213b70c9dd224C874c28f41B23B070f65"));

var one = client.sweeps().walletHistory(SweepWalletHistoryQuery.forAddress(depositAddress)
    .withStatus(SweepStatus.COMPLETED));

status takes one status. Leave it out and every status comes back, skipped among them — a skipped sweep is a balance the platform decided against moving, a normal outcome rather than a failure, and easy to be surprised by in a total. search is a substring match: on history it matches the wallet address, the sweep or gas-pump transaction hash and the task_id; on walletHistory the hashes and the task_id, since the address is already the question.

Blockchain data

contractsAvailable is the project's own asset catalogue — what it can be paid in right now, and the list that governs orders, sweeps and payouts. contractsList is the platform-wide one: every coin and token the platform supports anywhere, for building a "which assets could we turn on" picker. Same item shape:

for (var asset : client.blockchain().contractsList().items()) {
    System.out.println(asset.network() + " " + asset.coin()
        + " family=" + asset.chainFamily()
        + " test=" + asset.isTest()
        + " decimals=" + asset.decimals());
}

contract() is an empty string for a native coin, not null — there is no contract to name. isTest() marks an asset on a test network, which is what tells a worthless payment from a real one when the platform picks the asset for you.

blockchains is a different question: which chains the scanner is connected to and can read blocks from right now. Infrastructure, not your catalogue. It answers with a bare JSON array, so there is no envelope to unwrap:

for (var chain : client.blockchain().blockchains()) {
    System.out.println(chain.name() + " read as " + chain.type());   // ETH_MAINNET read as evm
}

type() is the scanner's own lower-case spelling of the protocol family (evm, tron, solana), not the upper-case ChainFamily used elsewhere in the API.

Currency lists

What can be quoted, for building a currency picker:

for (var fiat : client.currencies().fiats()) {
    System.out.println(fiat.code() + " — " + fiat.name());   // SEK — Swedish Krona
}

var cryptos = client.currencies().cryptos();
System.out.println(cryptos.count() + " tickers against " + cryptos.quote());
System.out.println(cryptos.byExchange().get("binance"));

fiats() are the codes fiatToCrypto and a pay-in's currency accept. cryptos() is rate availability only — a ticker listed there is one the platform can price, which does not mean your project can be paid in it. For that, use client.blockchain().contractsAvailable().

Contract calls

EVM / TRON:

import com.cryptochief.processing.Amount;
import java.util.List;

var signed = client.transactions().erc20Transfer(
    Chain.ETH_MAINNET,
    "0x...",
    "0xdAC17F958D2ee523a2206206994597C13D831ec7",
    "0x...",
    Amount.toBase("12.50", 6));
client.transactions().execute(signed.uuid());

Custom EVM call:

This snippet shows the encoder, not a complete swap. Uniswap's router moves your input token with transferFrom, so it needs an ERC-20 approve(address,uint256) on that token first, confirmed before the swap is signed — without it the swap reverts and burns the gas. And an amountOutMin of 0 accepts whatever the pool returns, which on a public mempool hands the trade to the first sandwich bot that sees it. Sign and confirm the approve as a separate transaction before signing the swap.

var signed = client.transactions().signEvmCall(
    Chain.ETH_SEPOLIA,
    "0x...",
    "0xUniswapV2Router",
    "swapExactTokensForTokens(uint256,uint256,address[],address,uint256)",
    List.of(amountIn, amountOutMin, path, "0xYou", deadline));

Solana Anchor:

import com.cryptochief.processing.solana.Borsh;
import com.cryptochief.processing.models.SolanaAccount;
import java.util.List;

var signed = client.transactions().signAnchorCall(
    Chain.SOLANA_DEVNET,
    "YourWallet...",
    "ProgramId...",
    "transfer",
    List.of(Borsh.u64(1_000_000L)),
    List.of(new SolanaAccount("Acc1", true, true)),
    null);

TON Jetton:

import com.cryptochief.processing.services.TransactionsService.JettonTransferRequest;

var signed = client.transactions().jettonTransfer(new JettonTransferRequest(
    Chain.TON_MAINNET,
    "EQ...",
    "EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs",
    "EQ...",
    Amount.toBase("12.50", 6),
    null, null, null, null,
    "Order #4242",
    0L,
    null));

Polling

import com.cryptochief.processing.PollOptions;
import com.cryptochief.processing.poll.Polling;
import java.time.Duration;

var terminal = Polling.waitForPayout(client, payout.uuid(),
    new PollOptions(Duration.ofSeconds(5), Duration.ofMinutes(10)));

Webhook handling

import com.cryptochief.processing.webhook.PayoutWebhookEvent;
import com.cryptochief.processing.webhook.WebhookSignatureException;
import com.cryptochief.processing.webhook.WebhookVerifier;

try {
    var event = WebhookVerifier.parse(apiKey, rawBody,
        request.getHeader("Signature"), PayoutWebhookEvent.class);
    System.out.println("payout " + event.uuid() + " → " + event.status());
} catch (WebhookSignatureException e) {
    response.setStatus(401);
}

IP allowlist:

if (!WebhookVerifier.SENDER_IPS.contains(request.getRemoteAddr())) {
    response.setStatus(403);
    return;
}

Typed events: PayoutWebhookEvent, TransactionWebhookEvent, PayInWebhookEvent, StaticDepositWebhookEvent.

Wallet private key decryption

Upload an RSA public key in the dashboard (Project Settings → RSA Key), then configure the client with the matching private key:

import com.cryptochief.processing.Options;
import com.cryptochief.processing.rsa.RsaKeyLoader;

var client = new CryptoChiefClient(Options.builder()
    .merchantId("mer_...")
    .apiKey("sk_...")
    .rsaPrivateKey(RsaKeyLoader.loadPrivateKeyFromFile("/path/to/key.pem"))
    .build());

var wallet = client.wallets().generate(req);
String rawHex = client.wallets().decryptPrivateKey(wallet.privateKeyEncrypted());

PKCS#1 and PKCS#8 PEM both supported, JDK crypto only.

Configuration

import java.time.Duration;
import com.cryptochief.processing.Options;

var client = new CryptoChiefClient(Options.builder()
    .merchantId("...")
    .apiKey("...")
    .baseUrl("https://staging-api.crypto-chief.com")
    .requestTimeout(Duration.ofSeconds(30))
    .maxRetries(5)
    .initialRetryDelay(Duration.ofMillis(250))
    .maxRetryDelay(Duration.ofSeconds(10))
    .userAgent("my-app/1.2.3")
    .httpClient(myPreconfiguredOkHttpClient)
    .build());

A caller-supplied httpClient is not closed by the SDK.

Errors

import com.cryptochief.processing.exceptions.ApiException;
import com.cryptochief.processing.exceptions.ErrorCode;
import com.cryptochief.processing.exceptions.NetworkException;

try {
    client.payouts().execute(req);
} catch (ApiException e) {
    switch (e.code()) {
        case ErrorCode.INSUFFICIENT_FUNDS -> { /* top up the master wallet */ }
        case ErrorCode.ORDER_ALREADY_EXIST -> { /* idempotent retry */ }
        default -> throw e;
    }
} catch (NetworkException e) {
    // already retried up to options.maxRetries
}

5xx is retried with exponential backoff and full jitter. 4xx is not retried.

Other SDKs

SDKs for other languages are listed at docs-sdk.crypto-chief.com/processing/processing.

License

MIT © 2026 Crypto Chief

About

Java SDK for Crypto Chief crypto-processing API — accept crypto payments, send single & mass payouts, sign on-chain transactions (EVM, TRON, Solana, TON), manage wallets, verify webhooks. Records, OkHttp, Jackson. JDK 17+.

Resources

Stars

6 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages