By using this site, you agree to the Privacy Policy and Terms of Use.
Accept
World of SoftwareWorld of SoftwareWorld of Software
  • News
  • Software
  • Mobile
  • Computing
  • Gaming
  • Videos
  • More
    • Gadget
    • Web Stories
    • Trending
    • Press Release
Search
  • Privacy
  • Terms
  • Advertise
  • Contact
Copyright © All Rights Reserved. World of Software.
Reading: How to Build Real-World Web3 Infrastructure Using Symfony 7.4 | HackerNoon
Share
Sign In
Notification Show More
Font ResizerAa
World of SoftwareWorld of Software
Font ResizerAa
  • Software
  • Mobile
  • Computing
  • Gadget
  • Gaming
  • Videos
Search
  • News
  • Software
  • Mobile
  • Computing
  • Gaming
  • Videos
  • More
    • Gadget
    • Web Stories
    • Trending
    • Press Release
Have an existing account? Sign In
Follow US
  • Privacy
  • Terms
  • Advertise
  • Contact
Copyright © All Rights Reserved. World of Software.
World of Software > Computing > How to Build Real-World Web3 Infrastructure Using Symfony 7.4 | HackerNoon
Computing

How to Build Real-World Web3 Infrastructure Using Symfony 7.4 | HackerNoon

News Room
Last updated: 2025/12/16 at 6:55 AM
News Room Published 16 December 2025
Share
How to Build Real-World Web3 Infrastructure Using Symfony 7.4 | HackerNoon
SHARE

The narrative that “PHP is dead” has been wrong for a decade. The narrative that “PHP can’t do Web3” is just as incorrect.

While Node.js dominates the frontend dApp ecosystem, PHP and Symfony are quietly powering the heavy lifting of the decentralized web: indexing off-chain data, managing private key orchestration for enterprise wallets, and bridging the gap between Web2 business logic and Web3 protocols.

In this guide, we will build a production-ready Web3 integration using Symfony 7.4 and PHP 8.3+. We won’t use obscure, unmaintained wrappers. We will use the industry-standard libraries to read the blockchain, interact with smart contracts, and implement a Sign-In with Ethereum (SIWE) authentication system using Symfony’s security core.

The Stack & Prerequisites

We are simulating a real-world environment. We will assume you are running Symfony 7.4 (the LTS release as of late 2025).

Requirements

  • PHP 8.3 or higher (with gmp and bcmath extensions enabled).
  • Symfony 7.4 CLI.
  • Composer.
  • An Ethereum Node URL (Infura, Alchemy, or a local Hardhat/Anvil node).

Library Selection

We will use the following strictly typed, verified libraries:

  1. web3p/web3.php: The foundational library for JSON-RPC communication.
  2. kornrunner/keccak: For Keccak-256 hashing (standard in Ethereum).
  3. simplito/elliptic-php: For cryptographic signature verification (essential for SIWE).

Installation

Create your project and install dependencies. Note that we explicitly allow web3p/web3.php to interface with modern Guzzle versions if needed.

composer create-project symfony/website-skeleton my_web3_app
cd my_web3_app

# Install the Web3 standard library
composer require web3p/web3.php:^0.3

# Install crypto utilities for signature verification
composer require kornrunner/keccak:^1.1 simplito/elliptic-php:^1.0

# Install the Maker bundle for rapid prototyping
composer require --dev symfony/maker-bundle

Infrastructure: The Ethereum Client Service

Directly instantiating libraries in controllers is an anti-pattern. We will wrap the Web3 connection in a robust Symfony Service using Dependency Injection.

First, configure your node URL in .env:

# .env
ETHEREUM_NODE_URL="https://mainnet.infura.io/v3/YOUR_INFURA_ID"

Now, create the service. We use PHP 8.2 Readonly Classes and Constructor Promotion for clean architecture.

// src/Service/Web3Client.php
namespace AppService;

use Web3Web3;
use Web3Eth;
use Web3Contract;
use Web3ProvidersHttpProvider;
use Web3RequestManagersHttpRequestManager;
use SymfonyComponentDependencyInjectionAttributeAutowire;

readonly class Web3Client
{
    private Web3 $web3;

    public function __construct(
        #[Autowire(env: 'ETHEREUM_NODE_URL')]
        private string $nodeUrl
    ) {
        // We utilize a timeout of 10 seconds for RPC calls
        $provider = new HttpProvider(new HttpRequestManager($this->nodeUrl, 10));
        $this->web3 = new Web3($provider);
    }

    public function getEth(): Eth
    {
        return $this->web3->eth;
    }

    public function getContract(string $abi, string $address): Contract
    {
        return new Contract($this->web3->provider, $abi);
    }
}

