Pure Java SDK for the Crypto Chief crypto-processing API. No Kotlin runtime, no reactive bridges — straightforward synchronous API with records, builders, and OkHttp.
<dependency>
<groupId>com.crypto-chief</groupId>
<artifactId>cryptochief-crypto-processing-java</artifactId>
<version>0.8.0</version>
</dependency>dependencies {
implementation("com.crypto-chief:cryptochief-crypto-processing-java:0.8.0")
}dependencies {
implementation 'com.crypto-chief:cryptochief-crypto-processing-java:0.8.0'
}Requires Java 17+.
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());
}
}
}| 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 |
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());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 announcementsStatic 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 unnamedEvery 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.
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 itThe 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.
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 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 isrented— so energy is supplied and billed to your credits without anyone having switched it on. To have the wallet burn its own TRX, sendnativeexplicitly.
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 againinherit() 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.
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.
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.
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().
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-20approve(address,uint256)on that token first, confirmed before the swap is signed — without it the swap reverts and burns the gas. And anamountOutMinof0accepts 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));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)));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.
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.
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.
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.
SDKs for other languages are listed at docs-sdk.crypto-chief.com/processing/processing.
MIT © 2026 Crypto Chief