Official PHP SDK for the Crypto Chief crypto processing API. Accept crypto payments, send single and mass payouts, sign and broadcast EVM / TRON / Solana / TON / XRP transactions, encode contract calls, manage wallets, and verify webhooks.
- 25 chains across EVM, TRON, Solana, TON, XRP, and the BTC family
- Single + batch payouts, auto-convert swaps, two-phase sign / execute, static deposits, pay-ins, sweeps, withdrawals, fiat ↔ crypto conversion
- High-level helpers: ERC-20 / TRC-20 transfers, ABI-encoded EVM calls, Solana Anchor instructions, TON Jetton / NFT / text-comment transfers
- Local RSA-OAEP / SHA-256 decryption of generated wallet private keys
- Webhook verification + typed event parsing (framework-agnostic)
- PSR-18 HTTP client support (Guzzle by default), strict types, readonly DTOs, backed enums
composer require crypto-chiefs/cryptochief-crypto-processing-phpRequires PHP 8.1+ with the bcmath, mbstring, openssl, and json extensions.
use CryptoChief\Processing\Chain;
use CryptoChief\Processing\Client;
use CryptoChief\Processing\Dto\EstimatePayoutRequest;
use CryptoChief\Processing\Dto\ExecutePayoutRequest;
$client = new Client(
merchantId: 'YOUR_MERCHANT_ID',
apiKey: 'YOUR_API_KEY',
);
// 1. Preview fees
$estimate = $client->payouts()->estimate(new EstimatePayoutRequest(
network: Chain::EthSepolia->value,
coin: 'ETH',
amount: '0.0001',
toAddress: '0xRecipient...',
));
echo "Will receive: {$estimate->amountToReceive}\n";
// 2. Execute - idempotent on orderId
$payout = $client->payouts()->execute(new ExecutePayoutRequest(
network: Chain::EthSepolia->value,
coin: 'ETH',
amount: '0.0001',
toAddress: '0xRecipient...',
orderId: 'order-1234',
userId: 'user-42',
urlCallback: 'https://example.com/webhook',
));
// 3. Poll until terminal (or rely on the webhook)
$final = $client->payouts()->waitFor($payout->uuid);
echo "Status: {$final->status}, tx: {$final->txid}\n";use CryptoChief\Processing\Dto\BatchPayoutRequest;
$items = [];
foreach ($recipients as $i => [$to, $amount]) {
$items[] = new ExecutePayoutRequest(
network: Chain::EthSepolia->value,
coin: 'ETH',
amount: $amount,
toAddress: $to,
orderId: "batch-{$i}",
userId: "user-{$i}",
urlCallback: 'https://example.com/webhook',
);
}
$result = $client->payouts()->batchExecute(new BatchPayoutRequest(items: $items));
foreach ($result->items ?? [] as $row) {
echo $row->uuid ? "OK {$row->uuid}\n" : "FAIL {$row->error}\n";
}Funds lock sequentially inside a batch — an intra-batch double-spend cannot occur, even when the total exceeds your balance partway through. Max 50 items per call.
A pay-in is an invoice that gives your customer a deposit address (or hosted payment page) and notifies you over webhook when it's paid. Two modes:
- FIAT — you fix the price in fiat (
amountFiat+currency); the SDK locks the crypto rate at confirmation time. The customer picks a coin/network at checkout (filter the menu withassets). - CRYPTO — you fix the crypto amount and the asset up front (
amountCrypto+asset).
use CryptoChief\Processing\Client;
use CryptoChief\Processing\Dto\Asset;
use CryptoChief\Processing\Dto\AssetsPolicy;
use CryptoChief\Processing\Dto\CreatePayInRequest;
$invoice = $client->payIns()->create(new CreatePayInRequest(
orderId: 'order-' . bin2hex(random_bytes(6)),
userId: 'customer-42',
mode: 'fiat',
amountFiat: '25.00',
currency: 'USD',
lifetimeSec: 3600, // expires after 1 hour
urlCallback: 'https://example.com/cryptochief/webhook',
urlSuccess: 'https://example.com/thanks',
urlError: 'https://example.com/oops',
assets: new AssetsPolicy(
allow: [
new Asset(coin: 'USDT'), // any network
],
),
));
echo "Invoice: {$invoice->uuid}\n";
echo "Payment link: {$invoice->paymentLink}\n";The customer opens paymentLink and picks a coin. Once they do, the invoice transitions
out of waiting_asset_select and exposes toAddress + paymentCoin + paymentNetwork.
use CryptoChief\Processing\Chain;
use CryptoChief\Processing\Dto\Asset;
$invoice = $client->payIns()->create(new CreatePayInRequest(
orderId: 'order-' . bin2hex(random_bytes(6)),
userId: 'customer-42',
mode: 'crypto',
amountCrypto: '0.01',
asset: new Asset(
network: Chain::EthSepolia->value,
coin: 'ETH',
),
lifetimeSec: 1800,
urlCallback: 'https://example.com/cryptochief/webhook',
));
echo "Send {$invoice->amountCrypto} {$invoice->paymentCoin} to {$invoice->toAddress}\n";use CryptoChief\Processing\Dto\SelectAssetRequest;
// H2H integrations: commit the asset choice server-side.
$client->payIns()->selectAsset(new SelectAssetRequest(
uuid: $invoice->uuid,
coin: 'USDT',
network: Chain::TronMainnet->value,
));
// Poll until terminal (paid / cancel / expired) - or rely on the invoice.* webhook.
$final = $client->payIns()->waitFor($invoice->uuid, intervalSec: 5.0, timeoutSec: 1800.0);
echo "Status: {$final->status}\n";
// Cancel an open order before it's paid.
$client->payIns()->cancel($invoice->uuid);The SDK ABI-encodes calldata for you. No more 0xa9059cbb... by hand.
use CryptoChief\Processing\Amount;
use CryptoChief\Processing\Dto\Erc20TransferRequest;
// ERC-20 / TRC-20 one-liner
$signed = $client->transactions()->erc20Transfer(new Erc20TransferRequest(
network: Chain::TronMainnet->value,
fromAddress: 'TYourWallet...',
tokenContract: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', // USDT on TRON
recipient: 'TRecipient...',
amount: Amount::humanToBase('1.23', 6),
));Arbitrary Solidity calls — the SDK reads the signature, computes the Keccak-256 selector, encodes head + tail, and hands you the bytes:
use CryptoChief\Processing\Dto\EvmCallRequest;
$client->transactions()->signEvmCall(new EvmCallRequest(
network: Chain::EthMainnet->value,
fromAddress: '0xMerchantWallet',
contract: '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D', // Uniswap V2
method: 'swapExactTokensForTokens(uint256,uint256,address[],address,uint256)',
args: [$amountIn, $minOut, [$dai, $weth], $to, $deadline],
));Solana Anchor programs — Borsh has no on-wire type tags, so the SDK forces explicit
typing through Borsh::* constructors:
use CryptoChief\Processing\Contract\Borsh;
use CryptoChief\Processing\Dto\AnchorCallRequest;
use CryptoChief\Processing\Dto\SolanaAccount;
$client->transactions()->signAnchorCall(new AnchorCallRequest(
network: Chain::SolanaMainnet->value,
fromAddress: 'YourMerchantOwnedSolanaWallet',
program: 'YourAnchorProgramId',
method: 'initialize',
args: [
Borsh::u64(1_000_000),
Borsh::string('hello'),
],
accounts: [
new SolanaAccount(pubkey: $from, isSigner: true, isWritable: true),
],
));High-level helpers build the standard TEP-74 / TEP-62 / text-comment bodies. The
underlying BoC encoding is delegated to olifanton/interop. For arbitrary contracts use
signTonCall(TonCallRequest) with raw BoC bytes.
use CryptoChief\Processing\Amount;
use CryptoChief\Processing\Chain;
use CryptoChief\Processing\Dto\JettonTransferRequest;
use CryptoChief\Processing\Dto\NftTransferRequest;
use CryptoChief\Processing\Dto\TonCommentRequest;
// USDT on TON — auto-resolves the sender's jetton wallet, picks gas (0.07 or 0.15 TON).
$client->transactions()->jettonTransfer(new JettonTransferRequest(
network: Chain::TonMainnet->value,
fromAddress: 'EQYourTonWallet...',
recipient: 'EQRecipientMainWallet...',
amount: Amount::humanToBase('1.5', 6), // 1.5 USDT
jettonMaster: 'EQCxE6mUtQJKFnGfaROTKOt1lZbDiiX1kCixRv7Nw2Id_sDs', // USDT jetton master
memo: 'invoice #1234', // shown by every wallet
));
// NFT transfer (TEP-62)
$client->transactions()->nftTransfer(new NftTransferRequest(
network: Chain::TonMainnet->value,
fromAddress: 'EQYourTonWallet...',
nftItem: 'EQNftItemAddress...',
newOwner: 'EQNewOwnerAddress...',
));
// Send TON with a text comment
$client->transactions()->sendTonComment(new TonCommentRequest(
network: Chain::TonMainnet->value,
fromAddress: 'EQYourTonWallet...',
recipient: 'EQRecipient...',
text: 'thanks!',
amountTon: Amount::nanoTon('0.5'),
));For a pre-built BoC body (custom contracts), use signTonCall(TonCallRequest) directly
with raw bytes.
use CryptoChief\Processing\ChainFamily;
use CryptoChief\Processing\Dto\GenerateWalletRequest;
$client = new Client(
merchantId: 'M',
apiKey: 'K',
rsaPrivateKey: '/path/to/private.pem', // PEM string or path
);
$wallet = $client->wallets()->generate(new GenerateWalletRequest(
walletType: 'transit',
chainFamily: ChainFamily::Evm->value,
));
if ($wallet->privateKeyEncrypted !== null) {
// Decryption is local - the plaintext private key never leaves the process.
$key = $client->wallets()->decryptPrivateKey($wallet->privateKeyEncrypted);
}use CryptoChief\Processing\Exception\WebhookSignatureException;
use CryptoChief\Processing\Webhook;
use CryptoChief\Processing\Webhook\PayoutEvent;
$raw = file_get_contents('php://input') ?: ''; // raw bytes - never re-encode
$signature = $_SERVER['HTTP_SIGNATURE'] ?? null;
try {
$event = Webhook::parseEvent($apiKey, $raw, $signature);
} catch (WebhookSignatureException) {
http_response_code(401);
return;
}
if ($event instanceof PayoutEvent) {
// typed access: $event->uuid, $event->status, $event->amountToReceive, ...
}Laravel / Symfony are the same shape — pass the request's raw body to
Webhook::parseEvent(). Optionally restrict by source IP:
Webhook::SENDER_IPS lists the production webhook IP addresses.
Every SDK error extends CryptoChiefException, so a single catch covers the library.
API failures arrive as ApiException with a stable $errorCode you can branch on:
use CryptoChief\Processing\ErrorCode;
use CryptoChief\Processing\Exception\ApiException;
try {
$client->payouts()->execute($req);
} catch (ApiException $e) {
if ($e->errorCode === ErrorCode::InsufficientFunds->value) {
// top up and retry
}
}Only 5xx and network failures retry; 4xx is the caller's fault and surfaces immediately.
Crypto amounts are decimal strings end-to-end. float loses precision past 2^53 and
binary rounding bites large token values, so the SDK never uses it for amounts. Convert
between human and base units with Amount::humanToBase() / Amount::baseToHuman():
use CryptoChief\Processing\Amount;
Amount::humanToBase('1.5', 18); // "1500000000000000000"
Amount::baseToHuman('10000', 8); // "0.0001"
Amount::nanoTon('0.05'); // "50000000"$client = new Client(
merchantId: 'M',
apiKey: 'K',
baseUrl: Client::DEFAULT_BASE_URL, // override for staging
userAgent: 'my-app/1.0',
retries: 3,
timeoutSec: 60.0,
retryBaseMs: 200.0,
retryMaxMs: 5000.0,
httpClient: $myPsr18Client, // bring your own
rsaPrivateKey: '/path/to/private.pem',
);httpClient accepts any Psr\Http\Client\ClientInterface. The default is Guzzle 7.
- SDK docs: https://docs-sdk.crypto-chief.com/processing/php
- REST API reference: https://docs-processing.crypto-chief.com
- Product page: https://crypto-chief.com/processing/
SDKs for other languages live under the crypto-chiefs GitHub organization.
- How do I accept crypto payments in PHP? Open a pay-in via
$client->payIns()->create(new CreatePayInRequest(...)). The response carries thepaymentLink(and the address once the customer picks a coin). - How do I send mass payouts in PHP? Call
$client->payouts()->batchExecute(new BatchPayoutRequest(items: $items))with up to 50 recipients. Each item idempotent on itsorderId. - How do I send USDT (TRC-20 / ERC-20 / BEP-20) from PHP?
erc20Transfer()— the SDK encodestransfer(address,uint256)and handles TRON base58 addresses transparently. - How do I send Jettons (USDT on TON, etc.) from PHP?
jettonTransfer()— the SDK builds the TEP-74 body, auto-resolves the sender's Jetton wallet via the gateway's TON RPC proxy, and picks the gas budget. - How do I verify Crypto Chief webhooks in PHP?
Webhook::parseEvent($apiKey, $rawBody, $signature)— re-canonicalizes the body, MD5-verifies, and returns a typed event. - Does it work with Laravel / Symfony? Yes — the HTTP client is PSR-18 compatible and the webhook verifier takes raw bytes, so it slots into any framework's request body.
MIT — see LICENSE.