Reading State: Balance Checker

Let’s verify our connection by reading the native ETH balance of an address.

Note on Asynchrony: web3p/web3.php uses callbacks by default. To make this compatible with Symfony’s synchronous request/response lifecycle, we wrap the callback in a simple latch or use the returned promise if available. For simplicity and reliability in this version, we will use a referenced variable capture method which is the standard pattern for this library in PHP 8.

// src/Controller/WalletController.php
namespace AppController;

use AppServiceWeb3Client;
use SymfonyBundleFrameworkBundleControllerAbstractController;
use SymfonyComponentHttpFoundationJsonResponse;
use SymfonyComponentRoutingAttributeRoute;
use Web3Utils;

#[Route('/api/wallet')]
class WalletController extends AbstractController
{
    public function __construct(private Web3Client $web3Client) {}

    #[Route('/balance/{address}', name: 'app_wallet_balance', methods: ['GET'])]
    public function balance(string $address): JsonResponse
    {
        $balance = null;
        $error = null;

        // Fetch balance via JSON-RPC
        $this->web3Client->getEth()->getBalance($address, function ($err, $data) use (&$balance, &$error) {
            if ($err !== null) {
                $error = $err;
                return;
            }
            $balance = $data;
        });

        if ($error) {
            return $this->json(['error' => $error->getMessage()], 500);
        }

        // Convert BigInteger to Ether string
        // web3p returns PHP GMP/BigInteger objects
        $ethBalance = Utils::fromWei($balance, 'ether');
        [$whole, $decimals] = $ethBalance; 

        return $this->json([
            'address' => $address,
            'balance_wei' => (string) $balance,
            'balance_eth' => $whole . '.' . $decimals,
        ]);
    }
}

Start your server (symfony server:start) and visit https://localhost:8000/api/wallet/balance/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 (Vitalik’s address). You should see a JSON response with his current balance.

Smart Contract Interaction (ERC-20)

Reading ETH is easy. Reading a token balance (like USDC) requires the ABI (Application Binary Interface).

We will create a Service method to read any ERC-20 balance.

// src/Service/TokenService.php
namespace AppService;

use Web3Contract;
use Web3Utils;

class TokenService
{
    // Minimal ERC-20 ABI for 'balanceOf'
    private const ERC20_ABI = '[{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"type":"function"}]';

    public function __construct(private Web3Client $web3Client) {}

    public function getBalance(string $tokenAddress, string $walletAddress): string
    {
        $contract = $this->web3Client->getContract(self::ERC20_ABI, $tokenAddress);

        $resultBalance = null;

        // The "at" method sets the contract address for the call
        $contract->at($tokenAddress)->call('balanceOf', $walletAddress, function ($err, $result) use (&$resultBalance) {
            if ($err !== null) {
                throw new RuntimeException($err->getMessage());
            }
            // Result is an array based on outputs in ABI
            $resultBalance = $result['balance'];
        });

        // Assuming 18 decimals for standard ERC-20
        // In production, you should fetch the 'decimals' function from the contract first
        $formatted = Utils::fromWei($resultBalance, 'ether'); 
        return $formatted[0] . '.' . $formatted[1];
    }
}

Security: Sign-In with Ethereum (SIWE)

This is the most critical part of Web3 UX. We do not want users to create passwords. We want them to sign a message with their wallet (Metamask, Rabby, etc.) to prove ownership.

The Logic:

  1. Frontend requests a “nonce” (a random string) from Symfony.
  2. Frontend signs a message: “I am signing into MyApp with nonce: X”.
  3. Frontend sends the address, signature and message to Symfony.
  4. Symfony cryptographically recovers the public key from the signature.
  5. If the recovered address matches the claimed address, the user is authenticated.

The Cryptographic Verifier

We need a helper to perform ecrecover. PHP does not have this built-in easily, so we use simplito/elliptic-php and kornrunner/keccak.

// src/Security/Web3/SignatureVerifier.php
namespace AppSecurityWeb3;

use EllipticEC;
use kornrunnerKeccak;

