diff --git a/src/blockchain/chains.config.ts b/src/blockchain/chains.config.ts index 260c0f2..a209297 100644 --- a/src/blockchain/chains.config.ts +++ b/src/blockchain/chains.config.ts @@ -49,8 +49,8 @@ export const RAW_CHAIN_CONFIGS: RawChainConfig[] = [ type: ChainType.EVM, env: 'production', rpcUrl: 'https://mainnet.base.org', - portalAddress: '0x399Dbd5DF04f83103F77A58cBa2B7c4d3cdede97', // prod portal - provers: { LayerZero: '0x0C4E3063239c9f4f323A956C79738916594D8Fd4' }, // prod prover + portalAddress: '0xfD12115CD8F37C7667050eD8499EDa6B9d9c03bA', // prod portal + provers: { LayerZero: '0x3a572CfA867691e4D8bD19B80294f3c744a384E9' }, // prod prover nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, }, { @@ -108,7 +108,7 @@ export const RAW_CHAIN_CONFIGS: RawChainConfig[] = [ portalAddress: '0x399Dbd5DF04f83103F77A58cBa2B7c4d3cdede97', provers: { Hyperlane: '0x9523b6c0cAaC8122DbD5Dd1c1d336CEBA637038D', - LayerZero: '0x6D8D9E68627b8eb2D4A3c1110be3FE46Ff6e92A3', + LayerZero: '0x82d378D05271743d6C03fbBb108f981E39dd81a9', }, nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 }, }, @@ -156,8 +156,8 @@ export const RAW_CHAIN_CONFIGS: RawChainConfig[] = [ type: ChainType.TVM, env: 'production', rpcUrl: 'https://api.trongrid.io', - portalAddress: 'TTXNcSeX5aYb1ETWYjcX3fvumynWoyFgYw', // prod portal - provers: { LayerZero: 'TFu38RELzp7jdR9s7vj4JSpw2kFuTSAq3E' }, // prod prover + portalAddress: 'TMu3sz3aQqAQyvnPYrmDM9FeZGC3HdTLs7', // prod portal + provers: { LayerZero: 'TQUwftPRikD9ngiFs6MJpo7SKhSYsKmfye' }, // prod prover nativeCurrency: { name: 'Tron', symbol: 'TRX', decimals: 6 }, }, { @@ -166,8 +166,8 @@ export const RAW_CHAIN_CONFIGS: RawChainConfig[] = [ type: ChainType.TVM, env: 'development', rpcUrl: 'https://api.shasta.trongrid.io', - portalAddress: 'TScmM6ZoR6grho3pKCzX6M2MKBYVURG1s5', - provers: { LayerZero: 'TM6cLaN3LStBFi9AjrhLQ9cc6QiVu5nFsD' }, + portalAddress: 'TTaASiEw2Q7ZK9um2s1M7VswiSe2wxKRD3', + provers: { LayerZero: 'TSCJtdKaJBt8THXZbKRddsHytSghZebsHm' }, nativeCurrency: { name: 'Tron', symbol: 'TRX', decimals: 6 }, }, @@ -178,6 +178,8 @@ export const RAW_CHAIN_CONFIGS: RawChainConfig[] = [ type: ChainType.SVM, env: 'production', rpcUrl: 'https://api.mainnet-beta.solana.com', + portalAddress: 'Ecoo5HDM2XCBy7QzkhDGrAmnRcWw7emU6xGr7CcCmooo', + provers: { Hyperlane: 'EcooFDTfKVVo5qZcpNoDngMmVXqrG6FQT1D5LDjZEGeR' }, nativeCurrency: { name: 'Solana', symbol: 'SOL', decimals: 9 }, }, { diff --git a/src/blockchain/utils/address-normalizer.ts b/src/blockchain/utils/address-normalizer.ts index 185e68a..88d9b97 100644 --- a/src/blockchain/utils/address-normalizer.ts +++ b/src/blockchain/utils/address-normalizer.ts @@ -59,9 +59,11 @@ export class AddressNormalizer { const unpadded = unpadFrom32Bytes(address); // Guard against old-format universals that already carry the 0x41 prefix // (e.g. values stored before the 20-byte normalization change). - const hexAddress = unpadded.startsWith('0x41') - ? unpadded.substring(2) - : '41' + unpadded.substring(2); + // Use length instead of prefix: '0x' + 42 hex = 21 bytes (old format, 0x41 already present); + // '0x' + 40 hex = 20 bytes (new format, needs 41 prepended). A prefix check + // would misfire on 20-byte bodies that happen to start with 41. + const hexAddress = + unpadded.length === 44 ? unpadded.substring(2) : '41' + unpadded.substring(2); const base58Address = TronWeb.address.fromHex(hexAddress); if (!TronWeb.isAddress(base58Address)) { throw new Error(`Invalid Tron address after denormalization: ${base58Address}`); diff --git a/src/cli/services/intent-publish-flow.service.ts b/src/cli/services/intent-publish-flow.service.ts index b34a1a5..194ef01 100644 --- a/src/cli/services/intent-publish-flow.service.ts +++ b/src/cli/services/intent-publish-flow.service.ts @@ -7,6 +7,7 @@ import { privateKeyToAccount } from 'viem/accounts'; import { AddressNormalizerService } from '@/blockchain/address-normalizer.service'; import { PublishResult } from '@/blockchain/base.publisher'; +import { PortalEncoderService } from '@/blockchain/encoding/portal-encoder.service'; import { PublisherFactory } from '@/blockchain/publisher-factory.service'; import { getErrorMessage } from '@/commons/utils/error-handler'; import { ConfigService } from '@/config/config.service'; @@ -14,7 +15,7 @@ import { TOKEN_CONFIGS } from '@/config/tokens.config'; import { IntentBuilder } from '@/intent/intent-builder.service'; import { IntentStorage } from '@/intent/intent-storage.service'; import { QuoteResult, QuoteService } from '@/quote/quote.service'; -import { RoutesCliError } from '@/shared/errors'; +import { ErrorCode, RoutesCliError } from '@/shared/errors'; import { KeyHandle } from '@/shared/security'; import { BlockchainAddress, @@ -77,7 +78,8 @@ export class IntentPublishFlow { private readonly intentStorage: IntentStorage, private readonly prompt: PromptService, private readonly display: DisplayService, - private readonly statusService: StatusService + private readonly statusService: StatusService, + private readonly encoder: PortalEncoderService ) {} async publish(args: { @@ -185,7 +187,7 @@ export class IntentPublishFlow { ); if (!result.success) { - this.display.fail('Publishing failed'); + this.display.fail(`Publishing failed: ${result.error ?? 'unknown error'}`); throw new Error(result.error); } @@ -281,6 +283,7 @@ export class IntentPublishFlow { }); this.display.succeed('Quote received'); this.display.displayQuote(quote, rewardToken, rewardAmount, routeToken); + this.validateQuoteRouteAmount(quote, destChain); const sourcePortal = this.normalizer.normalize( quote.sourcePortal as Parameters[0], sourceChain.type @@ -433,6 +436,26 @@ export class IntentPublishFlow { } } + private validateQuoteRouteAmount(quote: QuoteResult, destChain: ChainConfig): void { + let routeAmount: bigint; + try { + const route = this.encoder.decode(quote.encodedRoute, destChain.type, 'route'); + routeAmount = route.tokens.length > 0 ? route.tokens[0].amount : route.nativeAmount; + } catch { + return; // can't decode — skip the check + } + + const quotedAmount = BigInt(quote.destinationAmount); + if (routeAmount !== quotedAmount) { + throw new RoutesCliError( + ErrorCode.QUOTE_SERVICE_ERROR, + `Quote amount mismatch: solver quoted ${quotedAmount} but encoded route calls for ${routeAmount}. ` + + `The intent would revert on-chain. Please retry — the solver may have returned a stale quote.`, + true + ); + } + } + private static resolveKey(options: PublishFlowOptions, chainType: ChainType): string | undefined { switch (chainType) { case ChainType.EVM: diff --git a/src/config/config.service.ts b/src/config/config.service.ts index a7c05a3..aeef4f2 100644 --- a/src/config/config.service.ts +++ b/src/config/config.service.ts @@ -39,12 +39,12 @@ export class ConfigService { fallback: '', }, [ChainType.TVM]: { - primary: this.config.get('TVM_RPC_URL') ?? 'https://api.trongrid.io', - fallback: this.config.get('TVM_RPC_URL_2') ?? 'https://tron.publicnode.com', + primary: this.config.get('TVM_RPC_URL') ?? '', + fallback: this.config.get('TVM_RPC_URL_2') ?? '', }, [ChainType.SVM]: { - primary: this.config.get('SVM_RPC_URL') ?? 'https://api.mainnet-beta.solana.com', - fallback: this.config.get('SVM_RPC_URL_2') ?? 'https://solana.publicnode.com', + primary: this.config.get('SVM_RPC_URL') ?? '', + fallback: this.config.get('SVM_RPC_URL_2') ?? '', }, }; return map[chainType][variant] || undefined;