export interface GruCatalogPosture { isGru: boolean isWrappedTransport: boolean isX402Ready: boolean isForwardCanonical: boolean currencyCode?: string activeVersion?: string forwardVersion?: string } interface GruCatalogEntry extends GruCatalogPosture { symbol: string addresses?: string[] } const GRU_X402_READY_SYMBOLS = new Set([ 'CAUDC', 'CCADC', 'CCHFC', 'CEURC', 'CEURT', 'CGBPC', 'CGBPT', 'CJPYC', 'CUSDC', 'CUSDT', 'CXAUC', 'CXAUT', ]) const GRU_CATALOG: GruCatalogEntry[] = [ { symbol: 'cUSDC', currencyCode: 'USD', isGru: true, isWrappedTransport: false, isX402Ready: true, isForwardCanonical: true, activeVersion: 'v1', forwardVersion: 'v2', addresses: [ '0xf22258f57794cc8e06237084b353ab30fffa640b', '0x219522c60e83dee01fc5b0329d6fa8fd84b9d13d', ], }, { symbol: 'cUSDT', currencyCode: 'USD', isGru: true, isWrappedTransport: false, isX402Ready: true, isForwardCanonical: true, activeVersion: 'v1', forwardVersion: 'v2', addresses: [ '0x93e66202a11b1772e55407b32b44e5cd8eda7f22', '0x9fbfab33882efe0038daa608185718b772ee5660', ], }, ...['cAUDC', 'cCADC', 'cCHFC', 'cEURC', 'cEURT', 'cGBPC', 'cGBPT', 'cJPYC', 'cXAUC', 'cXAUT'].map((symbol) => ({ symbol, currencyCode: symbol.slice(1, 4).replace('XAU', 'XAU'), isGru: true, isWrappedTransport: false, isX402Ready: true, isForwardCanonical: true, forwardVersion: 'v2', })), ] function normalizeSymbol(symbol?: string | null): string { return (symbol || '').trim().toUpperCase() } function normalizeAddress(address?: string | null): string { return (address || '').trim().toLowerCase() } export function getGruCatalogPosture(input: { symbol?: string | null address?: string | null tags?: string[] | null }): GruCatalogPosture | null { const symbol = normalizeSymbol(input.symbol) const address = normalizeAddress(input.address) const tags = (input.tags || []).map((tag) => tag.toLowerCase()) const matched = GRU_CATALOG.find((entry) => { if (symbol && normalizeSymbol(entry.symbol) === symbol) return true if (address && entry.addresses?.includes(address)) return true return false }) if (matched) { return { isGru: true, isWrappedTransport: matched.isWrappedTransport, isX402Ready: matched.isX402Ready, isForwardCanonical: matched.isForwardCanonical, currencyCode: matched.currencyCode, activeVersion: matched.activeVersion, forwardVersion: matched.forwardVersion, } } const looksWrapped = symbol.startsWith('CW') const looksGru = looksWrapped || symbol.startsWith('C') || tags.includes('compliant') || tags.includes('wrapped') || tags.includes('bridge') if (!looksGru) return null return { isGru: true, isWrappedTransport: looksWrapped || tags.includes('wrapped') || tags.includes('bridge'), isX402Ready: GRU_X402_READY_SYMBOLS.has(symbol), isForwardCanonical: GRU_X402_READY_SYMBOLS.has(symbol) && !looksWrapped, } }