class SignatureVerifier
{
    public function verifySignature(string $message, string $signature, string $address): bool
    {
        // 1. Hash the message according to Ethereum standard (EIP-191)
        $prefix = sprintf("x19Ethereum Signed Message:n%d", strlen($message));
        $hash = Keccak::hash($prefix . $message, 256);

        // 2. Parse Signature (Remove 0x, split into r, s, v)
        $signature = substr($signature, 2);
        $r = substr($signature, 0, 64);
        $s = substr($signature, 64, 64);
        $v = hexdec(substr($signature, 128, 2));

        // Adjust v for recovery (Ethereum uses 27/28, library expects 0/1)
        $recId = $v - 27; 
        if ($recId < 0 || $recId > 1) {
            return false;
        }

        // 3. Recover Public Key
        $ec = new EC('secp256k1');
        try {
            $pubKey = $ec->recoverPubKey($hash, ['r' => $r, 's' => $s], $recId);
        } catch (Exception $e) {
            return false;
        }

        // 4. Derive Address from Public Key
        // Drop first byte (04 prefix), hash the rest, take last 20 bytes
        $pubKeyHex = $pubKey->encode('hex');
        $pubKeyHex = substr($pubKeyHex, 2); 
        $addressHash = Keccak::hash(hex2bin($pubKeyHex), 256);
        $recoveredAddress="0x" . substr($addressHash, -40);

        // 5. Compare (Case insensitive)
        return strtolower($address) === strtolower($recoveredAddress);
    }
}

The Symfony Authenticator

Now we implement the Symfony 7 AbstractAuthenticator.

// src/Security/Web3Authenticator.php
namespace AppSecurity;

use AppRepositoryUserRepository;
use AppSecurityWeb3SignatureVerifier;
use SymfonyComponentHttpFoundationJsonResponse;
use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;
use SymfonyComponentSecurityCoreAuthenticationTokenTokenInterface;
use SymfonyComponentSecurityCoreExceptionAuthenticationException;
use SymfonyComponentSecurityHttpAuthenticatorAbstractAuthenticator;
use SymfonyComponentSecurityHttpAuthenticatorPassportBadgeUserBadge;
use SymfonyComponentSecurityHttpAuthenticatorPassportPassport;
use SymfonyComponentSecurityHttpAuthenticatorPassportSelfValidatingPassport;

class Web3Authenticator extends AbstractAuthenticator
{
    public function __construct(
        private SignatureVerifier $verifier,
        private UserRepository $userRepository
    ) {}

    public function supports(Request $request): ?bool
    {
        return $request->isMethod('POST') && $request->getPathInfo() === '/api/login_web3';
    }

    public function authenticate(Request $request): Passport
    {
        $data = json_decode($request->getContent(), true);
        $address = $data['address'] ?? '';
        $message = $data['message'] ?? ''; // Contains the nonce
        $signature = $data['signature'] ?? '';

        if (!$address || !$message || !$signature) {
            throw new AuthenticationException('Missing Web3 credentials.');
        }

        // Verify the signature matches the address
        if (!$this->verifier->verifySignature($message, $signature, $address)) {
            throw new AuthenticationException('Invalid signature.');
        }

        // Check nonce (Optional but recommended: Verify nonce exists in session/cache)
        // $storedNonce = $request->getSession()->get('login_nonce');
        // if (!str_contains($message, $storedNonce)) throw ...

        return new SelfValidatingPassport(
            new UserBadge($address, function ($userIdentifier) {
                // Find user by wallet address or create new one
                return $this->userRepository->findOrCreateByWallet($userIdentifier);
            })
        );
    }

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $firewallName): ?Response
    {
        return new JsonResponse(['message' => 'Welcome to Web3', 'user' => $token->getUser()->getUserIdentifier()]);
    }

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception): ?Response
    {
        return new JsonResponse(['error' => $exception->getMessage()], 401);
    }
}

Advanced: Indexing Events with Symfony Messenger

Web3 is often about reacting to things happening off-chain. You shouldn’t make your user wait while you query the blockchain. Instead, use a worker.

We will create a command that polls for “Transfer” events and dispatches them to the Messenger bus.

// src/Command/BlockchainListenerCommand.php
namespace AppCommand;

use AppServiceWeb3Client;
use SymfonyComponentConsoleAttributeAsCommand;
use SymfonyComponentConsoleCommandCommand;
use SymfonyComponentConsoleInputInputInterface;
use SymfonyComponentConsoleOutputOutputInterface;

