Search by

amashukov / blockchain-context-bundle

Andrey Mashukov

Symfony bundle providing typed Bitcoin Core, TON and EVM integration services.

Package info

github.com/AndreyMashukov/blockchain-context-bundle

Type:symfony-bundle

pkg:composer/amashukov/blockchain-context-bundle

Statistics

Installs: 10

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v0.10.0 2026-11-03 07:41 UTC

README

Symfony 7 bundle for crypto payments — provides typed Bitcoin Core, Bitcoin Cash Node, TON and EVM clients alongside signature, finality and transaction-building services.

CI PHPStan L9 Latest Version Downloads PHP License Stars

amashukov/blockchain-context-bundle is a Symfony 7 bundle for crypto payments that exposes Bitcoin Core JSON-RPC together with the pure-PHP TON + EVM SDKs (amashukov/ton-php + amashukov/eth-php) through one autowired blockchain surface.

Features

  • Autowired RPC clients — a typed BitcoinRpcClient, Amashukov\EthRpc\JsonRpcProvider / EthRpcClient, and Amashukov\Toncenter\ToncenterClient, wired over PSR-18 transports.
  • Exact Bitcoin amounts — Bitcoin Core JSON numbers are preserved as decimal strings and converted to satoshis without floating-point arithmetic.
  • Signing & wallet — Amashukov\Eip1559TxSigner\Eip1559Signer (EIP-1559 offline signer) and Amashukov\TonWallet\WalletV4R2 (built from a mnemonic via the bundle factory); ToncenterWalletRpc implements the wallet's RPC port.
  • Per-chain domain services (host-app-agnostic, owning ports the host implements): Detection\ChainDepositCheckChain, Finality\{ConfirmationCheckChain,ConfirmationCounterRegistry,DepthPollingFinalityVerifier}, Gas\{EthGasFetcher,TonGasFetcher,ZeroGasFetcher}, TxBuilder\DepositTxBuilderChain (+ per-chain TON / TON-Jetton / ETH / USDT-ERC20 builders), Explorer\DefaultExplorerUrl, Numeric\{BcDecimal,UuidIntCodec,UsdtJettonDecimals}, Time\RealSleeper.
  • SignatureVerifier — EIP-191 (personal_sign, secp256k1 ecrecover + Keccak-256) + Ed25519 (TON Connect) verification.
  • PrivKeyEncrypter — AES-256-GCM authenticated encryption for keys at rest.
  • DepositWalletDeriverInterface port + DerivedWallet / DepositEvidence value objects.
  • HD deposit wallets — Service\Hd\EvmHdDeriver (BIP39 seed → BIP32 secp256k1, m/44'/60'/{account}'/0/{index}, lowercase 0x address) and Service\Hd\TonHdDeriver (BIP39 seed → SLIP-0010 ed25519, m/44'/607'/{account}'/0'/{index}', WalletV4R2 non-bounceable UQ… address) implement DepositWalletDeriverInterface. One mnemonic serves any number of separate address spaces through the account argument; TonHdDeriver::wallet($index) returns the signing WalletV4R2. The TON keys come from a BIP39 seed, not from a TON-native mnemonic, so wallet apps cannot import them from the phrase — recovery goes through the deriver. Pinned against the official BIP32 / SLIP-0010 / BIP39 vectors and against wallets derived by ethers and @ton/ton.
  • BIP39 mnemonics — Service\Hd\Bip39Mnemonic::generate(int $words = 24) turns fresh random_bytes entropy into a standard English mnemonic with its checksum, fromEntropy() does the same for given entropy, and isValid() checks the words and the checksum. The official 2048-word English list ships in Resources/bip39/english.txt (SHA-256 2f5eed53a4727b4bf8880d8f3f199efc90e58503646d9ff8eff3a2ed3b24dbda). Pinned against the official BIP39 test vectors. Feed the phrase to Bip39Seed, EvmHdDeriver or TonHdDeriver.
  • Tagged-iterator chains — ChainDepositCheckChain, ConfirmationCheckChain, ConfirmationCounterRegistry, and DepositTxBuilderChain auto-collect their members via bundle-namespaced tags (blockchain_context.*); drop a new impl and it joins the chain with no DI edits.

Why amashukov/blockchain-context-bundle

There is no maintained Symfony bundle for crypto-payment infrastructure — most projects glue raw RPC clients into services by hand. This bundle wires Bitcoin Core, TON and EVM services into Symfony's container with autowiring, env-agnostic config, and a tagged-iterator extension model.

Installation

composer require amashukov/blockchain-context-bundle

Register the bundle (Symfony Flex does this automatically):

// config/bundles.php
return [
    // ...
    Amashukov\BlockchainContextBundle\BlockchainContextBundle::class => ['all' => true],
];

Usage

Inject any autowired service directly:

use Amashukov\EthRpc\JsonRpcProviderInterface;
use Amashukov\Toncenter\ToncenterClientInterface;
use Amashukov\BlockchainContextBundle\Service\Bitcoin\BitcoinRpcClientInterface;
use Amashukov\BlockchainContextBundle\Service\SignatureVerifier;

final class SomeService
{
    public function __construct(
        private JsonRpcProviderInterface $eth,
        private ToncenterClientInterface $ton,
        private BitcoinRpcClientInterface $bitcoin,
        private SignatureVerifier $verifier,
    ) {}
}

Bitcoin transaction timestamps

Bitcoin wallet transaction DTOs expose optional blockTime (mined block timestamp) and timeReceived (wallet first receipt). Missing fields remain null; malformed, negative, fractional and overflowing timestamps fail decoding. These are separate evidence clocks, not an inferred payment timestamp. Existing constructor calls remain compatible.

Configuration

