Appearance
Non-EVM paths
Solana and TRON are supported for swaps and transfers, but neither goes through BlazpayRelayer. They are genuinely separate code paths with separate wallet stacks and separate failure modes.
Internal chain ids
Blazpay uses synthetic ids for the non-EVM chains, because both frontend and backend key chain records on a numeric id:
| Chain | Internal id | Real identifier |
|---|---|---|
| Solana | 102 | Not an EVM chain id |
| TRON | 728126428 | TRON's own mainnet id |
These are not real chain ids
102 is arbitrary. Anything that treats a chain id as EVM-shaped — an eth_chainId comparison, a block explorer URL builder, a relayer lookup — breaks on these two. chainPreselect.ts explicitly resolves Solana and TRON by name, not by id, for exactly this reason.
trade.ts translates on the way out: fromChainId = fromChainId === '102' ? 'solana' : fromChainId.
Wallet stacks
| Family | Library | Wallets |
|---|---|---|
| EVM | @web3modal/ethers5 | 100+ via Web3Modal — MetaMask, Trust, Coinbase, … |
| Solana | @solana/web3.js, @solana/spl-token | Phantom |
| TRON | tronweb, @tronweb3/tronwallet-adapters, @tronweb3/walletconnect-tron | TronLink, WalletConnect |
ChainContext in defi-dex/src/contexts/ holds the active family. Connecting one disconnects the others — there is exactly one active wallet at a time. The AI shell's WalletControl pill exposes all three; the in-chat connect_wallet card is EVM-only.
TRON
Provider access
TradeManager in defi-dex/src/utils/trade.ts reads window.tronWeb directly:
ts
this.tronweb = window.tronWeb
this.tronFeeLimit = 1_000_000_000
this.senderTronNitro = window?.tronWeb?.defaultAddress.base58 || nullsetSenderTronNitro(address) updates it when the wallet changes.
Address encoding
TRON addresses are base58 (T…) but contracts and calldata use a hex form prefixed with 41. The conversion appears throughout:
ts
this.tronweb.address.fromHex('41' + hexAddress.substring(2))Every token address, spender address and refund address crossing the boundary needs it. Getting it wrong produces a valid-looking address that is not the intended one — the most common class of TRON bug here.
Approvals
TRON has its own allowance path, setAllowanceTron:
ts
const encodedTokenAddress = this.tronweb.address.fromHex('41' + token.substring(2))
const encodedApprovalAddress = this.tronweb.address.fromHex('41' + spender.substring(2))
const contract = await this.tronweb.contract(abi, encodedTokenAddress)
await contract.allowance(this.senderTronNitro, encodedApprovalAddress).call()The spender here is the aggregator's own router, not a Blazpay relayer — because there is no relayer on TRON.
Execution
sendTransacionRawTron (the typo is in the source) builds and signs directly:
ts
const txn = await this.tronweb.transactionBuilder.triggerSmartContract(
contractAddress, functionSelector, { feeLimit: this.tronFeeLimit, … },
parameters, this.tronweb.defaultAddress.base58,
)data.type === 'tron' is the branch discriminator in trade.ts.
Consequences of skipping the relayer
| Protection | EVM | TRON |
|---|---|---|
| 0.1% platform fee | Collected | Not collected |
| Allowance reset after the call | Yes | No |
| Partial-fill token refund | Yes | No |
| Native overpayment refund | Yes | No |
| Replay nonce | Yes | N/A (TRON's own model) |
| Route attestation by platform signature | Yes | No |
TRON trades are unmonetised and unprotected
There is no fee, and none of v2's refund or allowance-hygiene protections apply. Whether that is acceptable is a product question; it is worth knowing that "0.1% flat on every trade" is not literally true for TRON.
Solana
Libraries
@solana/web3.js for transactions and @solana/spl-token for token accounts. Both are imported at the top of trade.ts.
Routing
Providers that support Solana handle it inside their own aggregator class, and trade.ts maps the internal id to the string solana before calling them. There is no Solana equivalent of the relayer, so the aggregator's returned transaction is signed and sent by the wallet directly.
Associated token accounts
The SPL model requires an associated token account per (owner, mint) pair, and it may not exist. A Solana swap into a token a user has never held needs the ATA created — which costs rent and can be an extra instruction in the same transaction. This is the main structural difference from EVM, where a balance simply appears.
Sends
The SEND_TOKEN intent supports Solana natively (native SOL and SPL tokens), as does the trading app.
Nitro's TRON special case
Base in swap-sdk carries senderTronNitro and tronFeeLimit as first-class fields, because Router Protocol's Nitro aggregator needed a distinct TRON sender address alongside the EVM one. Nitro is currently disabled (its mainnet pathfinder hostnames were retired), but the fields remain and other providers use them.
refundAddress for TRON routes is derived the same way:
ts
refundAddress: this?.senderTronNitro
? this.tronweb?.address?.fromHex('41' + this.senderTronNitro?.substring(2))
: …Deep-link preselect
src/utils/chainPreselect.ts resolves ?chain=solana and ?chain=tron by name:
resolveAppChain(slug) → matches the backend chain by chainId OR name
buildNativeToken(chain) → synthesises the native token recordThe chain landing pages for Solana and TRON exist in chainSeo.mjs (slug: 'solana', slug: 'tron') and link with ?chain={slug} like every other chain.
Portfolio and history
| Concern | Solana / TRON status |
|---|---|
| Portfolio balances | Depends on Zerion's coverage of the chain |
| Transaction records | Written to defiTransactions like EVM trades |
| Status polling | Uses the aggregator's own getTxStatus |
| Explorer links | Need chain-specific URL builders, not the EVM pattern |
Known limits
- No relayer, therefore no fee and no v2 protections. See the table above.
- No CSIP or Autopilot support. Both vaults are EVM contracts on Arbitrum. Automation is EVM-only.
connect_walletin chat is EVM-only. Solana and TRON users must connect through the pill, which is why the pill is not optional on the AI site.- The
102id leaks. Any code path assuming EVM chain-id semantics has to special-case it, and not all of them do. - TRON address encoding is manual. There is no single helper; the
'41' + substring(2)pattern is repeated at each site. sendTransacionRawTronis misspelled in the source.