#[AsCommand(name: 'app:blockchain:listen', description: 'Polls for ERC20 Transfer events')]
class BlockchainListenerCommand extends Command
{
    public function __construct(private Web3Client $web3Client)
    {
        parent::__construct();
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $contractAddress="0x..."; // USDC or your token
        $transferTopic="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; // Keccak('Transfer(address,address,uint256)')

        $output->writeln("Listening for events on $contractAddress...");

        // In a real app, you would store the 'last_scanned_block' in a DB
        $currentBlock = 'latest'; 

        // Uses eth_getLogs
        $this->web3Client->getEth()->getLogs([
            'address' => $contractAddress,
            'topics' => [$transferTopic],
            'fromBlock' => '0x' . dechex(20000000) // Hex block number
        ], function ($err, $logs) use ($output) {
            if ($err) {
                $output->writeln("Error: " . $err->getMessage());
                return;
            }

            foreach ($logs as $log) {
                // Dispatch to Symfony Messenger here
                $output->writeln("Transfer detected in transaction: " . $log->transactionHash);
            }
        });

        return Command::SUCCESS;
    }
}

Note: In production, you would run this command inside a supervisord loop or cron, maintaining state of the last scanned block to ensure no events are missed.

Conclusion

We have successfully bridged the gap. You now have a Symfony 7.4 application that can:

  1. Read direct blockchain state via JSON-RPC.
  2. Decode smart contract data (ERC-20).
  3. Authenticate users securely using their Ethereum wallets (SIWE) without passwords.
  4. Listen for on-chain events via CLI commands.

Web3 is not about rewriting your entire stack in Solidity or Rust. It’s about orchestration. Symfony is the perfect orchestrator — stable, secure and typed.

Ready to Tokenize Your Enterprise?

If you are looking to integrate high-value assets onto the blockchain or need a secure audit of your current Web3-PHP architecture, I can help.

Contact me to discuss your Web3 Strategy https://www.linkedin.com/in/matthew-mochalkin/

Sign Up For Daily Newsletter

Be keep up! Get the latest breaking news delivered straight to your inbox.
By signing up, you agree to our Terms of Use and acknowledge the data practices in our Privacy Policy. You may unsubscribe at any time.
Share This Article
Facebook Twitter Email Print
Share
What do you think?
Love0
Sad0
Happy0
Sleepy0
Angry0
Dead0
Wink0
Previous Article Survey shows you’re very keen on this smartphone that doesn’t run Android (or iOS) Survey shows you’re very keen on this smartphone that doesn’t run Android (or iOS)
Next Article Trump picks fight with MAGA allies in issuing AI executive order Trump picks fight with MAGA allies in issuing AI executive order
Leave a comment

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

Stay Connected

248.1k Like
69.1k Follow
134k Pin
54.3k Follow

Latest News

Beyond PoR: Phemex CEO on Building Structural Trust for the Next Decade of Crypto | HackerNoon
Computing
You can now build AI-powered mini apps directly from Gemini
You can now build AI-powered mini apps directly from Gemini
News
TikTok Shop replaces US with Chinese managers as US sales fall short of target · TechNode
TikTok Shop replaces US with Chinese managers as US sales fall short of target · TechNode
Computing
Got a &apos;Stranger Things&apos; Superfan in Your Life? Check Out the Best Holiday Gifts for 2025
Got a 'Stranger Things' Superfan in Your Life? Check Out the Best Holiday Gifts for 2025
News

You Might also Like

Beyond PoR: Phemex CEO on Building Structural Trust for the Next Decade of Crypto | HackerNoon

12 Min Read
TikTok Shop replaces US with Chinese managers as US sales fall short of target · TechNode
Computing

TikTok Shop replaces US with Chinese managers as US sales fall short of target · TechNode

4 Min Read
The Enterprise Architecture for Scaling Generative AI | HackerNoon
Computing

The Enterprise Architecture for Scaling Generative AI | HackerNoon

7 Min Read
China sees 400% y-o-y growth in humanoid robotics hiring in first five months of 2025 · TechNode
Computing

China sees 400% y-o-y growth in humanoid robotics hiring in first five months of 2025 · TechNode

1 Min Read
//

World of Software is your one-stop website for the latest tech news and updates, follow us now to get the news that matters to you.

Quick Link

  • Privacy Policy
  • Terms of use
  • Advertise
  • Contact

Topics

  • Computing
  • Software
  • Press Release
  • Trending

Sign Up for Our Newsletter

Subscribe to our newsletter to get our newest articles instantly!

World of SoftwareWorld of Software
Follow US
Copyright © All Rights Reserved. World of Software.
Welcome Back!

Sign in to your account

Lost your password?