The bundle is env-agnostic: it exposes a config tree and reads only %blockchain_context.*% parameters internally. The host maps them to its own environment. Config is split per chain (bitcoin: / eth: / ton:), each toggleable via enabled:

# config/packages/blockchain_context.yaml
blockchain_context:
    bitcoin:
        enabled:         true
        rpc_url:        '%env(BITCOIN_RPC_URL)%'
        rpc_user:       '%env(BITCOIN_RPC_USER)%'
        rpc_password:   '%env(BITCOIN_RPC_PASSWORD)%'
        timeout_seconds: 30
        explorer:       '%env(BITCOIN_EXPLORER)%'           # default https://mempool.space; '' disables links (regtest)
    eth:
        enabled:            true
        rpc_url:            '%env(ETH_RPC_URL)%'                  # EVM JSON-RPC endpoint
        chain_id:          '%env(int:BRIDGE_ETH_CHAIN_ID)%'
        wallet_private_key:'%env(BRIDGE_ETH_WALLET_PRIVATE_KEY)%'
        usdt_token_address:'%env(USDT_TOKEN_ADDRESS)%'           # USDT-ERC20 deposits
        explorer:          '%env(BRIDGE_ETH_EXPLORER)%'          # default https://etherscan.io
    ton:
        enabled:            true
        toncenter_api_key: '%env(TONCENTER_API_KEY)%'            # optional — lifts toncenter rate limit
        wallet_mnemonic:   '%env(BRIDGE_TON_WALLET_MNEMONIC)%'
        bridge_contract:   '%env(BRIDGE_TON_CONTRACT)%'          # USDT-Jetton deposits
        finality_polls:    '%env(int:TON_FINALITY_POLLS)%'       # 0 = skip depth re-poll
        timeout_seconds:   '%env(int:TON_HTTP_TIMEOUT_SECONDS)%'  # toncenter response timeout, default 60
        connect_timeout_seconds: '%env(int:TON_HTTP_CONNECT_TIMEOUT_SECONDS)%' # default 60
        explorer:          '%env(TON_EXPLORER)%'                 # default https://tonscan.org
    deposit_wallet_encryption_key: '%env(DEPOSIT_WALLET_ENCRYPTION_KEY)%' # base64 of 32 bytes

Every key is optional (sensible empty / 0 / explorer defaults), so a host that uses only one chain can leave the other section's values unset (or enabled: false).

Requirements

  • PHP 8.3+
  • Symfony 7.x (symfony/framework-bundle)
  • ext-gmp, ext-sodium, ext-openssl, ext-bcmath

Related packages

Package Layer
amashukov/ton-php Umbrella TON SDK (Cell/BOC, wallet, toncenter)
amashukov/eth-php Umbrella EVM SDK (Keccak, secp256k1, RLP, EIP-1559, ABI, RPC)
amashukov/http-client-php PSR-18 cURL HTTP client
amashukov/eth-rpc-client-php ethers.js v6-style JSON-RPC client
amashukov/toncenter-client-php Typed toncenter v2 client

Quality

  • PHPStan level 9 across src/.
  • php-cs-fixer with the @PER-CS ruleset.
  • GitHub Actions CI on every push.
composer install
composer test     # PHPUnit
composer stan     # PHPStan (level 9)
composer cs       # php-cs-fixer (dry-run)
composer rector   # Rector (dry-run)

License

MIT — see LICENSE.

Bitcoin Cash Node

BitcoinCashNodeRpcClient implements the same BitcoinRpcClientInterface on top of amashukov/bchn-rpc-client-php, so code written for Bitcoin Core can drive a Bitcoin Cash Node wallet (tested against BCHN v29.2.0):

  • answers come back in the Core shape — decodeRawTransaction() reports vsize = size and weight = 4 × size (no SegWit), an output with exactly one address gets scriptPubKey.address, OP_RETURN outputs keep no address;
  • walletProcessPsbt() signs with ALL|FORKID; createPsbt() builds from explicit inputs and outputs;
  • estimateSmartFee() reads estimatefee (BCH per kB);
  • listUnspent(..., includeTokens: true) returns CashToken outputs with tokenData;
  • lockUnspent() has no persistent flag on BCHN — locks are lost when the node restarts, so keep your own record of reserved inputs;
  • walletCreateFundedPsbt() and psbtBumpFee() throw: BCHN keeps the first-seen transaction, there is no fee bumping.

Wire it as a second service next to the Core client:

Amashukov\BchnRpc\BchnRpcClient:
    arguments:
        $rpcUrl: '%env(BCH_RPC_URL)%'
        $rpcUser: '%env(BCH_RPC_USER)%'
        $rpcPassword: '%env(BCH_RPC_PASSWORD)%'
Amashukov\BlockchainContextBundle\Service\Bitcoin\BitcoinCashNodeRpcClient:
    arguments:
        $node: '@Amashukov\BchnRpc\BchnRpcClient'

createPsbt() and getTxOut() are available on the Core client as well; BitcoinAddressInfo::hdKeyPath, BitcoinWalletInfo::hdSeedId and BitcoinUnspentOutput::tokenData are optional fields (null on Core where it has no such data).

Bitcoin transaction fee evidence

BitcoinRpcClientInterface::getRawTransaction($txid, $blockHash) uses Core verbosity 2 and returns typed inputs, outputs and block identity. BitcoinRawTransaction::fee is the exact complete input-minus-output sum; missing previous outputs or coinbase inputs leave it null. A proven zero stays zero. A conflicting reported fee or a returned transaction/block identity mismatch throws. Callers must check active-chain evidence and classify payer ownership separately. The whole transaction fee is not additive across multiple deposit outputs. Existing wallet transaction fee defaults are unchanged and must not be treated as proof of an external payer’s fee.