Skip to content

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:

ChainInternal idReal identifier
Solana102Not an EVM chain id
TRON728126428TRON'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

FamilyLibraryWallets
EVM@web3modal/ethers5100+ via Web3Modal — MetaMask, Trust, Coinbase, …
Solana@solana/web3.js, @solana/spl-tokenPhantom
TRONtronweb, @tronweb3/tronwallet-adapters, @tronweb3/walletconnect-tronTronLink, 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 || null

setSenderTronNitro(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

ProtectionEVMTRON
0.1% platform feeCollectedNot collected
Allowance reset after the callYesNo
Partial-fill token refundYesNo
Native overpayment refundYesNo
Replay nonceYesN/A (TRON's own model)
Route attestation by platform signatureYesNo

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))
  :

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 record

The 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

ConcernSolana / TRON status
Portfolio balancesDepends on Zerion's coverage of the chain
Transaction recordsWritten to defiTransactions like EVM trades
Status pollingUses the aggregator's own getTxStatus
Explorer linksNeed 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_wallet in 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 102 id 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.
  • sendTransacionRawTron is misspelled in the source.

Internal + developer documentation. Usage figures are point-in-time snapshots — re-verify before publishing them.