keyring-agent-core 0.2.10 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,22 +1,29 @@
1
- "use strict";var tr=Object.create;var cn=Object.defineProperty;var nr=Object.getOwnPropertyDescriptor;var or=Object.getOwnPropertyNames;var rr=Object.getPrototypeOf,sr=Object.prototype.hasOwnProperty;var ar=(c,e)=>{for(var t in e)cn(c,t,{get:e[t],enumerable:!0})},no=(c,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of or(e))!sr.call(c,r)&&r!==t&&cn(c,r,{get:()=>e[r],enumerable:!(n=nr(e,r))||n.enumerable});return c};var ir=(c,e,t)=>(t=c!=null?tr(rr(c)):{},no(e||!c||!c.__esModule?cn(t,"default",{value:c,enumerable:!0}):t,c)),lr=c=>no(cn({},"__esModule",{value:!0}),c);var ha={};ar(ha,{AI_AGENT_TOOL_NAMES:()=>En,AgentCore:()=>Bn,ApproveTokenTool:()=>Bt,BaseNftMessageTool:()=>ge,BaseSwapService:()=>Ue,BaseTool:()=>v,BaseWalletActionTool:()=>ae,BuyTokenTool:()=>Ot,ChatHistory:()=>$t,DEFAULT_CHAIN:()=>he,DEFAULT_PROVIDER:()=>Jt,DEFAULT_RPC_BY_CHAIN:()=>se,DebridgeAdapter:()=>Tt,EstimatePoolYieldTool:()=>Pt,GeminiProvider:()=>pe,GeminiSearchAiTool:()=>mt,HEX_TO_PANTOGRAPH:()=>wo,KnowledgeBase:()=>Vt,MoralisService:()=>E,NFTContractInfoTool:()=>ut,NFTMetadataTool:()=>dt,NFT_AGENT_TOOL_NAMES:()=>Rn,OpenAddLiquidityFormTool:()=>xt,POOL_AGENT_TOOL_NAMES:()=>Nn,PantographService:()=>Z,PoolByAddressTool:()=>ft,PoolDetailTool:()=>ht,PoolSearchTool:()=>gt,PoolService:()=>Me,PreviewAddLiquidityTool:()=>At,RelayAdapter:()=>vt,Router:()=>Ht,SUPPORTED_CHAINS:()=>$e,SUPPORTED_CHAINS_LABEL:()=>Ne,SWAP_PROVIDER_CONFIG:()=>Sn,SendNativeTool:()=>Lt,SendNftTool:()=>Ft,SendTokenTool:()=>Mt,Subagent:()=>Y,SubgraphCoinPoolPairsTool:()=>Nt,SubgraphPoolByAddressTool:()=>Ut,SubgraphPoolByPositionIdTool:()=>Rt,SubgraphPoolSearchTool:()=>_t,SubgraphPositionDetailTool:()=>Et,SubgraphTrendingPoolsTool:()=>Ct,Summarizer:()=>Wt,SwapServiceFactory:()=>Le,SwapTokenTool:()=>qt,Synthesizer:()=>Gt,TOKEN_AGENT_TOOL_NAMES:()=>Un,TRANSFER_TOPIC:()=>Oo,TRENDING_DEFAULT_BY_HEX_CHAIN:()=>ko,TokenAnalyticsTool:()=>je,TokenHoldersTool:()=>Ye,TokenInfoTool:()=>Ke,TokenScoreTool:()=>ze,ToolRegistry:()=>Fe,TopGainersTool:()=>Xe,TopPoolsTool:()=>pt,TransactionByHashTool:()=>lt,TrendingTokensTool:()=>Qe,UNISWAP_CHAIN_SLUG:()=>en,UpstashKnowledgeBase:()=>Re,WALLET_ACTION_AGENT_TOOL_NAMES:()=>In,WALLET_AGENT_TOOL_NAMES:()=>Cn,WalletApprovalsTool:()=>rt,WalletDefiPositionsTool:()=>at,WalletDefiProtocolPositionsTool:()=>it,WalletDefiSummaryTool:()=>st,WalletHistoryTool:()=>Ze,WalletNFTsTool:()=>ct,WalletNetWorthTool:()=>gn,WalletNftTransfersTool:()=>tt,WalletPnlSummaryTool:()=>nt,WalletPnlTool:()=>ot,WalletTokenBalancesTool:()=>Je,WalletTokenTransfersTool:()=>et,ZERO_ADDRESS:()=>O,buildRangePresets:()=>Zn,buildUniswapPoolUrl:()=>ue,clampTick:()=>ve,configureSupportedChains:()=>mn,createAiAgent:()=>sn,createDefaultSubagents:()=>Dn,createNftAgent:()=>rn,createPoolAgent:()=>an,createTokenAgent:()=>on,createWalletActionAgent:()=>ln,createWalletAgent:()=>nn,ethCallAt:()=>Lo,ethCallByChain:()=>Jn,getAffiliateFee:()=>wt,getChainMeta:()=>ee,getDefaultRpcUrl:()=>ws,getGatewayAddress:()=>An,getNativeTokenInfo:()=>St,getPositionManagerAddress:()=>Pn,getProviderByChain:()=>vn,ingestKnowledgeBase:()=>Zo,priceToTick:()=>De,resolveRpcUrl:()=>kt,roundTickToSpacing:()=>Te,rpcCall:()=>xe,setRpcOverrides:()=>Xn,swapServiceFactory:()=>fe,tickToPrice:()=>le,toPantographChain:()=>zt});module.exports=lr(ha);var ro=ir(require("axios"));var oo="0.2.10";var ur=`keyring-agent-core/${oo} (Keyring Chatbot Agent; +https://keyring.app)`,dr=3e4,mr=typeof process<"u"&&process.versions?.node!=null,qn=class{instance;constructor(e={}){this.instance=ro.default.create({baseURL:e.baseURL,timeout:e.timeout??dr,validateStatus:()=>!0,headers:{Accept:"application/json",...mr?{"User-Agent":e.userAgent??ur}:{},...e.headers}})}async request(e,t,n,r={}){let o=await this.instance.request({...r,method:e,url:t,data:n}),s={};for(let[a,i]of Object.entries(o.headers??{}))i!=null&&(s[a.toLowerCase()]=String(i));return{ok:o.status>=200&&o.status<300,status:o.status,statusText:o.statusText,data:o.data,headers:s}}get(e,t){return this.request("GET",e,void 0,t)}post(e,t,n){return this.request("POST",e,t,n)}put(e,t,n){return this.request("PUT",e,t,n)}patch(e,t,n){return this.request("PATCH",e,t,n)}delete(e,t){return this.request("DELETE",e,void 0,t)}},_=new qn;function N(c){let{data:e,status:t,statusText:n}=c,r=n?`HTTP ${t} ${n}`:`HTTP ${t}`;if(typeof e=="string")return e.trim()||r;if(e&&typeof e=="object"){let o=e,s=i=>i&&typeof i=="object"?i.message:void 0,a=[o.message,o.errorMessage,o.error,o.error_description,o.detail,s(o.error),s(o.data)];for(let i of a)if(typeof i=="string"&&i.trim())return i.trim()}return r}var so={string:"STRING",number:"NUMBER",boolean:"BOOLEAN",object:"OBJECT",array:"ARRAY"},pr="https://nft-demo.keyring.app/api/gemini-stable",hr=new Set([408,429,500,502,503,504]),ao=3e4,un=c=>new Promise(e=>setTimeout(e,c));function dn(c,e,t){if(t!=null&&t>0)return Math.min(t,ao);let n=Math.min(e*2**(c-1),ao);return n/2+Math.random()*(n/2)}function gr(c){if(!c)return null;let e=Number(c);if(Number.isFinite(e))return Math.max(0,e*1e3);let t=Date.parse(c);return Number.isNaN(t)?null:Math.max(0,t-Date.now())}var pe=class{model;maxTokens;temperature;baseUrl;constructor(e){this.model=e.model??"gemini-2.5-flash-lite",this.maxTokens=e.maxTokens??4096,this.temperature=e.temperature??1,this.baseUrl=e.baseUrl??pr}async chat(e,t,n){let{systemInstruction:r,contents:o}=this.toContents(e),{maxRetries:s=5,retryDelayMs:a=1e3}=n??{},i=o.length>0?o:[{role:"user",parts:[{text:"(continue)"}]}];if(n?._debug){console.log("[GeminiProvider] turn sequence:");for(let p of i){let m=p.parts.map(g=>g.functionCall?`fc:${g.functionCall.name}`:g.functionResponse?`fr:${g.functionResponse.name}`:`text:${(g.text??"").slice(0,40)}`).join(", ");console.log(` ${p.role}: [${m}]`)}}let l={contents:i,generationConfig:{maxOutputTokens:this.maxTokens,temperature:this.temperature}};r&&(l.systemInstruction=r),t?.length?(l.tools=[{functionDeclarations:this.toFunctionDeclarations(t)}],n?.forceToolUse&&(l.toolConfig={functionCallingConfig:{mode:"ANY"}})):n?.googleSearch&&(l.tools=[{googleSearch:{}}]);let u=`${this.baseUrl}/v1beta/models/${this.model}:generateContent`,d="Unknown error";for(let p=1;p<=s;p++){let m;try{m=await _.post(u,l,{headers:{"Content-Type":"application/json"}})}catch(b){if(d=b instanceof Error?b.message:String(b),p>=s)break;await un(dn(p,a));continue}if(!m.ok){let b=typeof m.data=="string"?m.data:JSON.stringify(m.data)||m.statusText;if(d=`Gemini proxy error ${m.status}: ${b}`,!hr.has(m.status))throw new Error(d);if(p>=s)break;let T=gr(m.headers["retry-after"]??null);await un(dn(p,a,T));continue}if(m.data==null||typeof m.data!="object"){if(d="Gemini proxy returned invalid JSON",p>=s)break;await un(dn(p,a));continue}let g=m.data,f=g.candidates?.[0];if(!f)return{text:"",toolCalls:[]};let h="",y=[],k=f.content?.parts;if(!Array.isArray(k)||k.length===0){let b=f.finishReason;if(!(b==="SAFETY"||b==="RECITATION")&&p<s){d=`Gemini returned an empty turn (finishReason=${b??"none"})`,await un(dn(p,a));continue}return{text:"",toolCalls:[]}}for(let b of k)b.text&&(h+=b.text),b.functionCall&&y.push({toolName:b.functionCall.name,args:b.functionCall.args??{},callId:b.functionCall.id??`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`});let w=g.usageMetadata?{promptTokens:g.usageMetadata.promptTokenCount??0,completionTokens:g.usageMetadata.candidatesTokenCount??0,totalTokens:g.usageMetadata.totalTokenCount??0}:void 0;return{text:h,toolCalls:y,usage:w}}throw new Error(d)}toContents(e){let t=[],n=[];for(let a of e){if(a.role==="system"){t.push(a.content);continue}if(a.role==="tool"){n.push({role:"function",parts:[{functionResponse:{name:a.toolName??"unknown",response:{result:a.content},...a.toolCallId?{id:a.toolCallId}:{}}}]});continue}if(a.role==="assistant"&&a.toolCalls?.length){n.push({role:"model",parts:a.toolCalls.map(i=>({functionCall:{name:i.toolName,args:i.args,...i.callId?{id:i.callId}:{}}}))});continue}n.push({role:a.role==="assistant"?"model":"user",parts:[{text:a.content}]})}let r=[];for(let a of n){let i=r[r.length-1],l=a.parts.some(d=>d.functionCall),u=i?.parts.some(d=>d.functionCall);i&&i.role===a.role&&!l&&!u?i.parts.push(...a.parts):r.push({...a,parts:[...a.parts]})}let o=[];for(let a=0;a<r.length;a++){let i=r[a],l=o[o.length-1];i.role==="function"&&!l?.parts.some(d=>d.functionCall)||i.role==="model"&&i.parts.some(d=>d.functionCall)&&!(r[a+1]?.role==="function")||o.push(i)}for(;o.length>0&&o[0].role!=="user";)o.shift();return{systemInstruction:t.length?{role:"user",parts:[{text:t.join(`
2
- `)}]}:null,contents:o}}toFunctionDeclarations(e){return e.map(t=>{let n={},r=[];for(let o of t.parameters){let s={type:so[o.type]??"STRING",description:o.description};o.type==="array"&&(s.items={type:so[o.items?.type??"string"]??"STRING"}),n[o.name]=s,o.required&&r.push(o.name)}return{name:t.name,description:t.description,parameters:{type:"OBJECT",properties:n,required:r}}})}};var Fe=class{tools=new Map;register(e){if(this.tools.has(e.name))throw new Error(`Tool "${e.name}" is already registered`);this.tools.set(e.name,e)}unregister(e){return this.tools.delete(e)}get(e){return this.tools.get(e)}has(e){return this.tools.has(e)}getDefinitions(){return Array.from(this.tools.values()).map(e=>({name:e.name,description:e.description,parameters:e.parameters,category:e.category,kind:e.kind}))}getDefinitionsByCategory(e){return this.getDefinitions().filter(t=>t.category===e)}async execute(e,t,n){let r=this.tools.get(e);if(!r)return{toolName:e,callId:`err_${Date.now()}`,success:!1,error:`Tool "${e}" not found in registry`,duration:0};let o=Date.now();try{return{...await r.execute(t,n),duration:Date.now()-o}}catch(s){return{toolName:e,callId:`err_${Date.now()}`,success:!1,error:s instanceof Error?s.message:String(s),duration:Date.now()-o}}}get size(){return this.tools.size}listNames(){return Array.from(this.tools.keys())}};var $e=["0x1","0xa","0x38","0x89","0x2105","0xa4b1","0xa86a","0xe708"],fr=new Set($e),Ee=[...$e],io=new Set(Ee),he="0x1",Ne="Ethereum (0x1), Optimism (0xa), BSC (0x38), Polygon (0x89), Base (0x2105), Arbitrum (0xa4b1), Avalanche (0xa86a), Linea (0xe708)";function yr(c){return c.map(e=>`${Ve[e]??e} (${e})`).join(", ")}function mn(c){let e=new Set;if(c)for(let n of c){let r=Fn(n);r&&e.add(r)}let t=$e.filter(n=>!e.has(n));Ee=t.length>0?[...t]:[...$e],io=new Set(Ee),he=Ee.includes("0x1")?"0x1":Ee[0],Ne=yr(Ee)}var br={"0x1":"0x1",1:"0x1",eth:"0x1",ether:"0x1",ethereum:"0x1",mainnet:"0x1","0xa":"0xa",10:"0xa",op:"0xa",optimism:"0xa","0x38":"0x38",56:"0x38",bsc:"0x38",bnb:"0x38",binance:"0x38","0x89":"0x89",137:"0x89",matic:"0x89",polygon:"0x89","0x2105":"0x2105",8453:"0x2105",base:"0x2105","0xa4b1":"0xa4b1",42161:"0xa4b1",arb:"0xa4b1",arbitrum:"0xa4b1","0xa86a":"0xa86a",43114:"0xa86a",avax:"0xa86a",avalanche:"0xa86a","0xe708":"0xe708",59144:"0xe708",linea:"0xe708"};function ke(c){let e=Fn(c);return e&&io.has(e)?e:null}function Fn(c){if(typeof c!="string")return null;let e=c.trim().toLowerCase();if(!e)return null;let t=br[e];if(t)return t;let n=null;return/^0x[0-9a-f]+$/.test(e)?n=e:/^[0-9]+$/.test(e)&&(n="0x"+Number(e).toString(16)),n&&fr.has(n)?n:null}function pn(c){return ke(c)!==null}var We=class extends Error{constructor(t){super(`Chain "${t}" is not supported. Supported chains: ${Ne}.`);this.requested=t;this.name="UnsupportedChainError"}requested;code="unsupported_chain"},_e=c=>typeof c=="string"&&c.trim()!=="";function lo(c,e){if(_e(c)){let t=ke(c);if(t)return t;throw new We(c.trim())}if(_e(e?.chain)){let t=ke(e.chain);if(t)return t;throw new We(e.chain.trim())}return null}function hn(c,e){return ke(c)??ke(e?.chain)??void 0}var Ve={"0x1":"Ethereum","0xa":"Optimism","0x38":"BSC","0x89":"Polygon","0x2105":"Base","0xa4b1":"Arbitrum","0xa86a":"Avalanche","0xe708":"Linea"};function L(c){let e=Fn(c);return e&&Ve[e]?Ve[e]:typeof c=="string"&&c.trim()?c.trim():"the requested chain"}var wr={bnb:"0x38",matic:"0x89",pol:"0x89",avax:"0xa86a"};function co(c){if(typeof c!="string")return null;let e=c.trim().toLowerCase();return e?wr[e]??null:null}function He(c,e){if(!_e(c))return null;let t=ke(c),n=ke(e?.chain);return!t||!n||t===n?null:{requested:t,connected:n,requestedLabel:Ve[t]??t,connectedLabel:Ve[n]??n}}function kr(){return Ee.map(c=>{let e=Ve[c]??c;return{label:e,prompt:e}})}var z=class extends Error{code="needs_chain_selection";buttons;constructor(e){super("Wallet is connected but no chain is set \u2014 ask the user to pick one."),this.name="NeedsChainSelectionError",this.buttons=e??kr()}},Kt=class extends Error{code="no_wallet_connected";constructor(){super("No wallet address available \u2014 ask the user to connect a wallet or supply one."),this.name="NoWalletConnectedError"}};function K(c,e){if(_e(c))return c.trim();if(_e(e?.walletAddress))return e.walletAddress.trim();throw new Kt}function R(c,e){if(_e(c)){let t=ke(c);if(t)return t;throw new We(c.trim())}if(_e(e?.chain)){let t=ke(e.chain);if(t)return t;throw new We(e.chain.trim())}if(_e(e?.walletAddress))throw new z;return he}var v=class{category;async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s=Tr(o);return{toolName:this.name,callId:n,success:!0,data:s.data,duration:Date.now()-r,...s.actionButtons?{actionButtons:s.actionButtons}:{}}}catch(o){return o instanceof z?{toolName:this.name,callId:n,success:!0,data:{_instructions:"The user has a connected wallet but no chain is set. Ask them which chain to use \u2014 the chain-picker buttons rendered below your reply let them choose. Keep the message short in the user's language and do NOT list the chain names yourself (the buttons already show them)."},duration:Date.now()-r,actionButtons:o.buttons}:o instanceof Kt?{toolName:this.name,callId:n,success:!0,data:{_instructions:"The user has not connected a wallet. In the user's language, tell them this action needs a wallet \u2014 they can either connect their wallet or include a wallet address (0x\u2026) directly in their question. Keep it short and friendly."},duration:Date.now()-r}:{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}};function Tr(c){if(!c||typeof c!="object")return{data:c};let e=c,t=e.actionButtons;if(!Array.isArray(t)||t.length===0)return{data:c};let{actionButtons:n,...r}=e;return{data:r,actionButtons:t}}var uo=require("js-sha3");function B(c){return typeof c=="string"&&/^0x[0-9a-fA-F]{40}$/.test(c)}function vr(c){return new Uint8Array(uo.keccak256.arrayBuffer(c))}function Yt(c){let e=c.startsWith("0x")?c.slice(2):c,t=e.length%2?"0"+e:e,n=new Uint8Array(t.length/2);for(let r=0;r<n.length;r++)n[r]=parseInt(t.slice(r*2,r*2+2),16);return n}function mo(c){return"0x"+Array.from(c).map(e=>e.toString(16).padStart(2,"0")).join("")}function po(c){return new TextEncoder().encode(c)}function Ie(...c){let e=c.reduce((r,o)=>r+o.length,0),t=new Uint8Array(e),n=0;for(let r of c)t.set(r,n),n+=r.length;return t}function Sr(c,e=32){let t=new Uint8Array(e);return t.set(c,e-c.length),t}function xr(c,e=32){let t=new Uint8Array(e);return t.set(c),t}function ho(c){let e=((c%(1n<<256n)+(1n<<256n))%(1n<<256n)).toString(16).padStart(64,"0");return Yt(e)}function jt(c){return ho(BigInt(c))}function go(c){return c==="uint"?"uint256":c==="int"?"int256":c.match(/^uint$/)?"uint256":c.match(/^int$/)?"int256":c.replace(/^uint(\[|$)/,"uint256$1").replace(/^int(\[|$)/,"int256$1")}function fo(c){let e=go(c.type);if(e==="tuple"||e.startsWith("tuple[")){let t=(c.components??[]).map(fo).join(","),n=e.startsWith("tuple[")?e.slice(5):"";return`(${t})${n}`}return e}function Ar(c){let e=(c.inputs??[]).map(fo).join(",");return`${c.name??""}(${e})`}function $n(c,e){let t=Cr(c.type);if(t){let[r,o]=t;return Pr({...c,type:o},e,r)}if(c.type==="tuple")return _r(c,e);if(c.type==="address"){let r=String(e),o=r.startsWith("0x")?r.slice(2):r;return{dynamic:!1,encoded:Sr(Yt(o.toLowerCase()))}}if(c.type==="bool"){let r=new Uint8Array(32);return r[31]=e?1:0,{dynamic:!1,encoded:r}}let n=go(c.type);if(/^uint\d*$/.test(n))return{dynamic:!1,encoded:jt(BigInt(e))};if(/^int\d*$/.test(n)){let r=BigInt(e);return r<0n&&(r=r+(1n<<256n)),{dynamic:!1,encoded:ho(r)}}if(/^bytes(\d+)$/.test(n)){let r=parseInt(n.slice(5),10),o=typeof e=="string"?Yt(e):e;if(o.length!==r)throw new Error(`bytes${r} expects exactly ${r} bytes, got ${o.length}`);return{dynamic:!1,encoded:xr(o)}}if(n==="bytes"){let r=typeof e=="string"?Yt(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:Ie(jt(r.length),s)}}if(n==="string"){let r=typeof e=="string"?po(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:Ie(jt(r.length),s)}}throw new Error(`Unsupported ABI type: ${c.type}`)}function Wn(c){let e=0;for(let{dynamic:o,encoded:s}of c)e+=o?32:s.length;let t=[],n=[],r=0;for(let{dynamic:o,encoded:s}of c)o?(t.push(jt(e+r)),n.push(s),r+=s.length):t.push(s);return Ie(...t,...n)}function Pr(c,e,t){let n=t===null;if(!n&&e.length!==t)throw new Error(`Array length mismatch: expected ${t}, got ${e.length}`);let r=!1,o=[];for(let s of e){let a=$n(c,s);a.dynamic&&(r=!0),o.push(a)}if(n||r){let s=Wn(o);if(n){let a=jt(o.length);return{dynamic:!0,encoded:o.length>0?Ie(a,s):a}}return{dynamic:!0,encoded:s}}return{dynamic:!1,encoded:Ie(...o.map(s=>s.encoded))}}function _r(c,e){let t=c.components??[],n=!1,r=[];for(let o=0;o<t.length;o++){let s=t[o],a=Array.isArray(e)?e[o]:e[s.name??""],i=$n(s,a);i.dynamic&&(n=!0),r.push(i)}return{dynamic:n,encoded:n?Wn(r):Ie(...r.map(o=>o.encoded))}}function Cr(c){let e=c.match(/^(.*)\[(\d+)?\]$/);return e?[e[2]?Number(e[2]):null,e[1]]:void 0}function Ur(c,e){if(c.length!==e.length)throw new Error(`Expected ${c.length} values, got ${e.length}`);if(c.length===0)return"0x";let t=c.map((r,o)=>$n(r,e[o])),n=Wn(t);return mo(n)}function Ge({abi:c,functionName:e,args:t=[]}){let n=c.find(i=>(i.type==="function"||i.type===void 0)&&i.name===e);if(!n)throw new Error(`Function "${e}" not found in ABI`);let r=Ar(n),o=vr(po(r)).slice(0,4),s=n.inputs??[],a=s.length===0||t.length===0?new Uint8Array(0):Yt(Ur(s,t).slice(2));return mo(Ie(o,a))}var Rr="https://wallet-api.pantograph.app",Er="0x0000000000000000000000000000000000000000",wo={"0x1":"ether","0xa":"optimism","0x38":"bsc","0x89":"matic","0x2105":"base","0xa4b1":"arbitrum","0xa86a":"avax","0xe708":"linea"},ko={"0x1":["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"],"0xa":["0x4200000000000000000000000000000000000006","0x68f180fcce6836688e9084f035309e29bf0a2095","0xc47da4cb96ce65a96844a01bfae509f9d5454534"],"0x38":["0x2170ed0880ac9a755fd29b2688956bd959f933f8","0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c"],"0x89":["0x7ceb23fd6bc0add59e62ac25578270cff1b9f619","0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6"],"0x2105":["0x4200000000000000000000000000000000000006","0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf","0xc47da4cb96ce65a96844a01bfae509f9d5454534"],"0xa4b1":["0x82af49447d8a07e3bd95bd0d56f35241523fbab1","0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f"],"0xa86a":["0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab","0x0555e30da8f98308edb960aa94c0db47230d2b9c"],"0xe708":["0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f","0x3aab2285ddcddad8edf438c1bab47e1a9d05a9b4"]};function zt(c){return wo[c.toLowerCase()]||c}function yo(c){let e=[];for(let[t,n]of Object.entries(c))if(n!=null)if(Array.isArray(n))for(let r of n)e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(r))}`);else e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(n))}`);return e.join("&")}function Vn(c){let e=c,t=e.decimals,n=typeof t=="number"?t:typeof t=="string"?parseInt(t,10):NaN,r=e.price,o=r!=null?parseFloat(String(r)):NaN;return{...e,token_address:e.token_address??e.address??"",decimals:Number.isFinite(n)?n:0,...Number.isFinite(o)?{usd_price:o}:{}}}function J(c){if(c==null)return null;let e=typeof c=="number"?c:parseFloat(String(c));return Number.isFinite(e)?e:null}function bo(c,e){let t=c.decimals,n=typeof t=="number"?t:typeof t=="string"?parseInt(t,10):null,r=J(c.price_change_percentage_24h)??J(c.priceChange24h)??J(c.usd_price_24hr_percent_change)??J(c.price_change_24h)??J(c.priceChange);return{chainId:e,tokenAddress:c.address??c.token_address??"",name:c.name??null,symbol:c.symbol??null,decimals:n!=null&&Number.isFinite(n)?n:null,logo:c.logoURI??c.icon_image??c.logo??null,usdPrice:J(c.price)??J(c.usd_price),marketCap:J(c.market_cap)??J(c.marketCap),totalVolume:{"24h":J(c.total_volume)??J(c.volume24h)},pricePercentChange:{"24h":r}}}var Z=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??Rr).replace(/\/+$/,"")}async enrichTokenPrices(e,t){if(e.length!==0)try{let n=zt(t),r=e.map(u=>u.token_address).join(","),o=`${this.baseUrl}/keyrings/tokens/${n}/v2?addresses=${encodeURIComponent(r)}`,s=await _.get(o);if(!s.ok)return;let a=s.data,i=Array.isArray(a)?a:Array.isArray(a.data)?a.data:[],l=new Map;for(let u of i)u.address&&l.set(u.address.toLowerCase(),{price:u.price!=null?parseFloat(String(u.price)):NaN,icon_image:u.icon_image});for(let u=0;u<e.length;u++){let d=l.get(e[u].token_address.toLowerCase());d&&(d.icon_image&&(e[u].logo=d.icon_image,e[u].thumbnail=d.icon_image),isNaN(d.price)||(e[u].usd_price=d.price,e[u].usd_value=parseFloat(e[u].balance_formatted||"0")*d.price))}}catch{}}async getTokenMetadata(e,t){try{let n=zt(t),r=`${this.baseUrl}/keyrings/tokens/${n}/v2?addresses=${encodeURIComponent(e)}`,o=await _.get(r);if(!o.ok)return null;let s=o.data,a=Array.isArray(s)?s:Array.isArray(s.data)?s.data:[],i=e.toLowerCase()===Er,l=a.find(u=>u.address?.toLowerCase()===e.toLowerCase()||i&&u.address===""||u.token_address?.toLowerCase()===e.toLowerCase()||i&&u.token_address==="");return l?Vn(l):null}catch{return null}}async getTokensMetadata(e,t){let n={};if(e.length===0)return n;try{let r=zt(t),o=`${this.baseUrl}/keyrings/tokens/${r}/v2?addresses=${encodeURIComponent(e.join(","))}`,s=await _.get(o);if(!s.ok)return n;let a=s.data,i=Array.isArray(a)?a:Array.isArray(a.data)?a.data:[],l=new Set(e.map(u=>u.toLowerCase()));for(let u of i){let d=u.address?.toLowerCase()||u.token_address?.toLowerCase()||(u.address===""?"":null);d&&l.has(d)&&(n[d]=Vn(u))}}catch{}return n}async searchTokensByKey(e){let{key:t,chain:n}=e;if(!t)return{success:!1,error:"Search key is required"};let o=parseInt(n||he,16);if(isNaN(o))return{success:!1,error:`Invalid hex chain: ${n}`};try{let s=yo({chainId:o,key:t}),a=`${this.baseUrl}/token-list?${s}`,i=await _.get(a);if(!i.ok)throw new Error(N(i));let l=i.data,u=[];return l?.data?u=Array.isArray(l.data)?l.data:Object.values(l.data).flat():typeof l=="object"&&!Array.isArray(l)&&(u=Object.values(l).flat()),{success:!0,data:u.map(Vn)}}catch(s){return{success:!1,error:s instanceof Error?s.message:"Unknown error"}}}async getTrendingTokens(e){let t=e?.chain||he,n=5,r=e?.limit&&e.limit>0?Math.floor(e.limit):void 0;try{let o=ko[t.toLowerCase()]??[],s=r!=null?r+o.length:n+o.length,[a,i]=await Promise.allSettled([this.fetchTopGainers(t,s),o.length>0?this.getTokensMetadata(o,t):Promise.resolve({})]),l=a.status==="fulfilled"?a.value:[],u=i.status==="fulfilled"?o.map(f=>i.value[f.toLowerCase()]).filter(f=>f!=null).map(f=>bo(f,t)):[],d=new Set,p=[];for(let f of u){let h=f.tokenAddress?.toLowerCase();!h||d.has(h)||(d.add(h),p.push(f))}let m=[];for(let f of l){let h=f.tokenAddress?.toLowerCase();!h||d.has(h)||(d.add(h),m.push(f))}let g=r!=null?[...p,...m].slice(0,r):[...p,...m.slice(0,n)];return await this.enrichTrendingMarketData(g,t),{success:!0,data:g}}catch(o){return{success:!1,error:o instanceof Error?o.message:"Unknown error"}}}async enrichTrendingMarketData(e,t){let n=e.filter(o=>o.tokenAddress&&(o.marketCap==null||o.totalVolume?.["24h"]==null));if(n.length===0)return;let r=await this.getTokensMetadata(n.map(o=>o.tokenAddress),t);for(let o of n){let s=r[o.tokenAddress.toLowerCase()];s&&(o.marketCap==null&&(o.marketCap=J(s.market_cap)??J(s.marketCap)),o.totalVolume?.["24h"]==null&&(o.totalVolume={"24h":J(s.total_volume)??J(s.volume24h)}))}}async fetchTopGainers(e,t,n="24h"){let r=zt(e),o=yo({duration:n,limit:t}),s=`${this.baseUrl}/token-list/top-gainers/${r}${o?`?${o}`:""}`,a=[];for(let i=0;i<5;i++){let l=await _.get(s);if(!l.ok)throw new Error(N(l));let u=l.data;if(a=Array.isArray(u)?u:Array.isArray(u.data)?u.data:[],a.length>0)break;i<4&&await new Promise(d=>setTimeout(d,2e3))}return a.map(i=>bo(i,e))}async getTopGainers(e){let t=e?.chain||he,n=e?.limit&&e.limit>0?Math.floor(e.limit):void 0,r=e?.duration||"24h";try{let o=await this.fetchTopGainers(t,n,r);return await this.enrichTrendingMarketData(o,t),{success:!0,data:o}}catch(o){return{success:!1,error:o instanceof Error?o.message:"Unknown error"}}}};var Nr="https://nft.keyring.app",Ir="https://nft-demo.keyring.app",Dr=.01,U=5,F=1e3,Lr="0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",Mr="0x0000000000000000000000000000000000000000";function V(c){return c||he}function $(c){let e=[];for(let[t,n]of Object.entries(c))if(n!=null)if(Array.isArray(n))for(let r of n)e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(r))}`);else e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(n))}`);return e.join("&")}function W(c){return new Promise(e=>setTimeout(e,c))}function Br(c){if(Array.isArray(c))return c;if(c&&typeof c=="object"){let e=c;return Array.isArray(e.result)?e.result.map(t=>({protocol_id:t.protocolId,protocol_name:t.protocolName,protocol_url:t.protocolUrl??"",protocol_logo:t.protocolLogo??"",position:{label:t.position.label,address:t.position.address,balance_usd:t.position.balanceUsd??0,total_unclaimed_usd_value:t.position.unclaimedUsd??0,position_details:t.position.details,tokens:t.position.tokens.map(n=>({token_type:n.tokenType,address:n.address,contract_address:n.address,name:n.name??"",symbol:n.symbol??"",decimals:n.decimals??18,logo:n.logo,balance:n.balance??"0",balance_formatted:n.balanceFormatted??"0",usd_price:n.usdPrice,usd_value:n.usdValue}))}})):Object.keys(e).filter(t=>/^\d+$/.test(t)).sort((t,n)=>Number(t)-Number(n)).map(t=>e[t]).filter(t=>!!t&&typeof t=="object")}return[]}function Hn(c){if(Array.isArray(c))return c;if(c&&typeof c=="object"){let e=c;return Array.isArray(e.result)?e.result:Object.keys(e).filter(t=>/^\d+$/.test(t)).sort((t,n)=>Number(t)-Number(n)).map(t=>e[t]).filter(t=>!!t&&typeof t=="object")}return[]}var E=class{baseUrl;v1BaseUrl;pantograph;constructor(e){this.baseUrl=(e?.baseUrl??Nr).replace(/\/+$/,""),this.v1BaseUrl=(e?.v1BaseUrl??Ir).replace(/\/+$/,""),this.pantograph=new Z({baseUrl:e?.pantographUrl})}async getWalletTokenBalances(e){let{address:t,chain:n,tokenAddresses:r,excludeSpam:o=!1,excludeUnverifiedContracts:s=!1}=e;if(!t)return{success:!1,error:"Address is required"};let a=V(n),i="Unknown error";for(let l=1;l<=U;l++)try{let u={chain:a,exclude_spam:o,exclude_unverified_contracts:s,limit:100};r&&(u.token_addresses=r);let d=[],p=null;do{p&&(u.cursor=p);let g=$(u),f=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/tokens?${g}`,h=await _.get(f);if(!h.ok)throw new Error(N(h));let y=h.data,k=y?.data??y;k?.result?.length>0&&d.push(...k.result),p=k?.cursor??null}while(p);let m={result:d};return m?.result?.length>0&&(m.result=m.result.filter(g=>parseFloat(g.balance||"0")>0),m.result=m.result.map(g=>({...g,token_address:g.token_address.toLowerCase()===Lr?Mr:g.token_address})),await this.enrichTokenPrices(m.result,a),m.result=m.result.filter(g=>g.usd_value==null||g.usd_value>=Dr)),{success:!0,data:m}}catch(u){i=u instanceof Error?u.message:"Unknown error",l<U&&await W(F)}return{success:!1,error:i}}async getTokenMetadata(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Token address is required"};let r=V(n),o="Unknown error",s=await this.pantograph.getTokenMetadata(t,r);if(s)return{success:!0,data:s};for(let a=1;a<=U;a++)try{let[i,l]=await Promise.allSettled([_.get(`${this.baseUrl}/api/moralis/proxy/erc20/metadata?${$({chain:r,addresses:[t]})}`),_.get(`${this.baseUrl}/api/moralis/proxy/erc20/${encodeURIComponent(t)}/price?${$({chain:r,include:"percent_change"})}`)]),u=null;if(i.status==="fulfilled"&&i.value.ok){let p=i.value.data;u=(Array.isArray(p)?p:Array.isArray(p?.data)?p.data:[])[0]??null}if(!u){o="Token metadata not found",a<U&&await W(F);continue}let d=null;if(l.status==="fulfilled"&&l.value.ok){let p=l.value.data;d=p?.data??p}return{success:!0,data:{...u,...d?.usd_price?{usd_price:d.usd_price}:{},...d?.usd_price_change_percentage_24h?{usd_price_change_percentage_24h:d.usd_price_change_percentage_24h}:{}}}}catch(i){o=i instanceof Error?i.message:"Unknown error",a<U&&await W(F)}return{success:!1,error:o}}async getWalletNFTs(e){let{address:t,chain:n,limit:r=10,excludeSpam:o=!0,cursor:s,tokenAddresses:a,includePrices:i,format:l="decimal"}=e;if(!t)return{success:!1,error:"Address is required"};let u=V(n),d="Unknown error";for(let p=1;p<=U;p++)try{let m={chain:u,format:l,limit:r,exclude_spam:o,media_items:!0,normalizeMetadata:!0};s&&(m.cursor=s),i&&(m.include_prices=!0),a&&a.length>0&&(m.token_addresses=a);let g=$(m),f=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/nft?${g}`,h=await _.get(f);if(!h.ok)throw new Error(N(h));let y=h.data;return{success:!0,data:y?.data??y}}catch(m){d=m instanceof Error?m.message:"Unknown error",p<U&&await W(F)}return{success:!1,error:d}}async getNftMetadata(e){let{address:t,tokenId:n,chain:r,format:o="decimal",normalizeMetadata:s=!0,mediaItems:a=!0,include:i}=e;if(!t)return{success:!1,error:"NFT contract address is required"};if(n==null||n==="")return{success:!1,error:"Token ID is required"};if(!B(t))return{success:!1,error:"NFT metadata lookup by token ID is only supported on EVM chains"};let l=V(r),u="Unknown error";for(let d=1;d<=U;d++)try{let p={chain:l,format:o,normalizeMetadata:s,media_items:a};i&&(p.include=i);let m=$(p),g=`${this.baseUrl}/api/moralis/proxy/nft/${encodeURIComponent(t)}/${encodeURIComponent(n)}?${m}`,f=await _.get(g);if(!f.ok)throw new Error(N(f));let h=f.data;return{success:!0,data:h?.data??h}}catch(p){u=p instanceof Error?p.message:"Unknown error",d<U&&await W(F)}return{success:!1,error:u}}async getNftContractMetadata(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Contract address is required"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/nft/${encodeURIComponent(t)}/metadata?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getWalletHistory(e){let{address:t,chain:n,limit:r=25,fromDate:o,toDate:s,fromBlock:a,toBlock:i,cursor:l,order:u="DESC",includeInternalTransactions:d,nftMetadata:p}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet history is only supported for EVM addresses"};let m=V(n),g=Math.max(1,Math.min(100,Math.floor(r))),f="Unknown error";for(let h=1;h<=U;h++)try{let y={chain:m,limit:g,order:u};o&&(y.from_date=o),s&&(y.to_date=s),a!=null&&(y.from_block=a),i!=null&&(y.to_block=i),l&&(y.cursor=l),d!=null&&(y.include_internal_transactions=d),p!=null&&(y.nft_metadata=p);let k=$(y),w=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/history?${k}`,b=await _.get(w);if(!b.ok)throw new Error(N(b));let T=b.data;return{success:!0,data:T?.data??T}}catch(y){f=y instanceof Error?y.message:"Unknown error",h<U&&await W(F)}return{success:!1,error:f}}async getWalletTokenTransfers(e){let{address:t,chain:n,contractAddresses:r,limit:o=25,fromDate:s,toDate:a,fromBlock:i,toBlock:l,cursor:u,order:d="DESC"}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"ERC-20 transfers are only supported for EVM addresses"};let p=V(n),m=Math.max(1,Math.min(100,Math.floor(o))),g="Unknown error";for(let f=1;f<=U;f++)try{let h={chain:p,limit:m,order:d};s&&(h.from_date=s),a&&(h.to_date=a),i!=null&&(h.from_block=i),l!=null&&(h.to_block=l),u&&(h.cursor=u),r?.length&&(h.contract_addresses=r);let y=$(h),k=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/erc20/transfers?${y}`,w=await _.get(k);if(!w.ok)throw new Error(N(w));let b=w.data;return{success:!0,data:b?.data??b}}catch(h){g=h instanceof Error?h.message:"Unknown error",f<U&&await W(F)}return{success:!1,error:g}}async getWalletNftTransfers(e){let{address:t,chain:n,contractAddresses:r,limit:o=25,fromDate:s,toDate:a,fromBlock:i,toBlock:l,cursor:u,order:d="DESC",includePrices:p,format:m="decimal"}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"NFT transfers are only supported for EVM addresses"};let g=V(n),f=Math.max(1,Math.min(100,Math.floor(o))),h="Unknown error";for(let y=1;y<=U;y++)try{let k={chain:g,limit:f,order:d,format:m};s&&(k.from_date=s),a&&(k.to_date=a),i!=null&&(k.from_block=i),l!=null&&(k.to_block=l),u&&(k.cursor=u),r?.length&&(k.contract_addresses=r),p!=null&&(k.include_prices=p);let w=$(k),b=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/nft/transfers?${w}`,T=await _.get(b);if(!T.ok)throw new Error(N(T));let P=T.data;return{success:!0,data:P?.data??P}}catch(k){h=k instanceof Error?k.message:"Unknown error",y<U&&await W(F)}return{success:!1,error:h}}async getTokenHolders(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Contract address is required"};if(!B(t))return{success:!1,error:"Token holders is only supported for EVM contract addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/erc20/${encodeURIComponent(t)}/holders?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getWalletNetWorth(e){let{address:t,chains:n,excludeSpam:r,excludeUnverifiedContracts:o,maxTokenInactivity:s,minPairSideLiquidityUsd:a}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet net worth is only supported for EVM addresses"};let i="Unknown error";for(let l=1;l<=U;l++)try{let u={};n&&n.length>0&&(u.chains=n.map(y=>V(y))),r!=null&&(u.exclude_spam=r),o!=null&&(u.exclude_unverified_contracts=o),s!=null&&(u.max_token_inactivity=s),a!=null&&(u.min_pair_side_liquidity_usd=a);let d=$(u),p=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/net-worth${d?`?${d}`:""}`,m=await _.get(p);if(!m.ok)throw new Error(N(m));let g=m.data;return{success:!0,data:g?.data??g}}catch(u){i=u instanceof Error?u.message:"Unknown error",l<U&&await W(F)}return{success:!1,error:i}}async getWalletPnlSummary(e){let{address:t,chain:n,days:r}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet PnL summary is only supported for EVM addresses"};let o=V(n),s="Unknown error";for(let a=1;a<=U;a++)try{let i={chain:o};r&&(i.days=r);let l=$(i),u=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/profitability/summary?${l}`,d=await _.get(u);if(!d.ok)throw new Error(N(d));let p=d.data;return{success:!0,data:p?.data??p}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await W(F)}return{success:!1,error:s}}async getWalletPnl(e){let{address:t,chain:n,days:r,tokenAddresses:o}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet PnL is only supported for EVM addresses"};let s=V(n),a="Unknown error";for(let i=1;i<=U;i++)try{let l={chain:s};r&&(l.days=r),o&&o.length>0&&(l.token_addresses=o.slice(0,25));let u=$(l),d=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/profitability?${u}`,p=await _.get(d);if(!p.ok)throw new Error(N(p));let m=p.data;return{success:!0,data:m?.data??m}}catch(l){a=l instanceof Error?l.message:"Unknown error",i<U&&await W(F)}return{success:!1,error:a}}async getTransactionByHash(e){let{transactionHash:t,chain:n}=e;if(!t)return{success:!1,error:"Transaction hash is required"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/transaction/${encodeURIComponent(t)}/verbose?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getWalletApprovals(e){let{address:t,chain:n,limit:r,cursor:o}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet approvals are only supported for EVM addresses"};let s=V(n),a="Unknown error";for(let i=1;i<=U;i++)try{let l={chain:s};r!=null&&(l.limit=r),o&&(l.cursor=o);let u=$(l),d=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/approvals?${u}`,p=await _.get(d);if(!p.ok)throw new Error(N(p));let m=p.data;return{success:!0,data:m?.data??m}}catch(l){a=l instanceof Error?l.message:"Unknown error",i<U&&await W(F)}return{success:!1,error:a}}async getWalletDefiSummary(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet DeFi summary is only supported for EVM addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chains:r}),i=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/summary?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getWalletDefiPositions(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet DeFi positions are only supported for EVM addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chains:r}),i=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/positions?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data,d=u?.data??u;return{success:!0,data:Br(d)}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getWalletDefiProtocolPositions(e){let{address:t,protocol:n,chain:r}=e;if(!t)return{success:!1,error:"Address is required"};if(!n)return{success:!1,error:"Protocol identifier is required"};if(!B(t))return{success:!1,error:"Wallet DeFi protocol positions are only supported for EVM addresses"};let o=V(r),s="Unknown error";for(let a=1;a<=U;a++)try{let i=$({chains:o}),l=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/${encodeURIComponent(n)}/positions?${i}`,u=await _.get(l);if(!u.ok)throw new Error(N(u));let m=u.data.result;return{success:!0,data:{protocol_id:m.protocolId,protocol_name:m.protocolName,protocol_url:m.protocolUrl??"",protocol_logo:m.protocolLogo??"",total_usd_value:m.totalUsd??0,total_unclaimed_usd_value:m.totalUnclaimedUsd??null,positions:m.positions.map(f=>({label:f.label,address:f.address,balance_usd:f.balanceUsd??0,total_unclaimed_usd_value:f.unclaimedUsd??0,position_details:f.details,tokens:f.tokens.map(h=>({token_type:h.tokenType,address:h.address,contract_address:h.address,name:h.name??"",symbol:h.symbol??"",decimals:h.decimals??18,logo:h.logo,balance:h.balance??"0",balance_formatted:h.balanceFormatted??"0",usd_price:h.usdPrice,usd_value:h.usdValue}))}))}}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await W(F)}return{success:!1,error:s}}async searchTokensByKey(e){return this.pantograph.searchTokensByKey(e)}async getTokenAnalytics(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Token contract address is required"};if(!B(t))return{success:!1,error:"Token analytics is only supported for EVM contract addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/tokens/${encodeURIComponent(t)}/analytics?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getTokenScore(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Token contract address is required"};if(!B(t))return{success:!1,error:"Token score is only supported for EVM contract addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/tokens/${encodeURIComponent(t)}/score?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getTrendingTokens(e){let t=e?.chain,n=e?.limit,r="Unknown error";for(let o=1;o<=U;o++)try{let s={};t&&(s.chain=V(t)),n!=null&&(s.limit=n);let a=$(s),i=`${this.baseUrl}/api/moralis/proxy/tokens/trending${a?`?${a}`:""}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data,d=u?.data??u;return{success:!0,data:Hn(d)}}catch(s){r=s instanceof Error?s.message:"Unknown error",o<U&&await W(F)}return{success:!1,error:r}}async getTopGainers(e){let{chain:t,timeFrame:n,minMarketCap:r,securityScore:o}=e??{},s="Unknown error";for(let a=1;a<=U;a++)try{let i={};t&&(i.chain=V(t)),n&&(i.time_frame=n),r!=null&&(i.min_market_cap=r),o!=null&&(i.security_score=o);let l=$(i),u=`${this.baseUrl}/api/moralis/proxy/discovery/tokens/top-gainers${l?`?${l}`:""}`,d=await _.get(u);if(!d.ok)throw new Error(N(d));let p=d.data,m=p?.data??p;return{success:!0,data:Hn(m)}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await W(F)}return{success:!1,error:s}}async getTopLosers(e){let{chain:t,timeFrame:n,minMarketCap:r,securityScore:o}=e??{},s="Unknown error";for(let a=1;a<=U;a++)try{let i={};t&&(i.chain=V(t)),n&&(i.time_frame=n),r!=null&&(i.min_market_cap=r),o!=null&&(i.security_score=o);let l=$(i),u=`${this.baseUrl}/api/moralis/proxy/discovery/tokens/top-losers${l?`?${l}`:""}`,d=await _.get(u);if(!d.ok)throw new Error(N(d));let p=d.data,m=p?.data??p;return{success:!0,data:Hn(m)}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await W(F)}return{success:!1,error:s}}async enrichTokenPrices(e,t){return this.pantograph.enrichTokenPrices(e,t)}};var Ke=class extends v{name="get-token-info";description='Get information about a specific cryptocurrency token: price, metadata, 24h change, market data, and user holdings. Use this when the user asks about: token price, token info, "what is X token", "tell me about X", "how much is X worth", or any token-specific question. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea';category="blockchain-data";parameters=[{name:"keyword",type:"string",description:'Token symbol, name, or contract address (e.g. "USDC", "Pepe", "ETH", "0x1234\u2026"). If a 0x address is provided, performs a precise contract lookup. Otherwise searches by name/symbol.',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"prompt",type:"string",description:`A rich, multi-angle research prompt sent to Gemini with Google Search grounding (real-time web access). The prompt must be self-contained \u2014 Gemini does NOT see the user's original message, only this prompt. ALWAYS include: the token symbol/name, the chain, and an instruction to use web search for up-to-date info. When the user asks an open/broad question (e.g. "show me X", "tell me about X", "what is X"), expand the prompt to cover ALL of these angles in one request: (1) what the token/project is and its core utility, (2) current USD price, 24h change, market cap, FDV, 24h volume, (3) recent news, announcements, or notable events in the last 7-30 days, (4) on-chain activity signals (holders, liquidity, unusual flows), (5) team/protocol updates or roadmap items, (6) key risks, red flags, or controversies, (7) sentiment and notable community discussion. When the user asks a narrow question (e.g. "price of X"), still include 2-3 supporting angles for context. Aim for 3-6 sentences. Be specific to the token. Do not use generic filler. Example for "show me JPYT": "Research the JPYT token on Ethereum using up-to-date web sources. Cover: what JPYT is and who issues it; current USD price, 24h change, market cap, FDV, and 24h volume; recent news or announcements in the last 30 days; on-chain signals such as holder count, liquidity, and unusual volume patterns; team or protocol updates; key risks or red flags; and overall sentiment."`,required:!0},{name:"buy_button_label",type:"string",description:`Label for the "Buy" suggestion button shown under the token info, IN THE USER'S CURRENT LANGUAGE. Use the exact placeholder "{token}" (do not translate the braces) for the token symbol; put the localized verb directly in the text. English example: "Buy {token}". Vietnamese example: "Mua {token}". Japanese example: "{token} \u8CFC\u5165". Always include {token}.`,required:!0},{name:"buy_prompt_template",type:"string",description:`A short "buy" command IN THE USER'S CURRENT LANGUAGE, submitted as the next user turn when the Buy button is clicked. Use the exact placeholder "{token}" (do not translate the braces) for the token symbol; put the localized verb directly in the text. English example: "buy {token}". Vietnamese example: "mua {token}". Japanese example: "{token} \u8CFC\u5165". Always include {token}.`,required:!0}];service;llm;constructor(e){super(),this.service=new E(e),this.llm=new pe({})}async run(e,t){let n=e.keyword||"",r=R(e.chain,t),o=e.prompt||"",s=e.buy_button_label||"",a=e.buy_prompt_template||"",i=t?.walletAddress||void 0;return n?await this.searchToken(n,r,o,s,a,i):{error:"Missing required parameter: keyword"}}async execute(e,t){let n=await super.execute(e,t),r=n.data;return n.success&&r&&Array.isArray(r.actionButtons)&&r.actionButtons.length>0&&(n.actionButtons=r.actionButtons),n}async searchToken(e,t,n,r,o,s){let a=[s?`User's connected wallet address: ${s}`:null,t?`Current chain (hex chain ID): ${t}, only consider tokens on this chain`:null,"Use this context to personalize your answer where relevant (e.g. whether the user holds the token, chain-specific data)."].filter(Boolean).join(`
3
- `),[i,l,u]=await Promise.all([this.service.searchTokensByKey({key:e,chain:t}),s?this.findTokenInWallet(s,t,e):Promise.resolve(null),n?this.askGeminiWithSearch(n,a):Promise.resolve(null)]),d=i.success&&i.data?Array.isArray(i.data)?i.data:[]:[],p=e.toLowerCase(),m=b=>b.symbol?.toLowerCase()===p||b.token_address?.toLowerCase()===p,g=[...d].sort((b,T)=>(m(b)?0:1)-(m(T)?0:1)),f=null,h=g[0];if(h?.token_address){let b=await this.service.getTokenMetadata({address:h.token_address,chain:t});b.success&&b.data&&(f=b.data)}!f&&h&&(f={...h});let y=f?.symbol?.trim(),k=y?[{label:this.fillTemplate(r,y,"Buy {token}"),prompt:this.fillTemplate(o,y,"buy {token}")}]:[];return{_instructions:'Present the token info in a clear summary. Include: token name & symbol, contract address (shortened), current USD price, 24h price change % (prefix "+" for positive, "-" for negative), market cap, fully diluted valuation, 24h trading volume, liquidity, and holder count (if available). If the user holds this token, prominently show their balance and USD value. If research is present, weave its substance naturally into your answer \u2014 fold the relevant points into the description, context, and any notable news or risks. Do NOT add a labelled "AI Analysis" section or any similar heading, and do NOT mention that the analysis came from AI; just write it as part of the token summary. Format numbers in a human-readable way (e.g. $1.23, +5.67%, $12.5M). If a field is null, omit it. A "Buy" button is shown below your reply automatically \u2014 do NOT mention it, list it, or repeat its text.',chain:t,userHoldsToken:!!l,walletBalance:l,token:f,...d.length>1?{searchResults:g.slice(0,10)}:{},research:u,...k.length>0?{actionButtons:k}:{}}}fillTemplate(e,t,n){return(e&&e.includes("{token}")?e:n).replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}async askGeminiWithSearch(e,t){let n=Date.now();try{return(await this.llm.chat([...t?[{role:"system",content:t,timestamp:n}]:[],{role:"user",content:e,timestamp:n}],void 0,{googleSearch:!0,maxRetries:1})).text?.trim()||null}catch{return null}}async findTokenInWallet(e,t,n){if(!n)return null;let r=await this.service.getWalletTokenBalances({address:e,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0});if(!r.success||!r.data?.result)return null;let o=n.toLowerCase(),s=r.data.result.find(a=>a.token_address.toLowerCase()===o||a.symbol?.toLowerCase()===o||a.name?.toLowerCase().includes(o));return s||null}};var Ye=class extends v{name="get-token-holders";description='Get holder statistics and analytics for an ERC-20 token. Returns: total holder count, holder count changes over time (5min/1h/6h/24h/3d/7d/30d), how holders acquired the token (swap/transfer/airdrop), supply concentration (% held by top 10/25/50/100/250/500 holders), and holder size distribution (whales, sharks, dolphins, fish, octopus, crabs, shrimps). Use tokenSymbol (e.g. "USDC") to look up by name \u2014 the tool auto-resolves the contract address. Use contractAddress when you already have the exact 0x address. Use this tool for questions like: "How many holders does USDC have?" (tokenSymbol=USDC), "Is USDC gaining or losing holders?" (tokenSymbol=USDC \u2192 holderChange), "How many new holders did ETH get in the last 7 days?" (tokenSymbol=WETH), "How concentrated is USDC ownership?" (tokenSymbol=USDC \u2192 holderSupply), "What % of supply do the top 10 holders control?" (tokenSymbol=USDC), "How many whales hold USDT?" (tokenSymbol=USDT \u2192 holderDistribution), "Did most holders buy via swap or transfer?" (tokenSymbol=USDC \u2192 holdersByAcquisition). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. ';category="blockchain-data";parameters=[{name:"tokenSymbol",type:"string",description:`Token symbol (e.g. "USDC", "USDT", "WETH"). The tool resolves the contract address automatically: first checks the connected wallet's balances, then searches by symbol. Takes precedence over contractAddress when both are given.`,required:!1},{name:"contractAddress",type:"string",description:"ERC-20 token contract address (0x\u2026). Use when the exact address is already known. Ignored if tokenSymbol is provided.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=R(e.chain,t),r=t?.walletAddress||void 0,o=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,s=typeof e.contractAddress=="string"&&e.contractAddress?e.contractAddress.trim():void 0;if(!o&&!s)return{error:'Provide either tokenSymbol (e.g. "USDC") or contractAddress (0x\u2026).'};let a,i;if(o){let d=await this.resolveContractAddress(o,n,r);if(!d)return{error:`Cannot resolve token symbol "${o}" to a contract address on ${n?L(n):"the selected chain"}. Try passing contractAddress directly.`};s=d.address,a=d.symbol,i=d.name}let l=await this.service.getTokenHolders({address:s,chain:n});if(!l.success)return{error:l.error||"Failed to fetch token holder stats"};let u=l.data;return{token:{symbol:a??o??null,name:i??null,contractAddress:s,chain:n||"default"},totalHolders:u.totalHolders,holdersByAcquisition:u.holdersByAcquisition,holderChange:u.holderChange,holderSupply:u.holderSupply,holderDistribution:u.holderDistribution}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};var je=class extends v{name="get-token-analytics";description=`Get detailed trading analytics for a single ERC-20 token, broken down across 5min / 1h / 6h / 24h windows. Returns: buy volume, sell volume, unique buyers, unique sellers, buy/sell transaction counts, unique wallets, price % change, current USD price, total liquidity and fully-diluted valuation (FDV). Use tokenSymbol (e.g. "PEPE") to look up by name \u2014 the tool auto-resolves the contract address. Use contractAddress when you already have the exact 0x address. Use this tool for questions like: "How is PEPE trading today?" (tokenSymbol=PEPE), "24h buy vs sell volume for USDC?" (tokenSymbol=USDC \u2192 totalBuyVolume vs totalSellVolume), "Is buying or selling pressure stronger for this token?", "How many unique wallets touched this token in the last hour?" (\u2192 uniqueWallets.1h), "What's the FDV and liquidity of X?" (\u2192 totalFullyDilutedValuation, totalLiquidity), "Price change over 6h?" (\u2192 pricePercentChange.6h). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.`;category="blockchain-data";parameters=[{name:"tokenSymbol",type:"string",description:`Token symbol (e.g. "PEPE", "USDC", "WETH"). The tool resolves the contract address automatically: first checks the connected wallet's balances, then searches by symbol. Takes precedence over contractAddress when both are given.`,required:!1},{name:"contractAddress",type:"string",description:"ERC-20 token contract address (0x\u2026). Use when the exact address is already known. Ignored if tokenSymbol is provided.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=R(e.chain,t),r=t?.walletAddress||void 0,o=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,s=typeof e.contractAddress=="string"&&e.contractAddress?e.contractAddress.trim():void 0;if(!o&&!s)return{error:'Provide either tokenSymbol (e.g. "PEPE") or contractAddress (0x\u2026).'};let a,i;if(o){let d=await this.resolveContractAddress(o,n,r);if(!d)return{error:`Cannot resolve token symbol "${o}" to a contract address on ${n?L(n):"the selected chain"}. Try passing contractAddress directly.`};s=d.address,a=d.symbol,i=d.name}let l=await this.service.getTokenAnalytics({address:s,chain:n});if(!l.success)return{error:l.error||"Failed to fetch token analytics"};let u=l.data??{};return{token:{symbol:a??o??null,name:i??null,contractAddress:s,chain:n||"default"},usdPrice:u.usdPrice??null,totalLiquidity:u.totalLiquidity??null,totalFullyDilutedValuation:u.totalFullyDilutedValuation??null,totalBuyVolume:u.totalBuyVolume??null,totalSellVolume:u.totalSellVolume??null,totalBuyers:u.totalBuyers??null,totalSellers:u.totalSellers??null,totalBuys:u.totalBuys??null,totalSells:u.totalSells??null,uniqueWallets:u.uniqueWallets??null,pricePercentChange:u.pricePercentChange??null}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};var ze=class extends v{name="get-token-score";description=`Get a composite 0\u2013100 health/safety score for a single ERC-20 token, plus the metrics behind it. Returns: overall score, last-updated timestamp, current USD price, paired liquidity, multi-timeframe volume buckets (10m / 30m / 1h / 4h / 12h / 1d / 7d / 30d), same buckets for transaction counts, and supply info (total supply + % held by top 10 holders). Use tokenSymbol (e.g. "PEPE") to look up by name \u2014 the tool auto-resolves the contract address. Use contractAddress when you already have the exact 0x address. Use this tool for questions like: "Is PEPE a safe token?" (tokenSymbol=PEPE \u2192 score), "What's the health/score of USDC?", "How concentrated is X's supply among whales?" (\u2192 metrics.supply.top10Percent), "How much volume did this token do over the last 7 days?" (\u2192 metrics.volumeUsd.7d), "Transaction count for this token in the last hour?" (\u2192 metrics.transactions.1h). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.`;category="blockchain-data";parameters=[{name:"tokenSymbol",type:"string",description:`Token symbol (e.g. "PEPE", "USDC", "WETH"). The tool resolves the contract address automatically: first checks the connected wallet's balances, then searches by symbol. Takes precedence over contractAddress when both are given.`,required:!1},{name:"contractAddress",type:"string",description:"ERC-20 token contract address (0x\u2026). Use when the exact address is already known. Ignored if tokenSymbol is provided.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=R(e.chain,t),r=t?.walletAddress||void 0,o=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,s=typeof e.contractAddress=="string"&&e.contractAddress?e.contractAddress.trim():void 0;if(!o&&!s)return{error:'Provide either tokenSymbol (e.g. "PEPE") or contractAddress (0x\u2026).'};let a,i;if(o){let p=await this.resolveContractAddress(o,n,r);if(!p)return{error:`Cannot resolve token symbol "${o}" to a contract address on ${n?L(n):"the selected chain"}. Try passing contractAddress directly.`};s=p.address,a=p.symbol,i=p.name}let l=await this.service.getTokenScore({address:s,chain:n});if(!l.success)return{error:l.error||"Failed to fetch token score"};let u=l.data??{},d=u.metrics??{};return{token:{symbol:a??o??null,name:i??null,contractAddress:s,chain:n||"default"},score:u.score??null,updatedAt:u.updatedAt??null,metrics:{usdPrice:d.usdPrice??null,liquidityUsd:d.liquidityUsd??null,volumeUsd:d.volumeUsd??null,transactions:d.transactions??null,supply:d.supply??null}}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};var Qe=class extends v{name="get-trending-tokens";description='List tokens trending on-chain right now, ranked by trading activity, volume, liquidity and holder growth. Returns price, market cap, liquidity, holder count, and multi-timeframe buckets (1h / 4h / 12h / 24h) of price change, volume, transaction counts, and unique buyers/sellers. Cross-chain by default \u2014 omit `chain` for a global ranking, or pass it to filter to one network. Use this tool for questions like: "What tokens are trending right now?", "Show me hot tokens", "What\'s popular on Base today?", "Which tokens have the highest 24h volume?", "Biggest movers in the last hour?". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea, and more.';category="blockchain-data";parameters=[{name:"chain",type:"string",description:'Optional hex chain ID to scope results to a single network: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user names a chain. Omit to return cross-chain trending results.',required:!1},{name:"limit",type:"number",description:'Total number of trending tokens to return (chain default tokens + top gainers combined). Only set this when the user asks for a specific count (e.g. "show 20 trending tokens" \u2192 20). Omit otherwise \u2014 when omitted, returns all default tokens plus 5 top gainers.',required:!1},{name:"buy_button_label",type:"string",description:`Label template for the per-token "Buy" suggestion buttons, IN THE USER'S CURRENT LANGUAGE. One button is rendered per trending token, with "{token}" replaced by that token's symbol. Use the exact placeholder "{token}" (do not translate the braces); put the localized verb in the text. English example: "Buy {token}". Vietnamese example: "Mua {token}". Japanese example: "{token} \u8CFC\u5165". Always include {token}.`,required:!0},{name:"buy_prompt_template",type:"string",description:`A short "buy" command template IN THE USER'S CURRENT LANGUAGE, submitted as the next user turn when a token's Buy button is clicked. "{token}" is replaced by that token's symbol. Use the exact placeholder "{token}" (do not translate the braces); put the localized verb in the text. English example: "buy {token}". Vietnamese example: "mua {token}". Japanese example: "{token} \u8CFC\u5165". Always include {token}.`,required:!0}];service;constructor(e){super(),this.service=new Z({baseUrl:e?.pantographUrl})}async run(e,t){let n=hn(e.chain,t),r=e.buy_button_label||"",o=e.buy_prompt_template||"",s=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.floor(e.limit):void 0,a=await this.service.getTrendingTokens({chain:n,limit:s});if(!a.success)return{error:a.error||"Failed to fetch trending tokens"};let i=a.data??[],l=new Set,u=[];for(let d of i){let p=d.symbol?.trim();!p||l.has(p.toLowerCase())||(l.add(p.toLowerCase()),u.push({label:this.fillTemplate(r,p,"Buy {token}"),prompt:this.fillTemplate(o,p,"buy {token}")}))}return{_instructions:`Start with a one-line header like "Here are some of the top trending tokens on <chain name> right now:". Then present the tokens as a numbered list. For EACH token render exactly these lines (omit any line whose value is null):
1
+ "use strict";var cr=Object.create;var hn=Object.defineProperty;var ur=Object.getOwnPropertyDescriptor;var dr=Object.getOwnPropertyNames;var mr=Object.getPrototypeOf,pr=Object.prototype.hasOwnProperty;var hr=(c,e)=>{for(var t in e)hn(c,t,{get:e[t],enumerable:!0})},uo=(c,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of dr(e))!pr.call(c,r)&&r!==t&&hn(c,r,{get:()=>e[r],enumerable:!(n=ur(e,r))||n.enumerable});return c};var gr=(c,e,t)=>(t=c!=null?cr(mr(c)):{},uo(e||!c||!c.__esModule?hn(t,"default",{value:c,enumerable:!0}):t,c)),fr=c=>uo(hn({},"__esModule",{value:!0}),c);var Ra={};hr(Ra,{AI_AGENT_TOOL_NAMES:()=>On,AgentCore:()=>Hn,ApproveTokenTool:()=>qt,BaseNftMessageTool:()=>fe,BaseSwapService:()=>Ue,BaseTool:()=>v,BaseWalletActionTool:()=>le,BuyTokenTool:()=>Vt,ChatHistory:()=>Yt,DEFAULT_CHAIN:()=>ge,DEFAULT_PROVIDER:()=>on,DEFAULT_RPC_BY_CHAIN:()=>ie,DebridgeAdapter:()=>vt,EstimatePoolYieldTool:()=>_t,GeminiProvider:()=>se,GeminiSearchAiTool:()=>mt,HEX_TO_PANTOGRAPH:()=>Co,KnowledgeBase:()=>jt,MoralisService:()=>E,NFTContractInfoTool:()=>ut,NFTMetadataTool:()=>dt,NFT_AGENT_TOOL_NAMES:()=>Bn,OpenAddLiquidityFormTool:()=>At,POOL_AGENT_TOOL_NAMES:()=>qn,PantographService:()=>ee,PoolByAddressTool:()=>yt,PoolDetailTool:()=>gt,PoolFallbackAiTool:()=>pt,PoolSearchTool:()=>ft,PoolService:()=>Me,PreviewAddLiquidityTool:()=>Pt,RelayAdapter:()=>St,Router:()=>zt,SUPPORTED_CHAINS:()=>$e,SUPPORTED_CHAINS_LABEL:()=>Ne,SWAP_PROVIDER_CONFIG:()=>Cn,SendNativeTool:()=>Bt,SendNftTool:()=>Gt,SendTokenTool:()=>Ot,Subagent:()=>j,SubgraphAddPoolTool:()=>Dt,SubgraphCoinPoolPairsTool:()=>It,SubgraphPoolByAddressTool:()=>Rt,SubgraphPoolByPositionIdTool:()=>Et,SubgraphPoolSearchTool:()=>Ct,SubgraphPositionDetailTool:()=>Nt,SubgraphTrendingPoolsTool:()=>Ut,Summarizer:()=>Kt,SwapServiceFactory:()=>Le,SwapTokenTool:()=>Ht,Synthesizer:()=>Qt,TOKEN_AGENT_TOOL_NAMES:()=>Mn,TRANSFER_TOPIC:()=>Ko,TRENDING_DEFAULT_BY_HEX_CHAIN:()=>Uo,TokenAnalyticsTool:()=>je,TokenHoldersTool:()=>Ke,TokenInfoTool:()=>Ye,TokenScoreTool:()=>ze,ToolRegistry:()=>Fe,TopGainersTool:()=>Xe,TopPoolsTool:()=>ht,TransactionByHashTool:()=>lt,TrendingTokensTool:()=>Qe,UNISWAP_CHAIN_SLUG:()=>sn,UpstashKnowledgeBase:()=>Re,WALLET_ACTION_AGENT_TOOL_NAMES:()=>Fn,WALLET_AGENT_TOOL_NAMES:()=>Ln,WalletApprovalsTool:()=>rt,WalletDefiPositionsTool:()=>at,WalletDefiProtocolPositionsTool:()=>it,WalletDefiSummaryTool:()=>st,WalletHistoryTool:()=>Ze,WalletNFTsTool:()=>ct,WalletNetWorthTool:()=>kn,WalletNftTransfersTool:()=>tt,WalletPnlSummaryTool:()=>nt,WalletPnlTool:()=>ot,WalletTokenBalancesTool:()=>Je,WalletTokenTransfersTool:()=>et,ZERO_ADDRESS:()=>O,buildRangePresets:()=>ao,buildUniswapPoolUrl:()=>oe,clampTick:()=>ve,configureSupportedChains:()=>yn,createAiAgent:()=>dn,createDefaultSubagents:()=>$n,createNftAgent:()=>un,createPoolAgent:()=>mn,createTokenAgent:()=>cn,createWalletActionAgent:()=>pn,createWalletAgent:()=>ln,ethCallAt:()=>Ho,ethCallByChain:()=>so,getAffiliateFee:()=>kt,getChainMeta:()=>te,getDefaultRpcUrl:()=>Us,getGatewayAddress:()=>Rn,getNativeTokenInfo:()=>xt,getPositionManagerAddress:()=>En,getProviderByChain:()=>_n,ingestKnowledgeBase:()=>ir,priceToTick:()=>De,resolveRpcUrl:()=>Tt,roundTickToSpacing:()=>Te,rpcCall:()=>xe,setRpcOverrides:()=>ro,swapServiceFactory:()=>ye,tickToPrice:()=>ue,toPantographChain:()=>en});module.exports=fr(Ra);var po=gr(require("axios"));var mo="0.2.12";var br=`keyring-agent-core/${mo} (Keyring Chatbot Agent; +https://keyring.app)`,wr=3e4,kr=typeof process<"u"&&process.versions?.node!=null,Yn=class{instance;constructor(e={}){this.instance=po.default.create({baseURL:e.baseURL,timeout:e.timeout??wr,validateStatus:()=>!0,headers:{Accept:"application/json",...kr?{"User-Agent":e.userAgent??br}:{},...e.headers}})}async request(e,t,n,r={}){let o=await this.instance.request({...r,method:e,url:t,data:n}),s={};for(let[a,i]of Object.entries(o.headers??{}))i!=null&&(s[a.toLowerCase()]=String(i));return{ok:o.status>=200&&o.status<300,status:o.status,statusText:o.statusText,data:o.data,headers:s}}get(e,t){return this.request("GET",e,void 0,t)}post(e,t,n){return this.request("POST",e,t,n)}put(e,t,n){return this.request("PUT",e,t,n)}patch(e,t,n){return this.request("PATCH",e,t,n)}delete(e,t){return this.request("DELETE",e,void 0,t)}},_=new Yn;function N(c){let{data:e,status:t,statusText:n}=c,r=n?`HTTP ${t} ${n}`:`HTTP ${t}`;if(typeof e=="string")return e.trim()||r;if(e&&typeof e=="object"){let o=e,s=i=>i&&typeof i=="object"?i.message:void 0,a=[o.message,o.errorMessage,o.error,o.error_description,o.detail,s(o.error),s(o.data)];for(let i of a)if(typeof i=="string"&&i.trim())return i.trim()}return r}var ho={string:"STRING",number:"NUMBER",boolean:"BOOLEAN",object:"OBJECT",array:"ARRAY"},Tr="https://nft-demo.keyring.app/api/gemini-stable",vr=new Set([408,429,500,502,503,504]),go=3e4,gn=c=>new Promise(e=>setTimeout(e,c));function fn(c,e,t){if(t!=null&&t>0)return Math.min(t,go);let n=Math.min(e*2**(c-1),go);return n/2+Math.random()*(n/2)}function Sr(c){if(!c)return null;let e=Number(c);if(Number.isFinite(e))return Math.max(0,e*1e3);let t=Date.parse(c);return Number.isNaN(t)?null:Math.max(0,t-Date.now())}var se=class{model;maxTokens;temperature;baseUrl;constructor(e){this.model=e.model??"gemini-2.5-flash-lite",this.maxTokens=e.maxTokens??4096,this.temperature=e.temperature??1,this.baseUrl=e.baseUrl??Tr}async chat(e,t,n){let{systemInstruction:r,contents:o}=this.toContents(e),{maxRetries:s=5,retryDelayMs:a=1e3}=n??{},i=o.length>0?o:[{role:"user",parts:[{text:"(continue)"}]}];if(n?._debug){console.log("[GeminiProvider] turn sequence:");for(let m of i){let p=m.parts.map(h=>h.functionCall?`fc:${h.functionCall.name}`:h.functionResponse?`fr:${h.functionResponse.name}`:`text:${(h.text??"").slice(0,40)}`).join(", ");console.log(` ${m.role}: [${p}]`)}}let l={contents:i,generationConfig:{maxOutputTokens:this.maxTokens,temperature:this.temperature}};r&&(l.systemInstruction=r),t?.length?(l.tools=[{functionDeclarations:this.toFunctionDeclarations(t)}],n?.forceToolUse&&(l.toolConfig={functionCallingConfig:{mode:"ANY"}})):n?.googleSearch&&(l.tools=[{googleSearch:{}}]);let u=`${this.baseUrl}/v1beta/models/${this.model}:generateContent`,d="Unknown error";for(let m=1;m<=s;m++){let p;try{p=await _.post(u,l,{headers:{"Content-Type":"application/json"}})}catch(b){if(d=b instanceof Error?b.message:String(b),m>=s)break;await gn(fn(m,a));continue}if(!p.ok){let b=typeof p.data=="string"?p.data:JSON.stringify(p.data)||p.statusText;if(d=`Gemini proxy error ${p.status}: ${b}`,!vr.has(p.status))throw new Error(d);if(m>=s)break;let T=Sr(p.headers["retry-after"]??null);await gn(fn(m,a,T));continue}if(p.data==null||typeof p.data!="object"){if(d="Gemini proxy returned invalid JSON",m>=s)break;await gn(fn(m,a));continue}let h=p.data,g=h.candidates?.[0];if(!g)return{text:"",toolCalls:[]};let f="",y=[],k=g.content?.parts;if(!Array.isArray(k)||k.length===0){let b=g.finishReason;if(!(b==="SAFETY"||b==="RECITATION")&&m<s){d=`Gemini returned an empty turn (finishReason=${b??"none"})`,await gn(fn(m,a));continue}return{text:"",toolCalls:[]}}for(let b of k)b.text&&(f+=b.text),b.functionCall&&y.push({toolName:b.functionCall.name,args:b.functionCall.args??{},callId:b.functionCall.id??`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`});let w=h.usageMetadata?{promptTokens:h.usageMetadata.promptTokenCount??0,completionTokens:h.usageMetadata.candidatesTokenCount??0,totalTokens:h.usageMetadata.totalTokenCount??0}:void 0;return{text:f,toolCalls:y,usage:w}}throw new Error(d)}toContents(e){let t=[],n=[];for(let a of e){if(a.role==="system"){t.push(a.content);continue}if(a.role==="tool"){n.push({role:"function",parts:[{functionResponse:{name:a.toolName??"unknown",response:{result:a.content},...a.toolCallId?{id:a.toolCallId}:{}}}]});continue}if(a.role==="assistant"&&a.toolCalls?.length){n.push({role:"model",parts:a.toolCalls.map(i=>({functionCall:{name:i.toolName,args:i.args,...i.callId?{id:i.callId}:{}}}))});continue}n.push({role:a.role==="assistant"?"model":"user",parts:[{text:a.content}]})}let r=[];for(let a of n){let i=r[r.length-1],l=a.parts.some(d=>d.functionCall),u=i?.parts.some(d=>d.functionCall);i&&i.role===a.role&&!l&&!u?i.parts.push(...a.parts):r.push({...a,parts:[...a.parts]})}let o=[];for(let a=0;a<r.length;a++){let i=r[a],l=o[o.length-1];i.role==="function"&&!l?.parts.some(d=>d.functionCall)||i.role==="model"&&i.parts.some(d=>d.functionCall)&&!(r[a+1]?.role==="function")||o.push(i)}for(;o.length>0&&o[0].role!=="user";)o.shift();return{systemInstruction:t.length?{role:"user",parts:[{text:t.join(`
2
+ `)}]}:null,contents:o}}toFunctionDeclarations(e){return e.map(t=>{let n={},r=[];for(let o of t.parameters){let s={type:ho[o.type]??"STRING",description:o.description};o.type==="array"&&(s.items={type:ho[o.items?.type??"string"]??"STRING"}),n[o.name]=s,o.required&&r.push(o.name)}return{name:t.name,description:t.description,parameters:{type:"OBJECT",properties:n,required:r}}})}};var Fe=class{tools=new Map;register(e){if(this.tools.has(e.name))throw new Error(`Tool "${e.name}" is already registered`);this.tools.set(e.name,e)}unregister(e){return this.tools.delete(e)}get(e){return this.tools.get(e)}has(e){return this.tools.has(e)}getDefinitions(){return Array.from(this.tools.values()).map(e=>({name:e.name,description:e.description,parameters:e.parameters,category:e.category,kind:e.kind}))}getDefinitionsByCategory(e){return this.getDefinitions().filter(t=>t.category===e)}async execute(e,t,n){let r=this.tools.get(e);if(!r)return{toolName:e,callId:`err_${Date.now()}`,success:!1,error:`Tool "${e}" not found in registry`,duration:0};let o=Date.now();try{return{...await r.execute(t,n),duration:Date.now()-o}}catch(s){return{toolName:e,callId:`err_${Date.now()}`,success:!1,error:s instanceof Error?s.message:String(s),duration:Date.now()-o}}}get size(){return this.tools.size}listNames(){return Array.from(this.tools.keys())}};var $e=["0x1","0xa","0x38","0x89","0x2105","0xa4b1","0xa86a","0xe708"],xr=new Set($e),Ee=[...$e],fo=new Set(Ee),ge="0x1",Ne="Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea";function Ar(c){return c.map(e=>Ve[e]??e).join(", ")}function yn(c){let e=new Set;if(c)for(let n of c){let r=Kn(n);r&&e.add(r)}let t=$e.filter(n=>!e.has(n));Ee=t.length>0?[...t]:[...$e],fo=new Set(Ee),ge=Ee.includes("0x1")?"0x1":Ee[0],Ne=Ar(Ee)}var Pr={"0x1":"0x1",1:"0x1",eth:"0x1",ether:"0x1",ethereum:"0x1","ethereum mainnet":"0x1",mainnet:"0x1","0xa":"0xa",10:"0xa",op:"0xa",optimism:"0xa","op mainnet":"0xa","optimism mainnet":"0xa","0x38":"0x38",56:"0x38",bsc:"0x38",bnb:"0x38",binance:"0x38","bnb chain":"0x38","bnb smart chain":"0x38","binance chain":"0x38","binance smart chain":"0x38","0x89":"0x89",137:"0x89",matic:"0x89",polygon:"0x89","polygon mainnet":"0x89","polygon pos":"0x89","0x2105":"0x2105",8453:"0x2105",base:"0x2105","base mainnet":"0x2105","0xa4b1":"0xa4b1",42161:"0xa4b1",arb:"0xa4b1",arbitrum:"0xa4b1","arbitrum one":"0xa4b1","arbitrum mainnet":"0xa4b1","0xa86a":"0xa86a",43114:"0xa86a",avax:"0xa86a",avalanche:"0xa86a","avalanche c-chain":"0xa86a","avalanche cchain":"0xa86a","avax c-chain":"0xa86a","0xe708":"0xe708",59144:"0xe708",linea:"0xe708","linea mainnet":"0xe708"};function he(c){let e=Kn(c);return e&&fo.has(e)?e:null}function Kn(c){if(typeof c!="string")return null;let e=c.trim().toLowerCase().replace(/\s+/g," ");if(!e)return null;let t=Pr[e];if(t)return t;let n=null;return/^0x[0-9a-f]+$/.test(e)?n=e:/^[0-9]+$/.test(e)&&(n="0x"+Number(e).toString(16)),n&&xr.has(n)?n:null}function bn(c){return he(c)!==null}var We=class extends Error{constructor(t){super(`Chain "${t}" is not supported. Supported chains: ${Ne}.`);this.requested=t;this.name="UnsupportedChainError"}requested;code="unsupported_chain"},_e=c=>typeof c=="string"&&c.trim()!=="";function yo(c,e){if(_e(c)){let t=he(c);if(t)return t;throw new We(c.trim())}if(_e(e?.chain)){let t=he(e.chain);if(t)return t;throw new We(e.chain.trim())}return null}function wn(c,e){return he(c)??he(e?.chain)??void 0}var Ve={"0x1":"Ethereum","0xa":"Optimism","0x38":"BSC","0x89":"Polygon","0x2105":"Base","0xa4b1":"Arbitrum","0xa86a":"Avalanche","0xe708":"Linea"};function I(c){let e=Kn(c);return e&&Ve[e]?Ve[e]:typeof c=="string"&&c.trim()?c.trim():"the requested chain"}var _r={bnb:"0x38",matic:"0x89",pol:"0x89",avax:"0xa86a"};function bo(c){if(typeof c!="string")return null;let e=c.trim().toLowerCase();return e?_r[e]??null:null}var Cr={"0x1":"ETH","0xa":"ETH","0x38":"BNB","0x89":"POL","0x2105":"ETH","0xa4b1":"ETH","0xa86a":"AVAX","0xe708":"ETH"};function wo(c){let e=he(c);return e?Cr[e]??null:null}function He(c,e){if(!_e(c))return null;let t=he(c),n=he(e?.chain);return!t||!n||t===n?null:{requested:t,connected:n,requestedLabel:Ve[t]??t,connectedLabel:Ve[n]??n}}function Ur(){return Ee.map(c=>{let e=Ve[c]??c;return{label:e,prompt:e}})}var K=class extends Error{code="needs_chain_selection";buttons;constructor(e){super("Wallet is connected but no chain is set \u2014 ask the user to pick one."),this.name="NeedsChainSelectionError",this.buttons=e??Ur()}},Xt=class extends Error{code="no_wallet_connected";constructor(){super("No wallet address available \u2014 ask the user to connect a wallet or supply one."),this.name="NoWalletConnectedError"}};function Y(c,e){if(_e(c))return c.trim();if(_e(e?.walletAddress))return e.walletAddress.trim();throw new Xt}function R(c,e){if(_e(c)){let t=he(c);if(t)return t;throw new We(c.trim())}if(_e(e?.chain)){let t=he(e.chain);if(t)return t;throw new We(e.chain.trim())}if(_e(e?.walletAddress))throw new K;return ge}var v=class{category;async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s=Rr(o);return{toolName:this.name,callId:n,success:!0,data:s.data,duration:Date.now()-r,...s.actionButtons?{actionButtons:s.actionButtons}:{}}}catch(o){return o instanceof K?{toolName:this.name,callId:n,success:!0,data:{_instructions:"The user has a connected wallet but no chain is set. Ask them which chain to use \u2014 the chain-picker buttons rendered below your reply let them choose. Keep the message short in the user's language and do NOT list the chain names yourself (the buttons already show them)."},duration:Date.now()-r,actionButtons:o.buttons}:o instanceof Xt?{toolName:this.name,callId:n,success:!0,data:{_instructions:"The user has not connected a wallet. In the user's language, tell them this action needs a wallet \u2014 they can either connect their wallet or include a wallet address (0x\u2026) directly in their question. Keep it short and friendly."},duration:Date.now()-r}:{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}};function Rr(c){if(!c||typeof c!="object")return{data:c};let e=c,t=e.actionButtons;if(!Array.isArray(t)||t.length===0)return{data:c};let{actionButtons:n,...r}=e;return{data:r,actionButtons:t}}var ko=require("js-sha3");function B(c){return typeof c=="string"&&/^0x[0-9a-fA-F]{40}$/.test(c)}function Er(c){return new Uint8Array(ko.keccak256.arrayBuffer(c))}function Jt(c){let e=c.startsWith("0x")?c.slice(2):c,t=e.length%2?"0"+e:e,n=new Uint8Array(t.length/2);for(let r=0;r<n.length;r++)n[r]=parseInt(t.slice(r*2,r*2+2),16);return n}function To(c){return"0x"+Array.from(c).map(e=>e.toString(16).padStart(2,"0")).join("")}function vo(c){return new TextEncoder().encode(c)}function Ie(...c){let e=c.reduce((r,o)=>r+o.length,0),t=new Uint8Array(e),n=0;for(let r of c)t.set(r,n),n+=r.length;return t}function Nr(c,e=32){let t=new Uint8Array(e);return t.set(c,e-c.length),t}function Ir(c,e=32){let t=new Uint8Array(e);return t.set(c),t}function So(c){let e=((c%(1n<<256n)+(1n<<256n))%(1n<<256n)).toString(16).padStart(64,"0");return Jt(e)}function Zt(c){return So(BigInt(c))}function xo(c){return c==="uint"?"uint256":c==="int"?"int256":c.match(/^uint$/)?"uint256":c.match(/^int$/)?"int256":c.replace(/^uint(\[|$)/,"uint256$1").replace(/^int(\[|$)/,"int256$1")}function Ao(c){let e=xo(c.type);if(e==="tuple"||e.startsWith("tuple[")){let t=(c.components??[]).map(Ao).join(","),n=e.startsWith("tuple[")?e.slice(5):"";return`(${t})${n}`}return e}function Dr(c){let e=(c.inputs??[]).map(Ao).join(",");return`${c.name??""}(${e})`}function jn(c,e){let t=Br(c.type);if(t){let[r,o]=t;return Lr({...c,type:o},e,r)}if(c.type==="tuple")return Mr(c,e);if(c.type==="address"){let r=String(e),o=r.startsWith("0x")?r.slice(2):r;return{dynamic:!1,encoded:Nr(Jt(o.toLowerCase()))}}if(c.type==="bool"){let r=new Uint8Array(32);return r[31]=e?1:0,{dynamic:!1,encoded:r}}let n=xo(c.type);if(/^uint\d*$/.test(n))return{dynamic:!1,encoded:Zt(BigInt(e))};if(/^int\d*$/.test(n)){let r=BigInt(e);return r<0n&&(r=r+(1n<<256n)),{dynamic:!1,encoded:So(r)}}if(/^bytes(\d+)$/.test(n)){let r=parseInt(n.slice(5),10),o=typeof e=="string"?Jt(e):e;if(o.length!==r)throw new Error(`bytes${r} expects exactly ${r} bytes, got ${o.length}`);return{dynamic:!1,encoded:Ir(o)}}if(n==="bytes"){let r=typeof e=="string"?Jt(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:Ie(Zt(r.length),s)}}if(n==="string"){let r=typeof e=="string"?vo(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:Ie(Zt(r.length),s)}}throw new Error(`Unsupported ABI type: ${c.type}`)}function zn(c){let e=0;for(let{dynamic:o,encoded:s}of c)e+=o?32:s.length;let t=[],n=[],r=0;for(let{dynamic:o,encoded:s}of c)o?(t.push(Zt(e+r)),n.push(s),r+=s.length):t.push(s);return Ie(...t,...n)}function Lr(c,e,t){let n=t===null;if(!n&&e.length!==t)throw new Error(`Array length mismatch: expected ${t}, got ${e.length}`);let r=!1,o=[];for(let s of e){let a=jn(c,s);a.dynamic&&(r=!0),o.push(a)}if(n||r){let s=zn(o);if(n){let a=Zt(o.length);return{dynamic:!0,encoded:o.length>0?Ie(a,s):a}}return{dynamic:!0,encoded:s}}return{dynamic:!1,encoded:Ie(...o.map(s=>s.encoded))}}function Mr(c,e){let t=c.components??[],n=!1,r=[];for(let o=0;o<t.length;o++){let s=t[o],a=Array.isArray(e)?e[o]:e[s.name??""],i=jn(s,a);i.dynamic&&(n=!0),r.push(i)}return{dynamic:n,encoded:n?zn(r):Ie(...r.map(o=>o.encoded))}}function Br(c){let e=c.match(/^(.*)\[(\d+)?\]$/);return e?[e[2]?Number(e[2]):null,e[1]]:void 0}function Or(c,e){if(c.length!==e.length)throw new Error(`Expected ${c.length} values, got ${e.length}`);if(c.length===0)return"0x";let t=c.map((r,o)=>jn(r,e[o])),n=zn(t);return To(n)}function Ge({abi:c,functionName:e,args:t=[]}){let n=c.find(i=>(i.type==="function"||i.type===void 0)&&i.name===e);if(!n)throw new Error(`Function "${e}" not found in ABI`);let r=Dr(n),o=Er(vo(r)).slice(0,4),s=n.inputs??[],a=s.length===0||t.length===0?new Uint8Array(0):Jt(Or(s,t).slice(2));return To(Ie(o,a))}var qr="https://wallet-api.pantograph.app",Fr="0x0000000000000000000000000000000000000000",Co={"0x1":"ether","0xa":"optimism","0x38":"bsc","0x89":"matic","0x2105":"base","0xa4b1":"arbitrum","0xa86a":"avax","0xe708":"linea"},Uo={"0x1":["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"],"0xa":["0x4200000000000000000000000000000000000006","0x68f180fcce6836688e9084f035309e29bf0a2095","0xc47da4cb96ce65a96844a01bfae509f9d5454534"],"0x38":["0x2170ed0880ac9a755fd29b2688956bd959f933f8","0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c"],"0x89":["0x7ceb23fd6bc0add59e62ac25578270cff1b9f619","0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6"],"0x2105":["0x4200000000000000000000000000000000000006","0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf","0xc47da4cb96ce65a96844a01bfae509f9d5454534"],"0xa4b1":["0x82af49447d8a07e3bd95bd0d56f35241523fbab1","0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f"],"0xa86a":["0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab","0x0555e30da8f98308edb960aa94c0db47230d2b9c"],"0xe708":["0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f","0x3aab2285ddcddad8edf438c1bab47e1a9d05a9b4"]};function en(c){return Co[c.toLowerCase()]||c}function Po(c){let e=[];for(let[t,n]of Object.entries(c))if(n!=null)if(Array.isArray(n))for(let r of n)e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(r))}`);else e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(n))}`);return e.join("&")}function Qn(c){let e=c,t=e.decimals,n=typeof t=="number"?t:typeof t=="string"?parseInt(t,10):NaN,r=e.price,o=r!=null?parseFloat(String(r)):NaN;return{...e,token_address:e.token_address??e.address??"",decimals:Number.isFinite(n)?n:0,...Number.isFinite(o)?{usd_price:o}:{}}}function Z(c){if(c==null)return null;let e=typeof c=="number"?c:parseFloat(String(c));return Number.isFinite(e)?e:null}function _o(c,e){let t=c.decimals,n=typeof t=="number"?t:typeof t=="string"?parseInt(t,10):null,r=Z(c.price_change_percentage_24h)??Z(c.priceChange24h)??Z(c.usd_price_24hr_percent_change)??Z(c.price_change_24h)??Z(c.priceChange);return{chainId:e,tokenAddress:c.address??c.token_address??"",name:c.name??null,symbol:c.symbol??null,decimals:n!=null&&Number.isFinite(n)?n:null,logo:c.logoURI??c.icon_image??c.logo??null,usdPrice:Z(c.price)??Z(c.usd_price),marketCap:Z(c.market_cap)??Z(c.marketCap),totalVolume:{"24h":Z(c.total_volume)??Z(c.volume24h)},pricePercentChange:{"24h":r}}}var ee=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??qr).replace(/\/+$/,"")}async enrichTokenPrices(e,t){if(e.length!==0)try{let n=en(t),r=e.map(u=>u.token_address).join(","),o=`${this.baseUrl}/keyrings/tokens/${n}/v2?addresses=${encodeURIComponent(r)}`,s=await _.get(o);if(!s.ok)return;let a=s.data,i=Array.isArray(a)?a:Array.isArray(a.data)?a.data:[],l=new Map;for(let u of i)u.address&&l.set(u.address.toLowerCase(),{price:u.price!=null?parseFloat(String(u.price)):NaN,icon_image:u.icon_image});for(let u=0;u<e.length;u++){let d=l.get(e[u].token_address.toLowerCase());d&&(d.icon_image&&(e[u].logo=d.icon_image,e[u].thumbnail=d.icon_image),isNaN(d.price)||(e[u].usd_price=d.price,e[u].usd_value=parseFloat(e[u].balance_formatted||"0")*d.price))}}catch{}}async getTokenMetadata(e,t){try{let n=en(t),r=`${this.baseUrl}/keyrings/tokens/${n}/v2?addresses=${encodeURIComponent(e)}`,o=await _.get(r);if(!o.ok)return null;let s=o.data,a=Array.isArray(s)?s:Array.isArray(s.data)?s.data:[],i=e.toLowerCase()===Fr,l=a.find(u=>u.address?.toLowerCase()===e.toLowerCase()||i&&u.address===""||u.token_address?.toLowerCase()===e.toLowerCase()||i&&u.token_address==="");return l?Qn(l):null}catch{return null}}async getTokensMetadata(e,t){let n={};if(e.length===0)return n;try{let r=en(t),o=`${this.baseUrl}/keyrings/tokens/${r}/v2?addresses=${encodeURIComponent(e.join(","))}`,s=await _.get(o);if(!s.ok)return n;let a=s.data,i=Array.isArray(a)?a:Array.isArray(a.data)?a.data:[],l=new Set(e.map(u=>u.toLowerCase()));for(let u of i){let d=u.address?.toLowerCase()||u.token_address?.toLowerCase()||(u.address===""?"":null);d&&l.has(d)&&(n[d]=Qn(u))}}catch{}return n}async searchTokensByKey(e){let{key:t,chain:n}=e;if(!t)return{success:!1,error:"Search key is required"};let o=parseInt(n||ge,16);if(isNaN(o))return{success:!1,error:`Invalid hex chain: ${n}`};try{let s=Po({chainId:o,key:t}),a=`${this.baseUrl}/token-list?${s}`,i=await _.get(a);if(!i.ok)throw new Error(N(i));let l=i.data,u=[];return l?.data?u=Array.isArray(l.data)?l.data:Object.values(l.data).flat():typeof l=="object"&&!Array.isArray(l)&&(u=Object.values(l).flat()),{success:!0,data:u.map(Qn)}}catch(s){return{success:!1,error:s instanceof Error?s.message:"Unknown error"}}}async getTrendingTokens(e){let t=e?.chain||ge,n=5,r=e?.limit&&e.limit>0?Math.floor(e.limit):void 0;try{let o=Uo[t.toLowerCase()]??[],s=r!=null?r+o.length:n+o.length,[a,i]=await Promise.allSettled([this.fetchTopGainers(t,s),o.length>0?this.getTokensMetadata(o,t):Promise.resolve({})]),l=a.status==="fulfilled"?a.value:[],u=i.status==="fulfilled"?o.map(g=>i.value[g.toLowerCase()]).filter(g=>g!=null).map(g=>_o(g,t)):[],d=new Set,m=[];for(let g of u){let f=g.tokenAddress?.toLowerCase();!f||d.has(f)||(d.add(f),m.push(g))}let p=[];for(let g of l){let f=g.tokenAddress?.toLowerCase();!f||d.has(f)||(d.add(f),p.push(g))}let h=r!=null?[...m,...p].slice(0,r):[...m,...p.slice(0,n)];return await this.enrichTrendingMarketData(h,t),{success:!0,data:h}}catch(o){return{success:!1,error:o instanceof Error?o.message:"Unknown error"}}}async enrichTrendingMarketData(e,t){let n=e.filter(o=>o.tokenAddress&&(o.marketCap==null||o.totalVolume?.["24h"]==null));if(n.length===0)return;let r=await this.getTokensMetadata(n.map(o=>o.tokenAddress),t);for(let o of n){let s=r[o.tokenAddress.toLowerCase()];s&&(o.marketCap==null&&(o.marketCap=Z(s.market_cap)??Z(s.marketCap)),o.totalVolume?.["24h"]==null&&(o.totalVolume={"24h":Z(s.total_volume)??Z(s.volume24h)}))}}async fetchTopGainers(e,t,n="24h"){let r=en(e),o=Po({duration:n,limit:t}),s=`${this.baseUrl}/token-list/top-gainers/${r}${o?`?${o}`:""}`,a=[];for(let i=0;i<5;i++){let l=await _.get(s);if(!l.ok)throw new Error(N(l));let u=l.data;if(a=Array.isArray(u)?u:Array.isArray(u.data)?u.data:[],a.length>0)break;i<4&&await new Promise(d=>setTimeout(d,2e3))}return a.map(i=>_o(i,e))}async getTopGainers(e){let t=e?.chain||ge,n=e?.limit&&e.limit>0?Math.floor(e.limit):void 0,r=e?.duration||"24h";try{let o=await this.fetchTopGainers(t,n,r);return await this.enrichTrendingMarketData(o,t),{success:!0,data:o}}catch(o){return{success:!1,error:o instanceof Error?o.message:"Unknown error"}}}};var $r="https://nft.keyring.app",Wr="https://nft-demo.keyring.app",Vr=.01,U=5,F=1e3,Hr="0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",Gr="0x0000000000000000000000000000000000000000";function V(c){return c||ge}function $(c){let e=[];for(let[t,n]of Object.entries(c))if(n!=null)if(Array.isArray(n))for(let r of n)e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(r))}`);else e.push(`${encodeURIComponent(t)}=${encodeURIComponent(String(n))}`);return e.join("&")}function W(c){return new Promise(e=>setTimeout(e,c))}function Yr(c){if(Array.isArray(c))return c;if(c&&typeof c=="object"){let e=c;return Array.isArray(e.result)?e.result.map(t=>({protocol_id:t.protocolId,protocol_name:t.protocolName,protocol_url:t.protocolUrl??"",protocol_logo:t.protocolLogo??"",position:{label:t.position.label,address:t.position.address,balance_usd:t.position.balanceUsd??0,total_unclaimed_usd_value:t.position.unclaimedUsd??0,position_details:t.position.details,tokens:t.position.tokens.map(n=>({token_type:n.tokenType,address:n.address,contract_address:n.address,name:n.name??"",symbol:n.symbol??"",decimals:n.decimals??18,logo:n.logo,balance:n.balance??"0",balance_formatted:n.balanceFormatted??"0",usd_price:n.usdPrice,usd_value:n.usdValue}))}})):Object.keys(e).filter(t=>/^\d+$/.test(t)).sort((t,n)=>Number(t)-Number(n)).map(t=>e[t]).filter(t=>!!t&&typeof t=="object")}return[]}function Xn(c){if(Array.isArray(c))return c;if(c&&typeof c=="object"){let e=c;return Array.isArray(e.result)?e.result:Object.keys(e).filter(t=>/^\d+$/.test(t)).sort((t,n)=>Number(t)-Number(n)).map(t=>e[t]).filter(t=>!!t&&typeof t=="object")}return[]}var E=class{baseUrl;v1BaseUrl;pantograph;constructor(e){this.baseUrl=(e?.baseUrl??$r).replace(/\/+$/,""),this.v1BaseUrl=(e?.v1BaseUrl??Wr).replace(/\/+$/,""),this.pantograph=new ee({baseUrl:e?.pantographUrl})}async getWalletTokenBalances(e){let{address:t,chain:n,tokenAddresses:r,excludeSpam:o=!1,excludeUnverifiedContracts:s=!1}=e;if(!t)return{success:!1,error:"Address is required"};let a=V(n),i="Unknown error";for(let l=1;l<=U;l++)try{let u={chain:a,exclude_spam:o,exclude_unverified_contracts:s,limit:100};r&&(u.token_addresses=r);let d=[],m=null;do{m&&(u.cursor=m);let h=$(u),g=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/tokens?${h}`,f=await _.get(g);if(!f.ok)throw new Error(N(f));let y=f.data,k=y?.data??y;k?.result?.length>0&&d.push(...k.result),m=k?.cursor??null}while(m);let p={result:d};return p?.result?.length>0&&(p.result=p.result.filter(h=>parseFloat(h.balance||"0")>0),p.result=p.result.map(h=>({...h,token_address:h.token_address.toLowerCase()===Hr?Gr:h.token_address})),await this.enrichTokenPrices(p.result,a),p.result=p.result.filter(h=>h.usd_value==null||h.usd_value>=Vr)),{success:!0,data:p}}catch(u){i=u instanceof Error?u.message:"Unknown error",l<U&&await W(F)}return{success:!1,error:i}}async getTokenMetadata(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Token address is required"};let r=V(n),o="Unknown error",s=await this.pantograph.getTokenMetadata(t,r);if(s)return{success:!0,data:s};for(let a=1;a<=U;a++)try{let[i,l]=await Promise.allSettled([_.get(`${this.baseUrl}/api/moralis/proxy/erc20/metadata?${$({chain:r,addresses:[t]})}`),_.get(`${this.baseUrl}/api/moralis/proxy/erc20/${encodeURIComponent(t)}/price?${$({chain:r,include:"percent_change"})}`)]),u=null;if(i.status==="fulfilled"&&i.value.ok){let m=i.value.data;u=(Array.isArray(m)?m:Array.isArray(m?.data)?m.data:[])[0]??null}if(!u){o="Token metadata not found",a<U&&await W(F);continue}let d=null;if(l.status==="fulfilled"&&l.value.ok){let m=l.value.data;d=m?.data??m}return{success:!0,data:{...u,...d?.usd_price?{usd_price:d.usd_price}:{},...d?.usd_price_change_percentage_24h?{usd_price_change_percentage_24h:d.usd_price_change_percentage_24h}:{}}}}catch(i){o=i instanceof Error?i.message:"Unknown error",a<U&&await W(F)}return{success:!1,error:o}}async getWalletNFTs(e){let{address:t,chain:n,limit:r=10,excludeSpam:o=!0,cursor:s,tokenAddresses:a,includePrices:i,format:l="decimal"}=e;if(!t)return{success:!1,error:"Address is required"};let u=V(n),d="Unknown error";for(let m=1;m<=U;m++)try{let p={chain:u,format:l,limit:r,exclude_spam:o,media_items:!0,normalizeMetadata:!0};s&&(p.cursor=s),i&&(p.include_prices=!0),a&&a.length>0&&(p.token_addresses=a);let h=$(p),g=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/nft?${h}`,f=await _.get(g);if(!f.ok)throw new Error(N(f));let y=f.data;return{success:!0,data:y?.data??y}}catch(p){d=p instanceof Error?p.message:"Unknown error",m<U&&await W(F)}return{success:!1,error:d}}async getNftMetadata(e){let{address:t,tokenId:n,chain:r,format:o="decimal",normalizeMetadata:s=!0,mediaItems:a=!0,include:i}=e;if(!t)return{success:!1,error:"NFT contract address is required"};if(n==null||n==="")return{success:!1,error:"Token ID is required"};if(!B(t))return{success:!1,error:"NFT metadata lookup by token ID is only supported on EVM chains"};let l=V(r),u="Unknown error";for(let d=1;d<=U;d++)try{let m={chain:l,format:o,normalizeMetadata:s,media_items:a};i&&(m.include=i);let p=$(m),h=`${this.baseUrl}/api/moralis/proxy/nft/${encodeURIComponent(t)}/${encodeURIComponent(n)}?${p}`,g=await _.get(h);if(!g.ok)throw new Error(N(g));let f=g.data;return{success:!0,data:f?.data??f}}catch(m){u=m instanceof Error?m.message:"Unknown error",d<U&&await W(F)}return{success:!1,error:u}}async getNftContractMetadata(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Contract address is required"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/nft/${encodeURIComponent(t)}/metadata?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getWalletHistory(e){let{address:t,chain:n,limit:r=25,fromDate:o,toDate:s,fromBlock:a,toBlock:i,cursor:l,order:u="DESC",includeInternalTransactions:d,nftMetadata:m}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet history is only supported for EVM addresses"};let p=V(n),h=Math.max(1,Math.min(100,Math.floor(r))),g="Unknown error";for(let f=1;f<=U;f++)try{let y={chain:p,limit:h,order:u};o&&(y.from_date=o),s&&(y.to_date=s),a!=null&&(y.from_block=a),i!=null&&(y.to_block=i),l&&(y.cursor=l),d!=null&&(y.include_internal_transactions=d),m!=null&&(y.nft_metadata=m);let k=$(y),w=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/history?${k}`,b=await _.get(w);if(!b.ok)throw new Error(N(b));let T=b.data;return{success:!0,data:T?.data??T}}catch(y){g=y instanceof Error?y.message:"Unknown error",f<U&&await W(F)}return{success:!1,error:g}}async getWalletTokenTransfers(e){let{address:t,chain:n,contractAddresses:r,limit:o=25,fromDate:s,toDate:a,fromBlock:i,toBlock:l,cursor:u,order:d="DESC"}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"ERC-20 transfers are only supported for EVM addresses"};let m=V(n),p=Math.max(1,Math.min(100,Math.floor(o))),h="Unknown error";for(let g=1;g<=U;g++)try{let f={chain:m,limit:p,order:d};s&&(f.from_date=s),a&&(f.to_date=a),i!=null&&(f.from_block=i),l!=null&&(f.to_block=l),u&&(f.cursor=u),r?.length&&(f.contract_addresses=r);let y=$(f),k=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/erc20/transfers?${y}`,w=await _.get(k);if(!w.ok)throw new Error(N(w));let b=w.data;return{success:!0,data:b?.data??b}}catch(f){h=f instanceof Error?f.message:"Unknown error",g<U&&await W(F)}return{success:!1,error:h}}async getWalletNftTransfers(e){let{address:t,chain:n,contractAddresses:r,limit:o=25,fromDate:s,toDate:a,fromBlock:i,toBlock:l,cursor:u,order:d="DESC",includePrices:m,format:p="decimal"}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"NFT transfers are only supported for EVM addresses"};let h=V(n),g=Math.max(1,Math.min(100,Math.floor(o))),f="Unknown error";for(let y=1;y<=U;y++)try{let k={chain:h,limit:g,order:d,format:p};s&&(k.from_date=s),a&&(k.to_date=a),i!=null&&(k.from_block=i),l!=null&&(k.to_block=l),u&&(k.cursor=u),r?.length&&(k.contract_addresses=r),m!=null&&(k.include_prices=m);let w=$(k),b=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/nft/transfers?${w}`,T=await _.get(b);if(!T.ok)throw new Error(N(T));let P=T.data;return{success:!0,data:P?.data??P}}catch(k){f=k instanceof Error?k.message:"Unknown error",y<U&&await W(F)}return{success:!1,error:f}}async getTokenHolders(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Contract address is required"};if(!B(t))return{success:!1,error:"Token holders is only supported for EVM contract addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/erc20/${encodeURIComponent(t)}/holders?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getWalletNetWorth(e){let{address:t,chains:n,excludeSpam:r,excludeUnverifiedContracts:o,maxTokenInactivity:s,minPairSideLiquidityUsd:a}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet net worth is only supported for EVM addresses"};let i="Unknown error";for(let l=1;l<=U;l++)try{let u={};n&&n.length>0&&(u.chains=n.map(y=>V(y))),r!=null&&(u.exclude_spam=r),o!=null&&(u.exclude_unverified_contracts=o),s!=null&&(u.max_token_inactivity=s),a!=null&&(u.min_pair_side_liquidity_usd=a);let d=$(u),m=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/net-worth${d?`?${d}`:""}`,p=await _.get(m);if(!p.ok)throw new Error(N(p));let h=p.data;return{success:!0,data:h?.data??h}}catch(u){i=u instanceof Error?u.message:"Unknown error",l<U&&await W(F)}return{success:!1,error:i}}async getWalletPnlSummary(e){let{address:t,chain:n,days:r}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet PnL summary is only supported for EVM addresses"};let o=V(n),s="Unknown error";for(let a=1;a<=U;a++)try{let i={chain:o};r&&(i.days=r);let l=$(i),u=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/profitability/summary?${l}`,d=await _.get(u);if(!d.ok)throw new Error(N(d));let m=d.data;return{success:!0,data:m?.data??m}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await W(F)}return{success:!1,error:s}}async getWalletPnl(e){let{address:t,chain:n,days:r,tokenAddresses:o}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet PnL is only supported for EVM addresses"};let s=V(n),a="Unknown error";for(let i=1;i<=U;i++)try{let l={chain:s};r&&(l.days=r),o&&o.length>0&&(l.token_addresses=o.slice(0,25));let u=$(l),d=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/profitability?${u}`,m=await _.get(d);if(!m.ok)throw new Error(N(m));let p=m.data;return{success:!0,data:p?.data??p}}catch(l){a=l instanceof Error?l.message:"Unknown error",i<U&&await W(F)}return{success:!1,error:a}}async getTransactionByHash(e){let{transactionHash:t,chain:n}=e;if(!t)return{success:!1,error:"Transaction hash is required"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/transaction/${encodeURIComponent(t)}/verbose?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getWalletApprovals(e){let{address:t,chain:n,limit:r,cursor:o}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet approvals are only supported for EVM addresses"};let s=V(n),a="Unknown error";for(let i=1;i<=U;i++)try{let l={chain:s};r!=null&&(l.limit=r),o&&(l.cursor=o);let u=$(l),d=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/approvals?${u}`,m=await _.get(d);if(!m.ok)throw new Error(N(m));let p=m.data;return{success:!0,data:p?.data??p}}catch(l){a=l instanceof Error?l.message:"Unknown error",i<U&&await W(F)}return{success:!1,error:a}}async getWalletDefiSummary(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet DeFi summary is only supported for EVM addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chains:r}),i=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/summary?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getWalletDefiPositions(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet DeFi positions are only supported for EVM addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chains:r}),i=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/positions?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data,d=u?.data??u;return{success:!0,data:Yr(d)}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getWalletDefiProtocolPositions(e){let{address:t,protocol:n,chain:r}=e;if(!t)return{success:!1,error:"Address is required"};if(!n)return{success:!1,error:"Protocol identifier is required"};if(!B(t))return{success:!1,error:"Wallet DeFi protocol positions are only supported for EVM addresses"};let o=V(r),s="Unknown error";for(let a=1;a<=U;a++)try{let i=$({chains:o}),l=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/${encodeURIComponent(n)}/positions?${i}`,u=await _.get(l);if(!u.ok)throw new Error(N(u));let p=u.data.result;return{success:!0,data:{protocol_id:p.protocolId,protocol_name:p.protocolName,protocol_url:p.protocolUrl??"",protocol_logo:p.protocolLogo??"",total_usd_value:p.totalUsd??0,total_unclaimed_usd_value:p.totalUnclaimedUsd??null,positions:p.positions.map(g=>({label:g.label,address:g.address,balance_usd:g.balanceUsd??0,total_unclaimed_usd_value:g.unclaimedUsd??0,position_details:g.details,tokens:g.tokens.map(f=>({token_type:f.tokenType,address:f.address,contract_address:f.address,name:f.name??"",symbol:f.symbol??"",decimals:f.decimals??18,logo:f.logo,balance:f.balance??"0",balance_formatted:f.balanceFormatted??"0",usd_price:f.usdPrice,usd_value:f.usdValue}))}))}}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await W(F)}return{success:!1,error:s}}async searchTokensByKey(e){return this.pantograph.searchTokensByKey(e)}async getTokenAnalytics(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Token contract address is required"};if(!B(t))return{success:!1,error:"Token analytics is only supported for EVM contract addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/tokens/${encodeURIComponent(t)}/analytics?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getTokenScore(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Token contract address is required"};if(!B(t))return{success:!1,error:"Token score is only supported for EVM contract addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/tokens/${encodeURIComponent(t)}/score?${a}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await W(F)}return{success:!1,error:o}}async getTrendingTokens(e){let t=e?.chain,n=e?.limit,r="Unknown error";for(let o=1;o<=U;o++)try{let s={};t&&(s.chain=V(t)),n!=null&&(s.limit=n);let a=$(s),i=`${this.baseUrl}/api/moralis/proxy/tokens/trending${a?`?${a}`:""}`,l=await _.get(i);if(!l.ok)throw new Error(N(l));let u=l.data,d=u?.data??u;return{success:!0,data:Xn(d)}}catch(s){r=s instanceof Error?s.message:"Unknown error",o<U&&await W(F)}return{success:!1,error:r}}async getTopGainers(e){let{chain:t,timeFrame:n,minMarketCap:r,securityScore:o}=e??{},s="Unknown error";for(let a=1;a<=U;a++)try{let i={};t&&(i.chain=V(t)),n&&(i.time_frame=n),r!=null&&(i.min_market_cap=r),o!=null&&(i.security_score=o);let l=$(i),u=`${this.baseUrl}/api/moralis/proxy/discovery/tokens/top-gainers${l?`?${l}`:""}`,d=await _.get(u);if(!d.ok)throw new Error(N(d));let m=d.data,p=m?.data??m;return{success:!0,data:Xn(p)}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await W(F)}return{success:!1,error:s}}async getTopLosers(e){let{chain:t,timeFrame:n,minMarketCap:r,securityScore:o}=e??{},s="Unknown error";for(let a=1;a<=U;a++)try{let i={};t&&(i.chain=V(t)),n&&(i.time_frame=n),r!=null&&(i.min_market_cap=r),o!=null&&(i.security_score=o);let l=$(i),u=`${this.baseUrl}/api/moralis/proxy/discovery/tokens/top-losers${l?`?${l}`:""}`,d=await _.get(u);if(!d.ok)throw new Error(N(d));let m=d.data,p=m?.data??m;return{success:!0,data:Xn(p)}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await W(F)}return{success:!1,error:s}}async enrichTokenPrices(e,t){return this.pantograph.enrichTokenPrices(e,t)}};var Ye=class extends v{name="get-token-info";description='Get information about a specific cryptocurrency token: price, metadata, 24h change, market data, and user holdings. Use this when the user asks about: token price, token info, "what is X token", "tell me about X", "how much is X worth", or any token-specific question. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea';category="blockchain-data";parameters=[{name:"keyword",type:"string",description:'Token symbol, name, or contract address (e.g. "USDC", "Pepe", "ETH", "0x1234\u2026"). If a 0x address is provided, performs a precise contract lookup. Otherwise searches by name/symbol.',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"prompt",type:"string",description:`A rich, multi-angle research prompt sent to Gemini with Google Search grounding (real-time web access). The prompt must be self-contained \u2014 Gemini does NOT see the user's original message, only this prompt. ALWAYS include: the token symbol/name, the chain, and an instruction to use web search for up-to-date info. When the user asks an open/broad question (e.g. "show me X", "tell me about X", "what is X"), expand the prompt to cover ALL of these angles in one request: (1) what the token/project is and its core utility, (2) current USD price, 24h change, market cap, FDV, 24h volume, (3) recent news, announcements, or notable events in the last 7-30 days, (4) on-chain activity signals (holders, liquidity, unusual flows), (5) team/protocol updates or roadmap items, (6) key risks, red flags, or controversies, (7) sentiment and notable community discussion. When the user asks a narrow question (e.g. "price of X"), still include 2-3 supporting angles for context. Aim for 3-6 sentences. Be specific to the token. Do not use generic filler. Example for "show me JPYT": "Research the JPYT token on Ethereum using up-to-date web sources. Cover: what JPYT is and who issues it; current USD price, 24h change, market cap, FDV, and 24h volume; recent news or announcements in the last 30 days; on-chain signals such as holder count, liquidity, and unusual volume patterns; team or protocol updates; key risks or red flags; and overall sentiment."`,required:!0},{name:"buy_button_label",type:"string",description:`Label for the "Buy" suggestion button shown under the token info, IN THE USER'S CURRENT LANGUAGE. Use the exact placeholder "{token}" (do not translate the braces) for the token symbol; put the localized verb directly in the text. English example: "Buy {token}". Vietnamese example: "Mua {token}". Japanese example: "{token} \u8CFC\u5165". Always include {token}.`,required:!0},{name:"buy_prompt_template",type:"string",description:`A short "buy" command IN THE USER'S CURRENT LANGUAGE, submitted as the next user turn when the Buy button is clicked. Use the exact placeholder "{token}" (do not translate the braces) for the token symbol; put the localized verb directly in the text. English example: "buy {token}". Vietnamese example: "mua {token}". Japanese example: "{token} \u8CFC\u5165". Always include {token}.`,required:!0}];service;llm;constructor(e){super(),this.service=new E(e),this.llm=new se({})}async run(e,t){let n=e.keyword||"",r=R(e.chain,t),o=e.prompt||"",s=e.buy_button_label||"",a=e.buy_prompt_template||"",i=t?.walletAddress||void 0;return n?await this.searchToken(n,r,o,s,a,i):{error:"Missing required parameter: keyword"}}async execute(e,t){let n=await super.execute(e,t),r=n.data;return n.success&&r&&Array.isArray(r.actionButtons)&&r.actionButtons.length>0&&(n.actionButtons=r.actionButtons),n}async searchToken(e,t,n,r,o,s){let a=[s?`User's connected wallet address: ${s}`:null,t?`Current chain (hex chain ID): ${t}, only consider tokens on this chain`:null,"Use this context to personalize your answer where relevant (e.g. whether the user holds the token, chain-specific data)."].filter(Boolean).join(`
3
+ `),[i,l,u]=await Promise.all([this.service.searchTokensByKey({key:e,chain:t}),s?this.findTokenInWallet(s,t,e):Promise.resolve(null),n?this.askGeminiWithSearch(n,a):Promise.resolve(null)]),d=i.success&&i.data?Array.isArray(i.data)?i.data:[]:[],m=e.toLowerCase(),p=b=>b.symbol?.toLowerCase()===m||b.token_address?.toLowerCase()===m,h=[...d].sort((b,T)=>(p(b)?0:1)-(p(T)?0:1)),g=null,f=h[0];if(f?.token_address){let b=await this.service.getTokenMetadata({address:f.token_address,chain:t});b.success&&b.data&&(g=b.data)}!g&&f&&(g={...f});let y=g?.symbol?.trim(),k=y?[{label:this.fillTemplate(r,y,"Buy {token}"),prompt:this.fillTemplate(o,y,"buy {token}")}]:[];return{_instructions:'Present the token info in a clear summary. Include: token name & symbol, contract address (shortened), current USD price, 24h price change % (prefix "+" for positive, "-" for negative), market cap, fully diluted valuation, 24h trading volume, liquidity, and holder count (if available). If the user holds this token, prominently show their balance and USD value. If research is present, weave its substance naturally into your answer \u2014 fold the relevant points into the description, context, and any notable news or risks. Do NOT add a labelled "AI Analysis" section or any similar heading, and do NOT mention that the analysis came from AI; just write it as part of the token summary. Format numbers in a human-readable way (e.g. $1.23, +5.67%, $12.5M). If a field is null, omit it. A "Buy" button is shown below your reply automatically \u2014 do NOT mention it, list it, or repeat its text.',chain:t,userHoldsToken:!!l,walletBalance:l,token:g,...d.length>1?{searchResults:h.slice(0,10)}:{},research:u,...k.length>0?{actionButtons:k}:{}}}fillTemplate(e,t,n){return(e&&e.includes("{token}")?e:n).replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}async askGeminiWithSearch(e,t){let n=Date.now();try{return(await this.llm.chat([...t?[{role:"system",content:t,timestamp:n}]:[],{role:"user",content:e,timestamp:n}],void 0,{googleSearch:!0,maxRetries:1})).text?.trim()||null}catch{return null}}async findTokenInWallet(e,t,n){if(!n)return null;let r=await this.service.getWalletTokenBalances({address:e,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0});if(!r.success||!r.data?.result)return null;let o=n.toLowerCase(),s=r.data.result.find(a=>a.token_address.toLowerCase()===o||a.symbol?.toLowerCase()===o||a.name?.toLowerCase().includes(o));return s||null}};var Ke=class extends v{name="get-token-holders";description='Get holder statistics and analytics for an ERC-20 token. Returns: total holder count, holder count changes over time (5min/1h/6h/24h/3d/7d/30d), how holders acquired the token (swap/transfer/airdrop), supply concentration (% held by top 10/25/50/100/250/500 holders), and holder size distribution (whales, sharks, dolphins, fish, octopus, crabs, shrimps). Use tokenSymbol (e.g. "USDC") to look up by name \u2014 the tool auto-resolves the contract address. Use contractAddress when you already have the exact 0x address. Use this tool for questions like: "How many holders does USDC have?" (tokenSymbol=USDC), "Is USDC gaining or losing holders?" (tokenSymbol=USDC \u2192 holderChange), "How many new holders did ETH get in the last 7 days?" (tokenSymbol=WETH), "How concentrated is USDC ownership?" (tokenSymbol=USDC \u2192 holderSupply), "What % of supply do the top 10 holders control?" (tokenSymbol=USDC), "How many whales hold USDT?" (tokenSymbol=USDT \u2192 holderDistribution), "Did most holders buy via swap or transfer?" (tokenSymbol=USDC \u2192 holdersByAcquisition). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. ';category="blockchain-data";parameters=[{name:"tokenSymbol",type:"string",description:`Token symbol (e.g. "USDC", "USDT", "WETH"). The tool resolves the contract address automatically: first checks the connected wallet's balances, then searches by symbol. Takes precedence over contractAddress when both are given.`,required:!1},{name:"contractAddress",type:"string",description:"ERC-20 token contract address (0x\u2026). Use when the exact address is already known. Ignored if tokenSymbol is provided.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=R(e.chain,t),r=t?.walletAddress||void 0,o=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,s=typeof e.contractAddress=="string"&&e.contractAddress?e.contractAddress.trim():void 0;if(!o&&!s)return{error:'Provide either tokenSymbol (e.g. "USDC") or contractAddress (0x\u2026).'};let a,i;if(o){let d=await this.resolveContractAddress(o,n,r);if(!d)return{error:`Cannot resolve token symbol "${o}" to a contract address on ${n?I(n):"the selected chain"}. Try passing contractAddress directly.`};s=d.address,a=d.symbol,i=d.name}let l=await this.service.getTokenHolders({address:s,chain:n});if(!l.success)return{error:l.error||"Failed to fetch token holder stats"};let u=l.data;return{token:{symbol:a??o??null,name:i??null,contractAddress:s,chain:n||"default"},totalHolders:u.totalHolders,holdersByAcquisition:u.holdersByAcquisition,holderChange:u.holderChange,holderSupply:u.holderSupply,holderDistribution:u.holderDistribution}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};var je=class extends v{name="get-token-analytics";description=`Get detailed trading analytics for a single ERC-20 token, broken down across 5min / 1h / 6h / 24h windows. Returns: buy volume, sell volume, unique buyers, unique sellers, buy/sell transaction counts, unique wallets, price % change, current USD price, total liquidity and fully-diluted valuation (FDV). Use tokenSymbol (e.g. "PEPE") to look up by name \u2014 the tool auto-resolves the contract address. Use contractAddress when you already have the exact 0x address. Use this tool for questions like: "How is PEPE trading today?" (tokenSymbol=PEPE), "24h buy vs sell volume for USDC?" (tokenSymbol=USDC \u2192 totalBuyVolume vs totalSellVolume), "Is buying or selling pressure stronger for this token?", "How many unique wallets touched this token in the last hour?" (\u2192 uniqueWallets.1h), "What's the FDV and liquidity of X?" (\u2192 totalFullyDilutedValuation, totalLiquidity), "Price change over 6h?" (\u2192 pricePercentChange.6h). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.`;category="blockchain-data";parameters=[{name:"tokenSymbol",type:"string",description:`Token symbol (e.g. "PEPE", "USDC", "WETH"). The tool resolves the contract address automatically: first checks the connected wallet's balances, then searches by symbol. Takes precedence over contractAddress when both are given.`,required:!1},{name:"contractAddress",type:"string",description:"ERC-20 token contract address (0x\u2026). Use when the exact address is already known. Ignored if tokenSymbol is provided.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=R(e.chain,t),r=t?.walletAddress||void 0,o=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,s=typeof e.contractAddress=="string"&&e.contractAddress?e.contractAddress.trim():void 0;if(!o&&!s)return{error:'Provide either tokenSymbol (e.g. "PEPE") or contractAddress (0x\u2026).'};let a,i;if(o){let d=await this.resolveContractAddress(o,n,r);if(!d)return{error:`Cannot resolve token symbol "${o}" to a contract address on ${n?I(n):"the selected chain"}. Try passing contractAddress directly.`};s=d.address,a=d.symbol,i=d.name}let l=await this.service.getTokenAnalytics({address:s,chain:n});if(!l.success)return{error:l.error||"Failed to fetch token analytics"};let u=l.data??{};return{token:{symbol:a??o??null,name:i??null,contractAddress:s,chain:n||"default"},usdPrice:u.usdPrice??null,totalLiquidity:u.totalLiquidity??null,totalFullyDilutedValuation:u.totalFullyDilutedValuation??null,totalBuyVolume:u.totalBuyVolume??null,totalSellVolume:u.totalSellVolume??null,totalBuyers:u.totalBuyers??null,totalSellers:u.totalSellers??null,totalBuys:u.totalBuys??null,totalSells:u.totalSells??null,uniqueWallets:u.uniqueWallets??null,pricePercentChange:u.pricePercentChange??null}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};var ze=class extends v{name="get-token-score";description=`Get a composite 0\u2013100 health/safety score for a single ERC-20 token, plus the metrics behind it. Returns: overall score, last-updated timestamp, current USD price, paired liquidity, multi-timeframe volume buckets (10m / 30m / 1h / 4h / 12h / 1d / 7d / 30d), same buckets for transaction counts, and supply info (total supply + % held by top 10 holders). Use tokenSymbol (e.g. "PEPE") to look up by name \u2014 the tool auto-resolves the contract address. Use contractAddress when you already have the exact 0x address. Use this tool for questions like: "Is PEPE a safe token?" (tokenSymbol=PEPE \u2192 score), "What's the health/score of USDC?", "How concentrated is X's supply among whales?" (\u2192 metrics.supply.top10Percent), "How much volume did this token do over the last 7 days?" (\u2192 metrics.volumeUsd.7d), "Transaction count for this token in the last hour?" (\u2192 metrics.transactions.1h). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.`;category="blockchain-data";parameters=[{name:"tokenSymbol",type:"string",description:`Token symbol (e.g. "PEPE", "USDC", "WETH"). The tool resolves the contract address automatically: first checks the connected wallet's balances, then searches by symbol. Takes precedence over contractAddress when both are given.`,required:!1},{name:"contractAddress",type:"string",description:"ERC-20 token contract address (0x\u2026). Use when the exact address is already known. Ignored if tokenSymbol is provided.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=R(e.chain,t),r=t?.walletAddress||void 0,o=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,s=typeof e.contractAddress=="string"&&e.contractAddress?e.contractAddress.trim():void 0;if(!o&&!s)return{error:'Provide either tokenSymbol (e.g. "PEPE") or contractAddress (0x\u2026).'};let a,i;if(o){let m=await this.resolveContractAddress(o,n,r);if(!m)return{error:`Cannot resolve token symbol "${o}" to a contract address on ${n?I(n):"the selected chain"}. Try passing contractAddress directly.`};s=m.address,a=m.symbol,i=m.name}let l=await this.service.getTokenScore({address:s,chain:n});if(!l.success)return{error:l.error||"Failed to fetch token score"};let u=l.data??{},d=u.metrics??{};return{token:{symbol:a??o??null,name:i??null,contractAddress:s,chain:n||"default"},score:u.score??null,updatedAt:u.updatedAt??null,metrics:{usdPrice:d.usdPrice??null,liquidityUsd:d.liquidityUsd??null,volumeUsd:d.volumeUsd??null,transactions:d.transactions??null,supply:d.supply??null}}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};var Qe=class extends v{name="get-trending-tokens";description='List tokens trending on-chain right now, ranked by trading activity, volume, liquidity and holder growth. Returns price, market cap, liquidity, holder count, and multi-timeframe buckets (1h / 4h / 12h / 24h) of price change, volume, transaction counts, and unique buyers/sellers. Cross-chain by default \u2014 omit `chain` for a global ranking, or pass it to filter to one network. Use this tool for questions like: "What tokens are trending right now?", "Show me hot tokens", "What\'s popular on Base today?", "Which tokens have the highest 24h volume?", "Biggest movers in the last hour?". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea, and more.';category="blockchain-data";parameters=[{name:"chain",type:"string",description:'Optional hex chain ID to scope results to a single network: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user names a chain. Omit to return cross-chain trending results.',required:!1},{name:"limit",type:"number",description:'Total number of trending tokens to return (chain default tokens + top gainers combined). Only set this when the user asks for a specific count (e.g. "show 20 trending tokens" \u2192 20). Omit otherwise \u2014 when omitted, returns all default tokens plus 5 top gainers.',required:!1},{name:"buy_button_label",type:"string",description:`Label template for the per-token "Buy" suggestion buttons, IN THE USER'S CURRENT LANGUAGE. One button is rendered per trending token, with "{token}" replaced by that token's symbol. Use the exact placeholder "{token}" (do not translate the braces); put the localized verb in the text. English example: "Buy {token}". Vietnamese example: "Mua {token}". Japanese example: "{token} \u8CFC\u5165". Always include {token}.`,required:!0},{name:"buy_prompt_template",type:"string",description:`A short "buy" command template IN THE USER'S CURRENT LANGUAGE, submitted as the next user turn when a token's Buy button is clicked. "{token}" is replaced by that token's symbol. Use the exact placeholder "{token}" (do not translate the braces); put the localized verb in the text. English example: "buy {token}". Vietnamese example: "mua {token}". Japanese example: "{token} \u8CFC\u5165". Always include {token}.`,required:!0}];service;constructor(e){super(),this.service=new ee({baseUrl:e?.pantographUrl})}async run(e,t){let n=wn(e.chain,t),r=e.buy_button_label||"",o=e.buy_prompt_template||"",s=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.floor(e.limit):void 0,a=await this.service.getTrendingTokens({chain:n,limit:s});if(!a.success)return{error:a.error||"Failed to fetch trending tokens"};let i=a.data??[],l=new Set,u=[];for(let d of i){let m=d.symbol?.trim();!m||l.has(m.toLowerCase())||(l.add(m.toLowerCase()),u.push({label:this.fillTemplate(r,m,"Buy {token}"),prompt:this.fillTemplate(o,m,"buy {token}")}))}return{_instructions:`Start with a one-line header like "Here are some of the top trending tokens on <chain name> right now:". Then present the tokens as a numbered list. For EACH token render exactly these lines (omit any line whose value is null):
4
4
  <rank>. <name> (<symbol>)
5
5
  - Price: <usdPrice>
6
6
  - Market Cap: <marketCap>
7
7
  - 24h Price Change: <pricePercentChange.24h>
8
8
  - 24h Volume: <totalVolume.24h>
9
- Format numbers human-readably: price as $0.65 / $0.016 (more decimals for sub-cent), market cap & volume compact ($12.8M, $6,474), price change signed with one or two decimals (+3.56%, -1.6%). Reply in the user's language and use the human-readable chain name. Do NOT mention tool names, UI, or forms. A "Buy" button is shown below your reply for each token automatically \u2014 do NOT mention, list, or repeat them.`,chain:n?L(n):"all",count:i.length,tokens:i,...u.length>0?{actionButtons:u}:{}}}async execute(e,t){let n=await super.execute(e,t),r=n.data;return n.success&&r&&Array.isArray(r.actionButtons)&&r.actionButtons.length>0&&(n.actionButtons=r.actionButtons),n}fillTemplate(e,t,n){return(e&&e.includes("{token}")?e:n).replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}};var vo={"1h":"1h","1d":"24h","1w":"7d","1M":"30d"},To=Object.keys(vo),Xe=class extends v{name="get-top-gainers";description='List tokens with the highest USD price increase over a chosen timeframe (1h / 1d / 1w / 1M). Returns name & symbol, current USD price, market cap, 24h volume, and the price change % over the selected window. Scoped to a single chain \u2014 pass `chain` to pick the network, otherwise the connected chain (or Ethereum) is used. Use this tool for questions like: "What are today\'s top gainers?", "Biggest pumps this week?", "Which tokens went up most in the last hour?", "Top gainers on Base". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. Defaults to the connected chain, or Ethereum (0x1).',required:!1},{name:"timeFrame",type:"string",description:`Window over which the price change is measured. Allowed values: "1h" (last hour), "1d" (last 24h), "1w" (last 7 days), "1M" (last 30 days). Pick the timeframe that matches the user's question \u2014 e.g. "today" \u2192 "1d", "this week" \u2192 "1w". Defaults to "1d".`,required:!1},{name:"limit",type:"number",description:'Number of top gainers to return. Only set this when the user asks for a specific count (e.g. "show 20 top gainers" \u2192 20). Omit otherwise.',required:!1}];service;constructor(e){super(),this.service=new Z({baseUrl:e?.pantographUrl})}async run(e,t){let n=hn(e.chain,t),r=typeof e.timeFrame=="string"?e.timeFrame.trim():void 0;if(r&&!To.includes(r))return{error:`Invalid timeFrame "${r}". Allowed values: ${To.join(", ")}.`};let o=r||void 0,s=o?vo[o]:void 0,a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.floor(e.limit):void 0,i=await this.service.getTopGainers({chain:n,limit:a,duration:s});if(!i.success)return{error:i.error||"Failed to fetch top gainers"};let l=i.data??[];return{_instructions:`Start with a one-line header like "Here are the top gainers on <chain name> right now:". Then present the tokens as a numbered list. For EACH token render exactly these lines (omit any line whose value is null):
9
+ Format numbers human-readably: price as $0.65 / $0.016 (more decimals for sub-cent), market cap & volume compact ($12.8M, $6,474), price change signed with one or two decimals (+3.56%, -1.6%). Reply in the user's language and use the human-readable chain name. Do NOT mention tool names, UI, or forms. A "Buy" button is shown below your reply for each token automatically \u2014 do NOT mention, list, or repeat them.`,chain:n?I(n):"all",count:i.length,tokens:i,...u.length>0?{actionButtons:u}:{}}}async execute(e,t){let n=await super.execute(e,t),r=n.data;return n.success&&r&&Array.isArray(r.actionButtons)&&r.actionButtons.length>0&&(n.actionButtons=r.actionButtons),n}fillTemplate(e,t,n){return(e&&e.includes("{token}")?e:n).replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}};var Eo={"1h":"1h","1d":"24h","1w":"7d","1M":"30d"},Ro=Object.keys(Eo),Xe=class extends v{name="get-top-gainers";description='List tokens with the highest USD price increase over a chosen timeframe (1h / 1d / 1w / 1M). Returns name & symbol, current USD price, market cap, 24h volume, and the price change % over the selected window. Scoped to a single chain \u2014 pass `chain` to pick the network, otherwise the connected chain (or Ethereum) is used. Use this tool for questions like: "What are today\'s top gainers?", "Biggest pumps this week?", "Which tokens went up most in the last hour?", "Top gainers on Base". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. Defaults to the connected chain, or Ethereum (0x1).',required:!1},{name:"timeFrame",type:"string",description:`Window over which the price change is measured. Allowed values: "1h" (last hour), "1d" (last 24h), "1w" (last 7 days), "1M" (last 30 days). Pick the timeframe that matches the user's question \u2014 e.g. "today" \u2192 "1d", "this week" \u2192 "1w". Defaults to "1d".`,required:!1},{name:"limit",type:"number",description:'Number of top gainers to return. Only set this when the user asks for a specific count (e.g. "show 20 top gainers" \u2192 20). Omit otherwise.',required:!1}];service;constructor(e){super(),this.service=new ee({baseUrl:e?.pantographUrl})}async run(e,t){let n=wn(e.chain,t),r=typeof e.timeFrame=="string"?e.timeFrame.trim():void 0;if(r&&!Ro.includes(r))return{error:`Invalid timeFrame "${r}". Allowed values: ${Ro.join(", ")}.`};let o=r||void 0,s=o?Eo[o]:void 0,a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.floor(e.limit):void 0,i=await this.service.getTopGainers({chain:n,limit:a,duration:s});if(!i.success)return{error:i.error||"Failed to fetch top gainers"};let l=i.data??[];return{_instructions:`Start with a one-line header like "Here are the top gainers on <chain name> right now:". Then present the tokens as a numbered list. For EACH token render exactly these lines (omit any line whose value is null):
10
10
  <rank>. <name> (<symbol>)
11
11
  - Price: <usdPrice>
12
12
  - Market Cap: <marketCap>
13
13
  - Price Change: <pricePercentChange.24h> (prefix "+" for positive, "-" for negative)
14
14
  - 24h Volume: <totalVolume.24h>
15
- Format numbers human-readably: price as $0.65 / $0.016 (more decimals for sub-cent), market cap & volume compact ($12.8M, $6,474), price change signed with one or two decimals (+5.67%, -1.6%). Reply in the user's language and use the human-readable chain name. Do NOT mention tool names, UI, or forms.`,chain:n?L(n):"all",timeFrame:o??null,count:l.length,tokens:l}}};var Je=class extends v{name="get-wallet-token-balances";description='Get all ERC-20 token balances for a wallet address, including USD prices and 24h changes. Use this when the user asks about: "my tokens", "my balances", "what do I hold", "portfolio", "how much ETH/USDC/\u2026 do I have", or any wallet balance question. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. ';category="blockchain-data";parameters=[{name:"address",type:"string",description:"Wallet address to query (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=K(e.address,t),r=R(e.chain,t),o=await this.service.getWalletTokenBalances({address:n,chain:r,excludeSpam:!1,excludeUnverifiedContracts:!1});if(!o.success)return{error:o.error||"Failed to fetch token balances"};let s=o.data?.result||[],a=s.map((i,l)=>{let u=i.name||i.symbol||"Unknown",d=i.balance_formatted??"0",p=i.symbol||"",m=typeof i.usd_value=="number"?`$${i.usd_value}`:"$-";return`${l+1}. ${u}: ${d} ${p} (${m})`.trim()});return{walletAddress:n,chain:r||"default",tokenCount:s.length,tokens:a,totalUsdValue:s.reduce((i,l)=>i+(l.usd_value||0),0)}}};var Ze=class extends v{name="get-wallet-history";description=`Get a wallet's decoded on-chain transaction history. Always fetches the latest 100 transactions. Each transaction includes: category, summary, from_address, to_address, and nested arrays: native_transfers (each has direction="send"|"receive", from_address, to_address, value_formatted, token_symbol), erc20_transfers (each has direction, token_symbol, value_formatted), nft_transfers. To find ETH sends/receives, inspect native_transfers[].direction \u2014 ETH moves appear across many categories (e.g. "token swap", "send", "receive"), not just category="send". Use this tool for: native coin transfers, swaps, airdrops, deposits, withdrawals, contract interactions, mixed history. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Returns paginated results \u2014 pass cursor to get the next page. Do NOT use for: specific ERC-20 tokens \u2192 get-wallet-token-transfers; NFT transfers \u2192 get-wallet-nft-transfers; balances \u2192 get-wallet-token-balances.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"fromDate",type:"string",description:'Inclusive start date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"toDate",type:"string",description:'Inclusive end date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"fromBlock",type:"number",description:"Minimum block number.",required:!1},{name:"toBlock",type:"number",description:"Maximum block number.",required:!1},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1},{name:"order",type:"string",description:'"DESC" newest first (default) or "ASC" oldest first.',required:!1,default:"DESC"},{name:"includeInternalTransactions",type:"boolean",description:"Include decoded internal transactions on each item.",required:!1},{name:"nftMetadata",type:"boolean",description:"Include NFT normalized metadata in nft_transfers.",required:!1},{name:"limit",type:"number",description:"Number of transactions to return per page. Defaults to 10.",required:!1,default:10}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=K(e.address,t),r=R(e.chain,t),o=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,s=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,a=typeof e.fromBlock=="number"?e.fromBlock:typeof e.fromBlock=="string"&&e.fromBlock?Number(e.fromBlock):void 0,i=typeof e.toBlock=="number"?e.toBlock:typeof e.toBlock=="string"&&e.toBlock?Number(e.toBlock):void 0,l=Number.isFinite(a)?a:void 0,u=Number.isFinite(i)?i:void 0,d=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,m=(typeof e.order=="string"?e.order.toUpperCase():void 0)==="ASC"?"ASC":"DESC",g=typeof e.includeInternalTransactions=="boolean"?e.includeInternalTransactions:void 0,f=typeof e.nftMetadata=="boolean"?e.nftMetadata:void 0,h=typeof e.limit=="number"?e.limit:typeof e.limit=="string"?Number(e.limit):void 0,y=Number.isFinite(h)&&h>0?h:10,k=await this.service.getWalletHistory({address:n,chain:r,limit:y,fromDate:o,toDate:s,fromBlock:l,toBlock:u,cursor:d,order:m,includeInternalTransactions:g,nftMetadata:f});if(!k.success)return{error:k.error||"Failed to fetch wallet history"};let w=k.data?.result||[];return{walletAddress:n,chain:r||"default",pagination:{cursor:k.data?.cursor??null,hasMore:!!k.data?.cursor},transactionCount:w.length,transactions:w}}};function Or(c){if(c.value_decimal)return c.value_decimal;let e=parseInt(c.token_decimals??"18",10);if(isNaN(e)||e===0)return c.value;try{let t=BigInt(c.value),n=BigInt(10)**BigInt(e),r=t/n,s=(t%n).toString().padStart(e,"0").replace(/0+$/,"");return s?`${r}.${s}`:`${r}`}catch{return c.value}}var et=class extends v{name="get-wallet-token-transfers";description=`Get all ERC-20 token transfers for a wallet from the dedicated Moralis transfers endpoint. Each transfer includes direction (send/receive), human-readable amount, and the exact counterparty address with its label/entity. The response includes a "counterparties" section that aggregates unique sender/recipient addresses with totals. Use tokenSymbol (e.g. "USDC") to filter by token name \u2014 the tool automatically resolves the contract address by checking the wallet's token balances first, then falling back to token search. Use contractAddresses when you already have the exact contract address. Use this tool for questions like: "When did I last send USDC and to whom?" (tokenSymbol=USDC, direction=send, limit=1), "Who have I sent token 0x\u2026 to?" (contractAddresses=[0x\u2026], direction=send), "Who sent me USDT?" (tokenSymbol=USDT, direction=receive), "Show all USDC transfers" (tokenSymbol=USDC), "What tokens did I receive this month?" (fromDate=..., direction=receive). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Returns paginated results \u2014 pass cursor to get the next page. IMPORTANT \u2014 this tool covers ERC-20 tokens ONLY. Do NOT use it for native coins (ETH, BNB, MATIC, AVAX, etc.) \u2014 native coin transfers are not ERC-20 and will not appear here. For native coin transfer history use get-wallet-history instead.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"tokenSymbol",type:"string",description:'Filter by token symbol (e.g. "USDC", "USDT", "WETH"). The tool resolves the contract address automatically: first checks wallet balances, then searches by symbol on the chain. Use this when the user mentions a token by name/symbol. Takes precedence over contractAddresses when both are given.',required:!1},{name:"contractAddresses",type:"array",description:"Filter by one or more ERC-20 token contract addresses (0x\u2026). Use when the exact contract address is already known. Ignored if tokenSymbol is provided.",required:!1,items:{type:"string"}},{name:"direction",type:"string",description:'Client-side direction filter: "send" (wallet is sender), "receive" (wallet is recipient), "all" (default).',required:!1,default:"all"},{name:"limit",type:"number",description:'Page size (1-100, default 25). Use limit=1 ONLY when the user asks for the single most recent transfer (e.g. "L\u1EA7n cu\u1ED1i c\xF9ng t\xF4i g\u1EEDi USDC l\xE0 khi n\xE0o?"). For "show recent history" or "list my transfers" questions use the default (25). Use 100 for bulk counterparty analysis.',required:!1,default:25},{name:"fromDate",type:"string",description:'Inclusive start date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"toDate",type:"string",description:'Inclusive end date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"fromBlock",type:"number",description:"Minimum block number.",required:!1},{name:"toBlock",type:"number",description:"Maximum block number.",required:!1},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1},{name:"order",type:"string",description:'"DESC" newest first (default) or "ASC" oldest first.',required:!1,default:"DESC"}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=K(e.address,t),r=R(e.chain,t),o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):25,a=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,i=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,l=this.parseBlock(e.fromBlock),u=this.parseBlock(e.toBlock),d=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,m=(typeof e.order=="string"?e.order.toUpperCase():"")==="ASC"?"ASC":"DESC",g=typeof e.direction=="string"?e.direction.trim().toLowerCase():"all",f=g==="send"?"send":g==="receive"?"receive":"all",h=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,y=Array.isArray(e.contractAddresses)?e.contractAddresses.filter(x=>typeof x=="string"&&!!x).map(x=>x.trim()):void 0,k,w;if(h){let x=await this.resolveContractAddressBySymbol(h,n,r);x&&(y=[x.address],k=x.symbol,w=x.name)}let b=await this.service.getWalletTokenTransfers({address:n,chain:r,contractAddresses:y,limit:s,fromDate:a,toDate:i,fromBlock:l,toBlock:u,cursor:d,order:m});if(!b.success)return{error:b.error||"Failed to fetch token transfers"};let T=n.toLowerCase(),S=(b.data?.result||[]).map(x=>{let I=x.from_address.toLowerCase()===T,A=I?"send":"receive",H=I?x.to_address:x.from_address,Pe=I?x.to_address_label??void 0:x.from_address_label??void 0,ie=I?x.to_address_entity??void 0:x.from_address_entity??void 0;return{...x,direction:A,amount_formatted:Or(x),counterparty:H,counterparty_label:Pe,counterparty_entity:ie}}),C=f==="all"?S:S.filter(x=>x.direction===f);if(h&&!y){let x=h.toLowerCase();C=C.filter(I=>(I.token_symbol??"").toLowerCase()===x)}let D=this.buildCounterparties(C);return{walletAddress:n,chain:r||"default",resolvedToken:k?{symbol:k,name:w,contractAddress:y?.[0]}:null,filters:{tokenSymbol:h||null,contractAddresses:y||null,direction:f,limit:s,order:m,fromDate:a||null,toDate:i||null,fromBlock:l??null,toBlock:u??null},pagination:{cursor:b.data?.cursor??null,pageSize:b.data?.page_size??s,page:b.data?.page??null,hasMore:!!b.data?.cursor},transferCount:C.length,transfers:C,counterparties:D}}async resolveContractAddressBySymbol(e,t,n){let r=e.toLowerCase(),o=await this.service.getWalletTokenBalances({address:t,chain:n});if(o.success&&o.data?.result?.length){let a=o.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}let s=await this.service.searchTokensByKey({key:e,chain:n});if(s.success&&s.data?.length){let i=s.data.find(l=>(l.symbol??"").toLowerCase()===r)??s.data[0];if(i?.token_address)return{address:i.token_address,symbol:i.symbol,name:i.name}}}buildCounterparties(e){let t=new Map,n=new Map;for(let o of e){let s=o.direction==="send"?t:n,a=o.counterparty.toLowerCase(),i=parseFloat(o.amount_formatted)||0,l=s.get(a);l?(l.total+=i,l.count+=1,!l.label&&o.counterparty_label&&(l.label=o.counterparty_label),!l.entity&&o.counterparty_entity&&(l.entity=o.counterparty_entity),o.block_timestamp<l.first&&(l.first=o.block_timestamp),o.block_timestamp>l.last&&(l.last=o.block_timestamp)):s.set(a,{label:o.counterparty_label,entity:o.counterparty_entity,total:i,count:1,first:o.block_timestamp,last:o.block_timestamp})}let r=o=>Array.from(o.entries()).sort((s,a)=>a[1].count-s[1].count).map(([s,a])=>({address:s,label:a.label,entity:a.entity,total_amount:a.total.toFixed(6).replace(/\.?0+$/,""),tx_count:a.count,first_seen:a.first,last_seen:a.last}));return{sent:r(t),received:r(n)}}parseBlock(e){if(typeof e=="number")return Number.isFinite(e)?e:void 0;if(typeof e=="string"&&e){let t=Number(e);return Number.isFinite(t)?t:void 0}}};var tt=class extends v{name="get-wallet-nft-transfers";description=`Get all NFT transfers for a wallet from the dedicated Moralis NFT transfers endpoint. Each transfer includes direction (send/receive), the exact counterparty address, token ID, collection, contract type (ERC721/ERC1155), and optional last sale price. The response includes a 'counterparties' section aggregating unique senders/recipients with token IDs and collections. Use this tool for questions like: "Which NFTs did I sell and to whom?" (direction=send), "Who sent me NFTs from collection 0x\u2026?" (contractAddresses=[0x\u2026], direction=receive), "Show all transfers for my NFT collection 0x\u2026" (contractAddresses=[0x\u2026]), "What NFTs did I mint?" (direction=receive), "Show NFT sales with prices" (direction=send, includePrices=true). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Returns paginated results \u2014 pass cursor to get the next page.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"contractAddresses",type:"array",description:"Filter by one or more NFT contract addresses (0x\u2026). Use this to scope queries to a specific collection. Omit to return all NFT transfers.",required:!1,items:{type:"string"}},{name:"direction",type:"string",description:'Client-side direction filter: "send" (wallet sent the NFT), "receive" (wallet received it), "all" (default).',required:!1,default:"all"},{name:"limit",type:"number",description:"Page size (1-100, default 25).",required:!1,default:25},{name:"fromDate",type:"string",description:'Inclusive start date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"toDate",type:"string",description:'Inclusive end date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"fromBlock",type:"number",description:"Minimum block number.",required:!1},{name:"toBlock",type:"number",description:"Maximum block number.",required:!1},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1},{name:"order",type:"string",description:'"DESC" newest first (default) or "ASC" oldest first.',required:!1,default:"DESC"},{name:"includePrices",type:"boolean",description:"If true, include last_sale price data (buyer, seller, price, USD value, payment token) on each transfer. Useful for questions about NFT sale prices.",required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=K(e.address,t),r=R(e.chain,t),o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):25,a=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,i=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,l=this.parseBlock(e.fromBlock),u=this.parseBlock(e.toBlock),d=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,m=(typeof e.order=="string"?e.order.toUpperCase():"")==="ASC"?"ASC":"DESC",g=typeof e.includePrices=="boolean"?e.includePrices:void 0,f=typeof e.direction=="string"?e.direction.trim().toLowerCase():"all",h=f==="send"?"send":f==="receive"?"receive":"all",y=Array.isArray(e.contractAddresses)?e.contractAddresses.filter(C=>typeof C=="string"&&!!C).map(C=>C.trim()):void 0,k=await this.service.getWalletNftTransfers({address:n,chain:r,contractAddresses:y,limit:s,fromDate:a,toDate:i,fromBlock:l,toBlock:u,cursor:d,order:m,includePrices:g});if(!k.success)return{error:k.error||"Failed to fetch NFT transfers"};let w=n.toLowerCase(),T=(k.data?.result||[]).map(C=>{let D=C.from_address.toLowerCase()===w,x=D?"send":"receive",I=D?C.to_address:C.from_address,A=D?C.to_address_label??void 0:C.from_address_label??void 0,H=D?C.to_address_entity??void 0:C.from_address_entity??void 0;return{...C,direction:x,counterparty:I,counterparty_label:A,counterparty_entity:H}}),P=h==="all"?T:T.filter(C=>C.direction===h),S=this.buildCounterparties(P);return{walletAddress:n,chain:r||"default",filters:{contractAddresses:y||null,direction:h,limit:s,order:m,fromDate:a||null,toDate:i||null,fromBlock:l??null,toBlock:u??null,includePrices:g??null},pagination:{cursor:k.data?.cursor??null,pageSize:k.data?.page_size??s,page:k.data?.page??null,hasMore:!!k.data?.cursor},transferCount:P.length,transfers:P,counterparties:S}}buildCounterparties(e){let t=new Map,n=new Map;for(let o of e){let s=o.direction==="send"?t:n,a=o.counterparty.toLowerCase(),i=o.token_name??o.token_address,l=s.get(a);l?(l.tokenIds.add(o.token_id),l.collections.add(i),l.count+=1,!l.label&&o.counterparty_label&&(l.label=o.counterparty_label),!l.entity&&o.counterparty_entity&&(l.entity=o.counterparty_entity),o.block_timestamp<l.first&&(l.first=o.block_timestamp),o.block_timestamp>l.last&&(l.last=o.block_timestamp)):s.set(a,{label:o.counterparty_label,entity:o.counterparty_entity,tokenIds:new Set([o.token_id]),collections:new Set([i]),count:1,first:o.block_timestamp,last:o.block_timestamp})}let r=o=>Array.from(o.entries()).sort((s,a)=>a[1].count-s[1].count).map(([s,a])=>({address:s,label:a.label,entity:a.entity,token_ids:Array.from(a.tokenIds),collections:Array.from(a.collections),tx_count:a.count,first_seen:a.first,last_seen:a.last}));return{sent:r(t),received:r(n)}}parseBlock(e){if(typeof e=="number")return Number.isFinite(e)?e:void 0;if(typeof e=="string"&&e){let t=Number(e);return Number.isFinite(t)?t:void 0}}};var gn=class extends v{name="get-wallet-net-worth";description=`Get a wallet's total net worth in USD, with a per-chain breakdown. Returns: total_networth_usd (grand total across all queried chains), chains[] (each with chain, native_balance_formatted, native_balance_usd, token_balance_usd, networth_usd), and optional unsupported_chain_ids / unavailable_chains lists. Use this tool for questions like: "How much is my wallet worth?", "What is my total portfolio value?", "What is my net worth on Ethereum?", "Portfolio value across all chains?". Pass chains=["0x1","0x2105"] to query multiple chains at once; defaults to the connected chain. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Do NOT use for: individual token balances \u2192 get-wallet-token-balances; realized profit/loss \u2192 get-wallet-pnl-summary.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chains",type:"array",description:'One or more hex chain IDs to aggregate. Values: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea If omitted, defaults to the connected chain.',required:!1,items:{type:"string"}},{name:"excludeSpam",type:"boolean",description:"Exclude tokens flagged as spam. Default true for cleaner totals.",required:!1,default:!0},{name:"excludeUnverifiedContracts",type:"boolean",description:"Exclude unverified token contracts. Default false.",required:!1,default:!1},{name:"maxTokenInactivity",type:"number",description:"Exclude tokens that have been inactive for more than N days.",required:!1},{name:"minPairSideLiquidityUsd",type:"number",description:"Exclude tokens with pair-side liquidity below this USD threshold.",required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0;if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let r=Array.isArray(e.chains)?e.chains.filter(m=>typeof m=="string"&&!!m).map(m=>m.trim()):void 0,o=t?.chain,s=r&&r.length>0?r:o?[o]:void 0,a=typeof e.excludeSpam=="boolean"?e.excludeSpam:!0,i=typeof e.excludeUnverifiedContracts=="boolean"?e.excludeUnverifiedContracts:void 0,l=typeof e.maxTokenInactivity=="number"&&Number.isFinite(e.maxTokenInactivity)?e.maxTokenInactivity:void 0,u=typeof e.minPairSideLiquidityUsd=="number"&&Number.isFinite(e.minPairSideLiquidityUsd)?e.minPairSideLiquidityUsd:void 0,d=await this.service.getWalletNetWorth({address:n,chains:s,excludeSpam:a,excludeUnverifiedContracts:i,maxTokenInactivity:l,minPairSideLiquidityUsd:u});if(!d.success)return{error:d.error||"Failed to fetch wallet net worth"};let p=d.data;return{walletAddress:n,chainsQueried:s??["default"],filters:{excludeSpam:a,excludeUnverifiedContracts:i??null,maxTokenInactivity:l??null,minPairSideLiquidityUsd:u??null},totalNetWorthUsd:p.total_networth_usd,chains:p.chains,unsupportedChainIds:p.unsupported_chain_ids??[],unavailableChains:p.unavailable_chains??[]}}};var qr=["all","7","30","60","90"],nt=class extends v{name="get-wallet-pnl-summary";description=`Get a wallet's aggregate realized profit-and-loss summary across all tokens it has traded. Returns: total_count_of_trades, total_trade_volume (USD), total_realized_profit_usd, total_realized_profit_percentage, total_buys, total_sells, total_sold_volume_usd, total_bought_volume_usd. Use this tool for questions like: "How much profit have I made trading?", "What is my realized PnL?", "How many trades have I done this month?" (days=30), "Am I up or down overall?". IMPORTANT \u2014 this endpoint only supports mainnet: Ethereum ("0x1"), Base ("0x2105"), Polygon ("0x89"). Use days="all" (default), "7", "30", "60", or "90" to scope the time window. Do NOT use for: per-token breakdown \u2192 get-wallet-pnl; current portfolio value \u2192 get-wallet-token-balance; transfer history \u2192 get-wallet-history.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"days",type:"string",description:'Time window for the summary. Values: "all" (default, lifetime), "7", "30", "60", "90". Pass "30" when the user asks about the last month, "7" for the last week, etc.',required:!1,default:"all"}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=K(e.address,t),r=R(e.chain,t),o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=qr.includes(o)?o:void 0,a=await this.service.getWalletPnlSummary({address:n,chain:r,days:s});if(!a.success)return{error:a.error||"Failed to fetch wallet PnL summary"};let i=a.data;return{walletAddress:n,chain:r||"default",days:s??"all",summary:{totalCountOfTrades:i.total_count_of_trades,totalTradeVolume:i.total_trade_volume,totalRealizedProfitUsd:i.total_realized_profit_usd,totalRealizedProfitPercentage:i.total_realized_profit_percentage,totalBuys:i.total_buys,totalSells:i.total_sells,totalBoughtVolumeUsd:i.total_bought_volume_usd,totalSoldVolumeUsd:i.total_sold_volume_usd}}}};var Fr=["all","7","30","60","90"],fn=25,ot=class extends v{name="get-wallet-pnl";description=`Get a wallet's realized profit-and-loss broken down per ERC-20 token. Each entry includes: token_address, name, symbol, decimals, logo, avg_buy_price_usd, avg_sell_price_usd, total_usd_invested, total_tokens_bought, total_tokens_sold, total_sold_usd, avg_cost_of_quantity_sold, count_of_trades, realized_profit_usd, realized_profit_percentage, total_buys, total_sells, possible_spam. Use this tool for questions like: "Which tokens made me the most money?", "What are my best/worst trades?", "How much profit did I make on USDC?" (tokenSymbols=["USDC"]), "PnL for PEPE and DEGEN this month" (tokenSymbols=["PEPE","DEGEN"], days=30), "PnL breakdown for each token I traded this month" (days=30). IMPORTANT \u2014 this endpoint ONLY supports mainnet: Ethereum ("0x1"), Polygon ("0x89"), Base ("0x2105"). Use days="all" (default), "7", "30", "60", or "90" for the time window. Pass tokenSymbols (by ticker, auto-resolved) and/or tokenAddresses (raw 0x\u2026) to restrict to specific tokens \u2014 combined total capped at ${fn}. Do NOT use for: aggregate summary \u2192 get-wallet-pnl-summary; current balances \u2192 get-wallet-token-balances; transfer history \u2192 get-wallet-token-transfers.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"days",type:"string",description:'Time window for the breakdown. Values: "all" (default, lifetime), "7", "30", "60", "90".',required:!1,default:"all"},{name:"tokenSymbols",type:"array",description:`Optional list of token symbols (e.g. ["USDC", "PEPE"]). Auto-resolved to contract addresses via the connected wallet's balances first, then Pantograph search. Symbols that can't be resolved are skipped. Merged with tokenAddresses; combined total capped at ${fn}.`,required:!1,items:{type:"string"}},{name:"tokenAddresses",type:"array",description:`Optional list of raw ERC-20 contract addresses (0x\u2026). Use when the exact addresses are already known. Merged with tokenSymbols; combined total capped at ${fn}.`,required:!1,items:{type:"string"}}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=K(e.address,t),r=R(e.chain,t),o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=Fr.includes(o)?o:void 0,a=Array.isArray(e.tokenSymbols)?e.tokenSymbols.filter(f=>typeof f=="string"&&!!f.trim()).map(f=>f.trim()):[],i=Array.isArray(e.tokenAddresses)?e.tokenAddresses.filter(f=>typeof f=="string"&&!!f.trim()).map(f=>f.trim()):[],l=[],u=[];a.length>0&&(await Promise.all(a.map(h=>this.resolveContractAddress(h,r,n)))).forEach((h,y)=>{h?l.push({symbol:a[y],address:h.address}):u.push(a[y])});let d=$r([...l.map(f=>f.address),...i]).slice(0,fn),p=d.length>0?d:void 0,m=await this.service.getWalletPnl({address:n,chain:r,days:s,tokenAddresses:p});if(!m.success)return{error:m.error||"Failed to fetch wallet PnL"};let g=m.data?.result??[];return{walletAddress:n,chain:r||"default",days:s??"all",filters:{tokenAddresses:p??null,resolvedSymbols:l.length>0?l:null,unresolvedSymbols:u.length>0?u:null},tokenCount:g.length,tokens:g}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};function $r(c){let e=new Set,t=[];for(let n of c){let r=n.toLowerCase();e.has(r)||(e.add(r),t.push(n))}return t}var Wr=BigInt(2)**BigInt(255),rt=class extends v{name="get-wallet-approvals";description=`List every active ERC-20 token approval granted by a wallet \u2014 i.e. which contracts can still spend the wallet's tokens. For each approval, returns: the token (address, name, symbol, logo, current balance, USD price, USD at risk, possible_spam, verified_contract), the spender (address, label, entity \u2014 e.g. "Uniswap V3 Router", "Permit2"), the approved amount (value / value_formatted / isUnlimited flag), and when the approval was granted (block number, timestamp, transaction hash). Also returns aggregates: total approvals, unlimited-approval count, total USD at risk, and approvals flagged as risky (spam tokens, unverified contracts, or unlimited allowance to unlabeled spenders). Use for questions like: "What approvals do I have?", "Show my token approvals", "Am I exposed to any risky approvals?", "Which contracts can drain my wallet?", "Do I have any unlimited approvals?", "Revoke approvals / token allowances". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Paginated \u2014 pass cursor to fetch the next page.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"limit",type:"number",description:"Page size (1-100, default 100).",required:!1,default:100},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=K(e.address,t),r=R(e.chain,t),o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):100,a=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,i=await this.service.getWalletApprovals({address:n,chain:r,limit:s,cursor:a});if(!i.success)return{error:i.error||"Failed to fetch wallet approvals"};let l=i.data,d=(l.result||[]).map(f=>Vr(f)),p=0,m=0,g=[];for(let f of d){f.isUnlimited&&(m+=1);let h=parseFloat(f.token.usdAtRisk??"");Number.isFinite(h)&&(p+=h);let y=f.token.possibleSpam||f.token.verifiedContract===!1,k=!f.spender.label&&!f.spender.entity;(y||f.isUnlimited&&k)&&g.push(f)}return{walletAddress:n,chain:r||"default",filters:{limit:s,cursor:a??null},pagination:{cursor:l.cursor??null,pageSize:l.page_size??s,page:l.page??null,hasMore:!!l.cursor},summary:{totalApprovals:d.length,unlimitedApprovals:m,totalUsdAtRisk:p,riskyApprovalCount:g.length},approvals:d,riskyApprovals:g}}};function Vr(c){let e=Hr(c.value);return{blockNumber:c.block_number,blockTimestamp:c.block_timestamp??null,transactionHash:c.transaction_hash??null,value:c.value,valueFormatted:c.value_formatted??null,isUnlimited:e,token:{address:c.token.address,addressLabel:c.token.address_label??null,name:c.token.name??null,symbol:c.token.symbol??null,logo:c.token.logo??null,possibleSpam:!!c.token.possible_spam,verifiedContract:c.token.verified_contract??null,currentBalance:c.token.current_balance??null,currentBalanceFormatted:c.token.current_balance_formatted??null,usdPrice:c.token.usd_price??null,usdAtRisk:c.token.usd_at_risk??null},spender:{address:c.spender.address,label:c.spender.address_label??null,entity:c.spender.entity??null,entityLogo:c.spender.entity_logo??null}}}function Hr(c){if(!c)return!1;try{return BigInt(c)>=Wr}catch{return!1}}var st=class extends v{name="get-wallet-defi-summary";description=`Get a wallet's DeFi protocols summary: which protocols the wallet is using, total USD value locked across all protocols, total unclaimed rewards/fees, number of active protocols, and total number of positions. Returns a per-protocol breakdown (protocol name, logo, url, positions count, usd value, unclaimed). Use for questions like: "Which DeFi protocols am I using?", "What's my total DeFi value?", "How much do I have in DeFi?", "Show my DeFi portfolio overview", "Unclaimed rewards across protocols". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Do NOT use for: detailed per-position data \u2192 get-wallet-defi-positions; positions inside a single protocol \u2192 get-wallet-defi-protocol-positions.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=K(e.address,t),r=R(e.chain,t),o=await this.service.getWalletDefiSummary({address:n,chain:r});if(!o.success)return{error:o.error||"Failed to fetch wallet DeFi summary"};let s=o.data;return{walletAddress:n,chain:r||"default",activeProtocols:s.active_protocols,totalPositions:s.total_positions,totalUsdValue:s.total_usd_value,totalUnclaimedUsdValue:s.total_unclaimed_usd_value,protocols:(s.protocols||[]).map(a=>({protocolName:a.protocol_name,protocolId:a.protocol_id,protocolUrl:a.protocol_url,protocolLogo:a.protocol_logo,positions:a.positions,totalUsdValue:a.total_usd_value,totalUnclaimedUsdValue:a.total_unclaimed_usd_value,accountHealthFactor:a.account_health_factor??null}))}}};var at=class extends v{name="get-wallet-defi-positions";description='Get every DeFi position a wallet holds across all supported protocols, with full detail: protocol identity, position label (e.g. "Liquidity Provision", "Supply", "Borrow", "Staking"), tokens involved (with balances and USD values), balance_usd, total_unclaimed_usd_value, and position_details (fee tier, liquidity, APY, price range, health factor, etc.). Use for questions like: "Show my DeFi positions", "What LP positions do I have?", "What am I staking?", "Do I have any borrows?", "List all my DeFi holdings with details". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Do NOT use for: aggregate totals \u2192 get-wallet-defi-summary; positions filtered to a single protocol \u2192 get-wallet-defi-protocol-positions.';category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=K(e.address,t),r=R(e.chain,t),o=await this.service.getWalletDefiPositions({address:n,chain:r});if(!o.success)return{error:o.error||"Failed to fetch wallet DeFi positions"};let s=o.data||[];return{walletAddress:n,chain:r||"default",positionCount:s.length,positions:s.map(a=>Gr(a))}}};function Gr(c){return{protocolName:c.protocol_name,protocolId:c.protocol_id,protocolUrl:c.protocol_url,protocolLogo:c.protocol_logo,accountHealthFactor:c.account_data?.health_factor??null,position:Kr(c.position)}}function Kr(c){return{label:c.label,address:c.address,balanceUsd:c.balance_usd,totalUnclaimedUsdValue:c.total_unclaimed_usd_value,tokens:(c.tokens||[]).map(e=>({tokenType:e.token_type,name:e.name,symbol:e.symbol,contractAddress:e.contract_address,decimals:e.decimals,logo:e.logo,thumbnail:e.thumbnail,balance:e.balance,balanceFormatted:e.balance_formatted,usdPrice:e.usd_price,usdValue:e.usd_value})),details:c.position_details??null}}var it=class extends v{name="get-wallet-defi-protocol-positions";description='Get every position a wallet holds inside a specific DeFi protocol, with full detail. Returns the protocol identity, totals (total_usd_value, total_unclaimed_usd_value), and an array of positions (label, tokens, balance_usd, unclaimed, fee tier, APY, health factor, etc.). Use when the user names a specific protocol, e.g. "my Uniswap v3 positions", "what do I have on Aave v3", "show my Lido staking", "my Curve pools", "Pendle positions". The "protocol" argument is the Moralis protocol id (kebab-case), e.g. uniswap-v2, uniswap-v3, pancakeswap-v2, pancakeswap-v3, quickswap-v2, quickswap-v3, sushiswap-v2, aave-v2, aave-v3, aave-lido, fraxswap-v1, fraxswap-v2, lido, makerdao, eigenlayer, pendle, etherfi, rocketpool, sparkfi, takara-lend, neverland, kintsu. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Do NOT use for: cross-protocol view \u2192 get-wallet-defi-positions; totals only \u2192 get-wallet-defi-summary.';category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"protocol",type:"string",description:'Moralis protocol identifier in kebab-case. Examples: "uniswap-v2", "uniswap-v3", "pancakeswap-v2", "pancakeswap-v3", "quickswap-v2", "quickswap-v3", "sushiswap-v2", "aave-v2", "aave-v3", "aave-lido", "fraxswap-v1", "fraxswap-v2", "lido", "makerdao", "eigenlayer", "pendle", "etherfi", "rocketpool", "sparkfi", "takara-lend", "neverland", "kintsu"Use get-wallet-defi-summary to discover which protocols a wallet uses.',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.protocol?.trim();if(!n)return{error:'Protocol identifier is required (e.g. "uniswap-v3", "aave-v3").'};let r=K(e.address,t),o=R(e.chain,t),s=await this.service.getWalletDefiProtocolPositions({address:r,protocol:n,chain:o});if(!s.success)return{error:s.error||"Failed to fetch wallet DeFi protocol positions"};let a=s.data;return a?{walletAddress:r,chain:o||"default",protocolName:a.protocol_name,protocolId:a.protocol_id,protocolUrl:a.protocol_url,protocolLogo:a.protocol_logo,totalUsdValue:a.total_usd_value,totalUnclaimedUsdValue:a.total_unclaimed_usd_value,accountHealthFactor:a.account_data?.health_factor??null,positionCount:(a.positions||[]).length,positions:(a.positions||[]).map(Yr)}:{error:"No data returned for this protocol"}}};function Yr(c){return{label:c.label,address:c.address,balanceUsd:c.balance_usd,totalUnclaimedUsdValue:c.total_unclaimed_usd_value,tokens:(c.tokens||[]).map(e=>({tokenType:e.token_type,name:e.name,symbol:e.symbol,contractAddress:e.contract_address,decimals:e.decimals,logo:e.logo,thumbnail:e.thumbnail,balance:e.balance,balanceFormatted:e.balance_formatted,usdPrice:e.usd_price,usdValue:e.usd_value})),details:c.position_details??null}}function jr(c){try{return(Number(BigInt(c))/1e18).toFixed(10).replace(/\.?0+$/,"")||"0"}catch{return c}}var lt=class extends v{name="get-transaction-by-hash";description='Get full decoded details of a single EVM transaction by its hash. Returns: status (success/failed), timestamp, from/to addresses with labels and entity names, native value transferred (ETH), transaction fee, gas used, decoded function call with params, and decoded event logs (ERC-20 transfers, swaps, approvals, etc.). Use this when the user asks: "what did this transaction do?", "explain tx 0x\u2026", "was this transaction successful?", "who sent/received in this tx?", "what function was called?". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"transactionHash",type:"string",description:"The transaction hash to look up (0x\u2026).",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.transactionHash;if(!n)return{error:"Transaction hash is required."};let r=R(e.chain,t),o=await this.service.getTransactionByHash({transactionHash:n,chain:r});if(!o.success||!o.data)return{error:o.error||"Failed to fetch transaction"};let s=o.data,a=(s.logs??[]).filter(l=>!!l.decoded_event).map(l=>({contract:l.address,signature:l.decoded_event.signature,label:l.decoded_event.label,params:l.decoded_event.params})),i=s.logs.map(l=>({address:l.address,decodedEvent:l.decoded_event?{signature:l.decoded_event.signature,label:l.decoded_event.label,params:l.decoded_event.params}:null}));return{hash:s.hash,status:s.receipt_status==="1"?"success":s.receipt_status==="0"?"failed":"unknown",timestamp:s.block_timestamp,blockNumber:s.block_number,from:{address:s.from_address,label:s.from_address_label||null,entity:s.from_address_entity||null},to:s.to_address?{address:s.to_address,label:s.to_address_label||null,entity:s.to_address_entity||null}:null,valueEth:jr(s.value||"0"),fee:s.transaction_fee,gasUsed:s.receipt_gas_used,gasPriceGwei:s.gas_price?(Number(s.gas_price)/1e9).toFixed(4):null,decodedCall:s.decoded_call?{signature:s.decoded_call.signature,label:s.decoded_call.label,params:s.decoded_call.params}:null,decodedEvents:a,contractCreated:s.receipt_contract_address||null,logs:i}}};var ge=class extends v{kind="data";category="nft";noSuggestions=!0;parameters=[];linkPhrase="this website";url;constructor(e){super();let t=e?.url?.trim();this.url=t||void 0}async run(){let e=this.url?`[${this.linkPhrase}](${this.url})`:this.linkPhrase,t=this.template.replace("{link}",e);return{message:t,_instructions:`This is a fixed notice. Output it to the user EXACTLY as written below \u2014 in English, without translating, rephrasing, summarising, or adding/removing any words. Preserve the Markdown link verbatim (do not change, wrap, or drop the URL or the linked words). Do not mention tools. Reply with only this message:
15
+ Format numbers human-readably: price as $0.65 / $0.016 (more decimals for sub-cent), market cap & volume compact ($12.8M, $6,474), price change signed with one or two decimals (+5.67%, -1.6%). Reply in the user's language and use the human-readable chain name. Do NOT mention tool names, UI, or forms.`,chain:n?I(n):"all",timeFrame:o??null,count:l.length,tokens:l}}};var Je=class extends v{name="get-wallet-token-balances";description='Get all ERC-20 token balances for a wallet address, including USD prices and 24h changes. Use this when the user asks about: "my tokens", "my balances", "what do I hold", "portfolio", "how much ETH/USDC/\u2026 do I have", or any wallet balance question. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. ';category="blockchain-data";parameters=[{name:"address",type:"string",description:"Wallet address to query (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=Y(e.address,t),r=R(e.chain,t),o=await this.service.getWalletTokenBalances({address:n,chain:r,excludeSpam:!1,excludeUnverifiedContracts:!1});if(!o.success)return{error:o.error||"Failed to fetch token balances"};let s=o.data?.result||[],a=s.map((i,l)=>{let u=i.name||i.symbol||"Unknown",d=i.balance_formatted??"0",m=i.symbol||"",p=typeof i.usd_value=="number"?`$${i.usd_value}`:"$-";return`${l+1}. ${u}: ${d} ${m} (${p})`.trim()});return{walletAddress:n,chain:r||"default",tokenCount:s.length,tokens:a,totalUsdValue:s.reduce((i,l)=>i+(l.usd_value||0),0)}}};var Ze=class extends v{name="get-wallet-history";description=`Get a wallet's decoded on-chain transaction history. Always fetches the latest 100 transactions. Each transaction includes: category, summary, from_address, to_address, and nested arrays: native_transfers (each has direction="send"|"receive", from_address, to_address, value_formatted, token_symbol), erc20_transfers (each has direction, token_symbol, value_formatted), nft_transfers. To find ETH sends/receives, inspect native_transfers[].direction \u2014 ETH moves appear across many categories (e.g. "token swap", "send", "receive"), not just category="send". Use this tool for: native coin transfers, swaps, airdrops, deposits, withdrawals, contract interactions, mixed history. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Returns paginated results \u2014 pass cursor to get the next page. Do NOT use for: specific ERC-20 tokens \u2192 get-wallet-token-transfers; NFT transfers \u2192 get-wallet-nft-transfers; balances \u2192 get-wallet-token-balances.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"fromDate",type:"string",description:'Inclusive start date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"toDate",type:"string",description:'Inclusive end date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"fromBlock",type:"number",description:"Minimum block number.",required:!1},{name:"toBlock",type:"number",description:"Maximum block number.",required:!1},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1},{name:"order",type:"string",description:'"DESC" newest first (default) or "ASC" oldest first.',required:!1,default:"DESC"},{name:"includeInternalTransactions",type:"boolean",description:"Include decoded internal transactions on each item.",required:!1},{name:"nftMetadata",type:"boolean",description:"Include NFT normalized metadata in nft_transfers.",required:!1},{name:"limit",type:"number",description:"Number of transactions to return per page. Defaults to 10.",required:!1,default:10}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=Y(e.address,t),r=R(e.chain,t),o=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,s=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,a=typeof e.fromBlock=="number"?e.fromBlock:typeof e.fromBlock=="string"&&e.fromBlock?Number(e.fromBlock):void 0,i=typeof e.toBlock=="number"?e.toBlock:typeof e.toBlock=="string"&&e.toBlock?Number(e.toBlock):void 0,l=Number.isFinite(a)?a:void 0,u=Number.isFinite(i)?i:void 0,d=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,p=(typeof e.order=="string"?e.order.toUpperCase():void 0)==="ASC"?"ASC":"DESC",h=typeof e.includeInternalTransactions=="boolean"?e.includeInternalTransactions:void 0,g=typeof e.nftMetadata=="boolean"?e.nftMetadata:void 0,f=typeof e.limit=="number"?e.limit:typeof e.limit=="string"?Number(e.limit):void 0,y=Number.isFinite(f)&&f>0?f:10,k=await this.service.getWalletHistory({address:n,chain:r,limit:y,fromDate:o,toDate:s,fromBlock:l,toBlock:u,cursor:d,order:p,includeInternalTransactions:h,nftMetadata:g});if(!k.success)return{error:k.error||"Failed to fetch wallet history"};let w=k.data?.result||[];return{walletAddress:n,chain:r||"default",pagination:{cursor:k.data?.cursor??null,hasMore:!!k.data?.cursor},transactionCount:w.length,transactions:w}}};function Kr(c){if(c.value_decimal)return c.value_decimal;let e=parseInt(c.token_decimals??"18",10);if(isNaN(e)||e===0)return c.value;try{let t=BigInt(c.value),n=BigInt(10)**BigInt(e),r=t/n,s=(t%n).toString().padStart(e,"0").replace(/0+$/,"");return s?`${r}.${s}`:`${r}`}catch{return c.value}}var et=class extends v{name="get-wallet-token-transfers";description=`Get all ERC-20 token transfers for a wallet from the dedicated Moralis transfers endpoint. Each transfer includes direction (send/receive), human-readable amount, and the exact counterparty address with its label/entity. The response includes a "counterparties" section that aggregates unique sender/recipient addresses with totals. Use tokenSymbol (e.g. "USDC") to filter by token name \u2014 the tool automatically resolves the contract address by checking the wallet's token balances first, then falling back to token search. Use contractAddresses when you already have the exact contract address. Use this tool for questions like: "When did I last send USDC and to whom?" (tokenSymbol=USDC, direction=send, limit=1), "Who have I sent token 0x\u2026 to?" (contractAddresses=[0x\u2026], direction=send), "Who sent me USDT?" (tokenSymbol=USDT, direction=receive), "Show all USDC transfers" (tokenSymbol=USDC), "What tokens did I receive this month?" (fromDate=..., direction=receive). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Returns paginated results \u2014 pass cursor to get the next page. IMPORTANT \u2014 this tool covers ERC-20 tokens ONLY. Do NOT use it for native coins (ETH, BNB, MATIC, AVAX, etc.) \u2014 native coin transfers are not ERC-20 and will not appear here. For native coin transfer history use get-wallet-history instead.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"tokenSymbol",type:"string",description:'Filter by token symbol (e.g. "USDC", "USDT", "WETH"). The tool resolves the contract address automatically: first checks wallet balances, then searches by symbol on the chain. Use this when the user mentions a token by name/symbol. Takes precedence over contractAddresses when both are given.',required:!1},{name:"contractAddresses",type:"array",description:"Filter by one or more ERC-20 token contract addresses (0x\u2026). Use when the exact contract address is already known. Ignored if tokenSymbol is provided.",required:!1,items:{type:"string"}},{name:"direction",type:"string",description:'Client-side direction filter: "send" (wallet is sender), "receive" (wallet is recipient), "all" (default).',required:!1,default:"all"},{name:"limit",type:"number",description:'Page size (1-100, default 25). Use limit=1 ONLY when the user asks for the single most recent transfer (e.g. "L\u1EA7n cu\u1ED1i c\xF9ng t\xF4i g\u1EEDi USDC l\xE0 khi n\xE0o?"). For "show recent history" or "list my transfers" questions use the default (25). Use 100 for bulk counterparty analysis.',required:!1,default:25},{name:"fromDate",type:"string",description:'Inclusive start date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"toDate",type:"string",description:'Inclusive end date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"fromBlock",type:"number",description:"Minimum block number.",required:!1},{name:"toBlock",type:"number",description:"Maximum block number.",required:!1},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1},{name:"order",type:"string",description:'"DESC" newest first (default) or "ASC" oldest first.',required:!1,default:"DESC"}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=Y(e.address,t),r=R(e.chain,t),o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):25,a=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,i=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,l=this.parseBlock(e.fromBlock),u=this.parseBlock(e.toBlock),d=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,p=(typeof e.order=="string"?e.order.toUpperCase():"")==="ASC"?"ASC":"DESC",h=typeof e.direction=="string"?e.direction.trim().toLowerCase():"all",g=h==="send"?"send":h==="receive"?"receive":"all",f=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,y=Array.isArray(e.contractAddresses)?e.contractAddresses.filter(x=>typeof x=="string"&&!!x).map(x=>x.trim()):void 0,k,w;if(f){let x=await this.resolveContractAddressBySymbol(f,n,r);x&&(y=[x.address],k=x.symbol,w=x.name)}let b=await this.service.getWalletTokenTransfers({address:n,chain:r,contractAddresses:y,limit:s,fromDate:a,toDate:i,fromBlock:l,toBlock:u,cursor:d,order:p});if(!b.success)return{error:b.error||"Failed to fetch token transfers"};let T=n.toLowerCase(),S=(b.data?.result||[]).map(x=>{let D=x.from_address.toLowerCase()===T,A=D?"send":"receive",H=D?x.to_address:x.from_address,Pe=D?x.to_address_label??void 0:x.from_address_label??void 0,ce=D?x.to_address_entity??void 0:x.from_address_entity??void 0;return{...x,direction:A,amount_formatted:Kr(x),counterparty:H,counterparty_label:Pe,counterparty_entity:ce}}),C=g==="all"?S:S.filter(x=>x.direction===g);if(f&&!y){let x=f.toLowerCase();C=C.filter(D=>(D.token_symbol??"").toLowerCase()===x)}let L=this.buildCounterparties(C);return{walletAddress:n,chain:r||"default",resolvedToken:k?{symbol:k,name:w,contractAddress:y?.[0]}:null,filters:{tokenSymbol:f||null,contractAddresses:y||null,direction:g,limit:s,order:p,fromDate:a||null,toDate:i||null,fromBlock:l??null,toBlock:u??null},pagination:{cursor:b.data?.cursor??null,pageSize:b.data?.page_size??s,page:b.data?.page??null,hasMore:!!b.data?.cursor},transferCount:C.length,transfers:C,counterparties:L}}async resolveContractAddressBySymbol(e,t,n){let r=e.toLowerCase(),o=await this.service.getWalletTokenBalances({address:t,chain:n});if(o.success&&o.data?.result?.length){let a=o.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}let s=await this.service.searchTokensByKey({key:e,chain:n});if(s.success&&s.data?.length){let i=s.data.find(l=>(l.symbol??"").toLowerCase()===r)??s.data[0];if(i?.token_address)return{address:i.token_address,symbol:i.symbol,name:i.name}}}buildCounterparties(e){let t=new Map,n=new Map;for(let o of e){let s=o.direction==="send"?t:n,a=o.counterparty.toLowerCase(),i=parseFloat(o.amount_formatted)||0,l=s.get(a);l?(l.total+=i,l.count+=1,!l.label&&o.counterparty_label&&(l.label=o.counterparty_label),!l.entity&&o.counterparty_entity&&(l.entity=o.counterparty_entity),o.block_timestamp<l.first&&(l.first=o.block_timestamp),o.block_timestamp>l.last&&(l.last=o.block_timestamp)):s.set(a,{label:o.counterparty_label,entity:o.counterparty_entity,total:i,count:1,first:o.block_timestamp,last:o.block_timestamp})}let r=o=>Array.from(o.entries()).sort((s,a)=>a[1].count-s[1].count).map(([s,a])=>({address:s,label:a.label,entity:a.entity,total_amount:a.total.toFixed(6).replace(/\.?0+$/,""),tx_count:a.count,first_seen:a.first,last_seen:a.last}));return{sent:r(t),received:r(n)}}parseBlock(e){if(typeof e=="number")return Number.isFinite(e)?e:void 0;if(typeof e=="string"&&e){let t=Number(e);return Number.isFinite(t)?t:void 0}}};var tt=class extends v{name="get-wallet-nft-transfers";description=`Get all NFT transfers for a wallet from the dedicated Moralis NFT transfers endpoint. Each transfer includes direction (send/receive), the exact counterparty address, token ID, collection, contract type (ERC721/ERC1155), and optional last sale price. The response includes a 'counterparties' section aggregating unique senders/recipients with token IDs and collections. Use this tool for questions like: "Which NFTs did I sell and to whom?" (direction=send), "Who sent me NFTs from collection 0x\u2026?" (contractAddresses=[0x\u2026], direction=receive), "Show all transfers for my NFT collection 0x\u2026" (contractAddresses=[0x\u2026]), "What NFTs did I mint?" (direction=receive), "Show NFT sales with prices" (direction=send, includePrices=true). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Returns paginated results \u2014 pass cursor to get the next page.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"contractAddresses",type:"array",description:"Filter by one or more NFT contract addresses (0x\u2026). Use this to scope queries to a specific collection. Omit to return all NFT transfers.",required:!1,items:{type:"string"}},{name:"direction",type:"string",description:'Client-side direction filter: "send" (wallet sent the NFT), "receive" (wallet received it), "all" (default).',required:!1,default:"all"},{name:"limit",type:"number",description:"Page size (1-100, default 25).",required:!1,default:25},{name:"fromDate",type:"string",description:'Inclusive start date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"toDate",type:"string",description:'Inclusive end date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"fromBlock",type:"number",description:"Minimum block number.",required:!1},{name:"toBlock",type:"number",description:"Maximum block number.",required:!1},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1},{name:"order",type:"string",description:'"DESC" newest first (default) or "ASC" oldest first.',required:!1,default:"DESC"},{name:"includePrices",type:"boolean",description:"If true, include last_sale price data (buyer, seller, price, USD value, payment token) on each transfer. Useful for questions about NFT sale prices.",required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=Y(e.address,t),r=R(e.chain,t),o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):25,a=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,i=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,l=this.parseBlock(e.fromBlock),u=this.parseBlock(e.toBlock),d=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,p=(typeof e.order=="string"?e.order.toUpperCase():"")==="ASC"?"ASC":"DESC",h=typeof e.includePrices=="boolean"?e.includePrices:void 0,g=typeof e.direction=="string"?e.direction.trim().toLowerCase():"all",f=g==="send"?"send":g==="receive"?"receive":"all",y=Array.isArray(e.contractAddresses)?e.contractAddresses.filter(C=>typeof C=="string"&&!!C).map(C=>C.trim()):void 0,k=await this.service.getWalletNftTransfers({address:n,chain:r,contractAddresses:y,limit:s,fromDate:a,toDate:i,fromBlock:l,toBlock:u,cursor:d,order:p,includePrices:h});if(!k.success)return{error:k.error||"Failed to fetch NFT transfers"};let w=n.toLowerCase(),T=(k.data?.result||[]).map(C=>{let L=C.from_address.toLowerCase()===w,x=L?"send":"receive",D=L?C.to_address:C.from_address,A=L?C.to_address_label??void 0:C.from_address_label??void 0,H=L?C.to_address_entity??void 0:C.from_address_entity??void 0;return{...C,direction:x,counterparty:D,counterparty_label:A,counterparty_entity:H}}),P=f==="all"?T:T.filter(C=>C.direction===f),S=this.buildCounterparties(P);return{walletAddress:n,chain:r||"default",filters:{contractAddresses:y||null,direction:f,limit:s,order:p,fromDate:a||null,toDate:i||null,fromBlock:l??null,toBlock:u??null,includePrices:h??null},pagination:{cursor:k.data?.cursor??null,pageSize:k.data?.page_size??s,page:k.data?.page??null,hasMore:!!k.data?.cursor},transferCount:P.length,transfers:P,counterparties:S}}buildCounterparties(e){let t=new Map,n=new Map;for(let o of e){let s=o.direction==="send"?t:n,a=o.counterparty.toLowerCase(),i=o.token_name??o.token_address,l=s.get(a);l?(l.tokenIds.add(o.token_id),l.collections.add(i),l.count+=1,!l.label&&o.counterparty_label&&(l.label=o.counterparty_label),!l.entity&&o.counterparty_entity&&(l.entity=o.counterparty_entity),o.block_timestamp<l.first&&(l.first=o.block_timestamp),o.block_timestamp>l.last&&(l.last=o.block_timestamp)):s.set(a,{label:o.counterparty_label,entity:o.counterparty_entity,tokenIds:new Set([o.token_id]),collections:new Set([i]),count:1,first:o.block_timestamp,last:o.block_timestamp})}let r=o=>Array.from(o.entries()).sort((s,a)=>a[1].count-s[1].count).map(([s,a])=>({address:s,label:a.label,entity:a.entity,token_ids:Array.from(a.tokenIds),collections:Array.from(a.collections),tx_count:a.count,first_seen:a.first,last_seen:a.last}));return{sent:r(t),received:r(n)}}parseBlock(e){if(typeof e=="number")return Number.isFinite(e)?e:void 0;if(typeof e=="string"&&e){let t=Number(e);return Number.isFinite(t)?t:void 0}}};var kn=class extends v{name="get-wallet-net-worth";description=`Get a wallet's total net worth in USD, with a per-chain breakdown. Returns: total_networth_usd (grand total across all queried chains), chains[] (each with chain, native_balance_formatted, native_balance_usd, token_balance_usd, networth_usd), and optional unsupported_chain_ids / unavailable_chains lists. Use this tool for questions like: "How much is my wallet worth?", "What is my total portfolio value?", "What is my net worth on Ethereum?", "Portfolio value across all chains?". Pass chains=["0x1","0x2105"] to query multiple chains at once; defaults to the connected chain. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Do NOT use for: individual token balances \u2192 get-wallet-token-balances; realized profit/loss \u2192 get-wallet-pnl-summary.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chains",type:"array",description:'One or more hex chain IDs to aggregate. Values: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea If omitted, defaults to the connected chain.',required:!1,items:{type:"string"}},{name:"excludeSpam",type:"boolean",description:"Exclude tokens flagged as spam. Default true for cleaner totals.",required:!1,default:!0},{name:"excludeUnverifiedContracts",type:"boolean",description:"Exclude unverified token contracts. Default false.",required:!1,default:!1},{name:"maxTokenInactivity",type:"number",description:"Exclude tokens that have been inactive for more than N days.",required:!1},{name:"minPairSideLiquidityUsd",type:"number",description:"Exclude tokens with pair-side liquidity below this USD threshold.",required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0;if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let r=Array.isArray(e.chains)?e.chains.filter(p=>typeof p=="string"&&!!p).map(p=>p.trim()):void 0,o=t?.chain,s=r&&r.length>0?r:o?[o]:void 0,a=typeof e.excludeSpam=="boolean"?e.excludeSpam:!0,i=typeof e.excludeUnverifiedContracts=="boolean"?e.excludeUnverifiedContracts:void 0,l=typeof e.maxTokenInactivity=="number"&&Number.isFinite(e.maxTokenInactivity)?e.maxTokenInactivity:void 0,u=typeof e.minPairSideLiquidityUsd=="number"&&Number.isFinite(e.minPairSideLiquidityUsd)?e.minPairSideLiquidityUsd:void 0,d=await this.service.getWalletNetWorth({address:n,chains:s,excludeSpam:a,excludeUnverifiedContracts:i,maxTokenInactivity:l,minPairSideLiquidityUsd:u});if(!d.success)return{error:d.error||"Failed to fetch wallet net worth"};let m=d.data;return{walletAddress:n,chainsQueried:s??["default"],filters:{excludeSpam:a,excludeUnverifiedContracts:i??null,maxTokenInactivity:l??null,minPairSideLiquidityUsd:u??null},totalNetWorthUsd:m.total_networth_usd,chains:m.chains,unsupportedChainIds:m.unsupported_chain_ids??[],unavailableChains:m.unavailable_chains??[]}}};var jr=["all","7","30","60","90"],nt=class extends v{name="get-wallet-pnl-summary";description=`Get a wallet's aggregate realized profit-and-loss summary across all tokens it has traded. Returns: total_count_of_trades, total_trade_volume (USD), total_realized_profit_usd, total_realized_profit_percentage, total_buys, total_sells, total_sold_volume_usd, total_bought_volume_usd. Use this tool for questions like: "How much profit have I made trading?", "What is my realized PnL?", "How many trades have I done this month?" (days=30), "Am I up or down overall?". IMPORTANT \u2014 this endpoint only supports mainnet: Ethereum ("0x1"), Base ("0x2105"), Polygon ("0x89"). Use days="all" (default), "7", "30", "60", or "90" to scope the time window. Do NOT use for: per-token breakdown \u2192 get-wallet-pnl; current portfolio value \u2192 get-wallet-token-balance; transfer history \u2192 get-wallet-history.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"days",type:"string",description:'Time window for the summary. Values: "all" (default, lifetime), "7", "30", "60", "90". Pass "30" when the user asks about the last month, "7" for the last week, etc.',required:!1,default:"all"}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=Y(e.address,t),r=R(e.chain,t),o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=jr.includes(o)?o:void 0,a=await this.service.getWalletPnlSummary({address:n,chain:r,days:s});if(!a.success)return{error:a.error||"Failed to fetch wallet PnL summary"};let i=a.data;return{walletAddress:n,chain:r||"default",days:s??"all",summary:{totalCountOfTrades:i.total_count_of_trades,totalTradeVolume:i.total_trade_volume,totalRealizedProfitUsd:i.total_realized_profit_usd,totalRealizedProfitPercentage:i.total_realized_profit_percentage,totalBuys:i.total_buys,totalSells:i.total_sells,totalBoughtVolumeUsd:i.total_bought_volume_usd,totalSoldVolumeUsd:i.total_sold_volume_usd}}}};var zr=["all","7","30","60","90"],Tn=25,ot=class extends v{name="get-wallet-pnl";description=`Get a wallet's realized profit-and-loss broken down per ERC-20 token. Each entry includes: token_address, name, symbol, decimals, logo, avg_buy_price_usd, avg_sell_price_usd, total_usd_invested, total_tokens_bought, total_tokens_sold, total_sold_usd, avg_cost_of_quantity_sold, count_of_trades, realized_profit_usd, realized_profit_percentage, total_buys, total_sells, possible_spam. Use this tool for questions like: "Which tokens made me the most money?", "What are my best/worst trades?", "How much profit did I make on USDC?" (tokenSymbols=["USDC"]), "PnL for PEPE and DEGEN this month" (tokenSymbols=["PEPE","DEGEN"], days=30), "PnL breakdown for each token I traded this month" (days=30). IMPORTANT \u2014 this endpoint ONLY supports mainnet: Ethereum ("0x1"), Polygon ("0x89"), Base ("0x2105"). Use days="all" (default), "7", "30", "60", or "90" for the time window. Pass tokenSymbols (by ticker, auto-resolved) and/or tokenAddresses (raw 0x\u2026) to restrict to specific tokens \u2014 combined total capped at ${Tn}. Do NOT use for: aggregate summary \u2192 get-wallet-pnl-summary; current balances \u2192 get-wallet-token-balances; transfer history \u2192 get-wallet-token-transfers.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"days",type:"string",description:'Time window for the breakdown. Values: "all" (default, lifetime), "7", "30", "60", "90".',required:!1,default:"all"},{name:"tokenSymbols",type:"array",description:`Optional list of token symbols (e.g. ["USDC", "PEPE"]). Auto-resolved to contract addresses via the connected wallet's balances first, then Pantograph search. Symbols that can't be resolved are skipped. Merged with tokenAddresses; combined total capped at ${Tn}.`,required:!1,items:{type:"string"}},{name:"tokenAddresses",type:"array",description:`Optional list of raw ERC-20 contract addresses (0x\u2026). Use when the exact addresses are already known. Merged with tokenSymbols; combined total capped at ${Tn}.`,required:!1,items:{type:"string"}}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=Y(e.address,t),r=R(e.chain,t),o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=zr.includes(o)?o:void 0,a=Array.isArray(e.tokenSymbols)?e.tokenSymbols.filter(g=>typeof g=="string"&&!!g.trim()).map(g=>g.trim()):[],i=Array.isArray(e.tokenAddresses)?e.tokenAddresses.filter(g=>typeof g=="string"&&!!g.trim()).map(g=>g.trim()):[],l=[],u=[];a.length>0&&(await Promise.all(a.map(f=>this.resolveContractAddress(f,r,n)))).forEach((f,y)=>{f?l.push({symbol:a[y],address:f.address}):u.push(a[y])});let d=Qr([...l.map(g=>g.address),...i]).slice(0,Tn),m=d.length>0?d:void 0,p=await this.service.getWalletPnl({address:n,chain:r,days:s,tokenAddresses:m});if(!p.success)return{error:p.error||"Failed to fetch wallet PnL"};let h=p.data?.result??[];return{walletAddress:n,chain:r||"default",days:s??"all",filters:{tokenAddresses:m??null,resolvedSymbols:l.length>0?l:null,unresolvedSymbols:u.length>0?u:null},tokenCount:h.length,tokens:h}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};function Qr(c){let e=new Set,t=[];for(let n of c){let r=n.toLowerCase();e.has(r)||(e.add(r),t.push(n))}return t}var Xr=BigInt(2)**BigInt(255),rt=class extends v{name="get-wallet-approvals";description=`List every active ERC-20 token approval granted by a wallet \u2014 i.e. which contracts can still spend the wallet's tokens. For each approval, returns: the token (address, name, symbol, logo, current balance, USD price, USD at risk, possible_spam, verified_contract), the spender (address, label, entity \u2014 e.g. "Uniswap V3 Router", "Permit2"), the approved amount (value / value_formatted / isUnlimited flag), and when the approval was granted (block number, timestamp, transaction hash). Also returns aggregates: total approvals, unlimited-approval count, total USD at risk, and approvals flagged as risky (spam tokens, unverified contracts, or unlimited allowance to unlabeled spenders). Use for questions like: "What approvals do I have?", "Show my token approvals", "Am I exposed to any risky approvals?", "Which contracts can drain my wallet?", "Do I have any unlimited approvals?", "Revoke approvals / token allowances". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Paginated \u2014 pass cursor to fetch the next page.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"limit",type:"number",description:"Page size (1-100, default 100).",required:!1,default:100},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=Y(e.address,t),r=R(e.chain,t),o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):100,a=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,i=await this.service.getWalletApprovals({address:n,chain:r,limit:s,cursor:a});if(!i.success)return{error:i.error||"Failed to fetch wallet approvals"};let l=i.data,d=(l.result||[]).map(g=>Jr(g)),m=0,p=0,h=[];for(let g of d){g.isUnlimited&&(p+=1);let f=parseFloat(g.token.usdAtRisk??"");Number.isFinite(f)&&(m+=f);let y=g.token.possibleSpam||g.token.verifiedContract===!1,k=!g.spender.label&&!g.spender.entity;(y||g.isUnlimited&&k)&&h.push(g)}return{walletAddress:n,chain:r||"default",filters:{limit:s,cursor:a??null},pagination:{cursor:l.cursor??null,pageSize:l.page_size??s,page:l.page??null,hasMore:!!l.cursor},summary:{totalApprovals:d.length,unlimitedApprovals:p,totalUsdAtRisk:m,riskyApprovalCount:h.length},approvals:d,riskyApprovals:h}}};function Jr(c){let e=Zr(c.value);return{blockNumber:c.block_number,blockTimestamp:c.block_timestamp??null,transactionHash:c.transaction_hash??null,value:c.value,valueFormatted:c.value_formatted??null,isUnlimited:e,token:{address:c.token.address,addressLabel:c.token.address_label??null,name:c.token.name??null,symbol:c.token.symbol??null,logo:c.token.logo??null,possibleSpam:!!c.token.possible_spam,verifiedContract:c.token.verified_contract??null,currentBalance:c.token.current_balance??null,currentBalanceFormatted:c.token.current_balance_formatted??null,usdPrice:c.token.usd_price??null,usdAtRisk:c.token.usd_at_risk??null},spender:{address:c.spender.address,label:c.spender.address_label??null,entity:c.spender.entity??null,entityLogo:c.spender.entity_logo??null}}}function Zr(c){if(!c)return!1;try{return BigInt(c)>=Xr}catch{return!1}}var st=class extends v{name="get-wallet-defi-summary";description=`Get a wallet's DeFi protocols summary: which protocols the wallet is using, total USD value locked across all protocols, total unclaimed rewards/fees, number of active protocols, and total number of positions. Returns a per-protocol breakdown (protocol name, logo, url, positions count, usd value, unclaimed). Use for questions like: "Which DeFi protocols am I using?", "What's my total DeFi value?", "How much do I have in DeFi?", "Show my DeFi portfolio overview", "Unclaimed rewards across protocols". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Do NOT use for: detailed per-position data \u2192 get-wallet-defi-positions; positions inside a single protocol \u2192 get-wallet-defi-protocol-positions.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=Y(e.address,t),r=R(e.chain,t),o=await this.service.getWalletDefiSummary({address:n,chain:r});if(!o.success)return{error:o.error||"Failed to fetch wallet DeFi summary"};let s=o.data;return{walletAddress:n,chain:r||"default",activeProtocols:s.active_protocols,totalPositions:s.total_positions,totalUsdValue:s.total_usd_value,totalUnclaimedUsdValue:s.total_unclaimed_usd_value,protocols:(s.protocols||[]).map(a=>({protocolName:a.protocol_name,protocolId:a.protocol_id,protocolUrl:a.protocol_url,protocolLogo:a.protocol_logo,positions:a.positions,totalUsdValue:a.total_usd_value,totalUnclaimedUsdValue:a.total_unclaimed_usd_value,accountHealthFactor:a.account_health_factor??null}))}}};var at=class extends v{name="get-wallet-defi-positions";description='Get every DeFi position a wallet holds across all supported protocols, with full detail: protocol identity, position label (e.g. "Liquidity Provision", "Supply", "Borrow", "Staking"), tokens involved (with balances and USD values), balance_usd, total_unclaimed_usd_value, and position_details (fee tier, liquidity, APY, price range, health factor, etc.). Use for questions like: "Show my DeFi positions", "What LP positions do I have?", "What am I staking?", "Do I have any borrows?", "List all my DeFi holdings with details". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Do NOT use for: aggregate totals \u2192 get-wallet-defi-summary; positions filtered to a single protocol \u2192 get-wallet-defi-protocol-positions.';category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=Y(e.address,t),r=R(e.chain,t),o=await this.service.getWalletDefiPositions({address:n,chain:r});if(!o.success)return{error:o.error||"Failed to fetch wallet DeFi positions"};let s=o.data||[];return{walletAddress:n,chain:r||"default",positionCount:s.length,positions:s.map(a=>es(a))}}};function es(c){return{protocolName:c.protocol_name,protocolId:c.protocol_id,protocolUrl:c.protocol_url,protocolLogo:c.protocol_logo,accountHealthFactor:c.account_data?.health_factor??null,position:ts(c.position)}}function ts(c){return{label:c.label,address:c.address,balanceUsd:c.balance_usd,totalUnclaimedUsdValue:c.total_unclaimed_usd_value,tokens:(c.tokens||[]).map(e=>({tokenType:e.token_type,name:e.name,symbol:e.symbol,contractAddress:e.contract_address,decimals:e.decimals,logo:e.logo,thumbnail:e.thumbnail,balance:e.balance,balanceFormatted:e.balance_formatted,usdPrice:e.usd_price,usdValue:e.usd_value})),details:c.position_details??null}}var it=class extends v{name="get-wallet-defi-protocol-positions";description='Get every position a wallet holds inside a specific DeFi protocol, with full detail. Returns the protocol identity, totals (total_usd_value, total_unclaimed_usd_value), and an array of positions (label, tokens, balance_usd, unclaimed, fee tier, APY, health factor, etc.). Use when the user names a specific protocol, e.g. "my Uniswap v3 positions", "what do I have on Aave v3", "show my Lido staking", "my Curve pools", "Pendle positions". The "protocol" argument is the Moralis protocol id (kebab-case), e.g. uniswap-v2, uniswap-v3, pancakeswap-v2, pancakeswap-v3, quickswap-v2, quickswap-v3, sushiswap-v2, aave-v2, aave-v3, aave-lido, fraxswap-v1, fraxswap-v2, lido, makerdao, eigenlayer, pendle, etherfi, rocketpool, sparkfi, takara-lend, neverland, kintsu. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Do NOT use for: cross-protocol view \u2192 get-wallet-defi-positions; totals only \u2192 get-wallet-defi-summary.';category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"protocol",type:"string",description:'Moralis protocol identifier in kebab-case. Examples: "uniswap-v2", "uniswap-v3", "pancakeswap-v2", "pancakeswap-v3", "quickswap-v2", "quickswap-v3", "sushiswap-v2", "aave-v2", "aave-v3", "aave-lido", "fraxswap-v1", "fraxswap-v2", "lido", "makerdao", "eigenlayer", "pendle", "etherfi", "rocketpool", "sparkfi", "takara-lend", "neverland", "kintsu"Use get-wallet-defi-summary to discover which protocols a wallet uses.',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.protocol?.trim();if(!n)return{error:'Protocol identifier is required (e.g. "uniswap-v3", "aave-v3").'};let r=Y(e.address,t),o=R(e.chain,t),s=await this.service.getWalletDefiProtocolPositions({address:r,protocol:n,chain:o});if(!s.success)return{error:s.error||"Failed to fetch wallet DeFi protocol positions"};let a=s.data;return a?{walletAddress:r,chain:o||"default",protocolName:a.protocol_name,protocolId:a.protocol_id,protocolUrl:a.protocol_url,protocolLogo:a.protocol_logo,totalUsdValue:a.total_usd_value,totalUnclaimedUsdValue:a.total_unclaimed_usd_value,accountHealthFactor:a.account_data?.health_factor??null,positionCount:(a.positions||[]).length,positions:(a.positions||[]).map(ns)}:{error:"No data returned for this protocol"}}};function ns(c){return{label:c.label,address:c.address,balanceUsd:c.balance_usd,totalUnclaimedUsdValue:c.total_unclaimed_usd_value,tokens:(c.tokens||[]).map(e=>({tokenType:e.token_type,name:e.name,symbol:e.symbol,contractAddress:e.contract_address,decimals:e.decimals,logo:e.logo,thumbnail:e.thumbnail,balance:e.balance,balanceFormatted:e.balance_formatted,usdPrice:e.usd_price,usdValue:e.usd_value})),details:c.position_details??null}}function os(c){try{return(Number(BigInt(c))/1e18).toFixed(10).replace(/\.?0+$/,"")||"0"}catch{return c}}var lt=class extends v{name="get-transaction-by-hash";description='Get full decoded details of a single EVM transaction by its hash. Returns: status (success/failed), timestamp, from/to addresses with labels and entity names, native value transferred (ETH), transaction fee, gas used, decoded function call with params, and decoded event logs (ERC-20 transfers, swaps, approvals, etc.). Use this when the user asks: "what did this transaction do?", "explain tx 0x\u2026", "was this transaction successful?", "who sent/received in this tx?", "what function was called?". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"transactionHash",type:"string",description:"The transaction hash to look up (0x\u2026).",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.transactionHash;if(!n)return{error:"Transaction hash is required."};let r=R(e.chain,t),o=await this.service.getTransactionByHash({transactionHash:n,chain:r});if(!o.success||!o.data)return{error:o.error||"Failed to fetch transaction"};let s=o.data,a=(s.logs??[]).filter(l=>!!l.decoded_event).map(l=>({contract:l.address,signature:l.decoded_event.signature,label:l.decoded_event.label,params:l.decoded_event.params})),i=s.logs.map(l=>({address:l.address,decodedEvent:l.decoded_event?{signature:l.decoded_event.signature,label:l.decoded_event.label,params:l.decoded_event.params}:null}));return{hash:s.hash,status:s.receipt_status==="1"?"success":s.receipt_status==="0"?"failed":"unknown",timestamp:s.block_timestamp,blockNumber:s.block_number,from:{address:s.from_address,label:s.from_address_label||null,entity:s.from_address_entity||null},to:s.to_address?{address:s.to_address,label:s.to_address_label||null,entity:s.to_address_entity||null}:null,valueEth:os(s.value||"0"),fee:s.transaction_fee,gasUsed:s.receipt_gas_used,gasPriceGwei:s.gas_price?(Number(s.gas_price)/1e9).toFixed(4):null,decodedCall:s.decoded_call?{signature:s.decoded_call.signature,label:s.decoded_call.label,params:s.decoded_call.params}:null,decodedEvents:a,contractCreated:s.receipt_contract_address||null,logs:i}}};var fe=class extends v{kind="data";category="nft";noSuggestions=!0;parameters=[];linkPhrase="this website";url;constructor(e){super();let t=e?.url?.trim();this.url=t||void 0}async run(){let e=this.url?`[${this.linkPhrase}](${this.url})`:this.linkPhrase,t=this.template.replace("{link}",e);return{message:t,_instructions:`This is a fixed notice. Output it to the user EXACTLY as written below \u2014 in English, without translating, rephrasing, summarising, or adding/removing any words. Preserve the Markdown link verbatim (do not change, wrap, or drop the URL or the linked words). Do not mention tools. Reply with only this message:
16
16
 
17
- `+t}}};var ct=class extends ge{name="get-wallet-nfts";description='Use when the user wants to view their NFTs / NFT collection / holdings ("my NFTs", "show my NFTs", "what NFTs do I own", "NFT collection"). Returns a fixed notice directing them to the NFT website. Takes no arguments.';template="Please view your NFT collection on {link} for more details"};var ut=class extends ge{name="get-nft-contract-info";description="Use when the user asks about an NFT collection / contract (name, symbol, type, floor, socials). Returns a fixed notice directing them to the NFT website. Takes no arguments.";template="Please view your NFT collection on {link} for more details"};var dt=class extends ge{name="get-nft-metadata";description='Use when the user asks about a specific NFT \u2014 its details, traits, image, or floor price ("show NFT #1234 of collection X", "traits of this NFT", "floor price of this NFT"). Returns a fixed notice directing them to the NFT website. Takes no arguments.';template="Please view your NFT collection on {link} for more details"};var mt=class extends v{name="gemini-search-ai";description="Ask a search-grounded Gemini model for blockchain / crypto insights that no dedicated tool can answer. Use this as a LAST-RESORT fallback when the specialised data tools are insufficient. Backed by live Google Search grounding \u2014 good for: market analysis, category/theme queries (meme, AI, gaming, L2, RWA), DeFi protocol explanations, cross-chain comparisons, trend / sentiment, recent news or announcements, gas insights, NFT collection lore, and any open-ended crypto reasoning question. Chain-agnostic \u2014 write the chain (Ethereum, BSC, Base, \u2026) and any addresses, time range, or token symbols directly into the prompt. Do NOT use this for: token balances \u2192 get-wallet-token-balances; token prices/metadata \u2192 get-token-info; NFT holdings \u2192 get-wallet-nfts; NFT contract info \u2192 get-nft-contract-info; transaction details \u2192 get-transaction-by-hash; wallet history \u2192 get-wallet-history; token transfers \u2192 get-wallet-token-transfers; NFT transfers \u2192 get-wallet-nft-transfers; token holders \u2192 get-token-holders; wallet PnL \u2192 get-wallet-pnl or get-wallet-pnl-summary; pool / liquidity data \u2192 pool-agent tools.";category="blockchain-ai";parameters=[{name:"prompt",type:"string",description:'A clear, self-contained English question for the AI. Must include every detail needed to answer without the conversation context: chain (Ethereum / BSC / Base / \u2026), wallet or contract addresses, token symbols, time range, what to compare or rank. Instruct the model to use up-to-date web sources when relevant. Example: "List the top 10 meme-category tokens on Ethereum right now, ranked by 24h trading volume, using up-to-date market data."',required:!0}];llm;constructor(e){super(),this.llm=new pe(e??{})}async run(e,t){let n=typeof e.prompt=="string"?e.prompt.trim():"";if(!n)return{error:"Missing required parameter: prompt"};let r=[];t?.walletAddress&&r.push(`- Connected wallet address: ${t.walletAddress}`),t?.chain&&r.push(`- Current chain: ${t.chain}`),r.push("Use this context only when relevant to the question. Do not invent on-chain values.");let o=Date.now(),s=[{role:"system",content:`[USER CONTEXT]
17
+ `+t}}};var ct=class extends fe{name="get-wallet-nfts";description='Use when the user wants to view their NFTs / NFT collection / holdings ("my NFTs", "show my NFTs", "what NFTs do I own", "NFT collection"). Returns a fixed notice directing them to the NFT website. Takes no arguments.';template="Please view your NFT collection on {link} for more details"};var ut=class extends fe{name="get-nft-contract-info";description="Use when the user asks about an NFT collection / contract (name, symbol, type, floor, socials). Returns a fixed notice directing them to the NFT website. Takes no arguments.";template="Please view your NFT collection on {link} for more details"};var dt=class extends fe{name="get-nft-metadata";description='Use when the user asks about a specific NFT \u2014 its details, traits, image, or floor price ("show NFT #1234 of collection X", "traits of this NFT", "floor price of this NFT"). Returns a fixed notice directing them to the NFT website. Takes no arguments.';template="Please view your NFT collection on {link} for more details"};var mt=class extends v{name="gemini-search-ai";description="Ask a search-grounded Gemini model for blockchain / crypto insights that no dedicated tool can answer. Use this as a LAST-RESORT fallback when the specialised data tools are insufficient. Backed by live Google Search grounding \u2014 good for: market analysis, category/theme queries (meme, AI, gaming, L2, RWA), DeFi protocol explanations, cross-chain comparisons, trend / sentiment, recent news or announcements, gas insights, NFT collection lore, and any open-ended crypto reasoning question. Chain-agnostic \u2014 write the chain (Ethereum, BSC, Base, \u2026) and any addresses, time range, or token symbols directly into the prompt. Do NOT use this for: token balances \u2192 get-wallet-token-balances; token prices/metadata \u2192 get-token-info; NFT holdings \u2192 get-wallet-nfts; NFT contract info \u2192 get-nft-contract-info; transaction details \u2192 get-transaction-by-hash; wallet history \u2192 get-wallet-history; token transfers \u2192 get-wallet-token-transfers; NFT transfers \u2192 get-wallet-nft-transfers; token holders \u2192 get-token-holders; wallet PnL \u2192 get-wallet-pnl or get-wallet-pnl-summary; pool / liquidity data \u2192 pool-agent tools.";category="blockchain-ai";parameters=[{name:"prompt",type:"string",description:'A clear, self-contained English question for the AI. Must include every detail needed to answer without the conversation context: chain (Ethereum / BSC / Base / \u2026), wallet or contract addresses, token symbols, time range, what to compare or rank. Instruct the model to use up-to-date web sources when relevant. Example: "List the top 10 meme-category tokens on Ethereum right now, ranked by 24h trading volume, using up-to-date market data."',required:!0}];llm;constructor(e){super(),this.llm=new se(e??{})}async run(e,t){let n=typeof e.prompt=="string"?e.prompt.trim():"";if(!n)return{error:"Missing required parameter: prompt"};let r=[];t?.walletAddress&&r.push(`- Connected wallet address: ${t.walletAddress}`),t?.chain&&r.push(`- Current chain: ${t.chain}`),r.push("Use this context only when relevant to the question. Do not invent on-chain values.");let o=Date.now(),s=[{role:"system",content:`[USER CONTEXT]
18
18
  ${r.join(`
19
- `)}`,timestamp:o},{role:"user",content:n,timestamp:o}];try{return{source:"gemini-search-ai",answer:(await this.llm.chat(s,void 0,{googleSearch:!0,maxRetries:1})).text?.trim()??""}}catch(a){return{error:a instanceof Error?a.message:String(a)}}}};var zr="https://interface.gateway.uniswap.org",Qr="/v2/uniswap.explore.v1.ExploreStatsService/ExploreStats",So="/v2/Search.v1.SearchService/SearchTokens",Xr="/v1/graphql",Ce=3,yn=800,Jr={"0x1":"ETHEREUM","0xa":"OPTIMISM","0x38":"BNB","0x89":"POLYGON","0x2105":"BASE","0xa4b1":"ARBITRUM","0xa86a":"AVALANCHE","0xe708":"LINEA"},Zr=`query V3Pool($chain: Chain!, $address: String!) {
19
+ `)}`,timestamp:o},{role:"user",content:n,timestamp:o}];try{return{source:"gemini-search-ai",answer:(await this.llm.chat(s,void 0,{googleSearch:!0,maxRetries:1})).text?.trim()??""}}catch(a){return{error:a instanceof Error?a.message:String(a)}}}};var rs=`You are a world-class DEX liquidity-pool master and analyst \u2014 the deepest pool expert there is.
20
+ You know everything about liquidity pools across protocols and chains: Uniswap V2/V3/V4, PancakeSwap, Curve, Balancer, SushiSwap, Aerodrome, and more.
21
+ Your expertise covers: concentrated liquidity and tick ranges, fee tiers, TVL / 24h volume / fee APR, impermanent loss, LP position management and rebalancing, range strategy (wide vs narrow), pool health and risk, MEV / slippage, incentives & farming, and how pool mechanics differ between AMM designs.
22
+ You answer ANY pool-related question \u2014 conceptual, strategic, comparative, or current-events \u2014 clearly and accurately.
23
+ Use the live Google Search grounding you are given to back claims with up-to-date data (TVL, volumes, APRs, recent protocol changes) whenever relevant; never invent specific on-chain numbers.
24
+ Be concise, practical, and honest about uncertainty. End anything that touches returns or strategy with a brief reminder that it is informational, not financial advice. Answer in the user's language. Do NOT mention tools, APIs, or that you are a fallback.`,pt=class extends v{name="pool-fallback-ai";description="Ask a master liquidity-pool analyst (search-grounded AI) any DEX pool question that no structured pool tool can answer. This is the LAST-RESORT pool fallback. Backed by live Google Search \u2014 great for: how Uniswap V3 / V4 pools and concentrated liquidity work, fee-tier and range strategy, impermanent loss, comparing protocols (Uniswap vs Curve vs PancakeSwap \u2026), pool risk / safety, incentives & farming, recent pool-related news or protocol changes, and any open-ended or conceptual pool reasoning. Chain-agnostic \u2014 write the chain (Ethereum, BSC, Base, \u2026) and any pool address or token symbols directly into the prompt. Do NOT use this when a structured subgraph tool fits (pool search, trending pools, pool/position detail by address or id, price-range candidates, add-liquidity) \u2014 only when none of them can serve the question.";category="blockchain-ai";parameters=[{name:"prompt",type:"string",description:'A clear, self-contained English question for the pool master. Must include every detail needed to answer without the conversation context: chain (Ethereum / BSC / Base / \u2026), pool address or token symbols, fee tier, time range, what to compare or explain. Instruct the model to use up-to-date web sources when relevant. Example: "Explain how to choose a price range for an ETH/USDC 0.05% Uniswap V3 pool on Base, and the trade-offs between a narrow and a wide range, using current market context."',required:!0}];llm;constructor(e){super(),this.llm=new se(e??{})}async run(e,t){let n=typeof e.prompt=="string"?e.prompt.trim():"";if(!n)return{error:"Missing required parameter: prompt"};let r=[];t?.walletAddress&&r.push(`- Connected wallet address: ${t.walletAddress}`),t?.chain&&r.push(`- Current chain: ${t.chain}`),r.push("Use this context only when relevant to the question. Do not invent on-chain values.");let o=Date.now(),s=[{role:"system",content:rs,timestamp:o},{role:"system",content:`[USER CONTEXT]
25
+ ${r.join(`
26
+ `)}`,timestamp:o},{role:"user",content:n,timestamp:o}];try{return{source:"pool-fallback-ai",answer:(await this.llm.chat(s,void 0,{googleSearch:!0,maxRetries:1})).text?.trim()??""}}catch(a){return{error:a instanceof Error?a.message:String(a)}}}};var ss="https://interface.gateway.uniswap.org",as="/v2/uniswap.explore.v1.ExploreStatsService/ExploreStats",No="/v2/Search.v1.SearchService/SearchTokens",is="/v1/graphql",Ce=3,vn=800,ls={"0x1":"ETHEREUM","0xa":"OPTIMISM","0x38":"BNB","0x89":"POLYGON","0x2105":"BASE","0xa4b1":"ARBITRUM","0xa86a":"AVALANCHE","0xe708":"LINEA"},cs=`query V3Pool($chain: Chain!, $address: String!) {
20
27
  v3Pool(chain: $chain, address: $address) {
21
28
  id
22
29
  protocolVersion
@@ -49,7 +56,7 @@ fragment TokenPrice on Token {
49
56
  id
50
57
  market(currency: USD) { id price { id value __typename } __typename }
51
58
  __typename
52
- }`,_o=`
59
+ }`,Mo=`
53
60
  fragment SimpleTokenDetails on Token {
54
61
  ...TokenBasicInfoParts
55
62
  project { id isSpam logoUrl name safetyLevel __typename }
@@ -64,7 +71,7 @@ fragment TokenPrice on Token {
64
71
  id
65
72
  market(currency: USD) { id price { id value __typename } __typename }
66
73
  __typename
67
- }`,es=`query V2Pair($chain: Chain!, $address: String!) {
74
+ }`,us=`query V2Pair($chain: Chain!, $address: String!) {
68
75
  v2Pair(chain: $chain, address: $address) {
69
76
  id
70
77
  protocolVersion
@@ -81,7 +88,7 @@ fragment TokenPrice on Token {
81
88
  __typename
82
89
  }
83
90
  }
84
- ${_o}`,ts=`query V4Pool($chain: Chain!, $poolId: String!) {
91
+ ${Mo}`,ds=`query V4Pool($chain: Chain!, $poolId: String!) {
85
92
  v4Pool(chain: $chain, poolId: $poolId) {
86
93
  id
87
94
  protocolVersion
@@ -102,12 +109,12 @@ ${_o}`,ts=`query V4Pool($chain: Chain!, $poolId: String!) {
102
109
  __typename
103
110
  }
104
111
  }
105
- ${_o}`,ns={"0x1":1,"0xa":10,"0x38":56,"0x89":137,"0x2105":8453,"0xa4b1":42161,"0xa86a":43114,"0xe708":59144};function bn(c){return new Promise(e=>setTimeout(e,c))}function Gn(c){if(!c)return 1;let e=c.toLowerCase();return ns[e]??1}function Kn(c){if(!c)return"ETHEREUM";let e=c.toLowerCase();return Jr[e]??"ETHEREUM"}function xo(c,e){if(!c)return[];switch(e){case"V2":return c.poolStatsV2??[];case"V3":return c.poolStatsV3??[];case"V4":return c.poolStatsV4??[]}}function Ao(c,e){switch(e){case"volume1Day":return c.volume1Day?.value??0;case"volume1Week":return c.volume1Week?.value??0;case"volume30Day":return c.volume30Day?.value??0;case"txCount":return c.txCount??0;case"apr":return c.apr??0;default:return c.totalLiquidity?.value??0}}function Po(c){let e=c.volume1Day?.value,t=c.totalLiquidity?.value;return typeof c.feeTier!="number"||!e||!t?void 0:c.feeTier/1e4*e/t*365}var te=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??zr).replace(/\/$/,"")}async getExploreStats(e){let t=Gn(e?.chain),n=e?.multichain===!0,r=encodeURIComponent(JSON.stringify({chainId:String(t),multichain:n})),o=`${this.baseUrl}${Qr}?connect=v1&encoding=json&message=${r}`,s="Unknown error";for(let a=1;a<=Ce;a++)try{let i=await _.get(o,{headers:{accept:"*/*",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"}});if(!i.ok)throw new Error(N(i));return{success:!0,data:i.data}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<Ce&&await bn(yn)}return{success:!1,error:s}}async getTopPools(e){let t=e?.protocolVersion??"V3",n=e?.limit&&e.limit>0?Math.floor(e.limit):10,r=e?.sortBy??"tvl",o=await this.getExploreStats({chain:e?.chain,multichain:e?.multichain});return!o.success||!o.data?{success:!1,error:o.error||"Failed to fetch ExploreStats"}:{success:!0,data:xo(o.data.stats,t).map(i=>({...i,apr:Po(i),fee24hUsd:i.volume1Day?.value&&i.feeTier?i.feeTier/1e4*i.volume1Day.value:void 0})).sort((i,l)=>Ao(l,r)-Ao(i,r)).slice(0,n)}}async getV3PoolDetail(e){let t=e.address?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V3Pool",query:Zr,variables:{chain:Kn(e.chain),address:t},pickField:"v3Pool"}):{success:!1,error:"Pool address is required"}}async getV2PoolDetail(e){let t=e.address?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V2Pair",query:es,variables:{chain:Kn(e.chain),address:t},pickField:"v2Pair"}):{success:!1,error:"Pair address is required"}}async getV4PoolDetail(e){let t=e.poolId?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V4Pool",query:ts,variables:{chain:Kn(e.chain),poolId:t},pickField:"v4Pool"}):{success:!1,error:"V4 poolId is required"}}async fetchPoolDetailGraphQL(e){let t=`${this.baseUrl}${Xr}`,n=JSON.stringify({operationName:e.operationName,variables:e.variables,query:e.query}),r="Unknown error";for(let o=1;o<=Ce;o++)try{let s=await _.post(t,n,{headers:{accept:"*/*","content-type":"application/json",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web","_dd-custom-header-graph-ql-operation-name":e.operationName,"_dd-custom-header-graph-ql-operation-type":"query"}});if(!s.ok)throw new Error(N(s));let a=s.data;if(a.errors)throw new Error(`GraphQL error: ${JSON.stringify(a.errors)}`);let i=a.data?.[e.pickField]??void 0;return i?{success:!0,data:i}:{success:!1,error:"Pool not found"}}catch(s){r=s instanceof Error?s.message:"Unknown error",o<Ce&&await bn(yn)}return{success:!1,error:r}}async searchTokens(e){let t=e.query?.trim();if(!t)return{success:!1,error:"Search query is required"};if(B(t))return{success:!1,error:"Address is invalid for token search. If you pasted a 0x address, it may be a Uniswap pool \u2014 try the pool search tool instead."};let n=Gn(e.chain),r=e.page&&e.page>0?Math.floor(e.page):1,o=e.size&&e.size>0?Math.min(Math.floor(e.size),50):15,s=`${this.baseUrl}${So}`,a=JSON.stringify({searchQuery:t,chainIds:[n],searchType:"TOKEN",page:r,size:o}),i="Unknown error";for(let l=1;l<=Ce;l++)try{let u=await _.post(s,a,{headers:{accept:"*/*","connect-protocol-version":"1","content-type":"application/json",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"}});if(!u.ok)throw new Error(N(u));return{success:!0,data:u.data.tokens??[]}}catch(u){i=u instanceof Error?u.message:"Unknown error",l<Ce&&await bn(yn)}return{success:!1,error:i}}async searchPools(e){let t=e.query?.trim();if(!t)return{success:!1,error:"Search query is required"};let n=Gn(e.chain),r=e.page&&e.page>0?Math.floor(e.page):1,o=e.size&&e.size>0?Math.min(Math.floor(e.size),50):15,s=e.protocolVersion??"V3",a=e.feeTier&&e.feeTier>0?Math.floor(e.feeTier):void 0,i=`${this.baseUrl}${So}`,l=JSON.stringify({searchQuery:t,chainIds:[n],searchType:"POOL",page:r,size:o}),[u,d]=await Promise.allSettled([this.fetchSearchPools(i,l),this.getExploreStats({chain:e.chain})]);if(u.status==="rejected"||!u.value.success)return{success:!1,error:u.status==="rejected"?String(u.reason):u.value.error??"Failed to search pools"};let p=t.toLowerCase().split(/\s+/).filter(Boolean),m=u.value.data??[];s!=="ALL"&&(m=m.filter(h=>h.protocolVersion===s)),a!=null&&(m=m.filter(h=>h.feeTier===a));let g=new Map;for(let h of m)h.id&&g.set(h.id.toLowerCase(),h);if(d.status==="fulfilled"&&d.value.success&&d.value.data){let h=d.value.data.stats,y=s==="ALL"?["V2","V3","V4"]:[s];for(let k of y)for(let w of xo(h,k)){if(!w.id||a!=null&&w.feeTier!==a)continue;let b=(w.token0?.symbol??"").toLowerCase(),T=(w.token1?.symbol??"").toLowerCase(),P=(w.token0?.name??"").toLowerCase(),S=(w.token1?.name??"").toLowerCase();if(!p.some(A=>b.includes(A)||T.includes(A)||P.includes(A)||S.includes(A)))continue;let D=Po(w),x=w.id.toLowerCase(),I=g.get(x);I?g.set(x,{...I,...D!=null?{apr:D}:{},...w.totalLiquidity?.value!=null?{tvlUsd:w.totalLiquidity.value}:{},...w.volume1Day?.value!=null?{volume1DayUsd:w.volume1Day.value}:{},...w.txCount!=null?{txCount:w.txCount}:{}}):g.set(x,{id:w.id,protocolVersion:w.protocolVersion,feeTier:w.feeTier,token0:w.token0?{address:w.token0.address,symbol:w.token0.symbol,name:w.token0.name,decimals:w.token0.decimals}:void 0,token1:w.token1?{address:w.token1.address,symbol:w.token1.symbol,name:w.token1.name,decimals:w.token1.decimals}:void 0,...D!=null?{apr:D}:{},...w.totalLiquidity?.value!=null?{tvlUsd:w.totalLiquidity.value}:{},...w.volume1Day?.value!=null?{volume1DayUsd:w.volume1Day.value}:{},...w.txCount!=null?{txCount:w.txCount}:{}})}}return{success:!0,data:Array.from(g.values()).sort((h,y)=>this.matchScore(y,p)-this.matchScore(h,p))}}async fetchSearchPools(e,t){let n="Unknown error";for(let r=1;r<=Ce;r++)try{let o=await _.post(e,t,{headers:{accept:"*/*","connect-protocol-version":"1","content-type":"application/json",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"}});if(!o.ok)throw new Error(N(o));return{success:!0,data:o.data.pools??[]}}catch(o){n=o instanceof Error?o.message:"Unknown error",r<Ce&&await bn(yn)}return{success:!1,error:n}}matchScore(e,t){let n=[e.token0?.symbol??"",e.token1?.symbol??""].map(o=>o.toLowerCase()),r=0;for(let o of t){let s=0;for(let a of n)a===o?s=Math.max(s,3):a.startsWith(o)?s=Math.max(s,2):a.includes(o)&&(s=Math.max(s,1));r+=s}return r}};var os=["tvl","volume1Day","volume1Week","volume30Day","txCount","apr"],rs=["V2","V3","V4"],pt=class extends v{name="get-top-pools";description=`List the top Uniswap liquidity pools on a given EVM chain, ranked by TVL by default. Returns each pool's address, fee tier, protocol version, both tokens (symbol, name, address, decimals), TVL, transaction count, and rolling volume buckets (1d / 1w / 30d). Use this tool for questions like: "top pools on Ethereum", "biggest Uniswap V3 pools on Base", "which pool has the most liquidity for USDC", "show pools with highest 24h volume on Arbitrum". Source: https://app.uniswap.org/explore (ExploreStats gateway). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea`;category="blockchain-data";parameters=[{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"protocolVersion",type:"string",description:'Uniswap protocol version to filter by: "V2", "V3", or "V4". Default is "V3" (the dataset with the richest stats). Use "V4" only when the user explicitly asks for V4 pools.',required:!1,default:"V3"},{name:"sortBy",type:"string",description:'Ranking metric: "tvl" (total value locked, default), "volume1Day", "volume1Week", "volume30Day", "txCount", or "apr" (estimated annualized fee yield). Pools are returned in descending order by this metric.',required:!1,default:"tvl"},{name:"limit",type:"number",description:"Maximum number of pools to return after sorting. Defaults to 10.",required:!1,default:10},{name:"multichain",type:"boolean",description:"When true, request Uniswap's multichain aggregated view instead of a single chain. Defaults to false. Most queries should leave this off.",required:!1,default:!1}];service;constructor(e){super(),this.service=new te(e)}async run(e,t){let n=R(e.chain,t),r=this.parseProtocolVersion(e.protocolVersion),o=this.parseSortKey(e.sortBy),s=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),50):10,a=e.multichain===!0,i=await this.service.getTopPools({chain:n,protocolVersion:r,sortBy:o,limit:s,multichain:a});if(!i.success)return{error:i.error||"Failed to fetch top pools"};let l=(i.data??[]).map(u=>this.formatPool(u));return{_instructions:"Present the pools as a ranked list. For each pool show: rank, pair (token0/token1 symbols), apr(in %), fee tier (in %), TVL in USD, 24h volume, 24h fee revenue, and protocol version. Format numbers human-readably (e.g. $12.5M, 0.05%). Mention the chain and the sort metric in the intro line. If a value is null, omit it. Do NOT mention tool or API names.",chain:n,protocolVersion:r,sortBy:o,count:l.length,pools:l}}parseProtocolVersion(e){if(typeof e=="string"){let t=e.trim().toUpperCase();if(rs.includes(t))return t}return"V3"}parseSortKey(e){if(typeof e=="string"){let t=e.trim();if(os.includes(t))return t}return"tvl"}formatPool(e){return{apr:e.apr,address:e.id,chain:e.chain,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0,tvlUsd:e.totalLiquidity?.value,volume1DayUsd:e.volume1Day?.value,volume1WeekUsd:e.volume1Week?.value,volume30DayUsd:e.volume30Day?.value,txCount:e.txCount,token0:e.token0?{address:e.token0.address,symbol:e.token0.symbol,name:e.token0.name,decimals:e.token0.decimals,priceUsd:e.token0.price?.value}:void 0,token1:e.token1?{address:e.token1.address,symbol:e.token1.symbol,name:e.token1.name,decimals:e.token1.decimals,priceUsd:e.token1.price?.value}:void 0}}};var ss=365,as=/^0x[a-fA-F0-9]{40}$/,ht=class extends v{name="get-pool-detail";description="Get detailed stats for a single Uniswap V3 pool, identified by token pair (and optional fee tier). Always runs a search first, filters by the provided symbols + fee tier, and only fetches detail when the filter resolves to exactly one pool. Otherwise returns a `candidates` list \u2014 show that to the user, ask which pool they want, then re-call this tool with the missing info. Detail response includes: pool address, fee tier (bps and %), both tokens (symbol, name, address, decimals, USD price), token0/token1 reserves, total TVL with 24h % change, 24h volume, weekly historical volume series, total tx count, and a derived APR estimate (fees \xF7 TVL \xD7 365). NEVER pass a pool address \u2014 the tool resolves the address itself by searching. Token contract addresses are NOT pool addresses. Source: https://app.uniswap.org. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea";category="blockchain-data";parameters=[{name:"token0Symbol",type:"string",description:`Symbol of one of the pool's tokens (e.g. "USDC"). Pair order does not matter \u2014 USDC/WETH and WETH/USDC resolve to the same pool. When both token0Symbol and token1Symbol are provided the search is filtered to that exact pair.`,required:!0},{name:"token1Symbol",type:"string",description:'Symbol of the other pool token (e.g. "WETH"). Optional \u2014 when omitted the tool returns all pools containing token0Symbol so the user can pick.',required:!1},{name:"feeTier",type:"number",description:"Fee tier in basis points (bps): 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. Optional \u2014 when omitted the tool returns all fee tiers for the pair so the user can pick.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new te(e)}async run(e,t){let n=R(e.chain,t),r=typeof e.token0Symbol=="string"?e.token0Symbol.trim():"",o=typeof e.token1Symbol=="string"?e.token1Symbol.trim():"",s=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)?Math.floor(e.feeTier):void 0;if(!r)return{error:"token0Symbol is required. Provide at least one token symbol; add token1Symbol and feeTier to narrow the result."};let a=o?`${r} ${o}`:r,i=await this.service.searchPools({query:a,chain:n,page:1,size:100,protocolVersion:"V3"});if(!i.success)return{error:i.error||"Pool search failed"};let l=i.data??[],d=l.filter(m=>this.matchesFilter(m,r,o,s,"ordered")),p=!1;if(d.length===0&&o){let m=l.filter(g=>this.matchesFilter(g,r,o,s,"reversed"));m.length>0&&(d=m,p=!0)}if(d.length===0)return{error:`No V3 pool matched ${this.describeQuery(r,o,s)} on ${L(n)}. Verify the symbols and fee tier with the user.`};if(d.length===1){let m=d[0];if(!m.id||!as.test(m.id))return{error:"Resolved pool has no valid address"};let g=await this.service.getV3PoolDetail({address:m.id,chain:n});return!g.success||!g.data?{error:g.error||"Failed to fetch pool detail"}:{_instructions:"Present pool details in a compact summary. Lead with the pair (token0/token1 symbols), fee tier (%), and chain. Then show: TVL (USD) with 24h % change, 24h volume (USD), 24h fees (USD), estimated APR (%), token0 reserve + USD price, token1 reserve + USD price, and total tx count. Optionally include the weekly historical volume as a sparkline-style bullet list. Format numbers human-readably (e.g. $12.5M, +1.23%, 0.05%). Omit null fields. Do NOT mention tool/API names.",pairOrderMatched:p?"reversed":"ordered",pool:this.formatPool(g.data)}}return{_instructions:"The filter matched multiple pools. Render them as a numbered list (pair, fee tier %, 24h volume, address) and ask the user which one they want to see. When they answer, re-call this tool with their selection \u2014 pass token0Symbol + token1Symbol + feeTier (use the exact feeTier in bps from the chosen candidate). Format numbers human-readably (e.g. $12.5M, 0.05%). Do NOT mention tool/API names.",chain:n,pairOrderMatched:p?"reversed":"ordered",reason:this.candidateReason(r,o,s),missing:this.missingFields(o,s),candidates:this.rankCandidates(d).slice(0,10).map(m=>this.formatCandidate(m))}}matchesFilter(e,t,n,r,o){if(r!=null&&e.feeTier!==r)return!1;let s=(e.token0?.symbol??"").toLowerCase(),a=(e.token1?.symbol??"").toLowerCase(),i=t.toLowerCase();if(n){let l=n.toLowerCase();return o==="reversed"?this.symbolIncludes(s,l)&&this.symbolIncludes(a,i):this.symbolIncludes(s,i)&&this.symbolIncludes(a,l)}return this.symbolIncludes(s,i)||this.symbolIncludes(a,i)}symbolIncludes(e,t){return!e||!t?!1:e.includes(t)||t.includes(e)}describeQuery(e,t,n){let r=t?`${e}/${t}`:e;return n!=null?`${r} at fee tier ${n} bps`:r}candidateReason(e,t,n){return t?n==null?`Multiple fee tiers exist for ${e}/${t}. Ask the user which fee tier they want.`:`Multiple ${e}/${t} pools share fee tier ${n} bps. Ask the user which one they meant.`:`Multiple pools contain ${e}. Ask the user for the other token (and fee tier).`}missingFields(e,t){let n=[];return e||n.push("token1Symbol"),t==null&&n.push("feeTier"),n}rankCandidates(e){return[...e].sort((t,n)=>(n.volumeUsd24hr??0)-(t.volumeUsd24hr??0))}formatCandidate(e){return{address:e.id,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0,volume24hUsd:e.volumeUsd24hr,pair:e.token0?.symbol&&e.token1?.symbol?`${e.token0.symbol}/${e.token1.symbol}`:void 0,token0:e.token0?{symbol:e.token0.symbol,name:e.token0.name,address:e.token0.address}:void 0,token1:e.token1?{symbol:e.token1.symbol,name:e.token1.name,address:e.token1.address}:void 0}}formatPool(e){let t=e.totalLiquidity?.value,n=e.volume24h?.value,r=e.feeTier,o=typeof r=="number"?r/1e4:void 0,s=typeof n=="number"&&typeof o=="number"?n*o/100:void 0,a=typeof s=="number"&&typeof t=="number"&&t>0?s/t*ss*100:void 0;return{address:e.address,protocolVersion:e.protocolVersion,feeTierBps:r,feeTierPercent:o,tvlUsd:t,tvlChange24hPercent:e.totalLiquidityPercentChange24h?.value,volume24hUsd:n,fees24hUsd:s,aprPercent:a,txCount:e.txCount,historicalVolumeWeek:this.formatHistorical(e.historicalVolume),token0:e.token0?{address:e.token0.address,symbol:e.token0.symbol,name:e.token0.name,decimals:e.token0.decimals,priceUsd:e.token0.market?.price?.value??e.token0.price?.value,reserve:e.token0Supply,isSpam:e.token0.project?.isSpam,safetyLevel:e.token0.project?.safetyLevel}:void 0,token1:e.token1?{address:e.token1.address,symbol:e.token1.symbol,name:e.token1.name,decimals:e.token1.decimals,priceUsd:e.token1.market?.price?.value??e.token1.price?.value,reserve:e.token1Supply,isSpam:e.token1.project?.isSpam,safetyLevel:e.token1.project?.safetyLevel}:void 0}}formatHistorical(e){if(!(!e||e.length===0))return e.map(t=>({timestamp:t.timestamp,volumeUsd:t.value}))}};var is=["V2","V3","V4","ALL"],gt=class extends v{name="search-pools";description='Search Uniswap liquidity pools on a given chain by free-text query \u2014 a SINGLE token symbol or token name. Returns matching pools with: pool address, fee tier (bps + %), protocol version, both tokens (symbol, name, address, decimals, logo), and short-window USD volume buckets (6h / 12h / 24h). Use this tool when the user names ONE token and wants pools related to it: "find WBTC pools on Optimism", "pools containing PEPE on Ethereum", "show USDC pools". Do NOT use for: (a) two-symbol pair lookups like "USDC/WETH 0.05%" \u2014 use get-pool-detail; (b) raw 0x addresses (token or pool) \u2014 use lookup-pool-by-address; (c) chain-wide rankings with no token mentioned \u2014 use get-top-pools. Source: https://app.uniswap.org search. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"query",type:"string",description:'Single token symbol or name to search pools by (e.g. "WBTC", "USDC", "pepe"). Do NOT pass a 0x address (use lookup-pool-by-address) or a two-symbol pair (use get-pool-detail).',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"protocolVersion",type:"string",description:'Filter results by Uniswap protocol version: "V2", "V3", "V4", or "ALL" to include every version. Default is "V3" \u2014 only switch when the user explicitly asks for V2, V4, or all versions.',required:!1,default:"V3"},{name:"page",type:"number",description:"1-based page index. Defaults to 1.",required:!1,default:1},{name:"size",type:"number",description:"Page size (max 50). Defaults to 15.",required:!1,default:15},{name:"feeTier",type:"number",description:'Optional fee tier filter in basis points (bps): 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. When provided, only pools with this exact fee tier are returned. Pass it whenever the user mentions a fee tier in the current message OR earlier in the conversation (e.g. "USDC/WETH 0.05% pool on Base" \u2192 feeTier=500). Omit otherwise.',required:!1}];service;constructor(e){super(),this.service=new te(e)}async run(e,t){let n=typeof e.query=="string"?e.query.trim():"";if(!n)return{error:"Search query is required"};let r=R(e.chain,t),o=typeof e.page=="number"&&Number.isFinite(e.page)&&e.page>0?Math.floor(e.page):1,s=typeof e.size=="number"&&Number.isFinite(e.size)&&e.size>0?Math.min(Math.floor(e.size),50):15,a=this.parseProtocolFilter(e.protocolVersion),i=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)&&e.feeTier>0?Math.floor(e.feeTier):void 0,l=await this.service.searchPools({query:n,chain:r,page:o,size:s,protocolVersion:a,feeTier:i});if(!l.success)return{error:l.error||"Failed to search pools"};let u=(l.data??[]).map(d=>this.formatPool(d));return{_instructions:"Present the matching pools as a list. For each pool show: pair (token0/token1 symbols), fee tier (%), protocol version, and only the fields that have actual data \u2014 apr (%), TVL (USD), 24h volume (USD), tx count. Skip any field that is null/undefined \u2014 do not show zeros or dashes. Format numbers human-readably (e.g. $12.5M, 0.30%, 4.2%). Mention the search query and chain in the intro. Do NOT mention tool or API names.",query:n,chain:r,protocolVersion:a,feeTierBps:i,feeTierPercent:i!=null?i/1e4:void 0,page:o,size:s,count:u.length,pools:u}}parseProtocolFilter(e){if(typeof e=="string"){let t=e.trim().toUpperCase();if(is.includes(t))return t}return"V3"}formatPool(e){let t={address:e.id,chainId:e.chainId,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0};e.apr!=null&&(t.apr=e.apr),e.tvlUsd!=null&&(t.tvlUsd=e.tvlUsd);let n=e.volume1DayUsd??e.volumeUsd24hr;n!=null&&(t.volume24hUsd=n),e.volumeUsd6hr!=null&&(t.volume6hUsd=e.volumeUsd6hr),e.volumeUsd12hr!=null&&(t.volume12hUsd=e.volumeUsd12hr),e.txCount!=null&&(t.txCount=e.txCount);let r=e.token0,o=e.token1;return r&&(t.token0={address:r.address,symbol:r.symbol,name:r.name,decimals:r.decimals}),o&&(t.token1={address:o.address,symbol:o.symbol,name:o.name,decimals:o.decimals}),t}};var ls=365,Co=/^0x[a-fA-F0-9]{40}$/,ft=class extends v{name="lookup-pool-by-address";description='Resolve an unknown 0x address as either a Uniswap pool or a token, then return the appropriate data: pool detail when the address is a pool, or the list of pools containing that token when the address is a token. Use this tool when the user pastes a single address and asks about "the pool" without saying which kind. For pool detail (when the address is a pool): pair, fee tier, TVL with 24h % change, 24h volume, 24h fees, estimated APR, token reserves with USD prices, and weekly volume series. For token resolution (when the address is a token): the resolved token (symbol, name, decimals) plus a ranked list of pools that include it (pair, fee tier, 24h volume, pool address). Source: https://app.uniswap.org. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea';category="blockchain-data";parameters=[{name:"address",type:"string",description:"A 0x-prefixed EVM address. May be a Uniswap pool contract OR a token contract \u2014 the tool figures out which.",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new te(e)}async run(e,t){let n=R(e.chain,t),r=typeof e.address=="string"?e.address.trim():"",[o,s]=await Promise.all([this.service.searchPools({query:r,chain:n,page:1,size:5,protocolVersion:"ALL"}),this.service.searchTokens({query:r,chain:n,page:1,size:5})]),a=o.success?this.findPoolByAddress(o.data??[],r):void 0;if(a)return this.handlePoolHit(a,n);let i=s.success?this.findTokenByAddress(s.data??[],r):void 0;return i?this.handleTokenHit(i,n):!o.success&&!s.success?{error:o.error||s.error||"Failed to resolve address"}:{error:`Address ${r} did not match any pool or token Uniswap knows on ${L(n)}. Verify the chain (the address may exist on a different network) or ask the user for context.`}}async handlePoolHit(e,t){if(!e.id)return{error:"Resolved pool has no identifier"};let n=e.protocolVersion??"V3",r=n==="V4"?await this.service.getV4PoolDetail({poolId:e.id,chain:t}):n==="V2"?Co.test(e.id)?await this.service.getV2PoolDetail({address:e.id,chain:t}):{success:!1,error:"Resolved V2 pair has no valid address"}:Co.test(e.id)?await this.service.getV3PoolDetail({address:e.id,chain:t}):{success:!1,error:"Resolved V3 pool has no valid address"};return!r.success||!r.data?{error:r.error||"Failed to fetch pool detail"}:{_instructions:`The address is a Uniswap ${n} pool. Present pool details in a compact summary. Lead with the pair (token0/token1 symbols), fee tier (%), protocol version, and chain. Then show: TVL (USD) with 24h % change, 24h volume (USD), 24h fees (USD), estimated APR (%), token0 reserve + USD price, token1 reserve + USD price, and total tx count. For V4 pools also mention tickSpacing, isDynamicFee, and the hook address if present. Format numbers human-readably (e.g. $12.5M, +1.23%, 0.05%). Omit null fields. Do NOT mention tool/API names.`,resolvedAs:"pool",chain:t,protocolVersion:n,pool:this.formatPoolDetail(r.data)}}async handleTokenHit(e,t){let n=e.symbol?.trim();if(!n)return{error:"Resolved token has no symbol \u2014 cannot search related pools."};let r=await this.service.searchPools({query:n,chain:t,page:1,size:100,protocolVersion:"V3"});if(!r.success)return{error:r.error||"Failed to fetch pools for resolved token"};let o=(e.address??"").toLowerCase(),a=[...(r.data??[]).filter(i=>{let l=(i.token0?.address??"").toLowerCase(),u=(i.token1?.address??"").toLowerCase();return l===o||u===o})].sort((i,l)=>(l.volumeUsd24hr??0)-(i.volumeUsd24hr??0)).slice(0,10);return{_instructions:"The address is a token, not a pool. Tell the user which token it is (symbol + name), then render the matching pools as a numbered list: pair (token0/token1 symbols), fee tier (%), protocol version, 24h volume (USD), and the pool address. Ask which pool they want detail on; when they answer, re-call this tool with that pool address to fetch the detail. Format numbers human-readably. Omit null fields. Do NOT mention tool/API names.",resolvedAs:"token",chain:t,protocolVersion:"V3",token:this.formatToken(e),poolCount:a.length,pools:a.map(i=>this.formatPoolCandidate(i))}}findPoolByAddress(e,t){let n=t.toLowerCase();return e.find(r=>(r.id??"").toLowerCase()===n)}findTokenByAddress(e,t){let n=t.toLowerCase();return e.find(r=>(r.address??"").toLowerCase()===n)}formatToken(e){return{address:e.address,symbol:e.symbol,name:e.name,decimals:e.decimals,chainId:e.chainId,isSpam:e.isSpam,safetyLevel:e.safetyLevel}}formatPoolCandidate(e){return{address:e.id,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0,volume24hUsd:e.volumeUsd24hr,pair:e.token0?.symbol&&e.token1?.symbol?`${e.token0.symbol}/${e.token1.symbol}`:void 0,token0:e.token0?{symbol:e.token0.symbol,name:e.token0.name,address:e.token0.address}:void 0,token1:e.token1?{symbol:e.token1.symbol,name:e.token1.name,address:e.token1.address}:void 0}}formatPoolDetail(e){let t=e.totalLiquidity?.value,n=e.volume24h?.value,r=e.feeTier,o=typeof r=="number"?r/1e4:void 0,s=typeof n=="number"&&typeof o=="number"?n*o/100:void 0,a=typeof s=="number"&&typeof t=="number"&&t>0?s/t*ls*100:void 0;return{address:e.address??e.poolId,protocolVersion:e.protocolVersion,poolId:e.poolId,tickSpacing:e.tickSpacing,isDynamicFee:e.isDynamicFee,hookAddress:e.hook?.address,feeTierBps:r,feeTierPercent:o,tvlUsd:t,tvlChange24hPercent:e.totalLiquidityPercentChange24h?.value,volume24hUsd:n,fees24hUsd:s,aprPercent:a,txCount:e.txCount,historicalVolumeWeek:this.formatHistorical(e.historicalVolume),token0:e.token0?{address:e.token0.address,symbol:e.token0.symbol,name:e.token0.name,decimals:e.token0.decimals,priceUsd:e.token0.market?.price?.value??e.token0.price?.value,reserve:e.token0Supply}:void 0,token1:e.token1?{address:e.token1.address,symbol:e.token1.symbol,name:e.token1.name,decimals:e.token1.decimals,priceUsd:e.token1.market?.price?.value??e.token1.price?.value,reserve:e.token1Supply}:void 0}}formatHistorical(e){if(!(!e||e.length===0))return e.map(t=>({timestamp:t.timestamp,volumeUsd:t.value}))}};var Yn=require("js-sha3"),Uo=2n**96n;function Ro(c,e,t){let n=c*c,r=Uo*Uo,o=n*10n**18n/r;return Number(o)/1e18*Math.pow(10,e-t)}function le(c,e,t){return Math.pow(1.0001,c)*Math.pow(10,e-t)}function De(c,e,t){if(c<=0)throw new Error("priceToTick: price must be > 0");let n=c/Math.pow(10,e-t),r=Math.log(n)/Math.log(1.0001),o=Math.round(r);return Math.abs(r-o)<.001?o:Math.floor(r)}function Te(c,e,t="nearest"){let n=Math.abs(c-Math.round(c))<1e-6?Math.round(c):c,r=(n%e+e)%e;if(r===0)return n;let o=n-r,s=o+e;return t==="down"?o:t==="up"?s:n-o<s-n?o:s}var yt=-887272,bt=887272;function ve(c){return c<yt?yt:c>bt?bt:c}var cs=/^0x[0-9a-f]+$/i;function re(c){return c.startsWith("0x")||c.startsWith("0X")?c.slice(2):c}function wn(c){let e=re(c).toLowerCase();if(e.length>64)throw new Error(`pad32: input too long (${e.length})`);return e.padStart(64,"0")}function kn(c){if(!cs.test(c)||re(c).length!==40)throw new Error(`encodeAddress: invalid address ${c}`);return wn(c.toLowerCase())}function ce(c,e=256){let t=typeof c=="bigint"?c:BigInt(c);if(t<0n)throw new Error("encodeUint: negative");let n=t.toString(16);if(n.length>e/4)throw new Error(`encodeUint: overflow ${e}`);return wn(n)}function jn(c){let e=typeof c=="bigint"?c:BigInt(c);if(e>=0n)return wn(e.toString(16));let t=1n<<256n;return wn((t+e).toString(16))}function Eo(c){return"0x"+(0,Yn.keccak_256)(us(c))}function us(c){let e=re(c),t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substring(n*2,n*2+2),16);return t}function Se(c){return"0x"+(0,Yn.keccak_256)(new TextEncoder().encode(c)).slice(0,8)}function Qt(c,e){let t=c.substring(e,e+64);return t?BigInt("0x"+t):0n}function zn(c,e){let t=c.substring(e,e+64),n=BigInt("0x"+t),r=1n<<256n,o=1n<<23n;return n>=1n<<255n?Number(n-r):(n<o,Number(n))}function Qn(c,e){return"0x"+c.substring(e+24,e+64).toLowerCase()}var Ue=class{config;constructor(e){this.config=e}hasIntegratedApproval(){return!1}};var ds="https://exchange-api.keyring.app/admin/setting?configs=others",ms="https://api.coinpool.app/config/bridgeProviderByChain",ps="https://coinpool-api-op.bacoor-test001.xyz/config/bridgeProviderByChain";function hs(c){let e=null,t=null;return async()=>e||t||(t=(async()=>{try{return e=await c(),e}catch{return null}finally{t=null}})(),t)}async function Io(c){return(await _.get(c,{headers:{Accept:"application/json"}})).data}var gs=hs(async()=>(await Io(ds))?.others??null),Tn={prod:null,dev:null},Xt={prod:null,dev:null};async function fs(c){let e=c?"prod":"dev";if(Tn[e])return Tn[e];if(Xt[e])return Xt[e];let t=c?ms:ps;return Xt[e]=(async()=>{try{let n=await Io(t);return Tn[e]=n?.data??null,Tn[e]}catch{return null}finally{Xt[e]=null}})(),Xt[e]}function ys(c){if(!c)return null;try{let e=JSON.parse(c);return e&&typeof e=="object"?e:null}catch{return null}}function No(c){if(c==null)return 0;let e=typeof c=="number"?c:Number(c);return Number.isFinite(e)?e:0}function bs(c){return c==="debridge"||c==="relay"}async function vn(c,e){let n=(await fs(e))?.[String(c)];return bs(n)?n:null}async function wt(c,e){let t=await gs();if(!t)return null;let n,r;if(c==="debridge")n=ys(t.affiliateRecipient)?.[String(e)],r=No(t.AFFILIATE_FEE_PERENT);else if(c==="relay")n=t.RELAY_AFFILIATE_FEE_RECIPIENT,r=No(t.RELAY_AFFILIATE_FEE_PERCENT);else return null;return!n||r<=0?null:{affiliateFeeRecipient:n,affiliateFeePercent:r}}var se={"0x1":"https://ethereum-rpc.publicnode.com","0xa":"https://mainnet.optimism.io","0x38":"https://bsc-dataseed.binance.org","0x89":'https://polygon-bor-rpc.publicnode.com"',"0x2105":"https://mainnet.base.org","0xa4b1":"https://arb1.arbitrum.io/rpc","0xa86a":"https://avalanche-c-chain-rpc.publicnode.com","0xe708":"https://rpc.linea.build"};function ws(c){return se[c.toLowerCase()]}var Do={};function Xn(c){let e={};if(c)for(let[t,n]of Object.entries(c))typeof n=="string"&&n.trim()&&(e[t.toLowerCase()]=n.trim());Do=e}function kt(c){let e=c.toLowerCase();return Do[e]??se[e]}async function xe(c,e,t){for(let r=0;r<=5;r++){let o=await _.post(c,{jsonrpc:"2.0",id:Date.now(),method:e,params:t},{headers:{"Content-Type":"application/json"}});if(o.status===429&&r<5){await new Promise(a=>setTimeout(a,2**r*500));continue}if(!o.ok)throw new Error(`RPC ${e}: ${N(o)}`);let s=o.data;if(s.error)throw new Error(`RPC ${e}: ${s.error.message}`);if(s.result===void 0)throw new Error(`RPC ${e}: empty result`);return s.result}throw new Error(`RPC ${e}: exceeded max retries (429)`)}function Lo(c,e,t){return xe(c,"eth_call",[{to:e,data:t},"latest"])}async function Jn(c,e,t){let n=kt(c);if(!n)return null;try{return await Lo(n,e,t)}catch{return null}}var ks="https://dln.debridge.finance/v1.0",Ts="0x0000000000000000000000000000000000000000",Tt=class extends Ue{apiBaseUrl;accessToken;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??ks,this.accessToken=e.accessToken??"d6c45897b8f6"}getProviderName(){return"debridge"}hasIntegratedApproval(){return!1}async getQuote(e){let{srcChainId:t,srcTokenAddress:n,srcTokenAmount:r,dstChainId:o,dstTokenAddress:s,recipientAddress:a,senderAddress:i,slippage:l,isCrossChain:u}=e,d=u??t!==o,p=await wt(this.getProviderName(),t);try{if(d){let f={srcChainId:String(t),srcChainTokenIn:n,srcChainTokenInAmount:r,dstChainId:String(o),dstChainTokenOut:s,dstChainTokenOutRecipient:a,srcChainOrderAuthorityAddress:i,dstChainOrderAuthorityAddress:a,accesstoken:this.accessToken};p&&(f.affiliateFeeRecipient=p.affiliateFeeRecipient,f.affiliateFeePercent=p.affiliateFeePercent);let h=await this.get(`${this.apiBaseUrl}/dln/order/create-tx`,f);return h.error||h.errorMessage?{success:!1,error:h.error??h.errorMessage,errorMessage:h.errorMessage??"deBridge quote failed"}:{success:!0,provider:"debridge",tx:h.tx,estimation:h.estimation,isCrossChain:!0,srcChainId:t,dstChainId:o,raw:h}}let m={chainId:String(t),tokenIn:n,tokenInAmount:r,tokenOut:s,tokenOutRecipient:a,accesstoken:this.accessToken};p&&(m.affiliateFeeRecipient=p.affiliateFeeRecipient,m.affiliateFeePercent=p.affiliateFeePercent),l!==void 0&&(m.slippage=l);let g=await this.get(`${this.apiBaseUrl}/chain/transaction`,m);return g.error||g.errorMessage?{success:!1,error:g.error??g.errorMessage,errorMessage:g.errorMessage??"deBridge quote failed"}:{success:!0,provider:"debridge",tx:g.tx,isCrossChain:!1,srcChainId:t,dstChainId:t,raw:g}}catch(m){return{success:!1,error:m,errorMessage:m instanceof Error?m.message:"Failed to fetch quote"}}}async checkApproval(e){let{chain:t,userAddress:n,tokenAddress:r,amount:o}=e,s=e.quoteData?.tx?.to;if(!s)return{isNeeded:!1,error:"No contract address found in quote"};if(!r||r.toLowerCase()===Ts)return{isNeeded:!1,integrated:!1,contractAddress:s,message:"Native coin needs no approval."};let a=await this.readAllowance(t,r,n,s);if(a===null)return{isNeeded:!0,integrated:!1,contractAddress:s,message:"Could not read allowance; approval required before the swap."};let i;try{i=BigInt(o)}catch{i=0n}return a>=i?{isNeeded:!1,integrated:!1,contractAddress:s,message:"Sufficient allowance already granted."}:{isNeeded:!0,integrated:!1,contractAddress:s,message:"Approval required: current allowance is below the swap amount."}}async readAllowance(e,t,n,r){if(!e)return null;let o=i=>i.toLowerCase().replace(/^0x/,"").padStart(64,"0"),s=`0xdd62ed3e${o(n)}${o(r)}`,a=await Jn(e,t,s);if(typeof a!="string"||!/^0x[0-9a-fA-F]*$/.test(a)||a==="0x")return null;try{return BigInt(a)}catch{return null}}async executeSwap(e){let{quoteData:t}=e;return{success:!0,provider:"debridge",txData:t.tx??void 0,estimation:t.estimation}}async trackTransaction(e){let{requestIdOrTxHash:t}=e;try{let n=await this.get(`${this.apiBaseUrl}/dln/tx/${t}/order-ids`,{accesstoken:this.accessToken});if(!n.orderIds?.length)return{success:!1,status:"PENDING"};let r=n.orderIds[0];switch(((await this.get(`${this.apiBaseUrl}/dln/order/${r}/status`,{accesstoken:this.accessToken})).status??"").toUpperCase()){case"FULFILLED":case"SENTUNLOCK":case"CLAIMEDUNLOCK":return{success:!0,status:"COMPLETED"};case"ORDERCANCELLED":case"SENTORDERCANCEL":case"CLAIMEDORDERCANCEL":return{success:!0,status:"FAILED"};default:return{success:!0,status:"PENDING"}}}catch(n){return{success:!1,status:"ERROR",error:n instanceof Error?n.message:String(n)}}}async get(e,t){let n=Object.entries(t).map(([s,a])=>`${encodeURIComponent(s)}=${encodeURIComponent(String(a))}`).join("&"),r=n?`${e}?${n}`:e;return(await _.get(r,{headers:{Accept:"application/json"}})).data}};var vs="https://api.relay.link";function Mo(c){return c===7565164||c==="7565164"?792703809:c}var vt=class extends Ue{apiBaseUrl;apiKey;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??vs,this.apiKey=e.apiKey}getProviderName(){return"relay"}hasIntegratedApproval(){return!0}async getQuote(e){let{srcChainId:t,srcTokenAddress:n,srcTokenAmount:r,dstChainId:o,dstTokenAddress:s,recipientAddress:a,senderAddress:i,slippage:l}=e,u=await wt(this.getProviderName(),t),d={user:a||i,originChainId:Mo(t),destinationChainId:Mo(o),originCurrency:n,destinationCurrency:s,amount:r,recipient:a||i,tradeType:"EXACT_INPUT"};u&&(d.appFees=[{recipient:u.affiliateFeeRecipient,fee:Math.floor(u.affiliateFeePercent*100)}]),typeof l=="number"&&Number.isFinite(l)&&(d.slippageTolerance=Math.floor(l*100).toString());try{let p=await this.request("/quote/v2","POST",d);if(!p||p.error)return{success:!1,error:p?.error??"Unknown error",errorMessage:p?.message??"Failed to fetch quote from Relay"};let m=p.steps?.find(k=>k.id==="approve"),g=p.steps?.find(k=>k.id==="swap"||k.id==="deposit"),f=g?.items?.[0]?.data,h=Array.isArray(f?.instructions);return{success:!0,provider:"relay",tx:f?h?{data:{instructions:f.instructions??[],addressLookupTableAddresses:f.addressLookupTableAddresses??[]},chainId:f.chainId}:{from:f.from,to:f.to,data:f.data,value:f.value,chainId:f.chainId,maxFeePerGas:f.maxFeePerGas,maxPriorityFeePerGas:f.maxPriorityFeePerGas}:null,isCrossChain:t!==o,srcChainId:t,dstChainId:o,raw:{steps:p.steps,fees:p.fees,details:p.details,approveStep:m,requestId:g?.requestId}}}catch(p){return{success:!1,error:p,errorMessage:p instanceof Error?p.message:"Failed to fetch quote"}}}async checkApproval(e){let t=e.quoteData?.raw?.approveStep;if(t){let n=t.items?.[0]?.data;return{isNeeded:!0,integrated:!0,approvalData:n,contractAddress:n?.to,message:"Approval step is included in quote and will be executed first."}}return{isNeeded:!1,integrated:!0,message:"No approval needed or already approved"}}async executeSwap(e){let{quoteData:t}=e,n=t.raw;return n?.steps?{success:!0,provider:"relay",steps:n.steps,estimation:t.estimation}:{success:!1,error:"Invalid quote data"}}async trackTransaction(e){try{let n=(await this.request("/intents/status/v3","GET",{requestId:e.requestIdOrTxHash})).status??"";return n==="success"||n==="refunded"?{success:!0,status:"COMPLETED"}:n==="failure"||n==="expired"?{success:!0,status:"FAILED"}:{success:!0,status:"PENDING"}}catch(t){return{success:!1,status:"ERROR",error:t instanceof Error?t.message:String(t)}}}async request(e,t,n){let r={"Content-Type":"application/json"};this.apiKey&&(r["x-api-key"]=this.apiKey);let o=`${this.apiBaseUrl}${e}`,s;if(t==="GET"&&n){let i=Object.entries(n).map(([l,u])=>`${encodeURIComponent(l)}=${encodeURIComponent(String(u))}`).join("&");o=`${o}?${i}`}else t==="POST"&&n&&(s=n);let a=await _.request(t,o,s,{headers:r});if(!a.ok)throw new Error(N(a));return a.data}};var Sn={debridge:{apiBaseUrl:"https://dln.debridge.finance/v1.0",accessToken:"d6c45897b8f6"},relay:{apiBaseUrl:"https://api.relay.link"}},Jt="debridge";function Ss(c){if(c==null)return null;let e=typeof c=="number"?c:Number(c);return Number.isFinite(e)?e:null}var Le=class{providerConfig;isProduction;cache=new Map;constructor(e=Sn,t=!0){this.providerConfig=e,this.isProduction=t}async getService(e){let t=await this.getActiveProvider(e),n=this.cache.get(t);if(n)return n;let r=this.createService(t);return this.cache.set(t,r),r}async getServiceByProvider(e){let t=this.cache.get(e);if(t)return t;let n=this.createService(e);return this.cache.set(e,n),n}async getActiveProvider(e){let t=Ss(e);return t===null?Jt:await vn(t,this.isProduction)??Jt}async isProviderActive(e,t){return await this.getActiveProvider(t)===e}getAvailableProviders(){return Object.keys(this.providerConfig)}clearCache(){this.cache.clear()}createService(e){let t=this.providerConfig[e]??{};switch(e){case"debridge":return new Tt(t);case"relay":return new vt(t);default:{let n=e;throw new Error(`Unknown swap provider: ${String(n)}`)}}}},fe=new Le;var xs="https://api.coinpool.app/config/addresses",As="https://coinpool-api-op.bacoor-test001.xyz/config/addresses",xn={prod:null,dev:null},Zt={prod:null,dev:null};async function Ps(c){let e=c?"prod":"dev";if(xn[e])return xn[e];if(Zt[e])return Zt[e];let t=c?xs:As;return Zt[e]=(async()=>{try{let r=(await _.get(t,{headers:{Accept:"application/json"}})).data;return xn[e]=r?.data??null,xn[e]}catch{return null}finally{Zt[e]=null}})(),Zt[e]}function _s(c){let e=c.trim();if(/^0x[0-9a-fA-F]+$/.test(e)){let t=Number.parseInt(e,16);return Number.isFinite(t)?String(t):null}return/^\d+$/.test(e)?e:null}async function Bo(c,e,t){let n=await Ps(t);if(!n)return;let r=_s(e);if(!r)return;let o=n[c]?.[r];return Array.isArray(o)?o[0]:typeof o=="string"?o:void 0}async function An(c,e){return Bo("createPoolProxyV3",c,e)}async function Pn(c,e){return Bo("nftPositionUniswap",c,e)}var O="0x0000000000000000000000000000000000000000",Cs={"0x1":{hexId:"0x1",name:"Ethereum",defaultRpc:se["0x1"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0xa":{hexId:"0xa",name:"Optimism",defaultRpc:se["0xa"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0x38":{hexId:"0x38",name:"BNB Smart Chain",defaultRpc:se["0x38"],native:{symbol:"BNB",name:"BNB",decimals:18,coingeckoId:"binancecoin"},defaultMintGasLimit:12e5},"0x89":{hexId:"0x89",name:"Polygon",defaultRpc:se["0x89"],native:{symbol:"MATIC",name:"Polygon",decimals:18,coingeckoId:"matic-network"},defaultMintGasLimit:12e5},"0x2105":{hexId:"0x2105",name:"Base",defaultRpc:se["0x2105"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0xa4b1":{hexId:"0xa4b1",name:"Arbitrum One",defaultRpc:se["0xa4b1"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:22e5},"0xa86a":{hexId:"0xa86a",name:"Avalanche",defaultRpc:se["0xa86a"],native:{symbol:"AVAX",name:"Avalanche",decimals:18,coingeckoId:"avalanche-2"},defaultMintGasLimit:12e5},"0xe708":{hexId:"0xe708",name:"Linea",defaultRpc:se["0xe708"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5}};function ee(c){return Cs[c.toLowerCase()]}function St(c){return ee(c)?.native}var Oo="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";function Zn(c){let e=c.stableTicks??3,t=c.widePercent??{down:50,up:100},n=le(c.currentTick,c.token0Decimals,c.token1Decimals),r=ve(Te(c.currentTick-e*c.tickSpacing,c.tickSpacing,"down")),o=ve(Te(c.currentTick+e*c.tickSpacing,c.tickSpacing,"up")),s=n*(1-t.down/100),a=n*(1+t.up/100),i=De(s,c.token0Decimals,c.token1Decimals),l=De(a,c.token0Decimals,c.token1Decimals),u=ve(Te(i,c.tickSpacing,"down")),d=ve(Te(l,c.tickSpacing,"up")),p=(m,g,f,h,y)=>{let k=le(h,c.token0Decimals,c.token1Decimals),w=le(y,c.token0Decimals,c.token1Decimals);return{id:m,label:g,description:f,tickLower:h,tickUpper:y,minPrice:k,maxPrice:w,minPercentChange:(k-n)/n*100,maxPercentChange:(w-n)/n*100}};return[p("stable","Stable","Good for stablecoins or low volatility pairs",r,o),p("wide","Wide","Good for volatile pairs",u,d)]}var Us=Se("slot0()"),Rs=Se("liquidity()"),Es=Se("tickSpacing()"),Ns=Se("fee()"),Is=Se("token0()"),Ds=Se("token1()"),Ls=Se("decimals()"),Me=class{swapFactory;isProduction;constructor(e){this.isProduction=e?.isProduction??!0,this.swapFactory=e?.swapFactory??(this.isProduction?fe:new Le(void 0,!1))}getRpcUrl(e){let t=kt(e);if(t)return t;let n=ee(e);if(!n)throw new Error(`Unsupported chain: ${e}`);return n.defaultRpc}async getGatewayAddress(e){return An(e,this.isProduction)}async getPositionManagerAddress(e){return Pn(e,this.isProduction)}async ethCall(e,t,n){return xe(this.getRpcUrl(e),"eth_call",[{to:t,data:n},"latest"])}async getNativeBalance(e,t){let n=await xe(this.getRpcUrl(e),"eth_getBalance",[t,"latest"]);return BigInt(n)}async getGasPrice(e){let t=await xe(this.getRpcUrl(e),"eth_gasPrice",[]);return BigInt(t)}async getTransactionReceipt(e,t){return await xe(this.getRpcUrl(e),"eth_getTransactionReceipt",[t])}async extractMintedNftId(e,t){let n=await this.getPositionManagerAddress(e);if(!n)return null;let r=n.toLowerCase();for(let o of t.logs??[]){if((o.address??"").toLowerCase()!==r)continue;let s=o.topics??[];if(!(s.length<4||s[0]?.toLowerCase()!==Oo||"0x"+s[1].slice(-40).toLowerCase()!==O))try{return BigInt(s[3]).toString()}catch{return null}}return null}async estimateGasReserveWei(e){let t=ee(e);if(!t)throw new Error(`Unsupported chain: ${e}`);let n=await this.getGasPrice(e);return BigInt(t.defaultMintGasLimit)*n*18n/10n}async readPoolOnchain(e,t){let[n,r,o,s,a,i]=await Promise.all([this.ethCall(e,t,Us),this.ethCall(e,t,Rs),this.ethCall(e,t,Es),this.ethCall(e,t,Ns),this.ethCall(e,t,Is),this.ethCall(e,t,Ds)]),l=re(n),u=Qt(l,0),d=zn(l,64),p=Qt(re(r),0),m=Number(zn(re(o),0)),g=Number(Qt(re(s),0)),f=Qn(re(a),0),h=Qn(re(i),0),[y,k]=await Promise.all([this.readErc20Decimals(e,f),this.readErc20Decimals(e,h)]),w=Ro(u,y,k);return{poolAddress:t,chainId:e,fee:g,tickSpacing:m,liquidity:p,sqrtPriceX96:u,currentTick:d,token0:f,token1:h,currentPrice:w}}async readErc20Decimals(e,t){if(t.toLowerCase()===O)return 18;let n=await this.ethCall(e,t,Ls);return Number(Qt(re(n),0))}async getNativePriceUsd(e){let t=ee(e);if(!t)return null;try{let n=`https://api.coingecko.com/api/v3/simple/price?ids=${t.native.coingeckoId}&vs_currencies=usd`,r=await _.get(n);if(!r.ok)return null;let s=r.data[t.native.coingeckoId]?.usd;return typeof s=="number"?s:null}catch{return null}}splitNativeAmountForRange(e){let{nativeAmountWei:t,pool:n,tickLower:r,tickUpper:o,token0Decimals:s,token1Decimals:a}=e,i=2**96,l=Number(n.sqrtPriceX96)/i,u=Math.pow(1.0001,r/2),d=Math.pow(1.0001,o/2);if(l<=u)return{ratio0:1,ratio1:0,amountInFor0Wei:t,amountInFor1Wei:0n};if(l>=d)return{ratio0:0,ratio1:1,amountInFor0Wei:0n,amountInFor1Wei:t};let p=1/l-1/d,m=l-u,g=l*l,f=p*g,y=f+m;if(!Number.isFinite(y)||y<=0)throw new Error("splitNativeAmountForRange: invalid total value");let k=f/y,w=1-k,b=1000000000000000n,T=BigInt(Math.floor(k*Number(b))),P=t*T/b,S=t-P;return{ratio0:k,ratio1:w,amountInFor0Wei:P,amountInFor1Wei:S}}async fetchSwapLeg(e){let t=Number.parseInt(e.chainId,16);if(!Number.isFinite(t))throw new Error(`Invalid hex chain id: ${e.chainId}`);let r=await(await this.swapFactory.getService(t)).getQuote({srcChainId:t,srcTokenAddress:e.tokenIn,srcTokenAmount:e.tokenInAmount,dstChainId:t,dstTokenAddress:e.tokenOut,recipientAddress:e.tokenOutRecipient,senderAddress:e.senderAddress??e.tokenOutRecipient,slippage:e.slippage,isCrossChain:!1});if(!r.success||!r.tx)throw new Error(r.errorMessage??"Swap quote failed");let o=r.tx;if(!o.to||typeof o.data!="string")throw new Error("Swap quote returned an unsupported tx shape (non-EVM payload?)");let s=r.raw??{},a=s.tokenOut?.minAmount??s.details?.currencyOut?.minimumAmount??"0";return{to:o.to,data:o.data,value:o.value??"0",minAmountOut:a}}encodeMintCalldata(e){if(e.paymentInfo.length!==2||e.exchanges.length!==2)throw new Error("encodeMintCalldata: paymentInfo and exchanges must each have length 2");let t=Se("mint((address,uint256,address,uint256)[2],(address,bytes,uint256)[2],(uint24,uint256,uint256,int24,int24))"),n=e.paymentInfo.map(g=>kn(g.tokenIn)+ce(g.tokenInAmount)+kn(g.tokenOut)+ce(g.tokenOutAmount)).join(""),o=e.exchanges.map(g=>{let f=re(g.data||"0x"),h=f.length/2,y=ce(h),k=(64-f.length%64)%64,w=f+"0".repeat(k);return{head:kn(g.to)+ce(96)+ce(g.value),body:y+w}}).map(g=>g.head+g.body),s=o.length*32,a=[];for(let g of o)a.push(ce(s)),s+=g.length/2;let i=a.join("")+o.join(""),l=ce(e.fee,24)+ce(e.amount0Min)+ce(e.amount1Min)+jn(e.tickLower)+jn(e.tickUpper),p=256+32+160,m=n+ce(p)+l+i;return t+m}_keccak(e){return Eo(e)}};var Ms=/^0x[a-fA-F0-9]{40}$/,xt=class extends v{name="open-add-liquidity-form";kind="ui";category="pool-action";noSuggestions=!0;description='Open the Add-Liquidity form for a specific Uniswap V3 pool. Use this when the user wants to provide liquidity / add LP / farm / stake / deposit into a pool \u2014 natural-language verbs in any language all map here. Resolves the pool by token symbols (and optional fee tier), reads on-chain state, checks the connected wallet\'s NATIVE balance, then returns a UI payload that the FE renders as the form (with two preset ranges: Stable \xB13 ticks and Wide -50%/+100%). CONSTRAINT: the gateway only accepts the chain\'s NATIVE coin as input (ETH on Ethereum/Base/Arbitrum/Optimism/Linea, BNB on BSC, MATIC on Polygon, AVAX on Avalanche). The native amount is auto-split via deBridge into both pool tokens \u2014 the user never selects a different input. If the user asks to add liquidity using a NON-NATIVE token amount (e.g. "add pool with 5 USDT", "th\xEAm 100 USDC"), pass that symbol via `requestedNonNativeToken` so the tool can return error="unsupported_input_token" \u2014 never silently convert their non-native amount into native or USD. PRECONDITION: a connected wallet (userContext.walletAddress). If absent, the tool returns error="wallet_not_connected" and the agent must ask the user to connect.';parameters=[{name:"token0Symbol",type:"string",description:`Symbol of one of the pool's tokens (e.g. "USDC"). Pair order does not matter.`,required:!0},{name:"token1Symbol",type:"string",description:'Symbol of the other pool token (e.g. "WETH"). Required to uniquely identify the pool.',required:!0},{name:"feeTier",type:"number",description:"Fee tier in basis points: 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. Omit when unknown \u2014 the tool will resolve the best match automatically.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"prefillNativeAmount",type:"number",description:"Optional NATIVE-COIN amount (human-readable) to pre-fill the form's input field. ONLY pass this when the user named an amount in the chain's native coin itself \u2014 ETH on Ethereum/Base/Arbitrum/Optimism/Linea, BNB on BSC, MATIC on Polygon, AVAX on Avalanche. Example: \"add 0.1 ETH to USDC/WETH on Base\". Pass the plain decimal number (e.g. 0.1 for 0.1 ETH). DO NOT convert a non-native amount (USDC, USDT, DAI, WBTC, \u2026) into native here \u2014 pass `requestedNonNativeToken` instead so the tool can reject. Mutually exclusive with prefillUsdAmount \u2014 pass one or neither.",required:!1},{name:"prefillUsdAmount",type:"number",description:'Optional USD amount to pre-fill the form\'s input field. Use this ONLY when the user explicitly speaks in USD/dollars \u2014 e.g. "add $10 to this pool", "10 dollars", "10 \u0111\xF4". The tool converts the USD value to native amount automatically using the current native-token price. DO NOT use this as a workaround for non-native token amounts (e.g. "5 USDT" is NOT $5 \u2014 USDT may de-peg, and even if 1:1 the user asked for a token, not USD). For non-native token amounts pass `requestedNonNativeToken` instead. Mutually exclusive with prefillNativeAmount \u2014 pass one or neither.',required:!1},{name:"requestedNonNativeToken",type:"string",description:`Set this ONLY when the user named an AMOUNT in a non-native token \u2014 e.g. "add pool with 5 USDT", "add 100 USDC to USDC/WETH", "th\xEAm 5 USDT v\xE0o pool". The trigger is "<number> <non-native-symbol>" in the user's message, NOT the mere appearance of the symbol. Pass the symbol the user attached the amount to (e.g. "USDT", "USDC", "DAI"). CRITICAL: If the user also names a NATIVE amount in the same message (e.g. "add USDC/WETH with 0.1 ETH", "th\xEAm pool USDC/WETH 0.3% v\u1EDBi 0.000005 ETH"), the native amount is the input \u2014 leave this EMPTY and use prefillNativeAmount. The pool pair symbols (the two tokens after "pool" / "to") are NEVER the input token by themselves \u2014 they only identify which pool. The tool will return error="unsupported_input_token" so the agent can tell the user that only the chain's native coin is accepted as input. DO NOT silently convert the user's non-native amount into a native amount or USD amount \u2014 the user explicitly named a different asset and must be informed, not auto-corrected. Leave empty when the user did name a native amount, a USD amount, or no amount at all.`,required:!1},{name:"rangeStrategy",type:"string",description:'Optional preset that pre-fills the min/max price inputs (FE keeps them editable). One of: "stable" \u2014 \xB13 tickSpacing around the current price (narrow, higher yield, higher out-of-range risk; pick for stablecoin / low-volatility pairs and when the user says "safe", "tight", "high yield", "t\u1EADp trung"); "wide" \u2014 currentPrice \xD7 [0.5, 2.0] (broad, lower yield, less rebalancing; pick when the user says "wide", "set and forget", "r\u1ED9ng"); "full" \u2014 full V3 tick range (V2-style, lowest yield, never goes out of range; pick when the user says "full range", "v2-style", "kh\xF4ng lo out of range"). Omit when the user did not signal a preference AND did not provide explicit minPrice/maxPrice. Mutually exclusive with minPrice/maxPrice \u2014 if both are passed, explicit prices win.',required:!1},{name:"minPrice",type:"number",description:'Optional explicit lower bound for the price range, in token1-per-token0 units (same unit as pool.currentPrice \u2014 decimal-adjusted). Pass when the user named a concrete number, e.g. "range 1800 to 2200" \u2192 minPrice=1800. Must be paired with maxPrice. When set, overrides rangeStrategy.',required:!1},{name:"maxPrice",type:"number",description:'Optional explicit upper bound for the price range, in token1-per-token0 units (same unit as pool.currentPrice \u2014 decimal-adjusted). Pass when the user named a concrete number, e.g. "range 1800 to 2200" \u2192 maxPrice=2200. Must be paired with minPrice. When set, overrides rangeStrategy.',required:!1}];uniswap;pool;minProvideUsd;constructor(e){super(),this.uniswap=new te(e),this.pool=new Me(e?.pool),this.minProvideUsd=e?.minProvideUsd??.01}async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){let n=R(e.chain,t),r=typeof e.token0Symbol=="string"?e.token0Symbol.trim():"",o=typeof e.token1Symbol=="string"?e.token1Symbol.trim():"",s=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)?Math.floor(e.feeTier):void 0,a=typeof e.prefillNativeAmount=="number"&&Number.isFinite(e.prefillNativeAmount)&&e.prefillNativeAmount>0?e.prefillNativeAmount:null,i=typeof e.prefillUsdAmount=="number"&&Number.isFinite(e.prefillUsdAmount)&&e.prefillUsdAmount>0?e.prefillUsdAmount:null,l=typeof e.requestedNonNativeToken=="string"&&e.requestedNonNativeToken.trim()?e.requestedNonNativeToken.trim():null,u=l&&a!=null&&(l.toLowerCase()===r.toLowerCase()||l.toLowerCase()===o.toLowerCase())?null:l,d=this.parseRangeStrategy(e.rangeStrategy),p=typeof e.minPrice=="number"&&Number.isFinite(e.minPrice)&&e.minPrice>0?e.minPrice:null,m=typeof e.maxPrice=="number"&&Number.isFinite(e.maxPrice)&&e.maxPrice>0?e.maxPrice:null;if(!r||!o)return{error:"missing_pool_identifier",_instructions:"token0Symbol and token1Symbol are required. Ask the user for the missing token symbol."};let g=ee(n);if(!g)return{error:"unsupported_chain",chain:n,_instructions:`Chain ${n} is not supported by the add-liquidity flow.`};let f=St(n);if(u&&f&&u.toLowerCase()!==f.symbol.toLowerCase())return{error:"unsupported_input_token",requestedToken:u,nativeSymbol:f.symbol,chainName:g.name,_instructions:`The user asked to add liquidity using ${u}, but this app only supports the chain's native coin (${f.symbol} on ${g.name}) as the input. Tell the user this in their language and ask them to either restate the amount in ${f.symbol} or in USD. DO NOT silently convert their ${u} amount, do NOT pre-fill the form, and do NOT call this tool again until the user provides a native or USD amount.`};let h=await this.pool.getGatewayAddress(n);if(!h)return{error:"no_gateway_configured",chain:n,_instructions:`No gateway contract is configured for chain ${n} (${g.name}). Tell the user this chain is not yet supported for add-liquidity in this app.`};let y=t?.walletAddress;if(!y)return{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before adding liquidity. Do NOT render the form."};let k=await this.uniswap.searchPools({query:`${r} ${o}`,chain:n,page:1,size:100,protocolVersion:"V3",feeTier:s});if(!k.success)return{error:k.error||"pool_search_failed"};let w=k.data??[],b=w.find(q=>this.matchesPairExact(q,r,o,s)),T=b?[]:w.filter(q=>this.matchesPair(q,r,o,s));if(!b&&T.length===0)return{error:"pool_not_found",_instructions:`No V3 pool found for ${r}/${o}${s!=null?` at ${s}bps`:""} on ${g.name}. Verify the inputs with the user.`};if(!b&&T.length>1)return{candidates:T.map(q=>({address:q.id,token0Symbol:q.token0?.symbol,token1Symbol:q.token1?.symbol,feeTier:q.feeTier,feeTierPercent:q.feeTier!=null?q.feeTier/1e4:void 0,volume24hUsd:q.volume1DayUsd??q.volumeUsd24hr,tvlUsd:q.tvlUsd,...q.apr?{apr:q.apr}:void 0})),_instructions:"Multiple pools matched. Present them as a numbered list (pair, fee %, TVL, 24h volume, address, apr) and ask the user to pick one. Then re-call this tool with the chosen pool's exact token0Symbol, token1Symbol, and feeTier."};let P=b??T[0];if(!P.id||!Ms.test(P.id))return{error:"invalid_pool_address"};let S=await this.pool.readPoolOnchain(n,P.id),[C,D,x]=await Promise.all([this.pool.getNativeBalance(n,y),this.pool.estimateGasReserveWei(n),this.pool.getNativePriceUsd(n)]),I=C>D?C-D:0n,A=St(n),H=Number(I)/10**A.decimals,Pe=Number(C)/10**A.decimals,ie=x!=null?H*x:null,[Q,X]=await Promise.all([this.pool.readErc20Decimals(n,S.token0),this.pool.readErc20Decimals(n,S.token1)]),we={min:le(yt,Q,X),max:le(bt,Q,X)},j=null;C===0n?j="no_balance":ie!=null&&ie<this.minProvideUsd&&(j="insufficient_balance");let de=P.token0,Be=P.token1,On=[25,50,75,100].map(q=>({percent:q,amountWei:(I*BigInt(q)/100n).toString(),amount:H*(q/100)})),Oe=null,me=null;if(a!=null){me=a;let q=BigInt(Math.floor(me*10**A.decimals));q>0n&&(Oe=q.toString())}else if(i!=null&&x!=null&&x>0){me=i/x;let q=BigInt(Math.floor(me*10**A.decimals));q>0n&&(Oe=q.toString())}let qe={address:O,isNative:!0,symbol:A.symbol,name:A.name,decimals:A.decimals,priceUsd:x,balanceWei:C.toString(),balance:Pe,gasReserveWei:D.toString(),spendableWei:I.toString(),spendable:H,spendableUsd:ie,quickRates:On,minProvideUsd:this.minProvideUsd,warning:j,warningMessage:j==="no_balance"?`You have no ${A.symbol} on ${g.name}. Top up to add liquidity.`:j==="insufficient_balance"?`Spendable ${A.symbol} is below the $${this.minProvideUsd} minimum.`:null,prefillAmount:me,prefillAmountWei:Oe},to=this.computePrefillRange({explicitMinPrice:p,explicitMaxPrice:m,rangeStrategy:d,currentTick:S.currentTick,tickSpacing:S.tickSpacing,token0Decimals:Q,token1Decimals:X,priceBounds:we});return{ui:{component:"AddLiquidityForm",props:{chain:{hexId:n,name:g.name},pool:{address:S.poolAddress,fee:S.fee,feePercent:S.fee/1e4,tickSpacing:S.tickSpacing,currentTick:S.currentTick,currentPrice:S.currentPrice,sqrtPriceX96:S.sqrtPriceX96.toString(),token0:{address:S.token0,symbol:de?.symbol,name:de?.name,logo:de?.logo},token1:{address:S.token1,symbol:Be?.symbol,name:Be?.name,logo:Be?.logo}},inputToken:qe,priceBounds:we,prefillRange:to,gatewayAddress:h}},summary:this.buildSummary({chainName:g.name,pair:`${de?.symbol??r}/${Be?.symbol??o}`,feePercent:S.fee/1e4,warning:j,minProvideUsd:this.minProvideUsd,nativeSymbol:A.symbol}),_instructions:`The add-liquidity action is ready. In the user's language, invite them to enter the amount and price range to add liquidity, naming the pool + chain (e.g. "Enter the amount and price range to add liquidity to <pool> on <chain>"). If warning="no_balance" tell them they need to top up. If warning="insufficient_balance" tell them the balance is below the minimum. Do NOT mention balance amounts, deBridge, native coin mechanics, UI, forms, or internal tool names. Wait for the user to fill in amount + price range before proceeding.`}}matchesPairExact(e,t,n,r){if(!r||r!=null&&e.feeTier!==r)return!1;let o=(e.token0?.symbol??"").toLowerCase(),s=(e.token1?.symbol??"").toLowerCase(),a=t.toLowerCase(),i=n.toLowerCase();return o===a&&s===i||o===i&&s===a}matchesPair(e,t,n,r){if(r!=null&&e.feeTier!==r)return!1;let o=(e.token0?.symbol??"").toLowerCase(),s=(e.token1?.symbol??"").toLowerCase(),a=t.toLowerCase(),i=n.toLowerCase(),l=this.symMatch(o,a)&&this.symMatch(s,i),u=this.symMatch(o,i)&&this.symMatch(s,a);return l||u}symMatch(e,t){return!e||!t?!1:e.includes(t)||t.includes(e)}parseRangeStrategy(e){if(typeof e!="string")return null;let t=e.trim().toLowerCase();return t==="stable"||t==="wide"||t==="full"?t:null}computePrefillRange(e){let{explicitMinPrice:t,explicitMaxPrice:n,rangeStrategy:r,priceBounds:o}=e;if(t!=null&&n!=null&&n>t)return{strategy:"user",minPrice:Math.max(t,o.min),maxPrice:Math.min(n,o.max)};if(r==="full")return{strategy:"full",minPrice:o.min,maxPrice:o.max};if(r==="stable"||r==="wide"){let a=Zn({currentTick:e.currentTick,tickSpacing:e.tickSpacing,token0Decimals:e.token0Decimals,token1Decimals:e.token1Decimals}).find(i=>i.id===r);if(a)return{strategy:r,minPrice:a.minPrice,maxPrice:a.maxPrice}}return null}buildSummary(e){let t=[`Add-liquidity form opened for ${e.pair} ${e.feePercent}% on ${e.chainName}.`];return e.warning==="no_balance"?t.push(`No ${e.nativeSymbol} balance \u2014 top up the wallet to add liquidity.`):e.warning==="insufficient_balance"&&t.push(`Balance is below the $${e.minProvideUsd} minimum \u2014 top up before submitting.`),t.join(" ")}};var qo=[{type:"constructor",inputs:[{name:"owner_",type:"address",internalType:"address"},{name:"supportedRouter_",type:"address",internalType:"address"},{name:"nonfungiblePositionManager_",type:"address",internalType:"address"}],stateMutability:"nonpayable"},{type:"function",name:"NATIVE_TOKEN",inputs:[],outputs:[{name:"",type:"address",internalType:"address"}],stateMutability:"view"},{type:"function",name:"getSupportedRouters",inputs:[],outputs:[{name:"",type:"address[]",internalType:"address[]"}],stateMutability:"view"},{type:"function",name:"increaseLiquidity",inputs:[{name:"tokenId_",type:"uint256",internalType:"uint256"},{name:"swapInfo_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapInfo[2]",components:[{name:"tokenIn",type:"address",internalType:"address"},{name:"tokenInAmount",type:"uint256",internalType:"uint256"},{name:"tokenOut",type:"address",internalType:"address"},{name:"tokenOutAmount",type:"uint256",internalType:"uint256"}]},{name:"swapOp_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapOp[2]",components:[{name:"to",type:"address",internalType:"address"},{name:"value",type:"uint256",internalType:"uint256"},{name:"data",type:"bytes",internalType:"bytes"}]},{name:"increaseLiquidityData_",type:"tuple",internalType:"struct CoinPoolGateway.IncreaseLiquidityData",components:[{name:"amount0Min",type:"uint256",internalType:"uint256"},{name:"amount1Min",type:"uint256",internalType:"uint256"}]}],outputs:[],stateMutability:"payable"},{type:"function",name:"mint",inputs:[{name:"swapInfo_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapInfo[2]",components:[{name:"tokenIn",type:"address",internalType:"address"},{name:"tokenInAmount",type:"uint256",internalType:"uint256"},{name:"tokenOut",type:"address",internalType:"address"},{name:"tokenOutAmount",type:"uint256",internalType:"uint256"}]},{name:"swapOp_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapOp[2]",components:[{name:"to",type:"address",internalType:"address"},{name:"value",type:"uint256",internalType:"uint256"},{name:"data",type:"bytes",internalType:"bytes"}]},{name:"mintData_",type:"tuple",internalType:"struct CoinPoolGateway.MintData",components:[{name:"fee",type:"uint24",internalType:"uint24"},{name:"tickLower",type:"int24",internalType:"int24"},{name:"tickUpper",type:"int24",internalType:"int24"},{name:"amount0Min",type:"uint256",internalType:"uint256"},{name:"amount1Min",type:"uint256",internalType:"uint256"}]}],outputs:[],stateMutability:"payable"},{type:"function",name:"nonfungiblePositionManager",inputs:[],outputs:[{name:"",type:"address",internalType:"contract INonfungiblePositionManager"}],stateMutability:"view"},{type:"function",name:"owner",inputs:[],outputs:[{name:"",type:"address",internalType:"address"}],stateMutability:"view"},{type:"function",name:"pause",inputs:[],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"paused",inputs:[],outputs:[{name:"",type:"bool",internalType:"bool"}],stateMutability:"view"},{type:"function",name:"renounceOwnership",inputs:[],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"rescue",inputs:[{name:"token_",type:"address",internalType:"address"},{name:"amount_",type:"uint256",internalType:"uint256"},{name:"recipient_",type:"address",internalType:"address"}],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"transferOwnership",inputs:[{name:"newOwner",type:"address",internalType:"address"}],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"unpause",inputs:[],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"updateSupportedRouters",inputs:[{name:"routers_",type:"address[]",internalType:"address[]"},{name:"isSupported_",type:"bool[]",internalType:"bool[]"}],outputs:[],stateMutability:"nonpayable"},{type:"event",name:"LiquidityIncreased",inputs:[{name:"tokenId",type:"uint256",indexed:!1,internalType:"uint256"},{name:"token0",type:"address",indexed:!1,internalType:"address"},{name:"token1",type:"address",indexed:!1,internalType:"address"},{name:"amount0LiquidityIncreased",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1LiquidityIncreased",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0IncreaseLiquidityRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1IncreaseLiquidityRefunded",type:"uint256",indexed:!1,internalType:"uint256"}],anonymous:!1},{type:"event",name:"Minted",inputs:[{name:"tokenId",type:"uint256",indexed:!1,internalType:"uint256"},{name:"token0",type:"address",indexed:!1,internalType:"address"},{name:"token1",type:"address",indexed:!1,internalType:"address"},{name:"amount0Minted",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1Minted",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0MintRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1MintRefunded",type:"uint256",indexed:!1,internalType:"uint256"}],anonymous:!1},{type:"event",name:"OwnershipTransferred",inputs:[{name:"previousOwner",type:"address",indexed:!0,internalType:"address"},{name:"newOwner",type:"address",indexed:!0,internalType:"address"}],anonymous:!1},{type:"event",name:"Paused",inputs:[{name:"account",type:"address",indexed:!1,internalType:"address"}],anonymous:!1},{type:"event",name:"SupportedRoutersUpdated",inputs:[{name:"routers",type:"address[]",indexed:!1,internalType:"address[]"},{name:"isSupported",type:"bool[]",indexed:!1,internalType:"bool[]"}],anonymous:!1},{type:"event",name:"Unpaused",inputs:[{name:"account",type:"address",indexed:!1,internalType:"address"}],anonymous:!1},{type:"error",name:"AddressEmptyCode",inputs:[{name:"target",type:"address",internalType:"address"}]},{type:"error",name:"EnforcedPause",inputs:[]},{type:"error",name:"ExpectedPause",inputs:[]},{type:"error",name:"FailedCall",inputs:[]},{type:"error",name:"InsufficientAmountOut",inputs:[]},{type:"error",name:"InsufficientBalance",inputs:[{name:"balance",type:"uint256",internalType:"uint256"},{name:"needed",type:"uint256",internalType:"uint256"}]},{type:"error",name:"InvalidAmountIn",inputs:[]},{type:"error",name:"InvalidLength",inputs:[]},{type:"error",name:"InvalidRefundUnusedToken",inputs:[]},{type:"error",name:"InvalidSwapAction",inputs:[]},{type:"error",name:"InvalidTokenInInfo",inputs:[]},{type:"error",name:"InvalidTokenOutInfo",inputs:[]},{type:"error",name:"InvalidTotalAmountIn",inputs:[]},{type:"error",name:"NotSupportedRouter",inputs:[]},{type:"error",name:"OwnableInvalidOwner",inputs:[{name:"owner",type:"address",internalType:"address"}]},{type:"error",name:"OwnableUnauthorizedAccount",inputs:[{name:"account",type:"address",internalType:"address"}]},{type:"error",name:"ReentrancyGuardReentrantCall",inputs:[]},{type:"error",name:"SafeERC20FailedDecreaseAllowance",inputs:[{name:"spender",type:"address",internalType:"address"},{name:"currentAllowance",type:"uint256",internalType:"uint256"},{name:"requestedDecrease",type:"uint256",internalType:"uint256"}]},{type:"error",name:"SafeERC20FailedOperation",inputs:[{name:"token",type:"address",internalType:"address"}]}],_n=[{type:"function",name:"approve",stateMutability:"nonpayable",inputs:[{name:"spender",type:"address"},{name:"amount",type:"uint256"}],outputs:[{name:"",type:"bool"}]},{type:"function",name:"allowance",stateMutability:"view",inputs:[{name:"owner",type:"address"},{name:"spender",type:"address"}],outputs:[{name:"",type:"uint256"}]}];var At=class extends v{name="preview-add-liquidity";kind="action";category="pool-action";noSuggestions=!0;description="Preview the add-liquidity transaction once the user has filled the AddLiquidityForm with a native amount and a price range (min/max price in token1 per token0). The tool converts prices \u2192 ticks (rounded to tickSpacing), fetches deBridge swap quotes for both legs, encodes the gateway.mint call, and returns an unsigned tx the FE will sign. Do NOT call this until the user has filled both prices and an amount.";parameters=[{name:"poolAddress",type:"string",description:"V3 pool contract address (returned by open-add-liquidity-form in props.pool.address).",required:!0},{name:"chain",type:"string",description:`Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea .Must match the pool's chain. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.`,required:!1},{name:"minPrice",type:"number",description:'Lower bound of the position price range, in token1-per-token0 units (decimal-adjusted \u2014 the SAME unit as pool.currentPrice from open-add-liquidity-form). Example: if currentPrice is 1800 (USDC per WETH), minPrice 900 means "the position is in-range while WETH \u2265 900 USDC". Must be > 0 and < maxPrice.',required:!0},{name:"maxPrice",type:"number",description:'Upper bound of the position price range, in the same units as minPrice. Must be > minPrice. Pass a very large number (or props.priceBounds.max) for a "full range upper" position.',required:!0},{name:"nativeAmount",type:"number",description:"Native amount the user wants to spend, as a human-readable decimal number (e.g. 0.1 for 0.1 ETH). The tool converts to wei internally.",required:!0},{name:"slippageBps",type:"number",description:"Slippage tolerance in basis points (100 = 1%). Optional \u2014 defaults to 100.",required:!1}];pool;defaultSlippageBps;mintMinPercent;pantograph;constructor(e){super(),this.pool=new Me(e?.pool),this.defaultSlippageBps=e?.defaultSlippageBps??"auto",this.mintMinPercent=e?.mintMinPercent??80,this.pantograph=new Z}async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){let n=typeof e.poolAddress=="string"?e.poolAddress.trim():"",r=R(e.chain,t),o=Number(e.minPrice),s=Number(e.maxPrice),a=typeof e.nativeAmount=="number"&&Number.isFinite(e.nativeAmount)?e.nativeAmount:NaN,i=typeof e.slippageBps=="number"&&Number.isFinite(e.slippageBps)&&String(e.slippageBps)!=="auto"?Math.floor(e.slippageBps)/100:String(this.defaultSlippageBps);if(!n||!r||!Number.isFinite(a)||!Number.isFinite(o)||!Number.isFinite(s))return{error:"missing_inputs",_instructions:"poolAddress, chain, minPrice, maxPrice, nativeAmount are all required."};if(o<=0||s<=0||o>=s)return{error:"invalid_price_range",_instructions:"minPrice and maxPrice must both be > 0 and minPrice must be strictly less than maxPrice."};if(a<=0)return{error:"invalid_native_amount",_instructions:"nativeAmount must be > 0."};let l=ee(r);if(!l)return{error:"unsupported_chain",chain:r};let u=await this.pool.getGatewayAddress(r);if(!u)return{error:"no_gateway_configured",chain:r};let d=t?.walletAddress;if(!d)return{error:"wallet_not_connected"};let p=St(r),m=BigInt(Math.floor(a*10**p.decimals));if(m<=0n)return{error:"invalid_native_amount",_instructions:"nativeAmount must be > 0."};let g=await this.pool.readPoolOnchain(r,n),[f,h]=await Promise.all([this.pool.readErc20Decimals(r,g.token0),this.pool.readErc20Decimals(r,g.token1)]),y=De(o,f,h),k=De(s,f,h),w=ve(Te(y,g.tickSpacing,"down")),b=ve(Te(k,g.tickSpacing,"up"));if(w>=b||w<yt||b>bt)return{error:"invalid_range_after_alignment",_instructions:"After aligning to tickSpacing the range collapsed. Ask the user for a wider price range.",rawTickLower:y,rawTickUpper:k,tickLower:w,tickUpper:b};let T={tickLower:w,tickUpper:b,minPrice:le(w,f,h),maxPrice:le(b,f,h)},[P,S,C]=await Promise.all([this.pool.getNativeBalance(r,d),this.pool.estimateGasReserveWei(r),this.pool.getNativePriceUsd(r)]),D=m+S;if(P<D)return{error:"insufficient_balance",_instructions:"User does not have enough native coin (amount + gas reserve). Tell them the shortfall in their language.",balanceWei:P.toString(),requiredWei:D.toString(),gasReserveWei:S.toString()};let x=this.pool.splitNativeAmountForRange({nativeAmountWei:m,pool:g,tickLower:w,tickUpper:b,token0Decimals:f,token1Decimals:h}),I=g.token0.toLowerCase()===O,A=g.token1.toLowerCase()===O,H=[];if(I||x.amountInFor0Wei===0n)H.push({tokenIn:O,tokenOut:g.token0,amountIn:x.amountInFor0Wei,tx:{to:O,data:"0x",value:"0"},minAmountOut:I?x.amountInFor0Wei:0n});else{let G=await this.pool.fetchSwapLeg({senderAddress:d,chainId:r,tokenIn:O,tokenInAmount:x.amountInFor0Wei.toString(),tokenOut:g.token0,tokenOutRecipient:u,slippage:typeof i=="number"?i:"auto"});H.push({tokenIn:O,tokenOut:g.token0,amountIn:x.amountInFor0Wei,tx:{to:G.to,data:G.data,value:G.value},minAmountOut:BigInt(G.minAmountOut||"0")})}if(A||x.amountInFor1Wei===0n)H.push({tokenIn:O,tokenOut:g.token1,amountIn:x.amountInFor1Wei,tx:{to:O,data:"0x",value:"0"},minAmountOut:A?x.amountInFor1Wei:0n});else{let G=await this.pool.fetchSwapLeg({senderAddress:d,chainId:r,tokenIn:O,tokenInAmount:x.amountInFor1Wei.toString(),tokenOut:g.token1,tokenOutRecipient:u,slippage:typeof i=="number"?i:"auto"});H.push({tokenIn:O,tokenOut:g.token1,amountIn:x.amountInFor1Wei,tx:{to:G.to,data:G.data,value:G.value},minAmountOut:BigInt(G.minAmountOut||"0")})}let Pe=G=>G*BigInt(this.mintMinPercent)/100n,ie=Pe(H[0].minAmountOut),Q=Pe(H[1].minAmountOut),X=H.map(G=>({tokenIn:G.tokenIn,tokenInAmount:BigInt(G.amountIn),tokenOut:G.tokenOut,tokenOutAmount:BigInt(G.minAmountOut)})),we=H.map(G=>({to:G.tx.to,data:G.tx.data,value:BigInt(G.tx.value||"0")}));console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ paymentInfo:",X),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ exchangesInfo:",we),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ args:",{args:[X,we,{fee:Number(g.fee),amount0Min:BigInt(ie),amount1Min:BigInt(Q),tickLower:w,tickUpper:b}]});let j=Ge({abi:qo,functionName:"mint",args:[X,we,{fee:Number(g.fee),amount0Min:BigInt(ie),amount1Min:BigInt(Q),tickLower:w,tickUpper:b}]}),de=a,Be={chainId:r,to:u,data:j,value:m.toString(),from:d},On={nativeIn:de,nativeInUsd:C!=null?de*C:null,ratio:{token0Percent:x.ratio0,token1Percent:x.ratio1},expectedToken0:Number(H[0].minAmountOut)/10**f,expectedToken1:Number(H[1].minAmountOut)/10**h,amount0Min:ie.toString(),amount1Min:Q.toString(),slippageBps:i},Oe=await this.pantograph.getTokensMetadata([g.token0,g.token1],r),me=Oe[g.token0],qe=Oe[g.token1];return{ui:{component:"ConfirmAddLiquidityTx",props:{chain:{hexId:r,name:l.name},unsignedTx:Be,summary:On,pool:{...g,feePercent:g.fee/1e4,token0:{address:g.token0,symbol:me.symbol,name:me.name,decimals:me.decimals,logo:me.icon_image},token1:{address:g.token1,symbol:qe.symbol,name:qe.name,decimals:qe.decimals,logo:qe.icon_image}},range:T,gatewayAddress:u}},summary:`Ready to add ${de} ${p.symbol} to pool ${g.token0}/${g.token1} ${g.fee/1e4}% on ${l.name}, range [${T.minPrice}, ${T.maxPrice}].`,_instructions:"A confirmation panel has been opened on the FE; the user will sign and broadcast the unsigned tx with their wallet. Briefly tell them (in their language) what is about to happen \u2014 the native amount, the auto-split ratio between the two pool tokens, and the chosen price range. Do NOT mention internal tool/component names."}}};var Fo=365,Bs=1e3,Os=/^0x[a-fA-F0-9]{40}$/;function qs(c,e){return{depositUsd:c,aprPercent:e*100,dailyUsd:c*e/Fo,weeklyUsd:c*e/52,monthlyUsd:c*e/12,yearlyUsd:c*e}}var Pt=class extends v{name="estimate_pool_yield";description=`Estimate daily / weekly / monthly / yearly fee yield for a USD deposit into a Uniswap V3 pool. Answers questions like: "If I put $1000 into the USDC/WETH 0.05% pool, how much per day?", "b\u1ECF 500$ v\xE0o pool ETH/USDC 0.3% m\u1ED7i ng\xE0y l\u1EDDi bao nhi\xEAu?", "APR of WBTC/ETH 0.3% on Arbitrum". BEHAVIOR (designed to minimize back-and-forth with the user):
112
+ ${Mo}`,ms={"0x1":1,"0xa":10,"0x38":56,"0x89":137,"0x2105":8453,"0xa4b1":42161,"0xa86a":43114,"0xe708":59144};function Sn(c){return new Promise(e=>setTimeout(e,c))}function Jn(c){if(!c)return 1;let e=c.toLowerCase();return ms[e]??1}function Zn(c){if(!c)return"ETHEREUM";let e=c.toLowerCase();return ls[e]??"ETHEREUM"}function Io(c,e){if(!c)return[];switch(e){case"V2":return c.poolStatsV2??[];case"V3":return c.poolStatsV3??[];case"V4":return c.poolStatsV4??[]}}function Do(c,e){switch(e){case"volume1Day":return c.volume1Day?.value??0;case"volume1Week":return c.volume1Week?.value??0;case"volume30Day":return c.volume30Day?.value??0;case"txCount":return c.txCount??0;case"apr":return c.apr??0;default:return c.totalLiquidity?.value??0}}function Lo(c){let e=c.volume1Day?.value,t=c.totalLiquidity?.value;return typeof c.feeTier!="number"||!e||!t?void 0:c.feeTier/1e4*e/t*365}var ne=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??ss).replace(/\/$/,"")}async getExploreStats(e){let t=Jn(e?.chain),n=e?.multichain===!0,r=encodeURIComponent(JSON.stringify({chainId:String(t),multichain:n})),o=`${this.baseUrl}${as}?connect=v1&encoding=json&message=${r}`,s="Unknown error";for(let a=1;a<=Ce;a++)try{let i=await _.get(o,{headers:{accept:"*/*",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"}});if(!i.ok)throw new Error(N(i));return{success:!0,data:i.data}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<Ce&&await Sn(vn)}return{success:!1,error:s}}async getTopPools(e){let t=e?.protocolVersion??"V3",n=e?.limit&&e.limit>0?Math.floor(e.limit):10,r=e?.sortBy??"tvl",o=await this.getExploreStats({chain:e?.chain,multichain:e?.multichain});return!o.success||!o.data?{success:!1,error:o.error||"Failed to fetch ExploreStats"}:{success:!0,data:Io(o.data.stats,t).map(i=>({...i,apr:Lo(i),fee24hUsd:i.volume1Day?.value&&i.feeTier?i.feeTier/1e4*i.volume1Day.value:void 0})).sort((i,l)=>Do(l,r)-Do(i,r)).slice(0,n)}}async getV3PoolDetail(e){let t=e.address?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V3Pool",query:cs,variables:{chain:Zn(e.chain),address:t},pickField:"v3Pool"}):{success:!1,error:"Pool address is required"}}async getV2PoolDetail(e){let t=e.address?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V2Pair",query:us,variables:{chain:Zn(e.chain),address:t},pickField:"v2Pair"}):{success:!1,error:"Pair address is required"}}async getV4PoolDetail(e){let t=e.poolId?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V4Pool",query:ds,variables:{chain:Zn(e.chain),poolId:t},pickField:"v4Pool"}):{success:!1,error:"V4 poolId is required"}}async fetchPoolDetailGraphQL(e){let t=`${this.baseUrl}${is}`,n=JSON.stringify({operationName:e.operationName,variables:e.variables,query:e.query}),r="Unknown error";for(let o=1;o<=Ce;o++)try{let s=await _.post(t,n,{headers:{accept:"*/*","content-type":"application/json",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web","_dd-custom-header-graph-ql-operation-name":e.operationName,"_dd-custom-header-graph-ql-operation-type":"query"}});if(!s.ok)throw new Error(N(s));let a=s.data;if(a.errors)throw new Error(`GraphQL error: ${JSON.stringify(a.errors)}`);let i=a.data?.[e.pickField]??void 0;return i?{success:!0,data:i}:{success:!1,error:"Pool not found"}}catch(s){r=s instanceof Error?s.message:"Unknown error",o<Ce&&await Sn(vn)}return{success:!1,error:r}}async searchTokens(e){let t=e.query?.trim();if(!t)return{success:!1,error:"Search query is required"};if(B(t))return{success:!1,error:"Address is invalid for token search. If you pasted a 0x address, it may be a Uniswap pool \u2014 try the pool search tool instead."};let n=Jn(e.chain),r=e.page&&e.page>0?Math.floor(e.page):1,o=e.size&&e.size>0?Math.min(Math.floor(e.size),50):15,s=`${this.baseUrl}${No}`,a=JSON.stringify({searchQuery:t,chainIds:[n],searchType:"TOKEN",page:r,size:o}),i="Unknown error";for(let l=1;l<=Ce;l++)try{let u=await _.post(s,a,{headers:{accept:"*/*","connect-protocol-version":"1","content-type":"application/json",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"}});if(!u.ok)throw new Error(N(u));return{success:!0,data:u.data.tokens??[]}}catch(u){i=u instanceof Error?u.message:"Unknown error",l<Ce&&await Sn(vn)}return{success:!1,error:i}}async searchPools(e){let t=e.query?.trim();if(!t)return{success:!1,error:"Search query is required"};let n=Jn(e.chain),r=e.page&&e.page>0?Math.floor(e.page):1,o=e.size&&e.size>0?Math.min(Math.floor(e.size),50):15,s=e.protocolVersion??"V3",a=e.feeTier&&e.feeTier>0?Math.floor(e.feeTier):void 0,i=`${this.baseUrl}${No}`,l=JSON.stringify({searchQuery:t,chainIds:[n],searchType:"POOL",page:r,size:o}),[u,d]=await Promise.allSettled([this.fetchSearchPools(i,l),this.getExploreStats({chain:e.chain})]);if(u.status==="rejected"||!u.value.success)return{success:!1,error:u.status==="rejected"?String(u.reason):u.value.error??"Failed to search pools"};let m=t.toLowerCase().split(/\s+/).filter(Boolean),p=u.value.data??[];s!=="ALL"&&(p=p.filter(f=>f.protocolVersion===s)),a!=null&&(p=p.filter(f=>f.feeTier===a));let h=new Map;for(let f of p)f.id&&h.set(f.id.toLowerCase(),f);if(d.status==="fulfilled"&&d.value.success&&d.value.data){let f=d.value.data.stats,y=s==="ALL"?["V2","V3","V4"]:[s];for(let k of y)for(let w of Io(f,k)){if(!w.id||a!=null&&w.feeTier!==a)continue;let b=(w.token0?.symbol??"").toLowerCase(),T=(w.token1?.symbol??"").toLowerCase(),P=(w.token0?.name??"").toLowerCase(),S=(w.token1?.name??"").toLowerCase();if(!m.some(A=>b.includes(A)||T.includes(A)||P.includes(A)||S.includes(A)))continue;let L=Lo(w),x=w.id.toLowerCase(),D=h.get(x);D?h.set(x,{...D,...L!=null?{apr:L}:{},...w.totalLiquidity?.value!=null?{tvlUsd:w.totalLiquidity.value}:{},...w.volume1Day?.value!=null?{volume1DayUsd:w.volume1Day.value}:{},...w.txCount!=null?{txCount:w.txCount}:{}}):h.set(x,{id:w.id,protocolVersion:w.protocolVersion,feeTier:w.feeTier,token0:w.token0?{address:w.token0.address,symbol:w.token0.symbol,name:w.token0.name,decimals:w.token0.decimals}:void 0,token1:w.token1?{address:w.token1.address,symbol:w.token1.symbol,name:w.token1.name,decimals:w.token1.decimals}:void 0,...L!=null?{apr:L}:{},...w.totalLiquidity?.value!=null?{tvlUsd:w.totalLiquidity.value}:{},...w.volume1Day?.value!=null?{volume1DayUsd:w.volume1Day.value}:{},...w.txCount!=null?{txCount:w.txCount}:{}})}}return{success:!0,data:Array.from(h.values()).sort((f,y)=>this.matchScore(y,m)-this.matchScore(f,m))}}async fetchSearchPools(e,t){let n="Unknown error";for(let r=1;r<=Ce;r++)try{let o=await _.post(e,t,{headers:{accept:"*/*","connect-protocol-version":"1","content-type":"application/json",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"}});if(!o.ok)throw new Error(N(o));return{success:!0,data:o.data.pools??[]}}catch(o){n=o instanceof Error?o.message:"Unknown error",r<Ce&&await Sn(vn)}return{success:!1,error:n}}matchScore(e,t){let n=[e.token0?.symbol??"",e.token1?.symbol??""].map(o=>o.toLowerCase()),r=0;for(let o of t){let s=0;for(let a of n)a===o?s=Math.max(s,3):a.startsWith(o)?s=Math.max(s,2):a.includes(o)&&(s=Math.max(s,1));r+=s}return r}};var ps=["tvl","volume1Day","volume1Week","volume30Day","txCount","apr"],hs=["V2","V3","V4"],ht=class extends v{name="get-top-pools";description=`List the top Uniswap liquidity pools on a given EVM chain, ranked by TVL by default. Returns each pool's address, fee tier, protocol version, both tokens (symbol, name, address, decimals), TVL, transaction count, and rolling volume buckets (1d / 1w / 30d). Use this tool for questions like: "top pools on Ethereum", "biggest Uniswap V3 pools on Base", "which pool has the most liquidity for USDC", "show pools with highest 24h volume on Arbitrum". Source: https://app.uniswap.org/explore (ExploreStats gateway). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea`;category="blockchain-data";parameters=[{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"protocolVersion",type:"string",description:'Uniswap protocol version to filter by: "V2", "V3", or "V4". Default is "V3" (the dataset with the richest stats). Use "V4" only when the user explicitly asks for V4 pools.',required:!1,default:"V3"},{name:"sortBy",type:"string",description:'Ranking metric: "tvl" (total value locked, default), "volume1Day", "volume1Week", "volume30Day", "txCount", or "apr" (estimated annualized fee yield). Pools are returned in descending order by this metric.',required:!1,default:"tvl"},{name:"limit",type:"number",description:"Maximum number of pools to return after sorting. Defaults to 10.",required:!1,default:10},{name:"multichain",type:"boolean",description:"When true, request Uniswap's multichain aggregated view instead of a single chain. Defaults to false. Most queries should leave this off.",required:!1,default:!1}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=R(e.chain,t),r=this.parseProtocolVersion(e.protocolVersion),o=this.parseSortKey(e.sortBy),s=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),50):10,a=e.multichain===!0,i=await this.service.getTopPools({chain:n,protocolVersion:r,sortBy:o,limit:s,multichain:a});if(!i.success)return{error:i.error||"Failed to fetch top pools"};let l=(i.data??[]).map(u=>this.formatPool(u));return{_instructions:"Present the pools as a ranked list. For each pool show: rank, pair (token0/token1 symbols), apr(in %), fee tier (in %), TVL in USD, 24h volume, 24h fee revenue, and protocol version. Format numbers human-readably (e.g. $12.5M, 0.05%). Mention the chain and the sort metric in the intro line. If a value is null, omit it. Do NOT mention tool or API names.",chain:n,protocolVersion:r,sortBy:o,count:l.length,pools:l}}parseProtocolVersion(e){if(typeof e=="string"){let t=e.trim().toUpperCase();if(hs.includes(t))return t}return"V3"}parseSortKey(e){if(typeof e=="string"){let t=e.trim();if(ps.includes(t))return t}return"tvl"}formatPool(e){return{apr:e.apr,address:e.id,chain:e.chain,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0,tvlUsd:e.totalLiquidity?.value,volume1DayUsd:e.volume1Day?.value,volume1WeekUsd:e.volume1Week?.value,volume30DayUsd:e.volume30Day?.value,txCount:e.txCount,token0:e.token0?{address:e.token0.address,symbol:e.token0.symbol,name:e.token0.name,decimals:e.token0.decimals,priceUsd:e.token0.price?.value}:void 0,token1:e.token1?{address:e.token1.address,symbol:e.token1.symbol,name:e.token1.name,decimals:e.token1.decimals,priceUsd:e.token1.price?.value}:void 0}}};var gs=365,fs=/^0x[a-fA-F0-9]{40}$/,gt=class extends v{name="get-pool-detail";description="Get detailed stats for a single Uniswap V3 pool, identified by token pair (and optional fee tier). Always runs a search first, filters by the provided symbols + fee tier, and only fetches detail when the filter resolves to exactly one pool. Otherwise returns a `candidates` list \u2014 show that to the user, ask which pool they want, then re-call this tool with the missing info. Detail response includes: pool address, fee tier (bps and %), both tokens (symbol, name, address, decimals, USD price), token0/token1 reserves, total TVL with 24h % change, 24h volume, weekly historical volume series, total tx count, and a derived APR estimate (fees \xF7 TVL \xD7 365). NEVER pass a pool address \u2014 the tool resolves the address itself by searching. Token contract addresses are NOT pool addresses. Source: https://app.uniswap.org. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea";category="blockchain-data";parameters=[{name:"token0Symbol",type:"string",description:`Symbol of one of the pool's tokens (e.g. "USDC"). Pair order does not matter \u2014 USDC/WETH and WETH/USDC resolve to the same pool. When both token0Symbol and token1Symbol are provided the search is filtered to that exact pair.`,required:!0},{name:"token1Symbol",type:"string",description:'Symbol of the other pool token (e.g. "WETH"). Optional \u2014 when omitted the tool returns all pools containing token0Symbol so the user can pick.',required:!1},{name:"feeTier",type:"number",description:"Fee tier in basis points (bps): 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. Optional \u2014 when omitted the tool returns all fee tiers for the pair so the user can pick.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=R(e.chain,t),r=typeof e.token0Symbol=="string"?e.token0Symbol.trim():"",o=typeof e.token1Symbol=="string"?e.token1Symbol.trim():"",s=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)?Math.floor(e.feeTier):void 0;if(!r)return{error:"token0Symbol is required. Provide at least one token symbol; add token1Symbol and feeTier to narrow the result."};let a=o?`${r} ${o}`:r,i=await this.service.searchPools({query:a,chain:n,page:1,size:100,protocolVersion:"V3"});if(!i.success)return{error:i.error||"Pool search failed"};let l=i.data??[],d=l.filter(p=>this.matchesFilter(p,r,o,s,"ordered")),m=!1;if(d.length===0&&o){let p=l.filter(h=>this.matchesFilter(h,r,o,s,"reversed"));p.length>0&&(d=p,m=!0)}if(d.length===0)return{error:`No V3 pool matched ${this.describeQuery(r,o,s)} on ${I(n)}. Verify the symbols and fee tier with the user.`};if(d.length===1){let p=d[0];if(!p.id||!fs.test(p.id))return{error:"Resolved pool has no valid address"};let h=await this.service.getV3PoolDetail({address:p.id,chain:n});return!h.success||!h.data?{error:h.error||"Failed to fetch pool detail"}:{_instructions:"Present pool details in a compact summary. Lead with the pair (token0/token1 symbols), fee tier (%), and chain. Then show: TVL (USD) with 24h % change, 24h volume (USD), 24h fees (USD), estimated APR (%), token0 reserve + USD price, token1 reserve + USD price, and total tx count. Optionally include the weekly historical volume as a sparkline-style bullet list. Format numbers human-readably (e.g. $12.5M, +1.23%, 0.05%). Omit null fields. Do NOT mention tool/API names.",pairOrderMatched:m?"reversed":"ordered",pool:this.formatPool(h.data)}}return{_instructions:"The filter matched multiple pools. Render them as a numbered list (pair, fee tier %, 24h volume, address) and ask the user which one they want to see. When they answer, re-call this tool with their selection \u2014 pass token0Symbol + token1Symbol + feeTier (use the exact feeTier in bps from the chosen candidate). Format numbers human-readably (e.g. $12.5M, 0.05%). Do NOT mention tool/API names.",chain:n,pairOrderMatched:m?"reversed":"ordered",reason:this.candidateReason(r,o,s),missing:this.missingFields(o,s),candidates:this.rankCandidates(d).slice(0,10).map(p=>this.formatCandidate(p))}}matchesFilter(e,t,n,r,o){if(r!=null&&e.feeTier!==r)return!1;let s=(e.token0?.symbol??"").toLowerCase(),a=(e.token1?.symbol??"").toLowerCase(),i=t.toLowerCase();if(n){let l=n.toLowerCase();return o==="reversed"?this.symbolIncludes(s,l)&&this.symbolIncludes(a,i):this.symbolIncludes(s,i)&&this.symbolIncludes(a,l)}return this.symbolIncludes(s,i)||this.symbolIncludes(a,i)}symbolIncludes(e,t){return!e||!t?!1:e.includes(t)||t.includes(e)}describeQuery(e,t,n){let r=t?`${e}/${t}`:e;return n!=null?`${r} at fee tier ${n} bps`:r}candidateReason(e,t,n){return t?n==null?`Multiple fee tiers exist for ${e}/${t}. Ask the user which fee tier they want.`:`Multiple ${e}/${t} pools share fee tier ${n} bps. Ask the user which one they meant.`:`Multiple pools contain ${e}. Ask the user for the other token (and fee tier).`}missingFields(e,t){let n=[];return e||n.push("token1Symbol"),t==null&&n.push("feeTier"),n}rankCandidates(e){return[...e].sort((t,n)=>(n.volumeUsd24hr??0)-(t.volumeUsd24hr??0))}formatCandidate(e){return{address:e.id,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0,volume24hUsd:e.volumeUsd24hr,pair:e.token0?.symbol&&e.token1?.symbol?`${e.token0.symbol}/${e.token1.symbol}`:void 0,token0:e.token0?{symbol:e.token0.symbol,name:e.token0.name,address:e.token0.address}:void 0,token1:e.token1?{symbol:e.token1.symbol,name:e.token1.name,address:e.token1.address}:void 0}}formatPool(e){let t=e.totalLiquidity?.value,n=e.volume24h?.value,r=e.feeTier,o=typeof r=="number"?r/1e4:void 0,s=typeof n=="number"&&typeof o=="number"?n*o/100:void 0,a=typeof s=="number"&&typeof t=="number"&&t>0?s/t*gs*100:void 0;return{address:e.address,protocolVersion:e.protocolVersion,feeTierBps:r,feeTierPercent:o,tvlUsd:t,tvlChange24hPercent:e.totalLiquidityPercentChange24h?.value,volume24hUsd:n,fees24hUsd:s,aprPercent:a,txCount:e.txCount,historicalVolumeWeek:this.formatHistorical(e.historicalVolume),token0:e.token0?{address:e.token0.address,symbol:e.token0.symbol,name:e.token0.name,decimals:e.token0.decimals,priceUsd:e.token0.market?.price?.value??e.token0.price?.value,reserve:e.token0Supply,isSpam:e.token0.project?.isSpam,safetyLevel:e.token0.project?.safetyLevel}:void 0,token1:e.token1?{address:e.token1.address,symbol:e.token1.symbol,name:e.token1.name,decimals:e.token1.decimals,priceUsd:e.token1.market?.price?.value??e.token1.price?.value,reserve:e.token1Supply,isSpam:e.token1.project?.isSpam,safetyLevel:e.token1.project?.safetyLevel}:void 0}}formatHistorical(e){if(!(!e||e.length===0))return e.map(t=>({timestamp:t.timestamp,volumeUsd:t.value}))}};var ys=["V2","V3","V4","ALL"],ft=class extends v{name="search-pools";description='Search Uniswap liquidity pools on a given chain by free-text query \u2014 a SINGLE token symbol or token name. Returns matching pools with: pool address, fee tier (bps + %), protocol version, both tokens (symbol, name, address, decimals, logo), and short-window USD volume buckets (6h / 12h / 24h). Use this tool when the user names ONE token and wants pools related to it: "find WBTC pools on Optimism", "pools containing PEPE on Ethereum", "show USDC pools". Do NOT use for: (a) two-symbol pair lookups like "USDC/WETH 0.05%" \u2014 use get-pool-detail; (b) raw 0x addresses (token or pool) \u2014 use lookup-pool-by-address; (c) chain-wide rankings with no token mentioned \u2014 use get-top-pools. Source: https://app.uniswap.org search. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"query",type:"string",description:'Single token symbol or name to search pools by (e.g. "WBTC", "USDC", "pepe"). Do NOT pass a 0x address (use lookup-pool-by-address) or a two-symbol pair (use get-pool-detail).',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"protocolVersion",type:"string",description:'Filter results by Uniswap protocol version: "V2", "V3", "V4", or "ALL" to include every version. Default is "V3" \u2014 only switch when the user explicitly asks for V2, V4, or all versions.',required:!1,default:"V3"},{name:"page",type:"number",description:"1-based page index. Defaults to 1.",required:!1,default:1},{name:"size",type:"number",description:"Page size (max 50). Defaults to 15.",required:!1,default:15},{name:"feeTier",type:"number",description:'Optional fee tier filter in basis points (bps): 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. When provided, only pools with this exact fee tier are returned. Pass it whenever the user mentions a fee tier in the current message OR earlier in the conversation (e.g. "USDC/WETH 0.05% pool on Base" \u2192 feeTier=500). Omit otherwise.',required:!1}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=typeof e.query=="string"?e.query.trim():"";if(!n)return{error:"Search query is required"};let r=R(e.chain,t),o=typeof e.page=="number"&&Number.isFinite(e.page)&&e.page>0?Math.floor(e.page):1,s=typeof e.size=="number"&&Number.isFinite(e.size)&&e.size>0?Math.min(Math.floor(e.size),50):15,a=this.parseProtocolFilter(e.protocolVersion),i=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)&&e.feeTier>0?Math.floor(e.feeTier):void 0,l=await this.service.searchPools({query:n,chain:r,page:o,size:s,protocolVersion:a,feeTier:i});if(!l.success)return{error:l.error||"Failed to search pools"};let u=(l.data??[]).map(d=>this.formatPool(d));return{_instructions:"Present the matching pools as a list. For each pool show: pair (token0/token1 symbols), fee tier (%), protocol version, and only the fields that have actual data \u2014 apr (%), TVL (USD), 24h volume (USD), tx count. Skip any field that is null/undefined \u2014 do not show zeros or dashes. Format numbers human-readably (e.g. $12.5M, 0.30%, 4.2%). Mention the search query and chain in the intro. Do NOT mention tool or API names.",query:n,chain:r,protocolVersion:a,feeTierBps:i,feeTierPercent:i!=null?i/1e4:void 0,page:o,size:s,count:u.length,pools:u}}parseProtocolFilter(e){if(typeof e=="string"){let t=e.trim().toUpperCase();if(ys.includes(t))return t}return"V3"}formatPool(e){let t={address:e.id,chainId:e.chainId,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0};e.apr!=null&&(t.apr=e.apr),e.tvlUsd!=null&&(t.tvlUsd=e.tvlUsd);let n=e.volume1DayUsd??e.volumeUsd24hr;n!=null&&(t.volume24hUsd=n),e.volumeUsd6hr!=null&&(t.volume6hUsd=e.volumeUsd6hr),e.volumeUsd12hr!=null&&(t.volume12hUsd=e.volumeUsd12hr),e.txCount!=null&&(t.txCount=e.txCount);let r=e.token0,o=e.token1;return r&&(t.token0={address:r.address,symbol:r.symbol,name:r.name,decimals:r.decimals}),o&&(t.token1={address:o.address,symbol:o.symbol,name:o.name,decimals:o.decimals}),t}};var bs=365,Bo=/^0x[a-fA-F0-9]{40}$/,yt=class extends v{name="lookup-pool-by-address";description='Resolve an unknown 0x address as either a Uniswap pool or a token, then return the appropriate data: pool detail when the address is a pool, or the list of pools containing that token when the address is a token. Use this tool when the user pastes a single address and asks about "the pool" without saying which kind. For pool detail (when the address is a pool): pair, fee tier, TVL with 24h % change, 24h volume, 24h fees, estimated APR, token reserves with USD prices, and weekly volume series. For token resolution (when the address is a token): the resolved token (symbol, name, decimals) plus a ranked list of pools that include it (pair, fee tier, 24h volume, pool address). Source: https://app.uniswap.org. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea';category="blockchain-data";parameters=[{name:"address",type:"string",description:"A 0x-prefixed EVM address. May be a Uniswap pool contract OR a token contract \u2014 the tool figures out which.",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=R(e.chain,t),r=typeof e.address=="string"?e.address.trim():"",[o,s]=await Promise.all([this.service.searchPools({query:r,chain:n,page:1,size:5,protocolVersion:"ALL"}),this.service.searchTokens({query:r,chain:n,page:1,size:5})]),a=o.success?this.findPoolByAddress(o.data??[],r):void 0;if(a)return this.handlePoolHit(a,n);let i=s.success?this.findTokenByAddress(s.data??[],r):void 0;return i?this.handleTokenHit(i,n):!o.success&&!s.success?{error:o.error||s.error||"Failed to resolve address"}:{error:`Address ${r} did not match any pool or token Uniswap knows on ${I(n)}. Verify the chain (the address may exist on a different network) or ask the user for context.`}}async handlePoolHit(e,t){if(!e.id)return{error:"Resolved pool has no identifier"};let n=e.protocolVersion??"V3",r=n==="V4"?await this.service.getV4PoolDetail({poolId:e.id,chain:t}):n==="V2"?Bo.test(e.id)?await this.service.getV2PoolDetail({address:e.id,chain:t}):{success:!1,error:"Resolved V2 pair has no valid address"}:Bo.test(e.id)?await this.service.getV3PoolDetail({address:e.id,chain:t}):{success:!1,error:"Resolved V3 pool has no valid address"};return!r.success||!r.data?{error:r.error||"Failed to fetch pool detail"}:{_instructions:`The address is a Uniswap ${n} pool. Present pool details in a compact summary. Lead with the pair (token0/token1 symbols), fee tier (%), protocol version, and chain. Then show: TVL (USD) with 24h % change, 24h volume (USD), 24h fees (USD), estimated APR (%), token0 reserve + USD price, token1 reserve + USD price, and total tx count. For V4 pools also mention tickSpacing, isDynamicFee, and the hook address if present. Format numbers human-readably (e.g. $12.5M, +1.23%, 0.05%). Omit null fields. Do NOT mention tool/API names.`,resolvedAs:"pool",chain:t,protocolVersion:n,pool:this.formatPoolDetail(r.data)}}async handleTokenHit(e,t){let n=e.symbol?.trim();if(!n)return{error:"Resolved token has no symbol \u2014 cannot search related pools."};let r=await this.service.searchPools({query:n,chain:t,page:1,size:100,protocolVersion:"V3"});if(!r.success)return{error:r.error||"Failed to fetch pools for resolved token"};let o=(e.address??"").toLowerCase(),a=[...(r.data??[]).filter(i=>{let l=(i.token0?.address??"").toLowerCase(),u=(i.token1?.address??"").toLowerCase();return l===o||u===o})].sort((i,l)=>(l.volumeUsd24hr??0)-(i.volumeUsd24hr??0)).slice(0,10);return{_instructions:"The address is a token, not a pool. Tell the user which token it is (symbol + name), then render the matching pools as a numbered list: pair (token0/token1 symbols), fee tier (%), protocol version, 24h volume (USD), and the pool address. Ask which pool they want detail on; when they answer, re-call this tool with that pool address to fetch the detail. Format numbers human-readably. Omit null fields. Do NOT mention tool/API names.",resolvedAs:"token",chain:t,protocolVersion:"V3",token:this.formatToken(e),poolCount:a.length,pools:a.map(i=>this.formatPoolCandidate(i))}}findPoolByAddress(e,t){let n=t.toLowerCase();return e.find(r=>(r.id??"").toLowerCase()===n)}findTokenByAddress(e,t){let n=t.toLowerCase();return e.find(r=>(r.address??"").toLowerCase()===n)}formatToken(e){return{address:e.address,symbol:e.symbol,name:e.name,decimals:e.decimals,chainId:e.chainId,isSpam:e.isSpam,safetyLevel:e.safetyLevel}}formatPoolCandidate(e){return{address:e.id,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0,volume24hUsd:e.volumeUsd24hr,pair:e.token0?.symbol&&e.token1?.symbol?`${e.token0.symbol}/${e.token1.symbol}`:void 0,token0:e.token0?{symbol:e.token0.symbol,name:e.token0.name,address:e.token0.address}:void 0,token1:e.token1?{symbol:e.token1.symbol,name:e.token1.name,address:e.token1.address}:void 0}}formatPoolDetail(e){let t=e.totalLiquidity?.value,n=e.volume24h?.value,r=e.feeTier,o=typeof r=="number"?r/1e4:void 0,s=typeof n=="number"&&typeof o=="number"?n*o/100:void 0,a=typeof s=="number"&&typeof t=="number"&&t>0?s/t*bs*100:void 0;return{address:e.address??e.poolId,protocolVersion:e.protocolVersion,poolId:e.poolId,tickSpacing:e.tickSpacing,isDynamicFee:e.isDynamicFee,hookAddress:e.hook?.address,feeTierBps:r,feeTierPercent:o,tvlUsd:t,tvlChange24hPercent:e.totalLiquidityPercentChange24h?.value,volume24hUsd:n,fees24hUsd:s,aprPercent:a,txCount:e.txCount,historicalVolumeWeek:this.formatHistorical(e.historicalVolume),token0:e.token0?{address:e.token0.address,symbol:e.token0.symbol,name:e.token0.name,decimals:e.token0.decimals,priceUsd:e.token0.market?.price?.value??e.token0.price?.value,reserve:e.token0Supply}:void 0,token1:e.token1?{address:e.token1.address,symbol:e.token1.symbol,name:e.token1.name,decimals:e.token1.decimals,priceUsd:e.token1.market?.price?.value??e.token1.price?.value,reserve:e.token1Supply}:void 0}}formatHistorical(e){if(!(!e||e.length===0))return e.map(t=>({timestamp:t.timestamp,volumeUsd:t.value}))}};var eo=require("js-sha3"),Oo=2n**96n;function qo(c,e,t){let n=c*c,r=Oo*Oo,o=n*10n**18n/r;return Number(o)/1e18*Math.pow(10,e-t)}function ue(c,e,t){return Math.pow(1.0001,c)*Math.pow(10,e-t)}function De(c,e,t){if(c<=0)throw new Error("priceToTick: price must be > 0");let n=c/Math.pow(10,e-t),r=Math.log(n)/Math.log(1.0001),o=Math.round(r);return Math.abs(r-o)<.001?o:Math.floor(r)}function Te(c,e,t="nearest"){let n=Math.abs(c-Math.round(c))<1e-6?Math.round(c):c,r=(n%e+e)%e;if(r===0)return n;let o=n-r,s=o+e;return t==="down"?o:t==="up"?s:n-o<s-n?o:s}var bt=-887272,wt=887272;function ve(c){return c<bt?bt:c>wt?wt:c}var ws=/^0x[0-9a-f]+$/i;function ae(c){return c.startsWith("0x")||c.startsWith("0X")?c.slice(2):c}function xn(c){let e=ae(c).toLowerCase();if(e.length>64)throw new Error(`pad32: input too long (${e.length})`);return e.padStart(64,"0")}function An(c){if(!ws.test(c)||ae(c).length!==40)throw new Error(`encodeAddress: invalid address ${c}`);return xn(c.toLowerCase())}function de(c,e=256){let t=typeof c=="bigint"?c:BigInt(c);if(t<0n)throw new Error("encodeUint: negative");let n=t.toString(16);if(n.length>e/4)throw new Error(`encodeUint: overflow ${e}`);return xn(n)}function to(c){let e=typeof c=="bigint"?c:BigInt(c);if(e>=0n)return xn(e.toString(16));let t=1n<<256n;return xn((t+e).toString(16))}function Fo(c){return"0x"+(0,eo.keccak_256)(ks(c))}function ks(c){let e=ae(c),t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substring(n*2,n*2+2),16);return t}function Se(c){return"0x"+(0,eo.keccak_256)(new TextEncoder().encode(c)).slice(0,8)}function tn(c,e){let t=c.substring(e,e+64);return t?BigInt("0x"+t):0n}function no(c,e){let t=c.substring(e,e+64),n=BigInt("0x"+t),r=1n<<256n,o=1n<<23n;return n>=1n<<255n?Number(n-r):(n<o,Number(n))}function oo(c,e){return"0x"+c.substring(e+24,e+64).toLowerCase()}var Ue=class{config;constructor(e){this.config=e}hasIntegratedApproval(){return!1}};var Ts="https://exchange-api.keyring.app/admin/setting?configs=others",vs="https://api.coinpool.app/config/bridgeProviderByChain",Ss="https://coinpool-api-op.bacoor-test001.xyz/config/bridgeProviderByChain";function xs(c){let e=null,t=null;return async()=>e||t||(t=(async()=>{try{return e=await c(),e}catch{return null}finally{t=null}})(),t)}async function Wo(c){return(await _.get(c,{headers:{Accept:"application/json"}})).data}var As=xs(async()=>(await Wo(Ts))?.others??null),Pn={prod:null,dev:null},nn={prod:null,dev:null};async function Ps(c){let e=c?"prod":"dev";if(Pn[e])return Pn[e];if(nn[e])return nn[e];let t=c?vs:Ss;return nn[e]=(async()=>{try{let n=await Wo(t);return Pn[e]=n?.data??null,Pn[e]}catch{return null}finally{nn[e]=null}})(),nn[e]}function _s(c){if(!c)return null;try{let e=JSON.parse(c);return e&&typeof e=="object"?e:null}catch{return null}}function $o(c){if(c==null)return 0;let e=typeof c=="number"?c:Number(c);return Number.isFinite(e)?e:0}function Cs(c){return c==="debridge"||c==="relay"}async function _n(c,e){let n=(await Ps(e))?.[String(c)];return Cs(n)?n:null}async function kt(c,e){let t=await As();if(!t)return null;let n,r;if(c==="debridge")n=_s(t.affiliateRecipient)?.[String(e)],r=$o(t.AFFILIATE_FEE_PERENT);else if(c==="relay")n=t.RELAY_AFFILIATE_FEE_RECIPIENT,r=$o(t.RELAY_AFFILIATE_FEE_PERCENT);else return null;return!n||r<=0?null:{affiliateFeeRecipient:n,affiliateFeePercent:r}}var ie={"0x1":"https://ethereum-rpc.publicnode.com","0xa":"https://mainnet.optimism.io","0x38":"https://bsc-dataseed.binance.org","0x89":'https://polygon-bor-rpc.publicnode.com"',"0x2105":"https://mainnet.base.org","0xa4b1":"https://arb1.arbitrum.io/rpc","0xa86a":"https://avalanche-c-chain-rpc.publicnode.com","0xe708":"https://rpc.linea.build"};function Us(c){return ie[c.toLowerCase()]}var Vo={};function ro(c){let e={};if(c)for(let[t,n]of Object.entries(c))typeof n=="string"&&n.trim()&&(e[t.toLowerCase()]=n.trim());Vo=e}function Tt(c){let e=c.toLowerCase();return Vo[e]??ie[e]}async function xe(c,e,t){for(let r=0;r<=5;r++){let o=await _.post(c,{jsonrpc:"2.0",id:Date.now(),method:e,params:t},{headers:{"Content-Type":"application/json"}});if(o.status===429&&r<5){await new Promise(a=>setTimeout(a,2**r*500));continue}if(!o.ok)throw new Error(`RPC ${e}: ${N(o)}`);let s=o.data;if(s.error)throw new Error(`RPC ${e}: ${s.error.message}`);if(s.result===void 0)throw new Error(`RPC ${e}: empty result`);return s.result}throw new Error(`RPC ${e}: exceeded max retries (429)`)}function Ho(c,e,t){return xe(c,"eth_call",[{to:e,data:t},"latest"])}async function so(c,e,t){let n=Tt(c);if(!n)return null;try{return await Ho(n,e,t)}catch{return null}}var Rs="https://dln.debridge.finance/v1.0",Es="0x0000000000000000000000000000000000000000",Ns=new Set([429,500]),Is=5,Ds=1e3,Ls=c=>new Promise(e=>setTimeout(e,c)),vt=class extends Ue{apiBaseUrl;accessToken;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??Rs,this.accessToken=e.accessToken??"d6c45897b8f6"}getProviderName(){return"debridge"}hasIntegratedApproval(){return!1}async getQuote(e){let{srcChainId:t,srcTokenAddress:n,srcTokenAmount:r,dstChainId:o,dstTokenAddress:s,recipientAddress:a,senderAddress:i,slippage:l,isCrossChain:u}=e,d=u??t!==o,m=await kt(this.getProviderName(),t);try{if(d){let g={srcChainId:String(t),srcChainTokenIn:n,srcChainTokenInAmount:r,dstChainId:String(o),dstChainTokenOut:s,dstChainTokenOutRecipient:a,srcChainOrderAuthorityAddress:i,dstChainOrderAuthorityAddress:a,accesstoken:this.accessToken};m&&(g.affiliateFeeRecipient=m.affiliateFeeRecipient,g.affiliateFeePercent=m.affiliateFeePercent);let f=await this.get(`${this.apiBaseUrl}/dln/order/create-tx`,g);return f.error||f.errorMessage?{success:!1,error:f.error??f.errorMessage,errorMessage:f.errorMessage??"deBridge quote failed"}:{success:!0,provider:"debridge",tx:f.tx,estimation:f.estimation,isCrossChain:!0,srcChainId:t,dstChainId:o,raw:f}}let p={chainId:String(t),tokenIn:n,tokenInAmount:r,tokenOut:s,tokenOutRecipient:a,accesstoken:this.accessToken};m&&(p.affiliateFeeRecipient=m.affiliateFeeRecipient,p.affiliateFeePercent=m.affiliateFeePercent),l!==void 0&&(p.slippage=l);let h=await this.get(`${this.apiBaseUrl}/chain/transaction`,p);return h.error||h.errorMessage?{success:!1,error:h.error??h.errorMessage,errorMessage:h.errorMessage??"deBridge quote failed"}:{success:!0,provider:"debridge",tx:h.tx,isCrossChain:!1,srcChainId:t,dstChainId:t,raw:h}}catch(p){return{success:!1,error:p,errorMessage:p instanceof Error?p.message:"Failed to fetch quote"}}}async checkApproval(e){let{chain:t,userAddress:n,tokenAddress:r,amount:o}=e,s=e.quoteData?.tx?.to;if(!s)return{isNeeded:!1,error:"No contract address found in quote"};if(!r||r.toLowerCase()===Es)return{isNeeded:!1,integrated:!1,contractAddress:s,message:"Native coin needs no approval."};let a=await this.readAllowance(t,r,n,s);if(a===null)return{isNeeded:!0,integrated:!1,contractAddress:s,message:"Could not read allowance; approval required before the swap."};let i;try{i=BigInt(o)}catch{i=0n}return a>=i?{isNeeded:!1,integrated:!1,contractAddress:s,message:"Sufficient allowance already granted."}:{isNeeded:!0,integrated:!1,contractAddress:s,message:"Approval required: current allowance is below the swap amount."}}async readAllowance(e,t,n,r){if(!e)return null;let o=i=>i.toLowerCase().replace(/^0x/,"").padStart(64,"0"),s=`0xdd62ed3e${o(n)}${o(r)}`,a=await so(e,t,s);if(typeof a!="string"||!/^0x[0-9a-fA-F]*$/.test(a)||a==="0x")return null;try{return BigInt(a)}catch{return null}}async executeSwap(e){let{quoteData:t}=e;return{success:!0,provider:"debridge",txData:t.tx??void 0,estimation:t.estimation}}async trackTransaction(e){let{requestIdOrTxHash:t}=e;try{let n=await this.get(`${this.apiBaseUrl}/dln/tx/${t}/order-ids`,{accesstoken:this.accessToken});if(!n.orderIds?.length)return{success:!1,status:"PENDING"};let r=n.orderIds[0];switch(((await this.get(`${this.apiBaseUrl}/dln/order/${r}/status`,{accesstoken:this.accessToken})).status??"").toUpperCase()){case"FULFILLED":case"SENTUNLOCK":case"CLAIMEDUNLOCK":return{success:!0,status:"COMPLETED"};case"ORDERCANCELLED":case"SENTORDERCANCEL":case"CLAIMEDORDERCANCEL":return{success:!0,status:"FAILED"};default:return{success:!0,status:"PENDING"}}}catch(n){return{success:!1,status:"ERROR",error:n instanceof Error?n.message:String(n)}}}async get(e,t){let n=Object.entries(t).map(([s,a])=>`${encodeURIComponent(s)}=${encodeURIComponent(String(a))}`).join("&"),r=n?`${e}?${n}`:e,o=await _.get(r,{headers:{Accept:"application/json"}});for(let s=0;s<Is&&Ns.has(o.status);s++)await Ls(Ds),o=await _.get(r,{headers:{Accept:"application/json"}});return o.data}};var Ms="https://api.relay.link";function Go(c){return c===7565164||c==="7565164"?792703809:c}var St=class extends Ue{apiBaseUrl;apiKey;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??Ms,this.apiKey=e.apiKey}getProviderName(){return"relay"}hasIntegratedApproval(){return!0}async getQuote(e){let{srcChainId:t,srcTokenAddress:n,srcTokenAmount:r,dstChainId:o,dstTokenAddress:s,recipientAddress:a,senderAddress:i,slippage:l}=e,u=await kt(this.getProviderName(),t),d={user:a||i,originChainId:Go(t),destinationChainId:Go(o),originCurrency:n,destinationCurrency:s,amount:r,recipient:a||i,tradeType:"EXACT_INPUT"};u&&(d.appFees=[{recipient:u.affiliateFeeRecipient,fee:Math.floor(u.affiliateFeePercent*100)}]),typeof l=="number"&&Number.isFinite(l)&&(d.slippageTolerance=Math.floor(l*100).toString());try{let m=await this.request("/quote/v2","POST",d);if(!m||m.error)return{success:!1,error:m?.error??"Unknown error",errorMessage:m?.message??"Failed to fetch quote from Relay"};let p=m.steps?.find(k=>k.id==="approve"),h=m.steps?.find(k=>k.id==="swap"||k.id==="deposit"),g=h?.items?.[0]?.data,f=Array.isArray(g?.instructions);return{success:!0,provider:"relay",tx:g?f?{data:{instructions:g.instructions??[],addressLookupTableAddresses:g.addressLookupTableAddresses??[]},chainId:g.chainId}:{from:g.from,to:g.to,data:g.data,value:g.value,chainId:g.chainId,maxFeePerGas:g.maxFeePerGas,maxPriorityFeePerGas:g.maxPriorityFeePerGas}:null,isCrossChain:t!==o,srcChainId:t,dstChainId:o,raw:{steps:m.steps,fees:m.fees,details:m.details,approveStep:p,requestId:h?.requestId}}}catch(m){return{success:!1,error:m,errorMessage:m instanceof Error?m.message:"Failed to fetch quote"}}}async checkApproval(e){let t=e.quoteData?.raw?.approveStep;if(t){let n=t.items?.[0]?.data;return{isNeeded:!0,integrated:!0,approvalData:n,contractAddress:n?.to,message:"Approval step is included in quote and will be executed first."}}return{isNeeded:!1,integrated:!0,message:"No approval needed or already approved"}}async executeSwap(e){let{quoteData:t}=e,n=t.raw;return n?.steps?{success:!0,provider:"relay",steps:n.steps,estimation:t.estimation}:{success:!1,error:"Invalid quote data"}}async trackTransaction(e){try{let n=(await this.request("/intents/status/v3","GET",{requestId:e.requestIdOrTxHash})).status??"";return n==="success"||n==="refunded"?{success:!0,status:"COMPLETED"}:n==="failure"||n==="expired"?{success:!0,status:"FAILED"}:{success:!0,status:"PENDING"}}catch(t){return{success:!1,status:"ERROR",error:t instanceof Error?t.message:String(t)}}}async request(e,t,n){let r={"Content-Type":"application/json"};this.apiKey&&(r["x-api-key"]=this.apiKey);let o=`${this.apiBaseUrl}${e}`,s;if(t==="GET"&&n){let i=Object.entries(n).map(([l,u])=>`${encodeURIComponent(l)}=${encodeURIComponent(String(u))}`).join("&");o=`${o}?${i}`}else t==="POST"&&n&&(s=n);let a=await _.request(t,o,s,{headers:r});if(!a.ok)throw new Error(N(a));return a.data}};var Cn={debridge:{apiBaseUrl:"https://dln.debridge.finance/v1.0",accessToken:"d6c45897b8f6"},relay:{apiBaseUrl:"https://api.relay.link"}},on="debridge";function Bs(c){if(c==null)return null;let e=typeof c=="number"?c:Number(c);return Number.isFinite(e)?e:null}var Le=class{providerConfig;isProduction;cache=new Map;constructor(e=Cn,t=!0){this.providerConfig=e,this.isProduction=t}async getService(e){let t=await this.getActiveProvider(e),n=this.cache.get(t);if(n)return n;let r=this.createService(t);return this.cache.set(t,r),r}async getServiceByProvider(e){let t=this.cache.get(e);if(t)return t;let n=this.createService(e);return this.cache.set(e,n),n}async getActiveProvider(e){let t=Bs(e);return t===null?on:await _n(t,this.isProduction)??on}async isProviderActive(e,t){return await this.getActiveProvider(t)===e}getAvailableProviders(){return Object.keys(this.providerConfig)}clearCache(){this.cache.clear()}createService(e){let t=this.providerConfig[e]??{};switch(e){case"debridge":return new vt(t);case"relay":return new St(t);default:{let n=e;throw new Error(`Unknown swap provider: ${String(n)}`)}}}},ye=new Le;var Os="https://api.coinpool.app/config/addresses",qs="https://coinpool-api-op.bacoor-test001.xyz/config/addresses",Un={prod:null,dev:null},rn={prod:null,dev:null};async function Fs(c){let e=c?"prod":"dev";if(Un[e])return Un[e];if(rn[e])return rn[e];let t=c?Os:qs;return rn[e]=(async()=>{try{let r=(await _.get(t,{headers:{Accept:"application/json"}})).data;return Un[e]=r?.data??null,Un[e]}catch{return null}finally{rn[e]=null}})(),rn[e]}function $s(c){let e=c.trim();if(/^0x[0-9a-fA-F]+$/.test(e)){let t=Number.parseInt(e,16);return Number.isFinite(t)?String(t):null}return/^\d+$/.test(e)?e:null}async function Yo(c,e,t){let n=await Fs(t);if(!n)return;let r=$s(e);if(!r)return;let o=n[c]?.[r];return Array.isArray(o)?o[0]:typeof o=="string"?o:void 0}async function Rn(c,e){return Yo("createPoolProxyV3",c,e)}async function En(c,e){return Yo("nftPositionUniswap",c,e)}var O="0x0000000000000000000000000000000000000000",Ws={"0x1":{hexId:"0x1",name:"Ethereum",defaultRpc:ie["0x1"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0xa":{hexId:"0xa",name:"Optimism",defaultRpc:ie["0xa"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0x38":{hexId:"0x38",name:"BNB Smart Chain",defaultRpc:ie["0x38"],native:{symbol:"BNB",name:"BNB",decimals:18,coingeckoId:"binancecoin"},defaultMintGasLimit:12e5},"0x89":{hexId:"0x89",name:"Polygon",defaultRpc:ie["0x89"],native:{symbol:"MATIC",name:"Polygon",decimals:18,coingeckoId:"matic-network"},defaultMintGasLimit:12e5},"0x2105":{hexId:"0x2105",name:"Base",defaultRpc:ie["0x2105"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0xa4b1":{hexId:"0xa4b1",name:"Arbitrum One",defaultRpc:ie["0xa4b1"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:22e5},"0xa86a":{hexId:"0xa86a",name:"Avalanche",defaultRpc:ie["0xa86a"],native:{symbol:"AVAX",name:"Avalanche",decimals:18,coingeckoId:"avalanche-2"},defaultMintGasLimit:12e5},"0xe708":{hexId:"0xe708",name:"Linea",defaultRpc:ie["0xe708"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5}};function te(c){return Ws[c.toLowerCase()]}function xt(c){return te(c)?.native}var Ko="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";function ao(c){let e=c.stableTicks??3,t=c.widePercent??{down:50,up:100},n=ue(c.currentTick,c.token0Decimals,c.token1Decimals),r=ve(Te(c.currentTick-e*c.tickSpacing,c.tickSpacing,"down")),o=ve(Te(c.currentTick+e*c.tickSpacing,c.tickSpacing,"up")),s=n*(1-t.down/100),a=n*(1+t.up/100),i=De(s,c.token0Decimals,c.token1Decimals),l=De(a,c.token0Decimals,c.token1Decimals),u=ve(Te(i,c.tickSpacing,"down")),d=ve(Te(l,c.tickSpacing,"up")),m=(p,h,g,f,y)=>{let k=ue(f,c.token0Decimals,c.token1Decimals),w=ue(y,c.token0Decimals,c.token1Decimals);return{id:p,label:h,description:g,tickLower:f,tickUpper:y,minPrice:k,maxPrice:w,minPercentChange:(k-n)/n*100,maxPercentChange:(w-n)/n*100}};return[m("stable","Stable","Good for stablecoins or low volatility pairs",r,o),m("wide","Wide","Good for volatile pairs",u,d)]}var Vs=Se("slot0()"),Hs=Se("liquidity()"),Gs=Se("tickSpacing()"),Ys=Se("fee()"),Ks=Se("token0()"),js=Se("token1()"),zs=Se("decimals()"),Me=class{swapFactory;isProduction;constructor(e){this.isProduction=e?.isProduction??!0,this.swapFactory=e?.swapFactory??(this.isProduction?ye:new Le(void 0,!1))}getRpcUrl(e){let t=Tt(e);if(t)return t;let n=te(e);if(!n)throw new Error(`Unsupported chain: ${e}`);return n.defaultRpc}async getGatewayAddress(e){return Rn(e,this.isProduction)}async getPositionManagerAddress(e){return En(e,this.isProduction)}async ethCall(e,t,n){return xe(this.getRpcUrl(e),"eth_call",[{to:t,data:n},"latest"])}async getNativeBalance(e,t){let n=await xe(this.getRpcUrl(e),"eth_getBalance",[t,"latest"]);return BigInt(n)}async getGasPrice(e){let t=await xe(this.getRpcUrl(e),"eth_gasPrice",[]);return BigInt(t)}async getTransactionReceipt(e,t){return await xe(this.getRpcUrl(e),"eth_getTransactionReceipt",[t])}async extractMintedNftId(e,t){let n=await this.getPositionManagerAddress(e);if(!n)return null;let r=n.toLowerCase();for(let o of t.logs??[]){if((o.address??"").toLowerCase()!==r)continue;let s=o.topics??[];if(!(s.length<4||s[0]?.toLowerCase()!==Ko||"0x"+s[1].slice(-40).toLowerCase()!==O))try{return BigInt(s[3]).toString()}catch{return null}}return null}async estimateGasReserveWei(e){let t=te(e);if(!t)throw new Error(`Unsupported chain: ${e}`);let n=await this.getGasPrice(e);return BigInt(t.defaultMintGasLimit)*n*18n/10n}async readPoolOnchain(e,t){let[n,r,o,s,a,i]=await Promise.all([this.ethCall(e,t,Vs),this.ethCall(e,t,Hs),this.ethCall(e,t,Gs),this.ethCall(e,t,Ys),this.ethCall(e,t,Ks),this.ethCall(e,t,js)]),l=ae(n),u=tn(l,0),d=no(l,64),m=tn(ae(r),0),p=Number(no(ae(o),0)),h=Number(tn(ae(s),0)),g=oo(ae(a),0),f=oo(ae(i),0),[y,k]=await Promise.all([this.readErc20Decimals(e,g),this.readErc20Decimals(e,f)]),w=qo(u,y,k);return{poolAddress:t,chainId:e,fee:h,tickSpacing:p,liquidity:m,sqrtPriceX96:u,currentTick:d,token0:g,token1:f,currentPrice:w}}async readErc20Decimals(e,t){if(t.toLowerCase()===O)return 18;let n=await this.ethCall(e,t,zs);return Number(tn(ae(n),0))}async getNativePriceUsd(e){let t=te(e);if(!t)return null;try{let n=`https://api.coingecko.com/api/v3/simple/price?ids=${t.native.coingeckoId}&vs_currencies=usd`,r=await _.get(n);if(!r.ok)return null;let s=r.data[t.native.coingeckoId]?.usd;return typeof s=="number"?s:null}catch{return null}}splitNativeAmountForRange(e){let{nativeAmountWei:t,pool:n,tickLower:r,tickUpper:o,token0Decimals:s,token1Decimals:a}=e,i=2**96,l=Number(n.sqrtPriceX96)/i,u=Math.pow(1.0001,r/2),d=Math.pow(1.0001,o/2);if(l<=u)return{ratio0:1,ratio1:0,amountInFor0Wei:t,amountInFor1Wei:0n};if(l>=d)return{ratio0:0,ratio1:1,amountInFor0Wei:0n,amountInFor1Wei:t};let m=1/l-1/d,p=l-u,h=l*l,g=m*h,y=g+p;if(!Number.isFinite(y)||y<=0)throw new Error("splitNativeAmountForRange: invalid total value");let k=g/y,w=1-k,b=1000000000000000n,T=BigInt(Math.floor(k*Number(b))),P=t*T/b,S=t-P;return{ratio0:k,ratio1:w,amountInFor0Wei:P,amountInFor1Wei:S}}async fetchSwapLeg(e){let t=Number.parseInt(e.chainId,16);if(!Number.isFinite(t))throw new Error(`Invalid hex chain id: ${e.chainId}`);let r=await(await this.swapFactory.getService(t)).getQuote({srcChainId:t,srcTokenAddress:e.tokenIn,srcTokenAmount:e.tokenInAmount,dstChainId:t,dstTokenAddress:e.tokenOut,recipientAddress:e.tokenOutRecipient,senderAddress:e.senderAddress??e.tokenOutRecipient,slippage:e.slippage,isCrossChain:!1});if(!r.success||!r.tx)throw new Error(r.errorMessage??"Swap quote failed");let o=r.tx;if(!o.to||typeof o.data!="string")throw new Error("Swap quote returned an unsupported tx shape (non-EVM payload?)");let s=r.raw??{},a=s.tokenOut?.minAmount??s.details?.currencyOut?.minimumAmount??"0";return{to:o.to,data:o.data,value:o.value??"0",minAmountOut:a}}encodeMintCalldata(e){if(e.paymentInfo.length!==2||e.exchanges.length!==2)throw new Error("encodeMintCalldata: paymentInfo and exchanges must each have length 2");let t=Se("mint((address,uint256,address,uint256)[2],(address,bytes,uint256)[2],(uint24,uint256,uint256,int24,int24))"),n=e.paymentInfo.map(h=>An(h.tokenIn)+de(h.tokenInAmount)+An(h.tokenOut)+de(h.tokenOutAmount)).join(""),o=e.exchanges.map(h=>{let g=ae(h.data||"0x"),f=g.length/2,y=de(f),k=(64-g.length%64)%64,w=g+"0".repeat(k);return{head:An(h.to)+de(96)+de(h.value),body:y+w}}).map(h=>h.head+h.body),s=o.length*32,a=[];for(let h of o)a.push(de(s)),s+=h.length/2;let i=a.join("")+o.join(""),l=de(e.fee,24)+de(e.amount0Min)+de(e.amount1Min)+to(e.tickLower)+to(e.tickUpper),m=256+32+160,p=n+de(m)+l+i;return t+p}_keccak(e){return Fo(e)}};var Qs=/^0x[a-fA-F0-9]{40}$/,At=class extends v{name="open-add-liquidity-form";kind="ui";category="pool-action";noSuggestions=!0;description='Open the Add-Liquidity form for a specific Uniswap V3 pool. Use this when the user wants to provide liquidity / add LP / farm / stake / deposit into a pool \u2014 natural-language verbs in any language all map here. Resolves the pool by token symbols (and optional fee tier), reads on-chain state, checks the connected wallet\'s NATIVE balance, then returns a UI payload that the FE renders as the form (with two preset ranges: Stable \xB13 ticks and Wide -50%/+100%). CONSTRAINT: the gateway only accepts the chain\'s NATIVE coin as input (ETH on Ethereum/Base/Arbitrum/Optimism/Linea, BNB on BSC, MATIC on Polygon, AVAX on Avalanche). The native amount is auto-split via deBridge into both pool tokens \u2014 the user never selects a different input. If the user asks to add liquidity using a NON-NATIVE token amount (e.g. "add pool with 5 USDT", "th\xEAm 100 USDC"), pass that symbol via `requestedNonNativeToken` so the tool can return error="unsupported_input_token" \u2014 never silently convert their non-native amount into native or USD. PRECONDITION: a connected wallet (userContext.walletAddress). If absent, the tool returns error="wallet_not_connected" and the agent must ask the user to connect.';parameters=[{name:"token0Symbol",type:"string",description:`Symbol of one of the pool's tokens (e.g. "USDC"). Pair order does not matter.`,required:!0},{name:"token1Symbol",type:"string",description:'Symbol of the other pool token (e.g. "WETH"). Required to uniquely identify the pool.',required:!0},{name:"feeTier",type:"number",description:"Fee tier in basis points: 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. Omit when unknown \u2014 the tool will resolve the best match automatically.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"prefillNativeAmount",type:"number",description:"Optional NATIVE-COIN amount (human-readable) to pre-fill the form's input field. ONLY pass this when the user named an amount in the chain's native coin itself \u2014 ETH on Ethereum/Base/Arbitrum/Optimism/Linea, BNB on BSC, MATIC on Polygon, AVAX on Avalanche. Example: \"add 0.1 ETH to USDC/WETH on Base\". Pass the plain decimal number (e.g. 0.1 for 0.1 ETH). DO NOT convert a non-native amount (USDC, USDT, DAI, WBTC, \u2026) into native here \u2014 pass `requestedNonNativeToken` instead so the tool can reject. Mutually exclusive with prefillUsdAmount \u2014 pass one or neither.",required:!1},{name:"prefillUsdAmount",type:"number",description:'Optional USD amount to pre-fill the form\'s input field. Use this ONLY when the user explicitly speaks in USD/dollars \u2014 e.g. "add $10 to this pool", "10 dollars", "10 \u0111\xF4". The tool converts the USD value to native amount automatically using the current native-token price. DO NOT use this as a workaround for non-native token amounts (e.g. "5 USDT" is NOT $5 \u2014 USDT may de-peg, and even if 1:1 the user asked for a token, not USD). For non-native token amounts pass `requestedNonNativeToken` instead. Mutually exclusive with prefillNativeAmount \u2014 pass one or neither.',required:!1},{name:"requestedNonNativeToken",type:"string",description:`Set this ONLY when the user named an AMOUNT in a non-native token \u2014 e.g. "add pool with 5 USDT", "add 100 USDC to USDC/WETH", "th\xEAm 5 USDT v\xE0o pool". The trigger is "<number> <non-native-symbol>" in the user's message, NOT the mere appearance of the symbol. Pass the symbol the user attached the amount to (e.g. "USDT", "USDC", "DAI"). CRITICAL: If the user also names a NATIVE amount in the same message (e.g. "add USDC/WETH with 0.1 ETH", "th\xEAm pool USDC/WETH 0.3% v\u1EDBi 0.000005 ETH"), the native amount is the input \u2014 leave this EMPTY and use prefillNativeAmount. The pool pair symbols (the two tokens after "pool" / "to") are NEVER the input token by themselves \u2014 they only identify which pool. The tool will return error="unsupported_input_token" so the agent can tell the user that only the chain's native coin is accepted as input. DO NOT silently convert the user's non-native amount into a native amount or USD amount \u2014 the user explicitly named a different asset and must be informed, not auto-corrected. Leave empty when the user did name a native amount, a USD amount, or no amount at all.`,required:!1},{name:"rangeStrategy",type:"string",description:'Optional preset that pre-fills the min/max price inputs (FE keeps them editable). One of: "stable" \u2014 \xB13 tickSpacing around the current price (narrow, higher yield, higher out-of-range risk; pick for stablecoin / low-volatility pairs and when the user says "safe", "tight", "high yield", "t\u1EADp trung"); "wide" \u2014 currentPrice \xD7 [0.5, 2.0] (broad, lower yield, less rebalancing; pick when the user says "wide", "set and forget", "r\u1ED9ng"); "full" \u2014 full V3 tick range (V2-style, lowest yield, never goes out of range; pick when the user says "full range", "v2-style", "kh\xF4ng lo out of range"). Omit when the user did not signal a preference AND did not provide explicit minPrice/maxPrice. Mutually exclusive with minPrice/maxPrice \u2014 if both are passed, explicit prices win.',required:!1},{name:"minPrice",type:"number",description:'Optional explicit lower bound for the price range, in token1-per-token0 units (same unit as pool.currentPrice \u2014 decimal-adjusted). Pass when the user named a concrete number, e.g. "range 1800 to 2200" \u2192 minPrice=1800. Must be paired with maxPrice. When set, overrides rangeStrategy.',required:!1},{name:"maxPrice",type:"number",description:'Optional explicit upper bound for the price range, in token1-per-token0 units (same unit as pool.currentPrice \u2014 decimal-adjusted). Pass when the user named a concrete number, e.g. "range 1800 to 2200" \u2192 maxPrice=2200. Must be paired with minPrice. When set, overrides rangeStrategy.',required:!1}];uniswap;pool;minProvideUsd;constructor(e){super(),this.uniswap=new ne(e),this.pool=new Me(e?.pool),this.minProvideUsd=e?.minProvideUsd??.01}async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){let n=R(e.chain,t),r=typeof e.token0Symbol=="string"?e.token0Symbol.trim():"",o=typeof e.token1Symbol=="string"?e.token1Symbol.trim():"",s=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)?Math.floor(e.feeTier):void 0,a=typeof e.prefillNativeAmount=="number"&&Number.isFinite(e.prefillNativeAmount)&&e.prefillNativeAmount>0?e.prefillNativeAmount:null,i=typeof e.prefillUsdAmount=="number"&&Number.isFinite(e.prefillUsdAmount)&&e.prefillUsdAmount>0?e.prefillUsdAmount:null,l=typeof e.requestedNonNativeToken=="string"&&e.requestedNonNativeToken.trim()?e.requestedNonNativeToken.trim():null,u=l&&a!=null&&(l.toLowerCase()===r.toLowerCase()||l.toLowerCase()===o.toLowerCase())?null:l,d=this.parseRangeStrategy(e.rangeStrategy),m=typeof e.minPrice=="number"&&Number.isFinite(e.minPrice)&&e.minPrice>0?e.minPrice:null,p=typeof e.maxPrice=="number"&&Number.isFinite(e.maxPrice)&&e.maxPrice>0?e.maxPrice:null;if(!r||!o)return{error:"missing_pool_identifier",_instructions:"token0Symbol and token1Symbol are required. Ask the user for the missing token symbol."};let h=te(n);if(!h)return{error:"unsupported_chain",chain:n,_instructions:`Chain ${n} is not supported by the add-liquidity flow.`};let g=xt(n);if(u&&g&&u.toLowerCase()!==g.symbol.toLowerCase())return{error:"unsupported_input_token",requestedToken:u,nativeSymbol:g.symbol,chainName:h.name,_instructions:`The user asked to add liquidity using ${u}, but this app only supports the chain's native coin (${g.symbol} on ${h.name}) as the input. Tell the user this in their language and ask them to either restate the amount in ${g.symbol} or in USD. DO NOT silently convert their ${u} amount, do NOT pre-fill the form, and do NOT call this tool again until the user provides a native or USD amount.`};let f=await this.pool.getGatewayAddress(n);if(!f)return{error:"no_gateway_configured",chain:n,_instructions:`No gateway contract is configured for chain ${n} (${h.name}). Tell the user this chain is not yet supported for add-liquidity in this app.`};let y=t?.walletAddress;if(!y)return{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before adding liquidity. Do NOT render the form."};let k=await this.uniswap.searchPools({query:`${r} ${o}`,chain:n,page:1,size:100,protocolVersion:"V3",feeTier:s});if(!k.success)return{error:k.error||"pool_search_failed"};let w=k.data??[],b=w.find(q=>this.matchesPairExact(q,r,o,s)),T=b?[]:w.filter(q=>this.matchesPair(q,r,o,s));if(!b&&T.length===0)return{error:"pool_not_found",_instructions:`No V3 pool found for ${r}/${o}${s!=null?` at ${s}bps`:""} on ${h.name}. Verify the inputs with the user.`};if(!b&&T.length>1)return{candidates:T.map(q=>({address:q.id,token0Symbol:q.token0?.symbol,token1Symbol:q.token1?.symbol,feeTier:q.feeTier,feeTierPercent:q.feeTier!=null?q.feeTier/1e4:void 0,volume24hUsd:q.volume1DayUsd??q.volumeUsd24hr,tvlUsd:q.tvlUsd,...q.apr?{apr:q.apr}:void 0})),_instructions:"Multiple pools matched. Present them as a numbered list (pair, fee %, TVL, 24h volume, address, apr) and ask the user to pick one. Then re-call this tool with the chosen pool's exact token0Symbol, token1Symbol, and feeTier."};let P=b??T[0];if(!P.id||!Qs.test(P.id))return{error:"invalid_pool_address"};let S=await this.pool.readPoolOnchain(n,P.id),[C,L,x]=await Promise.all([this.pool.getNativeBalance(n,y),this.pool.estimateGasReserveWei(n),this.pool.getNativePriceUsd(n)]),D=C>L?C-L:0n,A=xt(n),H=Number(D)/10**A.decimals,Pe=Number(C)/10**A.decimals,ce=x!=null?H*x:null,[Q,J]=await Promise.all([this.pool.readErc20Decimals(n,S.token0),this.pool.readErc20Decimals(n,S.token1)]),ke={min:ue(bt,Q,J),max:ue(wt,Q,J)},z=null;C===0n?z="no_balance":ce!=null&&ce<this.minProvideUsd&&(z="insufficient_balance");let me=P.token0,Be=P.token1,Gn=[25,50,75,100].map(q=>({percent:q,amountWei:(D*BigInt(q)/100n).toString(),amount:H*(q/100)})),Oe=null,pe=null;if(a!=null){pe=a;let q=BigInt(Math.floor(pe*10**A.decimals));q>0n&&(Oe=q.toString())}else if(i!=null&&x!=null&&x>0){pe=i/x;let q=BigInt(Math.floor(pe*10**A.decimals));q>0n&&(Oe=q.toString())}let qe={address:O,isNative:!0,symbol:A.symbol,name:A.name,decimals:A.decimals,priceUsd:x,balanceWei:C.toString(),balance:Pe,gasReserveWei:L.toString(),spendableWei:D.toString(),spendable:H,spendableUsd:ce,quickRates:Gn,minProvideUsd:this.minProvideUsd,warning:z,warningMessage:z==="no_balance"?`You have no ${A.symbol} on ${h.name}. Top up to add liquidity.`:z==="insufficient_balance"?`Spendable ${A.symbol} is below the $${this.minProvideUsd} minimum.`:null,prefillAmount:pe,prefillAmountWei:Oe},co=this.computePrefillRange({explicitMinPrice:m,explicitMaxPrice:p,rangeStrategy:d,currentTick:S.currentTick,tickSpacing:S.tickSpacing,token0Decimals:Q,token1Decimals:J,priceBounds:ke});return{ui:{component:"AddLiquidityForm",props:{chain:{hexId:n,name:h.name},pool:{address:S.poolAddress,fee:S.fee,feePercent:S.fee/1e4,tickSpacing:S.tickSpacing,currentTick:S.currentTick,currentPrice:S.currentPrice,sqrtPriceX96:S.sqrtPriceX96.toString(),token0:{address:S.token0,symbol:me?.symbol,name:me?.name,logo:me?.logo},token1:{address:S.token1,symbol:Be?.symbol,name:Be?.name,logo:Be?.logo}},inputToken:qe,priceBounds:ke,prefillRange:co,gatewayAddress:f}},summary:this.buildSummary({chainName:h.name,pair:`${me?.symbol??r}/${Be?.symbol??o}`,feePercent:S.fee/1e4,warning:z,minProvideUsd:this.minProvideUsd,nativeSymbol:A.symbol}),_instructions:`The add-liquidity action is ready. In the user's language, invite them to enter the amount and price range to add liquidity, naming the pool + chain (e.g. "Enter the amount and price range to add liquidity to <pool> on <chain>"). If warning="no_balance" tell them they need to top up. If warning="insufficient_balance" tell them the balance is below the minimum. Do NOT mention balance amounts, deBridge, native coin mechanics, UI, forms, or internal tool names. Wait for the user to fill in amount + price range before proceeding.`}}matchesPairExact(e,t,n,r){if(!r||r!=null&&e.feeTier!==r)return!1;let o=(e.token0?.symbol??"").toLowerCase(),s=(e.token1?.symbol??"").toLowerCase(),a=t.toLowerCase(),i=n.toLowerCase();return o===a&&s===i||o===i&&s===a}matchesPair(e,t,n,r){if(r!=null&&e.feeTier!==r)return!1;let o=(e.token0?.symbol??"").toLowerCase(),s=(e.token1?.symbol??"").toLowerCase(),a=t.toLowerCase(),i=n.toLowerCase(),l=this.symMatch(o,a)&&this.symMatch(s,i),u=this.symMatch(o,i)&&this.symMatch(s,a);return l||u}symMatch(e,t){return!e||!t?!1:e.includes(t)||t.includes(e)}parseRangeStrategy(e){if(typeof e!="string")return null;let t=e.trim().toLowerCase();return t==="stable"||t==="wide"||t==="full"?t:null}computePrefillRange(e){let{explicitMinPrice:t,explicitMaxPrice:n,rangeStrategy:r,priceBounds:o}=e;if(t!=null&&n!=null&&n>t)return{strategy:"user",minPrice:Math.max(t,o.min),maxPrice:Math.min(n,o.max)};if(r==="full")return{strategy:"full",minPrice:o.min,maxPrice:o.max};if(r==="stable"||r==="wide"){let a=ao({currentTick:e.currentTick,tickSpacing:e.tickSpacing,token0Decimals:e.token0Decimals,token1Decimals:e.token1Decimals}).find(i=>i.id===r);if(a)return{strategy:r,minPrice:a.minPrice,maxPrice:a.maxPrice}}return null}buildSummary(e){let t=[`Add-liquidity form opened for ${e.pair} ${e.feePercent}% on ${e.chainName}.`];return e.warning==="no_balance"?t.push(`No ${e.nativeSymbol} balance \u2014 top up the wallet to add liquidity.`):e.warning==="insufficient_balance"&&t.push(`Balance is below the $${e.minProvideUsd} minimum \u2014 top up before submitting.`),t.join(" ")}};var jo=[{type:"constructor",inputs:[{name:"owner_",type:"address",internalType:"address"},{name:"supportedRouter_",type:"address",internalType:"address"},{name:"nonfungiblePositionManager_",type:"address",internalType:"address"}],stateMutability:"nonpayable"},{type:"function",name:"NATIVE_TOKEN",inputs:[],outputs:[{name:"",type:"address",internalType:"address"}],stateMutability:"view"},{type:"function",name:"getSupportedRouters",inputs:[],outputs:[{name:"",type:"address[]",internalType:"address[]"}],stateMutability:"view"},{type:"function",name:"increaseLiquidity",inputs:[{name:"tokenId_",type:"uint256",internalType:"uint256"},{name:"swapInfo_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapInfo[2]",components:[{name:"tokenIn",type:"address",internalType:"address"},{name:"tokenInAmount",type:"uint256",internalType:"uint256"},{name:"tokenOut",type:"address",internalType:"address"},{name:"tokenOutAmount",type:"uint256",internalType:"uint256"}]},{name:"swapOp_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapOp[2]",components:[{name:"to",type:"address",internalType:"address"},{name:"value",type:"uint256",internalType:"uint256"},{name:"data",type:"bytes",internalType:"bytes"}]},{name:"increaseLiquidityData_",type:"tuple",internalType:"struct CoinPoolGateway.IncreaseLiquidityData",components:[{name:"amount0Min",type:"uint256",internalType:"uint256"},{name:"amount1Min",type:"uint256",internalType:"uint256"}]}],outputs:[],stateMutability:"payable"},{type:"function",name:"mint",inputs:[{name:"swapInfo_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapInfo[2]",components:[{name:"tokenIn",type:"address",internalType:"address"},{name:"tokenInAmount",type:"uint256",internalType:"uint256"},{name:"tokenOut",type:"address",internalType:"address"},{name:"tokenOutAmount",type:"uint256",internalType:"uint256"}]},{name:"swapOp_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapOp[2]",components:[{name:"to",type:"address",internalType:"address"},{name:"value",type:"uint256",internalType:"uint256"},{name:"data",type:"bytes",internalType:"bytes"}]},{name:"mintData_",type:"tuple",internalType:"struct CoinPoolGateway.MintData",components:[{name:"fee",type:"uint24",internalType:"uint24"},{name:"tickLower",type:"int24",internalType:"int24"},{name:"tickUpper",type:"int24",internalType:"int24"},{name:"amount0Min",type:"uint256",internalType:"uint256"},{name:"amount1Min",type:"uint256",internalType:"uint256"}]}],outputs:[],stateMutability:"payable"},{type:"function",name:"nonfungiblePositionManager",inputs:[],outputs:[{name:"",type:"address",internalType:"contract INonfungiblePositionManager"}],stateMutability:"view"},{type:"function",name:"owner",inputs:[],outputs:[{name:"",type:"address",internalType:"address"}],stateMutability:"view"},{type:"function",name:"pause",inputs:[],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"paused",inputs:[],outputs:[{name:"",type:"bool",internalType:"bool"}],stateMutability:"view"},{type:"function",name:"renounceOwnership",inputs:[],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"rescue",inputs:[{name:"token_",type:"address",internalType:"address"},{name:"amount_",type:"uint256",internalType:"uint256"},{name:"recipient_",type:"address",internalType:"address"}],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"transferOwnership",inputs:[{name:"newOwner",type:"address",internalType:"address"}],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"unpause",inputs:[],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"updateSupportedRouters",inputs:[{name:"routers_",type:"address[]",internalType:"address[]"},{name:"isSupported_",type:"bool[]",internalType:"bool[]"}],outputs:[],stateMutability:"nonpayable"},{type:"event",name:"LiquidityIncreased",inputs:[{name:"tokenId",type:"uint256",indexed:!1,internalType:"uint256"},{name:"token0",type:"address",indexed:!1,internalType:"address"},{name:"token1",type:"address",indexed:!1,internalType:"address"},{name:"amount0LiquidityIncreased",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1LiquidityIncreased",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0IncreaseLiquidityRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1IncreaseLiquidityRefunded",type:"uint256",indexed:!1,internalType:"uint256"}],anonymous:!1},{type:"event",name:"Minted",inputs:[{name:"tokenId",type:"uint256",indexed:!1,internalType:"uint256"},{name:"token0",type:"address",indexed:!1,internalType:"address"},{name:"token1",type:"address",indexed:!1,internalType:"address"},{name:"amount0Minted",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1Minted",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0MintRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1MintRefunded",type:"uint256",indexed:!1,internalType:"uint256"}],anonymous:!1},{type:"event",name:"OwnershipTransferred",inputs:[{name:"previousOwner",type:"address",indexed:!0,internalType:"address"},{name:"newOwner",type:"address",indexed:!0,internalType:"address"}],anonymous:!1},{type:"event",name:"Paused",inputs:[{name:"account",type:"address",indexed:!1,internalType:"address"}],anonymous:!1},{type:"event",name:"SupportedRoutersUpdated",inputs:[{name:"routers",type:"address[]",indexed:!1,internalType:"address[]"},{name:"isSupported",type:"bool[]",indexed:!1,internalType:"bool[]"}],anonymous:!1},{type:"event",name:"Unpaused",inputs:[{name:"account",type:"address",indexed:!1,internalType:"address"}],anonymous:!1},{type:"error",name:"AddressEmptyCode",inputs:[{name:"target",type:"address",internalType:"address"}]},{type:"error",name:"EnforcedPause",inputs:[]},{type:"error",name:"ExpectedPause",inputs:[]},{type:"error",name:"FailedCall",inputs:[]},{type:"error",name:"InsufficientAmountOut",inputs:[]},{type:"error",name:"InsufficientBalance",inputs:[{name:"balance",type:"uint256",internalType:"uint256"},{name:"needed",type:"uint256",internalType:"uint256"}]},{type:"error",name:"InvalidAmountIn",inputs:[]},{type:"error",name:"InvalidLength",inputs:[]},{type:"error",name:"InvalidRefundUnusedToken",inputs:[]},{type:"error",name:"InvalidSwapAction",inputs:[]},{type:"error",name:"InvalidTokenInInfo",inputs:[]},{type:"error",name:"InvalidTokenOutInfo",inputs:[]},{type:"error",name:"InvalidTotalAmountIn",inputs:[]},{type:"error",name:"NotSupportedRouter",inputs:[]},{type:"error",name:"OwnableInvalidOwner",inputs:[{name:"owner",type:"address",internalType:"address"}]},{type:"error",name:"OwnableUnauthorizedAccount",inputs:[{name:"account",type:"address",internalType:"address"}]},{type:"error",name:"ReentrancyGuardReentrantCall",inputs:[]},{type:"error",name:"SafeERC20FailedDecreaseAllowance",inputs:[{name:"spender",type:"address",internalType:"address"},{name:"currentAllowance",type:"uint256",internalType:"uint256"},{name:"requestedDecrease",type:"uint256",internalType:"uint256"}]},{type:"error",name:"SafeERC20FailedOperation",inputs:[{name:"token",type:"address",internalType:"address"}]}],Nn=[{type:"function",name:"approve",stateMutability:"nonpayable",inputs:[{name:"spender",type:"address"},{name:"amount",type:"uint256"}],outputs:[{name:"",type:"bool"}]},{type:"function",name:"allowance",stateMutability:"view",inputs:[{name:"owner",type:"address"},{name:"spender",type:"address"}],outputs:[{name:"",type:"uint256"}]}];var Pt=class extends v{name="preview-add-liquidity";kind="action";category="pool-action";noSuggestions=!0;description="Preview the add-liquidity transaction once the user has filled the AddLiquidityForm with a native amount and a price range (min/max price in token1 per token0). The tool converts prices \u2192 ticks (rounded to tickSpacing), fetches deBridge swap quotes for both legs, encodes the gateway.mint call, and returns an unsigned tx the FE will sign. Do NOT call this until the user has filled both prices and an amount.";parameters=[{name:"poolAddress",type:"string",description:"V3 pool contract address (returned by open-add-liquidity-form in props.pool.address).",required:!0},{name:"chain",type:"string",description:`Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea .Must match the pool's chain. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.`,required:!1},{name:"minPrice",type:"number",description:'Lower bound of the position price range, in token1-per-token0 units (decimal-adjusted \u2014 the SAME unit as pool.currentPrice from open-add-liquidity-form). Example: if currentPrice is 1800 (USDC per WETH), minPrice 900 means "the position is in-range while WETH \u2265 900 USDC". Must be > 0 and < maxPrice.',required:!0},{name:"maxPrice",type:"number",description:'Upper bound of the position price range, in the same units as minPrice. Must be > minPrice. Pass a very large number (or props.priceBounds.max) for a "full range upper" position.',required:!0},{name:"nativeAmount",type:"number",description:"Native amount the user wants to spend, as a human-readable decimal number (e.g. 0.1 for 0.1 ETH). The tool converts to wei internally.",required:!0},{name:"slippageBps",type:"number",description:"Slippage tolerance in basis points (100 = 1%). Optional \u2014 defaults to 100.",required:!1}];pool;defaultSlippageBps;mintMinPercent;pantograph;constructor(e){super(),this.pool=new Me(e?.pool),this.defaultSlippageBps=e?.defaultSlippageBps??"auto",this.mintMinPercent=e?.mintMinPercent??80,this.pantograph=new ee}async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){let n=typeof e.poolAddress=="string"?e.poolAddress.trim():"",r=R(e.chain,t),o=Number(e.minPrice),s=Number(e.maxPrice),a=typeof e.nativeAmount=="number"&&Number.isFinite(e.nativeAmount)?e.nativeAmount:NaN,i=typeof e.slippageBps=="number"&&Number.isFinite(e.slippageBps)&&String(e.slippageBps)!=="auto"?Math.floor(e.slippageBps)/100:String(this.defaultSlippageBps);if(!n||!r||!Number.isFinite(a)||!Number.isFinite(o)||!Number.isFinite(s))return{error:"missing_inputs",_instructions:"poolAddress, chain, minPrice, maxPrice, nativeAmount are all required."};if(o<=0||s<=0||o>=s)return{error:"invalid_price_range",_instructions:"minPrice and maxPrice must both be > 0 and minPrice must be strictly less than maxPrice."};if(a<=0)return{error:"invalid_native_amount",_instructions:"nativeAmount must be > 0."};let l=te(r);if(!l)return{error:"unsupported_chain",chain:r};let u=await this.pool.getGatewayAddress(r);if(!u)return{error:"no_gateway_configured",chain:r};let d=t?.walletAddress;if(!d)return{error:"wallet_not_connected"};let m=xt(r),p=BigInt(Math.floor(a*10**m.decimals));if(p<=0n)return{error:"invalid_native_amount",_instructions:"nativeAmount must be > 0."};let h=await this.pool.readPoolOnchain(r,n),[g,f]=await Promise.all([this.pool.readErc20Decimals(r,h.token0),this.pool.readErc20Decimals(r,h.token1)]),y=De(o,g,f),k=De(s,g,f),w=ve(Te(y,h.tickSpacing,"down")),b=ve(Te(k,h.tickSpacing,"up"));if(w>=b||w<bt||b>wt)return{error:"invalid_range_after_alignment",_instructions:"After aligning to tickSpacing the range collapsed. Ask the user for a wider price range.",rawTickLower:y,rawTickUpper:k,tickLower:w,tickUpper:b};let T={tickLower:w,tickUpper:b,minPrice:ue(w,g,f),maxPrice:ue(b,g,f)},[P,S,C]=await Promise.all([this.pool.getNativeBalance(r,d),this.pool.estimateGasReserveWei(r),this.pool.getNativePriceUsd(r)]),L=p+S;if(P<L)return{error:"insufficient_balance",_instructions:"User does not have enough native coin (amount + gas reserve). Tell them the shortfall in their language.",balanceWei:P.toString(),requiredWei:L.toString(),gasReserveWei:S.toString()};let x=this.pool.splitNativeAmountForRange({nativeAmountWei:p,pool:h,tickLower:w,tickUpper:b,token0Decimals:g,token1Decimals:f}),D=h.token0.toLowerCase()===O,A=h.token1.toLowerCase()===O,H=[];if(D||x.amountInFor0Wei===0n)H.push({tokenIn:O,tokenOut:h.token0,amountIn:x.amountInFor0Wei,tx:{to:O,data:"0x",value:"0"},minAmountOut:D?x.amountInFor0Wei:0n});else{let G=await this.pool.fetchSwapLeg({senderAddress:d,chainId:r,tokenIn:O,tokenInAmount:x.amountInFor0Wei.toString(),tokenOut:h.token0,tokenOutRecipient:u,slippage:typeof i=="number"?i:"auto"});H.push({tokenIn:O,tokenOut:h.token0,amountIn:x.amountInFor0Wei,tx:{to:G.to,data:G.data,value:G.value},minAmountOut:BigInt(G.minAmountOut||"0")})}if(A||x.amountInFor1Wei===0n)H.push({tokenIn:O,tokenOut:h.token1,amountIn:x.amountInFor1Wei,tx:{to:O,data:"0x",value:"0"},minAmountOut:A?x.amountInFor1Wei:0n});else{let G=await this.pool.fetchSwapLeg({senderAddress:d,chainId:r,tokenIn:O,tokenInAmount:x.amountInFor1Wei.toString(),tokenOut:h.token1,tokenOutRecipient:u,slippage:typeof i=="number"?i:"auto"});H.push({tokenIn:O,tokenOut:h.token1,amountIn:x.amountInFor1Wei,tx:{to:G.to,data:G.data,value:G.value},minAmountOut:BigInt(G.minAmountOut||"0")})}let Pe=G=>G*BigInt(this.mintMinPercent)/100n,ce=Pe(H[0].minAmountOut),Q=Pe(H[1].minAmountOut),J=H.map(G=>({tokenIn:G.tokenIn,tokenInAmount:BigInt(G.amountIn),tokenOut:G.tokenOut,tokenOutAmount:BigInt(G.minAmountOut)})),ke=H.map(G=>({to:G.tx.to,data:G.tx.data,value:BigInt(G.tx.value||"0")}));console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ paymentInfo:",J),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ exchangesInfo:",ke),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ args:",{args:[J,ke,{fee:Number(h.fee),amount0Min:BigInt(ce),amount1Min:BigInt(Q),tickLower:w,tickUpper:b}]});let z=Ge({abi:jo,functionName:"mint",args:[J,ke,{fee:Number(h.fee),amount0Min:BigInt(ce),amount1Min:BigInt(Q),tickLower:w,tickUpper:b}]}),me=a,Be={chainId:r,to:u,data:z,value:p.toString(),from:d},Gn={nativeIn:me,nativeInUsd:C!=null?me*C:null,ratio:{token0Percent:x.ratio0,token1Percent:x.ratio1},expectedToken0:Number(H[0].minAmountOut)/10**g,expectedToken1:Number(H[1].minAmountOut)/10**f,amount0Min:ce.toString(),amount1Min:Q.toString(),slippageBps:i},Oe=await this.pantograph.getTokensMetadata([h.token0,h.token1],r),pe=Oe[h.token0],qe=Oe[h.token1];return{ui:{component:"ConfirmAddLiquidityTx",props:{chain:{hexId:r,name:l.name},unsignedTx:Be,summary:Gn,pool:{...h,feePercent:h.fee/1e4,token0:{address:h.token0,symbol:pe.symbol,name:pe.name,decimals:pe.decimals,logo:pe.icon_image},token1:{address:h.token1,symbol:qe.symbol,name:qe.name,decimals:qe.decimals,logo:qe.icon_image}},range:T,gatewayAddress:u}},summary:`Ready to add ${me} ${m.symbol} to pool ${h.token0}/${h.token1} ${h.fee/1e4}% on ${l.name}, range [${T.minPrice}, ${T.maxPrice}].`,_instructions:"A confirmation panel has been opened on the FE; the user will sign and broadcast the unsigned tx with their wallet. Briefly tell them (in their language) what is about to happen \u2014 the native amount, the auto-split ratio between the two pool tokens, and the chosen price range. Do NOT mention internal tool/component names."}}};var zo=365,Xs=1e3,Js=/^0x[a-fA-F0-9]{40}$/;function Zs(c,e){return{depositUsd:c,aprPercent:e*100,dailyUsd:c*e/zo,weeklyUsd:c*e/52,monthlyUsd:c*e/12,yearlyUsd:c*e}}var _t=class extends v{name="estimate_pool_yield";description=`Estimate daily / weekly / monthly / yearly fee yield for a USD deposit into a Uniswap V3 pool. Answers questions like: "If I put $1000 into the USDC/WETH 0.05% pool, how much per day?", "b\u1ECF 500$ v\xE0o pool ETH/USDC 0.3% m\u1ED7i ng\xE0y l\u1EDDi bao nhi\xEAu?", "APR of WBTC/ETH 0.3% on Arbitrum". BEHAVIOR (designed to minimize back-and-forth with the user):
106
113
  \u2022 If only ONE token symbol is given \u2192 auto-pick the highest-volume V3 pool containing that token and compute yield. Do NOT ask the user for the other token.
107
114
  \u2022 If BOTH tokens are given but fee tier is missing AND multiple fee tiers exist \u2192 return candidates (one per fee tier) so the agent can ask the user to pick.
108
115
  \u2022 If depositUsd is missing \u2192 default to $1000 and clearly mark the result as a per-$1000 example. Do NOT ask the user for the deposit amount.
109
116
  \u2022 If chain is missing \u2192 default to userContext.chain or "0x1" (Ethereum).
110
- Source: Uniswap ExploreStats (live 24h data). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.`;category="blockchain-data";parameters=[{name:"token0Symbol",type:"string",description:'Symbol of one of the pool tokens (e.g. "USDC"). Pair order does not matter.',required:!0},{name:"token1Symbol",type:"string",description:'Symbol of the other pool token (e.g. "WETH"). Optional \u2014 if omitted, the tool auto-picks the highest-volume pool containing token0Symbol.',required:!1},{name:"feeTier",type:"number",description:"Fee tier in basis points: 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. Optional \u2014 if omitted and the pair has multiple fee tiers, the tool returns candidates.",required:!1},{name:"depositUsd",type:"number",description:"USD amount to deposit. Optional \u2014 defaults to 1000 and the response is marked as a per-$1000 example.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new te(e)}async run(e,t){let n=R(e.chain,t),r=typeof e.token0Symbol=="string"?e.token0Symbol.trim():"",o=typeof e.token1Symbol=="string"?e.token1Symbol.trim():"",s=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)?Math.floor(e.feeTier):void 0,a=typeof e.depositUsd=="number"&&Number.isFinite(e.depositUsd)&&e.depositUsd>0,i=a?e.depositUsd:Bs;if(!r)return{error:"token0Symbol is required. Ask the user which pool / token they want to estimate yield for."};let l=o?`${r} ${o}`:r,u=await this.service.searchPools({query:l,chain:n,page:1,size:50,protocolVersion:"V3"});if(!u.success)return{error:u.error||"Pool search failed"};let d=u.data??[],p=this.filterPools(d,r,o,s);if(p.length===0)return{error:`No V3 pool found for ${this.describeQuery(r,o,s)} on ${L(n)}. Verify the token symbols with the user.`};let m=this.rankByVolume(p);if(o&&s==null&&new Set(m.map(I=>I.feeTier).filter(I=>typeof I=="number")).size>1){let I=this.dedupeByFeeTier(m).slice(0,5);return{_instructions:"The pair has multiple fee tiers. Present them as a short numbered list (fee tier %, 24h volume). Ask which fee tier the user wants, then re-call with the same token symbols + chosen feeTier. If the user already mentioned a deposit amount, pass it along as depositUsd. Do NOT mention tool names. Format numbers human-readably (e.g. $12.5M, 0.05%).",reason:`${r}/${o} exists across multiple fee tiers \u2014 user choice required.`,missing:["feeTier"],candidates:I.map(A=>({pair:A.token0?.symbol&&A.token1?.symbol?`${A.token0.symbol}/${A.token1.symbol}`:void 0,feeTierBps:A.feeTier,feeTierPercent:typeof A.feeTier=="number"?A.feeTier/1e4:void 0,volume24hUsd:A.volumeUsd24hr,address:A.id}))}}let g=m[0];if(!g.id||!Os.test(g.id))return{error:"Resolved pool has no valid address."};let f=await this.service.getV3PoolDetail({address:g.id,chain:n});if(!f.success||!f.data)return{error:f.error||"Failed to fetch pool detail."};let h=f.data,y=h.totalLiquidity?.value,k=h.volume24h?.value,w=h.feeTier,b=typeof w=="number"?w/1e4:void 0,T=typeof k=="number"&&typeof b=="number"?k*b/100:void 0,P=typeof T=="number"&&typeof y=="number"&&y>0?T/y*Fo:void 0,S={address:h.address,pair:h.token0?.symbol&&h.token1?.symbol?`${h.token0.symbol}/${h.token1.symbol}`:void 0,feeTierBps:w,feeTierPercent:b,tvlUsd:y,volume24hUsd:k,fees24hUsd:T,aprPercent:typeof P=="number"?P*100:void 0,chain:n,token0:h.token0?{symbol:h.token0.symbol}:void 0,token1:h.token1?{symbol:h.token1.symbol}:void 0},C=o?void 0:`User only provided ${r}; auto-picked the highest-volume V3 pool containing it.`;if(P==null)return{_instructions:"Pool APR could not be computed (TVL or volume data unavailable). Inform the user briefly and show TVL/volume if present. Do NOT mention tool names.",pool:S,autoPickedReason:C,aprUnavailableReason:y?"Volume data unavailable":"TVL is zero or unavailable"};let D=qs(i,P);return{_instructions:"Present the yield estimate naturally in the user's language. "+(a?"Lead with: pool (pair + fee %), chain, APR %. Then show projected earnings for the user's deposit \u2014 daily, weekly, monthly, yearly. ":'IMPORTANT: depositUsd was NOT specified by the user \u2014 the result is a per-$1000 EXAMPLE. Lead with: pool (pair + fee %), chain, APR %. Then say "for every $1000 deposited you would earn approximately:" and list daily / weekly / monthly / yearly. End with a short note that the user can multiply these numbers by (their deposit / 1000) for a custom amount. ')+(C?'Mention briefly that you picked the highest-volume pool for this token (do NOT say "auto-picked"). ':"")+"Add a one-line disclaimer about impermanent loss. Format USD numbers with 2 decimal places (e.g. $3.45/day). Do NOT mention tool names.",pool:S,estimate:D,depositMode:a?"user-specified":"default-1000-example",autoPickedReason:C,disclaimer:"Fee yield is based on the pool's 24h volume snapshot and assumes full-range liquidity. Actual returns vary with price movement, volume changes, and impermanent loss."}}filterPools(e,t,n,r){let o=t.toLowerCase(),s=n?n.toLowerCase():"";return e.filter(a=>{if(r!=null&&a.feeTier!==r)return!1;let i=(a.token0?.symbol??"").toLowerCase(),l=(a.token1?.symbol??"").toLowerCase();return s?this.sym(i,o)&&this.sym(l,s)||this.sym(i,s)&&this.sym(l,o):this.sym(i,o)||this.sym(l,o)})}sym(e,t){return!e||!t?!1:e.includes(t)||t.includes(e)}rankByVolume(e){return[...e].sort((t,n)=>(n.volumeUsd24hr??0)-(t.volumeUsd24hr??0))}dedupeByFeeTier(e){let t=new Set,n=[];for(let r of e)typeof r.feeTier=="number"&&(t.has(r.feeTier)||(t.add(r.feeTier),n.push(r)));return n}describeQuery(e,t,n){let r=t?`${e}/${t}`:e;return n!=null?`${r} at ${n} bps`:r}};var Fs={"0x1":"https://gateway.thegraph.com/api/subgraphs/id/5zvR82QoaXYFyDEKLZ9t6v9adgnptxYpKpSbxtgVENFV","0xa4b1":"https://gateway.thegraph.com/api/subgraphs/id/FbCGRftH4a3yZugY7TnbYgPJVEv2LvMT6oF1fxPe9aJM","0x89":"https://gateway.thegraph.com/api/subgraphs/id/3hCPRGf4z88VC5rsBKU5AA9FBBq5nF3jbKJG7VZCbhjm","0x38":"https://gateway.thegraph.com/api/subgraphs/id/GcKPSgHoY42xNYVAkSPDhXSzi6aJDRQSKqBSXezL47gV","0x2105":"https://gateway.thegraph.com/api/subgraphs/id/HMuAwufqZ1YCRmzL2SfHTVkzZovC9VL2UAKhjvRqKiR1","0xa":"https://gateway.thegraph.com/api/subgraphs/id/Cghf4LfVqPiFw6fp6Y5X5Ubc8UpmUhSfJL82zwiBFLaj","0xa86a":"https://gateway.thegraph.com/api/subgraphs/id/GVH9h9KZ9CqheUEL93qMbq7QwgoBu32QXQDPR6bev4Eo"},$s={"0x1":"ethereum","0xa4b1":"arbitrum","0x89":"polygon","0x38":"bsc","0x2105":"base","0xa":"optimism","0xa86a":"avalanche"},Ws={"0x1":1,"0xa4b1":42161,"0x89":137,"0x38":56,"0x2105":8453,"0xa":10,"0xa86a":43114},$o={"0x1":"0x1",1:"0x1",eth:"0x1",ethereum:"0x1",mainnet:"0x1","0xa4b1":"0xa4b1",42161:"0xa4b1",arb:"0xa4b1",arbitrum:"0xa4b1","0x89":"0x89",137:"0x89",matic:"0x89",polygon:"0x89","0x38":"0x38",56:"0x38",bnb:"0x38",bsc:"0x38","0x2105":"0x2105",8453:"0x2105",base:"0x2105","0xa":"0xa",10:"0xa",op:"0xa",optimism:"0xa","0xa86a":"0xa86a",43114:"0xa86a",avax:"0xa86a",avalanche:"0xa86a"},Wo={100:.01,500:.05,3e3:.3,1e4:1},Vs={ethereum:"0x1","arbitrum one":"0xa4b1",arbitrum:"0xa4b1",polygon:"0x89",bsc:"0x38",base:"0x2105",optimism:"0xa","avalanche c-chain":"0xa86a",avalanche:"0xa86a"},Hs=300*1e3,ne=class{apiKey;subgraphUrls;coinPoolBaseUrl;keyringPoolBaseUrl;llamaCache=null;llamaCacheAt=0;constructor(e={}){this.apiKey=e.theGraphApiKey||"4c67ac7a75b21befbd28dc9120c709f1";let t={...Fs,...e.subgraphUrls||{}};this.subgraphUrls=Object.fromEntries(Object.entries(t).filter(([n])=>pn(n))),this.coinPoolBaseUrl=e.coinPoolBaseUrl||"https://api.coinpool.app",this.keyringPoolBaseUrl=e.keyringPoolBaseUrl||"https://pool-data.keyring.app"}resolveChain(e){if(!e)return"0x1";let t=e.toLowerCase();return $o[t]||(this.subgraphUrls[t]?t:"0x1")}isSupported(e){if(!e)return!1;let t=e.toLowerCase(),n=$o[t];return!!(n&&this.subgraphUrls[n])}chainName(e){let t=this.resolveChain(e);return $s[t]||t}numericChainId(e){return Ws[this.resolveChain(e)]}listSupportedChains(){return Object.keys(this.subgraphUrls)}async querySubgraph(e,t){let n=this.resolveChain(e),r=this.subgraphUrls[n];if(!r)throw new Error(`No subgraph available for chain: ${e}`);let o=await _.post(r,{query:t},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!o.ok)throw new Error(`Subgraph query failed: ${N(o)}`);let s=o.data;if(s.errors&&s.errors.length>0)throw new Error(`Subgraph error: ${s.errors[0].message||"Unknown"}`);return s.data}poolFragment(){return`
117
+ Source: Uniswap ExploreStats (live 24h data). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.`;category="blockchain-data";parameters=[{name:"token0Symbol",type:"string",description:'Symbol of one of the pool tokens (e.g. "USDC"). Pair order does not matter.',required:!0},{name:"token1Symbol",type:"string",description:'Symbol of the other pool token (e.g. "WETH"). Optional \u2014 if omitted, the tool auto-picks the highest-volume pool containing token0Symbol.',required:!1},{name:"feeTier",type:"number",description:"Fee tier in basis points: 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. Optional \u2014 if omitted and the pair has multiple fee tiers, the tool returns candidates.",required:!1},{name:"depositUsd",type:"number",description:"USD amount to deposit. Optional \u2014 defaults to 1000 and the response is marked as a per-$1000 example.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=R(e.chain,t),r=typeof e.token0Symbol=="string"?e.token0Symbol.trim():"",o=typeof e.token1Symbol=="string"?e.token1Symbol.trim():"",s=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)?Math.floor(e.feeTier):void 0,a=typeof e.depositUsd=="number"&&Number.isFinite(e.depositUsd)&&e.depositUsd>0,i=a?e.depositUsd:Xs;if(!r)return{error:"token0Symbol is required. Ask the user which pool / token they want to estimate yield for."};let l=o?`${r} ${o}`:r,u=await this.service.searchPools({query:l,chain:n,page:1,size:50,protocolVersion:"V3"});if(!u.success)return{error:u.error||"Pool search failed"};let d=u.data??[],m=this.filterPools(d,r,o,s);if(m.length===0)return{error:`No V3 pool found for ${this.describeQuery(r,o,s)} on ${I(n)}. Verify the token symbols with the user.`};let p=this.rankByVolume(m);if(o&&s==null&&new Set(p.map(D=>D.feeTier).filter(D=>typeof D=="number")).size>1){let D=this.dedupeByFeeTier(p).slice(0,5);return{_instructions:"The pair has multiple fee tiers. Present them as a short numbered list (fee tier %, 24h volume). Ask which fee tier the user wants, then re-call with the same token symbols + chosen feeTier. If the user already mentioned a deposit amount, pass it along as depositUsd. Do NOT mention tool names. Format numbers human-readably (e.g. $12.5M, 0.05%).",reason:`${r}/${o} exists across multiple fee tiers \u2014 user choice required.`,missing:["feeTier"],candidates:D.map(A=>({pair:A.token0?.symbol&&A.token1?.symbol?`${A.token0.symbol}/${A.token1.symbol}`:void 0,feeTierBps:A.feeTier,feeTierPercent:typeof A.feeTier=="number"?A.feeTier/1e4:void 0,volume24hUsd:A.volumeUsd24hr,address:A.id}))}}let h=p[0];if(!h.id||!Js.test(h.id))return{error:"Resolved pool has no valid address."};let g=await this.service.getV3PoolDetail({address:h.id,chain:n});if(!g.success||!g.data)return{error:g.error||"Failed to fetch pool detail."};let f=g.data,y=f.totalLiquidity?.value,k=f.volume24h?.value,w=f.feeTier,b=typeof w=="number"?w/1e4:void 0,T=typeof k=="number"&&typeof b=="number"?k*b/100:void 0,P=typeof T=="number"&&typeof y=="number"&&y>0?T/y*zo:void 0,S={address:f.address,pair:f.token0?.symbol&&f.token1?.symbol?`${f.token0.symbol}/${f.token1.symbol}`:void 0,feeTierBps:w,feeTierPercent:b,tvlUsd:y,volume24hUsd:k,fees24hUsd:T,aprPercent:typeof P=="number"?P*100:void 0,chain:n,token0:f.token0?{symbol:f.token0.symbol}:void 0,token1:f.token1?{symbol:f.token1.symbol}:void 0},C=o?void 0:`User only provided ${r}; auto-picked the highest-volume V3 pool containing it.`;if(P==null)return{_instructions:"Pool APR could not be computed (TVL or volume data unavailable). Inform the user briefly and show TVL/volume if present. Do NOT mention tool names.",pool:S,autoPickedReason:C,aprUnavailableReason:y?"Volume data unavailable":"TVL is zero or unavailable"};let L=Zs(i,P);return{_instructions:"Present the yield estimate naturally in the user's language. "+(a?"Lead with: pool (pair + fee %), chain, APR %. Then show projected earnings for the user's deposit \u2014 daily, weekly, monthly, yearly. ":'IMPORTANT: depositUsd was NOT specified by the user \u2014 the result is a per-$1000 EXAMPLE. Lead with: pool (pair + fee %), chain, APR %. Then say "for every $1000 deposited you would earn approximately:" and list daily / weekly / monthly / yearly. End with a short note that the user can multiply these numbers by (their deposit / 1000) for a custom amount. ')+(C?'Mention briefly that you picked the highest-volume pool for this token (do NOT say "auto-picked"). ':"")+"Add a one-line disclaimer about impermanent loss. Format USD numbers with 2 decimal places (e.g. $3.45/day). Do NOT mention tool names.",pool:S,estimate:L,depositMode:a?"user-specified":"default-1000-example",autoPickedReason:C,disclaimer:"Fee yield is based on the pool's 24h volume snapshot and assumes full-range liquidity. Actual returns vary with price movement, volume changes, and impermanent loss."}}filterPools(e,t,n,r){let o=t.toLowerCase(),s=n?n.toLowerCase():"";return e.filter(a=>{if(r!=null&&a.feeTier!==r)return!1;let i=(a.token0?.symbol??"").toLowerCase(),l=(a.token1?.symbol??"").toLowerCase();return s?this.sym(i,o)&&this.sym(l,s)||this.sym(i,s)&&this.sym(l,o):this.sym(i,o)||this.sym(l,o)})}sym(e,t){return!e||!t?!1:e.includes(t)||t.includes(e)}rankByVolume(e){return[...e].sort((t,n)=>(n.volumeUsd24hr??0)-(t.volumeUsd24hr??0))}dedupeByFeeTier(e){let t=new Set,n=[];for(let r of e)typeof r.feeTier=="number"&&(t.has(r.feeTier)||(t.add(r.feeTier),n.push(r)));return n}describeQuery(e,t,n){let r=t?`${e}/${t}`:e;return n!=null?`${r} at ${n} bps`:r}};var ea={"0x1":"https://gateway.thegraph.com/api/subgraphs/id/5zvR82QoaXYFyDEKLZ9t6v9adgnptxYpKpSbxtgVENFV","0xa4b1":"https://gateway.thegraph.com/api/subgraphs/id/FbCGRftH4a3yZugY7TnbYgPJVEv2LvMT6oF1fxPe9aJM","0x89":"https://gateway.thegraph.com/api/subgraphs/id/3hCPRGf4z88VC5rsBKU5AA9FBBq5nF3jbKJG7VZCbhjm","0x38":"https://gateway.thegraph.com/api/subgraphs/id/GcKPSgHoY42xNYVAkSPDhXSzi6aJDRQSKqBSXezL47gV","0x2105":"https://gateway.thegraph.com/api/subgraphs/id/HMuAwufqZ1YCRmzL2SfHTVkzZovC9VL2UAKhjvRqKiR1","0xa":"https://gateway.thegraph.com/api/subgraphs/id/Cghf4LfVqPiFw6fp6Y5X5Ubc8UpmUhSfJL82zwiBFLaj","0xa86a":"https://gateway.thegraph.com/api/subgraphs/id/GVH9h9KZ9CqheUEL93qMbq7QwgoBu32QXQDPR6bev4Eo"},ta={"0x1":"ethereum","0xa4b1":"arbitrum","0x89":"polygon","0x38":"bsc","0x2105":"base","0xa":"optimism","0xa86a":"avalanche"},na={"0x1":1,"0xa4b1":42161,"0x89":137,"0x38":56,"0x2105":8453,"0xa":10,"0xa86a":43114},Qo={"0x1":"0x1",1:"0x1",eth:"0x1",ethereum:"0x1",mainnet:"0x1","0xa4b1":"0xa4b1",42161:"0xa4b1",arb:"0xa4b1",arbitrum:"0xa4b1","0x89":"0x89",137:"0x89",matic:"0x89",polygon:"0x89","0x38":"0x38",56:"0x38",bnb:"0x38",bsc:"0x38","0x2105":"0x2105",8453:"0x2105",base:"0x2105","0xa":"0xa",10:"0xa",op:"0xa",optimism:"0xa","0xa86a":"0xa86a",43114:"0xa86a",avax:"0xa86a",avalanche:"0xa86a"},Xo={100:.01,500:.05,3e3:.3,1e4:1},oa={ethereum:"0x1","arbitrum one":"0xa4b1",arbitrum:"0xa4b1",polygon:"0x89",bsc:"0x38",base:"0x2105",optimism:"0xa","avalanche c-chain":"0xa86a",avalanche:"0xa86a"},ra=300*1e3,X=class{apiKey;subgraphUrls;coinPoolBaseUrl;keyringPoolBaseUrl;llamaCache=null;llamaCacheAt=0;constructor(e={}){this.apiKey=e.theGraphApiKey||"4c67ac7a75b21befbd28dc9120c709f1";let t={...ea,...e.subgraphUrls||{}};this.subgraphUrls=Object.fromEntries(Object.entries(t).filter(([n])=>bn(n))),this.coinPoolBaseUrl=e.coinPoolBaseUrl||"https://api.coinpool.app",this.keyringPoolBaseUrl=e.keyringPoolBaseUrl||"https://pool-data.keyring.app"}resolveChain(e){if(!e)return"0x1";let t=e.toLowerCase();return Qo[t]||(this.subgraphUrls[t]?t:"0x1")}isSupported(e){if(!e)return!1;let t=e.toLowerCase(),n=Qo[t];return!!(n&&this.subgraphUrls[n])}chainName(e){let t=this.resolveChain(e);return ta[t]||t}numericChainId(e){return na[this.resolveChain(e)]}listSupportedChains(){return Object.keys(this.subgraphUrls)}async querySubgraph(e,t){let n=this.resolveChain(e),r=this.subgraphUrls[n];if(!r)throw new Error(`No subgraph available for chain: ${e}`);let o=await _.post(r,{query:t},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!o.ok)throw new Error(`Subgraph query failed: ${N(o)}`);let s=o.data;if(s.errors&&s.errors.length>0)throw new Error(`Subgraph error: ${s.errors[0].message||"Unknown"}`);return s.data}poolFragment(){return`
111
118
  id
112
119
  token0 { id symbol name }
113
120
  token1 { id symbol name }
@@ -124,16 +131,16 @@ Source: Uniswap ExploreStats (live 24h data). Supports: Ethereum, Optimism, BSC,
124
131
  volumeToken0
125
132
  volumeToken1
126
133
  }
127
- `}hasValidVolume(e){let t=e.poolDayData?.[0];return t?parseFloat(t.volumeToken0||"0")!==0&&parseFloat(t.volumeToken1||"0")!==0&&parseFloat(t.volumeUSD||"0")!==0:!1}sanitize(e){return!isFinite(e)||isNaN(e)||e>1e11?0:e}mapPool(e,t){let n=e,r=Number(n.feeTier||0),o=Wo[r]??r/1e4,s=this.sanitize(parseFloat(n.totalValueLockedUSD||"0")),a=n.poolDayData?.[0],i=parseFloat(a?.volumeUSD||"0"),l=parseFloat(a?.feesUSD||"0"),u=null;return s>0&&l>0&&(u=parseFloat((l*365*100/s).toFixed(2))),{poolAddress:n.id,pair:`${n.token0.symbol}/${n.token1.symbol}`,token0:{symbol:n.token0.symbol,address:n.token0.id,name:n.token0.name},token1:{symbol:n.token1.symbol,address:n.token1.id,name:n.token1.name},tvl:s,volume24hUsd:i,fees24hUsd:l,apr:u,feeTierBps:r,feeTierPercent:o,liquidity:n.liquidity||"0",chain:this.chainName(t)}}async fetchDefiLlamaUniswap(){let e=Date.now();if(this.llamaCache&&e-this.llamaCacheAt<Hs)return this.llamaCache;try{let t=await _.get("https://yields.llama.fi/pools");if(!t.ok)throw new Error(`DefiLlama: ${N(t)}`);let r=(t.data.data||[]).filter(o=>o.project==="uniswap-v3");return this.llamaCache=r,this.llamaCacheAt=e,r}catch{return this.llamaCache??[]}}parseLlamaFeeTier(e){if(!e)return null;let t=e.match(/^([\d.]+)%/);return t?Math.round(parseFloat(t[1])*1e4):null}enrichWithLlama(e,t){return t.length?e.map(n=>{let r=this.resolveChain(n.chain),o=n.feeTierBps,s=n.token0.address.toLowerCase(),a=n.token1.address.toLowerCase(),i=t.filter(p=>Vs[p.chain.toLowerCase()]===r),l=i.find(p=>{if(!p.underlyingTokens||p.underlyingTokens.length<2)return!1;let m=p.underlyingTokens.map(f=>f.toLowerCase());if(!(m.includes(s)&&m.includes(a)))return!1;let g=this.parseLlamaFeeTier(p.poolMeta);return g===null||g===o});if(!l){let p=n.token0.symbol.toUpperCase(),m=n.token1.symbol.toUpperCase(),g=[p,m].sort().join("-");l=i.find(f=>{if([...f.symbol.split("-").map(k=>k.toUpperCase())].sort().join("-")!==g)return!1;let y=this.parseLlamaFeeTier(f.poolMeta);return y===null||y===o})}if(!l)return n;let u=l.apyBase??l.apy??null,d={...n};return u!==null&&(d.apr=parseFloat(u.toFixed(2))),l.tvlUsd!=null&&(d.tvl=this.sanitize(l.tvlUsd)),l.volumeUsd1d!=null&&(d.volume24hUsd=this.sanitize(l.volumeUsd1d)),d}):e}async searchPools(e){let{tokens:t,chain:n,sortBy:r,filters:o}=e,s=this.resolveChain(n),a=t.map(f=>{let h=f.toUpperCase(),y=new Set([h]);return h.startsWith("W")||y.add("W"+h),h.startsWith("W")&&h.length>1&&y.add(h.slice(1)),[...y]}),i=['totalValueLockedUSD_gt: "10000"','liquidity_gt: "0"','volumeUSD_gt: "0"'];o?.feeTier&&i.push(`feeTier: "${o.feeTier}"`);let l="totalValueLockedUSD";r==="volume"?l="volumeUSD":r==="fee"?l="feeTier":r==="liquidity"&&(l="liquidity");let u=r==="fee"?"asc":"desc",d=f=>f.length===1?`symbol: "${f[0]}"`:`symbol_in: [${f.map(h=>`"${h}"`).join(", ")}]`,p=(f,h)=>`{
134
+ `}hasValidVolume(e){let t=e.poolDayData?.[0];return t?parseFloat(t.volumeToken0||"0")!==0&&parseFloat(t.volumeToken1||"0")!==0&&parseFloat(t.volumeUSD||"0")!==0:!1}sanitize(e){return!isFinite(e)||isNaN(e)||e>1e11?0:e}mapPool(e,t){let n=e,r=Number(n.feeTier||0),o=Xo[r]??r/1e4,s=this.sanitize(parseFloat(n.totalValueLockedUSD||"0")),a=n.poolDayData?.[0],i=parseFloat(a?.volumeUSD||"0"),l=parseFloat(a?.feesUSD||"0"),u=null;return s>0&&l>0&&(u=parseFloat((l*365*100/s).toFixed(2))),{poolAddress:n.id,pair:`${n.token0.symbol}/${n.token1.symbol}`,token0:{symbol:n.token0.symbol,address:n.token0.id,name:n.token0.name},token1:{symbol:n.token1.symbol,address:n.token1.id,name:n.token1.name},tvl:s,volume24hUsd:i,fees24hUsd:l,apr:u,feeTierBps:r,feeTierPercent:o,liquidity:n.liquidity||"0",chain:this.chainName(t)}}async fetchDefiLlamaUniswap(){let e=Date.now();if(this.llamaCache&&e-this.llamaCacheAt<ra)return this.llamaCache;try{let t=await _.get("https://yields.llama.fi/pools");if(!t.ok)throw new Error(`DefiLlama: ${N(t)}`);let r=(t.data.data||[]).filter(o=>o.project==="uniswap-v3");return this.llamaCache=r,this.llamaCacheAt=e,r}catch{return this.llamaCache??[]}}parseLlamaFeeTier(e){if(!e)return null;let t=e.match(/^([\d.]+)%/);return t?Math.round(parseFloat(t[1])*1e4):null}enrichWithLlama(e,t){return t.length?e.map(n=>{let r=this.resolveChain(n.chain),o=n.feeTierBps,s=n.token0.address.toLowerCase(),a=n.token1.address.toLowerCase(),i=t.filter(m=>oa[m.chain.toLowerCase()]===r),l=i.find(m=>{if(!m.underlyingTokens||m.underlyingTokens.length<2)return!1;let p=m.underlyingTokens.map(g=>g.toLowerCase());if(!(p.includes(s)&&p.includes(a)))return!1;let h=this.parseLlamaFeeTier(m.poolMeta);return h===null||h===o});if(!l){let m=n.token0.symbol.toUpperCase(),p=n.token1.symbol.toUpperCase(),h=[m,p].sort().join("-");l=i.find(g=>{if([...g.symbol.split("-").map(k=>k.toUpperCase())].sort().join("-")!==h)return!1;let y=this.parseLlamaFeeTier(g.poolMeta);return y===null||y===o})}if(!l)return n;let u=l.apyBase??l.apy??null,d={...n};return u!==null&&(d.apr=parseFloat(u.toFixed(2))),l.tvlUsd!=null&&(d.tvl=this.sanitize(l.tvlUsd)),l.volumeUsd1d!=null&&(d.volume24hUsd=this.sanitize(l.volumeUsd1d)),d}):e}async searchPools(e){let{tokens:t,chain:n,sortBy:r,filters:o}=e,s=this.resolveChain(n),a=t.map(g=>{let f=g.toUpperCase(),y=new Set([f]);return f.startsWith("W")||y.add("W"+f),f.startsWith("W")&&f.length>1&&y.add(f.slice(1)),[...y]}),i=['totalValueLockedUSD_gt: "10000"','liquidity_gt: "0"','volumeUSD_gt: "0"'];o?.feeTier&&i.push(`feeTier: "${o.feeTier}"`);let l="totalValueLockedUSD";r==="volume"?l="volumeUSD":r==="fee"?l="feeTier":r==="liquidity"&&(l="liquidity");let u=r==="fee"?"asc":"desc",d=g=>g.length===1?`symbol: "${g[0]}"`:`symbol_in: [${g.map(f=>`"${f}"`).join(", ")}]`,m=(g,f)=>`{
128
135
  pools(
129
- first: ${h}
136
+ first: ${f}
130
137
  orderBy: ${l}
131
138
  orderDirection: ${u}
132
- where: { ${[...i,...f].join(", ")} }
139
+ where: { ${[...i,...g].join(", ")} }
133
140
  ) {
134
141
  ${this.poolFragment()}
135
142
  }
136
- }`,m;if(a.length>0){let h,y;a.length>=2?(h=p([`token0_: { ${d(a[0])} }`,`token1_: { ${d(a[1])} }`],100),y=p([`token0_: { ${d(a[1])} }`,`token1_: { ${d(a[0])} }`],100)):(h=p([`token0_: { ${d(a[0])} }`],100),y=p([`token1_: { ${d(a[0])} }`],100));let[k,w]=await Promise.allSettled([this.querySubgraph(s,h),this.querySubgraph(s,y)]),b=k.status==="fulfilled"?(k.value?.pools||[]).filter(S=>this.hasValidVolume(S)).map(S=>this.mapPool(S,s)):[],T=w.status==="fulfilled"?(w.value?.pools||[]).filter(S=>this.hasValidVolume(S)).map(S=>this.mapPool(S,s)):[],P=new Set(b.map(S=>S.poolAddress));m=[...b,...T.filter(S=>!P.has(S.poolAddress))]}else m=((await this.querySubgraph(s,p([],100)))?.pools||[]).filter(h=>this.hasValidVolume(h)).map(h=>this.mapPool(h,s));let g=await this.fetchDefiLlamaUniswap();return g.length>0&&(m=this.enrichWithLlama(m,g)),o?.minTVL!=null&&(m=m.filter(f=>f.tvl>=o.minTVL)),o?.maxTVL!=null&&(m=m.filter(f=>f.tvl<=o.maxTVL)),o?.minAPR!=null&&(m=m.filter(f=>f.apr!=null&&f.apr>=o.minAPR)),o?.maxAPR!=null&&(m=m.filter(f=>f.apr!=null&&f.apr<=o.maxAPR)),r==="apr"?m.sort((f,h)=>(h.apr??0)-(f.apr??0)):r==="liquidity"&&m.sort((f,h)=>{let y=BigInt(h.liquidity||"0")-BigInt(f.liquidity||"0");return y>0n?1:y<0n?-1:0}),m}async getTrendingPools(e){let t=this.resolveChain(e.chain),n=`{
143
+ }`,p;if(a.length>0){let f,y;a.length>=2?(f=m([`token0_: { ${d(a[0])} }`,`token1_: { ${d(a[1])} }`],100),y=m([`token0_: { ${d(a[1])} }`,`token1_: { ${d(a[0])} }`],100)):(f=m([`token0_: { ${d(a[0])} }`],100),y=m([`token1_: { ${d(a[0])} }`],100));let[k,w]=await Promise.allSettled([this.querySubgraph(s,f),this.querySubgraph(s,y)]),b=k.status==="fulfilled"?(k.value?.pools||[]).filter(S=>this.hasValidVolume(S)).map(S=>this.mapPool(S,s)):[],T=w.status==="fulfilled"?(w.value?.pools||[]).filter(S=>this.hasValidVolume(S)).map(S=>this.mapPool(S,s)):[],P=new Set(b.map(S=>S.poolAddress));p=[...b,...T.filter(S=>!P.has(S.poolAddress))]}else p=((await this.querySubgraph(s,m([],100)))?.pools||[]).filter(f=>this.hasValidVolume(f)).map(f=>this.mapPool(f,s));let h=await this.fetchDefiLlamaUniswap();return h.length>0&&(p=this.enrichWithLlama(p,h)),o?.minTVL!=null&&(p=p.filter(g=>g.tvl>=o.minTVL)),o?.maxTVL!=null&&(p=p.filter(g=>g.tvl<=o.maxTVL)),o?.minAPR!=null&&(p=p.filter(g=>g.apr!=null&&g.apr>=o.minAPR)),o?.maxAPR!=null&&(p=p.filter(g=>g.apr!=null&&g.apr<=o.maxAPR)),r==="apr"?p.sort((g,f)=>(f.apr??0)-(g.apr??0)):r==="liquidity"&&p.sort((g,f)=>{let y=BigInt(f.liquidity||"0")-BigInt(g.liquidity||"0");return y>0n?1:y<0n?-1:0}),p}async getTrendingPools(e){let t=this.resolveChain(e.chain),n=`{
137
144
  pools(
138
145
  first: 100
139
146
  orderBy: volumeUSD
@@ -166,22 +173,22 @@ Source: Uniswap ExploreStats (live 24h data). Supports: Ethereum, Optimism, BSC,
166
173
  ${this.poolFragment()}
167
174
  }
168
175
  }
169
- }`,r=await this.querySubgraph(t,n);if(!r?.position?.pool)return null;let o=r.position,s=this.mapPool(o.pool,t),a=await this.fetchDefiLlamaUniswap(),i=a.length>0?this.enrichWithLlama([s],a)[0]:s,l=i.feeTierBps,u=i.feeTierPercent;return{position:{positionId:o.id,poolAddress:i.poolAddress,pair:i.pair,token0:{symbol:i.token0.symbol,address:i.token0.address},token1:{symbol:i.token1.symbol,address:i.token1.address},feeTierBps:l,feeTierPercent:u,chain:i.chain,liquidity:o.liquidity,depositedToken0:o.depositedToken0,depositedToken1:o.depositedToken1,withdrawnToken0:o.withdrawnToken0,withdrawnToken1:o.withdrawnToken1,collectedFeesToken0:o.collectedFeesToken0,collectedFeesToken1:o.collectedFeesToken1},pool:i}}async getCoinPoolPairs(e){if(!this.coinPoolBaseUrl||!this.keyringPoolBaseUrl)return[];let t=this.numericChainId(e.chain);if(!t)return[];let n;try{let a=await _.get(`${this.coinPoolBaseUrl}/pair/pure-list?chainId=${t}`);if(!a.ok)throw new Error(N(a));n=a.data?.pairs||[]}catch{return[]}if(e.tokens&&e.tokens.length>=2){let a=e.tokens[0].toUpperCase(),i=e.tokens[1].toUpperCase();if(n=n.filter(l=>{let u=l.token0.symbol.toUpperCase(),d=l.token1.symbol.toUpperCase();return u.includes(a)&&d.includes(i)||u.includes(i)&&d.includes(a)}),n.length===0)return[]}let r=[];for(let a of n)for(let i of a.transaction||[])i.nftId&&r.push(i.nftId);let o=await this.fetchPositionDetails(t,r),s=new Map;for(let a of o){let i=String(a?.tokenId??"");i&&s.set(i,a)}return n.map(a=>this.mapCoinPoolPair(a,s)).filter(a=>a!==null)}mapCoinPoolPair(e,t){let n;for(let y of e.transaction||[])if(y.nftId&&(n=t.get(y.nftId),n))break;if(!n)return null;let r=e.selectorAddress.toLowerCase()===e.token0.address.toLowerCase(),o=n.tick?.minTick!=null?Number(n.tick.minTick):0,s=n.tick?.maxTick!=null?Number(n.tick.maxTick):0,a=n.tick?.currentTick!=null?Number(n.tick.currentTick):0,i=r?o:s>0?1/s:0,l=r?s:o>0?1/o:0,u=r?a:a>0?1/a:0,d=r?e.token0.symbol:e.token1.symbol,p=r?e.token1.symbol:e.token0.symbol;if(!u||!i&&!l)return null;let m=Math.min(i,l),g=Math.max(i,l);if(u<m||u>g)return null;let f=Number(e.fee),h=Wo[f]??f/1e4;return{poolId:e.poolId,pair:`${e.token0.symbol}/${e.token1.symbol}`,fee:`${h}%`,apr:n.apr??null,aprAvg:n.aprAvg??null,token0Price:e.token0.price,token1Price:e.token1.price,priceRange:{minPrice:i,maxPrice:l,currentPrice:u,baseToken:d,quoteToken:p}}}async fetchPositionDetails(e,t){if(!this.keyringPoolBaseUrl||t.length===0)return[];let n=Gs(t,200);return(await Promise.allSettled(n.map(async o=>{let s=new URLSearchParams({tokenIds:o.join(","),chainId:String(e)}),a=await _.get(`${this.keyringPoolBaseUrl}/user/positions-details/v3?${s}`);if(!a.ok)throw new Error(N(a));let i=a.data;return Array.isArray(i)?i:i?.data||[]}))).flatMap(o=>o.status==="fulfilled"?o.value:[])}};function Gs(c,e){if(e<=0)return[c];let t=[];for(let n=0;n<c.length;n+=e)t.push(c.slice(n,n+e));return t}var Ks=["tvl","volume","apr","fee","liquidity"],_t=class extends v{name="subgraph-search-pools";description='Search Uniswap V3 liquidity pools via The Graph subgraphs by token symbol(s) on one or more chains. Use for: "find ETH/USDC pools on Base", "show OP pools", "stablecoin pools on Arbitrum", "best USDC pool". Returns pools with pair, TVL, 24h volume, fee tier (%), APR (DefiLlama-enriched), and liquidity rank. Supports TVL/APR/fee filters and sort by tvl, volume, apr, fee, or liquidity. Supported chains: Ethereum (0x1), Arbitrum (0xa4b1), Polygon (0x89), BSC (0x38), Base (0x2105), Optimism (0xa), Avalanche (0xa86a).';category="blockchain-data";noSuggestions=!0;parameters=[{name:"tokens",type:"array",items:{type:"string"},description:"Token symbols to match. 0 tokens = top pools by chosen sort. 1 token = pools containing it. 2 tokens = pools with that exact pair (both orderings tried). Wrapped variants (WETH\u2194ETH, WBTC\u2194BTC) auto-handled.",required:!1,default:[]},{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]},{name:"sortBy",type:"string",description:'Sort key: "tvl" (default), "volume", "apr", "fee" (low-to-high), or "liquidity".',required:!1,default:"tvl"},{name:"minTVL",type:"number",description:"Minimum TVL in USD.",required:!1},{name:"maxTVL",type:"number",description:"Maximum TVL in USD.",required:!1},{name:"minAPR",type:"number",description:"Minimum APR percentage (e.g. 5 for 5%).",required:!1},{name:"maxAPR",type:"number",description:"Maximum APR percentage.",required:!1},{name:"feeTier",type:"number",description:"Fee tier in basis points: 100, 500, 3000, or 10000.",required:!1},{name:"limit",type:"number",description:"Max pools to return after sort/filter. Default 5, max 20.",required:!1,default:5}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=(Array.isArray(e.tokens)?e.tokens:[]).filter(m=>typeof m=="string"&&m.trim().length>0).map(m=>m.trim().toUpperCase()),r=(Array.isArray(e.chains)?e.chains:[]).filter(m=>typeof m=="string"&&m.trim().length>0),o=r.filter(m=>!this.service.isSupported(m)),s=r.filter(m=>this.service.isSupported(m)).map(m=>this.service.resolveChain(m));if(s.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new z;let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],i=typeof e.sortBy=="string"&&Ks.includes(e.sortBy)?e.sortBy:"tvl",l={minTVL:typeof e.minTVL=="number"?e.minTVL:void 0,maxTVL:typeof e.maxTVL=="number"?e.maxTVL:void 0,minAPR:typeof e.minAPR=="number"?e.minAPR:void 0,maxAPR:typeof e.maxAPR=="number"?e.maxAPR:void 0,feeTier:typeof e.feeTier=="number"?e.feeTier:void 0},u=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),20):5;if(r.length>0&&s.length===0)return{_instructions:"The user requested chains we do not support via The Graph subgraph. Politely list the unsupported chains and the supported chains. Do not show any pool data.",unsupportedChains:o.map(L),supportedChains:this.service.listSupportedChains().map(L),pools:[],count:0};let p=(await Promise.allSettled(a.map(m=>this.service.searchPools({tokens:n,chain:m,sortBy:i,filters:l})))).flatMap(m=>m.status==="fulfilled"?m.value:[]);return i==="apr"?p.sort((m,g)=>(g.apr??0)-(m.apr??0)):i==="tvl"?p.sort((m,g)=>g.tvl-m.tvl):i==="volume"?p.sort((m,g)=>g.volume24hUsd-m.volume24hUsd):i==="fee"&&p.sort((m,g)=>m.feeTierBps-g.feeTierBps),p=p.slice(0,u),{_instructions:'Prefix the answer with: "According to the latest data from Subgraph Uniswap V3," (translate naturally into the user language; keep the word "Subgraph" as-is). Then list the pools with: pair (e.g. USDC/WETH), fee tier (%), TVL (USD), 24h volume (USD), APR (%). Format USD human-readably ($12.5M, $450K). Mention chain and sort metric briefly. If a pool has no APR (null), skip the APR field rather than showing zero. For EACH pool, render the provided uniswapUrl as a clickable markdown link \u2014 e.g. wrap the pair as "[USDC/WETH](uniswapUrl)" or append "[View on Uniswap](uniswapUrl)". Never omit this link. Do not invent URLs. If unsupportedChains is non-empty, mention them once at the end.',tokens:n,chains:a.map(L),unsupportedChains:o.map(L),sortBy:i,filters:l,count:p.length,pools:p.map(m=>this.formatPool(m))}}formatPool(e){return{pair:e.pair,poolAddress:e.poolAddress,chain:e.chain,feeTierBps:e.feeTierBps,feeTierPercent:e.feeTierPercent,tvlUsd:e.tvl,volume24hUsd:e.volume24hUsd,fees24hUsd:e.fees24hUsd,apr:e.apr,token0:e.token0,token1:e.token1,uniswapUrl:ue(e)}}};function ue(c){return`https://app.uniswap.org/explore/pools/${en[c.chain]||c.chain}/${c.poolAddress}`}var en={ethereum:"ethereum",arbitrum:"arbitrum",polygon:"polygon",bsc:"bnb",base:"base",optimism:"optimism",avalanche:"avalanche"};var Ct=class extends v{name="subgraph-trending-pools";description='List Uniswap V3 pools with the highest 24h volume on one or more chains via The Graph subgraphs. Use for: "what are trending pools?", "most active pools", "hot pools right now", "pools with biggest volume today". Returns pair, TVL, 24h volume, fee tier (%), APR (DefiLlama-enriched) for each. Supported chains: Ethereum (0x1), Arbitrum (0xa4b1), Polygon (0x89), BSC (0x38), Base (0x2105), Optimism (0xa), Avalanche (0xa86a).';category="blockchain-data";noSuggestions=!0;parameters=[{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]},{name:"limit",type:"number",description:"Max pools to return. Default 5, max 20.",required:!1,default:5}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=(Array.isArray(e.chains)?e.chains:[]).filter(u=>typeof u=="string"&&u.trim().length>0),r=n.filter(u=>!this.service.isSupported(u)),o=n.filter(u=>this.service.isSupported(u)).map(u=>this.service.resolveChain(u));if(n.length>0&&o.length===0)return{_instructions:"User requested unsupported chains. Politely list them and the supported chains; do not show pool data.",unsupportedChains:r,supportedChains:this.service.listSupportedChains(),pools:[],count:0};if(o.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new z;let s=o.length>0?o:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),20):5,l=(await Promise.allSettled(s.map(u=>this.service.getTrendingPools({chain:u})))).flatMap(u=>u.status==="fulfilled"?u.value:[]);return l.sort((u,d)=>d.volume24hUsd-u.volume24hUsd),l=l.slice(0,a),{_instructions:'Prefix the answer with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). List pools ranked by 24h volume. For each: pair, fee %, 24h volume, TVL, APR (skip if null). For EACH pool render the provided uniswapUrl as a clickable markdown link (e.g. wrap the pair as "[USDC/WETH](uniswapUrl)" or append "[View on Uniswap](uniswapUrl)"). Never omit it. Do not invent URLs.',chains:s,unsupportedChains:r,count:l.length,pools:l.map(u=>({pair:u.pair,poolAddress:u.poolAddress,chain:u.chain,feeTierBps:u.feeTierBps,feeTierPercent:u.feeTierPercent,tvlUsd:u.tvl,volume24hUsd:u.volume24hUsd,fees24hUsd:u.fees24hUsd,apr:u.apr,token0:u.token0,token1:u.token1,uniswapUrl:ue(u)}))}}};var Ut=class extends v{name="subgraph-pool-by-address";description='Fetch full pool detail (pair, fee tier, TVL, 24h volume + fees, APR, token addresses) for a specific Uniswap V3 pool by its on-chain address via The Graph subgraphs. Use whenever the user pastes a 0x pool address: "tell me about pool 0xabc\u2026", "details of this pool 0x\u2026".';category="blockchain-data";noSuggestions=!0;parameters=[{name:"poolAddress",type:"string",description:"0x-prefixed Uniswap V3 pool contract address (40 hex chars).",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Defaults to the connected chain when omitted.',required:!1}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=typeof e.poolAddress=="string"?e.poolAddress.trim():"";if(!/^0x[a-fA-F0-9]{40}$/.test(n))return{error:"This only supports Uniswap V3 pools. Provide a valid Uniswap V3 pool address (a 0x-prefixed 40-character hex address)."};let r=typeof e.chain=="string"&&e.chain.trim();if(!r&&!t?.chain&&t?.walletAddress)throw new z;let o=r?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(o))return{_instructions:"The requested chain is not supported by the Subgraph pool service. List the supported chains briefly.",unsupportedChains:[o],supportedChains:this.service.listSupportedChains()};let s=this.service.resolveChain(o),a=await this.service.getPoolByAddress({poolAddress:n,chain:s});return a?{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). Show pair, fee tier (%), TVL, 24h volume, 24h fees, APR (skip if null), and the pool address. End the response with the provided uniswapUrl as a clickable markdown link, e.g. "[View on Uniswap](uniswapUrl)". Never omit it. Do not invent URLs.',chain:s,pool:{pair:a.pair,poolAddress:a.poolAddress,chain:a.chain,feeTierBps:a.feeTierBps,feeTierPercent:a.feeTierPercent,tvlUsd:a.tvl,volume24hUsd:a.volume24hUsd,fees24hUsd:a.fees24hUsd,apr:a.apr,token0:a.token0,token1:a.token1,uniswapUrl:ue(a)}}:{error:`No pool found at ${n} on ${L(s)}.`}}};var Rt=class extends v{name="subgraph-pool-by-position-id";description='Return the pool underlying a Uniswap V3 NFT position id. Use when the user wants the pool stats for a numeric id WITHOUT the deposit/withdraw/fee history (e.g. "which pool is position 962961 in?", "show the pool of id 12345"). For full position history use subgraph-position-detail instead.';category="blockchain-data";noSuggestions=!0;parameters=[{name:"positionId",type:"string",description:"Numeric Uniswap V3 NFT position id as a string.",required:!0},{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=typeof e.positionId=="string"?e.positionId.trim():"";if(!/^\d+$/.test(n))return{error:"positionId must be a numeric string."};let r=(Array.isArray(e.chains)?e.chains:[]).filter(u=>typeof u=="string"&&u.trim().length>0),o=r.filter(u=>!this.service.isSupported(u)),s=r.filter(u=>this.service.isSupported(u)).map(u=>this.service.resolveChain(u));if(r.length>0&&s.length===0)return{_instructions:"Requested chains are not supported. List the supported chains.",unsupportedChains:o,supportedChains:this.service.listSupportedChains()};if(s.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new z;let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],l=(await Promise.allSettled(a.map(u=>this.service.getPoolByPositionId({positionId:n,chain:u})))).map(u=>u.status==="fulfilled"?u.value:null).find(u=>u!=null);return l?{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). Show the pool of that position: pair, fee %, TVL, 24h volume, APR. Mention the position id. End the response with the provided uniswapUrl as a clickable markdown link, e.g. "[View on Uniswap](uniswapUrl)". Never omit it. Do not invent URLs.',positionId:n,pool:{pair:l.pair,poolAddress:l.poolAddress,chain:l.chain,feeTierBps:l.feeTierBps,feeTierPercent:l.feeTierPercent,tvlUsd:l.tvl,volume24hUsd:l.volume24hUsd,fees24hUsd:l.fees24hUsd,apr:l.apr,token0:l.token0,token1:l.token1,uniswapUrl:ue(l)}}:{_instructions:"No pool found for this position id on the chosen chains. Ask the user to verify the id or chain.",positionId:n,chains:a,found:!1}}};var Et=class extends v{name="subgraph-position-detail";description=`Look up a Uniswap V3 liquidity position by its numeric NFT id (e.g. 962961) on one or more chains via The Graph subgraphs. Returns the position (deposited/withdrawn token amounts, collected fees, liquidity) AND its underlying pool (pair, fee tier, TVL, 24h volume, APR). Use when the user names a numeric id with phrases like: "position 12345", "pool id 962961", "tell me about position 962961". Does NOT support the user's wallet-wide LP list \u2014 only a specific id.`;category="blockchain-data";noSuggestions=!0;parameters=[{name:"positionId",type:"string",description:'Numeric Uniswap V3 NFT position id as a string (e.g. "962961").',required:!0},{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=typeof e.positionId=="string"?e.positionId.trim():"";if(!/^\d+$/.test(n))return{error:"positionId must be a numeric string (Uniswap V3 NFT id)."};let r=(Array.isArray(e.chains)?e.chains:[]).filter(d=>typeof d=="string"&&d.trim().length>0),o=r.filter(d=>!this.service.isSupported(d)),s=r.filter(d=>this.service.isSupported(d)).map(d=>this.service.resolveChain(d));if(r.length>0&&s.length===0)return{_instructions:"User asked about a position on chains we do not support. List unsupported + supported chains.",unsupportedChains:o,supportedChains:this.service.listSupportedChains()};if(s.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new z;let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],l=(await Promise.allSettled(a.map(d=>this.service.getPositionDetail({positionId:n,chain:d})))).map(d=>d.status==="fulfilled"?d.value:null).find(d=>d!=null);if(!l)return{_instructions:"No position with this id was found on the chosen chains. Suggest the user double-check the id or specify another chain.",positionId:n,chains:a,found:!1};let u=en[l.position.chain]||l.position.chain;return{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). Show the pair, chain, fee tier (%), deposited/withdrawn/collected fees for each token, plus pool stats (TVL, 24h volume, APR). End the response with TWO clickable markdown links from the data: position.uniswapPositionUrl as "[View position on Uniswap](uniswapPositionUrl)" and pool.uniswapUrl as "[View pool on Uniswap](uniswapUrl)". Never omit them. Do not invent URLs.',positionId:n,position:{...l.position,uniswapPositionUrl:`https://app.uniswap.org/positions/v3/${u}/${l.position.positionId}`},pool:{...l.pool,uniswapUrl:ue(l.pool)}}}};var Nt=class extends v{name="subgraph-coinpool-pairs";description='List CoinPool concentrated-liquidity price-range candidates for a token pair on a chain. Each entry shows: pair, fee tier, min/max price range, current price, and APR within that range (NOT the overall pool APR \u2014 strictly per-range). Use ONLY for queries about price ranges or which range to pick: "what price range should I use for ETH/USDC?", "best range for USDC/WETH on Arbitrum", "g\u1EE3i \xFD kho\u1EA3ng gi\xE1 ETH/USDC". Do NOT use for general "show me ETH/USDC pools" \u2014 use subgraph-search-pools for that.';category="blockchain-data";noSuggestions=!0;parameters=[{name:"tokens",type:"array",items:{type:"string"},description:'Two token symbols (e.g. ["ETH","USDC"]).',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Defaults to the connected chain when omitted.',required:!1},{name:"limit",type:"number",description:"Max entries to return (sorted by APR desc). Default 10.",required:!1,default:10}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=(Array.isArray(e.tokens)?e.tokens:[]).filter(u=>typeof u=="string"&&u.trim().length>0).map(u=>u.trim().toUpperCase());if(n.length<2)return{error:'tokens must contain two token symbols (e.g. ["ETH","USDC"]).'};let r=typeof e.chain=="string"&&e.chain.trim();if(!r&&!t?.chain&&t?.walletAddress)throw new z;let o=r?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(o))return{_instructions:"Requested chain not supported. Mention supported chains.",unsupportedChains:[o],supportedChains:this.service.listSupportedChains()};let s=this.service.resolveChain(o),a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),30):10,i=await this.service.getCoinPoolPairs({chain:s,tokens:n});if(i.length===0)return{_instructions:"No CoinPool range data is available for this pair on this chain. Tell the user this nicely and suggest using general pool data instead (via subgraph-search-pools).",tokens:n,chain:s,pairs:[],count:0};let l=[...i].sort((u,d)=>(d.apr??-1/0)-(u.apr??-1/0)).slice(0,a);return{_instructions:'CRITICAL: APR in each entry is the APR for that SPECIFIC price range only \u2014 NEVER present it as the overall pool APR. Always show the min\u2013max price together with the APR, e.g. "APR X% (range: 1800 \u2013 2200 ETH/USDC)". Sort by APR descending; explain that the top entry is the best concentrated range but narrower = higher out-of-range risk.',tokens:n,chain:s,count:l.length,pairs:l}}};function ye(c){if(typeof c=="number")return Number.isFinite(c)&&c>0?{kind:"token",value:c,raw:String(c)}:null;if(typeof c!="string")return null;let e=c.trim();if(!e)return null;if(/^(?:max(?:imum)?|all|everything|tất\s*cả|toàn\s*bộ)$/i.test(e))return{kind:"percent",percent:100,raw:e};let t=e.match(/^%?\s*([0-9]*\.?[0-9]+)\s*%$|^%\s*([0-9]*\.?[0-9]+)$/);if(t){let o=parseFloat(t[1]??t[2]);return Number.isFinite(o)&&o>0?{kind:"percent",percent:o,raw:e}:null}let n=e.match(/^\$\s*([0-9]*\.?[0-9]+)$|^([0-9]*\.?[0-9]+)\s*\$$|^(?:usd)\s*([0-9]*\.?[0-9]+)$|^([0-9]*\.?[0-9]+)\s*usd$/i);if(n){let o=parseFloat(n[1]??n[2]??n[3]??n[4]);return Number.isFinite(o)&&o>0?{kind:"usd",usd:o,raw:e}:null}let r=e.match(/^([0-9]*\.?[0-9]+)(?:\s+[a-zA-Z][a-zA-Z0-9]*)?$/);if(r){let o=parseFloat(r[1]);return Number.isFinite(o)&&o>0?{kind:"token",value:o,raw:e}:null}return null}function Vo(c,e){switch(c.kind){case"token":return{ok:!0,amount:c.value};case"percent":return e.balance===void 0||!Number.isFinite(e.balance)||e.balance<=0?{ok:!1,reason:"no_balance"}:{ok:!0,amount:e.balance*c.percent/100};case"usd":return e.usdPrice===void 0||!Number.isFinite(e.usdPrice)||e.usdPrice<=0?{ok:!1,reason:"no_price"}:{ok:!0,amount:c.usd/e.usdPrice}}}var Ys={send:30000n,swap:1000000n},js={"0x1":["https://ethereum-rpc.publicnode.com","https://eth.drpc.org","https://1rpc.io/eth"],"0xa":["https://optimism-rpc.publicnode.com","https://optimism.drpc.org"],"0x38":["https://bsc-rpc.publicnode.com","https://bsc.drpc.org"],"0x89":["https://polygon-bor-rpc.publicnode.com","https://polygon.drpc.org"],"0x2105":["https://base-rpc.publicnode.com","https://base.drpc.org"],"0xa4b1":["https://arbitrum-one-rpc.publicnode.com","https://arbitrum.drpc.org"],"0xa86a":["https://avalanche-c-chain-rpc.publicnode.com","https://avalanche.drpc.org"],"0xe708":["https://linea-rpc.publicnode.com","https://linea.drpc.org"]};async function zs(c){let e=c.toLowerCase(),n=[kt(c),...js[e]??[]].filter(r=>!!r);for(let r of n)try{let o=await xe(r,"eth_gasPrice",[]),s=BigInt(o);if(s>0n)return s}catch{}return 0n}async function It(c,e){let t=await zs(c);return t<=0n?0n:Ys[e]*t}function Dt(c,e,t){if(e<=0n)return c;let n=ee(t)?.native.decimals;if(n===void 0)return c;let r=Number(e)/10**n;return!Number.isFinite(r)||r<=0?c:Math.max(0,c-r)}var ae=class extends v{kind="action";category="wallet-action";noSuggestions=!0;userInputFields=[];moralis;constructor(e){super(),e!==void 0&&(this.moralis=new E(e))}async resolveContractAddress(e,t,n){if(!this.moralis||!e)return;let r=e.trim().toLowerCase(),o=B(e.trim()),s=i=>o?(i.token_address??"").toLowerCase()===r:(i.symbol??"").toLowerCase()===r;if(n){let i=await this.moralis.getWalletTokenBalances({address:n,chain:t});if(i.success&&i.data?.result?.length){let l=i.data.result.find(s);if(l)return{address:l.token_address,symbol:l.symbol,name:l.name,decimals:typeof l.decimals=="number"?l.decimals:void 0}}}let a=await this.moralis.searchTokensByKey({key:e,chain:t});if(a.success&&a.data?.length){let l=a.data.find(s)??a.data[0];if(l?.token_address)return{address:l.token_address,symbol:l.symbol,name:l.name,decimals:typeof l.decimals=="number"?l.decimals:void 0}}}parseDecimals(e){if(typeof e=="number"&&Number.isInteger(e)&&e>=0)return e;if(typeof e=="string"&&e.trim()){let t=parseInt(e,10);if(Number.isInteger(t)&&t>=0)return t}}async resolveTokenRef(e){let{contractArg:t,symbolArg:n,decimalsArg:r,chain:o,walletAddress:s}=e,a=this.parseDecimals(r),i=typeof n=="string"&&n.trim()?n.trim():void 0,l=this.normaliseAddress(t),u=typeof t=="string"&&t.trim()?t.trim():void 0,d=l??i??u;if(!d)return{token:void 0};let p=await this.resolveContractAddress(d,o,s);return p?{token:{address:p.address,symbol:i??p.symbol,name:p.name,decimals:a??p.decimals}}:l?{token:{address:l,symbol:i,decimals:a}}:{error:"token_not_found",key:d}}requireWallet(e){let t=e?.walletAddress;return t?{ok:!0,address:t}:{ok:!1,payload:{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before submitting the action. Do NOT render the form."}}}requireChain(e,t){return lo(e.chain,t)}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return B(t)?t:null}normaliseAmount(e){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return String(e);if(typeof e!="string")return null;let t=e.trim();if(!t)return null;let n=parseFloat(t);return Number.isFinite(n)&&n>0?t:null}async resolveAmountInput(e){let{rawAmount:t,tokenAddress:n,chain:r,walletAddress:o,gasKind:s}=e,a=ye(t);if(!a)return null;let i=e.symbol??"the token",l=n.toLowerCase()==="native",u=!!s&&l&&!!r;if(a.kind==="token")return{amount:this.toPlainDecimal(a.value)};let d=await this.readTokenBalanceAndPrice(n,r,o),p=d?.balance;u&&d?.balance!==void 0&&(p=await this.computeNativeSpendable(r,s,d.balance));let m=Vo(a,{balance:p,usdPrice:d?.usdPrice});return m.ok?{amount:this.toPlainDecimal(m.amount)}:m.reason==="no_balance"?{error:"amount_balance_unavailable",_instructions:`Could not read the user's ${i} balance to work out ${a.raw} right now. Tell them, in their language, that the balance couldn't be fetched at the moment and ask them to enter an exact ${i} amount instead. Do NOT invent a balance or amount. Do NOT mention tool names, UI, or forms.`}:{error:"amount_price_unavailable",_instructions:`Could not get a USD price for ${i} to work out ${a.raw} right now. Tell them, in their language, that the price couldn't be fetched at the moment and ask them to enter an exact ${i} amount instead. Do NOT invent a price or amount. Do NOT mention tool names, UI, or forms.`}}async computeNativeSpendable(e,t,n){let r=await It(e,t);return Dt(n,r,e)}async resolveNativeSpendableFormatted(e,t,n){if(!(!e||!t))try{let r=await this.readTokenBalanceAndPrice("native",e,t);if(r?.balance===void 0)return;let o=await this.computeNativeSpendable(e,n,r.balance);return this.toPlainDecimal(o)}catch{return}}async readTokenBalanceAndPrice(e,t,n){if(!this.moralis||!n)return;let r=await this.moralis.getWalletTokenBalances({address:n,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[],s=e.toLowerCase()==="native",a=e.toLowerCase(),i=o.find(u=>s?u.native_token===!0:(u.token_address??"").toLowerCase()===a);if(!i)return;let l=i.balance_formatted!=null?Number(i.balance_formatted):void 0;return{balance:Number.isFinite(l)?l:void 0,usdPrice:typeof i.usd_price=="number"&&i.usd_price>0?i.usd_price:void 0,balanceFormatted:i.balance_formatted,symbol:i.symbol}}cleanAmountString(e){if(!/e/i.test(e))return e;let t=Number(e);return Number.isFinite(t)?this.toPlainDecimal(t):e}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),l=a+i,u;if(s>=0){let d=a.length+s;u=d>=l.length?l.padEnd(d,"0"):`${l.slice(0,d)}.${l.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${l}`;return u.includes(".")&&(u=u.replace(/\.?0+$/,"")),n?`-${u}`:u}async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),o&&Array.isArray(o.actionButtons)&&o.actionButtons.length>0&&(s.actionButtons=o.actionButtons),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){let n=this.requireWallet(t);if(!n.ok)return n.payload;let r=He(e.chain,t);if(r){let h=this.actionType.replace(/_/g," ");return{error:"wrong_chain",_instructions:`The user asked to ${h} on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to ${h} on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can ${h} on ${r.connectedLabel} (their current network) instead. Do NOT render the form. Do NOT mention tool names, UI, or forms.`}}let o=this.requireChain(e,t);if(!o)return{error:"missing_chain",_instructions:'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.'};let s=await this.buildParameters(e,t);if(s&&typeof s=="object"&&"error"in s&&typeof s.error=="string"||s&&typeof s=="object"&&"actionButtons"in s&&Array.isArray(s.actionButtons)||s&&typeof s=="object"&&"ui"in s&&s.ui&&typeof s.ui=="object"&&typeof s.ui.component=="string")return s;let a={action:this.actionType,chainId:o,walletAddress:n.address,parameters:s},i={component:this.component,props:a},l=a.parameters,u=h=>{let y=l[h];return y!=null&&y!==""},d=this.userInputFields.filter(h=>!(Array.isArray(h.key)?h.key:[h.key]).some(u)).map(h=>h.label),p=this.userInputFields.map(h=>{let k=(Array.isArray(h.key)?h.key:[h.key]).find(u);return k?`${h.label}: ${l[k]}`:null}).filter(h=>h!=null),m=this.actionType.replace(/_/g," "),g='NEVER invent, guess, or insert a placeholder (e.g. "[recipient address]") for any detail the user has not actually provided \u2014 only state values listed as already provided. Do NOT mention tool names, UI, or forms.',f=d.length>0?`The ${m} is in progress but still needs input from the user. Ask the user, in their language, to provide: ${d.join(", ")}. `+(p.length>0?`Already provided: ${p.join("; ")}. `:"")+`Do NOT say "review and confirm" yet \u2014 there are still missing details to collect. ${g} Wait for the user to provide the missing details before proceeding.`:`The ${m} has all required details. In the user's language, ask the user to review and confirm to complete the ${m}. `+(p.length>0?`Details provided: ${p.join("; ")}. `:"")+`${g} Wait for the user to confirm or edit before proceeding.`;return{ui:i,action:this.actionType,chainId:o,parameters:a.parameters,_instructions:f}}};var Lt=class extends ae{name="open-send-native-form";actionType="send_native";component="SendNativeForm";userInputFields=[{key:"to_address",label:"the recipient address"},{key:"amount",label:"the amount to send"}];constructor(e){super(e)}async run(e,t){if(e.chain==null||e.chain===""){let n=co(e.native_symbol);if(n)return super.run({...e,chain:n},t)}return super.run(e,t)}description=`Open the SEND NATIVE form for the chain's native coin (ETH/BNB/MATIC/AVAX/\u2026). Use for ANY native-send intent: "send eth", "send 0.1 ETH", "transfer 1 BNB to vitalik.eth", "g\u1EEDi ETH". Call EVEN IF the user did not name an amount or recipient \u2014 the form prompts for them. Do NOT use for ERC-20 tokens \u2014 use open-send-token-form instead.`;parameters=[{name:"chain",type:"string",description:'Hex chain id: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. Defaults to connected chain.',required:!1},{name:"native_symbol",type:"string",description:'The native coin the user named to send, VERBATIM (e.g. "ETH", "BNB", "MATIC", "AVAX"). ALWAYS set this when the user names the coin ("send bnb" \u2192 native_symbol="BNB"); leave blank only when they say just "send" with no coin. A coin that belongs to one specific chain (BNB\u2192BSC, MATIC/POL\u2192Polygon, AVAX\u2192Avalanche) selects that chain, so the tool can detect when the wallet is connected elsewhere.',required:!1},{name:"to_address",type:"string",description:"Recipient EVM address (0x\u2026). Omit when unknown \u2014 the form will prompt the user.",required:!1},{name:"amount",type:"string",description:'Amount of native coin to send. Accepted forms: a plain amount ("0.1"); a PERCENT of their balance ("50%"); the literal word "max" for the WHOLE balance; or a USD value ("5$", "$5", "5 usd"). NORMALIZE natural-language phrasing in ANY language to one of these before passing it: any "send everything / the whole balance / maximum" phrasing \u2014 regardless of language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") \u2014 MUST become the literal "max"; any "half" phrasing ("half", "m\u1ED9t n\u1EEDa", "\u4E00\u534A", "\u534A\u5206") MUST become "50%". Pass plain/percent/USD numbers VERBATIM. The tool converts percent/usd/max into a concrete amount itself (and for the native coin always leaves enough to cover the network fee). Omit when unknown.',required:!1}];async buildParameters(e,t){let n={},r=this.requireChain(e,t)??void 0,o=t?.walletAddress??void 0,s=await this.readTokenBalanceAndPrice("native",r,o);if(!s||s.balance==null||s.balance<=0){let l=s?.symbol||"the native coin";return{error:"no_native_balance",_instructions:`The user wants to send ${l} but their wallet holds 0 ${l} on this chain. Tell them, in their language, that they do not have any ${l} to send and suggest they top up first. Do NOT open the form. Do NOT mention tool names, UI, or forms.`}}if(e.to_address!=null){let l=this.normaliseAddress(e.to_address);l?n.to_address=l:typeof e.to_address=="string"&&e.to_address.trim()&&(n.to_address=e.to_address.trim())}let a=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:"native",symbol:"the native coin",chain:r,walletAddress:o,gasKind:"send"});if(a&&"error"in a)return a;a&&(n.amount=a.amount);let i=await this.resolveNativeSpendableFormatted(r,o,"send");return i!==void 0&&(n.spendable=i),n}};var Mt=class extends ae{name="open-send-token-form";actionType="send_token";component="SendTokenForm";userInputFields=[{key:"to_address",label:"the recipient address"},{key:"amount",label:"the amount to send"}];constructor(e){super(e)}description=`Open the SEND TOKEN (ERC-20) form. Use for ANY ERC-20 send/transfer intent: "send token", "send USDC", "transfer 50 DAI to 0x\u2026", "g\u1EEDi 10 USDT". Call this tool EVEN IF the user did not name a token, amount, or recipient \u2014 the tool handles missing token via a picker, the form prompts for the rest. Do NOT use for the chain's native coin (ETH/BNB/MATIC/AVAX) \u2014 use open-send-native-form instead.`;parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"contract_address",type:"string",description:"ERC-20 token contract address (0x\u2026). Pass when known. Otherwise leave empty and provide token_symbol \u2014 the tool will look up the address.",required:!1},{name:"token_symbol",type:"string",description:'Token symbol (e.g. "USDC"). ALWAYS set this whenever the user names a token to send ("send usdc" \u2192 token_symbol="USDC"); only leave it blank when the user names NO token at all ("send token"), so the tool can show a wallet picker. Used to auto-resolve the contract address and to display the symbol in the form. (Pass either this or contract_address.)',required:!1},{name:"decimals",type:"number",description:"Token decimals (e.g. 6 for USDC, 18 for most ERC-20s). Optional.",required:!1},{name:"to_address",type:"string",description:"Recipient EVM address (0x\u2026). Omit when unknown.",required:!1},{name:"amount",type:"string",description:'Amount of the token to send. Accepted forms: a plain amount ("100"); a PERCENT of their balance ("50%"); the literal word "max" for the WHOLE balance; or a USD value ("5$", "$5", "5 usd"). NORMALIZE natural-language phrasing in ANY language to one of these before passing it: any "send everything / the whole balance / maximum" phrasing \u2014 regardless of language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") \u2014 MUST become "max"; any "half" phrasing ("half", "m\u1ED9t n\u1EEDa", "\u4E00\u534A", "\u534A\u5206") MUST become "50%". Pass plain/percent/USD numbers VERBATIM. The tool converts percent/usd/max into a concrete amount itself. Omit when unknown.',required:!1},{name:"send_prompt_template",type:"string",description:`A short imperative "send" command IN THE USER'S LANGUAGE, used as the label/command when the user has not yet picked which token to send (token-picker buttons). Write it in the SAME language the user is using right now. Use these exact placeholders (do not translate the braces): "{symbol}" for the token, "{amount}" for the amount, "{to}" for the recipient address. Put the localized verb and the "to" preposition directly in the text. English example: "send {amount} {symbol} to {to}". Vietnamese example: "g\u1EEDi {amount} {symbol} \u0111\u1EBFn {to}". Japanese example: "{to} \u306B {amount} {symbol} \u3092\u9001\u308B". Always include {symbol}. Include {amount}/{to} too \u2014 the tool removes any whose value is unknown.`,required:!0}];async buildParameters(e,t){let n=this.normaliseAddress(e.contract_address)!=null,r=typeof e.token_symbol=="string"&&e.token_symbol.trim().length>0;if(!n&&!r&&this.moralis){let u=this.requireChain(e,t)??void 0,d=t?.walletAddress;if(d){let p=await this.moralis.getWalletTokenBalances({address:d,chain:u,excludeSpam:!0,excludeUnverifiedContracts:!0}),g=(p.success?p.data?.result??[]:[]).filter(w=>w.symbol&&(w.native_token||w.token_address)).sort((w,b)=>(b.usd_value??0)-(w.usd_value??0)).slice(0,8);if(g.length===0)return{error:"no_token_holdings",_instructions:"The wallet does not hold any sendable tokens on this chain. Tell the user they have nothing to send and suggest they top up first. Do NOT open the form."};let f=e.to_address!=null?this.normaliseAddress(e.to_address):null,h=this.normaliseAmount(e.amount),y=typeof e.send_prompt_template=="string"?e.send_prompt_template:"";return{actionButtons:g.map(w=>{let b=w.symbol,T=w.balance_formatted?this.cleanAmountString(w.balance_formatted):"",P=w.name?w.name:"",S=w.usd_value?`($${w.usd_value.toFixed(2)})`:"";return{label:`${P}:${T} ${b} ${S}`.trim(),prompt:this.buildSendPrompt(y,b,h,f)}}),_instructions:"Reply briefly in the user's language with two short sentences using the pattern 'state intent \u2192 ask to choose': first state what the user wants to do (e.g., 'B\u1EA1n mu\u1ED1n g\u1EEDi token.' / 'You want to send a token.'), then ask them to choose which token from the options below (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n g\u1EEDi.' / 'Please choose the token you want to send.'). Do NOT list the tokens in text \u2014 the options already show them. Do NOT mention tool names, UI, or forms."}}}let o=this.requireChain(e,t)??void 0,s=t?.walletAddress??void 0,a=await this.findOwnedTokenForSend(e,o,s);if(a&&"error"in a)return a;let i=a?.token,l={};if(i?.address&&(l.contract_address=i.address),i?.symbol&&(l.token_symbol=i.symbol),i?.decimals!==void 0&&(l.decimals=String(i.decimals)),e.to_address!=null){let u=this.normaliseAddress(e.to_address);u?l.to_address=u:typeof e.to_address=="string"&&e.to_address.trim()&&(l.to_address=e.to_address.trim())}if(i?.address){let u=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:i.address,symbol:i.symbol,chain:o,walletAddress:s});if(u&&"error"in u)return u;u&&(l.amount=u.amount)}else{let u=this.normaliseAmount(e.amount);u&&(l.amount=u)}return l}async findOwnedTokenForSend(e,t,n){let r=this.normaliseAddress(e.contract_address),o=typeof e.token_symbol=="string"&&e.token_symbol.trim()?e.token_symbol.trim():void 0,s=r??o;if(!this.moralis||!n||!s){let m=await this.resolveTokenRef({contractArg:e.contract_address,symbolArg:e.token_symbol,decimalsArg:e.decimals,chain:t,walletAddress:n});return"error"in m?{error:"token_not_found",symbol:m.key,_instructions:`Could not resolve token "${m.key}" to a contract address on this chain. Ask the user to provide the token contract directly.`}:{token:m.token}}let a=s.toLowerCase(),i=r!=null,l=await this.moralis.getWalletTokenBalances({address:n,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0}),d=(l.success?l.data?.result??[]:[]).find(m=>i?(m.token_address??"").toLowerCase()===a:(m.symbol??"").toLowerCase()===a);if(d&&d.balance&&!/^0*$/.test(d.balance.trim())){let m=this.parseDecimals(e.decimals);return{token:{address:d.token_address,symbol:d.symbol??o,name:d.name,decimals:m??(typeof d.decimals=="number"?d.decimals:void 0)}}}let p=d?.symbol??o??s;return{error:"no_balance",symbol:p,_instructions:`The user does not hold any ${p} in their wallet on this chain, so there is nothing to send. Tell them (in their language) that they don't have any ${p} to send, and suggest they top up or pick another token. Do NOT open a form. Do NOT mention tool names, UI, or forms.`}}buildSendPrompt(e,t,n,r){let s=(e&&e.includes("{symbol}")?e:"send {amount} {symbol} to {to}").replace(/\{symbol\}/g,t).replace(/\{amount\}/g,n??"");return r?s=s.replace(/\{to\}/g,r):s=s.replace(/\s*\S+\s*\{to\}/g,"").replace(/\{to\}\s*\S+\s*/g,"").replace(/\{to\}/g,""),s.replace(/\s+/g," ").trim()}};var Bt=class extends ae{name="open-approve-token-form";actionType="approve_token";component="ApproveTokenForm";userInputFields=[{key:"spender_address",label:"the spender address to approve"}];constructor(e){super(e)}description='Open the APPROVE TOKEN form so the user can grant a spender allowance on an ERC-20 token. Use when the user says "approve USDC for Uniswap", "c\u1EA5p quy\u1EC1n 100 USDT cho 0x\u2026", "revoke approval". Pass `contract_address` when you have it; otherwise pass `token_symbol` (e.g. "USDC") and the tool auto-resolves the contract. Always pass `spender_address` (the contract being approved). Pass `amount` for a specific allowance; omit for unlimited (max uint256). For approving 0 to revoke, pass amount="0".';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"contract_address",type:"string",description:"ERC-20 token contract address (0x\u2026). Pass when known. Otherwise leave empty and provide token_symbol.",required:!1},{name:"token_symbol",type:"string",description:'Token symbol (e.g. "USDC"). Required when contract_address is not given. Used to auto-resolve the contract.',required:!1},{name:"decimals",type:"number",description:"Token decimals.",required:!1},{name:"spender_address",type:"string",description:"EVM address of the spender contract being approved (0x\u2026). Omit when unknown \u2014 the form will prompt the user.",required:!1},{name:"amount",type:"string",description:'Human-readable allowance amount. Omit for UNLIMITED (max uint256). Pass "0" to revoke.',required:!1}];async buildParameters(e,t){let n=this.requireChain(e,t)??void 0,r=await this.resolveTokenRef({contractArg:e.contract_address,symbolArg:e.token_symbol,decimalsArg:e.decimals,chain:n,walletAddress:t?.walletAddress??void 0});if("error"in r)return{error:"token_not_found",symbol:r.key,_instructions:`Could not resolve token "${r.key}" to a contract address on this chain. Ask the user to provide the token contract directly.`};let o=r.token,s=this.normaliseAddress(e.spender_address),a={};if(o?.address&&(a.contract_address=o.address),s&&(a.spender_address=s),o?.symbol&&(a.token_symbol=o.symbol),o?.decimals!==void 0&&(a.decimals=String(o.decimals)),typeof e.amount=="string"&&e.amount.trim()){let i=e.amount.trim(),l=parseFloat(i);Number.isFinite(l)&&l>=0&&(a.amount=i)}else typeof e.amount=="number"&&Number.isFinite(e.amount)&&e.amount>=0&&(a.amount=String(e.amount));return a}};var Ot=class extends v{name="open-buy-token-form";kind="action";category="wallet-action";noSuggestions=!0;moralis;pantograph;debug;constructor(e,t){super(),e!==void 0&&(this.moralis=new E(e),this.pantograph=new Z({baseUrl:e.pantographUrl})),this.debug=t?.debug??!1}dbg(e,t){this.debug&&(t===void 0?console.log(`[BuyTokenTool] ${e}`):console.log(`[BuyTokenTool] ${e}`,JSON.stringify(t)))}description='Open the BUY TOKEN flow for ANY buy/purchase intent. Handles both cases: (a) the user NAMED a token \u2014 "buy PEPE", "mua DOGE", "buy 100 USDC with ETH": pass `token_symbol` (the destination); if you also know the pay token, fill `pay_with_symbol` (or `pay_with_address`), else leave pay_with_* blank and the tool returns a wallet picker. (b) the user did NOT name a token \u2014 "buy a trending token", "mua token trending", "buy something pumping", "buy hot tokens", "mua token t\u0103ng m\u1EA1nh": leave `token_symbol` blank and the tool returns buttons of the chain\'s current trending tokens so the user picks one. Amount: `buy_amount` = destination to RECEIVE; `pay_with_amount` = payment to SPEND. Never both.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"token_symbol",type:"string",description:'Symbol of the token the user wants to BUY (e.g. "PEPE"). Leave EMPTY when the user did not name a token (e.g. "buy a trending token") \u2014 the tool then returns trending tokens to pick from.',required:!1},{name:"contract_address",type:"string",description:`Destination token address (0x\u2026), or "native" for the chain's native coin. Leave empty when unknown \u2014 the tool resolves it from token_symbol.`,required:!1},{name:"buy_amount",type:"string",description:`Quantity of the DESTINATION token (the one being bought) the user wants to RECEIVE. Set this when the amount sits next to the token being bought: "buy 5 PEPE", "mua 5 PEPE", "buy 0.02 PCM with USDC" \u2192 buy_amount = the number next to PEPE/PCM. May also be a USD value when the user sizes the buy in dollars ("buy $5 of PEPE" \u2192 "5$"); pass the user's expression VERBATIM (the tool converts "$" to a token quantity). Mutually exclusive with pay_with_amount \u2014 set at most ONE of the two.`,required:!1},{name:"pay_with_symbol",type:"string",description:'Symbol of the payment token (e.g. "USDC", "ETH").',required:!1},{name:"pay_with_address",type:"string",description:`Address of the payment token (0x\u2026), OR "native" for the chain's native coin.`,required:!1},{name:"pay_with_amount",type:"string",description:'Quantity of the PAYMENT token (the one being spent) the user wants to SPEND. Set this when the amount sits next to the payment token: "buy PEPE with 10 USDC", "mua PEPE b\u1EB1ng 10 USDC", "spend 0.5 ETH on DOGE" \u2192 pay_with_amount = the number next to USDC/ETH. May also be a PERCENT of the pay-token balance ("buy USDC with 50% USDT" \u2192 "50%"), the literal word "max" meaning the WHOLE balance, or a USD value ("buy USDC with 5$ USDT" \u2192 "5$"). NORMALIZE any "whole balance / everything / maximum" phrasing in ANY language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") to the literal "max"; pass plain/percent/USD numbers VERBATIM (the tool converts "%"/"$"/"max"). "max" is an AMOUNT, never a token \u2014 still set pay_with_symbol to the payment token ("ETH" in "buy USDC with max ETH"). Mutually exclusive with buy_amount \u2014 set at most ONE of the two.',required:!1},{name:"limit",type:"number",description:"Only used when no token is named: max number of trending tokens to show as buttons. Default 6. Clamped to [1, 10].",required:!1},{name:"buy_prompt_template",type:"string",description:`A short "buy" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the trending-token picker (when the user named no token). Use the exact placeholder "{token}" (do not translate the braces) for the token symbol; put the localized verb directly in the text. English example: "buy {token}". Vietnamese example: "mua {token}". Japanese example: "{token} \u8CFC\u5165". Always include {token}.`,required:!0},{name:"buy_with_prompt_template",type:"string",description:`A short "buy X with Y" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the pay-token picker and the percentage-spend buttons. Use these exact placeholders (do not translate the braces): "{token}" = token being bought, "{pay}" = payment token, "{buy_amount}" = quantity of the BOUGHT token (place it next to {token}), "{pay_amount}" = quantity of the PAYMENT token (place it next to {pay}). Put the localized verb and the "with" preposition directly in the text. English example: "buy {buy_amount} {token} with {pay_amount} {pay}". Vietnamese example: "mua {buy_amount} {token} b\u1EB1ng {pay_amount} {pay}". Japanese example: "{pay_amount} {pay} \u3067 {buy_amount} {token} \u8CFC\u5165". Always include {token} and {pay}. Include both {buy_amount} and {pay_amount} too \u2014 the tool removes whichever amount is unknown (only one side ever carries an amount).`,required:!0}];async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),o&&Array.isArray(o.actionButtons)&&o.actionButtons.length>0&&(s.actionButtons=o.actionButtons),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){if(!t?.walletAddress)return{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before buying. Do NOT proceed."};let r=He(e.chain,t);return r?{error:"wrong_chain",_instructions:`The user asked to buy on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to buy on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can buy on ${r.connectedLabel} (their current network) instead. Do NOT proceed. Do NOT mention tool names, UI, or forms.`}:this.requireChain(e,t)?this.buildResult(e,t):{error:"missing_chain",_instructions:'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.'}}async buildResult(e,t){this.correctAmountSide(e,t);let n=typeof e.token_symbol=="string"?e.token_symbol.trim():"",r=typeof e.contract_address=="string"?e.contract_address.trim():"",o=this.requireChain(e,t)??void 0,s=t?.walletAddress??void 0;if(this.dbg("buildResult:args",{args:e,chain:o,walletAddress:s,hasMoralis:!!this.moralis}),!n&&!r)return this.dbg("\u2192 Branch 0: trending picker (no token named)"),await this.buildTrendingPicker(e,t);let a=await this.resolveSide(e.contract_address,n,o,s,"dest"),i=a.address,l=a.symbol??n,u=a.decimals,d=await this.resolveSide(e.pay_with_address,e.pay_with_symbol,o,s,"pay"),p=d.address,m=d.symbol,g=d.decimals,f=await this.resolveBuyAmountSpec(e.buy_amount,i,o,l),h=p?await this.resolvePayAmountSpec(e.pay_with_amount,p,o,s,m):this.normaliseAmount(e.pay_with_amount),y=ye(e.pay_with_amount),k=!!y&&y.kind!=="token",w=null;if(this.dbg("resolved sides",{dest:{destContract:i,destSymbol:l,destDecimals:u},pay:{payAddr:p,paySymbol:m,payDecimals:g},buyAmount:f,payAmount:h}),!i)return this.dbg("\u2192 dest token not resolved"),{error:"dest_token_not_found",_instructions:`Could not find the token "${l||r}" to buy on this chain. Tell the user, in their language, that the token could not be found and ask them to check the name or provide its contract address. Do NOT invent an address. Do NOT mention tool names, UI, or forms.`};if(!p&&this.moralis&&t?.walletAddress)return this.dbg("\u2192 Branch 1: pay picker (pay token missing)"),await this.buildPayWithPicker({args:e,userContext:t,destSymbol:l,destContract:i,buyAmount:f,payAmount:h});if(!p)return{error:"pay_token_missing",_instructions:`Ask the user, in their language, which token they want to spend to buy ${l}. Do NOT invent a token. Do NOT mention tool names, UI, or forms.`};if(f&&!h){this.dbg("Branch 1.3: deriving pay amount from buy amount",{buyAmount:f,destSymbol:l,paySymbol:m});let b=await this.derivePayAmountFromBuy({chain:o,buyAmount:f,destContract:i,destSymbol:l,payAddr:p,paySymbol:m});if(!b)return this.dbg("\u2192 Branch 1.3: price unavailable \u2014 needs pay amount"),{error:"needs_pay_amount",_instructions:`Could not work out how much ${m??"of the payment token"} equals ${f} ${l} right now. Tell the user, in their language, that the price couldn't be fetched at the moment, and ask how much ${m??"of the payment token"} they want to spend instead. Do NOT invent an amount or a price. Do NOT mention tool names, UI, or forms.`};h=b,w=f,k=!1,this.dbg("\u2192 Branch 1.3: derived pay amount",{payAmount:h,derivedFromBuy:w})}if(this.moralis&&t?.walletAddress){this.dbg("Branch 1.4: checking pay balance",{payAddr:p,paySymbol:m,payAmount:h});let b=await this.checkPayBalance({args:e,userContext:t,chain:o,destSymbol:l,destContract:i,payAddr:p,paySymbol:m,payAmount:h,buyAmount:f,derivedFromBuy:w,amountSizedToBalance:k});if(b)return this.dbg("\u2192 Branch 1.4: balance shortfall \u2014 returning",{error:b.error,hasButtons:Array.isArray(b.actionButtons)}),b;this.dbg("Branch 1.4: balance OK \u2014 continuing")}if(!f&&!h&&this.moralis&&t?.walletAddress){this.dbg("Branch 1.5: building amount picker");let b=await this.buildAmountPicker({userContext:t,chain:o,destSymbol:l,payAddr:p,paySymbol:m,buyWithTemplate:typeof e.buy_with_prompt_template=="string"?e.buy_with_prompt_template:""});if(b)return this.dbg("\u2192 Branch 1.5: amount picker returned"),b;this.dbg("Branch 1.5: amount picker null \u2014 needs amount")}if(h&&(g!==void 0||p==="native")){this.dbg("Branch 1.75: building confirm tx");let b=await this.buildConfirmTx({userContext:t,chain:o,destContract:i,destSymbol:l,destDecimals:u,payAddr:p,paySymbol:m,payDecimals:g,payAmount:h});return b&&"ui"in b?(this.dbg("\u2192 Branch 1.75: confirm tx returned"),b):b&&"quoteError"in b?(this.dbg("\u2192 Branch 1.75: quote error from provider",{quoteError:b.quoteError}),{error:"quote_failed",quoteError:b.quoteError,_instructions:`Could not quote buying ${l} with ${m??"the selected token"}. The swap provider reported: "${b.quoteError}". Tell the user, in their language, exactly that reason (translate the wording, keep any numbers/limits verbatim) and suggest they adjust the amount or try again shortly. Do NOT invent numbers or a different reason. Do NOT mention tool names, UI, or forms.`}):(this.dbg("Branch 1.75: confirm tx unavailable (null)"),{error:"quote_failed",_instructions:`Could not get a quote to buy ${l} with ${m??"the selected token"} right now. Tell the user, in their language, that the swap could not be quoted at the moment and ask them to try again shortly or with a different amount. Do NOT invent numbers. Do NOT mention tool names, UI, or forms.`})}return this.dbg("\u2192 needs pay amount"),{error:"needs_pay_amount",_instructions:`Ask the user, in their language, how much ${m??"of the payment token"} they want to spend to buy ${l}. Do NOT invent an amount. Do NOT mention tool names, UI, or forms.`}}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return B(t)?t:null}normaliseAmount(e){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return String(e);if(typeof e!="string")return null;let t=e.trim();if(!t)return null;let n=t.match(/^([0-9]*\.?[0-9]+)(?:\s+[a-zA-Z][a-zA-Z0-9]*)?$/);if(!n)return null;let r=parseFloat(n[1]);return Number.isFinite(r)&&r>0?n[1]:null}correctAmountSide(e,t){let n=typeof t?.query=="string"?t.query:"",r=typeof e.token_symbol=="string"?e.token_symbol.trim():"",o=typeof e.pay_with_symbol=="string"?e.pay_with_symbol.trim():"";if(!n||!r||!o)return;let s=typeof e.buy_amount=="string"?e.buy_amount.trim():"",a=typeof e.pay_with_amount=="string"?e.pay_with_amount.trim():"",i=s.length>0,l=a.length>0;if(i===l)return;let u=i?s:a,d=u.match(/[0-9]*\.?[0-9]+/)?.[0];if(!d)return;let p=this.nearestSymbolSide(n,d,r,o);p&&(p==="pay"&&i?(this.dbg("correctAmountSide: buy_amount \u2192 pay_with_amount",{amount:u,buySym:r,paySym:o}),e.pay_with_amount=u,e.buy_amount=""):p==="buy"&&l&&(this.dbg("correctAmountSide: pay_with_amount \u2192 buy_amount",{amount:u,buySym:r,paySym:o}),e.buy_amount=u,e.pay_with_amount=""))}nearestSymbolSide(e,t,n,r){let o=e.toLowerCase(),s=o.indexOf(t.toLowerCase()),a=this.indexOfToken(o,n.toLowerCase()),i=this.indexOfToken(o,r.toLowerCase());if(s<0||a<0||i<0)return null;let l=(p,m)=>Math.max(0,p-(s+t.length),s-(p+m)),u=l(a,n.length),d=l(i,r.length);return d===u?null:d<u?"pay":"buy"}indexOfToken(e,t){if(!t)return-1;let n=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=e.match(new RegExp(`(?:^|[^a-z0-9])(${n})(?:[^a-z0-9]|$)`));return r&&r.index!==void 0?r.index+r[0].indexOf(r[1]):-1}requireChain(e,t){let n=typeof e.chain=="string"&&e.chain.trim()?e.chain.trim():null;return n||(typeof t?.chain=="string"&&t.chain.trim()?t.chain.trim():null)}async resolveContractAddress(e,t,n){if(!this.moralis||!e)return;let r=e.trim().toLowerCase(),o=B(e.trim()),s=i=>o?(i.token_address??"").toLowerCase()===r:(i.symbol??"").toLowerCase()===r;if(n){let i=await this.moralis.getWalletTokenBalances({address:n,chain:t});if(i.success&&i.data?.result?.length){let l=i.data.result.find(s);if(l)return{address:l.token_address,symbol:l.symbol,name:l.name,decimals:typeof l.decimals=="number"?l.decimals:void 0}}}let a=await this.moralis.searchTokensByKey({key:e,chain:t});if(a.success&&a.data?.length){let l=a.data.find(s)??a.data[0];if(l?.token_address)return{address:l.token_address,symbol:l.symbol,name:l.name,decimals:typeof l.decimals=="number"?l.decimals:void 0}}}async resolveSide(e,t,n,r,o="side"){let s=typeof e=="string"?e.trim():"",a=typeof t=="string"?t.trim():"";if(s.toLowerCase()==="native"||a.toLowerCase()==="native")return{address:"native",symbol:a||void 0};let i=this.normaliseAddress(e),l=a||void 0,u=m=>m.toLowerCase()===O?"native":m,d=i??l??(s||void 0);if(!d)return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,resolved:"none"}),{address:null,symbol:l};let p=await this.resolveContractAddress(d,n,r);return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,key:d,lookup:p}),p?{address:u(p.address),symbol:l??p.symbol,decimals:p.decimals}:i?{address:u(i),symbol:l}:{address:null,symbol:l}}async buildConfirmTx(e){let{userContext:t,chain:n,destContract:r,destSymbol:o,destDecimals:s,payAddr:a,paySymbol:i,payDecimals:l,payAmount:u}=e,d=t?.walletAddress;if(!d||!n||!r)return null;let p=ee(n),m=Number.parseInt(n,16);if(!p||!Number.isFinite(m))return null;let g=a==="native",f=g?p.native.decimals??18:l;if(f===void 0)return null;let h=this.toRawAmount(u,f);if(!h)return null;let y=g?O:a,k=r==="native"?O:r,w;try{w=await(await fe.getServiceByProvider("debridge")).getQuote({srcChainId:m,srcTokenAddress:y,srcTokenAmount:h,dstChainId:m,dstTokenAddress:k,recipientAddress:d,senderAddress:d,slippage:"auto",isCrossChain:!1})}catch(A){return{quoteError:A instanceof Error?A.message:String(A)}}if(!w.success)return this.dbg("buildConfirmTx: quote failed",{error:w.error,errorMessage:w.errorMessage}),{quoteError:this.extractQuoteError(w)};if(!w.tx)return{quoteError:"No route available for this swap."};let b=w.tx;if(!b.to||typeof b.data!="string")return null;let T={chainId:n,to:b.to,data:b.data,value:b.value??"0",from:d},P=this.extractOutRaw(w),S=P&&s!==void 0?this.trimAmount(Number(P)/10**s):void 0,C;g||(C=await this.buildApproveTx({chain:n,walletAddress:d,payAddr:a,payDec:f,rawAmount:h,quote:w}));let x={component:"BuyTokenConfirmTx",props:{chain:{hexId:n,name:p.name},payToken:{address:g?"native":a,symbol:i,decimals:f,amount:u,rawAmount:h},buyToken:{address:r,symbol:o,decimals:s,amount:S,rawAmount:P},estimatedOut:S,estimatedOutRaw:P,provider:w.provider,swapTx:T,approveTx:C}},I=S?` They will receive about ${S} ${o}.`:"";return{ui:x,_instructions:`A confirmation panel has been opened for buying ${o} with ${u} ${i??"the selected token"}.${I} Briefly tell the user, in their language, the amount they are spending and the estimated amount they will receive, and ask them to review and confirm to complete the purchase. The estimate is approximate \u2014 say "about"/"estimated", never a guaranteed amount. NEVER invent numbers not provided here. Do NOT mention tool names, UI, forms, or internal steps like approval.`}}async derivePayAmountFromBuy(e){let{chain:t,buyAmount:n,destContract:r,payAddr:o}=e,[s,a]=await Promise.all([this.getUsdPrice(r,t),this.getUsdPrice(o,t)]);if(this.dbg("derivePayAmountFromBuy: prices",{destPrice:s,payPrice:a}),!s||!a)return null;let i=Number(n);if(!Number.isFinite(i)||i<=0)return null;let l=1.02,u=i*s/a;return!Number.isFinite(u)||u<=0?null:this.trimAmount(u*l)}async getUsdPrice(e,t){if(!this.moralis)return null;let n=e==="native"?O:e,r=await this.moralis.getTokenMetadata({address:n,chain:t}),o=r.success?r.data?.usd_price:void 0;return typeof o=="number"&&Number.isFinite(o)&&o>0?o:null}async resolvePayAmountSpec(e,t,n,r,o){let s=ye(e);if(!s)return null;if(s.kind==="token")return this.trimAmount(s.value);if(s.kind==="percent"){if(!r)return null;let i=await this.readPayBalance(r,t,n);return this.dbg("resolvePayAmountSpec: percent",{percent:s.percent,paySymbol:o,bal:i?.balanceNum}),!i||!Number.isFinite(i.balanceNum)||i.balanceNum<=0?null:s.percent>=100?this.cleanAmountString(i.balanceFormatted):this.trimAmount(i.balanceNum*s.percent/100)}let a=await this.getUsdPrice(t,n);return this.dbg("resolvePayAmountSpec: usd",{usd:s.usd,paySymbol:o,price:a}),a?this.trimAmount(s.usd/a):null}async resolveBuyAmountSpec(e,t,n,r){let o=ye(e);if(!o)return null;if(o.kind==="token")return this.trimAmount(o.value);if(o.kind==="percent"||!t)return null;let s=await this.getUsdPrice(t,n);return this.dbg("resolveBuyAmountSpec: usd",{usd:o.usd,destSymbol:r,price:s}),s?this.trimAmount(o.usd/s):null}async buildApproveTx(e){let{chain:t,walletAddress:n,payAddr:r,payDec:o,rawAmount:s,quote:a}=e;try{let l=await(await fe.getServiceByProvider("debridge")).checkApproval({chain:t,userAddress:n,tokenAddress:r,amount:s,tokenDecimals:o,quoteData:a});if(!l.isNeeded)return;let u=l.approvalData??{};if(u.to&&typeof u.data=="string")return{chainId:t,to:u.to,data:u.data,value:u.value??"0",from:n};let d=l.contractAddress;if(!d)return;let p=Ge({abi:_n,functionName:"approve",args:[d,BigInt(s)]});return{chainId:t,to:r,data:p,value:"0",from:n}}catch{return}}toRawAmount(e,t){let n=e.trim();if(!/^\d*\.?\d+$/.test(n))return null;let[r,o=""]=n.split("."),s=o.slice(0,t).padEnd(t,"0");try{let a=BigInt(r||"0")*10n**BigInt(t)+BigInt(s||"0");return a<=0n?null:a.toString()}catch{return null}}extractQuoteError(e){if(e.errorMessage&&e.errorMessage.trim())return e.errorMessage.trim();let t=r=>{if(typeof r=="string"&&r.trim())return r.trim();if(r&&typeof r=="object"){let o=r;for(let s of[o.message,o.errorMessage,o.error])if(typeof s=="string"&&s.trim())return s.trim()}},n=e.raw??{};return t(e.error)??t(n.errorMessage)??t(n.error)??"The swap could not be quoted right now."}extractOutRaw(e){let t=e.raw??{},n=t.tokenOut?.amount??t.tokenOut?.minAmount??t.details?.currencyOut?.amount??t.details?.currencyOut?.minimumAmount??t.estimation?.dstChainTokenOut?.recommendedAmount??t.estimation?.dstChainTokenOut?.amount;return n&&/^\d+$/.test(n)?n:void 0}async buildPayWithPicker(e){let{args:t,userContext:n,destSymbol:r,destContract:o}=e,s=this.requireChain(t,n)??void 0,a=this.moralis,i=n.walletAddress,l=await a.getWalletTokenBalances({address:i,chain:s,excludeSpam:!0,excludeUnverifiedContracts:!0}),u=l.success?l.data?.result??[]:[],d=o?.toLowerCase(),p=r.trim().toLowerCase(),m=u.filter(k=>!k.symbol||p&&k.symbol.toLowerCase()===p?!1:k.native_token?d!=="native":!(!k.token_address||d&&k.token_address.toLowerCase()===d)).sort((k,w)=>(w.usd_value??0)-(k.usd_value??0)).slice(0,8);if(m.length===0)return{error:"no_pay_token_holdings",_instructions:"The wallet has no tokens that can pay for this purchase on this chain. Tell the user to top up first. Do NOT mention tool names, UI, or forms."};let g=e.buyAmount??this.normaliseAmount(t.buy_amount),f=e.payAmount??this.normaliseAmount(t.pay_with_amount),h=typeof t.buy_with_prompt_template=="string"?t.buy_with_prompt_template:"";return{actionButtons:m.map(k=>{let w=k.symbol,b=k.name?.trim()||w,T=k.balance_formatted?`${this.cleanAmountString(k.balance_formatted)} `:"",P=typeof k.usd_value=="number"&&k.usd_value>0?` ($${k.usd_value.toFixed(2)})`:"",S=`${b}: ${T}${w}${P}`,C=g?this.buildBuyWithPrompt(h,r,w,g,null):this.buildBuyWithPrompt(h,r,w,null,f);return{label:S,prompt:C}}),_instructions:`Reply briefly in the user's language with two short sentences using the pattern 'state intent \u2192 ask to choose': first state what the user wants to do (e.g., 'B\u1EA1n mu\u1ED1n mua ${r}.' / 'You want to buy ${r}.'), then ask them to choose which token they want to spend to buy ${r} from the options below (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n d\xF9ng \u0111\u1EC3 mua.' / 'Please choose the token to spend.'). Do NOT list the tokens in text. Do NOT mention tool names, UI, or forms.`}}async checkPayBalance(e){let{args:t,userContext:n,chain:r,destSymbol:o,destContract:s,payAddr:a,payAmount:i,buyAmount:l,derivedFromBuy:u}=e,d=n.walletAddress,p=await this.readPayBalance(d,a,r),m=e.paySymbol??p?.symbol??"the selected token";if(!p){this.dbg("checkPayBalance: NOT held \u2192 pay picker fallback",{paySymbol:m});let k=await this.buildPayWithPicker({args:t,userContext:n,destSymbol:o,destContract:s,buyAmount:l,payAmount:i});return"actionButtons"in k?{error:"no_pay_balance",actionButtons:k.actionButtons,_instructions:`The user wanted to spend ${m} to buy ${o}, but their wallet holds no ${m} on this chain. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them they don't have any ${m} on this chain (e.g., 'B\u1EA1n kh\xF4ng c\xF3 ${m} tr\xEAn m\u1EA1ng n\xE0y.'), then ask them to choose one of the tokens they already hold to pay with (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n d\xF9ng \u0111\u1EC3 mua ${o} t\u1EEB danh s\xE1ch b\xEAn d\u01B0\u1EDBi.' / 'Please choose a token to pay with from the options below.'). Do NOT list the tokens in text, do NOT invent a balance, and do NOT mention tool names, UI, or forms.`}:k}if(!i||e.amountSizedToBalance)return null;let g=Number(i);if(!Number.isFinite(g)||g<=p.balanceNum)return null;let f=this.trimAmount(p.balanceNum),h=this.percentSpendButtons({balanceFormatted:p.balanceFormatted,balanceNum:p.balanceNum,paySymbol:m,destSymbol:o,buyWithTemplate:typeof t.buy_with_prompt_template=="string"?t.buy_with_prompt_template:""}),y=u?`Buying ${u} ${o} needs about ${i} ${m}, but they only have ${f} ${m} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them that buying ${u} ${o} would cost about ${i} ${m}, which is more than their balance of ${f} ${m}, then ask them to choose a smaller amount or spend less \u2014 `:`The user wants to spend ${i} ${m} to buy ${o}, but they only have ${f} ${m} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them ${i} ${m} exceeds their balance of ${f} ${m}, then ask them to choose a smaller amount \u2014 `;return{error:"insufficient_pay_balance",actionButtons:h,_instructions:y+'invite them to choose one of the options below (sized to their balance) or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT invent numbers, and do NOT mention tool names, UI, buttons, or forms.'}}async readPayBalance(e,t,n){if(!this.moralis)return null;let r=await this.moralis.getWalletTokenBalances({address:e,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[];this.dbg("readPayBalance",{payAddr:t,chain:n,ok:r.success,count:o.length,held:o.map(l=>({sym:l.symbol,addr:l.token_address,bal:l.balance_formatted}))});let s=t.toLowerCase(),a=o.find(l=>s==="native"?l.native_token===!0:l.token_address?.toLowerCase()===s);if(!a?.balance_formatted)return this.dbg("readPayBalance: pay token NOT held \u2192 null",{payAddr:t}),null;let i=Number(a.balance_formatted);if(!Number.isFinite(i)||i<=0)return this.dbg("readPayBalance: zero/invalid balance \u2192 null",{payAddr:t,balance:a.balance_formatted}),null;if(s==="native"&&n){let l=await It(n,"swap"),u=Dt(i,l,n);return this.dbg("readPayBalance: native spendable",{balanceNum:i,spendable:u}),u<=0?(this.dbg("readPayBalance: native spendable \u2264 0 \u2192 null",{balanceNum:i}),null):{balanceFormatted:this.trimAmount(u),balanceNum:u,symbol:a.symbol}}return this.dbg("readPayBalance: held",{payAddr:t,balance:a.balance_formatted,symbol:a.symbol}),{balanceFormatted:a.balance_formatted,balanceNum:i,symbol:a.symbol}}percentSpendButtons(e){let{balanceFormatted:t,balanceNum:n,paySymbol:r,destSymbol:o,buyWithTemplate:s}=e;return[.25,.5,.75,1].map(i=>{let l=i===1?this.cleanAmountString(t):this.trimAmount(n*i);return{label:`${Math.round(i*100)}%`,prompt:this.buildBuyWithPrompt(s,o,r,null,l)}})}async buildAmountPicker(e){let{userContext:t,chain:n,destSymbol:r,payAddr:o,buyWithTemplate:s}=e,a=t.walletAddress,i=await this.readPayBalance(a,o,n),l=e.paySymbol??i?.symbol;if(!i||!l)return null;let u=this.percentSpendButtons({balanceFormatted:i.balanceFormatted,balanceNum:i.balanceNum,paySymbol:l,destSymbol:r,buyWithTemplate:s}),d=this.trimAmount(i.balanceNum);return{actionButtons:u,_instructions:`The user's spendable balance is ${d} ${l}. Reply briefly in the user's language using the pattern 'state intent + balance \u2192 ask to choose': first state what the user wants and their spendable ${l} balance (e.g., 'B\u1EA1n mu\u1ED1n mua ${r} b\u1EB1ng ${l}, hi\u1EC7n c\xF3 ${d} ${l} c\xF3 th\u1EC3 d\xF9ng.' / 'You want to buy ${r} with ${l}; you have ${d} ${l} available.'), then ask how much they want to spend and invite them to choose one of the options below or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT mention tool names, UI, buttons, or forms.`}}trimAmount(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(8))):"0"}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),l=a+i,u;if(s>=0){let d=a.length+s;u=d>=l.length?l.padEnd(d,"0"):`${l.slice(0,d)}.${l.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${l}`;return u.includes(".")&&(u=u.replace(/\.?0+$/,"")),n?`-${u}`:u}cleanAmountString(e){if(!/e/i.test(e))return e;let t=Number(e);return Number.isFinite(t)?this.toPlainDecimal(t):e}buildBuyPrompt(e,t){return(e&&e.includes("{token}")?e:"buy {token}").replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}buildBuyWithPrompt(e,t,n,r,o){return(e&&e.includes("{token}")&&e.includes("{pay}")?e:"buy {buy_amount} {token} with {pay_amount} {pay}").replace(/\{token\}/g,t).replace(/\{pay\}/g,n).replace(/\{buy_amount\}/g,r??"").replace(/\{pay_amount\}/g,o??"").replace(/\s+/g," ").trim()}async buildTrendingPicker(e,t){if(!this.pantograph)return{error:"trending_unavailable",_instructions:"Trending tokens are unavailable in this build (service not configured). Tell the user briefly and ask them to name a token to buy."};let n=this.requireChain(e,t)??void 0,r=typeof e.limit=="number"&&Number.isFinite(e.limit)?Math.floor(e.limit):6,o=Math.max(1,Math.min(10,r)),s=await this.pantograph.getTrendingTokens({chain:n,limit:o}),a=s.success?s.data??[]:[];if(a.length===0)return{error:"no_trending_tokens",_instructions:"No trending tokens are available for this chain right now. Tell the user briefly and suggest they name a token directly."};let i=a.filter(m=>m.symbol),l=typeof e.buy_prompt_template=="string"?e.buy_prompt_template:"",u=i.map(m=>{let g=m.symbol;return{label:g,prompt:this.buildBuyPrompt(l,g)}}),p=i.map((m,g)=>{let f=m.name?.trim()||m.symbol,h=m.symbol,y=typeof m.usdPrice=="number"&&m.usdPrice>0?`$${this.formatPrice(m.usdPrice)}`:"N/A",k=m.pricePercentChange?.["24h"],w=typeof k=="number"&&Number.isFinite(k)?` (${k>0?"+":k<0?"-":""}${this.formatPercent(Math.abs(k))}%)`:"";return`${g+1}. ${f} (${h}) \u2014 ${y}${w}`}).join(`
176
+ }`,r=await this.querySubgraph(t,n);if(!r?.position?.pool)return null;let o=r.position,s=this.mapPool(o.pool,t),a=await this.fetchDefiLlamaUniswap(),i=a.length>0?this.enrichWithLlama([s],a)[0]:s,l=i.feeTierBps,u=i.feeTierPercent;return{position:{positionId:o.id,poolAddress:i.poolAddress,pair:i.pair,token0:{symbol:i.token0.symbol,address:i.token0.address},token1:{symbol:i.token1.symbol,address:i.token1.address},feeTierBps:l,feeTierPercent:u,chain:i.chain,liquidity:o.liquidity,depositedToken0:o.depositedToken0,depositedToken1:o.depositedToken1,withdrawnToken0:o.withdrawnToken0,withdrawnToken1:o.withdrawnToken1,collectedFeesToken0:o.collectedFeesToken0,collectedFeesToken1:o.collectedFeesToken1},pool:i}}async getCoinPoolPairs(e){if(!this.coinPoolBaseUrl||!this.keyringPoolBaseUrl)return[];let t=this.numericChainId(e.chain);if(!t)return[];let n;try{let a=await _.get(`${this.coinPoolBaseUrl}/pair/pure-list?chainId=${t}`);if(!a.ok)throw new Error(N(a));n=a.data?.pairs||[]}catch{return[]}if(e.tokens&&e.tokens.length>=2){let a=e.tokens[0].toUpperCase(),i=e.tokens[1].toUpperCase();if(n=n.filter(l=>{let u=l.token0.symbol.toUpperCase(),d=l.token1.symbol.toUpperCase();return u.includes(a)&&d.includes(i)||u.includes(i)&&d.includes(a)}),n.length===0)return[]}let r=[];for(let a of n)for(let i of a.transaction||[])i.nftId&&r.push(i.nftId);let o=await this.fetchPositionDetails(t,r),s=new Map;for(let a of o){let i=String(a?.tokenId??"");i&&s.set(i,a)}return n.map(a=>this.mapCoinPoolPair(a,s)).filter(a=>a!==null)}async getCoinPoolByAddress(e){if(!this.coinPoolBaseUrl||!this.keyringPoolBaseUrl)return[];let t=this.numericChainId(e.chain);if(!t)return[];let n=e.poolAddress.toLowerCase(),r;try{let l=await _.get(`${this.coinPoolBaseUrl}/pair/pure-list?chainId=${t}`);if(!l.ok)throw new Error(N(l));r=l.data?.pairs||[]}catch{return[]}let o=r.filter(l=>l.poolId?.toLowerCase()===n);if(o.length===0)return[];let s=[];for(let l of o)for(let u of l.transaction||[])u.nftId&&s.push(u.nftId);let a=await this.fetchPositionDetails(t,s),i=new Map;for(let l of a){let u=String(l?.tokenId??"");u&&i.set(u,l)}return o.map(l=>this.mapCoinPoolPair(l,i)).filter(l=>l!==null).sort((l,u)=>(u.apr??-1/0)-(l.apr??-1/0))}mapCoinPoolPair(e,t){let n;for(let y of e.transaction||[])if(y.nftId&&(n=t.get(y.nftId),n))break;if(!n)return null;let r=e.selectorAddress.toLowerCase()===e.token0.address.toLowerCase(),o=n.tick?.minTick!=null?Number(n.tick.minTick):0,s=n.tick?.maxTick!=null?Number(n.tick.maxTick):0,a=n.tick?.currentTick!=null?Number(n.tick.currentTick):0,i=r?o:s>0?1/s:0,l=r?s:o>0?1/o:0,u=r?a:a>0?1/a:0,d=r?e.token0.symbol:e.token1.symbol,m=r?e.token1.symbol:e.token0.symbol;if(!u||!i&&!l)return null;let p=Math.min(i,l),h=Math.max(i,l);if(u<p||u>h)return null;let g=Number(e.fee),f=Xo[g]??g/1e4;return{poolId:e.poolId,pair:`${e.token0.symbol}/${e.token1.symbol}`,fee:`${f}%`,apr:n.apr??null,aprAvg:n.aprAvg??null,token0Price:e.token0.price,token1Price:e.token1.price,priceRange:{minPrice:i,maxPrice:l,currentPrice:u,baseToken:d,quoteToken:m}}}async fetchPositionDetails(e,t){if(!this.keyringPoolBaseUrl||t.length===0)return[];let n=sa(t,200);return(await Promise.allSettled(n.map(async o=>{let s=new URLSearchParams({tokenIds:o.join(","),chainId:String(e)}),a=await _.get(`${this.keyringPoolBaseUrl}/user/positions-details/v3?${s}`);if(!a.ok)throw new Error(N(a));let i=a.data;return Array.isArray(i)?i:i?.data||[]}))).flatMap(o=>o.status==="fulfilled"?o.value:[])}};function sa(c,e){if(e<=0)return[c];let t=[];for(let n=0;n<c.length;n+=e)t.push(c.slice(n,n+e));return t}var aa=["tvl","volume","apr","fee","liquidity"],Ct=class extends v{name="subgraph-search-pools";description='Search Uniswap V3 liquidity pools via The Graph subgraphs by token symbol(s) on one or more chains. Use for: "find ETH/USDC pools on Base", "show OP pools", "stablecoin pools on Arbitrum", "best USDC pool". Returns pools with pair, TVL, 24h volume, fee tier (%), APR (DefiLlama-enriched), and liquidity rank. When exactly two tokens are given, also returns CoinPool concentrated-liquidity price-range candidates (per-range APR) for that pair. Supports TVL/APR/fee filters and sort by tvl, volume, apr, fee, or liquidity. Supported chains: Ethereum (0x1), Arbitrum (0xa4b1), Polygon (0x89), BSC (0x38), Base (0x2105), Optimism (0xa), Avalanche (0xa86a).';category="blockchain-data";noSuggestions=!0;parameters=[{name:"tokens",type:"array",items:{type:"string"},description:"Token symbols to match. 0 tokens = top pools by chosen sort. 1 token = pools containing it. 2 tokens = pools with that exact pair (both orderings tried). Wrapped variants (WETH\u2194ETH, WBTC\u2194BTC) auto-handled.",required:!1,default:[]},{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]},{name:"sortBy",type:"string",description:'Sort key: "tvl" (default), "volume", "apr", "fee" (low-to-high), or "liquidity".',required:!1,default:"tvl"},{name:"minTVL",type:"number",description:"Minimum TVL in USD.",required:!1},{name:"maxTVL",type:"number",description:"Maximum TVL in USD.",required:!1},{name:"minAPR",type:"number",description:"Minimum APR percentage (e.g. 5 for 5%).",required:!1},{name:"maxAPR",type:"number",description:"Maximum APR percentage.",required:!1},{name:"feeTier",type:"number",description:"Fee tier in basis points: 100, 500, 3000, or 10000.",required:!1},{name:"limit",type:"number",description:"Max pools to return after sort/filter. Default 5, max 20.",required:!1,default:5}];service;constructor(e){super(),this.service=new X(e)}async run(e,t){let n=(Array.isArray(e.tokens)?e.tokens:[]).filter(h=>typeof h=="string"&&h.trim().length>0).map(h=>h.trim().toUpperCase()),r=(Array.isArray(e.chains)?e.chains:[]).filter(h=>typeof h=="string"&&h.trim().length>0),o=r.filter(h=>!this.service.isSupported(h)),s=r.filter(h=>this.service.isSupported(h)).map(h=>this.service.resolveChain(h));if(s.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new K;let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],i=typeof e.sortBy=="string"&&aa.includes(e.sortBy)?e.sortBy:"tvl",l={minTVL:typeof e.minTVL=="number"?e.minTVL:void 0,maxTVL:typeof e.maxTVL=="number"?e.maxTVL:void 0,minAPR:typeof e.minAPR=="number"?e.minAPR:void 0,maxAPR:typeof e.maxAPR=="number"?e.maxAPR:void 0,feeTier:typeof e.feeTier=="number"?e.feeTier:void 0},u=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),20):5;if(r.length>0&&s.length===0)return{_instructions:"The user requested chains we do not support via The Graph subgraph. Politely list the unsupported chains and the supported chains. Do not show any pool data.",unsupportedChains:o.map(I),supportedChains:this.service.listSupportedChains().map(I),pools:[],count:0};let m=(await Promise.allSettled(a.map(h=>this.service.searchPools({tokens:n,chain:h,sortBy:i,filters:l})))).flatMap(h=>h.status==="fulfilled"?h.value:[]);i==="apr"?m.sort((h,g)=>(g.apr??0)-(h.apr??0)):i==="tvl"?m.sort((h,g)=>g.tvl-h.tvl):i==="volume"?m.sort((h,g)=>g.volume24hUsd-h.volume24hUsd):i==="fee"&&m.sort((h,g)=>h.feeTierBps-g.feeTierBps),m=m.slice(0,u);let p=[];return n.length===2&&(p=(await Promise.allSettled(a.map(g=>this.service.getCoinPoolPairs({chain:g,tokens:n})))).flatMap((g,f)=>g.status==="fulfilled"?g.value.map(y=>({...y,chain:I(a[f])})):[]),p.sort((g,f)=>(f.apr??-1/0)-(g.apr??-1/0)),p=p.slice(0,u)),{_instructions:'Prefix the answer with: "According to the latest data from Subgraph Uniswap V3," (translate naturally into the user language; keep the word "Subgraph" as-is). Then list the pools with: pair (e.g. USDC/WETH), fee tier (%), TVL (USD), 24h volume (USD), APR (%). Format USD human-readably ($12.5M, $450K). Mention chain and sort metric briefly. If a pool has no APR (null), skip the APR field rather than showing zero. For EACH pool, render the provided uniswapUrl as a clickable markdown link \u2014 e.g. wrap the pair as "[USDC/WETH](uniswapUrl)" or append "[View on Uniswap](uniswapUrl)". Never omit this link. Do not invent URLs. If coinPoolPairs is non-empty, add a short "Suggested price ranges (CoinPool)" section: for each entry show the min\u2013max price and its APR, e.g. "APR X% (range: 1800 \u2013 2200)". CRITICAL: the APR in coinPoolPairs is for that SPECIFIC price range only \u2014 never present it as the overall pool APR. Narrower ranges = higher APR but higher out-of-range risk. After the list, add a brief, natural read on the pools \u2014 conversational, like a knowledgeable friend, NOT a rigid template. Do NOT force a fixed heading ("\u{1F4CA} Evaluation"); vary your wording each time and only mention what actually stands out in THIS data. Reason ONLY from the numbers shown \u2014 never invent figures. Touch on, as it fits: which pool looks strongest and why (higher TVL & 24h volume = deeper liquidity, more fees, less slippage; a high volume-to-TVL ratio signals an active, fee-generating pool); whether the fee tier fits the pair (0.05% for stable/correlated, 0.30% for typical volatile, 1% for exotic/low-liquidity); any pool to be cautious about (thin TVL/volume, or an eye-catching APR that is likely volatile/unsustainable rather than a sure thing); and the key risks in a line (APR is variable, high APR usually means higher risk, impermanent loss for LPs on volatile pairs). Keep it short and human \u2014 a sentence or two or a couple of light bullets. Close with a brief, natural nudge that it is informational, not financial advice (phrase it differently each time, no stock disclaimer). If unsupportedChains is non-empty, mention them once at the end.',tokens:n,chains:a.map(I),unsupportedChains:o.map(I),sortBy:i,filters:l,count:m.length,pools:m.map(h=>this.formatPool(h)),coinPoolPairs:p}}formatPool(e){return{pair:e.pair,poolAddress:e.poolAddress,chain:e.chain,feeTierBps:e.feeTierBps,feeTierPercent:e.feeTierPercent,tvlUsd:e.tvl,volume24hUsd:e.volume24hUsd,fees24hUsd:e.fees24hUsd,apr:e.apr,token0:e.token0,token1:e.token1,uniswapUrl:oe(e)}}};function oe(c){return`https://app.uniswap.org/explore/pools/${sn[c.chain]||c.chain}/${c.poolAddress}`}var sn={ethereum:"ethereum",arbitrum:"arbitrum",polygon:"polygon",bsc:"bnb",base:"base",optimism:"optimism",avalanche:"avalanche"};var Ut=class extends v{name="subgraph-trending-pools";description='List Uniswap V3 pools with the highest 24h volume on one or more chains via The Graph subgraphs. Use for: "what are trending pools?", "most active pools", "hot pools right now", "pools with biggest volume today". Returns pair, TVL, 24h volume, fee tier (%), APR (DefiLlama-enriched) for each. Supported chains: Ethereum (0x1), Arbitrum (0xa4b1), Polygon (0x89), BSC (0x38), Base (0x2105), Optimism (0xa), Avalanche (0xa86a).';category="blockchain-data";noSuggestions=!0;parameters=[{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]},{name:"limit",type:"number",description:"Max pools to return. Default 5, max 20.",required:!1,default:5}];service;constructor(e){super(),this.service=new X(e)}async run(e,t){let n=(Array.isArray(e.chains)?e.chains:[]).filter(u=>typeof u=="string"&&u.trim().length>0),r=n.filter(u=>!this.service.isSupported(u)),o=n.filter(u=>this.service.isSupported(u)).map(u=>this.service.resolveChain(u));if(n.length>0&&o.length===0)return{_instructions:"User requested unsupported chains. Politely list them and the supported chains; do not show pool data.",unsupportedChains:r,supportedChains:this.service.listSupportedChains(),pools:[],count:0};if(o.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new K;let s=o.length>0?o:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),20):5,l=(await Promise.allSettled(s.map(u=>this.service.getTrendingPools({chain:u})))).flatMap(u=>u.status==="fulfilled"?u.value:[]);return l.sort((u,d)=>d.volume24hUsd-u.volume24hUsd),l=l.slice(0,a),{_instructions:'Prefix the answer with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). List pools ranked by 24h volume. For each: pair, fee %, 24h volume, TVL, APR (skip if null). For EACH pool render the provided uniswapUrl as a clickable markdown link (e.g. wrap the pair as "[USDC/WETH](uniswapUrl)" or append "[View on Uniswap](uniswapUrl)"). Never omit it. Do not invent URLs.',chains:s,unsupportedChains:r,count:l.length,pools:l.map(u=>({pair:u.pair,poolAddress:u.poolAddress,chain:u.chain,feeTierBps:u.feeTierBps,feeTierPercent:u.feeTierPercent,tvlUsd:u.tvl,volume24hUsd:u.volume24hUsd,fees24hUsd:u.fees24hUsd,apr:u.apr,token0:u.token0,token1:u.token1,uniswapUrl:oe(u)}))}}};var Rt=class extends v{name="subgraph-pool-by-address";description='Fetch full pool detail AND an evaluation for a specific Uniswap V3 pool by its on-chain address via The Graph subgraphs. Returns Uniswap fundamentals (pair, fee tier, TVL, 24h volume + fees, APR, token addresses) enriched with CoinPool concentrated-liquidity price-range candidates (per-range APR + min/max/current price), then a genuine assessment of the pool. Use whenever the user pastes a 0x pool address \u2014 both for plain detail ("tell me about pool 0xabc\u2026", "details of this pool 0x\u2026") and for an opinion ("\u0111\xE1nh gi\xE1 pool 0x\u2026", "rate/evaluate this pool 0x\u2026", "is pool 0x\u2026 good?", "pool n\xE0y c\xF3 t\u1ED1t kh\xF4ng").';category="blockchain-data";noSuggestions=!0;parameters=[{name:"poolAddress",type:"string",description:"0x-prefixed Uniswap V3 pool contract address (40 hex chars).",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Defaults to the connected chain when omitted.',required:!1}];service;constructor(e){super(),this.service=new X(e)}async run(e,t){let n=typeof e.poolAddress=="string"?e.poolAddress.trim():"";if(!/^0x[a-fA-F0-9]{40}$/.test(n))return{error:"This only supports Uniswap V3 pools. Provide a valid Uniswap V3 pool address (a 0x-prefixed 40-character hex address)."};let r=typeof e.chain=="string"&&e.chain.trim();if(!r&&!t?.chain&&t?.walletAddress)throw new K;let o=r?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(o))return{_instructions:"The requested chain is not supported by the Subgraph pool service. List the supported chains briefly.",unsupportedChains:[o],supportedChains:this.service.listSupportedChains()};let s=this.service.resolveChain(o),[a,i]=await Promise.all([this.service.getPoolByAddress({poolAddress:n,chain:s}),this.service.getCoinPoolByAddress({chain:s,poolAddress:n}).catch(()=>[])]);return a?{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3,'+(i.length>0?" enriched with CoinPool range data,":"")+'" (translate naturally; keep "Subgraph" and "CoinPool" as-is). First show the pool snapshot: pair, fee tier (%), TVL, 24h volume, 24h fees, APR (skip if null), and the pool address. If coinPoolPairs is non-empty, add a short "Suggested price ranges (CoinPool)" section: for each entry show the min\u2013max price and its APR, e.g. "APR X% (range: 1800 \u2013 2200)". CRITICAL: a range APR is for that SPECIFIC price range only \u2014 never present it as the overall pool APR. Then give a genuine evaluation of THIS pool \u2014 like a knowledgeable friend, NOT a rigid template. Do NOT force a fixed heading; vary the wording each time and only cover what is actually relevant to this pool and data. Reason ONLY from the numbers shown \u2014 never invent figures. Weave in, as it fits: liquidity depth & activity (higher TVL + 24h volume = deeper liquidity, more fees, less slippage; thin TVL/volume is a red flag); fee-tier fit (0.05% suits stable/correlated pairs, 0.30% typical volatile pairs, 1% exotic/low-liquidity ones \u2014 flag a mismatch); whether the APR looks attractive vs. how sustainable it is (fee-driven and steady vs. thin-TVL spikes that can vanish); if coinPoolPairs is present, which price range makes sense \u2014 wider around the current price is safer and lower-maintenance, narrower earns more but can fall out of range and stop earning; and the real risks (impermanent loss on volatile pairs/narrow ranges, out-of-range = 0 fees, gas/rebalancing, APR is variable). Close with a short, natural overall take (e.g. solid / decent-with-caveats / risky) and a brief nudge that it is informational, not financial advice \u2014 phrase it differently each time, do not paste a stock disclaimer. End the response with the provided uniswapUrl as a clickable markdown link, e.g. "[View on Uniswap](uniswapUrl)". Never omit it. Do not invent URLs. Do NOT mention tool names, UI, or forms.',chain:s,coinPoolPairs:i,pool:{pair:a.pair,poolAddress:a.poolAddress,chain:a.chain,feeTierBps:a.feeTierBps,feeTierPercent:a.feeTierPercent,tvlUsd:a.tvl,volume24hUsd:a.volume24hUsd,fees24hUsd:a.fees24hUsd,apr:a.apr,token0:a.token0,token1:a.token1,uniswapUrl:oe(a)}}:{error:`No pool found at ${n} on ${I(s)}.`}}};var Et=class extends v{name="subgraph-pool-by-position-id";description='Return the pool underlying a Uniswap V3 NFT position id. Use when the user wants the pool stats for a numeric id WITHOUT the deposit/withdraw/fee history (e.g. "which pool is position 962961 in?", "show the pool of id 12345"). For full position history use subgraph-position-detail instead.';category="blockchain-data";noSuggestions=!0;parameters=[{name:"positionId",type:"string",description:"Numeric Uniswap V3 NFT position id as a string.",required:!0},{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]}];service;constructor(e){super(),this.service=new X(e)}async run(e,t){let n=typeof e.positionId=="string"?e.positionId.trim():"";if(!/^\d+$/.test(n))return{error:"positionId must be a numeric string."};let r=(Array.isArray(e.chains)?e.chains:[]).filter(u=>typeof u=="string"&&u.trim().length>0),o=r.filter(u=>!this.service.isSupported(u)),s=r.filter(u=>this.service.isSupported(u)).map(u=>this.service.resolveChain(u));if(r.length>0&&s.length===0)return{_instructions:"Requested chains are not supported. List the supported chains.",unsupportedChains:o,supportedChains:this.service.listSupportedChains()};if(s.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new K;let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],l=(await Promise.allSettled(a.map(u=>this.service.getPoolByPositionId({positionId:n,chain:u})))).map(u=>u.status==="fulfilled"?u.value:null).find(u=>u!=null);return l?{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). Show the pool of that position: pair, fee %, TVL, 24h volume, APR. Mention the position id. End the response with the provided uniswapUrl as a clickable markdown link, e.g. "[View on Uniswap](uniswapUrl)". Never omit it. Do not invent URLs.',positionId:n,pool:{pair:l.pair,poolAddress:l.poolAddress,chain:l.chain,feeTierBps:l.feeTierBps,feeTierPercent:l.feeTierPercent,tvlUsd:l.tvl,volume24hUsd:l.volume24hUsd,fees24hUsd:l.fees24hUsd,apr:l.apr,token0:l.token0,token1:l.token1,uniswapUrl:oe(l)}}:{_instructions:"No pool found for this position id on the chosen chains. Ask the user to verify the id or chain.",positionId:n,chains:a,found:!1}}};var Nt=class extends v{name="subgraph-position-detail";description=`Look up a Uniswap V3 liquidity position by its numeric NFT id (e.g. 962961) on one or more chains via The Graph subgraphs. Returns the position (deposited/withdrawn token amounts, collected fees, liquidity) AND its underlying pool (pair, fee tier, TVL, 24h volume, APR). Use when the user names a numeric id with phrases like: "position 12345", "pool id 962961", "tell me about position 962961". Does NOT support the user's wallet-wide LP list \u2014 only a specific id.`;category="blockchain-data";noSuggestions=!0;parameters=[{name:"positionId",type:"string",description:'Numeric Uniswap V3 NFT position id as a string (e.g. "962961").',required:!0},{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]}];service;constructor(e){super(),this.service=new X(e)}async run(e,t){let n=typeof e.positionId=="string"?e.positionId.trim():"";if(!/^\d+$/.test(n))return{error:"positionId must be a numeric string (Uniswap V3 NFT id)."};let r=(Array.isArray(e.chains)?e.chains:[]).filter(d=>typeof d=="string"&&d.trim().length>0),o=r.filter(d=>!this.service.isSupported(d)),s=r.filter(d=>this.service.isSupported(d)).map(d=>this.service.resolveChain(d));if(r.length>0&&s.length===0)return{_instructions:"User asked about a position on chains we do not support. List unsupported + supported chains.",unsupportedChains:o,supportedChains:this.service.listSupportedChains()};if(s.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new K;let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],l=(await Promise.allSettled(a.map(d=>this.service.getPositionDetail({positionId:n,chain:d})))).map(d=>d.status==="fulfilled"?d.value:null).find(d=>d!=null);if(!l)return{_instructions:"No position with this id was found on the chosen chains. Suggest the user double-check the id or specify another chain.",positionId:n,chains:a,found:!1};let u=sn[l.position.chain]||l.position.chain;return{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). Show the pair, chain, fee tier (%), deposited/withdrawn/collected fees for each token, plus pool stats (TVL, 24h volume, APR). End the response with TWO clickable markdown links from the data: position.uniswapPositionUrl as "[View position on Uniswap](uniswapPositionUrl)" and pool.uniswapUrl as "[View pool on Uniswap](uniswapUrl)". Never omit them. Do not invent URLs.',positionId:n,position:{...l.position,uniswapPositionUrl:`https://app.uniswap.org/positions/v3/${u}/${l.position.positionId}`},pool:{...l.pool,uniswapUrl:oe(l.pool)}}}};var It=class extends v{name="subgraph-coinpool-pairs";description='List CoinPool concentrated-liquidity price-range candidates for a token pair on a chain. Each entry shows: pair, fee tier, min/max price range, current price, and APR within that range (NOT the overall pool APR \u2014 strictly per-range). Use ONLY for queries about price ranges or which range to pick: "what price range should I use for ETH/USDC?", "best range for USDC/WETH on Arbitrum", "g\u1EE3i \xFD kho\u1EA3ng gi\xE1 ETH/USDC". Do NOT use for general "show me ETH/USDC pools" \u2014 use subgraph-search-pools for that.';category="blockchain-data";noSuggestions=!0;parameters=[{name:"tokens",type:"array",items:{type:"string"},description:'Two token symbols (e.g. ["ETH","USDC"]).',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Defaults to the connected chain when omitted.',required:!1},{name:"limit",type:"number",description:"Max entries to return (sorted by APR desc). Default 10.",required:!1,default:10}];service;constructor(e){super(),this.service=new X(e)}async run(e,t){let n=(Array.isArray(e.tokens)?e.tokens:[]).filter(u=>typeof u=="string"&&u.trim().length>0).map(u=>u.trim().toUpperCase());if(n.length<2)return{error:'tokens must contain two token symbols (e.g. ["ETH","USDC"]).'};let r=typeof e.chain=="string"&&e.chain.trim();if(!r&&!t?.chain&&t?.walletAddress)throw new K;let o=r?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(o))return{_instructions:"Requested chain not supported. Mention supported chains.",unsupportedChains:[o],supportedChains:this.service.listSupportedChains()};let s=this.service.resolveChain(o),a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),30):10,i=await this.service.getCoinPoolPairs({chain:s,tokens:n});if(i.length===0)return{_instructions:"No CoinPool range data is available for this pair on this chain. Tell the user this nicely and suggest using general pool data instead (via subgraph-search-pools).",tokens:n,chain:s,pairs:[],count:0};let l=[...i].sort((u,d)=>(d.apr??-1/0)-(u.apr??-1/0)).slice(0,a);return{_instructions:'CRITICAL: APR in each entry is the APR for that SPECIFIC price range only \u2014 NEVER present it as the overall pool APR. Always show the min\u2013max price together with the APR, e.g. "APR X% (range: 1800 \u2013 2200 ETH/USDC)". Sort by APR descending; explain that the top entry is the best concentrated range but narrower = higher out-of-range risk.',tokens:n,chain:s,count:l.length,pairs:l}}};var Dt=class extends v{name="subgraph-add-pool";description='Open the ADD-LIQUIDITY / ADD-POOL flow for Uniswap V3 via The Graph subgraphs. Use for ANY "provide / add liquidity", "add LP", "add pool", "create pool", "create pair", "farm/deposit into a pool" intent (natural-language verbs in any language map here: "th\xEAm thanh kho\u1EA3n", "th\xEAm pool", "t\u1EA1o pool"). It RESOLVES the pool the user means \u2014 by 0x pool address, or by one/two token symbols (with optional fee tier) \u2014 like the detail/search tools, then points the user to the add-liquidity website. When two token symbols are given, it also surfaces CoinPool concentrated-liquidity price-range candidates (per-range APR) to help pick a range. Pass `poolAddress` when the user gives a 0x pool address; otherwise pass `tokens` (1 or 2 symbols) and optional `feeTier`. Leave both blank when the user named no pool/token \u2014 the tool returns the chain\'s top pools to choose from. It does NOT build a transaction; it surfaces the add-liquidity link.';category="blockchain-data";noSuggestions=!0;parameters=[{name:"poolAddress",type:"string",description:"0x-prefixed Uniswap V3 pool contract address (40 hex chars). Pass when the user gives a concrete pool address; otherwise leave blank and pass `tokens`.",required:!1},{name:"tokens",type:"array",items:{type:"string"},description:"Token symbols of the pair to add liquidity to. 1 token = pools containing it; 2 tokens = that exact pair (both orderings tried). Leave EMPTY when the user named no token \u2014 the tool returns top pools to choose from.",required:!1,default:[]},{name:"feeTier",type:"number",description:"Optional fee tier in basis points (100, 500, 3000, or 10000) to disambiguate the pool.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Defaults to the connected chain when omitted.',required:!1}];service;linkAddLiquidity;constructor(e){super(),this.service=new X(e),this.linkAddLiquidity=e?.linkAddLiquidity?.trim()||void 0}async run(e,t){let n=typeof e.poolAddress=="string"?e.poolAddress.trim():"",r=(Array.isArray(e.tokens)?e.tokens:[]).filter(u=>typeof u=="string"&&u.trim().length>0).map(u=>u.trim().toUpperCase()),o=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)?Math.floor(e.feeTier):void 0,s=typeof e.chain=="string"&&e.chain.trim();if(!s&&!t?.chain&&t?.walletAddress)throw new K;let a=s?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(a))return{_instructions:"The requested chain is not supported by the Subgraph pool service. Tell the user, in their language, that add-liquidity is not available on that network and list the supported chains briefly. Do NOT mention tool names.",unsupportedChains:[I(a)],supportedChains:this.service.listSupportedChains().map(I)};let i=this.service.resolveChain(a);if(n){if(!/^0x[a-fA-F0-9]{40}$/.test(n))return{error:"Provide a valid Uniswap V3 pool address (a 0x-prefixed 40-character hex address) to add liquidity to it."};let u=await this.service.getPoolByAddress({poolAddress:n,chain:i});return u?this.poolsResult([u],i,!0):{error:`No pool found at ${n} on ${I(i)}.`}}if(r.length>0){let u=await this.service.searchPools({tokens:r,chain:i,sortBy:"tvl",filters:{feeTier:o}});if(u.length===0)return{error:`No Uniswap V3 pool found for ${r.join("/")}${o!=null?` at ${o}bps`:""} on ${I(i)}. Ask the user to check the token symbols, or try a different pair.`};let d=r.length===2?await this.fetchCoinPoolPairs(i,r):[];return this.poolsResult(u.slice(0,8),i,!0,d)}let l=await this.service.searchPools({tokens:[],chain:i,sortBy:"tvl"});return{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally into the user language; keep the word "Subgraph" as-is). The user wants to add liquidity but has not named a pool yet. Briefly list these top pools (pair, fee tier %, TVL, APR \u2014 skip APR if null) as suggestions, render each `uniswapUrl` as a clickable markdown link, then ask the user which token pair (or pool) they want to add liquidity to. Do NOT show the add-liquidity link yet \u2014 only once a specific pool is chosen. Do NOT mention tool names, UI, or forms.',chain:i,needsSelection:!0,addLiquidityLink:null,count:l.length,pools:l.slice(0,6).map(u=>this.formatPool(u))}}async fetchCoinPoolPairs(e,t){try{return[...await this.service.getCoinPoolPairs({chain:e,tokens:t})].sort((r,o)=>(o.apr??-1/0)-(r.apr??-1/0)).slice(0,10)}catch{return[]}}poolsResult(e,t,n,r=[]){let o=n?this.linkAddLiquidity??null:null;return{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally into the user language; keep the word "Subgraph" as-is). The user wants to add liquidity. Show the resolved pool(s): pair (e.g. USDC/WETH), fee tier (%), TVL, 24h volume, APR (skip if null), and the pool address. For EACH pool, render the provided `uniswapUrl` as a clickable markdown link. If coinPoolPairs is non-empty, add a short "Suggested price ranges (CoinPool)" section: for each entry show the min\u2013max price and its APR, e.g. "APR X% (range: 1800 \u2013 2200)", to help the user choose a range when adding liquidity. CRITICAL: that APR is for the SPECIFIC price range only \u2014 never present it as the overall pool APR. Narrower ranges = higher APR but higher out-of-range risk. After the data, give a genuine, conversational recommendation \u2014 like a knowledgeable friend, NOT a rigid template. Do NOT force a fixed heading ("\u{1F4A1} Recommendation"); vary your wording naturally each time and only cover what is actually relevant to THIS pair and data. Reason ONLY from the numbers shown \u2014 never invent figures. Weave in, as it fits: which pool/fee tier looks best and why (higher TVL & 24h volume = deeper liquidity, more fees, less slippage; fee-tier fit: 0.05% for stable/correlated pairs, 0.30% for typical volatile pairs, 1% for exotic/low-liquidity ones); which price range makes sense (from coinPoolPairs if present) \u2014 wider around the current price is safer and lower-maintenance, narrower earns more but can fall out of range and stop earning; anything worth avoiding (thin TVL/volume, ranges far from current price, chasing the top APR blindly); and the real risks (impermanent loss on volatile pairs/narrow ranges, out-of-range = 0 fees, gas/rebalancing, APR is variable). Keep it short and human \u2014 a sentence or two or a couple of light bullets, tailored to whether the pair is stable or volatile. Close with a brief, natural nudge that it is informational, not financial advice (phrase it differently each time, do not paste a stock disclaimer). '+(o?`Then, at the VERY END, write ONE short, friendly sentence in the user's language inviting them to add liquidity, with the add-liquidity site (the addLiquidityLink) embedded as a clickable markdown link inside that sentence \u2014 NOT a bare "Add liquidity" label. The link TEXT must be a localized call-to-action phrase (e.g. "Add liquidity" / "Th\xEAm thanh kho\u1EA3n" / "here" / "t\u1EA1i \u0111\xE2y") \u2014 NEVER the URL/domain itself, and NEVER confuse it with a pool's uniswapUrl. For example: "If you'd like to add liquidity to one of these pools, you can do it here: [Add liquidity](${o})." / "N\u1EBFu b\u1EA1n mu\u1ED1n th\xEAm thanh kho\u1EA3n v\xE0o m\u1ED9t trong c\xE1c pool tr\xEAn, h\xE3y th\u1EF1c hi\u1EC7n t\u1EA1i \u0111\xE2y: [Th\xEAm thanh kho\u1EA3n](${o})." Use the provided addLiquidityLink verbatim as the href; never invent a URL.`:"Do NOT invent or show any add-liquidity link (none is configured). Just present the pool(s).")+" Do NOT mention tool names, UI, forms, or that a transaction is built elsewhere.",chain:t,addLiquidityLink:o,count:e.length,pools:e.map(a=>this.formatPool(a)),coinPoolPairs:r}}formatPool(e){return{pair:e.pair,poolAddress:e.poolAddress,chain:e.chain,feeTierBps:e.feeTierBps,feeTierPercent:e.feeTierPercent,tvlUsd:e.tvl,volume24hUsd:e.volume24hUsd,fees24hUsd:e.fees24hUsd,apr:e.apr,token0:e.token0,token1:e.token1,uniswapUrl:oe(e)}}};function be(c){if(typeof c=="number")return Number.isFinite(c)&&c>0?{kind:"token",value:c,raw:String(c)}:null;if(typeof c!="string")return null;let e=c.trim();if(!e)return null;if(/^(?:max(?:imum)?|all|everything|tất\s*cả|toàn\s*bộ)$/i.test(e))return{kind:"percent",percent:100,raw:e};let t=e.match(/^%?\s*([0-9]*\.?[0-9]+)\s*%$|^%\s*([0-9]*\.?[0-9]+)$/);if(t){let o=parseFloat(t[1]??t[2]);return Number.isFinite(o)&&o>0?{kind:"percent",percent:o,raw:e}:null}let n=e.match(/^\$\s*([0-9]*\.?[0-9]+)$|^([0-9]*\.?[0-9]+)\s*\$$|^(?:usd)\s*([0-9]*\.?[0-9]+)$|^([0-9]*\.?[0-9]+)\s*usd$/i);if(n){let o=parseFloat(n[1]??n[2]??n[3]??n[4]);return Number.isFinite(o)&&o>0?{kind:"usd",usd:o,raw:e}:null}let r=e.match(/^([0-9]*\.?[0-9]+)(?:\s+[a-zA-Z][a-zA-Z0-9]*)?$/);if(r){let o=parseFloat(r[1]);return Number.isFinite(o)&&o>0?{kind:"token",value:o,raw:e}:null}return null}function Jo(c,e){switch(c.kind){case"token":return{ok:!0,amount:c.value};case"percent":return e.balance===void 0||!Number.isFinite(e.balance)||e.balance<=0?{ok:!1,reason:"no_balance"}:{ok:!0,amount:e.balance*c.percent/100};case"usd":return e.usdPrice===void 0||!Number.isFinite(e.usdPrice)||e.usdPrice<=0?{ok:!1,reason:"no_price"}:{ok:!0,amount:c.usd/e.usdPrice}}}var ia={send:30000n,swap:1000000n},la={"0x1":["https://ethereum-rpc.publicnode.com","https://eth.drpc.org","https://1rpc.io/eth"],"0xa":["https://optimism-rpc.publicnode.com","https://optimism.drpc.org"],"0x38":["https://bsc-rpc.publicnode.com","https://bsc.drpc.org"],"0x89":["https://polygon-bor-rpc.publicnode.com","https://polygon.drpc.org"],"0x2105":["https://base-rpc.publicnode.com","https://base.drpc.org"],"0xa4b1":["https://arbitrum-one-rpc.publicnode.com","https://arbitrum.drpc.org"],"0xa86a":["https://avalanche-c-chain-rpc.publicnode.com","https://avalanche.drpc.org"],"0xe708":["https://linea-rpc.publicnode.com","https://linea.drpc.org"]};async function ca(c){let e=c.toLowerCase(),n=[Tt(c),...la[e]??[]].filter(r=>!!r);for(let r of n)try{let o=await xe(r,"eth_gasPrice",[]),s=BigInt(o);if(s>0n)return s}catch{}return 0n}async function Lt(c,e){let t=await ca(c);return t<=0n?0n:ia[e]*t}function Mt(c,e,t){if(e<=0n)return c;let n=te(t)?.native.decimals;if(n===void 0)return c;let r=Number(e)/10**n;return!Number.isFinite(r)||r<=0?c:Math.max(0,c-r)}var le=class extends v{kind="action";category="wallet-action";noSuggestions=!0;userInputFields=[];moralis;constructor(e){super(),e!==void 0&&(this.moralis=new E(e))}async resolveContractAddress(e,t,n){if(!this.moralis||!e)return;let r=e.trim().toLowerCase(),o=B(e.trim()),s=i=>o?(i.token_address??"").toLowerCase()===r:(i.symbol??"").toLowerCase()===r;if(n){let i=await this.moralis.getWalletTokenBalances({address:n,chain:t});if(i.success&&i.data?.result?.length){let l=i.data.result.find(s);if(l)return{address:l.token_address,symbol:l.symbol,name:l.name,decimals:typeof l.decimals=="number"?l.decimals:void 0}}}let a=await this.moralis.searchTokensByKey({key:e,chain:t});if(a.success&&a.data?.length){let l=a.data.find(s)??a.data[0];if(l?.token_address)return{address:l.token_address,symbol:l.symbol,name:l.name,decimals:typeof l.decimals=="number"?l.decimals:void 0}}}parseDecimals(e){if(typeof e=="number"&&Number.isInteger(e)&&e>=0)return e;if(typeof e=="string"&&e.trim()){let t=parseInt(e,10);if(Number.isInteger(t)&&t>=0)return t}}async resolveTokenRef(e){let{contractArg:t,symbolArg:n,decimalsArg:r,chain:o,walletAddress:s}=e,a=this.parseDecimals(r),i=typeof n=="string"&&n.trim()?n.trim():void 0,l=this.normaliseAddress(t),u=typeof t=="string"&&t.trim()?t.trim():void 0,d=l??i??u;if(!d)return{token:void 0};let m=await this.resolveContractAddress(d,o,s);return m?{token:{address:m.address,symbol:i??m.symbol,name:m.name,decimals:a??m.decimals}}:l?{token:{address:l,symbol:i,decimals:a}}:{error:"token_not_found",key:d}}requireWallet(e){let t=e?.walletAddress;return t?{ok:!0,address:t}:{ok:!1,payload:{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before submitting the action. Do NOT render the form."}}}requireChain(e,t){return yo(e.chain,t)}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return B(t)?t:null}normaliseAmount(e){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return String(e);if(typeof e!="string")return null;let t=e.trim();if(!t)return null;let n=parseFloat(t);return Number.isFinite(n)&&n>0?t:null}async resolveAmountInput(e){let{rawAmount:t,tokenAddress:n,chain:r,walletAddress:o,gasKind:s}=e,a=be(t);if(!a)return null;let i=e.symbol??"the token",l=n.toLowerCase()==="native",u=!!s&&l&&!!r;if(a.kind==="token")return{amount:this.toPlainDecimal(a.value)};let d=await this.readTokenBalanceAndPrice(n,r,o),m=d?.balance;u&&d?.balance!==void 0&&(m=await this.computeNativeSpendable(r,s,d.balance));let p=Jo(a,{balance:m,usdPrice:d?.usdPrice});return p.ok?{amount:this.toPlainDecimal(p.amount)}:p.reason==="no_balance"?{error:"amount_balance_unavailable",_instructions:`Could not read the user's ${i} balance to work out ${a.raw} right now. Tell them, in their language, that the balance couldn't be fetched at the moment and ask them to enter an exact ${i} amount instead. Do NOT invent a balance or amount. Do NOT mention tool names, UI, or forms.`}:{error:"amount_price_unavailable",_instructions:`Could not get a USD price for ${i} to work out ${a.raw} right now. Tell them, in their language, that the price couldn't be fetched at the moment and ask them to enter an exact ${i} amount instead. Do NOT invent a price or amount. Do NOT mention tool names, UI, or forms.`}}async computeNativeSpendable(e,t,n){let r=await Lt(e,t);return Mt(n,r,e)}async resolveNativeSpendableFormatted(e,t,n){if(!(!e||!t))try{let r=await this.readTokenBalanceAndPrice("native",e,t);if(r?.balance===void 0)return;let o=await this.computeNativeSpendable(e,n,r.balance);return this.toPlainDecimal(o)}catch{return}}async readTokenBalanceAndPrice(e,t,n){if(!this.moralis||!n)return;let r=await this.moralis.getWalletTokenBalances({address:n,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[],s=e.toLowerCase()==="native",a=e.toLowerCase(),i=o.find(u=>s?u.native_token===!0:(u.token_address??"").toLowerCase()===a);if(!i)return;let l=i.balance_formatted!=null?Number(i.balance_formatted):void 0;return{balance:Number.isFinite(l)?l:void 0,usdPrice:typeof i.usd_price=="number"&&i.usd_price>0?i.usd_price:void 0,balanceFormatted:i.balance_formatted,symbol:i.symbol}}cleanAmountString(e){if(!/e/i.test(e))return e;let t=Number(e);return Number.isFinite(t)?this.toPlainDecimal(t):e}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),l=a+i,u;if(s>=0){let d=a.length+s;u=d>=l.length?l.padEnd(d,"0"):`${l.slice(0,d)}.${l.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${l}`;return u.includes(".")&&(u=u.replace(/\.?0+$/,"")),n?`-${u}`:u}async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),o&&Array.isArray(o.actionButtons)&&o.actionButtons.length>0&&(s.actionButtons=o.actionButtons),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){let n=this.requireWallet(t);if(!n.ok)return n.payload;let r=He(e.chain,t);if(r){let f=this.actionType.replace(/_/g," ");return{error:"wrong_chain",_instructions:`The user asked to ${f} on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to ${f} on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can ${f} on ${r.connectedLabel} (their current network) instead. Do NOT render the form. Do NOT mention tool names, UI, or forms.`}}let o=this.requireChain(e,t);if(!o)return{error:"missing_chain",_instructions:'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.'};let s=await this.buildParameters(e,t);if(s&&typeof s=="object"&&"error"in s&&typeof s.error=="string"||s&&typeof s=="object"&&"actionButtons"in s&&Array.isArray(s.actionButtons)||s&&typeof s=="object"&&"ui"in s&&s.ui&&typeof s.ui=="object"&&typeof s.ui.component=="string")return s;let a={action:this.actionType,chainId:o,walletAddress:n.address,parameters:s},i={component:this.component,props:a},l=a.parameters,u=f=>{let y=l[f];return y!=null&&y!==""},d=this.userInputFields.filter(f=>!(Array.isArray(f.key)?f.key:[f.key]).some(u)).map(f=>f.label),m=this.userInputFields.map(f=>{let k=(Array.isArray(f.key)?f.key:[f.key]).find(u);return k?`${f.label}: ${l[k]}`:null}).filter(f=>f!=null),p=this.actionType.replace(/_/g," "),h='NEVER invent, guess, or insert a placeholder (e.g. "[recipient address]") for any detail the user has not actually provided \u2014 only state values listed as already provided. Do NOT mention tool names, UI, or forms.',g=d.length>0?`The ${p} is in progress but still needs input from the user. Ask the user, in their language, to provide: ${d.join(", ")}. `+(m.length>0?`Already provided: ${m.join("; ")}. `:"")+`Do NOT say "review and confirm" yet \u2014 there are still missing details to collect. ${h} Wait for the user to provide the missing details before proceeding.`:`The ${p} has all required details. In the user's language, ask the user to review and confirm to complete the ${p}. `+(m.length>0?`Details provided: ${m.join("; ")}. `:"")+`${h} Wait for the user to confirm or edit before proceeding.`;return{ui:i,action:this.actionType,chainId:o,parameters:a.parameters,_instructions:g}}};var Bt=class extends le{name="open-send-native-form";actionType="send_native";component="SendNativeForm";userInputFields=[{key:"to_address",label:"the recipient address"},{key:"amount",label:"the amount to send"}];constructor(e){super(e)}async run(e,t){if(e.chain==null||e.chain===""){let n=bo(e.native_symbol);if(n)return super.run({...e,chain:n},t)}return super.run(e,t)}description=`Open the SEND NATIVE form for the chain's native coin (ETH/BNB/MATIC/AVAX/\u2026). Use for ANY native-send intent: "send eth", "send 0.1 ETH", "transfer 1 BNB to vitalik.eth", "g\u1EEDi ETH". Call EVEN IF the user did not name an amount or recipient \u2014 the form prompts for them. Do NOT use for ERC-20 tokens \u2014 use open-send-token-form instead.`;parameters=[{name:"chain",type:"string",description:'Hex chain id: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. Defaults to connected chain.',required:!1},{name:"native_symbol",type:"string",description:'The native coin the user named to send, VERBATIM (e.g. "ETH", "BNB", "MATIC", "AVAX"). ALWAYS set this when the user names the coin ("send bnb" \u2192 native_symbol="BNB"); leave blank only when they say just "send" with no coin. A coin that belongs to one specific chain (BNB\u2192BSC, MATIC/POL\u2192Polygon, AVAX\u2192Avalanche) selects that chain, so the tool can detect when the wallet is connected elsewhere.',required:!1},{name:"to_address",type:"string",description:"Recipient EVM address (0x\u2026). Omit when unknown \u2014 the form will prompt the user.",required:!1},{name:"amount",type:"string",description:'Amount of native coin to send. Accepted forms: a plain amount ("0.1"); a PERCENT of their balance ("50%"); the literal word "max" for the WHOLE balance; or a USD value ("5$", "$5", "5 usd"). NORMALIZE natural-language phrasing in ANY language to one of these before passing it: any "send everything / the whole balance / maximum" phrasing \u2014 regardless of language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") \u2014 MUST become the literal "max"; any "half" phrasing ("half", "m\u1ED9t n\u1EEDa", "\u4E00\u534A", "\u534A\u5206") MUST become "50%". Pass plain/percent/USD numbers VERBATIM. The tool converts percent/usd/max into a concrete amount itself (and for the native coin always leaves enough to cover the network fee). Omit when unknown.',required:!1}];async buildParameters(e,t){let n={},r=this.requireChain(e,t)??void 0,o=t?.walletAddress??void 0,s=await this.readTokenBalanceAndPrice("native",r,o);if(!s||s.balance==null||s.balance<=0){let l=s?.symbol||"the native coin";return{error:"no_native_balance",_instructions:`The user wants to send ${l} but their wallet holds 0 ${l} on this chain. Tell them, in their language, that they do not have any ${l} to send and suggest they top up first. Do NOT open the form. Do NOT mention tool names, UI, or forms.`}}if(e.to_address!=null){let l=this.normaliseAddress(e.to_address);l?n.to_address=l:typeof e.to_address=="string"&&e.to_address.trim()&&(n.to_address=e.to_address.trim())}let a=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:"native",symbol:"the native coin",chain:r,walletAddress:o,gasKind:"send"});if(a&&"error"in a)return a;a&&(n.amount=a.amount);let i=await this.resolveNativeSpendableFormatted(r,o,"send");return i!==void 0&&(n.spendable=i),n}};var Ot=class extends le{name="open-send-token-form";actionType="send_token";component="SendTokenForm";userInputFields=[{key:"to_address",label:"the recipient address"},{key:"amount",label:"the amount to send"}];constructor(e){super(e)}description=`Open the SEND TOKEN (ERC-20) form. Use for ANY ERC-20 send/transfer intent: "send token", "send USDC", "transfer 50 DAI to 0x\u2026", "g\u1EEDi 10 USDT". Call this tool EVEN IF the user did not name a token, amount, or recipient \u2014 the tool handles missing token via a picker, the form prompts for the rest. Do NOT use for the chain's native coin (ETH/BNB/MATIC/AVAX) \u2014 use open-send-native-form instead.`;parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"contract_address",type:"string",description:"ERC-20 token contract address (0x\u2026). Pass when known. Otherwise leave empty and provide token_symbol \u2014 the tool will look up the address.",required:!1},{name:"token_symbol",type:"string",description:'Token symbol (e.g. "USDC"). ALWAYS set this whenever the user names a token to send ("send usdc" \u2192 token_symbol="USDC"); only leave it blank when the user names NO token at all ("send token"), so the tool can show a wallet picker. Used to auto-resolve the contract address and to display the symbol in the form. (Pass either this or contract_address.)',required:!1},{name:"decimals",type:"number",description:"Token decimals (e.g. 6 for USDC, 18 for most ERC-20s). Optional.",required:!1},{name:"to_address",type:"string",description:"Recipient EVM address (0x\u2026). Omit when unknown.",required:!1},{name:"amount",type:"string",description:'Amount of the token to send. Accepted forms: a plain amount ("100"); a PERCENT of their balance ("50%"); the literal word "max" for the WHOLE balance; or a USD value ("5$", "$5", "5 usd"). NORMALIZE natural-language phrasing in ANY language to one of these before passing it: any "send everything / the whole balance / maximum" phrasing \u2014 regardless of language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") \u2014 MUST become "max"; any "half" phrasing ("half", "m\u1ED9t n\u1EEDa", "\u4E00\u534A", "\u534A\u5206") MUST become "50%". Pass plain/percent/USD numbers VERBATIM. The tool converts percent/usd/max into a concrete amount itself. Omit when unknown.',required:!1},{name:"send_prompt_template",type:"string",description:`A short imperative "send" command IN THE USER'S LANGUAGE, used as the label/command when the user has not yet picked which token to send (token-picker buttons). Write it in the SAME language the user is using right now. Use these exact placeholders (do not translate the braces): "{symbol}" for the token, "{amount}" for the amount, "{to}" for the recipient address. Put the localized verb and the "to" preposition directly in the text. English example: "send {amount} {symbol} to {to}". Vietnamese example: "g\u1EEDi {amount} {symbol} \u0111\u1EBFn {to}". Japanese example: "{to} \u306B {amount} {symbol} \u3092\u9001\u308B". Always include {symbol}. Include {amount}/{to} too \u2014 the tool removes any whose value is unknown.`,required:!0}];async buildParameters(e,t){let n=this.normaliseAddress(e.contract_address)!=null,r=typeof e.token_symbol=="string"&&e.token_symbol.trim().length>0;if(!n&&!r&&this.moralis){let u=this.requireChain(e,t)??void 0,d=t?.walletAddress;if(d){let m=await this.moralis.getWalletTokenBalances({address:d,chain:u,excludeSpam:!0,excludeUnverifiedContracts:!0}),h=(m.success?m.data?.result??[]:[]).filter(w=>w.symbol&&(w.native_token||w.token_address)).sort((w,b)=>(b.usd_value??0)-(w.usd_value??0)).slice(0,8);if(h.length===0)return{error:"no_token_holdings",_instructions:"The wallet does not hold any sendable tokens on this chain. Tell the user they have nothing to send and suggest they top up first. Do NOT open the form."};let g=e.to_address!=null?this.normaliseAddress(e.to_address):null,f=this.normaliseAmount(e.amount),y=typeof e.send_prompt_template=="string"?e.send_prompt_template:"";return{actionButtons:h.map(w=>{let b=w.symbol,T=w.balance_formatted?this.cleanAmountString(w.balance_formatted):"",P=w.name?w.name:"",S=w.usd_value?`($${w.usd_value.toFixed(2)})`:"";return{label:`${P}:${T} ${b} ${S}`.trim(),prompt:this.buildSendPrompt(y,b,f,g)}}),_instructions:"Reply briefly in the user's language with two short sentences using the pattern 'state intent \u2192 ask to choose': first state what the user wants to do (e.g., 'B\u1EA1n mu\u1ED1n g\u1EEDi token.' / 'You want to send a token.'), then ask them to choose which token from the options below (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n g\u1EEDi.' / 'Please choose the token you want to send.'). Do NOT list the tokens in text \u2014 the options already show them. Do NOT mention tool names, UI, or forms."}}}let o=this.requireChain(e,t)??void 0,s=t?.walletAddress??void 0,a=await this.findOwnedTokenForSend(e,o,s);if(a&&"error"in a)return a;let i=a?.token,l={};if(i?.address&&(l.contract_address=i.address),i?.symbol&&(l.token_symbol=i.symbol),i?.decimals!==void 0&&(l.decimals=String(i.decimals)),e.to_address!=null){let u=this.normaliseAddress(e.to_address);u?l.to_address=u:typeof e.to_address=="string"&&e.to_address.trim()&&(l.to_address=e.to_address.trim())}if(i?.address){let u=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:i.address,symbol:i.symbol,chain:o,walletAddress:s});if(u&&"error"in u)return u;u&&(l.amount=u.amount)}else{let u=this.normaliseAmount(e.amount);u&&(l.amount=u)}return l}async findOwnedTokenForSend(e,t,n){let r=this.normaliseAddress(e.contract_address),o=typeof e.token_symbol=="string"&&e.token_symbol.trim()?e.token_symbol.trim():void 0,s=r??o;if(!this.moralis||!n||!s){let p=await this.resolveTokenRef({contractArg:e.contract_address,symbolArg:e.token_symbol,decimalsArg:e.decimals,chain:t,walletAddress:n});return"error"in p?{error:"token_not_found",symbol:p.key,_instructions:`Could not resolve token "${p.key}" to a contract address on this chain. Ask the user to provide the token contract directly.`}:{token:p.token}}let a=s.toLowerCase(),i=r!=null,l=await this.moralis.getWalletTokenBalances({address:n,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0}),d=(l.success?l.data?.result??[]:[]).find(p=>i?(p.token_address??"").toLowerCase()===a:(p.symbol??"").toLowerCase()===a);if(d&&d.balance&&!/^0*$/.test(d.balance.trim())){let p=this.parseDecimals(e.decimals);return{token:{address:d.token_address,symbol:d.symbol??o,name:d.name,decimals:p??(typeof d.decimals=="number"?d.decimals:void 0)}}}let m=d?.symbol??o??s;return{error:"no_balance",symbol:m,_instructions:`The user does not hold any ${m} in their wallet on this chain, so there is nothing to send. Tell them (in their language) that they don't have any ${m} to send, and suggest they top up or pick another token. Do NOT open a form. Do NOT mention tool names, UI, or forms.`}}buildSendPrompt(e,t,n,r){let s=(e&&e.includes("{symbol}")?e:"send {amount} {symbol} to {to}").replace(/\{symbol\}/g,t).replace(/\{amount\}/g,n??"");return r?s=s.replace(/\{to\}/g,r):s=s.replace(/\s*\S+\s*\{to\}/g,"").replace(/\{to\}\s*\S+\s*/g,"").replace(/\{to\}/g,""),s.replace(/\s+/g," ").trim()}};var qt=class extends le{name="open-approve-token-form";actionType="approve_token";component="ApproveTokenForm";userInputFields=[{key:"spender_address",label:"the spender address to approve"}];constructor(e){super(e)}description='Open the APPROVE TOKEN form so the user can grant a spender allowance on an ERC-20 token. Use when the user says "approve USDC for Uniswap", "c\u1EA5p quy\u1EC1n 100 USDT cho 0x\u2026", "revoke approval". Pass `contract_address` when you have it; otherwise pass `token_symbol` (e.g. "USDC") and the tool auto-resolves the contract. Always pass `spender_address` (the contract being approved). Pass `amount` for a specific allowance; omit for unlimited (max uint256). For approving 0 to revoke, pass amount="0".';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"contract_address",type:"string",description:"ERC-20 token contract address (0x\u2026). Pass when known. Otherwise leave empty and provide token_symbol.",required:!1},{name:"token_symbol",type:"string",description:'Token symbol (e.g. "USDC"). Required when contract_address is not given. Used to auto-resolve the contract.',required:!1},{name:"decimals",type:"number",description:"Token decimals.",required:!1},{name:"spender_address",type:"string",description:"EVM address of the spender contract being approved (0x\u2026). Omit when unknown \u2014 the form will prompt the user.",required:!1},{name:"amount",type:"string",description:'Human-readable allowance amount. Omit for UNLIMITED (max uint256). Pass "0" to revoke.',required:!1}];async buildParameters(e,t){let n=this.requireChain(e,t)??void 0,r=await this.resolveTokenRef({contractArg:e.contract_address,symbolArg:e.token_symbol,decimalsArg:e.decimals,chain:n,walletAddress:t?.walletAddress??void 0});if("error"in r)return{error:"token_not_found",symbol:r.key,_instructions:`Could not resolve token "${r.key}" to a contract address on this chain. Ask the user to provide the token contract directly.`};let o=r.token,s=this.normaliseAddress(e.spender_address),a={};if(o?.address&&(a.contract_address=o.address),s&&(a.spender_address=s),o?.symbol&&(a.token_symbol=o.symbol),o?.decimals!==void 0&&(a.decimals=String(o.decimals)),typeof e.amount=="string"&&e.amount.trim()){let i=e.amount.trim(),l=parseFloat(i);Number.isFinite(l)&&l>=0&&(a.amount=i)}else typeof e.amount=="number"&&Number.isFinite(e.amount)&&e.amount>=0&&(a.amount=String(e.amount));return a}};function ua(c){return In(String(c))??"0"}function In(c){let e=c.trim().match(/^([+-]?)(\d*)(?:\.(\d*))?(?:[eE]([+-]?\d+))?$/);if(!e)return null;let t=e[1]==="-"?"-":"",n=e[2]??"",r=e[3]??"";if(n===""&&r==="")return null;let o=e[4]?parseInt(e[4],10):0,s=n+r,a=n.length+o,i;return a<=0?i=`0.${"0".repeat(-a)}${s}`:a>=s.length?i=s+"0".repeat(a-s.length):i=`${s.slice(0,a)}.${s.slice(a)}`,i=i.replace(/^0+(?=\d)/,""),i.includes(".")&&(i=i.replace(/0+$/,"").replace(/\.$/,"")),(i===""||i==="-")&&(i="0"),i==="0"?"0":t+i}function io(c){let e=In(c);if(e===null||e.startsWith("-"))return null;let[t,n=""]=e.split(".");return{mant:BigInt(t+n),frac:n.length}}function da(c,e){let t=c<0n,n=(t?-c:c).toString(),r;if(e===0)r=n;else{let o=n.padStart(e+1,"0"),s=o.length-e;r=`${o.slice(0,s)}.${o.slice(s)}`.replace(/0+$/,"").replace(/\.$/,"")}return t?`-${r}`:r}function Ft(c){if(typeof c=="number")return Number.isFinite(c)&&c>0?ua(c):null;if(typeof c!="string")return null;let e=c.match(/[0-9]*\.?[0-9]+(?:[eE][+-]?[0-9]+)?/);if(!e)return null;let t=In(e[0]);return t&&t!=="0"&&!t.startsWith("-")?t:null}function $t(c,e){let t=In(c);if(t===null||e<0)return c;let n=t.startsWith("-"),[r,o=""]=(n?t.slice(1):t).split(".");if(o.length<=e)return t;let s=e===0?r:`${r}.${o.slice(0,e)}`,a=s.includes(".")?s.replace(/0+$/,"").replace(/\.$/,""):s;return n?`-${a}`:a}function Wt(c,e,t,n){let r=io(c);if(!r||t<=0n||n<0)return c;let o=r.mant*e*10n**BigInt(n)/(t*10n**BigInt(r.frac));return da(o,n)}function Dn(c,e){let t=io(c),n=io(e);if(!t||!n){let a=Number(c),i=Number(e);return a<i?-1:a>i?1:0}let r=Math.max(t.frac,n.frac),o=t.mant*10n**BigInt(r-t.frac),s=n.mant*10n**BigInt(r-n.frac);return o<s?-1:o>s?1:0}var Vt=class extends v{name="open-buy-token-form";kind="action";category="wallet-action";noSuggestions=!0;moralis;pantograph;debug;constructor(e,t){super(),e!==void 0&&(this.moralis=new E(e),this.pantograph=new ee({baseUrl:e.pantographUrl})),this.debug=t?.debug??!1}dbg(e,t){this.debug&&(t===void 0?console.log(`[BuyTokenTool] ${e}`):console.log(`[BuyTokenTool] ${e}`,JSON.stringify(t)))}description='Open the BUY TOKEN flow for ANY buy/purchase intent. Handles both cases: (a) the user NAMED a token \u2014 "buy PEPE", "mua DOGE", "buy 100 USDC with ETH": pass `token_symbol` (the destination); if you also know the pay token, fill `pay_with_symbol` (or `pay_with_address`), else leave pay_with_* blank and the tool returns a wallet picker. (b) the user did NOT name a token \u2014 "buy a trending token", "mua token trending", "buy something pumping", "buy hot tokens", "mua token t\u0103ng m\u1EA1nh": leave `token_symbol` blank and the tool returns buttons of the chain\'s current trending tokens so the user picks one. Amount: `buy_amount` = destination to RECEIVE; `pay_with_amount` = payment to SPEND. Never both.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"token_symbol",type:"string",description:'Symbol of the token the user wants to BUY (e.g. "PEPE"). Leave EMPTY when the user did not name a token (e.g. "buy a trending token") \u2014 the tool then returns trending tokens to pick from.',required:!1},{name:"contract_address",type:"string",description:`Destination token address (0x\u2026), or "native" for the chain's native coin. Leave empty when unknown \u2014 the tool resolves it from token_symbol.`,required:!1},{name:"buy_amount",type:"string",description:`Quantity of the DESTINATION token (the one being bought) the user wants to RECEIVE. Set this when the amount sits next to the token being bought: "buy 5 PEPE", "mua 5 PEPE", "buy 0.02 PCM with USDC" \u2192 buy_amount = the number next to PEPE/PCM. May also be a USD value when the user sizes the buy in dollars ("buy $5 of PEPE" \u2192 "5$"); pass the user's expression VERBATIM (the tool converts "$" to a token quantity). Mutually exclusive with pay_with_amount \u2014 set at most ONE of the two.`,required:!1},{name:"pay_with_symbol",type:"string",description:'Symbol of the payment token (e.g. "USDC", "ETH").',required:!1},{name:"pay_with_address",type:"string",description:`Address of the payment token (0x\u2026), OR "native" for the chain's native coin.`,required:!1},{name:"pay_with_amount",type:"string",description:'Quantity of the PAYMENT token (the one being spent) the user wants to SPEND. Set this when the amount sits next to the payment token: "buy PEPE with 10 USDC", "mua PEPE b\u1EB1ng 10 USDC", "spend 0.5 ETH on DOGE" \u2192 pay_with_amount = the number next to USDC/ETH. May also be a PERCENT of the pay-token balance ("buy USDC with 50% USDT" \u2192 "50%"), the literal word "max" meaning the WHOLE balance, or a USD value ("buy USDC with 5$ USDT" \u2192 "5$"). NORMALIZE any "whole balance / everything / maximum" phrasing in ANY language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") to the literal "max"; pass plain/percent/USD numbers VERBATIM (the tool converts "%"/"$"/"max"). "max" is an AMOUNT, never a token \u2014 still set pay_with_symbol to the payment token ("ETH" in "buy USDC with max ETH"). Mutually exclusive with buy_amount \u2014 set at most ONE of the two.',required:!1},{name:"limit",type:"number",description:"Only used when no token is named: max number of trending tokens to show as buttons. Default 6. Clamped to [1, 10].",required:!1},{name:"buy_prompt_template",type:"string",description:`A short "buy" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the trending-token picker (when the user named no token). Use the exact placeholder "{token}" (do not translate the braces) for the token symbol; put the localized verb directly in the text. English example: "buy {token}". Vietnamese example: "mua {token}". Japanese example: "{token} \u8CFC\u5165". Always include {token}.`,required:!0},{name:"buy_with_prompt_template",type:"string",description:`A short "buy X with Y" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the pay-token picker and the percentage-spend buttons. Use these exact placeholders (do not translate the braces): "{token}" = token being bought, "{pay}" = payment token, "{buy_amount}" = quantity of the BOUGHT token (place it next to {token}), "{pay_amount}" = quantity of the PAYMENT token (place it next to {pay}). Put the localized verb and the "with" preposition directly in the text. English example: "buy {buy_amount} {token} with {pay_amount} {pay}". Vietnamese example: "mua {buy_amount} {token} b\u1EB1ng {pay_amount} {pay}". Japanese example: "{pay_amount} {pay} \u3067 {buy_amount} {token} \u8CFC\u5165". Always include {token} and {pay}. Include both {buy_amount} and {pay_amount} too \u2014 the tool removes whichever amount is unknown (only one side ever carries an amount).`,required:!0}];async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),o&&Array.isArray(o.actionButtons)&&o.actionButtons.length>0&&(s.actionButtons=o.actionButtons),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){if(!t?.walletAddress)return{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before buying. Do NOT proceed."};let r=He(e.chain,t);return r?{error:"wrong_chain",_instructions:`The user asked to buy on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to buy on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can buy on ${r.connectedLabel} (their current network) instead. Do NOT proceed. Do NOT mention tool names, UI, or forms.`}:this.requireChain(e,t)?this.buildResult(e,t):{error:"missing_chain",_instructions:'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.'}}async buildResult(e,t){this.correctAmountSide(e,t);let n=typeof e.token_symbol=="string"?e.token_symbol.trim():"",r=typeof e.contract_address=="string"?e.contract_address.trim():"",o=this.requireChain(e,t)??void 0,s=t?.walletAddress??void 0;if(this.dbg("buildResult:args",{args:e,chain:o,walletAddress:s,hasMoralis:!!this.moralis}),!n&&!r)return this.dbg("\u2192 Branch 0: trending picker (no token named)"),await this.buildTrendingPicker(e,t);let a=await this.resolveSide(e.contract_address,n,o,s,"dest"),i=a.address,l=a.symbol??n,u=a.decimals,d=await this.resolveSide(e.pay_with_address,e.pay_with_symbol,o,s,"pay"),m=d.address,p=d.symbol,h=d.decimals,g=await this.resolveBuyAmountSpec(e.buy_amount,i,o,l),f=m?await this.resolvePayAmountSpec(e.pay_with_amount,m,o,s,p,h??(m==="native"?18:void 0)):this.normaliseAmount(e.pay_with_amount),y=be(e.pay_with_amount),k=!!y&&y.kind!=="token",w=null;if(this.dbg("resolved sides",{dest:{destContract:i,destSymbol:l,destDecimals:u},pay:{payAddr:m,paySymbol:p,payDecimals:h},buyAmount:g,payAmount:f}),!i)return this.dbg("\u2192 dest token not resolved"),{error:"dest_token_not_found",_instructions:`Could not find the token "${l||r}" to buy on this chain. Tell the user, in their language, that the token could not be found and ask them to check the name or provide its contract address. Do NOT invent an address. Do NOT mention tool names, UI, or forms.`};if(!m&&this.moralis&&t?.walletAddress)return this.dbg("\u2192 Branch 1: pay picker (pay token missing)"),await this.buildPayWithPicker({args:e,userContext:t,destSymbol:l,destContract:i,buyAmount:g,payAmount:f});if(!m)return{error:"pay_token_missing",_instructions:`Ask the user, in their language, which token they want to spend to buy ${l}. Do NOT invent a token. Do NOT mention tool names, UI, or forms.`};if(g&&!f){this.dbg("Branch 1.3: deriving pay amount from buy amount",{buyAmount:g,destSymbol:l,paySymbol:p});let b=await this.derivePayAmountFromBuy({chain:o,buyAmount:g,destContract:i,destSymbol:l,payAddr:m,paySymbol:p});if(!b)return this.dbg("\u2192 Branch 1.3: price unavailable \u2014 needs pay amount"),{error:"needs_pay_amount",_instructions:`Could not work out how much ${p??"of the payment token"} equals ${g} ${l} right now. Tell the user, in their language, that the price couldn't be fetched at the moment, and ask how much ${p??"of the payment token"} they want to spend instead. Do NOT invent an amount or a price. Do NOT mention tool names, UI, or forms.`};f=b,w=g,k=!1,this.dbg("\u2192 Branch 1.3: derived pay amount",{payAmount:f,derivedFromBuy:w})}if(this.moralis&&t?.walletAddress){this.dbg("Branch 1.4: checking pay balance",{payAddr:m,paySymbol:p,payAmount:f});let b=await this.checkPayBalance({args:e,userContext:t,chain:o,destSymbol:l,destContract:i,payAddr:m,paySymbol:p,payAmount:f,buyAmount:g,derivedFromBuy:w,amountSizedToBalance:k});if(b)return this.dbg("\u2192 Branch 1.4: balance shortfall \u2014 returning",{error:b.error,hasButtons:Array.isArray(b.actionButtons)}),b;this.dbg("Branch 1.4: balance OK \u2014 continuing")}if(!g&&!f&&this.moralis&&t?.walletAddress){this.dbg("Branch 1.5: building amount picker");let b=await this.buildAmountPicker({userContext:t,chain:o,destSymbol:l,payAddr:m,paySymbol:p,buyWithTemplate:typeof e.buy_with_prompt_template=="string"?e.buy_with_prompt_template:""});if(b)return this.dbg("\u2192 Branch 1.5: amount picker returned"),b;this.dbg("Branch 1.5: amount picker null \u2014 needs amount")}if(f&&(h!==void 0||m==="native")){this.dbg("Branch 1.75: building confirm tx");let b=await this.buildConfirmTx({userContext:t,chain:o,destContract:i,destSymbol:l,destDecimals:u,payAddr:m,paySymbol:p,payDecimals:h,payAmount:f});return b&&"ui"in b?(this.dbg("\u2192 Branch 1.75: confirm tx returned"),b):b&&"quoteError"in b?(this.dbg("\u2192 Branch 1.75: quote error from provider",{quoteError:b.quoteError}),{error:"quote_failed",quoteError:b.quoteError,_instructions:`Could not quote buying ${l} with ${p??"the selected token"}. The swap provider reported: "${b.quoteError}". Tell the user, in their language, exactly that reason (translate the wording, keep any numbers/limits verbatim) and suggest they adjust the amount or try again shortly. Do NOT invent numbers or a different reason. Do NOT mention tool names, UI, or forms.`}):(this.dbg("Branch 1.75: confirm tx unavailable (null)"),{error:"quote_failed",_instructions:`Could not get a quote to buy ${l} with ${p??"the selected token"} right now. Tell the user, in their language, that the swap could not be quoted at the moment and ask them to try again shortly or with a different amount. Do NOT invent numbers. Do NOT mention tool names, UI, or forms.`})}return this.dbg("\u2192 needs pay amount"),{error:"needs_pay_amount",_instructions:`Ask the user, in their language, how much ${p??"of the payment token"} they want to spend to buy ${l}. Do NOT invent an amount. Do NOT mention tool names, UI, or forms.`}}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return B(t)?t:null}normaliseAmount(e){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return String(e);if(typeof e!="string")return null;let t=e.trim();if(!t)return null;let n=t.match(/^([0-9]*\.?[0-9]+)(?:\s+[a-zA-Z][a-zA-Z0-9]*)?$/);if(!n)return null;let r=parseFloat(n[1]);return Number.isFinite(r)&&r>0?n[1]:null}correctAmountSide(e,t){let n=typeof t?.query=="string"?t.query:"",r=typeof e.token_symbol=="string"?e.token_symbol.trim():"",o=typeof e.pay_with_symbol=="string"?e.pay_with_symbol.trim():"";if(!n||!r||!o)return;let s=typeof e.buy_amount=="string"?e.buy_amount.trim():"",a=typeof e.pay_with_amount=="string"?e.pay_with_amount.trim():"",i=s.length>0,l=a.length>0;if(i===l)return;let u=i?s:a,d=u.match(/[0-9]*\.?[0-9]+/)?.[0];if(!d)return;let m=this.nearestSymbolSide(n,d,r,o);m&&(m==="pay"&&i?(this.dbg("correctAmountSide: buy_amount \u2192 pay_with_amount",{amount:u,buySym:r,paySym:o}),e.pay_with_amount=u,e.buy_amount=""):m==="buy"&&l&&(this.dbg("correctAmountSide: pay_with_amount \u2192 buy_amount",{amount:u,buySym:r,paySym:o}),e.buy_amount=u,e.pay_with_amount=""))}nearestSymbolSide(e,t,n,r){let o=e.toLowerCase(),s=o.indexOf(t.toLowerCase()),a=this.indexOfToken(o,n.toLowerCase()),i=this.indexOfToken(o,r.toLowerCase());if(s<0||a<0||i<0)return null;let l=(m,p)=>Math.max(0,m-(s+t.length),s-(m+p)),u=l(a,n.length),d=l(i,r.length);return d===u?null:d<u?"pay":"buy"}indexOfToken(e,t){if(!t)return-1;let n=t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=e.match(new RegExp(`(?:^|[^a-z0-9])(${n})(?:[^a-z0-9]|$)`));return r&&r.index!==void 0?r.index+r[0].indexOf(r[1]):-1}requireChain(e,t){let n=typeof e.chain=="string"&&e.chain.trim()?e.chain.trim():null;return n||(typeof t?.chain=="string"&&t.chain.trim()?t.chain.trim():null)}async resolveContractAddress(e,t,n){if(!this.moralis||!e)return;let r=e.trim().toLowerCase(),o=B(e.trim()),s=i=>o?(i.token_address??"").toLowerCase()===r:(i.symbol??"").toLowerCase()===r;if(n){let i=await this.moralis.getWalletTokenBalances({address:n,chain:t});if(i.success&&i.data?.result?.length){let l=i.data.result.find(s);if(l)return{address:l.token_address,symbol:l.symbol,name:l.name,decimals:typeof l.decimals=="number"?l.decimals:void 0}}}let a=await this.moralis.searchTokensByKey({key:e,chain:t});if(a.success&&a.data?.length){let l=a.data.find(s)??a.data[0];if(l?.token_address)return{address:l.token_address,symbol:l.symbol,name:l.name,decimals:typeof l.decimals=="number"?l.decimals:void 0}}}async resolveSide(e,t,n,r,o="side"){let s=typeof e=="string"?e.trim():"",a=typeof t=="string"?t.trim():"";if(s.toLowerCase()==="native"||a.toLowerCase()==="native")return{address:"native",symbol:a||void 0};let i=this.normaliseAddress(e),l=a||void 0,u=p=>p.toLowerCase()===O?"native":p,d=i??l??(s||void 0);if(!d)return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,resolved:"none"}),{address:null,symbol:l};let m=await this.resolveContractAddress(d,n,r);return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,key:d,lookup:m}),m?{address:u(m.address),symbol:l??m.symbol,decimals:m.decimals}:i?{address:u(i),symbol:l}:{address:null,symbol:l}}async buildConfirmTx(e){let{userContext:t,chain:n,destContract:r,destSymbol:o,destDecimals:s,payAddr:a,paySymbol:i,payDecimals:l,payAmount:u}=e,d=t?.walletAddress;if(!d||!n||!r)return null;let m=te(n),p=Number.parseInt(n,16);if(!m||!Number.isFinite(p))return null;let h=a==="native",g=h?m.native.decimals??18:l;if(g===void 0)return null;let f=this.toRawAmount(u,g);if(!f)return null;let y=h?O:a,k=r==="native"?O:r,w;try{w=await(await ye.getServiceByProvider("debridge")).getQuote({srcChainId:p,srcTokenAddress:y,srcTokenAmount:f,dstChainId:p,dstTokenAddress:k,recipientAddress:d,senderAddress:d,slippage:"auto",isCrossChain:!1})}catch(A){return{quoteError:A instanceof Error?A.message:String(A)}}if(!w.success)return this.dbg("buildConfirmTx: quote failed",{error:w.error,errorMessage:w.errorMessage}),{quoteError:this.extractQuoteError(w)};if(!w.tx)return{quoteError:"No route available for this swap."};let b=w.tx;if(!b.to||typeof b.data!="string")return null;let T={chainId:n,to:b.to,data:b.data,value:b.value??"0",from:d},P=this.extractOutRaw(w),S=P&&s!==void 0?this.trimAmount(Number(P)/10**s):void 0,C;h||(C=await this.buildApproveTx({chain:n,walletAddress:d,payAddr:a,payDec:g,rawAmount:f,quote:w}));let x={component:"BuyTokenConfirmTx",props:{chain:{hexId:n,name:m.name},payToken:{address:h?"native":a,symbol:i,decimals:g,amount:u,rawAmount:f},buyToken:{address:r,symbol:o,decimals:s,amount:S,rawAmount:P},estimatedOut:S,estimatedOutRaw:P,provider:w.provider,swapTx:T,approveTx:C}},D=S?` They will receive about ${S} ${o}.`:"";return{ui:x,_instructions:`A confirmation panel has been opened for buying ${o} with ${u} ${i??"the selected token"}.${D} Briefly tell the user, in their language, the amount they are spending and the estimated amount they will receive, and ask them to review and confirm to complete the purchase. The estimate is approximate \u2014 say "about"/"estimated", never a guaranteed amount. NEVER invent numbers not provided here. Do NOT mention tool names, UI, forms, or internal steps like approval.`}}async derivePayAmountFromBuy(e){let{chain:t,buyAmount:n,destContract:r,payAddr:o}=e,[s,a]=await Promise.all([this.getUsdPrice(r,t),this.getUsdPrice(o,t)]);if(this.dbg("derivePayAmountFromBuy: prices",{destPrice:s,payPrice:a}),!s||!a)return null;let i=Number(n);if(!Number.isFinite(i)||i<=0)return null;let l=1.02,u=i*s/a;return!Number.isFinite(u)||u<=0?null:this.trimAmount(u*l)}async getUsdPrice(e,t){if(!this.moralis)return null;let n=e==="native"?O:e,r=await this.moralis.getTokenMetadata({address:n,chain:t}),o=r.success?r.data?.usd_price:void 0;return typeof o=="number"&&Number.isFinite(o)&&o>0?o:null}async resolvePayAmountSpec(e,t,n,r,o,s){let a=be(e);if(!a)return null;let i=u=>s===void 0?u:$t(u,s);if(a.kind==="token")return i(Ft(e)??this.toPlainDecimal(a.value));if(a.kind==="percent"){if(!r)return null;let u=await this.readPayBalance(r,t,n);if(this.dbg("resolvePayAmountSpec: percent",{percent:a.percent,paySymbol:o,bal:u?.balanceNum}),!u||!Number.isFinite(u.balanceNum)||u.balanceNum<=0)return null;let d=u.decimals??s??18;return a.percent>=100?this.cleanAmountString(u.balanceFormatted):Wt(u.balanceFormatted,BigInt(Math.round(a.percent*1e6)),100000000n,d)}let l=await this.getUsdPrice(t,n);return this.dbg("resolvePayAmountSpec: usd",{usd:a.usd,paySymbol:o,price:l}),l?i(this.trimAmount(a.usd/l)):null}async resolveBuyAmountSpec(e,t,n,r){let o=be(e);if(!o)return null;if(o.kind==="token")return Ft(e)??this.toPlainDecimal(o.value);if(o.kind==="percent"||!t)return null;let s=await this.getUsdPrice(t,n);return this.dbg("resolveBuyAmountSpec: usd",{usd:o.usd,destSymbol:r,price:s}),s?this.trimAmount(o.usd/s):null}async buildApproveTx(e){let{chain:t,walletAddress:n,payAddr:r,payDec:o,rawAmount:s,quote:a}=e;try{let l=await(await ye.getServiceByProvider("debridge")).checkApproval({chain:t,userAddress:n,tokenAddress:r,amount:s,tokenDecimals:o,quoteData:a});if(!l.isNeeded)return;let u=l.approvalData??{};if(u.to&&typeof u.data=="string")return{chainId:t,to:u.to,data:u.data,value:u.value??"0",from:n};let d=l.contractAddress;if(!d)return;let m=Ge({abi:Nn,functionName:"approve",args:[d,BigInt(s)]});return{chainId:t,to:r,data:m,value:"0",from:n}}catch{return}}toRawAmount(e,t){let n=e.trim();if(!/^\d*\.?\d+$/.test(n))return null;let[r,o=""]=n.split("."),s=o.slice(0,t).padEnd(t,"0");try{let a=BigInt(r||"0")*10n**BigInt(t)+BigInt(s||"0");return a<=0n?null:a.toString()}catch{return null}}extractQuoteError(e){if(e.errorMessage&&e.errorMessage.trim())return e.errorMessage.trim();let t=r=>{if(typeof r=="string"&&r.trim())return r.trim();if(r&&typeof r=="object"){let o=r;for(let s of[o.message,o.errorMessage,o.error])if(typeof s=="string"&&s.trim())return s.trim()}},n=e.raw??{};return t(e.error)??t(n.errorMessage)??t(n.error)??"The swap could not be quoted right now."}extractOutRaw(e){let t=e.raw??{},n=t.tokenOut?.amount??t.tokenOut?.minAmount??t.details?.currencyOut?.amount??t.details?.currencyOut?.minimumAmount??t.estimation?.dstChainTokenOut?.recommendedAmount??t.estimation?.dstChainTokenOut?.amount;return n&&/^\d+$/.test(n)?n:void 0}async buildPayWithPicker(e){let{args:t,userContext:n,destSymbol:r,destContract:o}=e,s=this.requireChain(t,n)??void 0,a=this.moralis,i=n.walletAddress,l=await a.getWalletTokenBalances({address:i,chain:s,excludeSpam:!0,excludeUnverifiedContracts:!0}),u=l.success?l.data?.result??[]:[],d=o?.toLowerCase(),m=r.trim().toLowerCase(),p=u.filter(k=>!k.symbol||m&&k.symbol.toLowerCase()===m?!1:k.native_token?d!=="native":!(!k.token_address||d&&k.token_address.toLowerCase()===d)).sort((k,w)=>(w.usd_value??0)-(k.usd_value??0)).slice(0,8);if(p.length===0)return{error:"no_pay_token_holdings",_instructions:"The wallet has no tokens that can pay for this purchase on this chain. Tell the user to top up first. Do NOT mention tool names, UI, or forms."};let h=e.buyAmount??this.normaliseAmount(t.buy_amount),g=e.payAmount??this.normaliseAmount(t.pay_with_amount),f=typeof t.buy_with_prompt_template=="string"?t.buy_with_prompt_template:"";return{actionButtons:p.map(k=>{let w=k.symbol,b=k.name?.trim()||w,T=k.balance_formatted?`${this.cleanAmountString(k.balance_formatted)} `:"",P=typeof k.usd_value=="number"&&k.usd_value>0?` ($${k.usd_value.toFixed(2)})`:"",S=`${b}: ${T}${w}${P}`,C=h?this.buildBuyWithPrompt(f,r,w,h,null):this.buildBuyWithPrompt(f,r,w,null,g);return{label:S,prompt:C}}),_instructions:`Reply briefly in the user's language with two short sentences using the pattern 'state intent \u2192 ask to choose': first state what the user wants to do (e.g., 'B\u1EA1n mu\u1ED1n mua ${r}.' / 'You want to buy ${r}.'), then ask them to choose which token they want to spend to buy ${r} from the options below (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n d\xF9ng \u0111\u1EC3 mua.' / 'Please choose the token to spend.'). Do NOT list the tokens in text. Do NOT mention tool names, UI, or forms.`}}async checkPayBalance(e){let{args:t,userContext:n,chain:r,destSymbol:o,destContract:s,payAddr:a,payAmount:i,buyAmount:l,derivedFromBuy:u}=e,d=n.walletAddress,m=await this.readPayBalance(d,a,r),p=e.paySymbol??m?.symbol??"the selected token";if(!m){this.dbg("checkPayBalance: NOT held \u2192 pay picker fallback",{paySymbol:p});let k=await this.buildPayWithPicker({args:t,userContext:n,destSymbol:o,destContract:s,buyAmount:l,payAmount:i});return"actionButtons"in k?{error:"no_pay_balance",actionButtons:k.actionButtons,_instructions:`The user wanted to spend ${p} to buy ${o}, but their wallet holds no ${p} on this chain. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them they don't have any ${p} on this chain (e.g., 'B\u1EA1n kh\xF4ng c\xF3 ${p} tr\xEAn m\u1EA1ng n\xE0y.'), then ask them to choose one of the tokens they already hold to pay with (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n d\xF9ng \u0111\u1EC3 mua ${o} t\u1EEB danh s\xE1ch b\xEAn d\u01B0\u1EDBi.' / 'Please choose a token to pay with from the options below.'). Do NOT list the tokens in text, do NOT invent a balance, and do NOT mention tool names, UI, or forms.`}:k}if(!i||e.amountSizedToBalance)return null;let h=m.decimals!==void 0?$t(i,m.decimals):i;if(Dn(h,m.balanceFormatted)<=0)return null;let g=this.cleanAmountString(m.balanceFormatted),f=this.percentSpendButtons({balanceFormatted:m.balanceFormatted,balanceNum:m.balanceNum,decimals:m.decimals,paySymbol:p,destSymbol:o,buyWithTemplate:typeof t.buy_with_prompt_template=="string"?t.buy_with_prompt_template:""}),y=u?`Buying ${u} ${o} needs about ${i} ${p}, but they only have ${g} ${p} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them that buying ${u} ${o} would cost about ${i} ${p}, which is more than their balance of ${g} ${p}, then ask them to choose a smaller amount or spend less \u2014 `:`The user wants to spend ${i} ${p} to buy ${o}, but they only have ${g} ${p} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them ${i} ${p} exceeds their balance of ${g} ${p}, then ask them to choose a smaller amount \u2014 `;return{error:"insufficient_pay_balance",actionButtons:f,_instructions:y+'invite them to choose one of the options below (sized to their balance) or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT invent numbers, and do NOT mention tool names, UI, buttons, or forms.'}}async readPayBalance(e,t,n){if(!this.moralis)return null;let r=await this.moralis.getWalletTokenBalances({address:e,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[];this.dbg("readPayBalance",{payAddr:t,chain:n,ok:r.success,count:o.length,held:o.map(l=>({sym:l.symbol,addr:l.token_address,bal:l.balance_formatted}))});let s=t.toLowerCase(),a=o.find(l=>s==="native"?l.native_token===!0:l.token_address?.toLowerCase()===s);if(!a?.balance_formatted)return this.dbg("readPayBalance: pay token NOT held \u2192 null",{payAddr:t}),null;let i=Number(a.balance_formatted);if(!Number.isFinite(i)||i<=0)return this.dbg("readPayBalance: zero/invalid balance \u2192 null",{payAddr:t,balance:a.balance_formatted}),null;if(s==="native"&&n){let l=await Lt(n,"swap"),u=Mt(i,l,n);if(this.dbg("readPayBalance: native spendable",{balanceNum:i,spendable:u}),u<=0)return this.dbg("readPayBalance: native spendable \u2264 0 \u2192 null",{balanceNum:i}),null;let d=this.trimAmount(u),m=typeof a.decimals=="number"?a.decimals:18;return{balanceFormatted:d,balanceNum:u,symbol:a.symbol,decimals:m}}return this.dbg("readPayBalance: held",{payAddr:t,balance:a.balance_formatted,symbol:a.symbol}),{balanceFormatted:a.balance_formatted,balanceNum:i,symbol:a.symbol,decimals:typeof a.decimals=="number"?a.decimals:void 0}}percentSpendButtons(e){let{balanceFormatted:t,decimals:n,paySymbol:r,destSymbol:o,buyWithTemplate:s}=e,a=n??18;return[25,50,75,100].map(l=>{let u=Wt(this.cleanAmountString(t),BigInt(l),100n,a);return{label:`${l}%`,prompt:this.buildBuyWithPrompt(s,o,r,null,u)}})}async buildAmountPicker(e){let{userContext:t,chain:n,destSymbol:r,payAddr:o,buyWithTemplate:s}=e,a=t.walletAddress,i=await this.readPayBalance(a,o,n),l=e.paySymbol??i?.symbol;if(!i||!l)return null;let u=this.percentSpendButtons({balanceFormatted:i.balanceFormatted,balanceNum:i.balanceNum,decimals:i.decimals,paySymbol:l,destSymbol:r,buyWithTemplate:s}),d=this.cleanAmountString(i.balanceFormatted);return{actionButtons:u,_instructions:`The user's spendable balance is ${d} ${l}. Reply briefly in the user's language using the pattern 'state intent + balance \u2192 ask to choose': first state what the user wants and their spendable ${l} balance (e.g., 'B\u1EA1n mu\u1ED1n mua ${r} b\u1EB1ng ${l}, hi\u1EC7n c\xF3 ${d} ${l} c\xF3 th\u1EC3 d\xF9ng.' / 'You want to buy ${r} with ${l}; you have ${d} ${l} available.'), then ask how much they want to spend and invite them to choose one of the options below or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT mention tool names, UI, buttons, or forms.`}}trimAmount(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(8))):"0"}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),l=a+i,u;if(s>=0){let d=a.length+s;u=d>=l.length?l.padEnd(d,"0"):`${l.slice(0,d)}.${l.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${l}`;return u.includes(".")&&(u=u.replace(/\.?0+$/,"")),n?`-${u}`:u}cleanAmountString(e){if(!/e/i.test(e))return e;let t=Number(e);return Number.isFinite(t)?this.toPlainDecimal(t):e}buildBuyPrompt(e,t){return(e&&e.includes("{token}")?e:"buy {token}").replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}buildBuyWithPrompt(e,t,n,r,o){return(e&&e.includes("{token}")&&e.includes("{pay}")?e:"buy {buy_amount} {token} with {pay_amount} {pay}").replace(/\{token\}/g,t).replace(/\{pay\}/g,n).replace(/\{buy_amount\}/g,r??"").replace(/\{pay_amount\}/g,o??"").replace(/\s+/g," ").trim()}async buildTrendingPicker(e,t){if(!this.pantograph)return{error:"trending_unavailable",_instructions:"Trending tokens are unavailable in this build (service not configured). Tell the user briefly and ask them to name a token to buy."};let n=this.requireChain(e,t)??void 0,r=typeof e.limit=="number"&&Number.isFinite(e.limit)?Math.floor(e.limit):6,o=Math.max(1,Math.min(10,r)),s=await this.pantograph.getTrendingTokens({chain:n,limit:o}),a=s.success?s.data??[]:[];if(a.length===0)return{error:"no_trending_tokens",_instructions:"No trending tokens are available for this chain right now. Tell the user briefly and suggest they name a token directly."};let i=a.filter(p=>p.symbol),l=typeof e.buy_prompt_template=="string"?e.buy_prompt_template:"",u=i.map(p=>{let h=p.symbol;return{label:h,prompt:this.buildBuyPrompt(l,h)}}),m=i.map((p,h)=>{let g=p.name?.trim()||p.symbol,f=p.symbol,y=typeof p.usdPrice=="number"&&p.usdPrice>0?`$${this.formatPrice(p.usdPrice)}`:"N/A",k=p.pricePercentChange?.["24h"],w=typeof k=="number"&&Number.isFinite(k)?` (${k>0?"+":k<0?"-":""}${this.formatPercent(Math.abs(k))}%)`:"";return`${h+1}. ${g} (${f}) \u2014 ${y}${w}`}).join(`
170
177
  `)+`
171
178
 
172
- Is there any token you want to buy?`;return{actionButtons:u,_instructions:`The trending tokens are on ${n?L(n):"the connected chain"}. Reply in the user's language using the pattern 'state intent \u2192 ask to choose'. Start with a one-line header that states the user's intent (e.g., 'B\u1EA1n mu\u1ED1n mua token \u2014 \u0111\xE2y l\xE0 c\xE1c token \u0111ang trending tr\xEAn <chain name>:' / '\u{1F4C8} You want to buy a token \u2014 here are the tokens currently trending upwards on <chain name>:'), then output EXACTLY the following list, preserving the line breaks, ordering, prices, and percentages verbatim (translate only the wording, never the numbers). The list already ends with a 'choose' prompt. Do NOT add or remove tokens, and do NOT mention tool names, UI, or forms:
179
+ Is there any token you want to buy?`;return{actionButtons:u,_instructions:`The trending tokens are on ${n?I(n):"the connected chain"}. Reply in the user's language using the pattern 'state intent \u2192 ask to choose'. Start with a one-line header that states the user's intent (e.g., 'B\u1EA1n mu\u1ED1n mua token \u2014 \u0111\xE2y l\xE0 c\xE1c token \u0111ang trending tr\xEAn <chain name>:' / '\u{1F4C8} You want to buy a token \u2014 here are the tokens currently trending upwards on <chain name>:'), then output EXACTLY the following list, preserving the line breaks, ordering, prices, and percentages verbatim (translate only the wording, never the numbers). The list already ends with a 'choose' prompt. Do NOT add or remove tokens, and do NOT mention tool names, UI, or forms:
173
180
 
174
- ${p}`}}formatPercent(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(6))):"0"}formatPrice(e){if(e>=1)return e.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});if(e>=.01)return e.toFixed(4);let t=e.toFixed(12),n=t.match(/^0\.0*([1-9]\d{0,3})/);return n?t.slice(0,(n[0].length||0)+0):t}};var qt=class extends v{name="open-swap-token-form";kind="action";category="wallet-action";noSuggestions=!0;moralis;pantograph;debug;constructor(e,t){super(),e!==void 0&&(this.moralis=new E(e),this.pantograph=new Z({baseUrl:e.pantographUrl})),this.debug=t?.debug??!1}dbg(e,t){this.debug&&(t===void 0?console.log(`[SwapTokenTool] ${e}`):console.log(`[SwapTokenTool] ${e}`,JSON.stringify(t)))}description='Open the SWAP TOKEN flow for ANY swap/exchange/convert intent where the user swaps a token they ALREADY HOLD into another token \u2014 "swap USDC to USDT", "swap USDC", "\u0111\u1ED5i USDC sang ETH", "convert DAI to USDC", "exchange 10 USDC for PEPE". The FROM token is the user\'s own holding (the source). Handles both cases: (a) the user named the destination \u2014 "swap USDC to USDT": pass `from_symbol` (the source, e.g. "USDC") and `to_symbol` (the destination, e.g. "USDT"). (b) the user named only the source \u2014 "swap USDC": pass `from_symbol` and leave `to_symbol` blank; the tool returns buttons of the chain\'s current trending tokens so the user picks what to receive. If the user named NO token at all \u2014 "swap" / "\u0111\u1ED5i token": leave both blank and the tool returns the wallet\'s holdings so the user picks what to swap FROM. Amount: `from_amount` = source to SPEND; `to_amount` = destination to RECEIVE. Never both.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"from_symbol",type:"string",description:`Symbol of the SOURCE token the user wants to swap FROM \u2014 a token they already hold (e.g. "USDC"). Leave EMPTY only when the user named no token at all (e.g. "swap") \u2014 the tool then returns the wallet's holdings to pick the source from.`,required:!1},{name:"from_address",type:"string",description:`Source token address (0x\u2026), OR "native" for the chain's native coin. Leave empty when unknown \u2014 the tool resolves it from from_symbol.`,required:!1},{name:"from_amount",type:"string",description:'Quantity of the SOURCE token (the one being swapped FROM / spent) the user wants to SPEND. Set this when the amount sits next to the source token: "swap 10 USDC to USDT", "\u0111\u1ED5i 10 USDC sang ETH" \u2192 from_amount = the number next to USDC. May also be a PERCENT of the source balance ("swap 50% USDC to USDT" \u2192 "50%"), the literal word "max" meaning the WHOLE balance, or a USD value ("swap $5 USDC to USDT" \u2192 "5$"). NORMALIZE any "whole balance / everything / maximum" phrasing in ANY language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") to the literal "max"; pass plain/percent/USD numbers VERBATIM (the tool converts "%"/"$"/"max"). "max" is an AMOUNT, never a token \u2014 still set from_symbol to the source token ("ETH" in "swap max ETH to USDC"). Mutually exclusive with to_amount \u2014 set at most ONE of the two.',required:!1},{name:"to_symbol",type:"string",description:'Symbol of the DESTINATION token the user wants to RECEIVE (e.g. "USDT"). Leave EMPTY when the user did not name a destination (e.g. "swap USDC") \u2014 the tool then returns trending tokens to pick from.',required:!1},{name:"to_address",type:"string",description:`Destination token address (0x\u2026), OR "native" for the chain's native coin. Leave empty when unknown \u2014 the tool resolves it from to_symbol.`,required:!1},{name:"to_amount",type:"string",description:`Quantity of the DESTINATION token (the one being received) the user wants to RECEIVE. Set this when the amount sits next to the destination token: "swap USDC to 5 USDT" \u2192 to_amount = the number next to USDT. May also be a USD value ("swap USDC to $5 USDT" \u2192 "5$"); pass the user's expression VERBATIM (the tool converts "$"). Mutually exclusive with from_amount \u2014 set at most ONE.`,required:!1},{name:"limit",type:"number",description:"Only used when no destination is named: max number of trending tokens to show as buttons. Default 6. Clamped to [1, 10].",required:!1},{name:"swap_from_prompt_template",type:"string",description:`A short "swap {token}" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the source picker (when the user named no token). Use the exact placeholder "{token}" (do not translate the braces) for the source symbol; put the localized verb directly in the text. English example: "swap {token}". Vietnamese example: "\u0111\u1ED5i {token}". Japanese example: "{token} \u3092\u30B9\u30EF\u30C3\u30D7". Always include {token}.`,required:!0},{name:"swap_to_prompt_template",type:"string",description:`A short "swap X to Y" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the destination (trending) picker and the percentage-spend buttons. Use these exact placeholders (do not translate the braces): "{from}" = source token, "{to}" = destination token, "{from_amount}" = quantity of the SOURCE token (place it next to {from}), "{to_amount}" = quantity of the DESTINATION token (place it next to {to}). Put the localized verb and the "to" preposition directly in the text. English example: "swap {from_amount} {from} to {to_amount} {to}". Vietnamese example: "\u0111\u1ED5i {from_amount} {from} sang {to_amount} {to}". Japanese example: "{from_amount} {from} \u3092 {to_amount} {to} \u306B\u30B9\u30EF\u30C3\u30D7". Always include {from} and {to}. Include both {from_amount} and {to_amount} too \u2014 the tool removes whichever amount is unknown (only one side ever carries an amount).`,required:!0}];async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),o&&Array.isArray(o.actionButtons)&&o.actionButtons.length>0&&(s.actionButtons=o.actionButtons),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){if(!t?.walletAddress)return{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before swapping. Do NOT proceed."};let r=He(e.chain,t);return r?{error:"wrong_chain",_instructions:`The user asked to swap on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to swap on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can swap on ${r.connectedLabel} (their current network) instead. Do NOT proceed. Do NOT mention tool names, UI, or forms.`}:this.requireChain(e,t)?this.buildResult(e,t):{error:"missing_chain",_instructions:'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.'}}async buildResult(e,t){let n=typeof e.from_symbol=="string"?e.from_symbol.trim():"",r=typeof e.from_address=="string"?e.from_address.trim():"",o=typeof e.to_symbol=="string"?e.to_symbol.trim():"",s=this.requireChain(e,t)??void 0,a=t?.walletAddress??void 0;if(this.dbg("buildResult:args",{args:e,chain:s,walletAddress:a,hasMoralis:!!this.moralis}),!n&&!r)return this.dbg("\u2192 Branch 0: source picker (no source token named)"),await this.buildSourcePicker(e,t);let i=await this.resolveSide(e.from_address,n,s,a,"from"),l=i.address,u=i.symbol??n,d=i.decimals,p=await this.resolveSide(e.to_address,o,s,a,"to"),m=p.address,g=p.symbol??o,f=p.decimals,h=await this.resolveToAmountSpec(e.to_amount,m,s,g),y=l?await this.resolveFromAmountSpec(e.from_amount,l,s,a,u):this.normaliseAmount(e.from_amount),k=ye(e.from_amount),w=!!k&&k.kind!=="token",b=null;if(this.dbg("resolved sides",{from:{fromContract:l,fromSym:u,fromDecimals:d},to:{toContract:m,toSym:g,toDecimals:f},fromAmount:y,toAmount:h}),!l)return this.dbg("\u2192 source token not resolved"),{error:"from_token_not_found",_instructions:`Could not find the token "${u||r}" to swap from on this chain. Tell the user, in their language, that the token could not be found and ask them to check the name or provide its contract address. Do NOT invent an address. Do NOT mention tool names, UI, or forms.`};if(!m&&this.moralis&&t?.walletAddress)return this.dbg("\u2192 Branch 1: destination picker (dest token missing)"),await this.buildToTrendingPicker({args:e,userContext:t,fromSym:u,fromContract:l,fromAmount:y,toAmount:h});if(!m)return{error:"to_token_missing",_instructions:`Ask the user, in their language, which token they want to receive when swapping ${u}. Do NOT invent a token. Do NOT mention tool names, UI, or forms.`};if(h&&!y){this.dbg("Branch 1.3: deriving from amount from to amount",{toAmount:h,fromSym:u,toSym:g});let T=await this.deriveFromAmountFromTo({chain:s,toAmount:h,toContract:m,toSym:g,fromContract:l,fromSym:u});if(!T)return this.dbg("\u2192 Branch 1.3: price unavailable \u2014 needs from amount"),{error:"needs_from_amount",_instructions:`Could not work out how much ${u??"of the source token"} equals ${h} ${g} right now. Tell the user, in their language, that the price couldn't be fetched at the moment, and ask how much ${u??"of the source token"} they want to swap instead. Do NOT invent an amount or a price. Do NOT mention tool names, UI, or forms.`};y=T,b=h,w=!1,this.dbg("\u2192 Branch 1.3: derived from amount",{fromAmount:y,derivedFromTo:b})}if(this.moralis&&t?.walletAddress){this.dbg("Branch 1.4: checking source balance",{fromContract:l,fromSym:u,fromAmount:y});let T=await this.checkFromBalance({args:e,userContext:t,chain:s,fromContract:l,fromSym:u,fromAmount:y,toAmount:h,derivedFromTo:b,amountSizedToBalance:w});if(T)return this.dbg("\u2192 Branch 1.4: balance shortfall \u2014 returning",{error:T.error,hasButtons:Array.isArray(T.actionButtons)}),T;this.dbg("Branch 1.4: balance OK \u2014 continuing")}if(!y&&!h&&this.moralis&&t?.walletAddress){this.dbg("Branch 1.5: building amount picker");let T=await this.buildAmountPicker({userContext:t,chain:s,fromContract:l,fromSym:u,toSym:g,swapToTemplate:typeof e.swap_to_prompt_template=="string"?e.swap_to_prompt_template:""});if(T)return this.dbg("\u2192 Branch 1.5: amount picker returned"),T;this.dbg("Branch 1.5: amount picker null \u2014 needs amount")}if(y&&(d!==void 0||l==="native")){this.dbg("Branch 1.75: building confirm tx");let T=await this.buildConfirmTx({userContext:t,chain:s,toContract:m,toSym:g,toDecimals:f,fromContract:l,fromSym:u,fromDecimals:d,fromAmount:y});return T&&"ui"in T?(this.dbg("\u2192 Branch 1.75: confirm tx returned"),T):T&&"quoteError"in T?(this.dbg("\u2192 Branch 1.75: quote error from provider",{quoteError:T.quoteError}),{error:"quote_failed",quoteError:T.quoteError,_instructions:`Could not quote swapping ${u??"the source token"} to ${g}. The swap provider reported: "${T.quoteError}". Tell the user, in their language, exactly that reason (translate the wording, keep any numbers/limits verbatim) and suggest they adjust the amount or try again shortly. Do NOT invent numbers or a different reason. Do NOT mention tool names, UI, or forms.`}):(this.dbg("Branch 1.75: confirm tx unavailable (null)"),{error:"quote_failed",_instructions:`Could not get a quote to swap ${u??"the source token"} to ${g} right now. Tell the user, in their language, that the swap could not be quoted at the moment and ask them to try again shortly or with a different amount. Do NOT invent numbers. Do NOT mention tool names, UI, or forms.`})}return this.dbg("\u2192 needs from amount"),{error:"needs_from_amount",_instructions:`Ask the user, in their language, how much ${u??"of the source token"} they want to swap to ${g}. Do NOT invent an amount. Do NOT mention tool names, UI, or forms.`}}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return B(t)?t:null}normaliseAmount(e){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return String(e);if(typeof e!="string")return null;let t=e.trim();if(!t)return null;let n=parseFloat(t);return Number.isFinite(n)&&n>0?t:null}requireChain(e,t){let n=typeof e.chain=="string"&&e.chain.trim()?e.chain.trim():null;return n||(typeof t?.chain=="string"&&t.chain.trim()?t.chain.trim():null)}async resolveContractAddress(e,t,n){if(!this.moralis||!e)return;let r=e.trim().toLowerCase(),o=B(e.trim()),s=i=>o?(i.token_address??"").toLowerCase()===r:(i.symbol??"").toLowerCase()===r;if(n){let i=await this.moralis.getWalletTokenBalances({address:n,chain:t});if(i.success&&i.data?.result?.length){let l=i.data.result.find(s);if(l)return{address:l.token_address,symbol:l.symbol,name:l.name,decimals:typeof l.decimals=="number"?l.decimals:void 0}}}let a=await this.moralis.searchTokensByKey({key:e,chain:t});if(a.success&&a.data?.length){let l=a.data.find(s)??a.data[0];if(l?.token_address)return{address:l.token_address,symbol:l.symbol,name:l.name,decimals:typeof l.decimals=="number"?l.decimals:void 0}}}async resolveSide(e,t,n,r,o="side"){let s=typeof e=="string"?e.trim():"",a=typeof t=="string"?t.trim():"";if(s.toLowerCase()==="native"||a.toLowerCase()==="native")return{address:"native",symbol:a||void 0};let i=this.normaliseAddress(e),l=a||void 0,u=m=>m.toLowerCase()===O?"native":m,d=i??l??(s||void 0);if(!d)return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,resolved:"none"}),{address:null,symbol:l};let p=await this.resolveContractAddress(d,n,r);return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,key:d,lookup:p}),p?{address:u(p.address),symbol:l??p.symbol,decimals:p.decimals}:i?{address:u(i),symbol:l}:{address:null,symbol:l}}async buildConfirmTx(e){let{userContext:t,chain:n,toContract:r,toSym:o,toDecimals:s,fromContract:a,fromSym:i,fromDecimals:l,fromAmount:u}=e,d=t?.walletAddress;if(!d||!n||!r)return null;let p=ee(n),m=Number.parseInt(n,16);if(!p||!Number.isFinite(m))return null;let g=a==="native",f=g?p.native.decimals??18:l;if(f===void 0)return null;let h=this.toRawAmount(u,f);if(!h)return null;let y=g?O:a,k=r==="native"?O:r,w;try{w=await(await fe.getServiceByProvider("debridge")).getQuote({srcChainId:m,srcTokenAddress:y,srcTokenAmount:h,dstChainId:m,dstTokenAddress:k,recipientAddress:d,senderAddress:d,slippage:"auto",isCrossChain:!1})}catch(A){return{quoteError:A instanceof Error?A.message:String(A)}}if(!w.success)return this.dbg("buildConfirmTx: quote failed",{error:w.error,errorMessage:w.errorMessage}),{quoteError:this.extractQuoteError(w)};if(!w.tx)return{quoteError:"No route available for this swap."};let b=w.tx;if(!b.to||typeof b.data!="string")return null;let T={chainId:n,to:b.to,data:b.data,value:b.value??"0",from:d},P=this.extractOutRaw(w),S=P&&s!==void 0?this.trimAmount(Number(P)/10**s):void 0,C;g||(C=await this.buildApproveTx({chain:n,walletAddress:d,fromAddr:a,fromDec:f,rawAmount:h,quote:w}));let x={component:"SwapTokenConfirmTx",props:{chain:{hexId:n,name:p.name},fromToken:{address:g?"native":a,symbol:i,decimals:f,amount:u,rawAmount:h},toToken:{address:r,symbol:o,decimals:s,amount:S,rawAmount:P},estimatedOut:S,estimatedOutRaw:P,provider:w.provider,swapTx:T,approveTx:C}},I=S?` They will receive about ${S} ${o}.`:"";return{ui:x,_instructions:`A confirmation panel has been opened for swapping ${u} ${i??"the source token"} to ${o}.${I} Briefly tell the user, in their language, the amount they are swapping and the estimated amount they will receive, and ask them to review and confirm to complete the swap. The estimate is approximate \u2014 say "about"/"estimated", never a guaranteed amount. NEVER invent numbers not provided here. Do NOT mention tool names, UI, forms, or internal steps like approval.`}}async deriveFromAmountFromTo(e){let{chain:t,toAmount:n,toContract:r,fromContract:o}=e,[s,a]=await Promise.all([this.getUsdPrice(r,t),this.getUsdPrice(o,t)]);if(this.dbg("deriveFromAmountFromTo: prices",{toPrice:s,fromPrice:a}),!s||!a)return null;let i=Number(n);if(!Number.isFinite(i)||i<=0)return null;let l=1.02,u=i*s/a;return!Number.isFinite(u)||u<=0?null:this.trimAmount(u*l)}async getUsdPrice(e,t){if(!this.moralis)return null;let n=e==="native"?O:e,r=await this.moralis.getTokenMetadata({address:n,chain:t}),o=r.success?r.data?.usd_price:void 0;return typeof o=="number"&&Number.isFinite(o)&&o>0?o:null}async resolveFromAmountSpec(e,t,n,r,o){let s=ye(e);if(!s)return null;if(s.kind==="token")return this.trimAmount(s.value);if(s.kind==="percent"){if(!r)return null;let i=await this.readFromBalance(r,t,n);return this.dbg("resolveFromAmountSpec: percent",{percent:s.percent,fromSym:o,bal:i?.balanceNum}),!i||!Number.isFinite(i.balanceNum)||i.balanceNum<=0?null:s.percent>=100?this.cleanAmountString(i.balanceFormatted):this.trimAmount(i.balanceNum*s.percent/100)}let a=await this.getUsdPrice(t,n);return this.dbg("resolveFromAmountSpec: usd",{usd:s.usd,fromSym:o,price:a}),a?this.trimAmount(s.usd/a):null}async resolveToAmountSpec(e,t,n,r){let o=ye(e);if(!o)return null;if(o.kind==="token")return this.trimAmount(o.value);if(o.kind==="percent"||!t)return null;let s=await this.getUsdPrice(t,n);return this.dbg("resolveToAmountSpec: usd",{usd:o.usd,toSym:r,price:s}),s?this.trimAmount(o.usd/s):null}async buildApproveTx(e){let{chain:t,walletAddress:n,fromAddr:r,fromDec:o,rawAmount:s,quote:a}=e;try{let l=await(await fe.getServiceByProvider("debridge")).checkApproval({chain:t,userAddress:n,tokenAddress:r,amount:s,tokenDecimals:o,quoteData:a});if(!l.isNeeded)return;let u=l.approvalData??{};if(u.to&&typeof u.data=="string")return{chainId:t,to:u.to,data:u.data,value:u.value??"0",from:n};let d=l.contractAddress;if(!d)return;let p=Ge({abi:_n,functionName:"approve",args:[d,BigInt(s)]});return{chainId:t,to:r,data:p,value:"0",from:n}}catch{return}}toRawAmount(e,t){let n=e.trim();if(!/^\d*\.?\d+$/.test(n))return null;let[r,o=""]=n.split("."),s=o.slice(0,t).padEnd(t,"0");try{let a=BigInt(r||"0")*10n**BigInt(t)+BigInt(s||"0");return a<=0n?null:a.toString()}catch{return null}}extractQuoteError(e){if(e.errorMessage&&e.errorMessage.trim())return e.errorMessage.trim();let t=r=>{if(typeof r=="string"&&r.trim())return r.trim();if(r&&typeof r=="object"){let o=r;for(let s of[o.message,o.errorMessage,o.error])if(typeof s=="string"&&s.trim())return s.trim()}},n=e.raw??{};return t(e.error)??t(n.errorMessage)??t(n.error)??"The swap could not be quoted right now."}extractOutRaw(e){let t=e.raw??{},n=t.tokenOut?.amount??t.tokenOut?.minAmount??t.details?.currencyOut?.amount??t.details?.currencyOut?.minimumAmount??t.estimation?.dstChainTokenOut?.recommendedAmount??t.estimation?.dstChainTokenOut?.amount;return n&&/^\d+$/.test(n)?n:void 0}async buildSourcePicker(e,t){if(!this.moralis||!t?.walletAddress)return{error:"source_unavailable",_instructions:"Cannot list the wallet holdings in this build. Tell the user briefly and ask them to name the token they want to swap from."};let n=this.requireChain(e,t)??void 0,r=t.walletAddress,o=await this.moralis.getWalletTokenBalances({address:r,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),a=(o.success?o.data?.result??[]:[]).filter(u=>u.symbol?u.native_token?!0:!!u.token_address:!1).sort((u,d)=>(d.usd_value??0)-(u.usd_value??0)).slice(0,8);if(a.length===0)return{error:"no_swap_holdings",_instructions:"The wallet holds no tokens to swap on this chain. Tell the user to top up first. Do NOT mention tool names, UI, or forms."};let i=typeof e.swap_from_prompt_template=="string"?e.swap_from_prompt_template:"";return{actionButtons:a.map(u=>{let d=u.symbol,p=u.name?.trim()||d,m=u.balance_formatted?`${this.cleanAmountString(u.balance_formatted)} `:"",g=typeof u.usd_value=="number"&&u.usd_value>0?` ($${u.usd_value.toFixed(2)})`:"";return{label:`${p}: ${m}${d}${g}`,prompt:this.buildSwapFromPrompt(i,d)}}),_instructions:"Reply briefly in the user's language with two short sentences using the pattern 'state intent \u2192 ask to choose': first state what the user wants to do (e.g., 'B\u1EA1n mu\u1ED1n swap token.' / 'You want to swap a token.'), then ask them to choose which token to swap from in the options below (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n swap.' / 'Please choose the token to swap from.'). Do NOT list the tokens in text. Do NOT mention tool names, UI, or forms."}}async buildToTrendingPicker(e){let{args:t,userContext:n,fromSym:r,fromContract:o}=e,s=this.requireChain(t,n)??void 0,a=this.pantograph,i=typeof t.limit=="number"&&Number.isFinite(t.limit)?Math.floor(t.limit):6,l=Math.max(1,Math.min(10,i)),u=await a.getTrendingTokens({chain:s,limit:l+2}),d=u.success?u.data??[]:[],p=o?.toLowerCase(),m=r.toLowerCase(),g=d.filter(T=>!(!T.symbol||p&&p!=="native"&&T.tokenAddress&&T.tokenAddress.toLowerCase()===p||T.symbol.toLowerCase()===m)).slice(0,l);if(g.length===0)return{error:"no_trending_tokens",_instructions:`No trending tokens are available to swap ${r} into on this chain right now. Tell the user briefly and suggest they name the token they want to receive directly. Do NOT mention tool names, UI, or forms.`};let f=e.fromAmount??this.normaliseAmount(t.from_amount),h=e.toAmount??this.normaliseAmount(t.to_amount),y=typeof t.swap_to_prompt_template=="string"?t.swap_to_prompt_template:"",k=g.map(T=>{let P=T.symbol,S=f?this.buildSwapToPrompt(y,r,P,f,null):this.buildSwapToPrompt(y,r,P,null,h);return{label:P,prompt:S}}),b=g.map((T,P)=>{let S=T.name?.trim()||T.symbol,C=T.symbol,D=typeof T.usdPrice=="number"&&T.usdPrice>0?`$${this.formatPrice(T.usdPrice)}`:"N/A",x=T.pricePercentChange?.["24h"],I=typeof x=="number"&&Number.isFinite(x)?` (${x>0?"+":x<0?"-":""}${this.formatPercent(Math.abs(x))}%)`:"";return`${P+1}. ${S} (${C}) \u2014 ${D}${I}`}).join(`
181
+ ${m}`}}formatPercent(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(6))):"0"}formatPrice(e){if(e>=1)return e.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});if(e>=.01)return e.toFixed(4);let t=e.toFixed(12),n=t.match(/^0\.0*([1-9]\d{0,3})/);return n?t.slice(0,(n[0].length||0)+0):t}};var Ht=class extends v{name="open-swap-token-form";kind="action";category="wallet-action";noSuggestions=!0;moralis;pantograph;debug;constructor(e,t){super(),e!==void 0&&(this.moralis=new E(e),this.pantograph=new ee({baseUrl:e.pantographUrl})),this.debug=t?.debug??!1}dbg(e,t){this.debug&&(t===void 0?console.log(`[SwapTokenTool] ${e}`):console.log(`[SwapTokenTool] ${e}`,JSON.stringify(t)))}description='Open the SWAP TOKEN flow for ANY swap/exchange/convert intent where the user swaps a token they ALREADY HOLD into another token \u2014 "swap USDC to USDT", "swap USDC", "\u0111\u1ED5i USDC sang ETH", "convert DAI to USDC", "exchange 10 USDC for PEPE". The FROM token is the user\'s own holding (the source). Handles both cases: (a) the user named the destination \u2014 "swap USDC to USDT": pass `from_symbol` (the source, e.g. "USDC") and `to_symbol` (the destination, e.g. "USDT"). (b) the user named only the source \u2014 "swap USDC": pass `from_symbol` and leave `to_symbol` blank; the tool returns buttons of the chain\'s current trending tokens so the user picks what to receive. If the user named NO token at all \u2014 "swap" / "\u0111\u1ED5i token": leave both blank and the tool returns the wallet\'s holdings so the user picks what to swap FROM. Amount: `from_amount` = source to SPEND; `to_amount` = destination to RECEIVE. Never both.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"from_symbol",type:"string",description:`Symbol of the SOURCE token the user wants to swap FROM \u2014 a token they already hold (e.g. "USDC"). Leave EMPTY only when the user named no token at all (e.g. "swap") \u2014 the tool then returns the wallet's holdings to pick the source from.`,required:!1},{name:"from_address",type:"string",description:`Source token address (0x\u2026), OR "native" for the chain's native coin. Leave empty when unknown \u2014 the tool resolves it from from_symbol.`,required:!1},{name:"from_amount",type:"string",description:'Quantity of the SOURCE token (the one being swapped FROM / spent) the user wants to SPEND. Set this when the amount sits next to the source token: "swap 10 USDC to USDT", "\u0111\u1ED5i 10 USDC sang ETH" \u2192 from_amount = the number next to USDC. May also be a PERCENT of the source balance ("swap 50% USDC to USDT" \u2192 "50%"), the literal word "max" meaning the WHOLE balance, or a USD value ("swap $5 USDC to USDT" \u2192 "5$"). NORMALIZE any "whole balance / everything / maximum" phrasing in ANY language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") to the literal "max"; pass plain/percent/USD numbers VERBATIM (the tool converts "%"/"$"/"max"). "max" is an AMOUNT, never a token \u2014 still set from_symbol to the source token ("ETH" in "swap max ETH to USDC"). Mutually exclusive with to_amount \u2014 set at most ONE of the two.',required:!1},{name:"to_symbol",type:"string",description:'Symbol of the DESTINATION token the user wants to RECEIVE (e.g. "USDT"). Leave EMPTY when the user did not name a destination (e.g. "swap USDC") \u2014 the tool then returns trending tokens to pick from.',required:!1},{name:"to_address",type:"string",description:`Destination token address (0x\u2026), OR "native" for the chain's native coin. Leave empty when unknown \u2014 the tool resolves it from to_symbol.`,required:!1},{name:"to_amount",type:"string",description:`Quantity of the DESTINATION token (the one being received) the user wants to RECEIVE. Set this when the amount sits next to the destination token: "swap USDC to 5 USDT" \u2192 to_amount = the number next to USDT. May also be a USD value ("swap USDC to $5 USDT" \u2192 "5$"); pass the user's expression VERBATIM (the tool converts "$"). Mutually exclusive with from_amount \u2014 set at most ONE.`,required:!1},{name:"limit",type:"number",description:"Only used when no destination is named: max number of trending tokens to show as buttons. Default 6. Clamped to [1, 10].",required:!1},{name:"swap_from_prompt_template",type:"string",description:`A short "swap {token}" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the source picker (when the user named no token). Use the exact placeholder "{token}" (do not translate the braces) for the source symbol; put the localized verb directly in the text. English example: "swap {token}". Vietnamese example: "\u0111\u1ED5i {token}". Japanese example: "{token} \u3092\u30B9\u30EF\u30C3\u30D7". Always include {token}.`,required:!0},{name:"swap_to_prompt_template",type:"string",description:`A short "swap X to Y" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the destination (trending) picker and the percentage-spend buttons. Use these exact placeholders (do not translate the braces): "{from}" = source token, "{to}" = destination token, "{from_amount}" = quantity of the SOURCE token (place it next to {from}), "{to_amount}" = quantity of the DESTINATION token (place it next to {to}). Put the localized verb and the "to" preposition directly in the text. English example: "swap {from_amount} {from} to {to_amount} {to}". Vietnamese example: "\u0111\u1ED5i {from_amount} {from} sang {to_amount} {to}". Japanese example: "{from_amount} {from} \u3092 {to_amount} {to} \u306B\u30B9\u30EF\u30C3\u30D7". Always include {from} and {to}. Include both {from_amount} and {to_amount} too \u2014 the tool removes whichever amount is unknown (only one side ever carries an amount).`,required:!0}];async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),o&&Array.isArray(o.actionButtons)&&o.actionButtons.length>0&&(s.actionButtons=o.actionButtons),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){if(!t?.walletAddress)return{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before swapping. Do NOT proceed."};let r=He(e.chain,t);return r?{error:"wrong_chain",_instructions:`The user asked to swap on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to swap on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can swap on ${r.connectedLabel} (their current network) instead. Do NOT proceed. Do NOT mention tool names, UI, or forms.`}:this.requireChain(e,t)?this.buildResult(e,t):{error:"missing_chain",_instructions:'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.'}}async buildResult(e,t){let n=typeof e.from_symbol=="string"?e.from_symbol.trim():"",r=typeof e.from_address=="string"?e.from_address.trim():"",o=typeof e.to_symbol=="string"?e.to_symbol.trim():"",s=this.requireChain(e,t)??void 0,a=t?.walletAddress??void 0;if(this.dbg("buildResult:args",{args:e,chain:s,walletAddress:a,hasMoralis:!!this.moralis}),!n&&!r)return this.dbg("\u2192 Branch 0: source picker (no source token named)"),await this.buildSourcePicker(e,t);let i=await this.resolveSide(e.from_address,n,s,a,"from"),l=i.address,u=i.symbol??n,d=i.decimals,m=await this.resolveSide(e.to_address,o,s,a,"to"),p=m.address,h=m.symbol??o,g=m.decimals,f=await this.resolveToAmountSpec(e.to_amount,p,s,h),y=l?await this.resolveFromAmountSpec(e.from_amount,l,s,a,u,d??(l==="native"?18:void 0)):this.normaliseAmount(e.from_amount),k=be(e.from_amount),w=!!k&&k.kind!=="token",b=null;if(this.dbg("resolved sides",{from:{fromContract:l,fromSym:u,fromDecimals:d},to:{toContract:p,toSym:h,toDecimals:g},fromAmount:y,toAmount:f}),!l)return this.dbg("\u2192 source token not resolved"),{error:"from_token_not_found",_instructions:`Could not find the token "${u||r}" to swap from on this chain. Tell the user, in their language, that the token could not be found and ask them to check the name or provide its contract address. Do NOT invent an address. Do NOT mention tool names, UI, or forms.`};if(!p&&this.moralis&&t?.walletAddress)return this.dbg("\u2192 Branch 1: destination picker (dest token missing)"),await this.buildToTrendingPicker({args:e,userContext:t,fromSym:u,fromContract:l,fromAmount:y,toAmount:f});if(!p)return{error:"to_token_missing",_instructions:`Ask the user, in their language, which token they want to receive when swapping ${u}. Do NOT invent a token. Do NOT mention tool names, UI, or forms.`};if(f&&!y){this.dbg("Branch 1.3: deriving from amount from to amount",{toAmount:f,fromSym:u,toSym:h});let T=await this.deriveFromAmountFromTo({chain:s,toAmount:f,toContract:p,toSym:h,fromContract:l,fromSym:u});if(!T)return this.dbg("\u2192 Branch 1.3: price unavailable \u2014 needs from amount"),{error:"needs_from_amount",_instructions:`Could not work out how much ${u??"of the source token"} equals ${f} ${h} right now. Tell the user, in their language, that the price couldn't be fetched at the moment, and ask how much ${u??"of the source token"} they want to swap instead. Do NOT invent an amount or a price. Do NOT mention tool names, UI, or forms.`};y=T,b=f,w=!1,this.dbg("\u2192 Branch 1.3: derived from amount",{fromAmount:y,derivedFromTo:b})}if(this.moralis&&t?.walletAddress){this.dbg("Branch 1.4: checking source balance",{fromContract:l,fromSym:u,fromAmount:y});let T=await this.checkFromBalance({args:e,userContext:t,chain:s,fromContract:l,fromSym:u,fromAmount:y,toAmount:f,derivedFromTo:b,amountSizedToBalance:w});if(T)return this.dbg("\u2192 Branch 1.4: balance shortfall \u2014 returning",{error:T.error,hasButtons:Array.isArray(T.actionButtons)}),T;this.dbg("Branch 1.4: balance OK \u2014 continuing")}if(!y&&!f&&this.moralis&&t?.walletAddress){this.dbg("Branch 1.5: building amount picker");let T=await this.buildAmountPicker({userContext:t,chain:s,fromContract:l,fromSym:u,toSym:h,swapToTemplate:typeof e.swap_to_prompt_template=="string"?e.swap_to_prompt_template:""});if(T)return this.dbg("\u2192 Branch 1.5: amount picker returned"),T;this.dbg("Branch 1.5: amount picker null \u2014 needs amount")}if(y&&(d!==void 0||l==="native")){this.dbg("Branch 1.75: building confirm tx");let T=await this.buildConfirmTx({userContext:t,chain:s,toContract:p,toSym:h,toDecimals:g,fromContract:l,fromSym:u,fromDecimals:d,fromAmount:y});return T&&"ui"in T?(this.dbg("\u2192 Branch 1.75: confirm tx returned"),T):T&&"quoteError"in T?(this.dbg("\u2192 Branch 1.75: quote error from provider",{quoteError:T.quoteError}),{error:"quote_failed",quoteError:T.quoteError,_instructions:`Could not quote swapping ${u??"the source token"} to ${h}. The swap provider reported: "${T.quoteError}". Tell the user, in their language, exactly that reason (translate the wording, keep any numbers/limits verbatim) and suggest they adjust the amount or try again shortly. Do NOT invent numbers or a different reason. Do NOT mention tool names, UI, or forms.`}):(this.dbg("Branch 1.75: confirm tx unavailable (null)"),{error:"quote_failed",_instructions:`Could not get a quote to swap ${u??"the source token"} to ${h} right now. Tell the user, in their language, that the swap could not be quoted at the moment and ask them to try again shortly or with a different amount. Do NOT invent numbers. Do NOT mention tool names, UI, or forms.`})}return this.dbg("\u2192 needs from amount"),{error:"needs_from_amount",_instructions:`Ask the user, in their language, how much ${u??"of the source token"} they want to swap to ${h}. Do NOT invent an amount. Do NOT mention tool names, UI, or forms.`}}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return B(t)?t:null}normaliseAmount(e){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return String(e);if(typeof e!="string")return null;let t=e.trim();if(!t)return null;let n=parseFloat(t);return Number.isFinite(n)&&n>0?t:null}requireChain(e,t){let n=typeof e.chain=="string"&&e.chain.trim()?e.chain.trim():null;return n||(typeof t?.chain=="string"&&t.chain.trim()?t.chain.trim():null)}async resolveContractAddress(e,t,n){if(!this.moralis||!e)return;let r=e.trim().toLowerCase(),o=B(e.trim()),s=i=>o?(i.token_address??"").toLowerCase()===r:(i.symbol??"").toLowerCase()===r;if(n){let i=await this.moralis.getWalletTokenBalances({address:n,chain:t});if(i.success&&i.data?.result?.length){let l=i.data.result.find(s);if(l)return{address:l.token_address,symbol:l.symbol,name:l.name,decimals:typeof l.decimals=="number"?l.decimals:void 0}}}let a=await this.moralis.searchTokensByKey({key:e,chain:t});if(a.success&&a.data?.length){let l=a.data.find(s)??a.data[0];if(l?.token_address)return{address:l.token_address,symbol:l.symbol,name:l.name,decimals:typeof l.decimals=="number"?l.decimals:void 0}}}async resolveSide(e,t,n,r,o="side"){let s=typeof e=="string"?e.trim():"",a=typeof t=="string"?t.trim():"";if(s.toLowerCase()==="native"||a.toLowerCase()==="native")return{address:"native",symbol:a||void 0};let i=this.normaliseAddress(e),l=a||void 0,u=p=>p.toLowerCase()===O?"native":p,d=i??l??(s||void 0);if(!d)return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,resolved:"none"}),{address:null,symbol:l};let m=await this.resolveContractAddress(d,n,r);return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,key:d,lookup:m}),m?{address:u(m.address),symbol:l??m.symbol,decimals:m.decimals}:i?{address:u(i),symbol:l}:{address:null,symbol:l}}async buildConfirmTx(e){let{userContext:t,chain:n,toContract:r,toSym:o,toDecimals:s,fromContract:a,fromSym:i,fromDecimals:l,fromAmount:u}=e,d=t?.walletAddress;if(!d||!n||!r)return null;let m=te(n),p=Number.parseInt(n,16);if(!m||!Number.isFinite(p))return null;let h=a==="native",g=h?m.native.decimals??18:l;if(g===void 0)return null;let f=this.toRawAmount(u,g);if(!f)return null;let y=h?O:a,k=r==="native"?O:r,w;try{w=await(await ye.getServiceByProvider("debridge")).getQuote({srcChainId:p,srcTokenAddress:y,srcTokenAmount:f,dstChainId:p,dstTokenAddress:k,recipientAddress:d,senderAddress:d,slippage:"auto",isCrossChain:!1})}catch(A){return{quoteError:A instanceof Error?A.message:String(A)}}if(!w.success)return this.dbg("buildConfirmTx: quote failed",{error:w.error,errorMessage:w.errorMessage}),{quoteError:this.extractQuoteError(w)};if(!w.tx)return{quoteError:"No route available for this swap."};let b=w.tx;if(!b.to||typeof b.data!="string")return null;let T={chainId:n,to:b.to,data:b.data,value:b.value??"0",from:d},P=this.extractOutRaw(w),S=P&&s!==void 0?this.trimAmount(Number(P)/10**s):void 0,C;h||(C=await this.buildApproveTx({chain:n,walletAddress:d,fromAddr:a,fromDec:g,rawAmount:f,quote:w}));let x={component:"SwapTokenConfirmTx",props:{chain:{hexId:n,name:m.name},fromToken:{address:h?"native":a,symbol:i,decimals:g,amount:u,rawAmount:f},toToken:{address:r,symbol:o,decimals:s,amount:S,rawAmount:P},estimatedOut:S,estimatedOutRaw:P,provider:w.provider,swapTx:T,approveTx:C}},D=S?` They will receive about ${S} ${o}.`:"";return{ui:x,_instructions:`A confirmation panel has been opened for swapping ${u} ${i??"the source token"} to ${o}.${D} Briefly tell the user, in their language, the amount they are swapping and the estimated amount they will receive, and ask them to review and confirm to complete the swap. The estimate is approximate \u2014 say "about"/"estimated", never a guaranteed amount. NEVER invent numbers not provided here. Do NOT mention tool names, UI, forms, or internal steps like approval.`}}async deriveFromAmountFromTo(e){let{chain:t,toAmount:n,toContract:r,fromContract:o}=e,[s,a]=await Promise.all([this.getUsdPrice(r,t),this.getUsdPrice(o,t)]);if(this.dbg("deriveFromAmountFromTo: prices",{toPrice:s,fromPrice:a}),!s||!a)return null;let i=Number(n);if(!Number.isFinite(i)||i<=0)return null;let l=1.02,u=i*s/a;return!Number.isFinite(u)||u<=0?null:this.trimAmount(u*l)}async getUsdPrice(e,t){if(!this.moralis)return null;let n=e==="native"?O:e,r=await this.moralis.getTokenMetadata({address:n,chain:t}),o=r.success?r.data?.usd_price:void 0;return typeof o=="number"&&Number.isFinite(o)&&o>0?o:null}async resolveFromAmountSpec(e,t,n,r,o,s){let a=be(e);if(!a)return null;let i=u=>s===void 0?u:$t(u,s);if(a.kind==="token")return i(Ft(e)??this.toPlainDecimal(a.value));if(a.kind==="percent"){if(!r)return null;let u=await this.readFromBalance(r,t,n);if(this.dbg("resolveFromAmountSpec: percent",{percent:a.percent,fromSym:o,bal:u?.balanceNum}),!u||!Number.isFinite(u.balanceNum)||u.balanceNum<=0)return null;let d=u.decimals??s??18;return a.percent>=100?this.cleanAmountString(u.balanceFormatted):Wt(u.balanceFormatted,BigInt(Math.round(a.percent*1e6)),100000000n,d)}let l=await this.getUsdPrice(t,n);return this.dbg("resolveFromAmountSpec: usd",{usd:a.usd,fromSym:o,price:l}),l?i(this.trimAmount(a.usd/l)):null}async resolveToAmountSpec(e,t,n,r){let o=be(e);if(!o)return null;if(o.kind==="token")return Ft(e)??this.toPlainDecimal(o.value);if(o.kind==="percent"||!t)return null;let s=await this.getUsdPrice(t,n);return this.dbg("resolveToAmountSpec: usd",{usd:o.usd,toSym:r,price:s}),s?this.trimAmount(o.usd/s):null}async buildApproveTx(e){let{chain:t,walletAddress:n,fromAddr:r,fromDec:o,rawAmount:s,quote:a}=e;try{let l=await(await ye.getServiceByProvider("debridge")).checkApproval({chain:t,userAddress:n,tokenAddress:r,amount:s,tokenDecimals:o,quoteData:a});if(!l.isNeeded)return;let u=l.approvalData??{};if(u.to&&typeof u.data=="string")return{chainId:t,to:u.to,data:u.data,value:u.value??"0",from:n};let d=l.contractAddress;if(!d)return;let m=Ge({abi:Nn,functionName:"approve",args:[d,BigInt(s)]});return{chainId:t,to:r,data:m,value:"0",from:n}}catch{return}}toRawAmount(e,t){let n=e.trim();if(!/^\d*\.?\d+$/.test(n))return null;let[r,o=""]=n.split("."),s=o.slice(0,t).padEnd(t,"0");try{let a=BigInt(r||"0")*10n**BigInt(t)+BigInt(s||"0");return a<=0n?null:a.toString()}catch{return null}}extractQuoteError(e){if(e.errorMessage&&e.errorMessage.trim())return e.errorMessage.trim();let t=r=>{if(typeof r=="string"&&r.trim())return r.trim();if(r&&typeof r=="object"){let o=r;for(let s of[o.message,o.errorMessage,o.error])if(typeof s=="string"&&s.trim())return s.trim()}},n=e.raw??{};return t(e.error)??t(n.errorMessage)??t(n.error)??"The swap could not be quoted right now."}extractOutRaw(e){let t=e.raw??{},n=t.tokenOut?.amount??t.tokenOut?.minAmount??t.details?.currencyOut?.amount??t.details?.currencyOut?.minimumAmount??t.estimation?.dstChainTokenOut?.recommendedAmount??t.estimation?.dstChainTokenOut?.amount;return n&&/^\d+$/.test(n)?n:void 0}async buildSourcePicker(e,t){if(!this.moralis||!t?.walletAddress)return{error:"source_unavailable",_instructions:"Cannot list the wallet holdings in this build. Tell the user briefly and ask them to name the token they want to swap from."};let n=this.requireChain(e,t)??void 0,r=t.walletAddress,o=await this.moralis.getWalletTokenBalances({address:r,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),a=(o.success?o.data?.result??[]:[]).filter(u=>u.symbol?u.native_token?!0:!!u.token_address:!1).sort((u,d)=>(d.usd_value??0)-(u.usd_value??0)).slice(0,8);if(a.length===0)return{error:"no_swap_holdings",_instructions:"The wallet holds no tokens to swap on this chain. Tell the user to top up first. Do NOT mention tool names, UI, or forms."};let i=typeof e.swap_from_prompt_template=="string"?e.swap_from_prompt_template:"";return{actionButtons:a.map(u=>{let d=u.symbol,m=u.name?.trim()||d,p=u.balance_formatted?`${this.cleanAmountString(u.balance_formatted)} `:"",h=typeof u.usd_value=="number"&&u.usd_value>0?` ($${u.usd_value.toFixed(2)})`:"";return{label:`${m}: ${p}${d}${h}`,prompt:this.buildSwapFromPrompt(i,d)}}),_instructions:"Reply briefly in the user's language with two short sentences using the pattern 'state intent \u2192 ask to choose': first state what the user wants to do (e.g., 'B\u1EA1n mu\u1ED1n swap token.' / 'You want to swap a token.'), then ask them to choose which token to swap from in the options below (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n swap.' / 'Please choose the token to swap from.'). Do NOT list the tokens in text. Do NOT mention tool names, UI, or forms."}}async buildToTrendingPicker(e){let{args:t,userContext:n,fromSym:r,fromContract:o}=e,s=this.requireChain(t,n)??void 0,a=this.pantograph,i=typeof t.limit=="number"&&Number.isFinite(t.limit)?Math.floor(t.limit):6,l=Math.max(1,Math.min(10,i)),u=await a.getTrendingTokens({chain:s,limit:l+2}),d=u.success?u.data??[]:[],m=o?.toLowerCase(),p=r.toLowerCase(),h=d.filter(T=>!(!T.symbol||m&&m!=="native"&&T.tokenAddress&&T.tokenAddress.toLowerCase()===m||T.symbol.toLowerCase()===p)).slice(0,l);if(h.length===0)return{error:"no_trending_tokens",_instructions:`No trending tokens are available to swap ${r} into on this chain right now. Tell the user briefly and suggest they name the token they want to receive directly. Do NOT mention tool names, UI, or forms.`};let g=e.fromAmount??this.normaliseAmount(t.from_amount),f=e.toAmount??this.normaliseAmount(t.to_amount),y=typeof t.swap_to_prompt_template=="string"?t.swap_to_prompt_template:"",k=h.map(T=>{let P=T.symbol,S=g?this.buildSwapToPrompt(y,r,P,g,null):this.buildSwapToPrompt(y,r,P,null,f);return{label:P,prompt:S}}),b=h.map((T,P)=>{let S=T.name?.trim()||T.symbol,C=T.symbol,L=typeof T.usdPrice=="number"&&T.usdPrice>0?`$${this.formatPrice(T.usdPrice)}`:"N/A",x=T.pricePercentChange?.["24h"],D=typeof x=="number"&&Number.isFinite(x)?` (${x>0?"+":x<0?"-":""}${this.formatPercent(Math.abs(x))}%)`:"";return`${P+1}. ${S} (${C}) \u2014 ${L}${D}`}).join(`
175
182
  `)+`
176
183
 
177
- Which token do you want to receive?`;return{actionButtons:k,_instructions:`The user wants to swap ${r} into another token. The trending tokens are on ${s?L(s):"the connected chain"}. Reply in the user's language using the pattern 'state intent \u2192 ask to choose'. Start with a one-line header that states the user's intent (e.g., 'B\u1EA1n mu\u1ED1n swap ${r} sang token kh\xE1c \u2014 \u0111\xE2y l\xE0 c\xE1c token \u0111ang trending tr\xEAn <chain name>:' / '\u{1F4C8} You want to swap ${r} \u2014 here are the tokens currently trending on <chain name>:'), then output EXACTLY the following list, preserving the line breaks, ordering, prices, and percentages verbatim (translate only the wording, never the numbers). The list already ends with a 'choose' prompt. Do NOT add or remove tokens, and do NOT mention tool names, UI, or forms:
184
+ Which token do you want to receive?`;return{actionButtons:k,_instructions:`The user wants to swap ${r} into another token. The trending tokens are on ${s?I(s):"the connected chain"}. Reply in the user's language using the pattern 'state intent \u2192 ask to choose'. Start with a one-line header that states the user's intent (e.g., 'B\u1EA1n mu\u1ED1n swap ${r} sang token kh\xE1c \u2014 \u0111\xE2y l\xE0 c\xE1c token \u0111ang trending tr\xEAn <chain name>:' / '\u{1F4C8} You want to swap ${r} \u2014 here are the tokens currently trending on <chain name>:'), then output EXACTLY the following list, preserving the line breaks, ordering, prices, and percentages verbatim (translate only the wording, never the numbers). The list already ends with a 'choose' prompt. Do NOT add or remove tokens, and do NOT mention tool names, UI, or forms:
178
185
 
179
- ${b}`}}async checkFromBalance(e){let{args:t,userContext:n,chain:r,fromContract:o,fromAmount:s,derivedFromTo:a}=e,i=n.walletAddress,l=await this.readFromBalance(i,o,r),u=e.fromSym??l?.symbol??"the selected token";if(!l){this.dbg("checkFromBalance: NOT held \u2192 source picker fallback",{fromSym:u});let f=await this.buildSourcePicker(t,n);return"actionButtons"in f?{error:"no_from_balance",actionButtons:f.actionButtons,_instructions:`The user wanted to swap ${u}, but their wallet holds no ${u} on this chain. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them they don't have any ${u} on this chain (e.g., 'B\u1EA1n kh\xF4ng c\xF3 ${u} tr\xEAn m\u1EA1ng n\xE0y.'), then ask them to choose one of the tokens they already hold to swap from (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n swap t\u1EEB danh s\xE1ch b\xEAn d\u01B0\u1EDBi.' / 'Please choose a token to swap from the options below.'). Do NOT list the tokens in text, do NOT invent a balance, and do NOT mention tool names, UI, or forms.`}:f}if(!s||e.amountSizedToBalance)return null;let d=Number(s);if(!Number.isFinite(d)||d<=l.balanceNum)return null;let p=this.trimAmount(l.balanceNum),m=this.percentSpendButtons({balanceFormatted:l.balanceFormatted,balanceNum:l.balanceNum,fromSym:u,toSym:typeof t.to_symbol=="string"?t.to_symbol.trim():l.symbol??"the destination token",swapToTemplate:typeof t.swap_to_prompt_template=="string"?t.swap_to_prompt_template:""}),g=a?`Receiving ${a} of the destination token needs about ${s} ${u}, but they only have ${p} ${u} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them this would cost about ${s} ${u}, which is more than their balance of ${p} ${u}, then ask them to choose a smaller amount \u2014 `:`The user wants to swap ${s} ${u}, but they only have ${p} ${u} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them ${s} ${u} exceeds their balance of ${p} ${u}, then ask them to choose a smaller amount \u2014 `;return{error:"insufficient_from_balance",actionButtons:m,_instructions:g+'invite them to choose one of the options below (sized to their balance) or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT invent numbers, and do NOT mention tool names, UI, buttons, or forms.'}}async readFromBalance(e,t,n){if(!this.moralis)return null;let r=await this.moralis.getWalletTokenBalances({address:e,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[];this.dbg("readFromBalance",{fromAddr:t,chain:n,ok:r.success,count:o.length});let s=t.toLowerCase(),a=o.find(l=>s==="native"?l.native_token===!0:l.token_address?.toLowerCase()===s);if(!a?.balance_formatted)return this.dbg("readFromBalance: source token NOT held \u2192 null",{fromAddr:t}),null;let i=Number(a.balance_formatted);if(!Number.isFinite(i)||i<=0)return this.dbg("readFromBalance: zero/invalid balance \u2192 null",{fromAddr:t,balance:a.balance_formatted}),null;if(s==="native"&&n){let l=await It(n,"swap"),u=Dt(i,l,n);return this.dbg("readFromBalance: native spendable",{balanceNum:i,spendable:u}),u<=0?(this.dbg("readFromBalance: native spendable \u2264 0 \u2192 null",{balanceNum:i}),null):{balanceFormatted:this.trimAmount(u),balanceNum:u,symbol:a.symbol}}return this.dbg("readFromBalance: held",{fromAddr:t,balance:a.balance_formatted,symbol:a.symbol}),{balanceFormatted:a.balance_formatted,balanceNum:i,symbol:a.symbol}}percentSpendButtons(e){let{balanceFormatted:t,balanceNum:n,fromSym:r,toSym:o,swapToTemplate:s}=e;return[.25,.5,.75,1].map(i=>{let l=i===1?this.cleanAmountString(t):this.trimAmount(n*i);return{label:`${Math.round(i*100)}%`,prompt:this.buildSwapToPrompt(s,r,o,l,null)}})}async buildAmountPicker(e){let{userContext:t,chain:n,fromContract:r,toSym:o,swapToTemplate:s}=e,a=t.walletAddress,i=await this.readFromBalance(a,r,n),l=e.fromSym??i?.symbol;if(!i||!l)return null;let u=this.percentSpendButtons({balanceFormatted:i.balanceFormatted,balanceNum:i.balanceNum,fromSym:l,toSym:o,swapToTemplate:s}),d=this.trimAmount(i.balanceNum);return{actionButtons:u,_instructions:`The user's spendable balance is ${d} ${l}. Reply briefly in the user's language using the pattern 'state intent + balance \u2192 ask to choose': first state what the user wants and their spendable ${l} balance (e.g., 'B\u1EA1n mu\u1ED1n swap ${l} sang ${o}, hi\u1EC7n c\xF3 ${d} ${l} c\xF3 th\u1EC3 d\xF9ng.' / 'You want to swap ${l} to ${o}; you have ${d} ${l} available.'), then ask how much they want to swap and invite them to choose one of the options below or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT mention tool names, UI, buttons, or forms.`}}trimAmount(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(8))):"0"}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),l=a+i,u;if(s>=0){let d=a.length+s;u=d>=l.length?l.padEnd(d,"0"):`${l.slice(0,d)}.${l.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${l}`;return u.includes(".")&&(u=u.replace(/\.?0+$/,"")),n?`-${u}`:u}cleanAmountString(e){if(!/e/i.test(e))return e;let t=Number(e);return Number.isFinite(t)?this.toPlainDecimal(t):e}buildSwapFromPrompt(e,t){return(e&&e.includes("{token}")?e:"swap {token}").replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}buildSwapToPrompt(e,t,n,r,o){return(e&&e.includes("{from}")&&e.includes("{to}")?e:"swap {from_amount} {from} to {to_amount} {to}").replace(/\{from\}/g,t).replace(/\{to\}/g,n).replace(/\{from_amount\}/g,r??"").replace(/\{to_amount\}/g,o??"").replace(/\s+/g," ").trim()}formatPercent(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(6))):"0"}formatPrice(e){if(e>=1)return e.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});if(e>=.01)return e.toFixed(4);let t=e.toFixed(12),n=t.match(/^0\.0*([1-9]\d{0,3})/);return n?t.slice(0,(n[0].length||0)+0):t}};var Ft=class extends ae{name="open-send-nft-form";actionType="send_nft";component="SendNftForm";userInputFields=[{key:"to_address",label:"the recipient address"}];url;constructor(e,t){super(e);let n=t?.url?.trim();this.url=n||void 0}description=`Open the SEND NFT form. Use for ANY NFT send/transfer intent: "send NFT", "transfer my Bored Ape", "send token id 1234 to 0x\u2026", "g\u1EEDi NFT". Call this tool EVEN IF the user did not name a contract, token id, NFT name, or recipient \u2014 the tool resolves missing identifiers from the wallet's NFT holdings and emits picker buttons when more than one match exists. Do NOT use this for ERC-20s (use open-send-token-form) or the native coin (open-send-native-form).`;parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"contract_address",type:"string",description:'NFT COLLECTION contract address (0x\u2026) \u2014 identifies WHICH NFT collection to send from. Only set this when the user explicitly named the collection/contract (e.g. "contract 0x\u2026", "collection 0x\u2026", "from 0x\u2026"). NEVER put the recipient address here. Omit when unknown.',required:!1},{name:"token_id",type:"string",description:'NFT token id as a decimal string (e.g. "1234"). Omit when unknown.',required:!1},{name:"nft_name",type:"string",description:'Free-form NFT or collection name as the user said it (e.g. "Bored Ape"). Used to look up the NFT in the wallet when no contract_address / token_id is provided. Omit when unknown.',required:!1},{name:"token_standard",type:"string",description:'NFT contract type ("ERC721" or "ERC1155"). Optional; the tool re-derives it from the wallet row.',required:!1},{name:"to_address",type:"string",description:'RECIPIENT EVM address (0x\u2026) \u2014 the wallet that will RECEIVE the NFT. Only set this when the user explicitly named a destination (e.g. "send to 0x\u2026", "to 0x\u2026", "g\u1EEDi \u0111\u1EBFn 0x\u2026", "cho 0x\u2026"). NEVER put the NFT collection / contract address here. Omit when unknown.',required:!1},{name:"amount",type:"string",description:"Number of editions to send. Only meaningful for ERC-1155; for ERC-721 the tool forces 1. Pass as a positive integer string. Omit when unknown \u2014 defaults to 1.",required:!1}];async buildParameters(e,t){let n=this.requireChain(e,t)??void 0,r=t?.walletAddress,o=this.normaliseAddress(e.contract_address),s=this.normaliseTokenId(e.token_id),a=this.normaliseString(e.nft_name),i=this.normaliseAddress(e.to_address),l=this.normaliseString(e.token_standard),u=this.normaliseAmountInt(e.amount);if(!o&&!s&&!a)return{error:"no_nft_specified",_instructions:"The user wants to send an NFT but did not name one. In their language, ask them which NFT they would like to send \u2014 by name, by collection contract address, or by token id. Do NOT open the form. Do NOT mention tool names, UI, or forms."};if(!this.moralis||!r)return this.paramsFromInputs({contract:o,tokenId:s,toAddress:i,standardArg:l,nftName:a,amountInt:u});let d=await this.moralis.getWalletNFTs({address:r,chain:n,limit:100,excludeSpam:!1}),p=d.success?d.data?.result??[]:[],m=this.matchNfts(p,{contract:o,tokenId:s,nftName:a});if(m.length===0){let h=a??(o&&s?`#${s}`:o??`#${s??""}`),y=L(n);return{error:"nft_not_owned",_instructions:`The user asked to send "${h}" but the wallet does not hold a matching NFT on ${y}. Tell them, in their language, that they do not own that NFT on ${y} and ask them to pick another one (by name, contract, or token id). Refer to the chain by this name \u2014 never as a hex id. Do NOT open the form. Do NOT mention tool names, UI, or forms.`}}if(m.length===1)return this.paramsFromNft(m[0],{toAddress:i,amountInt:u});let f=`Please select the NFT you would like to send from the list and proceed via ${this.url?`[this website](${this.url})`:"this website"}`;return{error:"multiple_nfts_matched",message:f,_instructions:`This is a fixed notice. Output it to the user EXACTLY as written below \u2014 in English, without translating, rephrasing, summarising, or adding/removing any words. Preserve the Markdown link verbatim (do not change, wrap, or drop the URL or the linked words). Do NOT open the form. Do NOT mention tool names, UI, or forms. Reply with only this message:
186
+ ${b}`}}async checkFromBalance(e){let{args:t,userContext:n,chain:r,fromContract:o,fromAmount:s,derivedFromTo:a}=e,i=n.walletAddress,l=await this.readFromBalance(i,o,r),u=e.fromSym??l?.symbol??"the selected token";if(!l){this.dbg("checkFromBalance: NOT held \u2192 source picker fallback",{fromSym:u});let g=await this.buildSourcePicker(t,n);return"actionButtons"in g?{error:"no_from_balance",actionButtons:g.actionButtons,_instructions:`The user wanted to swap ${u}, but their wallet holds no ${u} on this chain. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them they don't have any ${u} on this chain (e.g., 'B\u1EA1n kh\xF4ng c\xF3 ${u} tr\xEAn m\u1EA1ng n\xE0y.'), then ask them to choose one of the tokens they already hold to swap from (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n swap t\u1EEB danh s\xE1ch b\xEAn d\u01B0\u1EDBi.' / 'Please choose a token to swap from the options below.'). Do NOT list the tokens in text, do NOT invent a balance, and do NOT mention tool names, UI, or forms.`}:g}if(!s||e.amountSizedToBalance)return null;let d=l.decimals!==void 0?$t(s,l.decimals):s;if(Dn(d,l.balanceFormatted)<=0)return null;let m=this.cleanAmountString(l.balanceFormatted),p=this.percentSpendButtons({balanceFormatted:l.balanceFormatted,balanceNum:l.balanceNum,decimals:l.decimals,fromSym:u,toSym:typeof t.to_symbol=="string"?t.to_symbol.trim():l.symbol??"the destination token",swapToTemplate:typeof t.swap_to_prompt_template=="string"?t.swap_to_prompt_template:""}),h=a?`Receiving ${a} of the destination token needs about ${s} ${u}, but they only have ${m} ${u} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them this would cost about ${s} ${u}, which is more than their balance of ${m} ${u}, then ask them to choose a smaller amount \u2014 `:`The user wants to swap ${s} ${u}, but they only have ${m} ${u} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them ${s} ${u} exceeds their balance of ${m} ${u}, then ask them to choose a smaller amount \u2014 `;return{error:"insufficient_from_balance",actionButtons:p,_instructions:h+'invite them to choose one of the options below (sized to their balance) or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT invent numbers, and do NOT mention tool names, UI, buttons, or forms.'}}async readFromBalance(e,t,n){if(!this.moralis)return null;let r=await this.moralis.getWalletTokenBalances({address:e,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[];this.dbg("readFromBalance",{fromAddr:t,chain:n,ok:r.success,count:o.length});let s=t.toLowerCase(),a=o.find(l=>s==="native"?l.native_token===!0:l.token_address?.toLowerCase()===s);if(!a?.balance_formatted)return this.dbg("readFromBalance: source token NOT held \u2192 null",{fromAddr:t}),null;let i=Number(a.balance_formatted);if(!Number.isFinite(i)||i<=0)return this.dbg("readFromBalance: zero/invalid balance \u2192 null",{fromAddr:t,balance:a.balance_formatted}),null;if(s==="native"&&n){let l=await Lt(n,"swap"),u=Mt(i,l,n);if(this.dbg("readFromBalance: native spendable",{balanceNum:i,spendable:u}),u<=0)return this.dbg("readFromBalance: native spendable \u2264 0 \u2192 null",{balanceNum:i}),null;let d=this.trimAmount(u),m=typeof a.decimals=="number"?a.decimals:18;return{balanceFormatted:d,balanceNum:u,symbol:a.symbol,decimals:m}}return this.dbg("readFromBalance: held",{fromAddr:t,balance:a.balance_formatted,symbol:a.symbol}),{balanceFormatted:a.balance_formatted,balanceNum:i,symbol:a.symbol,decimals:typeof a.decimals=="number"?a.decimals:void 0}}percentSpendButtons(e){let{balanceFormatted:t,decimals:n,fromSym:r,toSym:o,swapToTemplate:s}=e,a=n??18;return[25,50,75,100].map(l=>{let u=Wt(this.cleanAmountString(t),BigInt(l),100n,a);return{label:`${l}%`,prompt:this.buildSwapToPrompt(s,r,o,u,null)}})}async buildAmountPicker(e){let{userContext:t,chain:n,fromContract:r,toSym:o,swapToTemplate:s}=e,a=t.walletAddress,i=await this.readFromBalance(a,r,n),l=e.fromSym??i?.symbol;if(!i||!l)return null;let u=this.percentSpendButtons({balanceFormatted:i.balanceFormatted,balanceNum:i.balanceNum,decimals:i.decimals,fromSym:l,toSym:o,swapToTemplate:s}),d=this.cleanAmountString(i.balanceFormatted);return{actionButtons:u,_instructions:`The user's spendable balance is ${d} ${l}. Reply briefly in the user's language using the pattern 'state intent + balance \u2192 ask to choose': first state what the user wants and their spendable ${l} balance (e.g., 'B\u1EA1n mu\u1ED1n swap ${l} sang ${o}, hi\u1EC7n c\xF3 ${d} ${l} c\xF3 th\u1EC3 d\xF9ng.' / 'You want to swap ${l} to ${o}; you have ${d} ${l} available.'), then ask how much they want to swap and invite them to choose one of the options below or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT mention tool names, UI, buttons, or forms.`}}trimAmount(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(8))):"0"}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),l=a+i,u;if(s>=0){let d=a.length+s;u=d>=l.length?l.padEnd(d,"0"):`${l.slice(0,d)}.${l.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${l}`;return u.includes(".")&&(u=u.replace(/\.?0+$/,"")),n?`-${u}`:u}cleanAmountString(e){if(!/e/i.test(e))return e;let t=Number(e);return Number.isFinite(t)?this.toPlainDecimal(t):e}buildSwapFromPrompt(e,t){return(e&&e.includes("{token}")?e:"swap {token}").replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}buildSwapToPrompt(e,t,n,r,o){return(e&&e.includes("{from}")&&e.includes("{to}")?e:"swap {from_amount} {from} to {to_amount} {to}").replace(/\{from\}/g,t).replace(/\{to\}/g,n).replace(/\{from_amount\}/g,r??"").replace(/\{to_amount\}/g,o??"").replace(/\s+/g," ").trim()}formatPercent(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(6))):"0"}formatPrice(e){if(e>=1)return e.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});if(e>=.01)return e.toFixed(4);let t=e.toFixed(12),n=t.match(/^0\.0*([1-9]\d{0,3})/);return n?t.slice(0,(n[0].length||0)+0):t}};var Gt=class extends le{name="open-send-nft-form";actionType="send_nft";component="SendNftForm";userInputFields=[{key:"to_address",label:"the recipient address"}];url;constructor(e,t){super(e);let n=t?.url?.trim();this.url=n||void 0}description=`Open the SEND NFT form. Use for ANY NFT send/transfer intent: "send NFT", "transfer my Bored Ape", "send token id 1234 to 0x\u2026", "g\u1EEDi NFT". Call this tool EVEN IF the user did not name a contract, token id, NFT name, or recipient \u2014 the tool resolves missing identifiers from the wallet's NFT holdings and emits picker buttons when more than one match exists. Do NOT use this for ERC-20s (use open-send-token-form) or the native coin (open-send-native-form).`;parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"contract_address",type:"string",description:'NFT COLLECTION contract address (0x\u2026) \u2014 identifies WHICH NFT collection to send from. Only set this when the user explicitly named the collection/contract (e.g. "contract 0x\u2026", "collection 0x\u2026", "from 0x\u2026"). NEVER put the recipient address here. Omit when unknown.',required:!1},{name:"token_id",type:"string",description:'NFT token id as a decimal string (e.g. "1234"). Omit when unknown.',required:!1},{name:"nft_name",type:"string",description:'Free-form NFT or collection name as the user said it (e.g. "Bored Ape"). Used to look up the NFT in the wallet when no contract_address / token_id is provided. Omit when unknown.',required:!1},{name:"token_standard",type:"string",description:'NFT contract type ("ERC721" or "ERC1155"). Optional; the tool re-derives it from the wallet row.',required:!1},{name:"to_address",type:"string",description:'RECIPIENT EVM address (0x\u2026) \u2014 the wallet that will RECEIVE the NFT. Only set this when the user explicitly named a destination (e.g. "send to 0x\u2026", "to 0x\u2026", "g\u1EEDi \u0111\u1EBFn 0x\u2026", "cho 0x\u2026"). NEVER put the NFT collection / contract address here. Omit when unknown.',required:!1},{name:"amount",type:"string",description:"Number of editions to send. Only meaningful for ERC-1155; for ERC-721 the tool forces 1. Pass as a positive integer string. Omit when unknown \u2014 defaults to 1.",required:!1}];async buildParameters(e,t){let n=this.requireChain(e,t)??void 0,r=t?.walletAddress,o=this.normaliseAddress(e.contract_address),s=this.normaliseTokenId(e.token_id),a=this.normaliseString(e.nft_name),i=this.normaliseAddress(e.to_address),l=this.normaliseString(e.token_standard),u=this.normaliseAmountInt(e.amount);if(!o&&!s&&!a)return{error:"no_nft_specified",_instructions:"The user wants to send an NFT but did not name one. In their language, ask them which NFT they would like to send \u2014 by name, by collection contract address, or by token id. Do NOT open the form. Do NOT mention tool names, UI, or forms."};if(!this.moralis||!r)return this.paramsFromInputs({contract:o,tokenId:s,toAddress:i,standardArg:l,nftName:a,amountInt:u});let d=await this.moralis.getWalletNFTs({address:r,chain:n,limit:100,excludeSpam:!1}),m=d.success?d.data?.result??[]:[],p=this.matchNfts(m,{contract:o,tokenId:s,nftName:a});if(p.length===0){let f=a??(o&&s?`#${s}`:o??`#${s??""}`),y=I(n);return{error:"nft_not_owned",_instructions:`The user asked to send "${f}" but the wallet does not hold a matching NFT on ${y}. Tell them, in their language, that they do not own that NFT on ${y} and ask them to pick another one (by name, contract, or token id). Refer to the chain by this name \u2014 never as a hex id. Do NOT open the form. Do NOT mention tool names, UI, or forms.`}}if(p.length===1)return this.paramsFromNft(p[0],{toAddress:i,amountInt:u});let g=`Please select the NFT you would like to send from the list and proceed via ${this.url?`[this website](${this.url})`:"this website"}`;return{error:"multiple_nfts_matched",message:g,_instructions:`This is a fixed notice. Output it to the user EXACTLY as written below \u2014 in English, without translating, rephrasing, summarising, or adding/removing any words. Preserve the Markdown link verbatim (do not change, wrap, or drop the URL or the linked words). Do NOT open the form. Do NOT mention tool names, UI, or forms. Reply with only this message:
180
187
 
181
- `+f}}normaliseString(e){if(typeof e!="string")return;let t=e.trim();return t||void 0}normaliseTokenId(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0?String(Math.trunc(e)):this.normaliseString(e)}normaliseAmountInt(e){if(typeof e=="number"&&Number.isFinite(e)&&e>=1)return Math.trunc(e);if(typeof e=="string"&&e.trim()){let t=parseInt(e,10);if(Number.isFinite(t)&&t>=1)return t}}nftDisplayName(e){return(e.normalized_metadata?.name||e.name||`#${e.token_id}`).toString()}matchNfts(e,t){let n=t.contract?t.contract.toLowerCase():void 0,r=t.nftName?t.nftName.toLowerCase():void 0;return e.filter(o=>{if(n&&o.token_address.toLowerCase()!==n||t.tokenId&&o.token_id!==t.tokenId)return!1;if(r&&!n&&!t.tokenId){let s=(o.normalized_metadata?.name??"").toLowerCase(),a=(o.name??"").toLowerCase();if(!s.includes(r)&&!a.includes(r))return!1}return!0})}paramsFromInputs(e){let t={};return e.contract&&(t.contract_address=e.contract),e.tokenId&&(t.token_id=e.tokenId),e.toAddress&&(t.to_address=e.toAddress),e.standardArg&&(t.token_standard=e.standardArg.toUpperCase()),e.nftName&&(t.nft_name=e.nftName),e.amountInt&&(t.amount=String(e.amountInt)),t}paramsFromNft(e,t){let n=(e.contract_type||"").toUpperCase(),r=e.amount&&e.amount.trim()?e.amount.trim():"1",o=n==="ERC1155"?String(Math.min(t.amountInt??1,parseInt(r,10)||1)):"1",s={contract_address:e.token_address,token_id:e.token_id,token_standard:n||"ERC721",amount:o,maxAmount:r,nft_name:this.nftDisplayName(e)};t.toAddress&&(s.to_address=t.toAddress);let a=e.normalized_metadata?.image||e.media?.original_media_url||"";return a&&(s.nft_image=a),s}};var $t=class{messages=[];fullMessages=[];summary=null;maxMessages;constructor(e=50){this.maxMessages=e}add(e){this.messages.push(e),this.fullMessages.push(e)}addMany(e){this.messages.push(...e),this.fullMessages.push(...e)}addUIOnly(e){this.fullMessages.push(e)}rollbackLastWorkingMessage(){this.messages.pop()}getAll(){return[...this.messages]}getAllFull(){return[...this.fullMessages]}getRecent(e){return this.messages.slice(-e)}getConversation(){let e=[];return this.summary&&e.push({role:"system",content:`[Previous conversation summary]: ${this.summary}`,timestamp:0}),e.push(...this.messages),e}get length(){return this.messages.length}needsSummary(){return this.messages.filter(t=>t.role==="user").length>this.maxMessages}getSummary(){return this.summary}compact(e,t=10){this.summary=e,this.messages=this.messages.slice(-t)}compactWith(e,t){this.summary=e,this.messages=[...t]}clear(){this.messages=[],this.fullMessages=[],this.summary=null}serialise(){return{messages:[...this.messages],summary:this.summary,fullMessages:[...this.fullMessages]}}restore(e){Array.isArray(e)?(this.messages=[...e],this.fullMessages=[...e],this.summary=null):(this.messages=[...e.messages],this.summary=e.summary??null,this.fullMessages=e.fullMessages?[...e.fullMessages]:[...e.messages])}};var Wt=class{llm;constructor(e){this.llm=e}async summarize(e){let t=e.filter(s=>s.role==="user"||s.role==="assistant");if(t.length===0)return"";let n=t.map(s=>`${s.role==="user"?"User":"Assistant"}: ${s.content}`).join(`
188
+ `+g}}normaliseString(e){if(typeof e!="string")return;let t=e.trim();return t||void 0}normaliseTokenId(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0?String(Math.trunc(e)):this.normaliseString(e)}normaliseAmountInt(e){if(typeof e=="number"&&Number.isFinite(e)&&e>=1)return Math.trunc(e);if(typeof e=="string"&&e.trim()){let t=parseInt(e,10);if(Number.isFinite(t)&&t>=1)return t}}nftDisplayName(e){return(e.normalized_metadata?.name||e.name||`#${e.token_id}`).toString()}matchNfts(e,t){let n=t.contract?t.contract.toLowerCase():void 0,r=t.nftName?t.nftName.toLowerCase():void 0;return e.filter(o=>{if(n&&o.token_address.toLowerCase()!==n||t.tokenId&&o.token_id!==t.tokenId)return!1;if(r&&!n&&!t.tokenId){let s=(o.normalized_metadata?.name??"").toLowerCase(),a=(o.name??"").toLowerCase();if(!s.includes(r)&&!a.includes(r))return!1}return!0})}paramsFromInputs(e){let t={};return e.contract&&(t.contract_address=e.contract),e.tokenId&&(t.token_id=e.tokenId),e.toAddress&&(t.to_address=e.toAddress),e.standardArg&&(t.token_standard=e.standardArg.toUpperCase()),e.nftName&&(t.nft_name=e.nftName),e.amountInt&&(t.amount=String(e.amountInt)),t}paramsFromNft(e,t){let n=(e.contract_type||"").toUpperCase(),r=e.amount&&e.amount.trim()?e.amount.trim():"1",o=n==="ERC1155"?String(Math.min(t.amountInt??1,parseInt(r,10)||1)):"1",s={contract_address:e.token_address,token_id:e.token_id,token_standard:n||"ERC721",amount:o,maxAmount:r,nft_name:this.nftDisplayName(e)};t.toAddress&&(s.to_address=t.toAddress);let a=e.normalized_metadata?.image||e.media?.original_media_url||"";return a&&(s.nft_image=a),s}};var Yt=class{messages=[];fullMessages=[];summary=null;maxMessages;constructor(e=50){this.maxMessages=e}add(e){this.messages.push(e),this.fullMessages.push(e)}addMany(e){this.messages.push(...e),this.fullMessages.push(...e)}addUIOnly(e){this.fullMessages.push(e)}rollbackLastWorkingMessage(){this.messages.pop()}getAll(){return[...this.messages]}getAllFull(){return[...this.fullMessages]}getRecent(e){return this.messages.slice(-e)}getConversation(){let e=[];return this.summary&&e.push({role:"system",content:`[Previous conversation summary]: ${this.summary}`,timestamp:0}),e.push(...this.messages),e}get length(){return this.messages.length}needsSummary(){return this.messages.filter(t=>t.role==="user").length>this.maxMessages}getSummary(){return this.summary}compact(e,t=10){this.summary=e,this.messages=this.messages.slice(-t)}compactWith(e,t){this.summary=e,this.messages=[...t]}clear(){this.messages=[],this.fullMessages=[],this.summary=null}serialise(){return{messages:[...this.messages],summary:this.summary,fullMessages:[...this.fullMessages]}}restore(e){Array.isArray(e)?(this.messages=[...e],this.fullMessages=[...e],this.summary=null):(this.messages=[...e.messages],this.summary=e.summary??null,this.fullMessages=e.fullMessages?[...e.fullMessages]:[...e.messages])}};var Kt=class{llm;constructor(e){this.llm=e}async summarize(e){let t=e.filter(s=>s.role==="user"||s.role==="assistant");if(t.length===0)return"";let n=t.map(s=>`${s.role==="user"?"User":"Assistant"}: ${s.content}`).join(`
182
189
  `),r=[{role:"system",content:"You are a conversation summarizer. Produce a concise summary of the conversation below. Keep key facts, decisions, and context that would be needed to continue the conversation. Reply with only the summary, no extra commentary. Respond in the same language as the conversation.",timestamp:0},{role:"user",content:`Summarise this conversation:
183
190
 
184
- ${n}`,timestamp:Date.now()}];return(await this.llm.chat(r)).text}};var Qs=`You are a strict FAQ matcher. Given a user query and a list of FAQ entries, determine which FAQ entries the user is SPECIFICALLY asking about.
191
+ ${n}`,timestamp:Date.now()}];return(await this.llm.chat(r)).text}};var ma=`You are a strict FAQ matcher. Given a user query and a list of FAQ entries, determine which FAQ entries the user is SPECIFICALLY asking about.
185
192
 
186
193
  RULES:
187
194
  - Only match when the user's question is CLEARLY about the same topic as the FAQ entry.
@@ -196,13 +203,13 @@ RULES:
196
203
  - Return ONLY the JSON array, no other text.
197
204
 
198
205
  FAQ ENTRIES:
199
- `,Vt=class{entries=[];llm=null;constructor(e){e&&(this.entries=e)}setLLM(e){this.llm=e}setEntries(e){this.entries=e}addEntries(e){this.entries.push(...e)}getEntries(){return this.entries.slice()}get size(){return this.entries.length}async search(e,t=3){return this.entries.length===0?[]:this.llm?this.llmSearch(e,t):this.tokenSearch(e,t,.25)}async llmSearch(e,t){let n=this.entries.map((o,s)=>`[${s}] ${o.question}`).join(`
200
- `),r=[{role:"user",content:Qs+n+`
206
+ `,jt=class{entries=[];llm=null;constructor(e){e&&(this.entries=e)}setLLM(e){this.llm=e}setEntries(e){this.entries=e}addEntries(e){this.entries.push(...e)}getEntries(){return this.entries.slice()}get size(){return this.entries.length}async search(e,t=3){return this.entries.length===0?[]:this.llm?this.llmSearch(e,t):this.tokenSearch(e,t,.25)}async llmSearch(e,t){let n=this.entries.map((o,s)=>`[${s}] ${o.question}`).join(`
207
+ `),r=[{role:"user",content:ma+n+`
201
208
 
202
- USER QUERY: ${e}`,timestamp:0}];try{let a=(await this.llm.chat(r)).text.trim().match(/\[[\s\S]*\]/);if(!a)return[];let i=JSON.parse(a[0]),l=[];for(let u of i)u.index>=0&&u.index<this.entries.length&&u.score>=.6&&l.push({entry:this.entries[u.index],score:u.score});return l.sort((u,d)=>d.score-u.score),l.slice(0,t)}catch{return this.tokenSearch(e,t,.25)}}tokenSearch(e,t,n){let r=this.tokenize(e);if(r.size===0)return[];let o=[];for(let s of this.entries){let a=this.tokenize(s.question),i=this.jaccardSimilarity(r,a);i>=n&&o.push({entry:s,score:i})}return o.sort((s,a)=>a.score-s.score),o.slice(0,t)}tokenize(e){let t=e.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu," ").split(/\s+/).filter(n=>n.length>1);return new Set(t)}jaccardSimilarity(e,t){if(e.size===0||t.size===0)return 0;let n=0;for(let o of e)t.has(o)&&n++;let r=e.size+t.size-n;return r===0?0:n/r}};var tn=require("@upstash/vector"),Xs="https://perfect-octopus-59684-us1-vector.upstash.io",Js="ABkFMHBlcmZlY3Qtb2N0b3B1cy01OTY4NC11czFhZG1pbk56WXdOV0k0T0RndFpUaGtOeTAwWWpjMkxXRmtNRFV0WW1WaU9UaGtOMlV5T0ROaQ==",Re=class{index;namespace;minScore;defaultTopK;embedStrategy;fusionAlgorithm;debug;constructor(e={}){let t=e.url??(typeof process<"u"?process.env.UPSTASH_VECTOR_REST_URL:void 0)??Xs,n=e.token??(typeof process<"u"?process.env.UPSTASH_VECTOR_REST_TOKEN:void 0)??Js;this.index=new tn.Index({url:t,token:n}),this.namespace=e.namespace,this.minScore=e.minScore??1.3,this.defaultTopK=e.defaultTopK??3,this.embedStrategy=e.embedStrategy??"question",this.fusionAlgorithm=e.fusionAlgorithm==="RRF"?tn.FusionAlgorithm.RRF:tn.FusionAlgorithm.DBSF,this.debug=e.debug??!1}async search(e,t){let n=t??this.defaultTopK;if(!e.trim())return[];let r=this.namespace?this.index.namespace(this.namespace):this.index,o;try{o=await r.query({data:e,topK:n,includeMetadata:!0,fusionAlgorithm:this.fusionAlgorithm})}catch(a){return this.debug&&console.warn("[UpstashKnowledgeBase] query failed:",a),[]}let s=[];for(let a of o){if(a.score<this.minScore)continue;let i=a.metadata;!i?.question||!i?.answer||s.push({entry:{question:i.question,answer:i.answer},score:a.score})}return this.debug&&console.log(`[UpstashKnowledgeBase] query="${e}" \u2192 ${s.length}/${o.length} hit(s) above ${this.minScore}`+(s[0]?` (top: ${s[0].score.toFixed(3)})`:"")),s}async upsert(e){if(e.length===0)return{count:0};let t=this.namespace?this.index.namespace(this.namespace):this.index,n=await Promise.all(e.map(async r=>({id:r.id??await this.deriveId(r.question),data:this.buildEmbedText(r),metadata:{question:r.question,answer:r.answer,...r.metadata??{}}})));return await t.upsert(n),this.debug&&console.log(`[UpstashKnowledgeBase] upserted ${n.length} entr${n.length===1?"y":"ies"} (strategy=${this.embedStrategy})`),{count:n.length}}buildEmbedText(e){return this.embedStrategy==="question-and-answer"?`${e.question}
209
+ USER QUERY: ${e}`,timestamp:0}];try{let a=(await this.llm.chat(r)).text.trim().match(/\[[\s\S]*\]/);if(!a)return[];let i=JSON.parse(a[0]),l=[];for(let u of i)u.index>=0&&u.index<this.entries.length&&u.score>=.6&&l.push({entry:this.entries[u.index],score:u.score});return l.sort((u,d)=>d.score-u.score),l.slice(0,t)}catch{return this.tokenSearch(e,t,.25)}}tokenSearch(e,t,n){let r=this.tokenize(e);if(r.size===0)return[];let o=[];for(let s of this.entries){let a=this.tokenize(s.question),i=this.jaccardSimilarity(r,a);i>=n&&o.push({entry:s,score:i})}return o.sort((s,a)=>a.score-s.score),o.slice(0,t)}tokenize(e){let t=e.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu," ").split(/\s+/).filter(n=>n.length>1);return new Set(t)}jaccardSimilarity(e,t){if(e.size===0||t.size===0)return 0;let n=0;for(let o of e)t.has(o)&&n++;let r=e.size+t.size-n;return r===0?0:n/r}};var an=require("@upstash/vector"),pa="https://perfect-octopus-59684-us1-vector.upstash.io",ha="ABkFMHBlcmZlY3Qtb2N0b3B1cy01OTY4NC11czFhZG1pbk56WXdOV0k0T0RndFpUaGtOeTAwWWpjMkxXRmtNRFV0WW1WaU9UaGtOMlV5T0ROaQ==",Re=class{index;namespace;minScore;defaultTopK;embedStrategy;fusionAlgorithm;debug;constructor(e={}){let t=e.url??(typeof process<"u"?process.env.UPSTASH_VECTOR_REST_URL:void 0)??pa,n=e.token??(typeof process<"u"?process.env.UPSTASH_VECTOR_REST_TOKEN:void 0)??ha;this.index=new an.Index({url:t,token:n}),this.namespace=e.namespace,this.minScore=e.minScore??1.3,this.defaultTopK=e.defaultTopK??3,this.embedStrategy=e.embedStrategy??"question",this.fusionAlgorithm=e.fusionAlgorithm==="RRF"?an.FusionAlgorithm.RRF:an.FusionAlgorithm.DBSF,this.debug=e.debug??!1}async search(e,t){let n=t??this.defaultTopK;if(!e.trim())return[];let r=this.namespace?this.index.namespace(this.namespace):this.index,o;try{o=await r.query({data:e,topK:n,includeMetadata:!0,fusionAlgorithm:this.fusionAlgorithm})}catch(a){return this.debug&&console.warn("[UpstashKnowledgeBase] query failed:",a),[]}let s=[];for(let a of o){if(a.score<this.minScore)continue;let i=a.metadata;!i?.question||!i?.answer||s.push({entry:{question:i.question,answer:i.answer},score:a.score})}return this.debug&&console.log(`[UpstashKnowledgeBase] query="${e}" \u2192 ${s.length}/${o.length} hit(s) above ${this.minScore}`+(s[0]?` (top: ${s[0].score.toFixed(3)})`:"")),s}async upsert(e){if(e.length===0)return{count:0};let t=this.namespace?this.index.namespace(this.namespace):this.index,n=await Promise.all(e.map(async r=>({id:r.id??await this.deriveId(r.question),data:this.buildEmbedText(r),metadata:{question:r.question,answer:r.answer,...r.metadata??{}}})));return await t.upsert(n),this.debug&&console.log(`[UpstashKnowledgeBase] upserted ${n.length} entr${n.length===1?"y":"ies"} (strategy=${this.embedStrategy})`),{count:n.length}}buildEmbedText(e){return this.embedStrategy==="question-and-answer"?`${e.question}
203
210
 
204
- ${e.answer}`:e.question}async delete(e){return e.length===0?{deleted:0}:{deleted:(await(this.namespace?this.index.namespace(this.namespace):this.index).delete(e)).deleted}}async reset(){this.namespace?await this.index.reset({namespace:this.namespace}):await this.index.reset()}async size(){let e=await this.index.info();return this.namespace?e.namespaces?.[this.namespace]?.vectorCount??0:e.vectorCount??0}async deriveId(e){let t=new TextEncoder().encode(e.trim().toLowerCase()),n=await globalThis.crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(n)).map(r=>r.toString(16).padStart(2,"0")).join("").slice(0,32)}};function be(c){return c.filter(e=>e.role!=="tool"&&!(e.role==="assistant"&&e.toolCalls&&e.toolCalls.length>0)&&!e._intermediate)}function Ho(c,e){let t=[];for(let n=0;n<c.length;n++){let r=c[n];if(r.role==="assistant"&&r.toolCalls&&r.toolCalls.length>0){if(r.toolCalls.every(i=>e.has(i.toolName))){t.push(r);continue}let s=r.toolCalls.map(i=>i.toolName).join(", ");t.push({role:"assistant",content:`(Previously called external tools: ${s} \u2014 results not visible here.)`,timestamp:r.timestamp});let a=new Set(r.toolCalls.map(i=>i.callId));for(;n+1<c.length;){let i=c[n+1];if(i.role==="tool"&&i.toolCallId&&a.has(i.toolCallId))n++;else break}continue}if(r.role==="tool"){r.toolName&&e.has(r.toolName)&&t.push(r);continue}t.push(r)}return t}function Ae(c,e){if(c.length<=e)return[...c];let t=c.length-e;for(;t>0;){if(c[t].role==="tool"){t--;continue}break}let n=c.slice(t),r=0;for(;r<n.length&&n[r].role==="tool";)r++;return n.slice(r)}function Go(c,e){return Ae(c,e)}function Ko(c){for(let e=c.length-1;e>=0;e--){let t=c[e];if(t.role==="assistant"&&!(t.toolCalls&&t.toolCalls.length>0))return t.subagents&&t.subagents.length>0?[...t.subagents]:[]}return[]}function Yo(c,e=5){let t=c.filter(s=>s.role==="tool").slice(-e);if(t.length===0)return"";let n=[],r=[];for(let s of t){let a;try{a=JSON.parse(s.content)}catch{continue}if(typeof a!="object"||a===null)continue;let i=a,l=i.pagination;l&&typeof l.cursor=="string"&&l.cursor&&r.push({toolName:s.toolName??"tool",cursor:l.cursor});let u=i.transactions??i.result??i.transfers;if(Array.isArray(u))for(let d of u)typeof d.hash=="string"&&d.hash.startsWith("0x")?n.push(d.hash):typeof d.transaction_hash=="string"&&d.transaction_hash.startsWith("0x")&&n.push(d.transaction_hash)}if(n.length===0&&r.length===0)return"";let o=["[Tool data available from previous calls:]"];if(n.length>0&&o.push(`Transaction hashes (use EXACT values): ${n.slice(0,10).join(", ")}`),r.length>0){let s=r[r.length-1];o.push(`Pagination cursor from ${s.toolName}: ${s.cursor}`)}return o.join(`
205
- `)}var Zs=`You are a ROUTER.
211
+ ${e.answer}`:e.question}async delete(e){return e.length===0?{deleted:0}:{deleted:(await(this.namespace?this.index.namespace(this.namespace):this.index).delete(e)).deleted}}async reset(){this.namespace?await this.index.reset({namespace:this.namespace}):await this.index.reset()}async size(){let e=await this.index.info();return this.namespace?e.namespaces?.[this.namespace]?.vectorCount??0:e.vectorCount??0}async deriveId(e){let t=new TextEncoder().encode(e.trim().toLowerCase()),n=await globalThis.crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(n)).map(r=>r.toString(16).padStart(2,"0")).join("").slice(0,32)}};function we(c){return c.filter(e=>e.role!=="tool"&&!(e.role==="assistant"&&e.toolCalls&&e.toolCalls.length>0)&&!e._intermediate)}function Zo(c,e){let t=[];for(let n=0;n<c.length;n++){let r=c[n];if(r.role==="assistant"&&r.toolCalls&&r.toolCalls.length>0){if(r.toolCalls.every(i=>e.has(i.toolName))){t.push(r);continue}let s=r.toolCalls.map(i=>i.toolName).join(", ");t.push({role:"assistant",content:`(Previously called external tools: ${s} \u2014 results not visible here.)`,timestamp:r.timestamp});let a=new Set(r.toolCalls.map(i=>i.callId));for(;n+1<c.length;){let i=c[n+1];if(i.role==="tool"&&i.toolCallId&&a.has(i.toolCallId))n++;else break}continue}if(r.role==="tool"){r.toolName&&e.has(r.toolName)&&t.push(r);continue}t.push(r)}return t}function Ae(c,e){if(c.length<=e)return[...c];let t=c.length-e;for(;t>0;){if(c[t].role==="tool"){t--;continue}break}let n=c.slice(t),r=0;for(;r<n.length&&n[r].role==="tool";)r++;return n.slice(r)}function er(c,e){return Ae(c,e)}function tr(c){for(let e=c.length-1;e>=0;e--){let t=c[e];if(t.role==="assistant"&&!(t.toolCalls&&t.toolCalls.length>0))return t.subagents&&t.subagents.length>0?[...t.subagents]:[]}return[]}function nr(c,e=5){let t=c.filter(s=>s.role==="tool").slice(-e);if(t.length===0)return"";let n=[],r=[];for(let s of t){let a;try{a=JSON.parse(s.content)}catch{continue}if(typeof a!="object"||a===null)continue;let i=a,l=i.pagination;l&&typeof l.cursor=="string"&&l.cursor&&r.push({toolName:s.toolName??"tool",cursor:l.cursor});let u=i.transactions??i.result??i.transfers;if(Array.isArray(u))for(let d of u)typeof d.hash=="string"&&d.hash.startsWith("0x")?n.push(d.hash):typeof d.transaction_hash=="string"&&d.transaction_hash.startsWith("0x")&&n.push(d.transaction_hash)}if(n.length===0&&r.length===0)return"";let o=["[Tool data available from previous calls:]"];if(n.length>0&&o.push(`Transaction hashes (use EXACT values): ${n.slice(0,10).join(", ")}`),r.length>0){let s=r[r.length-1];o.push(`Pagination cursor from ${s.toolName}: ${s.cursor}`)}return o.join(`
212
+ `)}var ga=`You are a ROUTER.
206
213
 
207
214
  The incoming user query has ALREADY been contextualized upstream \u2014 it is a
208
215
  self-contained, standalone question. Your ONLY job: decide which subagent(s)
@@ -248,7 +255,7 @@ The query may arrive in ANY language (English, Vietnamese, Japanese, Korean, Chi
248
255
  The subagent descriptions are written in English, but they apply equally to the SAME intent expressed in
249
256
  any language. Route by MEANING, never by matching English keywords or script \u2014 a wallet / token / NFT / pool /
250
257
  swap / buy request is the same intent however it is phrased or whichever language it is written in. Token
251
- symbols, contract addresses, and numbers are language-neutral.`,Ht=class{llm;historyMessages;debug;constructor(e,t){this.llm=e,this.historyMessages=t?.historyMessages??10,this.debug=t?.debug??!1}async route(e,t,n,r,o=[]){let s=n.map(b=>`### ${b.name} (domain: ${b.domain})
258
+ symbols, contract addresses, and numbers are language-neutral.`,zt=class{llm;historyMessages;debug;constructor(e,t){this.llm=e,this.historyMessages=t?.historyMessages??10,this.debug=t?.debug??!1}async route(e,t,n,r,o=[]){let s=n.map(b=>`### ${b.name} (domain: ${b.domain})
252
259
  ${b.description}`).join(`
253
260
 
254
261
  `),a=[];r?.walletAddress&&a.push(`Connected wallet: ${r.walletAddress}`),r?.chain&&a.push(`Current chain: ${r.chain}`);let i=a.length>0?`
@@ -257,20 +264,20 @@ User context:
257
264
  ${a.join(`
258
265
  `)}`:"",l=o.length>0?`
259
266
 
260
- Previous turn handled by: ${o.join(", ")}. If this query is a natural continuation (same topic), prefer the same subagent(s). Override only if the query clearly belongs to a different domain.`:"",u=be(t),d=Ae(u,this.historyMessages);this.debug&&(console.log(`
261
- [Router] history window (${d.length} msg${d.length===1?"":"s"}):`),console.log(`[Router] user query: "${e}"`));let p=[{role:"system",content:Zs,timestamp:0},...d,{role:"user",content:`Available subagents:
267
+ Previous turn handled by: ${o.join(", ")}. If this query is a natural continuation (same topic), prefer the same subagent(s). Override only if the query clearly belongs to a different domain.`:"",u=we(t),d=Ae(u,this.historyMessages);this.debug&&(console.log(`
268
+ [Router] history window (${d.length} msg${d.length===1?"":"s"}):`),console.log(`[Router] user query: "${e}"`));let m=[{role:"system",content:ga,timestamp:0},...d,{role:"user",content:`Available subagents:
262
269
  ${s}${i}${l}
263
270
 
264
- User query: ${e}`,timestamp:Date.now()}],m=Date.now(),g=null,f="",h=null;for(let b=1;b<=2;b++){let T=await this.llm.chat(p);f=T.text;try{g=this.parseJSON(T.text);break}catch(P){h=P,b<2&&(this.debug&&console.log(`[Router] JSON parse failed (attempt ${b}) \u2014 re-prompting for JSON`),p.push({role:"assistant",content:T.text||"",timestamp:Date.now()}),p.push({role:"user",content:'That response was not valid JSON. Do NOT answer the user and do NOT ask any questions. Reply with ONLY the routing JSON object ({"analysis": "...", "assignments": [...]}) and nothing else.',timestamp:Date.now()}))}}let y=Date.now()-m;if(!g)return this.debug&&(console.log(`[Router] JSON parse failed after retry: ${h}`),console.log(`[Router] raw response: ${f.slice(0,300)}`)),{analysis:"Router could not parse response.",assignments:[]};let k=new Set(n.map(b=>b.name)),w=Array.isArray(g.assignments)?g.assignments.filter(b=>!!b&&typeof b.subagent=="string").filter(b=>k.has(b.subagent)).map(b=>({subagent:b.subagent,query:typeof b.query=="string"&&b.query?b.query:e,reasoning:typeof b.reasoning=="string"?b.reasoning:""})):[];if(this.debug){let b=w.length>0?w.map(T=>T.subagent).join(", "):"(none \u2014 direct answer)";if(console.log(`
265
- [Router] (${y}ms)`),console.log(` analysis : ${g.analysis??""}`),console.log(` dispatch : ${b}`),w.length>0)for(let T of w)console.log(` \u2192 ${T.subagent}: "${T.query}"`),console.log(` reason: ${T.reasoning}`)}return{analysis:typeof g.analysis=="string"?g.analysis:"",assignments:w}}async dispatch(e,t,n,r){if(e.assignments.length===0)return[];this.debug&&console.log(`
266
- [Router] dispatching ${e.assignments.length} subagent(s) in parallel\u2026`);let o=Date.now(),s=e.assignments.map(async i=>{let l=t.get(i.subagent);if(!l)return this.debug&&console.log(`[Router] WARN: subagent "${i.subagent}" not registered`),{subagentName:i.subagent,steps:[],finalAnswer:`Subagent "${i.subagent}" is not registered.`,toolResults:[],toolMessages:[]};let u=Date.now(),d=await l.run({query:i.query,reasoning:i.reasoning},n,r);if(this.debug){let p=d.toolResults.map(m=>m.toolName).join(", ")||"\u2014";console.log(`[Router] ${i.subagent} done in ${Date.now()-u}ms | tools: ${p}`)}return d}),a=await Promise.all(s);return this.debug&&console.log(`[Router] all subagents done in ${Date.now()-o}ms total`),a}parseJSON(e){let t=e.trim();t.startsWith("```")&&(t=t.replace(/^```(?:json)?\s*/,"").replace(/```\s*$/,"").trim());try{return JSON.parse(t)}catch(n){let r=t.match(/\{[\s\S]*\}/);if(r)return JSON.parse(r[0]);throw n}}};var ea=`
271
+ User query: ${e}`,timestamp:Date.now()}],p=Date.now(),h=null,g="",f=null;for(let b=1;b<=2;b++){let T=await this.llm.chat(m);g=T.text;try{h=this.parseJSON(T.text);break}catch(P){f=P,b<2&&(this.debug&&console.log(`[Router] JSON parse failed (attempt ${b}) \u2014 re-prompting for JSON`),m.push({role:"assistant",content:T.text||"",timestamp:Date.now()}),m.push({role:"user",content:'That response was not valid JSON. Do NOT answer the user and do NOT ask any questions. Reply with ONLY the routing JSON object ({"analysis": "...", "assignments": [...]}) and nothing else.',timestamp:Date.now()}))}}let y=Date.now()-p;if(!h)return this.debug&&(console.log(`[Router] JSON parse failed after retry: ${f}`),console.log(`[Router] raw response: ${g.slice(0,300)}`)),{analysis:"Router could not parse response.",assignments:[]};let k=new Set(n.map(b=>b.name)),w=Array.isArray(h.assignments)?h.assignments.filter(b=>!!b&&typeof b.subagent=="string").filter(b=>k.has(b.subagent)).map(b=>({subagent:b.subagent,query:typeof b.query=="string"&&b.query?b.query:e,reasoning:typeof b.reasoning=="string"?b.reasoning:""})):[];if(this.debug){let b=w.length>0?w.map(T=>T.subagent).join(", "):"(none \u2014 direct answer)";if(console.log(`
272
+ [Router] (${y}ms)`),console.log(` analysis : ${h.analysis??""}`),console.log(` dispatch : ${b}`),w.length>0)for(let T of w)console.log(` \u2192 ${T.subagent}: "${T.query}"`),console.log(` reason: ${T.reasoning}`)}return{analysis:typeof h.analysis=="string"?h.analysis:"",assignments:w}}async dispatch(e,t,n,r){if(e.assignments.length===0)return[];this.debug&&console.log(`
273
+ [Router] dispatching ${e.assignments.length} subagent(s) in parallel\u2026`);let o=Date.now(),s=e.assignments.map(async i=>{let l=t.get(i.subagent);if(!l)return this.debug&&console.log(`[Router] WARN: subagent "${i.subagent}" not registered`),{subagentName:i.subagent,steps:[],finalAnswer:`Subagent "${i.subagent}" is not registered.`,toolResults:[],toolMessages:[]};let u=Date.now(),d=await l.run({query:i.query,reasoning:i.reasoning},n,r);if(this.debug){let m=d.toolResults.map(p=>p.toolName).join(", ")||"\u2014";console.log(`[Router] ${i.subagent} done in ${Date.now()-u}ms | tools: ${m}`)}return d}),a=await Promise.all(s);return this.debug&&console.log(`[Router] all subagents done in ${Date.now()-o}ms total`),a}parseJSON(e){let t=e.trim();t.startsWith("```")&&(t=t.replace(/^```(?:json)?\s*/,"").replace(/```\s*$/,"").trim());try{return JSON.parse(t)}catch(n){let r=t.match(/\{[\s\S]*\}/);if(r)return JSON.parse(r[0]);throw n}}};var fa=`
267
274
 
268
- LANGUAGE-AGNOSTIC TOOL SELECTION: The user query may arrive in ANY language (English, Vietnamese, Japanese, Korean, Chinese, Thai, Spanish, Arabic, \u2026). Every intent\u2192tool mapping, verb, and example phrase in this prompt is ILLUSTRATIVE ONLY \u2014 written mostly in English for brevity, NOT a list of accepted words. Always pick the tool by the MEANING of the request, treating translated, transliterated, or mixed-language equivalents exactly like the English example (buy / send / swap / approve / show / search / trending / balance, etc. are the same intent in every language). Token symbols, contract addresses, and numbers are language-neutral \u2014 extract them verbatim. Never refuse, misroute, or fall back to plain text merely because the wording is not in English.`,Y=class{name;description;domain;toolNames;systemPrompt;cortexFallbackTool;mustCallTool;llm;registry;maxToolCalls;historyMessages;debug;constructor(e){if(this.name=e.name,this.description=e.description,this.domain=e.domain,this.toolNames=new Set(e.toolNames),this.systemPrompt=e.systemPrompt+ea,this.cortexFallbackTool=e.cortexFallbackTool,this.mustCallTool=e.mustCallTool??!1,this.cortexFallbackTool&&!this.toolNames.has(this.cortexFallbackTool))throw new Error(`Subagent "${e.name}": cortexFallbackTool "${this.cortexFallbackTool}" must be in toolNames.`);this.llm=e.llm,this.registry=e.registry,this.maxToolCalls=e.options?.maxToolCalls??5,this.historyMessages=e.options?.historyMessages??10,this.debug=e.options?.debug??!1}getCard(){return{name:this.name,description:this.description,domain:this.domain}}getTools(){return this.registry.getDefinitions().filter(e=>this.toolNames.has(e.name))}async run(e,t,n){let r=this.getTools();if(this.debug&&(console.log(`
269
- [${this.name}] query: "${e.query}"`),console.log(` [${this.name}] tools available: ${r.map(T=>T.name).join(", ")}`)),r.length===0)return{subagentName:this.name,steps:[],finalAnswer:`Subagent "${this.name}" has no registered tools available.`,toolResults:[],toolMessages:[]};let o=[],s=[],a=[],i=new Set,l=0,u=!1,d=[];n?.walletAddress&&d.push(`Connected wallet: ${n.walletAddress}`),n?.chain&&d.push(`Current chain: ${n.chain}`);let p=d.length>0?`User context:
275
+ LANGUAGE-AGNOSTIC TOOL SELECTION: The user query may arrive in ANY language (English, Vietnamese, Japanese, Korean, Chinese, Thai, Spanish, Arabic, \u2026). Every intent\u2192tool mapping, verb, and example phrase in this prompt is ILLUSTRATIVE ONLY \u2014 written mostly in English for brevity, NOT a list of accepted words. Always pick the tool by the MEANING of the request, treating translated, transliterated, or mixed-language equivalents exactly like the English example (buy / send / swap / approve / show / search / trending / balance, etc. are the same intent in every language). Token symbols, contract addresses, and numbers are language-neutral \u2014 extract them verbatim. Never refuse, misroute, or fall back to plain text merely because the wording is not in English.`,j=class{name;description;domain;toolNames;systemPrompt;cortexFallbackTool;mustCallTool;llm;registry;maxToolCalls;historyMessages;debug;constructor(e){if(this.name=e.name,this.description=e.description,this.domain=e.domain,this.toolNames=new Set(e.toolNames),this.systemPrompt=e.systemPrompt+fa,this.cortexFallbackTool=e.cortexFallbackTool,this.mustCallTool=e.mustCallTool??!1,this.cortexFallbackTool&&!this.toolNames.has(this.cortexFallbackTool))throw new Error(`Subagent "${e.name}": cortexFallbackTool "${this.cortexFallbackTool}" must be in toolNames.`);this.llm=e.llm,this.registry=e.registry,this.maxToolCalls=e.options?.maxToolCalls??5,this.historyMessages=e.options?.historyMessages??10,this.debug=e.options?.debug??!1}getCard(){return{name:this.name,description:this.description,domain:this.domain}}getTools(){return this.registry.getDefinitions().filter(e=>this.toolNames.has(e.name))}async run(e,t,n){let r=this.getTools();if(this.debug&&(console.log(`
276
+ [${this.name}] query: "${e.query}"`),console.log(` [${this.name}] tools available: ${r.map(T=>T.name).join(", ")}`)),r.length===0)return{subagentName:this.name,steps:[],finalAnswer:`Subagent "${this.name}" has no registered tools available.`,toolResults:[],toolMessages:[]};let o=[],s=[],a=[],i=new Set,l=0,u=!1,d=[];if(n?.walletAddress&&d.push(`Connected wallet: ${n.walletAddress}`),n?.chain){let T=I(n.chain),P=wo(n.chain),S=P?`; native coin: ${P}`:"";d.push(`Current chain: ${T} (${n.chain})${S}`)}let m=d.length>0?`User context:
270
277
  ${d.join(`
271
- `)}`:"",m=e.language?`The user's current language is "${e.language}" (BCP-47). Write every user-facing string you produce \u2014 replies AND any localized tool arguments \u2014 in this language.`:"",g=[p,m,`Routed query: ${e.query}`,e.reasoning?`Router reasoning: ${e.reasoning}`:"","Use only the tools you own. If you already have enough information, respond directly in the user's language."].filter(Boolean).join(`
278
+ `)}`:"",p=e.language?`The user's current language is "${e.language}" (BCP-47). Write every user-facing string you produce \u2014 replies AND any localized tool arguments \u2014 in this language.`:"",h=[m,p,`Routed query: ${e.query}`,e.reasoning?`Router reasoning: ${e.reasoning}`:"","Use only the tools you own. If you already have enough information, respond directly in the user's language."].filter(Boolean).join(`
272
279
 
273
- `),f=Ho(t,this.toolNames),h=Ae(f,this.historyMessages),y=[{role:"system",content:this.systemPrompt,timestamp:0},...h,{role:"user",content:g,timestamp:Date.now()}],k=6e3,w=this.maxToolCalls+2;for(let T=0;T<w;T++){let P=l<this.maxToolCalls,S=P?r:void 0,C=P&&this.mustCallTool&&l===0,D=await this.llm.chat(y,S,C?{forceToolUse:!0}:void 0);if(D.toolCalls.length===0){if(l===0&&this.cortexFallbackTool&&P){this.debug&&console.log(` [${this.name}] no tool called \u2014 forcing fallback to ${this.cortexFallbackTool}`),y.push({role:"user",content:`You answered without calling any tool. This is not allowed for on-chain queries. Call "${this.cortexFallbackTool}" now with a clear, well-formed English prompt that captures the user's intent. Resolve ambiguity with sensible defaults (chain: Ethereum unless specified; result count: top 10; time window: last 24h; sort key: 24h volume for "top/best/trending" queries). Do NOT answer in text \u2014 call the tool.`,timestamp:Date.now()});continue}if(l===0&&this.mustCallTool&&P&&!u){u=!0,this.debug&&console.log(` [${this.name}] no tool called \u2014 forcing the LLM to pick a tool`),y.push({role:"user",content:"You answered in text without calling any tool. That is not allowed here: this request needs you to open a form. Call exactly ONE of your tools that matches the user's intent now. Pass only the fields the user actually gave; leave everything else blank \u2014 the tool/form collects the rest (including a token/amount picker when needed). Do NOT ask the user for missing fields. Do NOT answer in text \u2014 call the tool.",timestamp:Date.now()});continue}return this.debug&&console.log(` [${this.name}] done \u2014 ${l} tool call(s), answer: "${D.text.slice(0,80)}\u2026"`),{subagentName:this.name,steps:o,finalAnswer:D.text,toolResults:s,toolMessages:a}}let x={role:"assistant",content:D.text||"",toolCalls:D.toolCalls,timestamp:Date.now()};y.push(x);let I=[];for(let A of D.toolCalls){if(!this.toolNames.has(A.toolName)){let j=`Tool "${A.toolName}" is not owned by ${this.name}`;this.debug&&console.log(` [${this.name}] WARN: ${j}`),o.push({phase:"observe",content:j,timestamp:Date.now()}),y.push({role:"tool",content:j,toolName:A.toolName,toolCallId:A.callId,timestamp:Date.now()});continue}let H=`${A.toolName}::${JSON.stringify(A.args)}`;if(i.has(H)){let j=`Skipped duplicate call: ${A.toolName}`;this.debug&&console.log(` [${this.name}] SKIP duplicate: ${A.toolName}`),o.push({phase:"observe",content:j,timestamp:Date.now()}),y.push({role:"tool",content:j,toolName:A.toolName,toolCallId:A.callId,timestamp:Date.now()});continue}i.add(H),l++,this.debug&&console.log(` [${this.name}] call #${l}: ${A.toolName} ${JSON.stringify(A.args)}`),o.push({phase:"think",content:D.text||`Calling ${A.toolName}`,timestamp:Date.now()}),o.push({phase:"act",content:`Calling tool: ${A.toolName}`,toolCall:{toolName:A.toolName,args:A.args},timestamp:Date.now()});let Pe=Date.now(),ie={...n,query:e.query},Q=await this.registry.execute(A.toolName,A.args,ie);s.push(Q);let X=Q.success?JSON.stringify(Q.data):`Error: ${Q.error}`;if(this.debug){let j=Q.success?"OK":"ERR",de=Q.success?`${X.slice(0,120)}${X.length>120?"\u2026":""}`:Q.error;console.log(` [${this.name}] ${j} (${Date.now()-Pe}ms): ${de}`)}o.push({phase:"observe",content:X,toolResult:Q,timestamp:Date.now()});let we={role:"tool",content:X,toolName:A.toolName,toolCallId:A.callId,timestamp:Date.now()};y.push(we),I.push({...we,content:X.length>k?X.slice(0,k)+"\u2026[truncated]":X})}I.length>0&&(a.push(x),a.push(...I))}this.debug&&console.log(` [${this.name}] budget exhausted after ${l} calls, forcing final answer`),y.push({role:"user",content:"Tool budget reached. Give your best answer now based on the data gathered.",timestamp:Date.now()});let b=await this.llm.chat(y);return{subagentName:this.name,steps:o,finalAnswer:b.text,toolResults:s,toolMessages:a}}};var ta=`You are the Wallet domain expert. You answer questions about a specific wallet's on-chain state.
280
+ `),g=Zo(t,this.toolNames),f=Ae(g,this.historyMessages),y=[{role:"system",content:this.systemPrompt,timestamp:0},...f,{role:"user",content:h,timestamp:Date.now()}],k=6e3,w=this.maxToolCalls+2;for(let T=0;T<w;T++){let P=l<this.maxToolCalls,S=P?r:void 0,C=P&&this.mustCallTool&&l===0,L=await this.llm.chat(y,S,C?{forceToolUse:!0}:void 0);if(L.toolCalls.length===0){if(l===0&&this.cortexFallbackTool&&P){this.debug&&console.log(` [${this.name}] no tool called \u2014 forcing fallback to ${this.cortexFallbackTool}`),y.push({role:"user",content:`You answered without calling any tool. This is not allowed for on-chain queries. Call "${this.cortexFallbackTool}" now with a clear, well-formed English prompt that captures the user's intent. Resolve ambiguity with sensible defaults (chain: Ethereum unless specified; result count: top 10; time window: last 24h; sort key: 24h volume for "top/best/trending" queries). Do NOT answer in text \u2014 call the tool.`,timestamp:Date.now()});continue}if(l===0&&this.mustCallTool&&P&&!u){u=!0,this.debug&&console.log(` [${this.name}] no tool called \u2014 forcing the LLM to pick a tool`),y.push({role:"user",content:"You answered in text without calling any tool. That is not allowed here: this request needs you to open a form. Call exactly ONE of your tools that matches the user's intent now. Pass only the fields the user actually gave; leave everything else blank \u2014 the tool/form collects the rest (including a token/amount picker when needed). Do NOT ask the user for missing fields. Do NOT answer in text \u2014 call the tool.",timestamp:Date.now()});continue}return this.debug&&console.log(` [${this.name}] done \u2014 ${l} tool call(s), answer: "${L.text.slice(0,80)}\u2026"`),{subagentName:this.name,steps:o,finalAnswer:L.text,toolResults:s,toolMessages:a}}let x={role:"assistant",content:L.text||"",toolCalls:L.toolCalls,timestamp:Date.now()};y.push(x);let D=[];for(let A of L.toolCalls){if(!this.toolNames.has(A.toolName)){let z=`Tool "${A.toolName}" is not owned by ${this.name}`;this.debug&&console.log(` [${this.name}] WARN: ${z}`),o.push({phase:"observe",content:z,timestamp:Date.now()}),y.push({role:"tool",content:z,toolName:A.toolName,toolCallId:A.callId,timestamp:Date.now()});continue}let H=`${A.toolName}::${JSON.stringify(A.args)}`;if(i.has(H)){let z=`Skipped duplicate call: ${A.toolName}`;this.debug&&console.log(` [${this.name}] SKIP duplicate: ${A.toolName}`),o.push({phase:"observe",content:z,timestamp:Date.now()}),y.push({role:"tool",content:z,toolName:A.toolName,toolCallId:A.callId,timestamp:Date.now()});continue}i.add(H),l++,this.debug&&console.log(` [${this.name}] call #${l}: ${A.toolName} ${JSON.stringify(A.args)}`),o.push({phase:"think",content:L.text||`Calling ${A.toolName}`,timestamp:Date.now()}),o.push({phase:"act",content:`Calling tool: ${A.toolName}`,toolCall:{toolName:A.toolName,args:A.args},timestamp:Date.now()});let Pe=Date.now(),ce={...n,query:e.query},Q=await this.registry.execute(A.toolName,A.args,ce);s.push(Q);let J=Q.success?JSON.stringify(Q.data):`Error: ${Q.error}`;if(this.debug){let z=Q.success?"OK":"ERR",me=Q.success?`${J.slice(0,120)}${J.length>120?"\u2026":""}`:Q.error;console.log(` [${this.name}] ${z} (${Date.now()-Pe}ms): ${me}`)}o.push({phase:"observe",content:J,toolResult:Q,timestamp:Date.now()});let ke={role:"tool",content:J,toolName:A.toolName,toolCallId:A.callId,timestamp:Date.now()};y.push(ke),D.push({...ke,content:J.length>k?J.slice(0,k)+"\u2026[truncated]":J})}D.length>0&&(a.push(x),a.push(...D))}this.debug&&console.log(` [${this.name}] budget exhausted after ${l} calls, forcing final answer`),y.push({role:"user",content:"Tool budget reached. Give your best answer now based on the data gathered.",timestamp:Date.now()});let b=await this.llm.chat(y);return{subagentName:this.name,steps:o,finalAnswer:b.text,toolResults:s,toolMessages:a}}};var ya=`You are the Wallet domain expert. You answer questions about a specific wallet's on-chain state.
274
281
 
275
282
  SPECIALISED TOOLS (prefer these when intent matches):
276
283
  - get-wallet-token-balances: token + native coin balances
@@ -321,7 +328,7 @@ PAGINATION:
321
328
  pass the cursor from the LAST history response in conversation. Always tell the user
322
329
  how many results were returned and whether more pages exist.
323
330
 
324
- Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea .`,Cn=["get-wallet-token-balances","get-wallet-history","get-wallet-token-transfers","get-wallet-nft-transfers","get-wallet-net-worth","get-wallet-pnl-summary","get-wallet-pnl","get-wallet-approvals","get-wallet-defi-summary","get-wallet-defi-positions","get-wallet-defi-protocol-positions","get-transaction-by-hash","gemini-search-ai"];function nn(c,e,t){return new Y({name:"wallet-agent",domain:"wallet",description:["Wallet-scoped on-chain state. Specialised tools cover balances, net worth, transaction history,","single-tx lookup, ERC-20 / NFT transfers, PnL, DeFi positions, and approvals.","For wallet queries without a direct tool , this agent falls","back to a search-grounded Gemini model internally \u2014 it never refuses a wallet-scoped query","and always calls a tool.","NOT in scope: market-wide token data (\u2192 token-agent), NFT current holdings (\u2192 nft-agent)."].join(" "),toolNames:[...Cn],systemPrompt:ta,cortexFallbackTool:"gemini-search-ai",llm:c,registry:e,options:t})}var na=`You are the Token domain expert. You answer market-wide token questions, not wallet-specific ones.
331
+ Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea .`,Ln=["get-wallet-token-balances","get-wallet-history","get-wallet-token-transfers","get-wallet-nft-transfers","get-wallet-net-worth","get-wallet-pnl-summary","get-wallet-pnl","get-wallet-approvals","get-wallet-defi-summary","get-wallet-defi-positions","get-wallet-defi-protocol-positions","get-transaction-by-hash","gemini-search-ai"];function ln(c,e,t){return new j({name:"wallet-agent",domain:"wallet",description:["Wallet-scoped on-chain state. Specialised tools cover balances, net worth, transaction history,","single-tx lookup, ERC-20 / NFT transfers, PnL, DeFi positions, and approvals.","For wallet queries without a direct tool , this agent falls","back to a search-grounded Gemini model internally \u2014 it never refuses a wallet-scoped query","and always calls a tool.","NOT in scope: market-wide token data (\u2192 token-agent), NFT current holdings (\u2192 nft-agent)."].join(" "),toolNames:[...Ln],systemPrompt:ya,cortexFallbackTool:"gemini-search-ai",llm:c,registry:e,options:t})}var ba=`You are the Token domain expert. You answer market-wide token questions, not wallet-specific ones.
325
332
 
326
333
  SPECIALISED TOOLS (prefer these when intent matches):
327
334
  - get-token-info: metadata (name, symbol, decimals, address, logo)
@@ -362,7 +369,7 @@ GENERAL RULES:
362
369
  - Never repeat a tool call with identical arguments.
363
370
  - Answer in the user's language. Do not mention tool names.
364
371
 
365
- Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea. `,Un=["get-token-info","get-token-holders","get-token-analytics","get-token-score","get-trending-tokens","get-top-gainers","gemini-search-ai"];function on(c,e,t){return new Y({name:"token-agent",domain:"token",description:["Market-wide token data. Specialised tools cover metadata, holders, analytics, risk score,","trending, and top gainers. For token category/theme queries (meme, AI, gaming, L2, RWA),","any token question without a direct tool, this agent falls back to a","search-grounded Gemini model internally \u2014 it never refuses a token query and always calls a tool.","NOT in scope: wallet balances or transfer history (\u2192 wallet-agent), NFT data (\u2192 nft-agent)."].join(" "),toolNames:[...Un],systemPrompt:na,cortexFallbackTool:"gemini-search-ai",llm:c,registry:e,options:t})}var oa=`You are the NFT domain expert. You answer questions about NFT holdings and metadata, not transfer history.
372
+ Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea. `,Mn=["get-token-info","get-token-holders","get-token-analytics","get-token-score","get-trending-tokens","get-top-gainers","gemini-search-ai"];function cn(c,e,t){return new j({name:"token-agent",domain:"token",description:["Market-wide token data. Specialised tools cover metadata, holders, analytics, risk score,","trending, and top gainers. For token category/theme queries (meme, AI, gaming, L2, RWA),","any token question without a direct tool, this agent falls back to a","search-grounded Gemini model internally \u2014 it never refuses a token query and always calls a tool.","NOT in scope: wallet balances or transfer history (\u2192 wallet-agent), NFT data (\u2192 nft-agent)."].join(" "),toolNames:[...Mn],systemPrompt:ba,cortexFallbackTool:"gemini-search-ai",llm:c,registry:e,options:t})}var wa=`You are the NFT domain expert. You answer questions about NFT holdings and metadata, not transfer history.
366
373
 
367
374
  SPECIALISED TOOLS (prefer these when intent matches):
368
375
  - get-wallet-nfts: user wants to view / browse their NFTs / holdings \u2014 returns a fixed website notice
@@ -414,7 +421,7 @@ GENERAL RULES:
414
421
  - One tool per turn. Never repeat a tool call with identical arguments.
415
422
  - Answer in the user's language. Do not mention tool names.
416
423
 
417
- Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea.`,Rn=["get-wallet-nfts","get-nft-metadata","get-nft-contract-info","open-send-nft-form","gemini-search-ai"];function rn(c,e,t){return new Y({name:"nft-agent",domain:"nft",description:["NFT holdings, metadata, and sending. Specialised tools cover NFT holdings, single-NFT metadata,","collection / contract info, and sending/transferring an NFT. For NFT category, theme, or market-wide","questions without a direct tool, this agent falls back to a search-grounded Gemini model internally \u2014","it never refuses an NFT query and always calls a tool.",'Handles the SEND / TRANSFER-an-NFT action ("send nft", "g\u1EEDi nft"); but past NFT transfer history (events) \u2192 wallet-agent.',"Key distinction: nft-agent = NFT holdings / details / send; wallet-agent = NFT TRANSFER-EVENT history."].join(" "),toolNames:[...Rn],systemPrompt:oa,cortexFallbackTool:"gemini-search-ai",llm:c,registry:e,options:t})}var ra=`You are the Crypto AI fallback expert powered by a search-grounded Gemini model.
424
+ Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea.`,Bn=["get-wallet-nfts","get-nft-metadata","get-nft-contract-info","open-send-nft-form","gemini-search-ai"];function un(c,e,t){return new j({name:"nft-agent",domain:"nft",description:["NFT holdings, metadata, and sending. Specialised tools cover NFT holdings, single-NFT metadata,","collection / contract info, and sending/transferring an NFT. For NFT category, theme, or market-wide","questions without a direct tool, this agent falls back to a search-grounded Gemini model internally \u2014","it never refuses an NFT query and always calls a tool.",'Handles the SEND / TRANSFER-an-NFT action ("send nft", "g\u1EEDi nft"); but past NFT transfer history (events) \u2192 wallet-agent.',"Key distinction: nft-agent = NFT holdings / details / send; wallet-agent = NFT TRANSFER-EVENT history."].join(" "),toolNames:[...Bn],systemPrompt:wa,cortexFallbackTool:"gemini-search-ai",llm:c,registry:e,options:t})}var ka=`You are the Crypto AI fallback expert powered by a search-grounded Gemini model.
418
425
 
419
426
  You answer ANY blockchain / crypto / token / NFT / DeFi / on-chain question that does not have a
420
427
  direct structured tool in the other agents. The underlying model has live web-search grounding \u2014
@@ -433,7 +440,7 @@ ROUTING:
433
440
  RULES:
434
441
  - ALWAYS call the tool \u2014 NEVER refuse, NEVER answer without calling it.
435
442
  - NEVER retry with the same prompt if the call fails \u2014 return what you have.
436
- - Answer in the user's language. Do NOT mention tool names.`,En=["gemini-search-ai"];function sn(c,e,t){return new Y({name:"ai-agent",domain:"ai",description:["Catch-all crypto / blockchain expert powered by a search-grounded Gemini model (live Google","Search). USE THIS AGENT whenever the query is about blockchain, crypto, tokens, NFTs, DeFi,","wallets, on-chain data, smart contracts, or any supported EVM chain BUT no other agent","(wallet-agent, token-agent, nft-agent, pool-agent) has a direct structured tool for it.","Scope examples: (1) token categories or themes \u2014 meme tokens, AI tokens, gaming tokens, L2 tokens, RWA;",'(2) protocol-specific questions \u2014 "how does X protocol work", "what happened to Y";',"(3) cross-contract or exploratory EVM analysis spanning many contracts;","(4) any open-ended on-chain question without a direct structured tool.","Prefer the specific agents when they clearly match (wallet balances \u2192 wallet-agent,","single-token info/analytics \u2192 token-agent, NFT holdings/metadata \u2192 nft-agent, DEX pools \u2192 pool-agent).","But for anything crypto-related that falls outside those, route HERE \u2014 do not return empty."].join(" "),toolNames:[...En],systemPrompt:ra,cortexFallbackTool:"gemini-search-ai",llm:c,registry:e,options:t})}var sa=`You are the Pool / DEX Liquidity domain expert.
443
+ - Answer in the user's language. Do NOT mention tool names.`,On=["gemini-search-ai"];function dn(c,e,t){return new j({name:"ai-agent",domain:"ai",description:["Catch-all crypto / blockchain expert powered by a search-grounded Gemini model (live Google","Search). USE THIS AGENT whenever the query is about blockchain, crypto, tokens, NFTs, DeFi,","wallets, on-chain data, smart contracts, or any supported EVM chain BUT no other agent","(wallet-agent, token-agent, nft-agent, pool-agent) has a direct structured tool for it.","Scope examples: (1) token categories or themes \u2014 meme tokens, AI tokens, gaming tokens, L2 tokens, RWA;",'(2) protocol-specific questions \u2014 "how does X protocol work", "what happened to Y";',"(3) cross-contract or exploratory EVM analysis spanning many contracts;","(4) any open-ended on-chain question without a direct structured tool.","Prefer the specific agents when they clearly match (wallet balances \u2192 wallet-agent,","single-token info/analytics \u2192 token-agent, NFT holdings/metadata \u2192 nft-agent, DEX pools \u2192 pool-agent).","But for anything crypto-related that falls outside those, route HERE \u2014 do not return empty."].join(" "),toolNames:[...On],systemPrompt:ka,cortexFallbackTool:"gemini-search-ai",llm:c,registry:e,options:t})}var Ta=`You are the Pool / DEX Liquidity domain expert.
437
444
 
438
445
  You answer questions about decentralized exchange liquidity pools \u2014 specifically Uniswap V2/V3/V4 pools across EVM chains \u2014 AND you guide the user through providing liquidity to a pool.
439
446
 
@@ -574,7 +581,7 @@ State awareness:
574
581
 
575
582
  Never fabricate pool addresses, tick numbers, prices, or NFT ids. Every value must come from a tool call this turn or the immediately preceding turn.
576
583
 
577
- Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea .`,Nn=["get-top-pools","get-pool-detail","search-pools","lookup-pool-by-address","open-add-liquidity-form","preview-add-liquidity","estimate_pool_yield"];function an(c,e,t){return new Y({name:"pool-agent",domain:"pool",description:["Handles questions about decentralized exchange liquidity pools \u2014 Uniswap V2/V3/V4 across EVM chains \u2014","AND walks the user through ADDING LIQUIDITY to a pool (provide LP / farm / stake / deposit / cung c\u1EA5p thanh kho\u1EA3n).","Scope: (1) top pools by chain \u2014 biggest pools ranked by TVL, volume, or transaction count;","(2) pool composition \u2014 which token pairs dominate liquidity on a given network;","(3) pool-level analytics \u2014 fee tier, rolling volume buckets (1d/1w/30d), tx count, both tokens;","(4) pool detail by token pair (and optional fee tier) \u2014 full stats for a single pool including TVL, 24h volume, fees, APR estimate, token reserves, and weekly volume history;","(5) pool search \u2014 find pools that match a free-text query (token symbol, token name, or pool address) on a given chain;","(6) single-address lookup \u2014 when the user pastes one 0x address without saying whether it is a pool or a token, resolve both;",'(7) YIELD ESTIMATION \u2014 estimate daily/weekly/monthly/yearly fee income for a given USD deposit into a specific pool ("if I put $1000 into USDC/WETH 0.05%, how much do I earn per day?", "b\u1ECF 500$ v\xE0o pool n\xE0y l\u1EDDi bao nhi\xEAu?");',"(8) ADD LIQUIDITY \u2014 open the AddLiquidityForm UI for a chosen pool, then build the unsigned mint transaction once the user confirms amount and price range.","NOT in scope: wallet-specific LP positions or DeFi holdings the user already owns (\u2192 wallet-agent),","token metadata or token-level market data (\u2192 token-agent), NFT data (\u2192 nft-agent).",'Key distinction from token-agent: pool-agent answers "which POOLS are biggest / most active" and handles "I want to add LP",','while token-agent answers "what is this TOKEN" and "what is the market doing for this TOKEN".'].join(" "),toolNames:[...Nn],systemPrompt:sa,llm:c,registry:e,options:t})}var jo=`You are the Uniswap V3 Subgraph DEX-pool expert.
584
+ Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea .`,qn=["get-top-pools","get-pool-detail","search-pools","lookup-pool-by-address","open-add-liquidity-form","preview-add-liquidity","estimate_pool_yield"];function mn(c,e,t){return new j({name:"pool-agent",domain:"pool",description:["Handles questions about decentralized exchange liquidity pools \u2014 Uniswap V2/V3/V4 across EVM chains \u2014","AND walks the user through ADDING LIQUIDITY to a pool (provide LP / farm / stake / deposit / cung c\u1EA5p thanh kho\u1EA3n).","Scope: (1) top pools by chain \u2014 biggest pools ranked by TVL, volume, or transaction count;","(2) pool composition \u2014 which token pairs dominate liquidity on a given network;","(3) pool-level analytics \u2014 fee tier, rolling volume buckets (1d/1w/30d), tx count, both tokens;","(4) pool detail by token pair (and optional fee tier) \u2014 full stats for a single pool including TVL, 24h volume, fees, APR estimate, token reserves, and weekly volume history;","(5) pool search \u2014 find pools that match a free-text query (token symbol, token name, or pool address) on a given chain;","(6) single-address lookup \u2014 when the user pastes one 0x address without saying whether it is a pool or a token, resolve both;",'(7) YIELD ESTIMATION \u2014 estimate daily/weekly/monthly/yearly fee income for a given USD deposit into a specific pool ("if I put $1000 into USDC/WETH 0.05%, how much do I earn per day?", "b\u1ECF 500$ v\xE0o pool n\xE0y l\u1EDDi bao nhi\xEAu?");',"(8) ADD LIQUIDITY \u2014 open the AddLiquidityForm UI for a chosen pool, then build the unsigned mint transaction once the user confirms amount and price range.","NOT in scope: wallet-specific LP positions or DeFi holdings the user already owns (\u2192 wallet-agent),","token metadata or token-level market data (\u2192 token-agent), NFT data (\u2192 nft-agent).",'Key distinction from token-agent: pool-agent answers "which POOLS are biggest / most active" and handles "I want to add LP",','while token-agent answers "what is this TOKEN" and "what is the market doing for this TOKEN".'].join(" "),toolNames:[...qn],systemPrompt:Ta,llm:c,registry:e,options:t})}var va=`You are the Uniswap V3 Subgraph DEX-pool expert.
578
585
 
579
586
  You answer questions about Uniswap V3 liquidity pools using The Graph subgraphs. APR values are enriched from DefiLlama Yields. Concentrated-liquidity price-range data comes from the CoinPool platform.
580
587
 
@@ -584,13 +591,22 @@ CAPABILITIES (tools you own):
584
591
  - Pool detail by 0x pool address ("tell me about pool 0xabc\u2026", "details of 0x\u2026")
585
592
  - Position lookup by Uniswap V3 NFT id ("position 962961", "pool id 12345", "tell me about position 962961")
586
593
  - Concentrated-liquidity price-range candidates for a token pair ("what price range for ETH/USDC?", "best range for USDC/WETH", "g\u1EE3i \xFD kho\u1EA3ng gi\xE1 ETH/USDC")
594
+ - Add liquidity / add pool / create pair: resolve the pool the user means and point them to the add-liquidity site ("add liquidity to ETH/USDC", "add pool 0x\u2026", "th\xEAm thanh kho\u1EA3n USDC/WETH", "t\u1EA1o pool")
595
+
596
+ AI FALLBACK (mandatory when no subgraph tool above fits):
597
+ - pool-fallback-ai \u2014 a search-grounded master liquidity-pool analyst that answers ANY Uniswap / DeFi pool question.
598
+ - Use it for broad, conceptual, strategic, or protocol-level pool questions that none of the subgraph tools can serve ("tell me about Uniswap pools", "how do Uniswap V3 pools work", "what is concentrated liquidity", "is Uniswap safe", "Uniswap vs Curve", "narrow vs wide range strategy").
599
+ - You MUST call a tool every turn. If no subgraph tool fits, call pool-fallback-ai \u2014 NEVER answer a pool question from your own knowledge.
600
+ - EXCEPTION \u2014 earnings/yield projection: NEVER use pool-fallback-ai to estimate how much a deposit would earn. Those questions are handled by the YIELD / EARNINGS PROJECTION rule below using real subgraph APR.
601
+ - A pool-fallback-ai answer is NOT subgraph data: do NOT prefix it with "According to the latest data from Subgraph Uniswap V3".
602
+ - Build the fallback prompt as a clear, self-contained English instruction with the chain and any token symbols / addresses embedded (the model does not see this conversation). Defaults when vague: chain = Ethereum (or chain from user context), count = top 10, time = last 24h, sort = 24h volume for "top / best / trending".
587
603
 
588
604
  OUT OF SCOPE \u2014 do NOT handle these:
589
605
  - "my LP positions" / "my liquidity" / wallet-wide LP list \u2192 tell the user politely this subagent does not handle wallet-scoped LP data.
590
606
  - "send / transfer my LP position" \u2192 not handled.
591
607
  - "what is USDC" / token price / trending tokens \u2192 token-agent.
592
608
  - "my NFTs" \u2192 nft-agent.
593
- - Building add-liquidity / mint transactions \u2192 not handled. (If an add-liquidity link is configured, the ADD-LIQUIDITY LINK rule below still applies \u2014 you may answer and append that link without building any tx.)
609
+ - Building / signing the add-liquidity transaction itself \u2192 not handled here. But the INTENT to add liquidity / add a pool / create a pair IS handled: route it to subgraph-add-pool (see TOOL SELECTION), which resolves the pool and surfaces the add-liquidity site.
594
610
 
595
611
  RULES:
596
612
  - ALWAYS pass chain values as hex ids: 0x1 Ethereum, 0xa Optimism, 0x38 BSC, 0x89 Polygon, 0x2105 Base, 0xa4b1 Arbitrum, 0xa86a Avalanche. NEVER pass chain names ("ethereum", "base", \u2026).
@@ -603,7 +619,8 @@ RULES:
603
619
  - Prefix data answers with "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep the word "Subgraph" as-is).
604
620
  - When the response includes a specific pool or position, render the provided \`uniswapUrl\` / \`uniswapPositionUrl\` as a clickable markdown link. Never invent URLs.
605
621
 
606
- TOOL SELECTION (count token symbols in the user's request):
622
+ TOOL SELECTION (check the ADD-LIQUIDITY intent FIRST, then count token symbols):
623
+ - ADD-LIQUIDITY / ADD-POOL / CREATE-PAIR intent ("add liquidity", "add LP", "add pool", "provide liquidity", "create pool", "create pair", "th\xEAm thanh kho\u1EA3n", "th\xEAm pool", "t\u1EA1o pool") \u2192 subgraph-add-pool. Pass poolAddress when a 0x pool address is given; else pass tokens=[the 1-2 named symbols] (and feeTier if named); leave both blank when no pool/token is named. This WINS over the token-count rules below \u2014 "add liquidity to ETH/USDC" goes here, NOT to subgraph-search-pools.
607
624
  - 0 token symbols \u2192 subgraph-trending-pools ("trending pools", "top pools on Base").
608
625
  - 1 token symbol \u2192 subgraph-search-pools with tokens=[symbol] ("show pool USDC", "find WBTC pools", "best USDC pool", "stablecoin pools" \u2192 tokens=["USDC","USDT"]). Words like "top"/"biggest"/"best" do NOT change this \u2014 keep it search-pools and set sortBy accordingly.
609
626
  - 2 token symbols \u2192 subgraph-search-pools with tokens=[sym0, sym1] ("ETH/USDC pools", "WBTC/ETH on Arbitrum").
@@ -633,24 +650,35 @@ COINPOOL RANGE GUIDANCE (when using subgraph-coinpool-pairs):
633
650
  - Always pair APR with the min\u2013max range, e.g. "APR 14.3% (range: 1800 \u2013 2200 USDC/ETH)".
634
651
  - When suggesting the best range, point to the highest-APR entry and call out that narrower ranges = higher concentration = higher returns but more risk of going out of range.
635
652
 
653
+ YIELD / EARNINGS PROJECTION ("how much would I earn"):
654
+ - Triggers: any "how much will I earn / make / profit", "l\u1EDDi/l\xE3i bao nhi\xEAu", "m\u1ED7i ng\xE0y/tu\u1EA7n/th\xE1ng/n\u0103m \u0111\u01B0\u1EE3c bao nhi\xEAu", "if I put/deposit $X into <pool/pair>", "b\u1ECF 1000$ v\xE0o pool \u2026". These are arithmetic on a pool's APR \u2014 they are NOT a search question and must NEVER go to pool-fallback-ai.
655
+ - Step 1 \u2014 get the pool's APR (annual %, overall pool APR):
656
+ \u2022 If the IMMEDIATELY PRECEDING turn already showed the SAME pool/pair the user means together with its APR, REUSE that APR directly \u2014 do NOT re-fetch.
657
+ \u2022 Otherwise resolve the pool with the normal tools and read its \`apr\`: a 0x pool address \u2192 subgraph-pool-by-address; a token symbol/pair \u2192 subgraph-search-pools (sortBy="tvl", use the top match). Only one tool call.
658
+ - Step 2 \u2014 compute the projection yourself from the deposit D (default D = 1000 and clearly label it a per-$1000 example when the user gave no amount) and APR%:
659
+ \u2022 daily = D \xD7 APR/100 / 365
660
+ \u2022 weekly = D \xD7 APR/100 / 52
661
+ \u2022 monthly = D \xD7 APR/100 / 12
662
+ \u2022 yearly = D \xD7 APR/100
663
+ - Step 3 \u2014 present in the user's language: lead with the pool (pair + fee %) and its APR %, then the daily/weekly/monthly/yearly figures (USD, 2 decimals, e.g. $0.34/day). Add a one-line note that this is fee yield assuming the APR holds and full-range liquidity, and that real returns vary with volume and impermanent loss \u2014 informational, not financial advice (vary the wording).
664
+ - If the resolved pool's APR is null/unknown, say so plainly and show TVL/volume instead \u2014 do NOT invent an APR or fall back to pool-fallback-ai.
665
+ - This is the ONE case where you do the math from data already on hand: every APR, TVL and price figure must still come from a tool call (this turn) or the immediately preceding turn \u2014 never from your own memory of token prices.
666
+
636
667
  FOLLOW-UPS / CONTINUATIONS:
637
668
  - Short replies like "yes", "tell me more", "the first one", "show ranges" refer back to the previous turn. Extract tokens/chains/pool addresses from the prior assistant message and call the appropriate tool. NEVER ask for clarification when history clearly shows what was discussed.
638
669
 
639
- NEVER fabricate pool addresses, token prices, APRs, or position ids. Every value must come from a tool call this turn or the immediately preceding turn.`;function aa(c){return c?`${jo}
640
-
641
- ADD-LIQUIDITY LINK:
642
- - If the user mentions adding liquidity / adding a pool / adding a position (including phrases like "add liquidity", "add pool", "add position", "create pool", "create pair", "th\xEAm thanh kho\u1EA3n", "th\xEAm pool", "th\xEAm position"), keep your normal answer content and append this exact link at the end of the response: ${c}`:jo}var zo=["subgraph-search-pools","subgraph-trending-pools","subgraph-pool-by-address","subgraph-pool-by-position-id","subgraph-position-detail","subgraph-coinpool-pairs"];function eo(c,e,t){return new Y({name:"pool-subgraph-agent",domain:"pool",description:["Answers questions about Uniswap V3 liquidity pools using The Graph subgraphs as the primary source","(APR is enriched from DefiLlama Yields; concentrated-liquidity price ranges come from CoinPool).","Scope: (1) pool search by token symbols on one or more EVM chains, with TVL/APR/fee filters and sort by tvl/volume/apr/fee/liquidity;","(2) trending pools ranked by 24h volume on a chain;","(3) pool detail for a specific 0x pool address;","(4) lookup the underlying pool for a Uniswap V3 NFT position id (numeric, e.g. 962961);","(5) full position detail (deposits, withdrawals, collected fees) for a numeric position id;","(6) CoinPool concentrated-liquidity price-range candidates with per-range APR for a token pair.",'NOT in scope: wallet-wide LP holdings ("my positions"), sending/transferring LP NFTs, add-liquidity action flows,',"token metadata (\u2192 token-agent), NFT data (\u2192 nft-agent).","Prefer this subagent over pool-agent when the deployment is configured to use Subgraph data (the two are mutually exclusive at config time)."].join(" "),toolNames:[...zo],systemPrompt:aa(t?.linkAddLiquidity),llm:c,registry:e,options:t})}var ia=`Call exactly ONE tool every turn \u2014 ALWAYS, even for a bare verb with no details ("send token", "buy", "approve"). Never reply in text and never ask the user for missing fields (token, amount, recipient): the tool opens a picker/form that collects them.
670
+ NEVER fabricate pool addresses, token prices, APRs, or position ids. Every value must come from a tool call this turn or the immediately preceding turn.`,or=["subgraph-search-pools","subgraph-trending-pools","subgraph-pool-by-address","subgraph-pool-by-position-id","subgraph-position-detail","subgraph-coinpool-pairs","subgraph-add-pool","pool-fallback-ai"];function lo(c,e,t){return new j({name:"pool-subgraph-agent",domain:"pool",description:["Answers questions about Uniswap V3 liquidity pools using The Graph subgraphs as the primary source","(APR is enriched from DefiLlama Yields; concentrated-liquidity price ranges come from CoinPool).","Scope: (1) pool search by token symbols on one or more EVM chains, with TVL/APR/fee filters and sort by tvl/volume/apr/fee/liquidity;","(2) trending pools ranked by 24h volume on a chain;","(3) pool detail for a specific 0x pool address;","(4) lookup the underlying pool for a Uniswap V3 NFT position id (numeric, e.g. 962961);","(5) full position detail (deposits, withdrawals, collected fees) for a numeric position id;","(6) CoinPool concentrated-liquidity price-range candidates with per-range APR for a token pair;","(7) the add-liquidity / add-pool / create-pair INTENT \u2014 resolves the pool (by address or token pair) and surfaces the add-liquidity website link.",'NOT in scope: wallet-wide LP holdings ("my positions"), sending/transferring LP NFTs, building/signing the add-liquidity transaction itself,',"token metadata (\u2192 token-agent), NFT data (\u2192 nft-agent).","Prefer this subagent over pool-agent when the deployment is configured to use Subgraph data (the two are mutually exclusive at config time)."].join(" "),toolNames:[...or],systemPrompt:va,cortexFallbackTool:"pool-fallback-ai",llm:c,registry:e,options:t})}var Sa=`Call exactly ONE tool every turn \u2014 ALWAYS, even for a bare verb with no details ("send token", "buy", "approve"). Never reply in text and never ask the user for missing fields (token, amount, recipient): the tool opens a picker/form that collects them.
643
671
 
644
672
  Tools:
645
- - open-send-native-form \u2014 native coin (ETH/BNB/MATIC/\u2026)
646
- - open-send-token-form \u2014 ERC-20
673
+ - open-send-native-form \u2014 ONLY the current chain's native coin (see "native coin" in User context)
674
+ - open-send-token-form \u2014 ERC-20 (ANY coin that is NOT the current chain's native one)
647
675
  - open-buy-token-form \u2014 buy a token (named OR, if none named, returns trending picks)
648
676
  - open-swap-token-form \u2014 swap a token the user ALREADY HOLDS into another ("swap USDC to USDT", "swap USDC")
649
677
  - open-approve-token-form \u2014 ERC-20 approve/revoke
650
678
 
651
679
  Intent \u2192 tool:
652
- - "send eth" / "transfer 0.1 eth" \u2192 open-send-native-form
653
- - "send <token>" / "send usdc" / "transfer 50 DAI" \u2192 open-send-token-form WITH token_symbol = the named token (e.g. token_symbol="USDC"); "send token" / "transfer some token" (no token named) \u2192 open-send-token-form, leave token_symbol blank \u2192 wallet picker. ALWAYS set token_symbol whenever the user names a token.
680
+ - "send <coin>": route by the CURRENT chain's native coin (given in User context). If <coin> equals it \u2192 open-send-native-form (native_symbol=<coin>); otherwise <coin> is an ERC-20 on this chain \u2192 open-send-token-form (token_symbol=<coin>). E.g. on BSC (native BNB): "send eth" \u2192 open-send-token-form, "send bnb" \u2192 open-send-native-form.
681
+ - "send token" / "transfer some token" (no coin named) \u2192 open-send-token-form, leave token_symbol blank \u2192 wallet picker. ALWAYS set token_symbol whenever the user names a token.
654
682
  - "buy <token>" \u2192 open-buy-token-form (with token_symbol); "buy a token / buy trending" (no token named) \u2192 open-buy-token-form (leave token_symbol blank)
655
683
  - "swap <A> to <B>" / "\u0111\u1ED5i <A> sang <B>" / "convert/exchange <A> for <B>" \u2192 open-swap-token-form (from_symbol = A, to_symbol = B); "swap <A>" (only source named) \u2192 open-swap-token-form (from_symbol = A, leave to_symbol blank \u2192 trending picker); "swap" (nothing named) \u2192 open-swap-token-form (leave both blank \u2192 wallet picker). SWAP is when the user spends a token they hold; BUY is when they name a token to acquire and pick a wallet token to pay with.
656
684
  - "approve" \u2192 open-approve-token-form
@@ -660,7 +688,7 @@ NFT sends are NOT handled here \u2014 they belong to the nft-agent.
660
688
 
661
689
  Skip the tool ONLY if: walletAddress missing (ask to connect) \xB7 user explicitly names a chain different from userContext.chain (ask to switch).
662
690
 
663
- After the tool returns: 1 short sentence in the user's language. Don't list missing fields. Don't mention tool names.`,In=["open-send-native-form","open-send-token-form","open-buy-token-form","open-swap-token-form","open-approve-token-form"];function ln(c,e,t){return new Y({name:"wallet-action-agent",domain:"wallet-action",description:["User wants to PERFORM an on-chain action (send, buy, swap, approve).","This agent picks the matching open-*-form tool which emits a pre-filled form (or, for buy/swap, a confirm panel) for the FE to render.","The FE \u2014 not this agent \u2014 builds and submits the tx after the user confirms.","NOT in scope: read-only wallet/token/NFT queries, sending/transferring an NFT (\u2192 nft-agent), liquidity provisioning."].join(" "),toolNames:[...In],systemPrompt:ia,mustCallTool:!0,llm:c,registry:e,options:t})}function Dn(c,e,t,n){let r=(l,u)=>{let d=n?.[l];return d===void 0?u:d},o=r("pool-subgraph",!1),s=n?.pool===!0,a=o?s:r("pool",!0),i=[];return r("wallet",!0)&&i.push(nn(c,e,t)),r("wallet-action",!0)&&i.push(ln(c,e,t)),r("token",!0)&&i.push(on(c,e,t)),r("nft",!0)&&i.push(rn(c,e,t)),a&&i.push(an(c,e,t)),o&&i.push(eo(c,e,t)),r("ai",!0)&&i.push(sn(c,e,t)),i}var la=`You are a response synthesizer. Given the user's question and tool execution results, produce a clear, helpful final answer.
691
+ After the tool returns: 1 short sentence in the user's language. Don't list missing fields. Don't mention tool names.`,Fn=["open-send-native-form","open-send-token-form","open-buy-token-form","open-swap-token-form","open-approve-token-form"];function pn(c,e,t){return new j({name:"wallet-action-agent",domain:"wallet-action",description:["User wants to PERFORM an on-chain action (send, buy, swap, approve).","This agent picks the matching open-*-form tool which emits a pre-filled form (or, for buy/swap, a confirm panel) for the FE to render.","The FE \u2014 not this agent \u2014 builds and submits the tx after the user confirms.","NOT in scope: read-only wallet/token/NFT queries, sending/transferring an NFT (\u2192 nft-agent), liquidity provisioning."].join(" "),toolNames:[...Fn],systemPrompt:Sa,mustCallTool:!0,llm:c,registry:e,options:t})}function $n(c,e,t,n){let r=(l,u)=>{let d=n?.[l];return d===void 0?u:d},o=r("pool-subgraph",!1),s=n?.pool===!0,a=o?s:r("pool",!0),i=[];return r("wallet",!0)&&i.push(ln(c,e,t)),r("wallet-action",!0)&&i.push(pn(c,e,t)),r("token",!0)&&i.push(cn(c,e,t)),r("nft",!0)&&i.push(un(c,e,t)),a&&i.push(mn(c,e,t)),o&&i.push(lo(c,e,t)),r("ai",!0)&&i.push(dn(c,e,t)),i}var xa=`You are a response synthesizer. Given the user's question and tool execution results, produce a clear, helpful final answer.
664
692
 
665
693
  Rules:
666
694
  - Be concise but complete.
@@ -669,12 +697,12 @@ Rules:
669
697
  - If tools returned errors, explain honestly what happened.
670
698
  - Respond in the same language as the user's message.
671
699
  - Do NOT mention internal tool names, JSON structures, or technical implementation details.
672
- `,Gt=class{llm;constructor(e){this.llm=e}async synthesise(e,t,n,r){if(!t.steps.some(m=>m.phase==="act")&&t.finalAnswer)return Qo(t.finalAnswer);let s=t.steps.filter(m=>m.phase==="observe").map((m,g)=>`[Result ${g+1}]: ${m.content}`).join(`
673
- `),a=be(n),i=Ae(a,8),l=(r||"").trim().toLowerCase(),u=l?`IMPORTANT: Reply in BCP-47 language "${l}" (the language the user actually typed). Tool results may contain text in other languages \u2014 ignore that and reply ONLY in "${l}".`:`IMPORTANT: The user wrote in the language of this message: "${e}". Reply in that exact language, ignoring any other languages that appear inside tool results.`,d=[{role:"system",content:la,timestamp:0},...i,{role:"user",content:[`User question: ${e}`,"",`Agent reasoning: ${t.finalAnswer}`,"",s?`Tool results:
700
+ `,Qt=class{llm;constructor(e){this.llm=e}async synthesise(e,t,n,r){if(!t.steps.some(p=>p.phase==="act")&&t.finalAnswer)return rr(t.finalAnswer);let s=t.steps.filter(p=>p.phase==="observe").map((p,h)=>`[Result ${h+1}]: ${p.content}`).join(`
701
+ `),a=we(n),i=Ae(a,8),l=(r||"").trim().toLowerCase(),u=l?`IMPORTANT: Reply in BCP-47 language "${l}" (the language the user actually typed). Tool results may contain text in other languages \u2014 ignore that and reply ONLY in "${l}".`:`IMPORTANT: The user wrote in the language of this message: "${e}". Reply in that exact language, ignoring any other languages that appear inside tool results.`,d=[{role:"system",content:xa,timestamp:0},...i,{role:"user",content:[`User question: ${e}`,"",`Agent reasoning: ${t.finalAnswer}`,"",s?`Tool results:
674
702
  ${s}`:"","",u].filter(Boolean).join(`
675
- `),timestamp:Date.now()}],p=await this.llm.chat(d);return Qo(p.text)}};function Qo(c){return c&&c.replace(/^\[[a-z0-9-]+-agent\]\s*/i,"").replace(/\n\n\[[a-z0-9-]+-agent\]\s*/gi,`
703
+ `),timestamp:Date.now()}],m=await this.llm.chat(d);return rr(m.text)}};function rr(c){return c&&c.replace(/^\[[a-z0-9-]+-agent\]\s*/i,"").replace(/\n\n\[[a-z0-9-]+-agent\]\s*/gi,`
676
704
 
677
- `)}var ca=`You are a CONTEXTUALIZER.
705
+ `)}var Aa=`You are a CONTEXTUALIZER.
678
706
 
679
707
  Your ONLY job: read the conversation history + the user's latest message,
680
708
  and produce a SELF-CONTAINED version of that message.
@@ -717,6 +745,13 @@ Output STRICT JSON with this shape (no prose, no code fences):
717
745
  6. mentionedChain: set it to the blockchain network the user explicitly names
718
746
  this turn (resolve it from context for follow-ups). Use the network's common
719
747
  name. Leave it "" when the user names no specific chain.
748
+ 8. PRESERVE THE USER'S EXPLICIT ACTION/INTENT VERB. When THIS message states an
749
+ action verb (add / provide / create / remove / buy / sell / swap / send /
750
+ stake / show / compare \u2026), the rewrite MUST keep that exact intent. You may
751
+ borrow missing OBJECTS from history (token pair, fee tier, chain, amount) but
752
+ you may NEVER swap the user's verb for a different one \u2014 e.g. do NOT turn
753
+ "add pool" into "show pool" just because the previous turn was a "show".
754
+ Only infer the verb from history when THIS message has none (pure ellipsis).
720
755
  7. needsWebSearch: judge the REWRITTEN query. Set true ONLY when answering it
721
756
  correctly requires LIVE or UP-TO-DATE information from the web:
722
757
  - current/real-time prices, market cap, volume, rankings;
@@ -790,13 +825,20 @@ User: "what is the floor price of Bored Apes?"
790
825
  \u2192 {"isFollowUp": false, "rewrittenQuery": "what is the floor price of Bored Apes?",
791
826
  "topicChanged": true, "reasoning": "already self-contained; topic pivot to NFTs"}
792
827
 
828
+ History: assistant showed the ETH/USDC 0.3% pool details.
829
+ User: "i want add pool eth/usdc"
830
+ \u2192 {"isFollowUp": true, "rewrittenQuery": "add liquidity to the ETH/USDC 0.3% pool",
831
+ "topicChanged": false,
832
+ "reasoning": "kept the user's 'add' intent; only borrowed the 0.3% fee tier from history",
833
+ "needsWebSearch": false}
834
+
793
835
  ## IMPORTANT FOR TRANSACTION REFERENCES
794
836
 
795
837
  When the user references "this transaction", "that tx", "the transaction above", "chi ti\u1EBFt giao d\u1ECBch n\xE0y", etc.:
796
838
  - Scan the conversation history for tool results (role: "tool") that contain transaction data.
797
839
  - Extract the EXACT hash value (starts with "0x", 66 chars) from the most recent such result.
798
840
  - Include it verbatim in rewrittenQuery. NEVER invent or modify a hash.
799
- - If no hash is found in history, set rewrittenQuery to the original message verbatim so the subagent knows to fetch it.`,ua=`You detect the language of a single user message.
841
+ - If no hash is found in history, set rewrittenQuery to the original message verbatim so the subagent knows to fetch it.`,Pa=`You detect the language of a single user message.
800
842
 
801
843
  Output STRICT JSON, no prose, no code fences:
802
844
  {"language": "<BCP-47 tag>"}
@@ -824,17 +866,17 @@ Examples:
824
866
  "\uC548\uB155\uD558\uC138\uC694" \u2192 {"language":"ko"}
825
867
  "\u043F\u0440\u0438\u0432\u0435\u0442" \u2192 {"language":"ru"}
826
868
  "\u0E2A\u0E27\u0E31\u0E2A\u0E14\u0E35" \u2192 {"language":"th"}
827
- "123" \u2192 {"language":""}`,Ln=class{llm;historyMessages;debug;constructor(e,t){this.llm=e,this.historyMessages=t?.historyMessages??8,this.debug=t?.debug??!1}async rewrite(e,t,n=[]){let r=t.some(k=>k.role==="user"||k.role==="assistant"),o=Yo(t),s=be(t),a=Ae(s,this.historyMessages),i=[];n.length>0&&i.push(`Domain hint: the previous assistant turn was handled by ${n.join(", ")}. A short follow-up almost certainly still belongs to that domain \u2014 preserve it in the rewrite.`),o&&i.push(o);let l=i.length>0?`
869
+ "123" \u2192 {"language":""}`,Wn=class{llm;historyMessages;debug;constructor(e,t){this.llm=e,this.historyMessages=t?.historyMessages??8,this.debug=t?.debug??!1}async rewrite(e,t,n=[]){let r=t.some(k=>k.role==="user"||k.role==="assistant"),o=nr(t),s=we(t),a=Ae(s,this.historyMessages),i=[];n.length>0&&i.push(`Domain hint: the previous assistant turn was handled by ${n.join(", ")}. A short follow-up almost certainly still belongs to that domain \u2014 preserve it in the rewrite.`),o&&i.push(o);let l=i.length>0?`
828
870
 
829
871
  ${i.join(`
830
872
 
831
- `)}`:"",u=Date.now(),d=[{role:"system",content:ca,timestamp:0},...a,{role:"user",content:`Latest user message to contextualize:
873
+ `)}`:"",u=Date.now(),d=[{role:"system",content:Aa,timestamp:0},...a,{role:"user",content:`Latest user message to contextualize:
832
874
  ${e}${l}
833
875
 
834
- Return JSON only.`,timestamp:Date.now()}],p=[{role:"system",content:ua,timestamp:0},{role:"user",content:e,timestamp:Date.now()}],[m,g]=await Promise.allSettled([this.llm.chat(d),this.llm.chat(p)]),f={};if(m.status==="fulfilled")try{f=this.parseJSON(m.value.text)}catch(k){this.debug&&console.log(`[QueryRewriter] rewrite parse fail: ${k}`)}else this.debug&&console.log(`[QueryRewriter] rewrite call fail: ${m.reason}`);let h="";if(g.status==="fulfilled")try{let k=this.parseJSON(g.value.text);typeof k.language=="string"&&(h=k.language.trim().toLowerCase())}catch(k){this.debug&&console.log(`[QueryRewriter] detect parse fail: ${k}`)}else this.debug&&console.log(`[QueryRewriter] detect call fail: ${g.reason}`);let y={isFollowUp:r&&typeof f.isFollowUp=="boolean"?f.isFollowUp:!1,rewrittenQuery:typeof f.rewrittenQuery=="string"&&f.rewrittenQuery.trim()?f.rewrittenQuery.trim():e,topicChanged:r&&typeof f.topicChanged=="boolean"?f.topicChanged:!1,reasoning:typeof f.reasoning=="string"?f.reasoning:"",language:h,mentionedChain:typeof f.mentionedChain=="string"?f.mentionedChain.trim():"",needsWebSearch:f.needsWebSearch===!0};if(this.debug){let k=Date.now()-u,w=y.rewrittenQuery!==e;console.log(`
835
- [QueryRewriter] (${k}ms)`),console.log(` original : "${e}"`),console.log(` rewritten : "${y.rewrittenQuery}"${w?"":" (unchanged)"}`),console.log(` followUp : ${y.isFollowUp} topicChanged: ${y.topicChanged}`),y.reasoning&&console.log(` reasoning : ${y.reasoning}`),console.log(` language : ${y.language}`),console.log(` webSearch : ${y.needsWebSearch}`)}return y}parseJSON(e){let t=e.trim();t.startsWith("```")&&(t=t.replace(/^```(?:json)?\s*/,"").replace(/```\s*$/,""));let n=t.match(/\{[\s\S]*\}/);return n&&(t=n[0]),JSON.parse(t)}};var M=require("@langchain/langgraph");var oe=c=>({reducer:(e,t)=>t,default:c}),da=M.Annotation.Root({userMessage:(0,M.Annotation)(),doSuggest:(0,M.Annotation)(oe(()=>!0)),overrideLang:(0,M.Annotation)(oe(()=>"")),conversationMessages:(0,M.Annotation)(oe(()=>[])),lastSubagents:(0,M.Annotation)(oe(()=>[])),messageId:(0,M.Annotation)(oe(()=>"")),rewrite:(0,M.Annotation)(oe(()=>null)),effectiveQuery:(0,M.Annotation)(oe(()=>"")),turnLanguage:(0,M.Annotation)(oe(()=>"")),kbContext:(0,M.Annotation)(oe(()=>"")),decision:(0,M.Annotation)(oe(()=>null)),assignment:(0,M.Annotation)(oe(()=>null)),assignmentIndex:(0,M.Annotation)(oe(()=>0)),subResults:(0,M.Annotation)({reducer:(c,e)=>c.concat(e),default:()=>[]}),response:(0,M.Annotation)(oe(()=>null))});function Xo(c){let e=async d=>{c.addUserMessage(d.userMessage),c.needsSummary()&&await c.compactHistory();let p=c.conversationForTurn(),m=c.lastAssistantSubagents(p);return c.debug&&m.length>0&&console.log(`[AgentCore] previous turn handled by: ${m.join(", ")}`),{conversationMessages:p,lastSubagents:m,messageId:c.generateMessageId()}},t=async d=>{let p=await c.rewrite(d.userMessage,d.conversationMessages,d.lastSubagents),m=d.overrideLang||p.language||"";return{rewrite:p,effectiveQuery:p.rewrittenQuery,turnLanguage:m}},n=async d=>{let p=d.rewrite,m=p.isFollowUp?d.effectiveQuery:d.userMessage,g=await c.searchKB(m),f=c.formatKBContext(g),h=await c.tryAnswerFromKB(d.userMessage,g,f,d.messageId);return h?{kbContext:f,response:{...h,messageId:d.messageId,rewrite:p}}:{kbContext:f}},r=async d=>({decision:await c.route(d.effectiveQuery,d.conversationMessages,c.subagentCards(),d.lastSubagents)}),o=async d=>({response:{...await c.answerDirectly(d.effectiveQuery,d.conversationMessages,d.kbContext,d.doSuggest,d.messageId,d.rewrite?.needsWebSearch??!1),messageId:d.messageId,routerDecision:d.decision,rewrite:d.rewrite}}),s=async d=>{let p=await c.runSubagent(d.assignment,d.conversationMessages,d.turnLanguage);return{subResults:[{i:d.assignmentIndex,result:p}]}},a=async d=>{let p=d.subResults.slice().sort((b,T)=>b.i-T.i).map(b=>b.result);c.persistToolMessages(p);let m=c.mergeTrace(p),g=await c.synthesise(d.userMessage,m,c.synthesiserHistory(),d.turnLanguage||void 0),f=p.map(b=>b.subagentName),h=c.collectUiActions(p,d.turnLanguage||null),y=c.collectActionButtons(p,d.turnLanguage||null),k=c.subResultsTriggerNoSuggestions(p);return{response:{...await c.finaliseAnswer({userMessage:d.userMessage,answer:g,subagents:f,uiActions:h,actionButtons:y,messageId:d.messageId},{generateSuggestions:d.doSuggest&&!k&&y.length===0}),messageId:d.messageId,trace:m,routerDecision:d.decision,subagentResults:p,rewrite:d.rewrite,...h.length>0?{uiActions:h}:{},...y.length>0?{actionButtons:y}:{}}}},i=async d=>{let p=(d.rewrite?.mentionedChain||String(c.connectedChain()??"")).trim();return{response:{...await c.answerUnsupportedChain(p,d.turnLanguage,d.messageId),messageId:d.messageId,routerDecision:d.decision,rewrite:d.rewrite}}},l=d=>d.response?M.END:"route",u=d=>{let p=d.decision?.assignments??[];if(p.length===0)return"direct";let m=p.some(f=>f.subagent!=="ai-agent"),g=(d.rewrite?.mentionedChain||c.connectedChain()||"").trim();return m&&g!==""&&!pn(g)?"unsupportedChain":p.map((f,h)=>new M.Send("subagent",{...d,assignment:f,assignmentIndex:h}))};return new M.StateGraph(da).addNode("setup",e).addNode("contextualize",t).addNode("kb",n).addNode("route",r).addNode("direct",o).addNode("subagent",s).addNode("synthesize",a).addNode("unsupportedChain",i).addEdge(M.START,"setup").addEdge("setup","contextualize").addEdge("contextualize","kb").addConditionalEdges("kb",l,["route",M.END]).addConditionalEdges("route",u,["direct","subagent","unsupportedChain"]).addEdge("unsupportedChain",M.END).addEdge("direct",M.END).addEdge("subagent","synthesize").addEdge("synthesize",M.END).compile()}var Jo=require("js-sha3");function Mn(){let c=Date.now().toString(16),e=Math.floor(Math.random()*65535).toString(16).padStart(4,"0");return(c+e).slice(-8)}var ma="You are a helpful AI assistant. Answer the user accurately and concisely. Respond in the same language as the user.",Bn=class{llm;registry;history;summarizer;router;rewriter;subagents;synthesizer;chatGraph;systemPrompt;debug;secretKey;licenseChecked=!1;userContext={};storage;storageKey;persistHistoryEnabled;kb;kbEntries;vectorKB=null;vectorKBAutoIngested=!1;vectorKBAutoIngestPromise=null;autoIngestEnabled=!1;kbAnswerThreshold;suggestionsEnabled;historyLoaded=!1;historyLoadingPromise=null;historyReady;constructor(e){this.secretKey=e.secretKey,Xn(e.rpcUrls),mn(e.excludeChains),this.llm=new pe(e.llm),this.registry=new Fe,this.history=new $t(e.maxHistoryMessages??50),this.summarizer=new Wt(this.llm),this.synthesizer=new Gt(this.llm),this.systemPrompt=e.systemPrompt??ma,this.debug=e.debug??!1,this.suggestionsEnabled=e.generateSuggestions??!0,this.storage=e.storage??null,this.storageKey=e.storageKey??"keyring-agent-history",this.persistHistoryEnabled=e.persistHistory??!1,this.kbEntries=new Vt(e.knowledgeBase),this.kbEntries.setLLM(this.llm);let t=e.vectorKB;t?.enabled?(t.provider?this.kb=t.provider:(this.vectorKB=new Re({url:t.url,token:t.token,namespace:t.namespace,minScore:t.minScore,defaultTopK:t.defaultTopK,debug:this.debug}),this.kb=this.vectorKB,this.autoIngestEnabled=t.autoIngest===!0),this.debug&&console.log(`[AgentCore] vector KB enabled (${t.provider?"custom provider":"Upstash"}`+(t.namespace?`, ns=${t.namespace}`:"")+")")):this.kb=this.kbEntries;let n=e.kbAnswerThreshold;if(typeof n=="number"?this.kbAnswerThreshold=n:t?.enabled&&!t.provider?this.kbAnswerThreshold=t.minScore??1.3:this.kbAnswerThreshold=.8,this.debug&&console.log(`[AgentCore] kbAnswerThreshold = ${this.kbAnswerThreshold}`),e.moralis!==!1){let u=typeof e.moralis=="object"?e.moralis:void 0;this.registry.register(new Je(u)),this.registry.register(new Ke(u)),this.registry.register(new Ze(u)),this.registry.register(new et(u)),this.registry.register(new tt(u)),this.registry.register(new Ye(u)),this.registry.register(new nt(u)),this.registry.register(new ot(u)),this.registry.register(new lt(u)),this.registry.register(new st(u)),this.registry.register(new at(u)),this.registry.register(new it(u)),this.registry.register(new rt(u)),this.registry.register(new Qe(u)),this.registry.register(new Xe(u)),this.registry.register(new je(u)),this.registry.register(new ze(u))}let r=e.nftLink;this.registry.register(new ct({url:r})),this.registry.register(new ut({url:r})),this.registry.register(new dt({url:r})),this.registry.register(new mt(e.llm));let o=e.subagents?.["pool-subgraph"]===!0;if((o?e.subagents?.pool===!0:e.subagents?.pool!==!1)&&e.uniswap!==!1){let u=typeof e.uniswap=="object"?e.uniswap:void 0;this.registry.register(new pt(u)),this.registry.register(new ht(u)),this.registry.register(new gt(u)),this.registry.register(new ft(u)),this.registry.register(new Pt(u));let d=u?{isProduction:u.isProduction}:void 0;this.registry.register(new xt({baseUrl:u?.baseUrl,pool:d,minProvideUsd:u?.minProvideUsd})),this.registry.register(new At({pool:d}))}if(o&&e.subgraph!==!1){let u=typeof e.subgraph=="object"?e.subgraph:void 0;this.registry.register(new _t(u)),this.registry.register(new Ct(u)),this.registry.register(new Ut(u)),this.registry.register(new Rt(u)),this.registry.register(new Et(u)),this.registry.register(new Nt(u))}if(e.subagents?.["wallet-action"]!==!1){let u=e.moralis===!1?void 0:typeof e.moralis=="object"?e.moralis:{};this.registry.register(new Lt(u)),this.registry.register(new Mt(u)),this.registry.register(new Ft(u,{url:r})),this.registry.register(new Bt(u)),this.registry.register(new Ot(u,{debug:this.debug})),this.registry.register(new qt(u,{debug:this.debug}))}this.router=new Ht(this.llm,{debug:this.debug}),this.rewriter=new Ln(this.llm,{debug:this.debug});let i=typeof e.subgraph=="object"?e.subgraph:void 0,l=Dn(this.llm,this.registry,{maxToolCalls:e.maxIterations??5,debug:this.debug,linkAddLiquidity:i?.linkAddLiquidity},e.subagents);this.subagents=new Map(l.map(u=>[u.name,u])),this.chatGraph=Xo(this.createGraphHost()),this.historyReady=this.loadHistory()}registerTool(e){this.registry.register(e)}registerTools(e){for(let t of e)this.registry.register(t)}unregisterTool(e){return this.registry.unregister(e)}listTools(){return this.registry.listNames()}async invokeTool(e,t,n){if(this.assertLicense(),await this.loadHistory(),!this.registry.get(e))return{answer:`Unknown tool: "${e}".`,messageId:Mn()};this.debug&&console.log(`[AgentCore] invokeTool ${e} ${JSON.stringify(t)}`);let o=n?.userMessage??`[invoke ${e}]`;this.history.add({role:"user",content:o,timestamp:Date.now()});let s=await this.registry.execute(e,t,this.userContext),a=s.data,i=s.success?a?.summary??"Done.":`Action failed: ${s.error??"unknown error"}`,l=Mn(),u=s.success&&s.ui?[this.stampLanguage(s.ui,null)]:[],d={role:"assistant",content:i,timestamp:Date.now(),subagents:["direct-invoke"],messageId:l};return u.length>0&&(d.uiActions=u),this.history.add(d),await this.persistHistory(),{answer:i,messageId:l,...u.length>0?{uiActions:u}:{}}}registerSubagent(e){this.subagents.set(e.name,e)}unregisterSubagent(e){return this.subagents.delete(e)}listSubagents(){return Array.from(this.subagents.keys())}setKnowledgeBase(e){this.kbEntries.setEntries(e),this.vectorKBAutoIngested=!1}addKnowledgeBase(e){this.kbEntries.addEntries(e),this.vectorKBAutoIngested=!1}async ingestKnowledgeBase(){if(!this.vectorKB)return{count:0};let e=this.kbEntries.getEntries();if(e.length===0)return{count:0};let t=await this.vectorKB.upsert(e);return this.vectorKBAutoIngested=!0,t}async maybeAutoIngest(e){if(!(!e||!this.vectorKB||this.vectorKBAutoIngested)){if(this.vectorKBAutoIngestPromise)return this.vectorKBAutoIngestPromise;this.vectorKBAutoIngestPromise=(async()=>{try{let{count:t}=await this.ingestKnowledgeBase();this.debug&&t>0&&console.log(`[AgentCore] auto-ingested ${t} KB entr${t===1?"y":"ies"} to vector store`)}catch(t){this.debug&&console.warn("[AgentCore] auto-ingest failed:",t)}finally{this.vectorKBAutoIngestPromise=null}})(),await this.vectorKBAutoIngestPromise}}setUserContext(e){this.userContext={...e}}getUserContext(){return{...this.userContext}}async loadHistory(){if(!this.historyLoaded){if(this.historyLoadingPromise)return this.historyLoadingPromise;this.historyLoadingPromise=this._doLoadHistory(),await this.historyLoadingPromise}}async _doLoadHistory(){if(!this.storage){this.debug&&console.log("[AgentCore] loadHistory: no storage configured"),this.historyLoaded=!0;return}if(!this.persistHistoryEnabled){this.debug&&console.log("[AgentCore] loadHistory: persistHistory disabled \u2014 starting fresh session");try{await this.storage.removeItem(this.storageKey)}catch(e){this.debug&&console.warn("[AgentCore] failed to clear stale history:",e)}this.historyLoaded=!0;return}try{let e=await this.storage.getItem(this.storageKey);if(this.debug&&console.log(`[AgentCore] loadHistory: storage key "${this.storageKey}" ${e?`has ${e.length} chars`:"is empty"}`),e){let t=JSON.parse(e);this.history.restore(t),this.debug&&console.log(`[AgentCore] Restored ${this.history.length} messages from storage`)}}catch(e){this.debug&&console.warn("[AgentCore] Failed to load history from storage:",e)}finally{this.historyLoaded=!0}}async persistHistory(){if(this.storage&&this.persistHistoryEnabled)try{let e=JSON.stringify(this.history.serialise());await this.storage.setItem(this.storageKey,e)}catch(e){this.debug&&console.warn("[AgentCore] Failed to persist history to storage:",e)}}assertLicense(){if(this.licenseChecked)return;let e="d00d67291d3c660a5034b01c0b2afbc8d4ac5552099a04f28a92d75fdb44c188";if(e&&(!this.secretKey||(0,Jo.sha3_256)(this.secretKey)!==e))throw new Error("AgentCore: invalid or missing secretKey.");this.licenseChecked=!0}async chat(e,t){this.assertLicense();let n=t?.generateSuggestions??this.suggestionsEnabled,r=typeof t?.language=="string"&&t.language.trim()?t.language.trim():"";await this.loadHistory(),await this.maybeAutoIngest(this.autoIngestEnabled);let o=Date.now();this.debug&&(console.log(`
876
+ Return JSON only.`,timestamp:Date.now()}],m=[{role:"system",content:Pa,timestamp:0},{role:"user",content:e,timestamp:Date.now()}],[p,h]=await Promise.allSettled([this.llm.chat(d),this.llm.chat(m)]),g={};if(p.status==="fulfilled")try{g=this.parseJSON(p.value.text)}catch(k){this.debug&&console.log(`[QueryRewriter] rewrite parse fail: ${k}`)}else this.debug&&console.log(`[QueryRewriter] rewrite call fail: ${p.reason}`);let f="";if(h.status==="fulfilled")try{let k=this.parseJSON(h.value.text);typeof k.language=="string"&&(f=k.language.trim().toLowerCase())}catch(k){this.debug&&console.log(`[QueryRewriter] detect parse fail: ${k}`)}else this.debug&&console.log(`[QueryRewriter] detect call fail: ${h.reason}`);let y={isFollowUp:r&&typeof g.isFollowUp=="boolean"?g.isFollowUp:!1,rewrittenQuery:typeof g.rewrittenQuery=="string"&&g.rewrittenQuery.trim()?g.rewrittenQuery.trim():e,topicChanged:r&&typeof g.topicChanged=="boolean"?g.topicChanged:!1,reasoning:typeof g.reasoning=="string"?g.reasoning:"",language:f,mentionedChain:typeof g.mentionedChain=="string"?g.mentionedChain.trim():"",needsWebSearch:g.needsWebSearch===!0};if(this.debug){let k=Date.now()-u,w=y.rewrittenQuery!==e;console.log(`
877
+ [QueryRewriter] (${k}ms)`),console.log(` original : "${e}"`),console.log(` rewritten : "${y.rewrittenQuery}"${w?"":" (unchanged)"}`),console.log(` followUp : ${y.isFollowUp} topicChanged: ${y.topicChanged}`),y.reasoning&&console.log(` reasoning : ${y.reasoning}`),console.log(` language : ${y.language}`),console.log(` webSearch : ${y.needsWebSearch}`)}return y}parseJSON(e){let t=e.trim();t.startsWith("```")&&(t=t.replace(/^```(?:json)?\s*/,"").replace(/```\s*$/,""));let n=t.match(/\{[\s\S]*\}/);return n&&(t=n[0]),JSON.parse(t)}};var M=require("@langchain/langgraph");var re=c=>({reducer:(e,t)=>t,default:c}),_a=M.Annotation.Root({userMessage:(0,M.Annotation)(),doSuggest:(0,M.Annotation)(re(()=>!0)),overrideLang:(0,M.Annotation)(re(()=>"")),conversationMessages:(0,M.Annotation)(re(()=>[])),lastSubagents:(0,M.Annotation)(re(()=>[])),messageId:(0,M.Annotation)(re(()=>"")),rewrite:(0,M.Annotation)(re(()=>null)),effectiveQuery:(0,M.Annotation)(re(()=>"")),turnLanguage:(0,M.Annotation)(re(()=>"")),kbContext:(0,M.Annotation)(re(()=>"")),decision:(0,M.Annotation)(re(()=>null)),assignment:(0,M.Annotation)(re(()=>null)),assignmentIndex:(0,M.Annotation)(re(()=>0)),subResults:(0,M.Annotation)({reducer:(c,e)=>c.concat(e),default:()=>[]}),response:(0,M.Annotation)(re(()=>null))});function sr(c){let e=async d=>{c.addUserMessage(d.userMessage),c.needsSummary()&&await c.compactHistory();let m=c.conversationForTurn(),p=c.lastAssistantSubagents(m);return c.debug&&p.length>0&&console.log(`[AgentCore] previous turn handled by: ${p.join(", ")}`),{conversationMessages:m,lastSubagents:p,messageId:c.generateMessageId()}},t=async d=>{let m=await c.rewrite(d.userMessage,d.conversationMessages,d.lastSubagents),p=d.overrideLang||m.language||"";return{rewrite:m,effectiveQuery:m.rewrittenQuery,turnLanguage:p}},n=async d=>{let m=d.rewrite,p=m.isFollowUp?d.effectiveQuery:d.userMessage,h=await c.searchKB(p),g=c.formatKBContext(h),f=await c.tryAnswerFromKB(d.userMessage,h,g,d.messageId);return f?{kbContext:g,response:{...f,messageId:d.messageId,rewrite:m}}:{kbContext:g}},r=async d=>({decision:await c.route(d.effectiveQuery,d.conversationMessages,c.subagentCards(),d.lastSubagents)}),o=async d=>({response:{...await c.answerDirectly(d.effectiveQuery,d.conversationMessages,d.kbContext,d.doSuggest,d.messageId,d.rewrite?.needsWebSearch??!1),messageId:d.messageId,routerDecision:d.decision,rewrite:d.rewrite}}),s=async d=>{let m=await c.runSubagent(d.assignment,d.conversationMessages,d.turnLanguage);return{subResults:[{i:d.assignmentIndex,result:m}]}},a=async d=>{let m=d.subResults.slice().sort((b,T)=>b.i-T.i).map(b=>b.result);c.persistToolMessages(m);let p=c.mergeTrace(m),h=await c.synthesise(d.userMessage,p,c.synthesiserHistory(),d.turnLanguage||void 0),g=m.map(b=>b.subagentName),f=c.collectUiActions(m,d.turnLanguage||null),y=c.collectActionButtons(m,d.turnLanguage||null),k=c.subResultsTriggerNoSuggestions(m);return{response:{...await c.finaliseAnswer({userMessage:d.userMessage,answer:h,subagents:g,uiActions:f,actionButtons:y,messageId:d.messageId},{generateSuggestions:d.doSuggest&&!k&&y.length===0}),messageId:d.messageId,trace:p,routerDecision:d.decision,subagentResults:m,rewrite:d.rewrite,...f.length>0?{uiActions:f}:{},...y.length>0?{actionButtons:y}:{}}}},i=async d=>{let m=(d.rewrite?.mentionedChain||String(c.connectedChain()??"")).trim();return{response:{...await c.answerUnsupportedChain(m,d.turnLanguage,d.messageId),messageId:d.messageId,routerDecision:d.decision,rewrite:d.rewrite}}},l=d=>d.response?M.END:"route",u=d=>{let m=d.decision?.assignments??[];if(m.length===0)return"direct";let p=m.some(g=>g.subagent!=="ai-agent"),h=(d.rewrite?.mentionedChain||c.connectedChain()||"").trim();return p&&h!==""&&!bn(h)?"unsupportedChain":m.map((g,f)=>new M.Send("subagent",{...d,assignment:g,assignmentIndex:f}))};return new M.StateGraph(_a).addNode("setup",e).addNode("contextualize",t).addNode("kb",n).addNode("route",r).addNode("direct",o).addNode("subagent",s).addNode("synthesize",a).addNode("unsupportedChain",i).addEdge(M.START,"setup").addEdge("setup","contextualize").addEdge("contextualize","kb").addConditionalEdges("kb",l,["route",M.END]).addConditionalEdges("route",u,["direct","subagent","unsupportedChain"]).addEdge("unsupportedChain",M.END).addEdge("direct",M.END).addEdge("subagent","synthesize").addEdge("synthesize",M.END).compile()}var ar=require("js-sha3");function Vn(){let c=Date.now().toString(16),e=Math.floor(Math.random()*65535).toString(16).padStart(4,"0");return(c+e).slice(-8)}var Ca="You are a helpful AI assistant. Answer the user accurately and concisely. Respond in the same language as the user.",Hn=class{llm;registry;history;summarizer;router;rewriter;subagents;synthesizer;chatGraph;systemPrompt;debug;secretKey;licenseChecked=!1;userContext={};storage;storageKey;persistHistoryEnabled;kb;kbEntries;vectorKB=null;vectorKBAutoIngested=!1;vectorKBAutoIngestPromise=null;autoIngestEnabled=!1;kbAnswerThreshold;suggestionsEnabled;historyLoaded=!1;historyLoadingPromise=null;historyReady;constructor(e){this.secretKey=e.secretKey,ro(e.rpcUrls),yn(e.excludeChains),this.llm=new se(e.llm),this.registry=new Fe,this.history=new Yt(e.maxHistoryMessages??50),this.summarizer=new Kt(this.llm),this.synthesizer=new Qt(this.llm),this.systemPrompt=e.systemPrompt??Ca,this.debug=e.debug??!1,this.suggestionsEnabled=e.generateSuggestions??!0,this.storage=e.storage??null,this.storageKey=e.storageKey??"keyring-agent-history",this.persistHistoryEnabled=e.persistHistory??!1,this.kbEntries=new jt(e.knowledgeBase),this.kbEntries.setLLM(this.llm);let t=e.vectorKB;t?.enabled?(t.provider?this.kb=t.provider:(this.vectorKB=new Re({url:t.url,token:t.token,namespace:t.namespace,minScore:t.minScore,defaultTopK:t.defaultTopK,debug:this.debug}),this.kb=this.vectorKB,this.autoIngestEnabled=t.autoIngest===!0),this.debug&&console.log(`[AgentCore] vector KB enabled (${t.provider?"custom provider":"Upstash"}`+(t.namespace?`, ns=${t.namespace}`:"")+")")):this.kb=this.kbEntries;let n=e.kbAnswerThreshold;if(typeof n=="number"?this.kbAnswerThreshold=n:t?.enabled&&!t.provider?this.kbAnswerThreshold=t.minScore??1.3:this.kbAnswerThreshold=.8,this.debug&&console.log(`[AgentCore] kbAnswerThreshold = ${this.kbAnswerThreshold}`),e.moralis!==!1){let u=typeof e.moralis=="object"?e.moralis:void 0;this.registry.register(new Je(u)),this.registry.register(new Ye(u)),this.registry.register(new Ze(u)),this.registry.register(new et(u)),this.registry.register(new tt(u)),this.registry.register(new Ke(u)),this.registry.register(new nt(u)),this.registry.register(new ot(u)),this.registry.register(new lt(u)),this.registry.register(new st(u)),this.registry.register(new at(u)),this.registry.register(new it(u)),this.registry.register(new rt(u)),this.registry.register(new Qe(u)),this.registry.register(new Xe(u)),this.registry.register(new je(u)),this.registry.register(new ze(u))}let r=e.nftLink;this.registry.register(new ct({url:r})),this.registry.register(new ut({url:r})),this.registry.register(new dt({url:r})),this.registry.register(new mt(e.llm));let o=e.subagents?.["pool-subgraph"]===!0;if((o?e.subagents?.pool===!0:e.subagents?.pool!==!1)&&e.uniswap!==!1){let u=typeof e.uniswap=="object"?e.uniswap:void 0;this.registry.register(new ht(u)),this.registry.register(new gt(u)),this.registry.register(new ft(u)),this.registry.register(new yt(u)),this.registry.register(new _t(u));let d=u?{isProduction:u.isProduction}:void 0;this.registry.register(new At({baseUrl:u?.baseUrl,pool:d,minProvideUsd:u?.minProvideUsd})),this.registry.register(new Pt({pool:d}))}if(o&&e.subgraph!==!1){let u=typeof e.subgraph=="object"?e.subgraph:void 0;this.registry.register(new Ct(u)),this.registry.register(new Ut(u)),this.registry.register(new Rt(u)),this.registry.register(new Et(u)),this.registry.register(new Nt(u)),this.registry.register(new It(u)),this.registry.register(new Dt(u)),this.registry.register(new pt(e.llm))}if(e.subagents?.["wallet-action"]!==!1){let u=e.moralis===!1?void 0:typeof e.moralis=="object"?e.moralis:{};this.registry.register(new Bt(u)),this.registry.register(new Ot(u)),this.registry.register(new Gt(u,{url:r})),this.registry.register(new qt(u)),this.registry.register(new Vt(u,{debug:this.debug})),this.registry.register(new Ht(u,{debug:this.debug}))}this.router=new zt(this.llm,{debug:this.debug}),this.rewriter=new Wn(this.llm,{debug:this.debug});let i=typeof e.subgraph=="object"?e.subgraph:void 0,l=$n(this.llm,this.registry,{maxToolCalls:e.maxIterations??5,debug:this.debug,linkAddLiquidity:i?.linkAddLiquidity},e.subagents);this.subagents=new Map(l.map(u=>[u.name,u])),this.chatGraph=sr(this.createGraphHost()),this.historyReady=this.loadHistory()}registerTool(e){this.registry.register(e)}registerTools(e){for(let t of e)this.registry.register(t)}unregisterTool(e){return this.registry.unregister(e)}listTools(){return this.registry.listNames()}async invokeTool(e,t,n){if(this.assertLicense(),await this.loadHistory(),!this.registry.get(e))return{answer:`Unknown tool: "${e}".`,messageId:Vn()};this.debug&&console.log(`[AgentCore] invokeTool ${e} ${JSON.stringify(t)}`);let o=n?.userMessage??`[invoke ${e}]`;this.history.add({role:"user",content:o,timestamp:Date.now()});let s=await this.registry.execute(e,t,this.userContext),a=s.data,i=s.success?a?.summary??"Done.":`Action failed: ${s.error??"unknown error"}`,l=Vn(),u=s.success&&s.ui?[this.stampLanguage(s.ui,null)]:[],d={role:"assistant",content:i,timestamp:Date.now(),subagents:["direct-invoke"],messageId:l};return u.length>0&&(d.uiActions=u),this.history.add(d),await this.persistHistory(),{answer:i,messageId:l,...u.length>0?{uiActions:u}:{}}}registerSubagent(e){this.subagents.set(e.name,e)}unregisterSubagent(e){return this.subagents.delete(e)}listSubagents(){return Array.from(this.subagents.keys())}setKnowledgeBase(e){this.kbEntries.setEntries(e),this.vectorKBAutoIngested=!1}addKnowledgeBase(e){this.kbEntries.addEntries(e),this.vectorKBAutoIngested=!1}async ingestKnowledgeBase(){if(!this.vectorKB)return{count:0};let e=this.kbEntries.getEntries();if(e.length===0)return{count:0};let t=await this.vectorKB.upsert(e);return this.vectorKBAutoIngested=!0,t}async maybeAutoIngest(e){if(!(!e||!this.vectorKB||this.vectorKBAutoIngested)){if(this.vectorKBAutoIngestPromise)return this.vectorKBAutoIngestPromise;this.vectorKBAutoIngestPromise=(async()=>{try{let{count:t}=await this.ingestKnowledgeBase();this.debug&&t>0&&console.log(`[AgentCore] auto-ingested ${t} KB entr${t===1?"y":"ies"} to vector store`)}catch(t){this.debug&&console.warn("[AgentCore] auto-ingest failed:",t)}finally{this.vectorKBAutoIngestPromise=null}})(),await this.vectorKBAutoIngestPromise}}setUserContext(e){this.userContext={...e}}getUserContext(){return{...this.userContext}}async loadHistory(){if(!this.historyLoaded){if(this.historyLoadingPromise)return this.historyLoadingPromise;this.historyLoadingPromise=this._doLoadHistory(),await this.historyLoadingPromise}}async _doLoadHistory(){if(!this.storage){this.debug&&console.log("[AgentCore] loadHistory: no storage configured"),this.historyLoaded=!0;return}if(!this.persistHistoryEnabled){this.debug&&console.log("[AgentCore] loadHistory: persistHistory disabled \u2014 starting fresh session");try{await this.storage.removeItem(this.storageKey)}catch(e){this.debug&&console.warn("[AgentCore] failed to clear stale history:",e)}this.historyLoaded=!0;return}try{let e=await this.storage.getItem(this.storageKey);if(this.debug&&console.log(`[AgentCore] loadHistory: storage key "${this.storageKey}" ${e?`has ${e.length} chars`:"is empty"}`),e){let t=JSON.parse(e);this.history.restore(t),this.debug&&console.log(`[AgentCore] Restored ${this.history.length} messages from storage`)}}catch(e){this.debug&&console.warn("[AgentCore] Failed to load history from storage:",e)}finally{this.historyLoaded=!0}}async persistHistory(){if(this.storage&&this.persistHistoryEnabled)try{let e=JSON.stringify(this.history.serialise());await this.storage.setItem(this.storageKey,e)}catch(e){this.debug&&console.warn("[AgentCore] Failed to persist history to storage:",e)}}assertLicense(){if(this.licenseChecked)return;let e="d00d67291d3c660a5034b01c0b2afbc8d4ac5552099a04f28a92d75fdb44c188";if(e&&(!this.secretKey||(0,ar.sha3_256)(this.secretKey)!==e))throw new Error("AgentCore: invalid or missing secretKey.");this.licenseChecked=!0}async chat(e,t){this.assertLicense();let n=t?.generateSuggestions??this.suggestionsEnabled,r=typeof t?.language=="string"&&t.language.trim()?t.language.trim():"";await this.loadHistory(),await this.maybeAutoIngest(this.autoIngestEnabled);let o=Date.now();this.debug&&(console.log(`
836
878
  ${"\u2500".repeat(60)}`),console.log(`[AgentCore] query: "${e}"`));let s=await this.chatGraph.invoke({userMessage:e,doSuggest:n,overrideLang:r});return this.debug&&(console.log(`[AgentCore] turn done in ${Date.now()-o}ms total`),console.log(`${"\u2500".repeat(60)}
837
- `)),s.response??{answer:"",messageId:Mn()}}createGraphHost(){return{debug:this.debug,addUserMessage:e=>this.addUserMessage(e),needsSummary:()=>this.history.needsSummary(),compactHistory:()=>this.compactHistory(),conversationForTurn:()=>{let e=this.history.getConversation();return e.length>0&&e[e.length-1].role==="user"?e.slice(0,-1):e},lastAssistantSubagents:e=>Ko(e),generateMessageId:()=>Mn(),rewrite:(e,t,n)=>this.rewriter.rewrite(e,t,n),searchKB:e=>this.searchKB(e),formatKBContext:e=>this.formatKBContext(e),tryAnswerFromKB:(e,t,n,r)=>this.tryAnswerFromKB(e,t,n,r),subagentCards:()=>Array.from(this.subagents.values()).map(e=>e.getCard()),route:(e,t,n,r)=>this.router.route(e,t,n,this.userContext,r),connectedChain:()=>this.userContext.chain,answerUnsupportedChain:(e,t,n)=>this.answerUnsupportedChain(e,t,n),answerDirectly:(e,t,n,r,o,s)=>this.answerDirectly(e,t,n,r,o,s),runSubagent:async(e,t,n)=>{let r=this.subagents.get(e.subagent);if(!r)return this.debug&&console.log(`[AgentCore] WARN: subagent "${e.subagent}" not registered`),{subagentName:e.subagent,steps:[],finalAnswer:`Subagent "${e.subagent}" is not registered.`,toolResults:[],toolMessages:[]};let o=Date.now(),s=await r.run({query:e.query,reasoning:e.reasoning,language:n},t,this.userContext);if(this.debug){let a=s.toolResults.map(i=>i.toolName).join(", ")||"\u2014";console.log(`[AgentCore] ${e.subagent} done in ${Date.now()-o}ms | tools: ${a}`)}return s},persistToolMessages:e=>this.persistToolMessages(e),synthesiserHistory:()=>{let e=this.history.getConversation(),t=e.length>0&&e[e.length-1].role==="user"?e.slice(0,-1):e;return be(t)},mergeTrace:e=>this.mergeTrace(e),synthesise:(e,t,n,r)=>this.synthesizer.synthesise(e,t,n,r),collectUiActions:(e,t)=>this.collectUiActions(e,t),collectActionButtons:(e,t)=>this.collectActionButtons(e,t),subResultsTriggerNoSuggestions:e=>this.subResultsTriggerNoSuggestions(e),finaliseAnswer:(e,t)=>this.finaliseAnswer(e,t)}}subResultsTriggerNoSuggestions(e){for(let t of e)for(let n of t.toolResults){if(!n.success)continue;if(this.registry.get(n.toolName)?.noSuggestions===!0)return!0}return!1}capitalizeFirst(e){if(!e)return e;let t=Array.from(e);return t[0]=t[0].toLocaleUpperCase(),t.join("")}collectActionButtons(e,t){let n=new Map,r=[];for(let o of e)for(let s of o.toolResults)if(!(!s.success||!Array.isArray(s.actionButtons)))for(let a of s.actionButtons){if(!a||typeof a!="object"||typeof a.label!="string"||!a.label.trim()||typeof a.prompt!="string"||!a.prompt.trim())continue;let i=this.capitalizeFirst(a.prompt),l={label:a.label,prompt:i,...t?{language:t}:{}},u=n.get(i);u?l.label.length>u.label.length&&n.set(i,l):(n.set(i,l),r.push(i))}return r.map(o=>n.get(o))}collectUiActions(e,t){let n=[];for(let r of e)for(let o of r.toolResults)o.success&&o.ui&&typeof o.ui=="object"&&typeof o.ui.component=="string"&&n.push(this.stampLanguage(o.ui,t));if(this.debug&&n.length>0){let r=n.map(o=>`${o.component}=${o.language}`).join(", ");console.log(`[AgentCore] uiActions stamped (rewrite.language="${t??""}"): ${r}`)}return n}stampLanguage(e,t){return!t||e.language?e:{...e,language:t}}addUserMessage(e){this.history.add({role:"user",content:e,timestamp:Date.now()})}async searchKB(e){let t=await this.kb.search(e);if(this.debug)if(t.length===0)console.log(`[AgentCore] KB no hits for "${e}"`);else{let n=t.slice(0,3).map(r=>`${r.score.toFixed(3)} ${this.snippet(r.entry.question,60)}`).join(" | ");console.log(`[AgentCore] KB hits (${t.length}): ${n}`)}return t}snippet(e,t){return e.length<=t?e:e.slice(0,t-1)+"\u2026"}formatKBContext(e){return e.length===0?"":e.map((t,n)=>`[Reference ${n+1}]
879
+ `)),s.response??{answer:"",messageId:Vn()}}createGraphHost(){return{debug:this.debug,addUserMessage:e=>this.addUserMessage(e),needsSummary:()=>this.history.needsSummary(),compactHistory:()=>this.compactHistory(),conversationForTurn:()=>{let e=this.history.getConversation();return e.length>0&&e[e.length-1].role==="user"?e.slice(0,-1):e},lastAssistantSubagents:e=>tr(e),generateMessageId:()=>Vn(),rewrite:(e,t,n)=>this.rewriter.rewrite(e,t,n),searchKB:e=>this.searchKB(e),formatKBContext:e=>this.formatKBContext(e),tryAnswerFromKB:(e,t,n,r)=>this.tryAnswerFromKB(e,t,n,r),subagentCards:()=>Array.from(this.subagents.values()).map(e=>e.getCard()),route:(e,t,n,r)=>this.router.route(e,t,n,this.userContext,r),connectedChain:()=>this.userContext.chain,answerUnsupportedChain:(e,t,n)=>this.answerUnsupportedChain(e,t,n),answerDirectly:(e,t,n,r,o,s)=>this.answerDirectly(e,t,n,r,o,s),runSubagent:async(e,t,n)=>{let r=this.subagents.get(e.subagent);if(!r)return this.debug&&console.log(`[AgentCore] WARN: subagent "${e.subagent}" not registered`),{subagentName:e.subagent,steps:[],finalAnswer:`Subagent "${e.subagent}" is not registered.`,toolResults:[],toolMessages:[]};let o=Date.now(),s=await r.run({query:e.query,reasoning:e.reasoning,language:n},t,this.userContext);if(this.debug){let a=s.toolResults.map(i=>i.toolName).join(", ")||"\u2014";console.log(`[AgentCore] ${e.subagent} done in ${Date.now()-o}ms | tools: ${a}`)}return s},persistToolMessages:e=>this.persistToolMessages(e),synthesiserHistory:()=>{let e=this.history.getConversation(),t=e.length>0&&e[e.length-1].role==="user"?e.slice(0,-1):e;return we(t)},mergeTrace:e=>this.mergeTrace(e),synthesise:(e,t,n,r)=>this.synthesizer.synthesise(e,t,n,r),collectUiActions:(e,t)=>this.collectUiActions(e,t),collectActionButtons:(e,t)=>this.collectActionButtons(e,t),subResultsTriggerNoSuggestions:e=>this.subResultsTriggerNoSuggestions(e),finaliseAnswer:(e,t)=>this.finaliseAnswer(e,t)}}subResultsTriggerNoSuggestions(e){for(let t of e)for(let n of t.toolResults){if(!n.success)continue;if(this.registry.get(n.toolName)?.noSuggestions===!0)return!0}return!1}capitalizeFirst(e){if(!e)return e;let t=Array.from(e);return t[0]=t[0].toLocaleUpperCase(),t.join("")}collectActionButtons(e,t){let n=new Map,r=[];for(let o of e)for(let s of o.toolResults)if(!(!s.success||!Array.isArray(s.actionButtons)))for(let a of s.actionButtons){if(!a||typeof a!="object"||typeof a.label!="string"||!a.label.trim()||typeof a.prompt!="string"||!a.prompt.trim())continue;let i=this.capitalizeFirst(a.prompt),l={label:a.label,prompt:i,...t?{language:t}:{}},u=n.get(i);u?l.label.length>u.label.length&&n.set(i,l):(n.set(i,l),r.push(i))}return r.map(o=>n.get(o))}collectUiActions(e,t){let n=[];for(let r of e)for(let o of r.toolResults)o.success&&o.ui&&typeof o.ui=="object"&&typeof o.ui.component=="string"&&n.push(this.stampLanguage(o.ui,t));if(this.debug&&n.length>0){let r=n.map(o=>`${o.component}=${o.language}`).join(", ");console.log(`[AgentCore] uiActions stamped (rewrite.language="${t??""}"): ${r}`)}return n}stampLanguage(e,t){return!t||e.language?e:{...e,language:t}}addUserMessage(e){this.history.add({role:"user",content:e,timestamp:Date.now()})}async searchKB(e){let t=await this.kb.search(e);if(this.debug)if(t.length===0)console.log(`[AgentCore] KB no hits for "${e}"`);else{let n=t.slice(0,3).map(r=>`${r.score.toFixed(3)} ${this.snippet(r.entry.question,60)}`).join(" | ");console.log(`[AgentCore] KB hits (${t.length}): ${n}`)}return t}snippet(e,t){return e.length<=t?e:e.slice(0,t-1)+"\u2026"}formatKBContext(e){return e.length===0?"":e.map((t,n)=>`[Reference ${n+1}]
838
880
  Q: ${t.entry.question}
839
881
  A: ${t.entry.answer}`).join(`
840
882
 
@@ -861,7 +903,7 @@ REFERENCE MATERIAL:
861
903
  `+n+`
862
904
  ---`,timestamp:0},{role:"user",content:`${e}
863
905
 
864
- (You MUST reply in the same language as the question above, regardless of the reference material language.)`,timestamp:Date.now()}],p=(await this.llm.chat(u)).text,m={role:"assistant",content:p,timestamp:Date.now()};return r&&(m.messageId=r),this.history.add(m),await this.persistHistory(),{answer:p,suggestedPrompts:[]}}async answerDirectly(e,t,n,r=!0,o,s=!1){this.debug&&console.log(`[AgentCore] Router returned no assignments, answering directly via LLM\u2026 (webSearch=${s})`);let a=s?"":`
906
+ (You MUST reply in the same language as the question above, regardless of the reference material language.)`,timestamp:Date.now()}],m=(await this.llm.chat(u)).text,p={role:"assistant",content:m,timestamp:Date.now()};return r&&(p.messageId=r),this.history.add(p),await this.persistHistory(),{answer:m,suggestedPrompts:[]}}async answerDirectly(e,t,n,r=!0,o,s=!1){this.debug&&console.log(`[AgentCore] Router returned no assignments, answering directly via LLM\u2026 (webSearch=${s})`);let a=s?"":`
865
907
 
866
908
  You are answering WITHOUT live web access. Rely only on knowledge you are confident about. If the question needs real-time or recent information you do not have (current prices, today's news, latest events), say plainly that you do not have up-to-date data instead of guessing. Never fabricate numbers, dates, or facts.`,i=(n?this.systemPrompt+`
867
909
 
@@ -870,7 +912,7 @@ You may use the following reference material if relevant:
870
912
  ---
871
913
  REFERENCE MATERIAL:
872
914
  `+n+`
873
- ---`:this.systemPrompt)+a,l=be(t),u=[{role:"system",content:i,timestamp:0},...l,{role:"user",content:e,timestamp:Date.now()}],d=await this.llm.chat(u,void 0,{googleSearch:s});return await this.finaliseAnswer({userMessage:e,answer:d.text,messageId:o},{generateSuggestions:r})}async answerUnsupportedChain(e,t,n){this.debug&&console.log(`[AgentCore] connected chain "${e}" is not supported \u2192 short-circuit with notice`);let r=(t||"").trim(),o=[{role:"system",content:`Write ONE short, polite message telling the user that the blockchain network they want to use is NOT supported by this assistant, and inviting them to use one of the supported networks. List the supported networks: ${Ne}. Do NOT mention or guess the name or chain id of the unsupported network. `+(r?`Reply in BCP-47 language "${r}".`:"Reply in the same language the user has been using.")+" Do NOT mention tools or internal implementation details.",timestamp:0},{role:"user",content:"Is my currently connected network supported?",timestamp:Date.now()}],a=(await this.llm.chat(o)).text?.trim()||`Your currently connected network is not supported yet. Supported networks: ${Ne}.`;return await this.finaliseAnswer({userMessage:`[unsupported chain: ${e}]`,answer:a,messageId:n},{generateSuggestions:!1})}mergeTrace(e){let t=[];for(let r of e)t.push({phase:"think",content:`[${r.subagentName}] begin`,timestamp:Date.now()}),t.push(...r.steps),t.push({phase:"observe",content:`[${r.subagentName}] answer: ${r.finalAnswer}`,timestamp:Date.now()});let n=e.map(r=>`[${r.subagentName}] ${r.finalAnswer}`).join(`
915
+ ---`:this.systemPrompt)+a,l=we(t),u=[{role:"system",content:i,timestamp:0},...l,{role:"user",content:e,timestamp:Date.now()}],d=await this.llm.chat(u,void 0,{googleSearch:s});return await this.finaliseAnswer({userMessage:e,answer:d.text,messageId:o},{generateSuggestions:r})}async answerUnsupportedChain(e,t,n){this.debug&&console.log(`[AgentCore] connected chain "${e}" is not supported \u2192 short-circuit with notice`);let r=(t||"").trim(),o=[{role:"system",content:`Write ONE short, polite message telling the user that the blockchain network they want to use is NOT supported by this assistant, and inviting them to use one of the supported networks. List the supported networks: ${Ne}. Use ONLY the network NAMES \u2014 NEVER show any hex chain id (e.g. never write "0x1" or "(0xa)"). Do NOT mention or guess the name or chain id of the unsupported network. `+(r?`Reply in BCP-47 language "${r}".`:"Reply in the same language the user has been using.")+" Do NOT mention tools or internal implementation details.",timestamp:0},{role:"user",content:"Is my currently connected network supported?",timestamp:Date.now()}],a=(await this.llm.chat(o)).text?.trim()||`Your currently connected network is not supported yet. Supported networks: ${Ne}.`;return await this.finaliseAnswer({userMessage:`[unsupported chain: ${e}]`,answer:a,messageId:n},{generateSuggestions:!1})}mergeTrace(e){let t=[];for(let r of e)t.push({phase:"think",content:`[${r.subagentName}] begin`,timestamp:Date.now()}),t.push(...r.steps),t.push({phase:"observe",content:`[${r.subagentName}] answer: ${r.finalAnswer}`,timestamp:Date.now()});let n=e.map(r=>`[${r.subagentName}] ${r.finalAnswer}`).join(`
874
916
 
875
917
  `);return{steps:t,finalAnswer:n}}async finaliseAnswer(e,t){let{userMessage:n,answer:r,subagents:o,uiActions:s,actionButtons:a,messageId:i}=e??{},{generateSuggestions:l=!0}=t??{},u=l?await this.generateSuggestions(n,r,o??[]):[],d={role:"assistant",content:r,timestamp:Date.now()};return o&&o.length>0&&(d.subagents=o),u.length>0&&(d.suggestedPrompts=u),s&&s.length>0&&(d.uiActions=s),a&&a.length>0&&(d.actionButtons=a),i&&(d.messageId=i),this.history.add(d),await this.persistHistory(),{answer:r,suggestedPrompts:u,...a&&a.length>0?{actionButtons:a}:{}}}async generateSuggestions(e,t,n){let r=n.includes("pool")?`
876
918
  POOL FUNNEL: This turn was handled by the pool subagent. If the answer references a SPECIFIC pool (token pair + fee tier, or a pool address), the FIRST suggestion MUST propose adding liquidity to THAT exact pool (e.g. "Add liquidity to USDC/WETH 0.05%"). A second suggestion should propose estimating yield for a deposit into the same pool. Skip this funnel only if no specific pool is identifiable in the answer.
@@ -890,4 +932,4 @@ RULES:
890
932
  - Suggestions must be diverse \u2014 do not repeat the same intent in different words.
891
933
  - Use the same language as the user.
892
934
  - Good example (pool): ["Add liquidity to USDC/WETH 0.05%", "Estimate yield for $1000 here", "Compare with 0.3% fee tier"]
893
- - Bad example: ["What is a pool?", "How do pools work?", "Tell me more"] (generic, no action, no specifics)`,timestamp:0}])).text.match(/\[[\s\S]*\]/);return a?JSON.parse(a[0]).filter(l=>typeof l=="string"&&l.length>0).slice(0,3):[]}catch{return[]}}getHistory(){return this.history.getAllFull()}getDisplayHistory(){return this.history.getAllFull().filter(e=>!(e.role!=="user"&&e.role!=="assistant"||e._intermediate||e.role==="assistant"&&!e.content?.trim()))}getLastSuggestions(){let e=this.getDisplayHistory();for(let t=e.length-1;t>=0;t--){let n=e[t];if(n.role==="assistant")return n.suggestedPrompts??[]}return[]}async clearHistory(){this.history.clear(),this.historyLoaded=!0,this.historyLoadingPromise=null,await this.persistHistory()}restoreHistory(e){this.history.restore(e)}serialiseHistory(){return this.history.serialise()}async compactHistory(){this.debug&&console.log("[AgentCore] Summarizing conversation history\u2026");let e=this.history.getAll(),t=6,n=0,r=e.length;for(let p=e.length-1;p>=0;p--)if(e[p].role==="user"&&(n++,n>=t)){r=p;break}let o=e.slice(r),s=Go(o,o.length),a=e.length-s.length,i=be(e.slice(0,a)),l=this.history.getSummary(),u=[];l&&u.push({role:"user",content:`[Previous context summary]: ${l}`,timestamp:0}),u.push(...i);let d=await this.summarizer.summarize(u);this.history.compactWith(d,s)}persistToolMessages(e){for(let t of e)t.toolMessages.length>0&&(this.history.addMany(t.toolMessages),this.history.add({role:"assistant",content:t.finalAnswer,_intermediate:!0,timestamp:Date.now()}),this.debug&&console.log(`[AgentCore] stored ${t.toolMessages.length} tool message(s) + finalAnswer from ${t.subagentName}`))}getLLMProvider(){return this.llm}getToolRegistry(){return this.registry}};var pa=c=>{let e=JSON.parse(c),t=Array.isArray(e)?e:e?.entries;if(!Array.isArray(t))throw new Error("JSON must be an array or { entries: [...] }");return t.map((n,r)=>{if(!n||typeof n.question!="string"||typeof n.answer!="string")throw new Error(`Entry #${r} is missing "question" or "answer"`);return n})};async function Zo(c,e){let t=c.entries??(c.json?pa(c.json):[]);return t.length===0?{count:0}:new Re(e).upsert(t)}0&&(module.exports={AI_AGENT_TOOL_NAMES,AgentCore,ApproveTokenTool,BaseNftMessageTool,BaseSwapService,BaseTool,BaseWalletActionTool,BuyTokenTool,ChatHistory,DEFAULT_CHAIN,DEFAULT_PROVIDER,DEFAULT_RPC_BY_CHAIN,DebridgeAdapter,EstimatePoolYieldTool,GeminiProvider,GeminiSearchAiTool,HEX_TO_PANTOGRAPH,KnowledgeBase,MoralisService,NFTContractInfoTool,NFTMetadataTool,NFT_AGENT_TOOL_NAMES,OpenAddLiquidityFormTool,POOL_AGENT_TOOL_NAMES,PantographService,PoolByAddressTool,PoolDetailTool,PoolSearchTool,PoolService,PreviewAddLiquidityTool,RelayAdapter,Router,SUPPORTED_CHAINS,SUPPORTED_CHAINS_LABEL,SWAP_PROVIDER_CONFIG,SendNativeTool,SendNftTool,SendTokenTool,Subagent,SubgraphCoinPoolPairsTool,SubgraphPoolByAddressTool,SubgraphPoolByPositionIdTool,SubgraphPoolSearchTool,SubgraphPositionDetailTool,SubgraphTrendingPoolsTool,Summarizer,SwapServiceFactory,SwapTokenTool,Synthesizer,TOKEN_AGENT_TOOL_NAMES,TRANSFER_TOPIC,TRENDING_DEFAULT_BY_HEX_CHAIN,TokenAnalyticsTool,TokenHoldersTool,TokenInfoTool,TokenScoreTool,ToolRegistry,TopGainersTool,TopPoolsTool,TransactionByHashTool,TrendingTokensTool,UNISWAP_CHAIN_SLUG,UpstashKnowledgeBase,WALLET_ACTION_AGENT_TOOL_NAMES,WALLET_AGENT_TOOL_NAMES,WalletApprovalsTool,WalletDefiPositionsTool,WalletDefiProtocolPositionsTool,WalletDefiSummaryTool,WalletHistoryTool,WalletNFTsTool,WalletNetWorthTool,WalletNftTransfersTool,WalletPnlSummaryTool,WalletPnlTool,WalletTokenBalancesTool,WalletTokenTransfersTool,ZERO_ADDRESS,buildRangePresets,buildUniswapPoolUrl,clampTick,configureSupportedChains,createAiAgent,createDefaultSubagents,createNftAgent,createPoolAgent,createTokenAgent,createWalletActionAgent,createWalletAgent,ethCallAt,ethCallByChain,getAffiliateFee,getChainMeta,getDefaultRpcUrl,getGatewayAddress,getNativeTokenInfo,getPositionManagerAddress,getProviderByChain,ingestKnowledgeBase,priceToTick,resolveRpcUrl,roundTickToSpacing,rpcCall,setRpcOverrides,swapServiceFactory,tickToPrice,toPantographChain});
935
+ - Bad example: ["What is a pool?", "How do pools work?", "Tell me more"] (generic, no action, no specifics)`,timestamp:0}])).text.match(/\[[\s\S]*\]/);return a?JSON.parse(a[0]).filter(l=>typeof l=="string"&&l.length>0).slice(0,3):[]}catch{return[]}}getHistory(){return this.history.getAllFull()}getDisplayHistory(){return this.history.getAllFull().filter(e=>!(e.role!=="user"&&e.role!=="assistant"||e._intermediate||e.role==="assistant"&&!e.content?.trim()))}getLastSuggestions(){let e=this.getDisplayHistory();for(let t=e.length-1;t>=0;t--){let n=e[t];if(n.role==="assistant")return n.suggestedPrompts??[]}return[]}async clearHistory(){this.history.clear(),this.historyLoaded=!0,this.historyLoadingPromise=null,await this.persistHistory()}restoreHistory(e){this.history.restore(e)}serialiseHistory(){return this.history.serialise()}async compactHistory(){this.debug&&console.log("[AgentCore] Summarizing conversation history\u2026");let e=this.history.getAll(),t=6,n=0,r=e.length;for(let m=e.length-1;m>=0;m--)if(e[m].role==="user"&&(n++,n>=t)){r=m;break}let o=e.slice(r),s=er(o,o.length),a=e.length-s.length,i=we(e.slice(0,a)),l=this.history.getSummary(),u=[];l&&u.push({role:"user",content:`[Previous context summary]: ${l}`,timestamp:0}),u.push(...i);let d=await this.summarizer.summarize(u);this.history.compactWith(d,s)}persistToolMessages(e){for(let t of e)t.toolMessages.length>0&&(this.history.addMany(t.toolMessages),this.history.add({role:"assistant",content:t.finalAnswer,_intermediate:!0,timestamp:Date.now()}),this.debug&&console.log(`[AgentCore] stored ${t.toolMessages.length} tool message(s) + finalAnswer from ${t.subagentName}`))}getLLMProvider(){return this.llm}getToolRegistry(){return this.registry}};var Ua=c=>{let e=JSON.parse(c),t=Array.isArray(e)?e:e?.entries;if(!Array.isArray(t))throw new Error("JSON must be an array or { entries: [...] }");return t.map((n,r)=>{if(!n||typeof n.question!="string"||typeof n.answer!="string")throw new Error(`Entry #${r} is missing "question" or "answer"`);return n})};async function ir(c,e){let t=c.entries??(c.json?Ua(c.json):[]);return t.length===0?{count:0}:new Re(e).upsert(t)}0&&(module.exports={AI_AGENT_TOOL_NAMES,AgentCore,ApproveTokenTool,BaseNftMessageTool,BaseSwapService,BaseTool,BaseWalletActionTool,BuyTokenTool,ChatHistory,DEFAULT_CHAIN,DEFAULT_PROVIDER,DEFAULT_RPC_BY_CHAIN,DebridgeAdapter,EstimatePoolYieldTool,GeminiProvider,GeminiSearchAiTool,HEX_TO_PANTOGRAPH,KnowledgeBase,MoralisService,NFTContractInfoTool,NFTMetadataTool,NFT_AGENT_TOOL_NAMES,OpenAddLiquidityFormTool,POOL_AGENT_TOOL_NAMES,PantographService,PoolByAddressTool,PoolDetailTool,PoolFallbackAiTool,PoolSearchTool,PoolService,PreviewAddLiquidityTool,RelayAdapter,Router,SUPPORTED_CHAINS,SUPPORTED_CHAINS_LABEL,SWAP_PROVIDER_CONFIG,SendNativeTool,SendNftTool,SendTokenTool,Subagent,SubgraphAddPoolTool,SubgraphCoinPoolPairsTool,SubgraphPoolByAddressTool,SubgraphPoolByPositionIdTool,SubgraphPoolSearchTool,SubgraphPositionDetailTool,SubgraphTrendingPoolsTool,Summarizer,SwapServiceFactory,SwapTokenTool,Synthesizer,TOKEN_AGENT_TOOL_NAMES,TRANSFER_TOPIC,TRENDING_DEFAULT_BY_HEX_CHAIN,TokenAnalyticsTool,TokenHoldersTool,TokenInfoTool,TokenScoreTool,ToolRegistry,TopGainersTool,TopPoolsTool,TransactionByHashTool,TrendingTokensTool,UNISWAP_CHAIN_SLUG,UpstashKnowledgeBase,WALLET_ACTION_AGENT_TOOL_NAMES,WALLET_AGENT_TOOL_NAMES,WalletApprovalsTool,WalletDefiPositionsTool,WalletDefiProtocolPositionsTool,WalletDefiSummaryTool,WalletHistoryTool,WalletNFTsTool,WalletNetWorthTool,WalletNftTransfersTool,WalletPnlSummaryTool,WalletPnlTool,WalletTokenBalancesTool,WalletTokenTransfersTool,ZERO_ADDRESS,buildRangePresets,buildUniswapPoolUrl,clampTick,configureSupportedChains,createAiAgent,createDefaultSubagents,createNftAgent,createPoolAgent,createTokenAgent,createWalletActionAgent,createWalletAgent,ethCallAt,ethCallByChain,getAffiliateFee,getChainMeta,getDefaultRpcUrl,getGatewayAddress,getNativeTokenInfo,getPositionManagerAddress,getProviderByChain,ingestKnowledgeBase,priceToTick,resolveRpcUrl,roundTickToSpacing,rpcCall,setRpcOverrides,swapServiceFactory,tickToPrice,toPantographChain});