keyring-agent-core 0.2.2 → 0.2.4
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.d.ts +4 -4
- package/dist/index.js +43 -43
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
"use strict";var Cn=Object.defineProperty;var Fo=Object.getOwnPropertyDescriptor;var Wo=Object.getOwnPropertyNames;var Vo=Object.prototype.hasOwnProperty;var Ho=(
|
|
2
|
-
`)}]}:null,contents:o}}toFunctionDeclarations(e){return e.map(t=>{let n={},r=[];for(let o of t.parameters){let s={type:Hn[o.type]??"STRING",description:o.description};o.type==="array"&&(s.items={type:Hn[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 Ee=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 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);return{toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r}}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}};var Qo="0x2105",Xo=["0x1","0xa","0x38","0x89","0x2105","0xa4b1","0xa86a","0xe708"],Jo=new Set(Xo),tn="Ethereum (0x1), Optimism (0xa), BSC (0x38), Polygon (0x89), Base (0x2105), Arbitrum (0xa4b1), Avalanche (0xa86a), Linea (0xe708)",Zo={"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 me(l){if(typeof l!="string")return null;let e=l.trim().toLowerCase();if(!e)return null;let t=Zo[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&&Jo.has(n)?n:null}function jn(l){return me(l)!==null}var Ne=class extends Error{constructor(t){super(`Chain "${t}" is not supported. Supported chains: ${tn}.`);this.requested=t;this.name="UnsupportedChainError"}requested;code="unsupported_chain"},Bt=l=>typeof l=="string"&&l.trim()!=="";function R(l,e,t=Qo){if(Bt(l)){let n=me(l);if(n)return n;throw new Ne(l.trim())}if(Bt(e?.chain)){let n=me(e.chain);if(n)return n;throw new Ne(e.chain.trim())}return t}function Yn(l,e){if(Bt(l)){let t=me(l);if(t)return t;throw new Ne(l.trim())}if(Bt(e?.chain)){let t=me(e.chain);if(t)return t;throw new Ne(e.chain.trim())}return null}function nn(l,e){return me(l)??me(e?.chain)??void 0}var Kn={"0x1":"Ethereum","0xa":"Optimism","0x38":"BSC","0x89":"Polygon","0x2105":"Base","0xa4b1":"Arbitrum","0xa86a":"Avalanche","0xe708":"Linea"};var er={bnb:"0x38",matic:"0x89",pol:"0x89",avax:"0xa86a"};function zn(l){if(typeof l!="string")return null;let e=l.trim().toLowerCase();return e?er[e]??null:null}function Ie(l,e){if(!Bt(l))return null;let t=me(l),n=me(e?.chain);return!t||!n||t===n?null:{requested:t,connected:n,requestedLabel:Kn[t]??t,connectedLabel:Kn[n]??n}}var Qn=require("js-sha3");function D(l){return typeof l=="string"&&/^0x[0-9a-fA-F]{40}$/.test(l)}function tr(l){return new Uint8Array(Qn.keccak256.arrayBuffer(l))}function Ot(l){let e=l.startsWith("0x")?l.slice(2):l,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 Xn(l){return"0x"+Array.from(l).map(e=>e.toString(16).padStart(2,"0")).join("")}function Jn(l){return new TextEncoder().encode(l)}function xe(...l){let e=l.reduce((r,o)=>r+o.length,0),t=new Uint8Array(e),n=0;for(let r of l)t.set(r,n),n+=r.length;return t}function nr(l,e=32){let t=new Uint8Array(e);return t.set(l,e-l.length),t}function or(l,e=32){let t=new Uint8Array(e);return t.set(l),t}function Zn(l){let e=((l%(1n<<256n)+(1n<<256n))%(1n<<256n)).toString(16).padStart(64,"0");return Ot(e)}function $t(l){return Zn(BigInt(l))}function eo(l){return l==="uint"?"uint256":l==="int"?"int256":l.match(/^uint$/)?"uint256":l.match(/^int$/)?"int256":l.replace(/^uint(\[|$)/,"uint256$1").replace(/^int(\[|$)/,"int256$1")}function to(l){let e=eo(l.type);if(e==="tuple"||e.startsWith("tuple[")){let t=(l.components??[]).map(to).join(","),n=e.startsWith("tuple[")?e.slice(5):"";return`(${t})${n}`}return e}function rr(l){let e=(l.inputs??[]).map(to).join(",");return`${l.name??""}(${e})`}function Un(l,e){let t=ir(l.type);if(t){let[r,o]=t;return sr({...l,type:o},e,r)}if(l.type==="tuple")return ar(l,e);if(l.type==="address"){let r=String(e),o=r.startsWith("0x")?r.slice(2):r;return{dynamic:!1,encoded:nr(Ot(o.toLowerCase()))}}if(l.type==="bool"){let r=new Uint8Array(32);return r[31]=e?1:0,{dynamic:!1,encoded:r}}let n=eo(l.type);if(/^uint\d*$/.test(n))return{dynamic:!1,encoded:$t(BigInt(e))};if(/^int\d*$/.test(n)){let r=BigInt(e);return r<0n&&(r=r+(1n<<256n)),{dynamic:!1,encoded:Zn(r)}}if(/^bytes(\d+)$/.test(n)){let r=parseInt(n.slice(5),10),o=typeof e=="string"?Ot(e):e;if(o.length!==r)throw new Error(`bytes${r} expects exactly ${r} bytes, got ${o.length}`);return{dynamic:!1,encoded:or(o)}}if(n==="bytes"){let r=typeof e=="string"?Ot(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:xe($t(r.length),s)}}if(n==="string"){let r=typeof e=="string"?Jn(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:xe($t(r.length),s)}}throw new Error(`Unsupported ABI type: ${l.type}`)}function Rn(l){let e=0;for(let{dynamic:o,encoded:s}of l)e+=o?32:s.length;let t=[],n=[],r=0;for(let{dynamic:o,encoded:s}of l)o?(t.push($t(e+r)),n.push(s),r+=s.length):t.push(s);return xe(...t,...n)}function sr(l,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=Un(l,s);a.dynamic&&(r=!0),o.push(a)}if(n||r){let s=Rn(o);if(n){let a=$t(o.length);return{dynamic:!0,encoded:o.length>0?xe(a,s):a}}return{dynamic:!0,encoded:s}}return{dynamic:!1,encoded:xe(...o.map(s=>s.encoded))}}function ar(l,e){let t=l.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=Un(s,a);i.dynamic&&(n=!0),r.push(i)}return{dynamic:n,encoded:n?Rn(r):xe(...r.map(o=>o.encoded))}}function ir(l){let e=l.match(/^(.*)\[(\d+)?\]$/);return e?[e[2]?Number(e[2]):null,e[1]]:void 0}function lr(l,e){if(l.length!==e.length)throw new Error(`Expected ${l.length} values, got ${e.length}`);if(l.length===0)return"0x";let t=l.map((r,o)=>Un(r,e[o])),n=Rn(t);return Xn(n)}function De({abi:l,functionName:e,args:t=[]}){let n=l.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=rr(n),o=tr(Jn(r)).slice(0,4),s=n.inputs??[],a=s.length===0||t.length===0?new Uint8Array(0):Ot(lr(s,t).slice(2));return Xn(xe(o,a))}var cr="https://wallet-api.pantograph.app",ur="0x0000000000000000000000000000000000000000",ro={"0x1":"ether","0xa":"optimism","0x38":"bsc","0x89":"matic","0x2105":"base","0xa4b1":"arbitrum","0xa86a":"avax","0xe708":"linea"},so={"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 qt(l){return ro[l.toLowerCase()]||l}function no(l){let e=[];for(let[t,n]of Object.entries(l))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 En(l){let e=l,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(l){if(l==null)return null;let e=typeof l=="number"?l:parseFloat(String(l));return Number.isFinite(e)?e:null}function oo(l,e){let t=l.decimals,n=typeof t=="number"?t:typeof t=="string"?parseInt(t,10):null,r=j(l.price_change_percentage_24h)??j(l.priceChange24h)??j(l.usd_price_24hr_percent_change)??j(l.price_change_24h)??j(l.priceChange);return{chainId:e,tokenAddress:l.address??l.token_address??"",name:l.name??null,symbol:l.symbol??null,decimals:n!=null&&Number.isFinite(n)?n:null,logo:l.logoURI??l.icon_image??l.logo??null,usdPrice:j(l.price)??j(l.usd_price),marketCap:j(l.market_cap)??j(l.marketCap),totalVolume:{"24h":j(l.total_volume)??j(l.volume24h)},pricePercentChange:{"24h":r}}}var Y=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??cr).replace(/\/+$/,"")}async enrichTokenPrices(e,t){if(e.length!==0)try{let n=qt(t),r=e.map(u=>u.token_address).join(","),o=`${this.baseUrl}/keyrings/tokens/${n}/v2?addresses=${encodeURIComponent(r)}`,s=await fetch(o);if(!s.ok)return;let a=await s.json(),i=Array.isArray(a)?a:Array.isArray(a.data)?a.data:[],c=new Map;for(let u of i)u.address&&c.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=c.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=qt(t),r=`${this.baseUrl}/keyrings/tokens/${n}/v2?addresses=${encodeURIComponent(e)}`,o=await fetch(r);if(!o.ok)return null;let s=await o.json(),a=Array.isArray(s)?s:Array.isArray(s.data)?s.data:[],i=e.toLowerCase()===ur,c=a.find(u=>u.address?.toLowerCase()===e.toLowerCase()||i&&u.address===""||u.token_address?.toLowerCase()===e.toLowerCase()||i&&u.token_address==="");return c?En(c):null}catch{return null}}async getTokensMetadata(e,t){let n={};if(e.length===0)return n;try{let r=qt(t),o=`${this.baseUrl}/keyrings/tokens/${r}/v2?addresses=${encodeURIComponent(e.join(","))}`,s=await fetch(o);if(!s.ok)return n;let a=await s.json(),i=Array.isArray(a)?a:Array.isArray(a.data)?a.data:[],c=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&&c.has(d)&&(n[d]=En(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||"0x2105",16);if(isNaN(o))return{success:!1,error:`Invalid hex chain: ${n}`};try{let s=no({chainId:o,key:t}),a=`${this.baseUrl}/token-list?${s}`,i=await fetch(a);if(!i.ok)throw new Error(`HTTP ${i.status}`);let c=await i.json(),u=[];return c?.data?u=Array.isArray(c.data)?c.data:Object.values(c.data).flat():typeof c=="object"&&!Array.isArray(c)&&(u=Object.values(c).flat()),{success:!0,data:u.map(En)}}catch(s){return{success:!1,error:s instanceof Error?s.message:"Unknown error"}}}async getTrendingTokens(e){let t=e?.chain||"0x2105",n=5,r=e?.limit&&e.limit>0?Math.floor(e.limit):void 0;try{let o=so[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({})]),c=a.status==="fulfilled"?a.value:[],u=i.status==="fulfilled"?o.map(f=>i.value[f.toLowerCase()]).filter(f=>f!=null).map(f=>oo(f,t)):[],d=new Set,p=[];for(let f of u){let g=f.tokenAddress?.toLowerCase();!g||d.has(g)||(d.add(g),p.push(f))}let m=[];for(let f of c){let g=f.tokenAddress?.toLowerCase();!g||d.has(g)||(d.add(g),m.push(f))}let h=r!=null?[...p,...m].slice(0,r):[...p,...m.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=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=qt(e),o=no({duration:n,limit:t}),s=`${this.baseUrl}/token-list/top-gainers/${r}${o?`?${o}`:""}`,a=await fetch(s);if(!a.ok)throw new Error(`HTTP ${a.status}`);let i=await a.json();return(Array.isArray(i)?i:Array.isArray(i.data)?i.data:[]).map(u=>oo(u,e))}async getTopGainers(e){let t=e?.chain||"0x2105",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 dr="https://nft.keyring.app",mr="https://nft-demo.keyring.app",pr=.01,U=5,B=1e3,hr="0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",gr="0x0000000000000000000000000000000000000000";function F(l){return l||"0x2105"}function O(l){let e=[];for(let[t,n]of Object.entries(l))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 $(l){return new Promise(e=>setTimeout(e,l))}function fr(l){if(Array.isArray(l))return l;if(l&&typeof l=="object"){let e=l;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 Nn(l){if(Array.isArray(l))return l;if(l&&typeof l=="object"){let e=l;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??dr).replace(/\/+$/,""),this.v1BaseUrl=(e?.v1BaseUrl??mr).replace(/\/+$/,""),this.pantograph=new Y({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=F(n),i="Unknown error";for(let c=1;c<=U;c++)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 h=O(u),f=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/tokens?${h}`,g=await fetch(f);if(!g.ok)throw new Error(`HTTP ${g.status}`);let y=await g.json(),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(h=>parseFloat(h.balance||"0")>0),m.result=m.result.map(h=>({...h,token_address:h.token_address.toLowerCase()===hr?gr:h.token_address})),await this.enrichTokenPrices(m.result,a),m.result=m.result.filter(h=>h.usd_value==null||h.usd_value>=pr)),{success:!0,data:m}}catch(u){i=u instanceof Error?u.message:"Unknown error",c<U&&await $(B)}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=F(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,c]=await Promise.allSettled([fetch(`${this.baseUrl}/api/moralis/proxy/erc20/metadata?${O({chain:r,addresses:[t]})}`),fetch(`${this.baseUrl}/api/moralis/proxy/erc20/${encodeURIComponent(t)}/price?${O({chain:r,include:"percent_change"})}`)]),u=null;if(i.status==="fulfilled"&&i.value.ok){let p=await i.value.json();u=(Array.isArray(p)?p:Array.isArray(p?.data)?p.data:[])[0]??null}if(!u){o="Token metadata not found",a<U&&await $(B);continue}let d=null;if(c.status==="fulfilled"&&c.value.ok){let p=await c.value.json();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 $(B)}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:c="decimal"}=e;if(!t)return{success:!1,error:"Address is required"};let u=F(n),d="Unknown error";for(let p=1;p<=U;p++)try{let m={chain:u,format:c,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 h=O(m),f=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/nft?${h}`,g=await fetch(f);if(!g.ok)throw new Error(`HTTP ${g.status}`);let y=await g.json();return{success:!0,data:y?.data??y}}catch(m){d=m instanceof Error?m.message:"Unknown error",p<U&&await $(B)}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(!D(t))return{success:!1,error:"NFT metadata lookup by token ID is only supported on EVM chains"};let c=F(r),u="Unknown error";for(let d=1;d<=U;d++)try{let p={chain:c,format:o,normalizeMetadata:s,media_items:a};i&&(p.include=i);let m=O(p),h=`${this.baseUrl}/api/moralis/proxy/nft/${encodeURIComponent(t)}/${encodeURIComponent(n)}?${m}`,f=await fetch(h);if(!f.ok)throw new Error(`HTTP ${f.status}`);let g=await f.json();return{success:!0,data:g?.data??g}}catch(p){u=p instanceof Error?p.message:"Unknown error",d<U&&await $(B)}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=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/nft/${encodeURIComponent(t)}/metadata?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}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:c,order:u="DESC",includeInternalTransactions:d,nftMetadata:p}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"Wallet history is only supported for EVM addresses"};let m=F(n),h=Math.max(1,Math.min(100,Math.floor(r))),f="Unknown error";for(let g=1;g<=U;g++)try{let y={chain:m,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),c&&(y.cursor=c),d!=null&&(y.include_internal_transactions=d),p!=null&&(y.nft_metadata=p);let k=O(y),w=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/history?${k}`,b=await fetch(w);if(!b.ok)throw new Error(`HTTP ${b.status}`);let T=await b.json();return{success:!0,data:T?.data??T}}catch(y){f=y instanceof Error?y.message:"Unknown error",g<U&&await $(B)}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:c,cursor:u,order:d="DESC"}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"ERC-20 transfers are only supported for EVM addresses"};let p=F(n),m=Math.max(1,Math.min(100,Math.floor(o))),h="Unknown error";for(let f=1;f<=U;f++)try{let g={chain:p,limit:m,order:d};s&&(g.from_date=s),a&&(g.to_date=a),i!=null&&(g.from_block=i),c!=null&&(g.to_block=c),u&&(g.cursor=u),r?.length&&(g.contract_addresses=r);let y=O(g),k=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/erc20/transfers?${y}`,w=await fetch(k);if(!w.ok)throw new Error(`HTTP ${w.status}`);let b=await w.json();return{success:!0,data:b?.data??b}}catch(g){h=g instanceof Error?g.message:"Unknown error",f<U&&await $(B)}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:c,cursor:u,order:d="DESC",includePrices:p,format:m="decimal"}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"NFT transfers are only supported for EVM addresses"};let h=F(n),f=Math.max(1,Math.min(100,Math.floor(o))),g="Unknown error";for(let y=1;y<=U;y++)try{let k={chain:h,limit:f,order:d,format:m};s&&(k.from_date=s),a&&(k.to_date=a),i!=null&&(k.from_block=i),c!=null&&(k.to_block=c),u&&(k.cursor=u),r?.length&&(k.contract_addresses=r),p!=null&&(k.include_prices=p);let w=O(k),b=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/nft/transfers?${w}`,T=await fetch(b);if(!T.ok)throw new Error(`HTTP ${T.status}`);let P=await T.json();return{success:!0,data:P?.data??P}}catch(k){g=k instanceof Error?k.message:"Unknown error",y<U&&await $(B)}return{success:!1,error:g}}async getTokenHolders(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Contract address is required"};if(!D(t))return{success:!1,error:"Token holders is only supported for EVM contract addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/erc20/${encodeURIComponent(t)}/holders?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}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(!D(t))return{success:!1,error:"Wallet net worth is only supported for EVM addresses"};let i="Unknown error";for(let c=1;c<=U;c++)try{let u={};n&&n.length>0&&(u.chains=n.map(y=>F(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=O(u),p=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/net-worth${d?`?${d}`:""}`,m=await fetch(p);if(!m.ok)throw new Error(`HTTP ${m.status}`);let h=await m.json();return{success:!0,data:h?.data??h}}catch(u){i=u instanceof Error?u.message:"Unknown error",c<U&&await $(B)}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(!D(t))return{success:!1,error:"Wallet PnL summary is only supported for EVM addresses"};let o=F(n),s="Unknown error";for(let a=1;a<=U;a++)try{let i={chain:o};r&&(i.days=r);let c=O(i),u=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/profitability/summary?${c}`,d=await fetch(u);if(!d.ok)throw new Error(`HTTP ${d.status}`);let p=await d.json();return{success:!0,data:p?.data??p}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}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(!D(t))return{success:!1,error:"Wallet PnL is only supported for EVM addresses"};let s=F(n),a="Unknown error";for(let i=1;i<=U;i++)try{let c={chain:s};r&&(c.days=r),o&&o.length>0&&(c.token_addresses=o.slice(0,25));let u=O(c),d=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/profitability?${u}`,p=await fetch(d);if(!p.ok)throw new Error(`HTTP ${p.status}`);let m=await p.json();return{success:!0,data:m?.data??m}}catch(c){a=c instanceof Error?c.message:"Unknown error",i<U&&await $(B)}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=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/transaction/${encodeURIComponent(t)}/verbose?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}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(!D(t))return{success:!1,error:"Wallet approvals are only supported for EVM addresses"};let s=F(n),a="Unknown error";for(let i=1;i<=U;i++)try{let c={chain:s};r!=null&&(c.limit=r),o&&(c.cursor=o);let u=O(c),d=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/approvals?${u}`,p=await fetch(d);if(!p.ok)throw new Error(`HTTP ${p.status}`);let m=await p.json();return{success:!0,data:m?.data??m}}catch(c){a=c instanceof Error?c.message:"Unknown error",i<U&&await $(B)}return{success:!1,error:a}}async getWalletDefiSummary(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"Wallet DeFi summary is only supported for EVM addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chains:r}),i=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/summary?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}return{success:!1,error:o}}async getWalletDefiPositions(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"Wallet DeFi positions are only supported for EVM addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chains:r}),i=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/positions?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json(),d=u?.data??u;return{success:!0,data:fr(d)}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}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(!D(t))return{success:!1,error:"Wallet DeFi protocol positions are only supported for EVM addresses"};let o=F(r),s="Unknown error";for(let a=1;a<=U;a++)try{let i=O({chains:o}),c=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/${encodeURIComponent(n)}/positions?${i}`,u=await fetch(c);if(!u.ok)throw new Error(`HTTP ${u.status}`);let m=(await u.json()).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(g=>({token_type:g.tokenType,address:g.address,contract_address:g.address,name:g.name??"",symbol:g.symbol??"",decimals:g.decimals??18,logo:g.logo,balance:g.balance??"0",balance_formatted:g.balanceFormatted??"0",usd_price:g.usdPrice,usd_value:g.usdValue}))}))}}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}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(!D(t))return{success:!1,error:"Token analytics is only supported for EVM contract addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/tokens/${encodeURIComponent(t)}/analytics?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}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(!D(t))return{success:!1,error:"Token score is only supported for EVM contract addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/tokens/${encodeURIComponent(t)}/score?${a}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}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=F(t)),n!=null&&(s.limit=n);let a=O(s),i=`${this.baseUrl}/api/moralis/proxy/tokens/trending${a?`?${a}`:""}`,c=await fetch(i);if(!c.ok)throw new Error(`HTTP ${c.status}`);let u=await c.json(),d=u?.data??u;return{success:!0,data:Nn(d)}}catch(s){r=s instanceof Error?s.message:"Unknown error",o<U&&await $(B)}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=F(t)),n&&(i.time_frame=n),r!=null&&(i.min_market_cap=r),o!=null&&(i.security_score=o);let c=O(i),u=`${this.baseUrl}/api/moralis/proxy/discovery/tokens/top-gainers${c?`?${c}`:""}`,d=await fetch(u);if(!d.ok)throw new Error(`HTTP ${d.status}`);let p=await d.json(),m=p?.data??p;return{success:!0,data:Nn(m)}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}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=F(t)),n&&(i.time_frame=n),r!=null&&(i.min_market_cap=r),o!=null&&(i.security_score=o);let c=O(i),u=`${this.baseUrl}/api/moralis/proxy/discovery/tokens/top-losers${c?`?${c}`:""}`,d=await fetch(u);if(!d.ok)throw new Error(`HTTP ${d.status}`);let p=await d.json(),m=p?.data??p;return{success:!0,data:Nn(m)}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}return{success:!1,error:s}}async enrichTokenPrices(e,t){return this.pantograph.enrichTokenPrices(e,t)}};var Le=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} \u3092\u8CB7\u3046". 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} \u3092\u8CB7\u3046". Always include {token}.`,required:!0}];service;llm;constructor(e){super(),this.service=new E(e),this.llm=new le({})}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,c,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,h=[...d].sort((b,T)=>(m(b)?0:1)-(m(T)?0:1)),f=null,g=h[0];if(g?.token_address){let b=await this.service.getTokenMetadata({address:g.token_address,chain:t});b.success&&b.data&&(f=b.data)}!f&&g&&(f={...g});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 % (use \u25B2 for positive, \u25BC 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:!!c,walletBalance:c,token:f,...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 Me=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 chain ${n??"default"}. Try passing contractAddress directly.`};s=d.address,a=d.symbol,i=d.name}let c=await this.service.getTokenHolders({address:s,chain:n});if(!c.success)return{error:c.error||"Failed to fetch token holder stats"};let u=c.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 Be=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 chain ${n??"default"}. Try passing contractAddress directly.`};s=d.address,a=d.symbol,i=d.name}let c=await this.service.getTokenAnalytics({address:s,chain:n});if(!c.success)return{error:c.error||"Failed to fetch token analytics"};let u=c.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 Oe=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 chain ${n??"default"}. Try passing contractAddress directly.`};s=p.address,a=p.symbol,i=p.name}let c=await this.service.getTokenScore({address:s,chain:n});if(!c.success)return{error:c.error||"Failed to fetch token score"};let u=c.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 $e=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}];service;constructor(e){super(),this.service=new Y({baseUrl:e?.pantographUrl})}async run(e,t){let n=nn(e.chain,t),r=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.floor(e.limit):void 0,o=await this.service.getTrendingTokens({chain:n,limit:r});if(!o.success)return{error:o.error||"Failed to fetch trending tokens"};let s=o.data??[];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 Cn=Object.defineProperty;var Fo=Object.getOwnPropertyDescriptor;var Wo=Object.getOwnPropertyNames;var Vo=Object.prototype.hasOwnProperty;var Ho=(c,e)=>{for(var t in e)Cn(c,t,{get:e[t],enumerable:!0})},Go=(c,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of Wo(e))!Vo.call(c,r)&&r!==t&&Cn(c,r,{get:()=>e[r],enumerable:!(n=Fo(e,r))||n.enumerable});return c};var Ko=c=>Go(Cn({},"__esModule",{value:!0}),c);var Ks={};Ho(Ks,{AI_AGENT_TOOL_NAMES:()=>kn,AgentCore:()=>An,ApproveTokenTool:()=>_t,BaseNftMessageTool:()=>ee,BaseSwapService:()=>ve,BaseTool:()=>v,BaseWalletActionTool:()=>J,BuyTokenTool:()=>Rt,ChatHistory:()=>Nt,DEFAULT_PROVIDER:()=>Vt,DEFAULT_RPC_BY_CHAIN:()=>ne,DebridgeAdapter:()=>dt,EstimatePoolYieldTool:()=>ft,GeminiProvider:()=>le,GeminiSearchAiTool:()=>nt,HEX_TO_PANTOGRAPH:()=>ro,KnowledgeBase:()=>Dt,MoralisService:()=>E,NFTContractInfoTool:()=>et,NFTMetadataTool:()=>tt,NFT_AGENT_TOOL_NAMES:()=>wn,OpenAddLiquidityFormTool:()=>ht,POOL_AGENT_TOOL_NAMES:()=>Tn,PantographService:()=>Y,PoolByAddressTool:()=>at,PoolDetailTool:()=>rt,PoolSearchTool:()=>st,PoolService:()=>_e,PreviewAddLiquidityTool:()=>gt,RelayAdapter:()=>mt,Router:()=>Lt,SWAP_PROVIDER_CONFIG:()=>mn,SendNativeTool:()=>Pt,SendNftTool:()=>Je,SendTokenTool:()=>At,Subagent:()=>V,SubgraphCoinPoolPairsTool:()=>vt,SubgraphPoolByAddressTool:()=>wt,SubgraphPoolByPositionIdTool:()=>kt,SubgraphPoolSearchTool:()=>yt,SubgraphPositionDetailTool:()=>Tt,SubgraphTrendingPoolsTool:()=>bt,Summarizer:()=>It,SwapServiceFactory:()=>Ae,SwapTokenTool:()=>Et,Synthesizer:()=>Mt,TOKEN_AGENT_TOOL_NAMES:()=>bn,TRANSFER_TOPIC:()=>xo,TRENDING_DEFAULT_BY_HEX_CHAIN:()=>so,TokenAnalyticsTool:()=>Be,TokenHoldersTool:()=>Me,TokenInfoTool:()=>Le,TokenScoreTool:()=>Oe,ToolRegistry:()=>Ee,TopGainersTool:()=>qe,TopPoolsTool:()=>ot,TransactionByHashTool:()=>Xe,TrendingTokensTool:()=>$e,UNISWAP_CHAIN_SLUG:()=>Gt,UnwrapNativeTool:()=>Ut,UpstashKnowledgeBase:()=>Se,WALLET_ACTION_AGENT_TOOL_NAMES:()=>vn,WALLET_AGENT_TOOL_NAMES:()=>yn,WalletApprovalsTool:()=>je,WalletDefiPositionsTool:()=>ze,WalletDefiProtocolPositionsTool:()=>Qe,WalletDefiSummaryTool:()=>Ye,WalletHistoryTool:()=>We,WalletNFTsTool:()=>Ze,WalletNetWorthTool:()=>on,WalletNftTransfersTool:()=>He,WalletPnlSummaryTool:()=>Ge,WalletPnlTool:()=>Ke,WalletTokenBalancesTool:()=>Fe,WalletTokenTransfersTool:()=>Ve,WrapNativeTool:()=>Ct,ZERO_ADDRESS:()=>L,buildRangePresets:()=>Fn,buildUniswapPoolUrl:()=>se,clampTick:()=>he,createAiAgent:()=>Qt,createDefaultSubagents:()=>Sn,createNftAgent:()=>zt,createPoolAgent:()=>Xt,createTokenAgent:()=>Yt,createWalletActionAgent:()=>Jt,createWalletAgent:()=>jt,ethCallAt:()=>To,ethCallByChain:()=>qn,getAffiliateFee:()=>ct,getChainMeta:()=>z,getDefaultRpcUrl:()=>Jr,getGatewayAddress:()=>hn,getNativeTokenInfo:()=>pt,getPositionManagerAddress:()=>gn,getProviderByChain:()=>dn,ingestKnowledgeBase:()=>$o,priceToTick:()=>Pe,resolveRpcUrl:()=>ut,roundTickToSpacing:()=>pe,rpcCall:()=>fe,setRpcOverrides:()=>$n,swapServiceFactory:()=>ce,tickToPrice:()=>oe,toPantographChain:()=>qt});module.exports=Ko(Ks);var Hn={string:"STRING",number:"NUMBER",boolean:"BOOLEAN",object:"OBJECT",array:"ARRAY"},jo="https://nft-demo.keyring.app/api/gemini-stable",Yo=new Set([408,429,500,502,503,504]),Gn=3e4,Zt=c=>new Promise(e=>setTimeout(e,c));function en(c,e,t){if(t!=null&&t>0)return Math.min(t,Gn);let n=Math.min(e*2**(c-1),Gn);return n/2+Math.random()*(n/2)}function zo(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 le=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??jo}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?.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 fetch(u,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(l)})}catch(b){if(d=b instanceof Error?b.message:String(b),m>=s)break;await Zt(en(m,a));continue}if(!p.ok){let b=await p.text().catch(()=>p.statusText);if(d=`Gemini proxy error ${p.status}: ${b}`,!Yo.has(p.status))throw new Error(d);if(m>=s)break;let T=zo(p.headers.get("retry-after"));await Zt(en(m,a,T));continue}let h;try{h=await p.json()}catch(b){if(d=`Gemini proxy returned invalid JSON: ${b instanceof Error?b.message:String(b)}`,m>=s)break;await Zt(en(m,a));continue}let f=h.candidates?.[0];if(!f)return{text:"",toolCalls:[]};let g="",y=[],k=f.content?.parts;if(!Array.isArray(k)||k.length===0){let b=f.finishReason;if(!(b==="SAFETY"||b==="RECITATION")&&m<s){d=`Gemini returned an empty turn (finishReason=${b??"none"})`,await Zt(en(m,a));continue}return{text:"",toolCalls:[]}}for(let b of k)b.text&&(g+=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:g,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:Hn[o.type]??"STRING",description:o.description};o.type==="array"&&(s.items={type:Hn[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 Ee=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 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);return{toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r}}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}};var Qo="0x2105",Xo=["0x1","0xa","0x38","0x89","0x2105","0xa4b1","0xa86a","0xe708"],Jo=new Set(Xo),tn="Ethereum (0x1), Optimism (0xa), BSC (0x38), Polygon (0x89), Base (0x2105), Arbitrum (0xa4b1), Avalanche (0xa86a), Linea (0xe708)",Zo={"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 me(c){if(typeof c!="string")return null;let e=c.trim().toLowerCase();if(!e)return null;let t=Zo[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&&Jo.has(n)?n:null}function jn(c){return me(c)!==null}var Ne=class extends Error{constructor(t){super(`Chain "${t}" is not supported. Supported chains: ${tn}.`);this.requested=t;this.name="UnsupportedChainError"}requested;code="unsupported_chain"},Bt=c=>typeof c=="string"&&c.trim()!=="";function R(c,e,t=Qo){if(Bt(c)){let n=me(c);if(n)return n;throw new Ne(c.trim())}if(Bt(e?.chain)){let n=me(e.chain);if(n)return n;throw new Ne(e.chain.trim())}return t}function Yn(c,e){if(Bt(c)){let t=me(c);if(t)return t;throw new Ne(c.trim())}if(Bt(e?.chain)){let t=me(e.chain);if(t)return t;throw new Ne(e.chain.trim())}return null}function nn(c,e){return me(c)??me(e?.chain)??void 0}var Kn={"0x1":"Ethereum","0xa":"Optimism","0x38":"BSC","0x89":"Polygon","0x2105":"Base","0xa4b1":"Arbitrum","0xa86a":"Avalanche","0xe708":"Linea"};var er={bnb:"0x38",matic:"0x89",pol:"0x89",avax:"0xa86a"};function zn(c){if(typeof c!="string")return null;let e=c.trim().toLowerCase();return e?er[e]??null:null}function Ie(c,e){if(!Bt(c))return null;let t=me(c),n=me(e?.chain);return!t||!n||t===n?null:{requested:t,connected:n,requestedLabel:Kn[t]??t,connectedLabel:Kn[n]??n}}var Qn=require("js-sha3");function D(c){return typeof c=="string"&&/^0x[0-9a-fA-F]{40}$/.test(c)}function tr(c){return new Uint8Array(Qn.keccak256.arrayBuffer(c))}function Ot(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 Xn(c){return"0x"+Array.from(c).map(e=>e.toString(16).padStart(2,"0")).join("")}function Jn(c){return new TextEncoder().encode(c)}function xe(...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 or(c,e=32){let t=new Uint8Array(e);return t.set(c),t}function Zn(c){let e=((c%(1n<<256n)+(1n<<256n))%(1n<<256n)).toString(16).padStart(64,"0");return Ot(e)}function $t(c){return Zn(BigInt(c))}function eo(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 to(c){let e=eo(c.type);if(e==="tuple"||e.startsWith("tuple[")){let t=(c.components??[]).map(to).join(","),n=e.startsWith("tuple[")?e.slice(5):"";return`(${t})${n}`}return e}function rr(c){let e=(c.inputs??[]).map(to).join(",");return`${c.name??""}(${e})`}function Un(c,e){let t=ir(c.type);if(t){let[r,o]=t;return sr({...c,type:o},e,r)}if(c.type==="tuple")return ar(c,e);if(c.type==="address"){let r=String(e),o=r.startsWith("0x")?r.slice(2):r;return{dynamic:!1,encoded:nr(Ot(o.toLowerCase()))}}if(c.type==="bool"){let r=new Uint8Array(32);return r[31]=e?1:0,{dynamic:!1,encoded:r}}let n=eo(c.type);if(/^uint\d*$/.test(n))return{dynamic:!1,encoded:$t(BigInt(e))};if(/^int\d*$/.test(n)){let r=BigInt(e);return r<0n&&(r=r+(1n<<256n)),{dynamic:!1,encoded:Zn(r)}}if(/^bytes(\d+)$/.test(n)){let r=parseInt(n.slice(5),10),o=typeof e=="string"?Ot(e):e;if(o.length!==r)throw new Error(`bytes${r} expects exactly ${r} bytes, got ${o.length}`);return{dynamic:!1,encoded:or(o)}}if(n==="bytes"){let r=typeof e=="string"?Ot(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:xe($t(r.length),s)}}if(n==="string"){let r=typeof e=="string"?Jn(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:xe($t(r.length),s)}}throw new Error(`Unsupported ABI type: ${c.type}`)}function Rn(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($t(e+r)),n.push(s),r+=s.length):t.push(s);return xe(...t,...n)}function sr(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=Un(c,s);a.dynamic&&(r=!0),o.push(a)}if(n||r){let s=Rn(o);if(n){let a=$t(o.length);return{dynamic:!0,encoded:o.length>0?xe(a,s):a}}return{dynamic:!0,encoded:s}}return{dynamic:!1,encoded:xe(...o.map(s=>s.encoded))}}function ar(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=Un(s,a);i.dynamic&&(n=!0),r.push(i)}return{dynamic:n,encoded:n?Rn(r):xe(...r.map(o=>o.encoded))}}function ir(c){let e=c.match(/^(.*)\[(\d+)?\]$/);return e?[e[2]?Number(e[2]):null,e[1]]:void 0}function lr(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)=>Un(r,e[o])),n=Rn(t);return Xn(n)}function De({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=rr(n),o=tr(Jn(r)).slice(0,4),s=n.inputs??[],a=s.length===0||t.length===0?new Uint8Array(0):Ot(lr(s,t).slice(2));return Xn(xe(o,a))}var cr="https://wallet-api.pantograph.app",ur="0x0000000000000000000000000000000000000000",ro={"0x1":"ether","0xa":"optimism","0x38":"bsc","0x89":"matic","0x2105":"base","0xa4b1":"arbitrum","0xa86a":"avax","0xe708":"linea"},so={"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 qt(c){return ro[c.toLowerCase()]||c}function no(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 En(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 oo(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 Y=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??cr).replace(/\/+$/,"")}async enrichTokenPrices(e,t){if(e.length!==0)try{let n=qt(t),r=e.map(u=>u.token_address).join(","),o=`${this.baseUrl}/keyrings/tokens/${n}/v2?addresses=${encodeURIComponent(r)}`,s=await fetch(o);if(!s.ok)return;let a=await s.json(),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=qt(t),r=`${this.baseUrl}/keyrings/tokens/${n}/v2?addresses=${encodeURIComponent(e)}`,o=await fetch(r);if(!o.ok)return null;let s=await o.json(),a=Array.isArray(s)?s:Array.isArray(s.data)?s.data:[],i=e.toLowerCase()===ur,l=a.find(u=>u.address?.toLowerCase()===e.toLowerCase()||i&&u.address===""||u.token_address?.toLowerCase()===e.toLowerCase()||i&&u.token_address==="");return l?En(l):null}catch{return null}}async getTokensMetadata(e,t){let n={};if(e.length===0)return n;try{let r=qt(t),o=`${this.baseUrl}/keyrings/tokens/${r}/v2?addresses=${encodeURIComponent(e.join(","))}`,s=await fetch(o);if(!s.ok)return n;let a=await s.json(),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]=En(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||"0x2105",16);if(isNaN(o))return{success:!1,error:`Invalid hex chain: ${n}`};try{let s=no({chainId:o,key:t}),a=`${this.baseUrl}/token-list?${s}`,i=await fetch(a);if(!i.ok)throw new Error(`HTTP ${i.status}`);let l=await i.json(),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(En)}}catch(s){return{success:!1,error:s instanceof Error?s.message:"Unknown error"}}}async getTrendingTokens(e){let t=e?.chain||"0x2105",n=5,r=e?.limit&&e.limit>0?Math.floor(e.limit):void 0;try{let o=so[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=>oo(f,t)):[],d=new Set,m=[];for(let f of u){let g=f.tokenAddress?.toLowerCase();!g||d.has(g)||(d.add(g),m.push(f))}let p=[];for(let f of l){let g=f.tokenAddress?.toLowerCase();!g||d.has(g)||(d.add(g),p.push(f))}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=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=qt(e),o=no({duration:n,limit:t}),s=`${this.baseUrl}/token-list/top-gainers/${r}${o?`?${o}`:""}`,a=await fetch(s);if(!a.ok)throw new Error(`HTTP ${a.status}`);let i=await a.json();return(Array.isArray(i)?i:Array.isArray(i.data)?i.data:[]).map(u=>oo(u,e))}async getTopGainers(e){let t=e?.chain||"0x2105",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 dr="https://nft.keyring.app",mr="https://nft-demo.keyring.app",pr=.01,U=5,B=1e3,hr="0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",gr="0x0000000000000000000000000000000000000000";function F(c){return c||"0x2105"}function O(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 $(c){return new Promise(e=>setTimeout(e,c))}function fr(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 Nn(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??dr).replace(/\/+$/,""),this.v1BaseUrl=(e?.v1BaseUrl??mr).replace(/\/+$/,""),this.pantograph=new Y({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=F(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=O(u),f=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/tokens?${h}`,g=await fetch(f);if(!g.ok)throw new Error(`HTTP ${g.status}`);let y=await g.json(),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>=pr)),{success:!0,data:p}}catch(u){i=u instanceof Error?u.message:"Unknown error",l<U&&await $(B)}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=F(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([fetch(`${this.baseUrl}/api/moralis/proxy/erc20/metadata?${O({chain:r,addresses:[t]})}`),fetch(`${this.baseUrl}/api/moralis/proxy/erc20/${encodeURIComponent(t)}/price?${O({chain:r,include:"percent_change"})}`)]),u=null;if(i.status==="fulfilled"&&i.value.ok){let m=await i.value.json();u=(Array.isArray(m)?m:Array.isArray(m?.data)?m.data:[])[0]??null}if(!u){o="Token metadata not found",a<U&&await $(B);continue}let d=null;if(l.status==="fulfilled"&&l.value.ok){let m=await l.value.json();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 $(B)}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=F(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=O(p),f=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/nft?${h}`,g=await fetch(f);if(!g.ok)throw new Error(`HTTP ${g.status}`);let y=await g.json();return{success:!0,data:y?.data??y}}catch(p){d=p instanceof Error?p.message:"Unknown error",m<U&&await $(B)}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(!D(t))return{success:!1,error:"NFT metadata lookup by token ID is only supported on EVM chains"};let l=F(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=O(m),h=`${this.baseUrl}/api/moralis/proxy/nft/${encodeURIComponent(t)}/${encodeURIComponent(n)}?${p}`,f=await fetch(h);if(!f.ok)throw new Error(`HTTP ${f.status}`);let g=await f.json();return{success:!0,data:g?.data??g}}catch(m){u=m instanceof Error?m.message:"Unknown error",d<U&&await $(B)}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=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/nft/${encodeURIComponent(t)}/metadata?${a}`,l=await fetch(i);if(!l.ok)throw new Error(`HTTP ${l.status}`);let u=await l.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}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(!D(t))return{success:!1,error:"Wallet history is only supported for EVM addresses"};let p=F(n),h=Math.max(1,Math.min(100,Math.floor(r))),f="Unknown error";for(let g=1;g<=U;g++)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=O(y),w=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/history?${k}`,b=await fetch(w);if(!b.ok)throw new Error(`HTTP ${b.status}`);let T=await b.json();return{success:!0,data:T?.data??T}}catch(y){f=y instanceof Error?y.message:"Unknown error",g<U&&await $(B)}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(!D(t))return{success:!1,error:"ERC-20 transfers are only supported for EVM addresses"};let m=F(n),p=Math.max(1,Math.min(100,Math.floor(o))),h="Unknown error";for(let f=1;f<=U;f++)try{let g={chain:m,limit:p,order:d};s&&(g.from_date=s),a&&(g.to_date=a),i!=null&&(g.from_block=i),l!=null&&(g.to_block=l),u&&(g.cursor=u),r?.length&&(g.contract_addresses=r);let y=O(g),k=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/erc20/transfers?${y}`,w=await fetch(k);if(!w.ok)throw new Error(`HTTP ${w.status}`);let b=await w.json();return{success:!0,data:b?.data??b}}catch(g){h=g instanceof Error?g.message:"Unknown error",f<U&&await $(B)}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(!D(t))return{success:!1,error:"NFT transfers are only supported for EVM addresses"};let h=F(n),f=Math.max(1,Math.min(100,Math.floor(o))),g="Unknown error";for(let y=1;y<=U;y++)try{let k={chain:h,limit:f,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=O(k),b=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/nft/transfers?${w}`,T=await fetch(b);if(!T.ok)throw new Error(`HTTP ${T.status}`);let P=await T.json();return{success:!0,data:P?.data??P}}catch(k){g=k instanceof Error?k.message:"Unknown error",y<U&&await $(B)}return{success:!1,error:g}}async getTokenHolders(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Contract address is required"};if(!D(t))return{success:!1,error:"Token holders is only supported for EVM contract addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/erc20/${encodeURIComponent(t)}/holders?${a}`,l=await fetch(i);if(!l.ok)throw new Error(`HTTP ${l.status}`);let u=await l.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}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(!D(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=>F(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=O(u),m=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/net-worth${d?`?${d}`:""}`,p=await fetch(m);if(!p.ok)throw new Error(`HTTP ${p.status}`);let h=await p.json();return{success:!0,data:h?.data??h}}catch(u){i=u instanceof Error?u.message:"Unknown error",l<U&&await $(B)}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(!D(t))return{success:!1,error:"Wallet PnL summary is only supported for EVM addresses"};let o=F(n),s="Unknown error";for(let a=1;a<=U;a++)try{let i={chain:o};r&&(i.days=r);let l=O(i),u=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/profitability/summary?${l}`,d=await fetch(u);if(!d.ok)throw new Error(`HTTP ${d.status}`);let m=await d.json();return{success:!0,data:m?.data??m}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}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(!D(t))return{success:!1,error:"Wallet PnL is only supported for EVM addresses"};let s=F(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=O(l),d=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/profitability?${u}`,m=await fetch(d);if(!m.ok)throw new Error(`HTTP ${m.status}`);let p=await m.json();return{success:!0,data:p?.data??p}}catch(l){a=l instanceof Error?l.message:"Unknown error",i<U&&await $(B)}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=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/transaction/${encodeURIComponent(t)}/verbose?${a}`,l=await fetch(i);if(!l.ok)throw new Error(`HTTP ${l.status}`);let u=await l.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}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(!D(t))return{success:!1,error:"Wallet approvals are only supported for EVM addresses"};let s=F(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=O(l),d=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/approvals?${u}`,m=await fetch(d);if(!m.ok)throw new Error(`HTTP ${m.status}`);let p=await m.json();return{success:!0,data:p?.data??p}}catch(l){a=l instanceof Error?l.message:"Unknown error",i<U&&await $(B)}return{success:!1,error:a}}async getWalletDefiSummary(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"Wallet DeFi summary is only supported for EVM addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chains:r}),i=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/summary?${a}`,l=await fetch(i);if(!l.ok)throw new Error(`HTTP ${l.status}`);let u=await l.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}return{success:!1,error:o}}async getWalletDefiPositions(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Address is required"};if(!D(t))return{success:!1,error:"Wallet DeFi positions are only supported for EVM addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chains:r}),i=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/positions?${a}`,l=await fetch(i);if(!l.ok)throw new Error(`HTTP ${l.status}`);let u=await l.json(),d=u?.data??u;return{success:!0,data:fr(d)}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}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(!D(t))return{success:!1,error:"Wallet DeFi protocol positions are only supported for EVM addresses"};let o=F(r),s="Unknown error";for(let a=1;a<=U;a++)try{let i=O({chains:o}),l=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/${encodeURIComponent(n)}/positions?${i}`,u=await fetch(l);if(!u.ok)throw new Error(`HTTP ${u.status}`);let p=(await u.json()).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(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(g=>({token_type:g.tokenType,address:g.address,contract_address:g.address,name:g.name??"",symbol:g.symbol??"",decimals:g.decimals??18,logo:g.logo,balance:g.balance??"0",balance_formatted:g.balanceFormatted??"0",usd_price:g.usdPrice,usd_value:g.usdValue}))}))}}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}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(!D(t))return{success:!1,error:"Token analytics is only supported for EVM contract addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/tokens/${encodeURIComponent(t)}/analytics?${a}`,l=await fetch(i);if(!l.ok)throw new Error(`HTTP ${l.status}`);let u=await l.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}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(!D(t))return{success:!1,error:"Token score is only supported for EVM contract addresses"};let r=F(n),o="Unknown error";for(let s=1;s<=U;s++)try{let a=O({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/tokens/${encodeURIComponent(t)}/score?${a}`,l=await fetch(i);if(!l.ok)throw new Error(`HTTP ${l.status}`);let u=await l.json();return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<U&&await $(B)}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=F(t)),n!=null&&(s.limit=n);let a=O(s),i=`${this.baseUrl}/api/moralis/proxy/tokens/trending${a?`?${a}`:""}`,l=await fetch(i);if(!l.ok)throw new Error(`HTTP ${l.status}`);let u=await l.json(),d=u?.data??u;return{success:!0,data:Nn(d)}}catch(s){r=s instanceof Error?s.message:"Unknown error",o<U&&await $(B)}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=F(t)),n&&(i.time_frame=n),r!=null&&(i.min_market_cap=r),o!=null&&(i.security_score=o);let l=O(i),u=`${this.baseUrl}/api/moralis/proxy/discovery/tokens/top-gainers${l?`?${l}`:""}`,d=await fetch(u);if(!d.ok)throw new Error(`HTTP ${d.status}`);let m=await d.json(),p=m?.data??m;return{success:!0,data:Nn(p)}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}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=F(t)),n&&(i.time_frame=n),r!=null&&(i.min_market_cap=r),o!=null&&(i.security_score=o);let l=O(i),u=`${this.baseUrl}/api/moralis/proxy/discovery/tokens/top-losers${l?`?${l}`:""}`,d=await fetch(u);if(!d.ok)throw new Error(`HTTP ${d.status}`);let m=await d.json(),p=m?.data??m;return{success:!0,data:Nn(p)}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<U&&await $(B)}return{success:!1,error:s}}async enrichTokenPrices(e,t){return this.pantograph.enrichTokenPrices(e,t)}};var Le=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} \u3092\u8CB7\u3046". 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} \u3092\u8CB7\u3046". Always include {token}.`,required:!0}];service;llm;constructor(e){super(),this.service=new E(e),this.llm=new le({})}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)),f=null,g=h[0];if(g?.token_address){let b=await this.service.getTokenMetadata({address:g.token_address,chain:t});b.success&&b.data&&(f=b.data)}!f&&g&&(f={...g});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 % (use \u25B2 for positive, \u25BC 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: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 Me=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 chain ${n??"default"}. 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 Be=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 chain ${n??"default"}. 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 Oe=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 chain ${n??"default"}. 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 $e=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} \u3092\u8CB7\u3046". 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} \u3092\u8CB7\u3046". Always include {token}.`,required:!0}];service;constructor(e){super(),this.service=new Y({baseUrl:e?.pantographUrl})}async run(e,t){let n=nn(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.`,chain:n??"all",count:
|
|
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??"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 io={"1h":"1h","1d":"24h","1w":"7d","1M":"30d"},ao=Object.keys(io),qe=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 Base) 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 Base (0x2105).',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 Y({baseUrl:e?.pantographUrl})}async run(e,t){let n=nn(e.chain,t),r=typeof e.timeFrame=="string"?e.timeFrame.trim():void 0;if(r&&!ao.includes(r))return{error:`Invalid timeFrame "${r}". Allowed values: ${ao.join(", ")}.`};let o=r||void 0,s=o?io[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> (use \u25B2 for positive, \u25BC 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??"all",timeFrame:o??null,count:c.length,tokens:c}}};var Fe=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:"No wallet address available. Please connect a wallet."};let 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||[];return{walletAddress:n,chain:r||"default",tokenCount:s.length,tokens:s,totalUsdValue:s.reduce((a,i)=>a+(i.usd_value||0),0)}}};var We=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let 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,c=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",h=typeof e.includeInternalTransactions=="boolean"?e.includeInternalTransactions:void 0,f=typeof e.nftMetadata=="boolean"?e.nftMetadata:void 0,g=typeof e.limit=="number"?e.limit:typeof e.limit=="string"?Number(e.limit):void 0,y=Number.isFinite(g)&&g>0?g:10,k=await this.service.getWalletHistory({address:n,chain:r,limit:y,fromDate:o,toDate:s,fromBlock:c,toBlock:u,cursor:d,order:m,includeInternalTransactions:h,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 yr(l){if(l.value_decimal)return l.value_decimal;let e=parseInt(l.token_decimals??"18",10);if(isNaN(e)||e===0)return l.value;try{let t=BigInt(l.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 l.value}}var Ve=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let 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,c=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",h=typeof e.direction=="string"?e.direction.trim().toLowerCase():"all",f=h==="send"?"send":h==="receive"?"receive":"all",g=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,y=Array.isArray(e.contractAddresses)?e.contractAddresses.filter(S=>typeof S=="string"&&!!S).map(S=>S.trim()):void 0,k,w;if(g){let S=await this.resolveContractAddressBySymbol(g,n,r);S&&(y=[S.address],k=S.symbol,w=S.name)}let b=await this.service.getWalletTokenTransfers({address:n,chain:r,contractAddresses:y,limit:s,fromDate:a,toDate:i,fromBlock:c,toBlock:u,cursor:d,order:m});if(!b.success)return{error:b.error||"Failed to fetch token transfers"};let T=n.toLowerCase(),x=(b.data?.result||[]).map(S=>{let _=S.from_address.toLowerCase()===T,C=_?"send":"receive",q=_?S.to_address:S.from_address,K=_?S.to_address_label??void 0:S.from_address_label??void 0,H=_?S.to_address_entity??void 0:S.from_address_entity??void 0;return{...S,direction:C,amount_formatted:yr(S),counterparty:q,counterparty_label:K,counterparty_entity:H}}),A=f==="all"?x:x.filter(S=>S.direction===f);if(g&&!y){let S=g.toLowerCase();A=A.filter(_=>(_.token_symbol??"").toLowerCase()===S)}let I=this.buildCounterparties(A);return{walletAddress:n,chain:r||"default",resolvedToken:k?{symbol:k,name:w,contractAddress:y?.[0]}:null,filters:{tokenSymbol:g||null,contractAddresses:y||null,direction:f,limit:s,order:m,fromDate:a||null,toDate:i||null,fromBlock:c??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:A.length,transfers:A,counterparties:I}}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(c=>(c.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,c=s.get(a);c?(c.total+=i,c.count+=1,!c.label&&o.counterparty_label&&(c.label=o.counterparty_label),!c.entity&&o.counterparty_entity&&(c.entity=o.counterparty_entity),o.block_timestamp<c.first&&(c.first=o.block_timestamp),o.block_timestamp>c.last&&(c.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 He=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let 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,c=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",h=typeof e.includePrices=="boolean"?e.includePrices:void 0,f=typeof e.direction=="string"?e.direction.trim().toLowerCase():"all",g=f==="send"?"send":f==="receive"?"receive":"all",y=Array.isArray(e.contractAddresses)?e.contractAddresses.filter(A=>typeof A=="string"&&!!A).map(A=>A.trim()):void 0,k=await this.service.getWalletNftTransfers({address:n,chain:r,contractAddresses:y,limit:s,fromDate:a,toDate:i,fromBlock:c,toBlock:u,cursor:d,order:m,includePrices:h});if(!k.success)return{error:k.error||"Failed to fetch NFT transfers"};let w=n.toLowerCase(),T=(k.data?.result||[]).map(A=>{let I=A.from_address.toLowerCase()===w,S=I?"send":"receive",_=I?A.to_address:A.from_address,C=I?A.to_address_label??void 0:A.from_address_label??void 0,q=I?A.to_address_entity??void 0:A.from_address_entity??void 0;return{...A,direction:S,counterparty:_,counterparty_label:C,counterparty_entity:q}}),P=g==="all"?T:T.filter(A=>A.direction===g),x=this.buildCounterparties(P);return{walletAddress:n,chain:r||"default",filters:{contractAddresses:y||null,direction:g,limit:s,order:m,fromDate:a||null,toDate:i||null,fromBlock:c??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:x}}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,c=s.get(a);c?(c.tokenIds.add(o.token_id),c.collections.add(i),c.count+=1,!c.label&&o.counterparty_label&&(c.label=o.counterparty_label),!c.entity&&o.counterparty_entity&&(c.entity=o.counterparty_entity),o.block_timestamp<c.first&&(c.first=o.block_timestamp),o.block_timestamp>c.last&&(c.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 on=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,c=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:c,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:c??null,minPairSideLiquidityUsd:u??null},totalNetWorthUsd:p.total_networth_usd,chains:p.chains,unsupportedChainIds:p.unsupported_chain_ids??[],unavailableChains:p.unavailable_chains??[]}}};var br=["all","7","30","60","90"],Ge=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=br.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 wr=["all","7","30","60","90"],rn=25,Ke=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 ${rn}. 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 ${rn}.`,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 ${rn}.`,required:!1,items:{type:"string"}}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=wr.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()):[],c=[],u=[];a.length>0&&(await Promise.all(a.map(g=>this.resolveContractAddress(g,r,n)))).forEach((g,y)=>{g?c.push({symbol:a[y],address:g.address}):u.push(a[y])});let d=kr([...c.map(f=>f.address),...i]).slice(0,rn),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 h=m.data?.result??[];return{walletAddress:n,chain:r||"default",days:s??"all",filters:{tokenAddresses:p??null,resolvedSymbols:c.length>0?c: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 kr(l){let e=new Set,t=[];for(let n of l){let r=n.toLowerCase();e.has(r)||(e.add(r),t.push(n))}return t}var Tr=BigInt(2)**BigInt(255),je=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let 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 c=i.data,d=(c.result||[]).map(f=>vr(f)),p=0,m=0,h=[];for(let f of d){f.isUnlimited&&(m+=1);let g=parseFloat(f.token.usdAtRisk??"");Number.isFinite(g)&&(p+=g);let y=f.token.possibleSpam||f.token.verifiedContract===!1,k=!f.spender.label&&!f.spender.entity;(y||f.isUnlimited&&k)&&h.push(f)}return{walletAddress:n,chain:r||"default",filters:{limit:s,cursor:a??null},pagination:{cursor:c.cursor??null,pageSize:c.page_size??s,page:c.page??null,hasMore:!!c.cursor},summary:{totalApprovals:d.length,unlimitedApprovals:m,totalUsdAtRisk:p,riskyApprovalCount:h.length},approvals:d,riskyApprovals:h}}};function vr(l){let e=Sr(l.value);return{blockNumber:l.block_number,blockTimestamp:l.block_timestamp??null,transactionHash:l.transaction_hash??null,value:l.value,valueFormatted:l.value_formatted??null,isUnlimited:e,token:{address:l.token.address,addressLabel:l.token.address_label??null,name:l.token.name??null,symbol:l.token.symbol??null,logo:l.token.logo??null,possibleSpam:!!l.token.possible_spam,verifiedContract:l.token.verified_contract??null,currentBalance:l.token.current_balance??null,currentBalanceFormatted:l.token.current_balance_formatted??null,usdPrice:l.token.usd_price??null,usdAtRisk:l.token.usd_at_risk??null},spender:{address:l.spender.address,label:l.spender.address_label??null,entity:l.spender.entity??null,entityLogo:l.spender.entity_logo??null}}}function Sr(l){if(!l)return!1;try{return BigInt(l)>=Tr}catch{return!1}}var Ye=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let 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 ze=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let 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=>xr(a))}}};function xr(l){return{protocolName:l.protocol_name,protocolId:l.protocol_id,protocolUrl:l.protocol_url,protocolLogo:l.protocol_logo,accountHealthFactor:l.account_data?.health_factor??null,position:Pr(l.position)}}function Pr(l){return{label:l.label,address:l.address,balanceUsd:l.balance_usd,totalUnclaimedUsdValue:l.total_unclaimed_usd_value,tokens:(l.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:l.position_details??null}}var Qe=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.address||t?.walletAddress||void 0,r=e.protocol?.trim(),o=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};if(!r)return{error:'Protocol identifier is required (e.g. "uniswap-v3", "aave-v3").'};let s=await this.service.getWalletDefiProtocolPositions({address:n,protocol:r,chain:o});if(!s.success)return{error:s.error||"Failed to fetch wallet DeFi protocol positions"};let a=s.data;return a?{walletAddress:n,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(Ar)}:{error:"No data returned for this protocol"}}};function Ar(l){return{label:l.label,address:l.address,balanceUsd:l.balance_usd,totalUnclaimedUsdValue:l.total_unclaimed_usd_value,tokens:(l.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:l.position_details??null}}function _r(l){try{return(Number(BigInt(l))/1e18).toFixed(10).replace(/\.?0+$/,"")||"0"}catch{return l}}var Xe=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,r=R(e.chain,t);if(!n)return{error:"Transaction hash is required."};let 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(c=>!!c.decoded_event).map(c=>({contract:c.address,signature:c.decoded_event.signature,label:c.decoded_event.label,params:c.decoded_event.params})),i=s.logs.map(c=>({address:c.address,decodedEvent:c.decoded_event?{signature:c.decoded_event.signature,label:c.decoded_event.label,params:c.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:_r(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 ee=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??"all",timeFrame:o??null,count:l.length,tokens:l}}};var Fe=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:"No wallet address available. Please connect a wallet."};let 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||[];return{walletAddress:n,chain:r||"default",tokenCount:s.length,tokens:s,totalUsdValue:s.reduce((a,i)=>a+(i.usd_value||0),0)}}};var We=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let 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,f=typeof e.nftMetadata=="boolean"?e.nftMetadata:void 0,g=typeof e.limit=="number"?e.limit:typeof e.limit=="string"?Number(e.limit):void 0,y=Number.isFinite(g)&&g>0?g: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: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 yr(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 Ve=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let 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",f=h==="send"?"send":h==="receive"?"receive":"all",g=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,y=Array.isArray(e.contractAddresses)?e.contractAddresses.filter(S=>typeof S=="string"&&!!S).map(S=>S.trim()):void 0,k,w;if(g){let S=await this.resolveContractAddressBySymbol(g,n,r);S&&(y=[S.address],k=S.symbol,w=S.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(),x=(b.data?.result||[]).map(S=>{let _=S.from_address.toLowerCase()===T,C=_?"send":"receive",q=_?S.to_address:S.from_address,K=_?S.to_address_label??void 0:S.from_address_label??void 0,H=_?S.to_address_entity??void 0:S.from_address_entity??void 0;return{...S,direction:C,amount_formatted:yr(S),counterparty:q,counterparty_label:K,counterparty_entity:H}}),A=f==="all"?x:x.filter(S=>S.direction===f);if(g&&!y){let S=g.toLowerCase();A=A.filter(_=>(_.token_symbol??"").toLowerCase()===S)}let I=this.buildCounterparties(A);return{walletAddress:n,chain:r||"default",resolvedToken:k?{symbol:k,name:w,contractAddress:y?.[0]}:null,filters:{tokenSymbol:g||null,contractAddresses:y||null,direction:f,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:A.length,transfers:A,counterparties:I}}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 He=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let 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,f=typeof e.direction=="string"?e.direction.trim().toLowerCase():"all",g=f==="send"?"send":f==="receive"?"receive":"all",y=Array.isArray(e.contractAddresses)?e.contractAddresses.filter(A=>typeof A=="string"&&!!A).map(A=>A.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(A=>{let I=A.from_address.toLowerCase()===w,S=I?"send":"receive",_=I?A.to_address:A.from_address,C=I?A.to_address_label??void 0:A.from_address_label??void 0,q=I?A.to_address_entity??void 0:A.from_address_entity??void 0;return{...A,direction:S,counterparty:_,counterparty_label:C,counterparty_entity:q}}),P=g==="all"?T:T.filter(A=>A.direction===g),x=this.buildCounterparties(P);return{walletAddress:n,chain:r||"default",filters:{contractAddresses:y||null,direction:g,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:x}}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 on=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 br=["all","7","30","60","90"],Ge=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=br.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 wr=["all","7","30","60","90"],rn=25,Ke=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 ${rn}. 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 ${rn}.`,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 ${rn}.`,required:!1,items:{type:"string"}}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=wr.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(g=>this.resolveContractAddress(g,r,n)))).forEach((g,y)=>{g?l.push({symbol:a[y],address:g.address}):u.push(a[y])});let d=kr([...l.map(f=>f.address),...i]).slice(0,rn),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 kr(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 Tr=BigInt(2)**BigInt(255),je=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let 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)),m=0,p=0,h=[];for(let f of d){f.isUnlimited&&(p+=1);let g=parseFloat(f.token.usdAtRisk??"");Number.isFinite(g)&&(m+=g);let y=f.token.possibleSpam||f.token.verifiedContract===!1,k=!f.spender.label&&!f.spender.entity;(y||f.isUnlimited&&k)&&h.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:p,totalUsdAtRisk:m,riskyApprovalCount:h.length},approvals:d,riskyApprovals:h}}};function vr(c){let e=Sr(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 Sr(c){if(!c)return!1;try{return BigInt(c)>=Tr}catch{return!1}}var Ye=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let 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 ze=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=e.address||t?.walletAddress||void 0,r=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};let 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=>xr(a))}}};function xr(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:Pr(c.position)}}function Pr(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 Qe=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.address||t?.walletAddress||void 0,r=e.protocol?.trim(),o=R(e.chain,t);if(!n)return{error:'No wallet address available. Please connect a wallet or pass "address".'};if(!r)return{error:'Protocol identifier is required (e.g. "uniswap-v3", "aave-v3").'};let s=await this.service.getWalletDefiProtocolPositions({address:n,protocol:r,chain:o});if(!s.success)return{error:s.error||"Failed to fetch wallet DeFi protocol positions"};let a=s.data;return a?{walletAddress:n,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(Ar)}:{error:"No data returned for this protocol"}}};function Ar(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 _r(c){try{return(Number(BigInt(c))/1e18).toFixed(10).replace(/\.?0+$/,"")||"0"}catch{return c}}var Xe=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,r=R(e.chain,t);if(!n)return{error:"Transaction hash is required."};let 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:_r(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 ee=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
17
|
`+t}}};var Je=class extends ee{name="send-nft";description='Use when the user wants to SEND / transfer an NFT ("send my NFT", "transfer an NFT", "g\u1EEDi NFT"). Returns a fixed message directing them to select and send the NFT on the website. Takes no arguments.';template="Please select the NFT you would like to send from the list and proceed via {link}."};var Ze=class extends ee{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 et=class extends ee{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 tt=class extends ee{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 nt=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 le(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(`
|
|
@@ -102,12 +102,12 @@ ${po}`,Dr=`query V4Pool($chain: Chain!, $poolId: String!) {
|
|
|
102
102
|
__typename
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
${po}`,Lr={"0x1":1,"0xa":10,"0x38":56,"0x89":137,"0x2105":8453,"0xa4b1":42161,"0xa86a":43114,"0xe708":59144};function an(l){return new Promise(e=>setTimeout(e,l))}function In(l){if(!l)return 1;let e=l.toLowerCase();return Lr[e]??1}function Dn(l){if(!l)return"ETHEREUM";let e=l.toLowerCase();return Er[e]??"ETHEREUM"}function co(l,e){if(!l)return[];switch(e){case"V2":return l.poolStatsV2??[];case"V3":return l.poolStatsV3??[];case"V4":return l.poolStatsV4??[]}}function uo(l,e){switch(e){case"volume1Day":return l.volume1Day?.value??0;case"volume1Week":return l.volume1Week?.value??0;case"volume30Day":return l.volume30Day?.value??0;case"txCount":return l.txCount??0;case"apr":return l.apr??0;default:return l.totalLiquidity?.value??0}}function mo(l){let e=l.volume1Day?.value,t=l.totalLiquidity?.value;return typeof l.feeTier!="number"||!e||!t?void 0:l.feeTier/1e4*e/t*365}var Q=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??Cr).replace(/\/$/,"")}async getExploreStats(e){let t=In(e?.chain),n=e?.multichain===!0,r=encodeURIComponent(JSON.stringify({chainId:String(t),multichain:n})),o=`${this.baseUrl}${Ur}?connect=v1&encoding=json&message=${r}`,s="Unknown error";for(let a=1;a<=Te;a++)try{let i=await fetch(o,{headers:{accept:"*/*",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"}});if(!i.ok)throw new Error(`HTTP ${i.status}`);return{success:!0,data:await i.json()}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<Te&&await an(sn)}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:co(o.data.stats,t).map(i=>({...i,apr:mo(i),fee24hUsd:i.volume1Day?.value&&i.feeTier?i.feeTier/1e4*i.volume1Day.value:void 0})).sort((i,c)=>uo(c,r)-uo(i,r)).slice(0,n)}}async getV3PoolDetail(e){let t=e.address?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V3Pool",query:Nr,variables:{chain:Dn(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:Ir,variables:{chain:Dn(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:Dr,variables:{chain:Dn(e.chain),poolId:t},pickField:"v4Pool"}):{success:!1,error:"V4 poolId is required"}}async fetchPoolDetailGraphQL(e){let t=`${this.baseUrl}${Rr}`,n=JSON.stringify({operationName:e.operationName,variables:e.variables,query:e.query}),r="Unknown error";for(let o=1;o<=Te;o++)try{let s=await fetch(t,{method:"POST",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"},body:n});if(!s.ok)throw new Error(`HTTP ${s.status}`);let a=await s.json();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<Te&&await an(sn)}return{success:!1,error:r}}async searchTokens(e){let t=e.query?.trim();if(!t)return{success:!1,error:"Search query is required"};if(D(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=In(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}${lo}`,a=JSON.stringify({searchQuery:t,chainIds:[n],searchType:"TOKEN",page:r,size:o}),i="Unknown error";for(let c=1;c<=Te;c++)try{let u=await fetch(s,{method:"POST",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"},body:a});if(!u.ok)throw new Error(`HTTP ${u.status}`);return{success:!0,data:(await u.json()).tokens??[]}}catch(u){i=u instanceof Error?u.message:"Unknown error",c<Te&&await an(sn)}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=In(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}${lo}`,c=JSON.stringify({searchQuery:t,chainIds:[n],searchType:"POOL",page:r,size:o}),[u,d]=await Promise.allSettled([this.fetchSearchPools(i,c),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(g=>g.protocolVersion===s)),a!=null&&(m=m.filter(g=>g.feeTier===a));let h=new Map;for(let g of m)g.id&&h.set(g.id.toLowerCase(),g);if(d.status==="fulfilled"&&d.value.success&&d.value.data){let g=d.value.data.stats,y=s==="ALL"?["V2","V3","V4"]:[s];for(let k of y)for(let w of co(g,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(),x=(w.token1?.name??"").toLowerCase();if(!p.some(C=>b.includes(C)||T.includes(C)||P.includes(C)||x.includes(C)))continue;let I=mo(w),S=w.id.toLowerCase(),_=h.get(S);_?h.set(S,{..._,...I!=null?{apr:I}:{},...w.totalLiquidity?.value!=null?{tvlUsd:w.totalLiquidity.value}:{},...w.volume1Day?.value!=null?{volume1DayUsd:w.volume1Day.value}:{},...w.txCount!=null?{txCount:w.txCount}:{}}):h.set(S,{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,...I!=null?{apr:I}:{},...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((g,y)=>this.matchScore(y,p)-this.matchScore(g,p))}}async fetchSearchPools(e,t){let n="Unknown error";for(let r=1;r<=Te;r++)try{let o=await fetch(e,{method:"POST",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"},body:t});if(!o.ok)throw new Error(`HTTP ${o.status}`);return{success:!0,data:(await o.json()).pools??[]}}catch(o){n=o instanceof Error?o.message:"Unknown error",r<Te&&await an(sn)}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 Mr=["tvl","volume1Day","volume1Week","volume30Day","txCount","apr"],Br=["V2","V3","V4"],ot=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 Q(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 c=(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:c.length,pools:c}}parseProtocolVersion(e){if(typeof e=="string"){let t=e.trim().toUpperCase();if(Br.includes(t))return t}return"V3"}parseSortKey(e){if(typeof e=="string"){let t=e.trim();if(Mr.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 Or=365,$r=/^0x[a-fA-F0-9]{40}$/,rt=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 Q(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 c=i.data??[],d=c.filter(m=>this.matchesFilter(m,r,o,s,"ordered")),p=!1;if(d.length===0&&o){let m=c.filter(h=>this.matchesFilter(h,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 chain ${n}. Verify the symbols and fee tier with the user.`};if(d.length===1){let m=d[0];if(!m.id||!$r.test(m.id))return{error:"Resolved pool has no valid address"};let h=await this.service.getV3PoolDetail({address:m.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:p?"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: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 c=n.toLowerCase();return o==="reversed"?this.symbolIncludes(s,c)&&this.symbolIncludes(a,i):this.symbolIncludes(s,i)&&this.symbolIncludes(a,c)}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*Or*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 qr=["V2","V3","V4","ALL"],st=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 Q(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,c=await this.service.searchPools({query:n,chain:r,page:o,size:s,protocolVersion:a,feeTier:i});if(!c.success)return{error:c.error||"Failed to search pools"};let u=(c.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(qr.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 Fr=365,ho=/^0x[a-fA-F0-9]{40}$/,at=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 Q(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 chain ${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"?ho.test(e.id)?await this.service.getV2PoolDetail({address:e.id,chain:t}):{success:!1,error:"Resolved V2 pair has no valid address"}:ho.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 c=(i.token0?.address??"").toLowerCase(),u=(i.token1?.address??"").toLowerCase();return c===o||u===o})].sort((i,c)=>(c.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*Fr*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 Ln=require("js-sha3"),go=2n**96n;function fo(l,e,t){let n=l*l,r=go*go,o=n*10n**18n/r;return Number(o)/1e18*Math.pow(10,e-t)}function oe(l,e,t){return Math.pow(1.0001,l)*Math.pow(10,e-t)}function Pe(l,e,t){if(l<=0)throw new Error("priceToTick: price must be > 0");let n=l/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 pe(l,e,t="nearest"){let n=Math.abs(l-Math.round(l))<1e-6?Math.round(l):l,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 it=-887272,lt=887272;function he(l){return l<it?it:l>lt?lt:l}var Wr=/^0x[0-9a-f]+$/i;function te(l){return l.startsWith("0x")||l.startsWith("0X")?l.slice(2):l}function ln(l){let e=te(l).toLowerCase();if(e.length>64)throw new Error(`pad32: input too long (${e.length})`);return e.padStart(64,"0")}function cn(l){if(!Wr.test(l)||te(l).length!==40)throw new Error(`encodeAddress: invalid address ${l}`);return ln(l.toLowerCase())}function re(l,e=256){let t=typeof l=="bigint"?l:BigInt(l);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 ln(n)}function Mn(l){let e=typeof l=="bigint"?l:BigInt(l);if(e>=0n)return ln(e.toString(16));let t=1n<<256n;return ln((t+e).toString(16))}function yo(l){return"0x"+(0,Ln.keccak_256)(Vr(l))}function Vr(l){let e=te(l),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 ge(l){return"0x"+(0,Ln.keccak_256)(new TextEncoder().encode(l)).slice(0,8)}function Ft(l,e){let t=l.substring(e,e+64);return t?BigInt("0x"+t):0n}function Bn(l,e){let t=l.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 On(l,e){return"0x"+l.substring(e+24,e+64).toLowerCase()}var ve=class{config;constructor(e){this.config=e}hasIntegratedApproval(){return!1}};var Hr="https://exchange-api.keyring.app/admin/setting?configs=others",Gr="https://api.coinpool.app/config/bridgeProviderByChain",Kr="https://coinpool-api-op.bacoor-test001.xyz/config/bridgeProviderByChain";function jr(l){let e=null,t=null;return async()=>e||t||(t=(async()=>{try{return e=await l(),e}catch{return null}finally{t=null}})(),t)}async function wo(l){return await(await fetch(l,{method:"GET",headers:{Accept:"application/json"}})).json()}var Yr=jr(async()=>(await wo(Hr))?.others??null),un={prod:null,dev:null},Wt={prod:null,dev:null};async function zr(l){let e=l?"prod":"dev";if(un[e])return un[e];if(Wt[e])return Wt[e];let t=l?Gr:Kr;return Wt[e]=(async()=>{try{let n=await wo(t);return un[e]=n?.data??null,un[e]}catch{return null}finally{Wt[e]=null}})(),Wt[e]}function Qr(l){if(!l)return null;try{let e=JSON.parse(l);return e&&typeof e=="object"?e:null}catch{return null}}function bo(l){if(l==null)return 0;let e=typeof l=="number"?l:Number(l);return Number.isFinite(e)?e:0}function Xr(l){return l==="debridge"||l==="relay"}async function dn(l,e){let n=(await zr(e))?.[String(l)];return Xr(n)?n:null}async function ct(l,e){let t=await Yr();if(!t)return null;let n,r;if(l==="debridge")n=Qr(t.affiliateRecipient)?.[String(e)],r=bo(t.AFFILIATE_FEE_PERENT);else if(l==="relay")n=t.RELAY_AFFILIATE_FEE_RECIPIENT,r=bo(t.RELAY_AFFILIATE_FEE_PERCENT);else return null;return!n||r<=0?null:{affiliateFeeRecipient:n,affiliateFeePercent:r}}var ne={"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 Jr(l){return ne[l.toLowerCase()]}var ko={};function $n(l){let e={};if(l)for(let[t,n]of Object.entries(l))typeof n=="string"&&n.trim()&&(e[t.toLowerCase()]=n.trim());ko=e}function ut(l){let e=l.toLowerCase();return ko[e]??ne[e]}async function fe(l,e,t){for(let r=0;r<=5;r++){let o=await fetch(l,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:Date.now(),method:e,params:t})});if(o.status===429&&r<5){await new Promise(a=>setTimeout(a,2**r*500));continue}if(!o.ok)throw new Error(`RPC ${e} HTTP ${o.status}`);let s=await o.json();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 To(l,e,t){return fe(l,"eth_call",[{to:e,data:t},"latest"])}async function qn(l,e,t){let n=ut(l);if(!n)return null;try{return await To(n,e,t)}catch{return null}}var Zr="https://dln.debridge.finance/v1.0",es="0x0000000000000000000000000000000000000000",dt=class extends ve{apiBaseUrl;accessToken;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??Zr,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:c,isCrossChain:u}=e,d=u??t!==o,p=await ct(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 g=await this.get(`${this.apiBaseUrl}/dln/order/create-tx`,f);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,estimation:g.estimation,isCrossChain:!0,srcChainId:t,dstChainId:o,raw:g}}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),c!==void 0&&(m.slippage=c);let h=await this.get(`${this.apiBaseUrl}/chain/transaction`,m);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(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()===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 qn(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(await fetch(r,{method:"GET",headers:{Accept:"application/json"}})).json()}};var ts="https://api.relay.link";function vo(l){return l===7565164||l==="7565164"?792703809:l}var mt=class extends ve{apiBaseUrl;apiKey;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??ts,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:c}=e,u=await ct(this.getProviderName(),t),d={user:a||i,originChainId:vo(t),destinationChainId:vo(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 c=="number"&&Number.isFinite(c)&&(d.slippageTolerance=Math.floor(c*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"),h=p.steps?.find(k=>k.id==="swap"||k.id==="deposit"),f=h?.items?.[0]?.data,g=Array.isArray(f?.instructions);return{success:!0,provider:"relay",tx:f?g?{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:h?.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={method:t,headers:r};if(t==="GET"&&n){let i=Object.entries(n).map(([c,u])=>`${encodeURIComponent(c)}=${encodeURIComponent(String(u))}`).join("&");o=`${o}?${i}`}else t==="POST"&&n&&(s.body=JSON.stringify(n));let a=await fetch(o,s);if(!a.ok){let i=await a.json().catch(()=>({}));throw new Error(i.message??`HTTP ${a.status}`)}return await a.json()}};var mn={debridge:{apiBaseUrl:"https://dln.debridge.finance/v1.0",accessToken:"d6c45897b8f6"},relay:{apiBaseUrl:"https://api.relay.link"}},Vt="debridge";function ns(l){if(l==null)return null;let e=typeof l=="number"?l:Number(l);return Number.isFinite(e)?e:null}var Ae=class{providerConfig;isProduction;cache=new Map;constructor(e=mn,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=ns(e);return t===null?Vt:await dn(t,this.isProduction)??Vt}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 dt(t);case"relay":return new mt(t);default:{let n=e;throw new Error(`Unknown swap provider: ${String(n)}`)}}}},ce=new Ae;var os="https://api.coinpool.app/config/addresses",rs="https://coinpool-api-op.bacoor-test001.xyz/config/addresses",pn={prod:null,dev:null},Ht={prod:null,dev:null};async function ss(l){let e=l?"prod":"dev";if(pn[e])return pn[e];if(Ht[e])return Ht[e];let t=l?os:rs;return Ht[e]=(async()=>{try{let r=await(await fetch(t,{method:"GET",headers:{Accept:"application/json"}})).json();return pn[e]=r?.data??null,pn[e]}catch{return null}finally{Ht[e]=null}})(),Ht[e]}function as(l){let e=l.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 So(l,e,t){let n=await ss(t);if(!n)return;let r=as(e);if(!r)return;let o=n[l]?.[r];return Array.isArray(o)?o[0]:typeof o=="string"?o:void 0}async function hn(l,e){return So("createPoolProxyV3",l,e)}async function gn(l,e){return So("nftPositionUniswap",l,e)}var L="0x0000000000000000000000000000000000000000",is={"0x1":{hexId:"0x1",name:"Ethereum",defaultRpc:ne["0x1"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0xa":{hexId:"0xa",name:"Optimism",defaultRpc:ne["0xa"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0x38":{hexId:"0x38",name:"BNB Smart Chain",defaultRpc:ne["0x38"],native:{symbol:"BNB",name:"BNB",decimals:18,coingeckoId:"binancecoin"},defaultMintGasLimit:12e5},"0x89":{hexId:"0x89",name:"Polygon",defaultRpc:ne["0x89"],native:{symbol:"MATIC",name:"Polygon",decimals:18,coingeckoId:"matic-network"},defaultMintGasLimit:12e5},"0x2105":{hexId:"0x2105",name:"Base",defaultRpc:ne["0x2105"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0xa4b1":{hexId:"0xa4b1",name:"Arbitrum One",defaultRpc:ne["0xa4b1"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:22e5},"0xa86a":{hexId:"0xa86a",name:"Avalanche",defaultRpc:ne["0xa86a"],native:{symbol:"AVAX",name:"Avalanche",decimals:18,coingeckoId:"avalanche-2"},defaultMintGasLimit:12e5},"0xe708":{hexId:"0xe708",name:"Linea",defaultRpc:ne["0xe708"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5}};function z(l){return is[l.toLowerCase()]}function pt(l){return z(l)?.native}var xo="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";function Fn(l){let e=l.stableTicks??3,t=l.widePercent??{down:50,up:100},n=oe(l.currentTick,l.token0Decimals,l.token1Decimals),r=he(pe(l.currentTick-e*l.tickSpacing,l.tickSpacing,"down")),o=he(pe(l.currentTick+e*l.tickSpacing,l.tickSpacing,"up")),s=n*(1-t.down/100),a=n*(1+t.up/100),i=Pe(s,l.token0Decimals,l.token1Decimals),c=Pe(a,l.token0Decimals,l.token1Decimals),u=he(pe(i,l.tickSpacing,"down")),d=he(pe(c,l.tickSpacing,"up")),p=(m,h,f,g,y)=>{let k=oe(g,l.token0Decimals,l.token1Decimals),w=oe(y,l.token0Decimals,l.token1Decimals);return{id:m,label:h,description:f,tickLower:g,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 ls=ge("slot0()"),cs=ge("liquidity()"),us=ge("tickSpacing()"),ds=ge("fee()"),ms=ge("token0()"),ps=ge("token1()"),hs=ge("decimals()"),_e=class{swapFactory;isProduction;constructor(e){this.isProduction=e?.isProduction??!0,this.swapFactory=e?.swapFactory??(this.isProduction?ce:new Ae(void 0,!1))}getRpcUrl(e){let t=ut(e);if(t)return t;let n=z(e);if(!n)throw new Error(`Unsupported chain: ${e}`);return n.defaultRpc}async getGatewayAddress(e){return hn(e,this.isProduction)}async getPositionManagerAddress(e){return gn(e,this.isProduction)}async ethCall(e,t,n){return fe(this.getRpcUrl(e),"eth_call",[{to:t,data:n},"latest"])}async getNativeBalance(e,t){let n=await fe(this.getRpcUrl(e),"eth_getBalance",[t,"latest"]);return BigInt(n)}async getGasPrice(e){let t=await fe(this.getRpcUrl(e),"eth_gasPrice",[]);return BigInt(t)}async getTransactionReceipt(e,t){return await fe(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()!==xo||"0x"+s[1].slice(-40).toLowerCase()!==L))try{return BigInt(s[3]).toString()}catch{return null}}return null}async estimateGasReserveWei(e){let t=z(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,ls),this.ethCall(e,t,cs),this.ethCall(e,t,us),this.ethCall(e,t,ds),this.ethCall(e,t,ms),this.ethCall(e,t,ps)]),c=te(n),u=Ft(c,0),d=Bn(c,64),p=Ft(te(r),0),m=Number(Bn(te(o),0)),h=Number(Ft(te(s),0)),f=On(te(a),0),g=On(te(i),0),[y,k]=await Promise.all([this.readErc20Decimals(e,f),this.readErc20Decimals(e,g)]),w=fo(u,y,k);return{poolAddress:t,chainId:e,fee:h,tickSpacing:m,liquidity:p,sqrtPriceX96:u,currentTick:d,token0:f,token1:g,currentPrice:w}}async readErc20Decimals(e,t){if(t.toLowerCase()===L)return 18;let n=await this.ethCall(e,t,hs);return Number(Ft(te(n),0))}async getNativePriceUsd(e){let t=z(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 fetch(n);if(!r.ok)return null;let s=(await r.json())[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,c=Number(n.sqrtPriceX96)/i,u=Math.pow(1.0001,r/2),d=Math.pow(1.0001,o/2);if(c<=u)return{ratio0:1,ratio1:0,amountInFor0Wei:t,amountInFor1Wei:0n};if(c>=d)return{ratio0:0,ratio1:1,amountInFor0Wei:0n,amountInFor1Wei:t};let p=1/c-1/d,m=c-u,h=c*c,f=p*h,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,x=t-P;return{ratio0:k,ratio1:w,amountInFor0Wei:P,amountInFor1Wei:x}}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=ge("mint((address,uint256,address,uint256)[2],(address,bytes,uint256)[2],(uint24,uint256,uint256,int24,int24))"),n=e.paymentInfo.map(h=>cn(h.tokenIn)+re(h.tokenInAmount)+cn(h.tokenOut)+re(h.tokenOutAmount)).join(""),o=e.exchanges.map(h=>{let f=te(h.data||"0x"),g=f.length/2,y=re(g),k=(64-f.length%64)%64,w=f+"0".repeat(k);return{head:cn(h.to)+re(96)+re(h.value),body:y+w}}).map(h=>h.head+h.body),s=o.length*32,a=[];for(let h of o)a.push(re(s)),s+=h.length/2;let i=a.join("")+o.join(""),c=re(e.fee,24)+re(e.amount0Min)+re(e.amount1Min)+Mn(e.tickLower)+Mn(e.tickUpper),p=256+32+160,m=n+re(p)+c+i;return t+m}_keccak(e){return yo(e)}};var gs=/^0x[a-fA-F0-9]{40}$/,ht=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 Q(e),this.pool=new _e(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,c=typeof e.requestedNonNativeToken=="string"&&e.requestedNonNativeToken.trim()?e.requestedNonNativeToken.trim():null,u=c&&a!=null&&(c.toLowerCase()===r.toLowerCase()||c.toLowerCase()===o.toLowerCase())?null:c,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 h=z(n);if(!h)return{error:"unsupported_chain",chain:n,_instructions:`Chain ${n} is not supported by the add-liquidity flow.`};let f=pt(n);if(u&&f&&u.toLowerCase()!==f.symbol.toLowerCase())return{error:"unsupported_input_token",requestedToken:u,nativeSymbol:f.symbol,chainName:h.name,_instructions:`The user asked to add liquidity using ${u}, but this app only supports the chain's native coin (${f.symbol} on ${h.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 g=await this.pool.getGatewayAddress(n);if(!g)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(M=>this.matchesPairExact(M,r,o,s)),T=b?[]:w.filter(M=>this.matchesPair(M,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(M=>({address:M.id,token0Symbol:M.token0?.symbol,token1Symbol:M.token1?.symbol,feeTier:M.feeTier,feeTierPercent:M.feeTier!=null?M.feeTier/1e4:void 0,volume24hUsd:M.volume1DayUsd??M.volumeUsd24hr,tvlUsd:M.tvlUsd,...M.apr?{apr:M.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||!gs.test(P.id))return{error:"invalid_pool_address"};let x=await this.pool.readPoolOnchain(n,P.id),[A,I,S]=await Promise.all([this.pool.getNativeBalance(n,y),this.pool.estimateGasReserveWei(n),this.pool.getNativePriceUsd(n)]),_=A>I?A-I:0n,C=pt(n),q=Number(_)/10**C.decimals,K=Number(A)/10**C.decimals,H=S!=null?q*S:null,[ae,G]=await Promise.all([this.pool.readErc20Decimals(n,x.token0),this.pool.readErc20Decimals(n,x.token1)]),be={min:oe(it,ae,G),max:oe(lt,ae,G)},we=null;A===0n?we="no_balance":H!=null&&H<this.minProvideUsd&&(we="insufficient_balance");let ke=P.token0,Ce=P.token1,_n=[25,50,75,100].map(M=>({percent:M,amountWei:(_*BigInt(M)/100n).toString(),amount:q*(M/100)})),Ue=null,ie=null;if(a!=null){ie=a;let M=BigInt(Math.floor(ie*10**C.decimals));M>0n&&(Ue=M.toString())}else if(i!=null&&S!=null&&S>0){ie=i/S;let M=BigInt(Math.floor(ie*10**C.decimals));M>0n&&(Ue=M.toString())}let Re={address:L,isNative:!0,symbol:C.symbol,name:C.name,decimals:C.decimals,priceUsd:S,balanceWei:A.toString(),balance:K,gasReserveWei:I.toString(),spendableWei:_.toString(),spendable:q,spendableUsd:H,quickRates:_n,minProvideUsd:this.minProvideUsd,warning:we,warningMessage:we==="no_balance"?`You have no ${C.symbol} on ${h.name}. Top up to add liquidity.`:we==="insufficient_balance"?`Spendable ${C.symbol} is below the $${this.minProvideUsd} minimum.`:null,prefillAmount:ie,prefillAmountWei:Ue},Vn=this.computePrefillRange({explicitMinPrice:p,explicitMaxPrice:m,rangeStrategy:d,currentTick:x.currentTick,tickSpacing:x.tickSpacing,token0Decimals:ae,token1Decimals:G,priceBounds:be});return{ui:{component:"AddLiquidityForm",props:{chain:{hexId:n,name:h.name},pool:{address:x.poolAddress,fee:x.fee,feePercent:x.fee/1e4,tickSpacing:x.tickSpacing,currentTick:x.currentTick,currentPrice:x.currentPrice,sqrtPriceX96:x.sqrtPriceX96.toString(),token0:{address:x.token0,symbol:ke?.symbol,name:ke?.name,logo:ke?.logo},token1:{address:x.token1,symbol:Ce?.symbol,name:Ce?.name,logo:Ce?.logo}},inputToken:Re,priceBounds:be,prefillRange:Vn,gatewayAddress:g}},summary:this.buildSummary({chainName:h.name,pair:`${ke?.symbol??r}/${Ce?.symbol??o}`,feePercent:x.fee/1e4,warning:we,minProvideUsd:this.minProvideUsd,nativeSymbol:C.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(),c=this.symMatch(o,a)&&this.symMatch(s,i),u=this.symMatch(o,i)&&this.symMatch(s,a);return c||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=Fn({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 Po=[{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"}]}],fn=[{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 gt=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 _e(e?.pool),this.defaultSlippageBps=e?.defaultSlippageBps??"auto",this.mintMinPercent=e?.mintMinPercent??80,this.pantograph=new Y}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 c=z(r);if(!c)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=pt(r),m=BigInt(Math.floor(a*10**p.decimals));if(m<=0n)return{error:"invalid_native_amount",_instructions:"nativeAmount must be > 0."};let h=await this.pool.readPoolOnchain(r,n),[f,g]=await Promise.all([this.pool.readErc20Decimals(r,h.token0),this.pool.readErc20Decimals(r,h.token1)]),y=Pe(o,f,g),k=Pe(s,f,g),w=he(pe(y,h.tickSpacing,"down")),b=he(pe(k,h.tickSpacing,"up"));if(w>=b||w<it||b>lt)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:oe(w,f,g),maxPrice:oe(b,f,g)},[P,x,A]=await Promise.all([this.pool.getNativeBalance(r,d),this.pool.estimateGasReserveWei(r),this.pool.getNativePriceUsd(r)]),I=m+x;if(P<I)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:I.toString(),gasReserveWei:x.toString()};let S=this.pool.splitNativeAmountForRange({nativeAmountWei:m,pool:h,tickLower:w,tickUpper:b,token0Decimals:f,token1Decimals:g}),_=h.token0.toLowerCase()===L,C=h.token1.toLowerCase()===L,q=[];if(_||S.amountInFor0Wei===0n)q.push({tokenIn:L,tokenOut:h.token0,amountIn:S.amountInFor0Wei,tx:{to:L,data:"0x",value:"0"},minAmountOut:_?S.amountInFor0Wei:0n});else{let W=await this.pool.fetchSwapLeg({senderAddress:d,chainId:r,tokenIn:L,tokenInAmount:S.amountInFor0Wei.toString(),tokenOut:h.token0,tokenOutRecipient:u,slippage:typeof i=="number"?i:"auto"});q.push({tokenIn:L,tokenOut:h.token0,amountIn:S.amountInFor0Wei,tx:{to:W.to,data:W.data,value:W.value},minAmountOut:BigInt(W.minAmountOut||"0")})}if(C||S.amountInFor1Wei===0n)q.push({tokenIn:L,tokenOut:h.token1,amountIn:S.amountInFor1Wei,tx:{to:L,data:"0x",value:"0"},minAmountOut:C?S.amountInFor1Wei:0n});else{let W=await this.pool.fetchSwapLeg({senderAddress:d,chainId:r,tokenIn:L,tokenInAmount:S.amountInFor1Wei.toString(),tokenOut:h.token1,tokenOutRecipient:u,slippage:typeof i=="number"?i:"auto"});q.push({tokenIn:L,tokenOut:h.token1,amountIn:S.amountInFor1Wei,tx:{to:W.to,data:W.data,value:W.value},minAmountOut:BigInt(W.minAmountOut||"0")})}let K=W=>W*BigInt(this.mintMinPercent)/100n,H=K(q[0].minAmountOut),ae=K(q[1].minAmountOut),G=q.map(W=>({tokenIn:W.tokenIn,tokenInAmount:BigInt(W.amountIn),tokenOut:W.tokenOut,tokenOutAmount:BigInt(W.minAmountOut)})),be=q.map(W=>({to:W.tx.to,data:W.tx.data,value:BigInt(W.tx.value||"0")}));console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ paymentInfo:",G),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ exchangesInfo:",be),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ args:",{args:[G,be,{fee:Number(h.fee),amount0Min:BigInt(H),amount1Min:BigInt(ae),tickLower:w,tickUpper:b}]});let we=De({abi:Po,functionName:"mint",args:[G,be,{fee:Number(h.fee),amount0Min:BigInt(H),amount1Min:BigInt(ae),tickLower:w,tickUpper:b}]}),ke=a,Ce={chainId:r,to:u,data:we,value:m.toString(),from:d},_n={nativeIn:ke,nativeInUsd:A!=null?ke*A:null,ratio:{token0Percent:S.ratio0,token1Percent:S.ratio1},expectedToken0:Number(q[0].minAmountOut)/10**f,expectedToken1:Number(q[1].minAmountOut)/10**g,amount0Min:H.toString(),amount1Min:ae.toString(),slippageBps:i},Ue=await this.pantograph.getTokensMetadata([h.token0,h.token1],r),ie=Ue[h.token0],Re=Ue[h.token1];return{ui:{component:"ConfirmAddLiquidityTx",props:{chain:{hexId:r,name:c.name},unsignedTx:Ce,summary:_n,pool:{...h,feePercent:h.fee/1e4,token0:{address:h.token0,symbol:ie.symbol,name:ie.name,decimals:ie.decimals,logo:ie.icon_image},token1:{address:h.token1,symbol:Re.symbol,name:Re.name,decimals:Re.decimals,logo:Re.icon_image}},range:T,gatewayAddress:u}},summary:`Ready to add ${ke} ${p.symbol} to pool ${h.token0}/${h.token1} ${h.fee/1e4}% on ${c.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 Ao=365,fs=1e3,ys=/^0x[a-fA-F0-9]{40}$/;function bs(l,e){return{depositUsd:l,aprPercent:e*100,dailyUsd:l*e/Ao,weeklyUsd:l*e/52,monthlyUsd:l*e/12,yearlyUsd:l*e}}var ft=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):
|
|
105
|
+
${po}`,Lr={"0x1":1,"0xa":10,"0x38":56,"0x89":137,"0x2105":8453,"0xa4b1":42161,"0xa86a":43114,"0xe708":59144};function an(c){return new Promise(e=>setTimeout(e,c))}function In(c){if(!c)return 1;let e=c.toLowerCase();return Lr[e]??1}function Dn(c){if(!c)return"ETHEREUM";let e=c.toLowerCase();return Er[e]??"ETHEREUM"}function co(c,e){if(!c)return[];switch(e){case"V2":return c.poolStatsV2??[];case"V3":return c.poolStatsV3??[];case"V4":return c.poolStatsV4??[]}}function uo(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 mo(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 Q=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??Cr).replace(/\/$/,"")}async getExploreStats(e){let t=In(e?.chain),n=e?.multichain===!0,r=encodeURIComponent(JSON.stringify({chainId:String(t),multichain:n})),o=`${this.baseUrl}${Ur}?connect=v1&encoding=json&message=${r}`,s="Unknown error";for(let a=1;a<=Te;a++)try{let i=await fetch(o,{headers:{accept:"*/*",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"}});if(!i.ok)throw new Error(`HTTP ${i.status}`);return{success:!0,data:await i.json()}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<Te&&await an(sn)}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:co(o.data.stats,t).map(i=>({...i,apr:mo(i),fee24hUsd:i.volume1Day?.value&&i.feeTier?i.feeTier/1e4*i.volume1Day.value:void 0})).sort((i,l)=>uo(l,r)-uo(i,r)).slice(0,n)}}async getV3PoolDetail(e){let t=e.address?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V3Pool",query:Nr,variables:{chain:Dn(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:Ir,variables:{chain:Dn(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:Dr,variables:{chain:Dn(e.chain),poolId:t},pickField:"v4Pool"}):{success:!1,error:"V4 poolId is required"}}async fetchPoolDetailGraphQL(e){let t=`${this.baseUrl}${Rr}`,n=JSON.stringify({operationName:e.operationName,variables:e.variables,query:e.query}),r="Unknown error";for(let o=1;o<=Te;o++)try{let s=await fetch(t,{method:"POST",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"},body:n});if(!s.ok)throw new Error(`HTTP ${s.status}`);let a=await s.json();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<Te&&await an(sn)}return{success:!1,error:r}}async searchTokens(e){let t=e.query?.trim();if(!t)return{success:!1,error:"Search query is required"};if(D(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=In(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}${lo}`,a=JSON.stringify({searchQuery:t,chainIds:[n],searchType:"TOKEN",page:r,size:o}),i="Unknown error";for(let l=1;l<=Te;l++)try{let u=await fetch(s,{method:"POST",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"},body:a});if(!u.ok)throw new Error(`HTTP ${u.status}`);return{success:!0,data:(await u.json()).tokens??[]}}catch(u){i=u instanceof Error?u.message:"Unknown error",l<Te&&await an(sn)}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=In(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}${lo}`,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(g=>g.protocolVersion===s)),a!=null&&(p=p.filter(g=>g.feeTier===a));let h=new Map;for(let g of p)g.id&&h.set(g.id.toLowerCase(),g);if(d.status==="fulfilled"&&d.value.success&&d.value.data){let g=d.value.data.stats,y=s==="ALL"?["V2","V3","V4"]:[s];for(let k of y)for(let w of co(g,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(),x=(w.token1?.name??"").toLowerCase();if(!m.some(C=>b.includes(C)||T.includes(C)||P.includes(C)||x.includes(C)))continue;let I=mo(w),S=w.id.toLowerCase(),_=h.get(S);_?h.set(S,{..._,...I!=null?{apr:I}:{},...w.totalLiquidity?.value!=null?{tvlUsd:w.totalLiquidity.value}:{},...w.volume1Day?.value!=null?{volume1DayUsd:w.volume1Day.value}:{},...w.txCount!=null?{txCount:w.txCount}:{}}):h.set(S,{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,...I!=null?{apr:I}:{},...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((g,y)=>this.matchScore(y,m)-this.matchScore(g,m))}}async fetchSearchPools(e,t){let n="Unknown error";for(let r=1;r<=Te;r++)try{let o=await fetch(e,{method:"POST",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"},body:t});if(!o.ok)throw new Error(`HTTP ${o.status}`);return{success:!0,data:(await o.json()).pools??[]}}catch(o){n=o instanceof Error?o.message:"Unknown error",r<Te&&await an(sn)}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 Mr=["tvl","volume1Day","volume1Week","volume30Day","txCount","apr"],Br=["V2","V3","V4"],ot=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 Q(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(Br.includes(t))return t}return"V3"}parseSortKey(e){if(typeof e=="string"){let t=e.trim();if(Mr.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 Or=365,$r=/^0x[a-fA-F0-9]{40}$/,rt=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 Q(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 chain ${n}. Verify the symbols and fee tier with the user.`};if(d.length===1){let p=d[0];if(!p.id||!$r.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*Or*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 qr=["V2","V3","V4","ALL"],st=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 Q(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(qr.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 Fr=365,ho=/^0x[a-fA-F0-9]{40}$/,at=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 Q(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 chain ${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"?ho.test(e.id)?await this.service.getV2PoolDetail({address:e.id,chain:t}):{success:!1,error:"Resolved V2 pair has no valid address"}:ho.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*Fr*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 Ln=require("js-sha3"),go=2n**96n;function fo(c,e,t){let n=c*c,r=go*go,o=n*10n**18n/r;return Number(o)/1e18*Math.pow(10,e-t)}function oe(c,e,t){return Math.pow(1.0001,c)*Math.pow(10,e-t)}function Pe(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 pe(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 it=-887272,lt=887272;function he(c){return c<it?it:c>lt?lt:c}var Wr=/^0x[0-9a-f]+$/i;function te(c){return c.startsWith("0x")||c.startsWith("0X")?c.slice(2):c}function ln(c){let e=te(c).toLowerCase();if(e.length>64)throw new Error(`pad32: input too long (${e.length})`);return e.padStart(64,"0")}function cn(c){if(!Wr.test(c)||te(c).length!==40)throw new Error(`encodeAddress: invalid address ${c}`);return ln(c.toLowerCase())}function re(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 ln(n)}function Mn(c){let e=typeof c=="bigint"?c:BigInt(c);if(e>=0n)return ln(e.toString(16));let t=1n<<256n;return ln((t+e).toString(16))}function yo(c){return"0x"+(0,Ln.keccak_256)(Vr(c))}function Vr(c){let e=te(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 ge(c){return"0x"+(0,Ln.keccak_256)(new TextEncoder().encode(c)).slice(0,8)}function Ft(c,e){let t=c.substring(e,e+64);return t?BigInt("0x"+t):0n}function Bn(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 On(c,e){return"0x"+c.substring(e+24,e+64).toLowerCase()}var ve=class{config;constructor(e){this.config=e}hasIntegratedApproval(){return!1}};var Hr="https://exchange-api.keyring.app/admin/setting?configs=others",Gr="https://api.coinpool.app/config/bridgeProviderByChain",Kr="https://coinpool-api-op.bacoor-test001.xyz/config/bridgeProviderByChain";function jr(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(await fetch(c,{method:"GET",headers:{Accept:"application/json"}})).json()}var Yr=jr(async()=>(await wo(Hr))?.others??null),un={prod:null,dev:null},Wt={prod:null,dev:null};async function zr(c){let e=c?"prod":"dev";if(un[e])return un[e];if(Wt[e])return Wt[e];let t=c?Gr:Kr;return Wt[e]=(async()=>{try{let n=await wo(t);return un[e]=n?.data??null,un[e]}catch{return null}finally{Wt[e]=null}})(),Wt[e]}function Qr(c){if(!c)return null;try{let e=JSON.parse(c);return e&&typeof e=="object"?e:null}catch{return null}}function bo(c){if(c==null)return 0;let e=typeof c=="number"?c:Number(c);return Number.isFinite(e)?e:0}function Xr(c){return c==="debridge"||c==="relay"}async function dn(c,e){let n=(await zr(e))?.[String(c)];return Xr(n)?n:null}async function ct(c,e){let t=await Yr();if(!t)return null;let n,r;if(c==="debridge")n=Qr(t.affiliateRecipient)?.[String(e)],r=bo(t.AFFILIATE_FEE_PERENT);else if(c==="relay")n=t.RELAY_AFFILIATE_FEE_RECIPIENT,r=bo(t.RELAY_AFFILIATE_FEE_PERCENT);else return null;return!n||r<=0?null:{affiliateFeeRecipient:n,affiliateFeePercent:r}}var ne={"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 Jr(c){return ne[c.toLowerCase()]}var ko={};function $n(c){let e={};if(c)for(let[t,n]of Object.entries(c))typeof n=="string"&&n.trim()&&(e[t.toLowerCase()]=n.trim());ko=e}function ut(c){let e=c.toLowerCase();return ko[e]??ne[e]}async function fe(c,e,t){for(let r=0;r<=5;r++){let o=await fetch(c,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:Date.now(),method:e,params:t})});if(o.status===429&&r<5){await new Promise(a=>setTimeout(a,2**r*500));continue}if(!o.ok)throw new Error(`RPC ${e} HTTP ${o.status}`);let s=await o.json();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 To(c,e,t){return fe(c,"eth_call",[{to:e,data:t},"latest"])}async function qn(c,e,t){let n=ut(c);if(!n)return null;try{return await To(n,e,t)}catch{return null}}var Zr="https://dln.debridge.finance/v1.0",es="0x0000000000000000000000000000000000000000",dt=class extends ve{apiBaseUrl;accessToken;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??Zr,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 ct(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};m&&(f.affiliateFeeRecipient=m.affiliateFeeRecipient,f.affiliateFeePercent=m.affiliateFeePercent);let g=await this.get(`${this.apiBaseUrl}/dln/order/create-tx`,f);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,estimation:g.estimation,isCrossChain:!0,srcChainId:t,dstChainId:o,raw:g}}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 qn(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(await fetch(r,{method:"GET",headers:{Accept:"application/json"}})).json()}};var ts="https://api.relay.link";function vo(c){return c===7565164||c==="7565164"?792703809:c}var mt=class extends ve{apiBaseUrl;apiKey;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??ts,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 ct(this.getProviderName(),t),d={user:a||i,originChainId:vo(t),destinationChainId:vo(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"),f=h?.items?.[0]?.data,g=Array.isArray(f?.instructions);return{success:!0,provider:"relay",tx:f?g?{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: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={method:t,headers:r};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.body=JSON.stringify(n));let a=await fetch(o,s);if(!a.ok){let i=await a.json().catch(()=>({}));throw new Error(i.message??`HTTP ${a.status}`)}return await a.json()}};var mn={debridge:{apiBaseUrl:"https://dln.debridge.finance/v1.0",accessToken:"d6c45897b8f6"},relay:{apiBaseUrl:"https://api.relay.link"}},Vt="debridge";function ns(c){if(c==null)return null;let e=typeof c=="number"?c:Number(c);return Number.isFinite(e)?e:null}var Ae=class{providerConfig;isProduction;cache=new Map;constructor(e=mn,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=ns(e);return t===null?Vt:await dn(t,this.isProduction)??Vt}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 dt(t);case"relay":return new mt(t);default:{let n=e;throw new Error(`Unknown swap provider: ${String(n)}`)}}}},ce=new Ae;var os="https://api.coinpool.app/config/addresses",rs="https://coinpool-api-op.bacoor-test001.xyz/config/addresses",pn={prod:null,dev:null},Ht={prod:null,dev:null};async function ss(c){let e=c?"prod":"dev";if(pn[e])return pn[e];if(Ht[e])return Ht[e];let t=c?os:rs;return Ht[e]=(async()=>{try{let r=await(await fetch(t,{method:"GET",headers:{Accept:"application/json"}})).json();return pn[e]=r?.data??null,pn[e]}catch{return null}finally{Ht[e]=null}})(),Ht[e]}function as(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 So(c,e,t){let n=await ss(t);if(!n)return;let r=as(e);if(!r)return;let o=n[c]?.[r];return Array.isArray(o)?o[0]:typeof o=="string"?o:void 0}async function hn(c,e){return So("createPoolProxyV3",c,e)}async function gn(c,e){return So("nftPositionUniswap",c,e)}var L="0x0000000000000000000000000000000000000000",is={"0x1":{hexId:"0x1",name:"Ethereum",defaultRpc:ne["0x1"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0xa":{hexId:"0xa",name:"Optimism",defaultRpc:ne["0xa"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0x38":{hexId:"0x38",name:"BNB Smart Chain",defaultRpc:ne["0x38"],native:{symbol:"BNB",name:"BNB",decimals:18,coingeckoId:"binancecoin"},defaultMintGasLimit:12e5},"0x89":{hexId:"0x89",name:"Polygon",defaultRpc:ne["0x89"],native:{symbol:"MATIC",name:"Polygon",decimals:18,coingeckoId:"matic-network"},defaultMintGasLimit:12e5},"0x2105":{hexId:"0x2105",name:"Base",defaultRpc:ne["0x2105"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0xa4b1":{hexId:"0xa4b1",name:"Arbitrum One",defaultRpc:ne["0xa4b1"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:22e5},"0xa86a":{hexId:"0xa86a",name:"Avalanche",defaultRpc:ne["0xa86a"],native:{symbol:"AVAX",name:"Avalanche",decimals:18,coingeckoId:"avalanche-2"},defaultMintGasLimit:12e5},"0xe708":{hexId:"0xe708",name:"Linea",defaultRpc:ne["0xe708"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5}};function z(c){return is[c.toLowerCase()]}function pt(c){return z(c)?.native}var xo="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";function Fn(c){let e=c.stableTicks??3,t=c.widePercent??{down:50,up:100},n=oe(c.currentTick,c.token0Decimals,c.token1Decimals),r=he(pe(c.currentTick-e*c.tickSpacing,c.tickSpacing,"down")),o=he(pe(c.currentTick+e*c.tickSpacing,c.tickSpacing,"up")),s=n*(1-t.down/100),a=n*(1+t.up/100),i=Pe(s,c.token0Decimals,c.token1Decimals),l=Pe(a,c.token0Decimals,c.token1Decimals),u=he(pe(i,c.tickSpacing,"down")),d=he(pe(l,c.tickSpacing,"up")),m=(p,h,f,g,y)=>{let k=oe(g,c.token0Decimals,c.token1Decimals),w=oe(y,c.token0Decimals,c.token1Decimals);return{id:p,label:h,description:f,tickLower:g,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 ls=ge("slot0()"),cs=ge("liquidity()"),us=ge("tickSpacing()"),ds=ge("fee()"),ms=ge("token0()"),ps=ge("token1()"),hs=ge("decimals()"),_e=class{swapFactory;isProduction;constructor(e){this.isProduction=e?.isProduction??!0,this.swapFactory=e?.swapFactory??(this.isProduction?ce:new Ae(void 0,!1))}getRpcUrl(e){let t=ut(e);if(t)return t;let n=z(e);if(!n)throw new Error(`Unsupported chain: ${e}`);return n.defaultRpc}async getGatewayAddress(e){return hn(e,this.isProduction)}async getPositionManagerAddress(e){return gn(e,this.isProduction)}async ethCall(e,t,n){return fe(this.getRpcUrl(e),"eth_call",[{to:t,data:n},"latest"])}async getNativeBalance(e,t){let n=await fe(this.getRpcUrl(e),"eth_getBalance",[t,"latest"]);return BigInt(n)}async getGasPrice(e){let t=await fe(this.getRpcUrl(e),"eth_gasPrice",[]);return BigInt(t)}async getTransactionReceipt(e,t){return await fe(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()!==xo||"0x"+s[1].slice(-40).toLowerCase()!==L))try{return BigInt(s[3]).toString()}catch{return null}}return null}async estimateGasReserveWei(e){let t=z(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,ls),this.ethCall(e,t,cs),this.ethCall(e,t,us),this.ethCall(e,t,ds),this.ethCall(e,t,ms),this.ethCall(e,t,ps)]),l=te(n),u=Ft(l,0),d=Bn(l,64),m=Ft(te(r),0),p=Number(Bn(te(o),0)),h=Number(Ft(te(s),0)),f=On(te(a),0),g=On(te(i),0),[y,k]=await Promise.all([this.readErc20Decimals(e,f),this.readErc20Decimals(e,g)]),w=fo(u,y,k);return{poolAddress:t,chainId:e,fee:h,tickSpacing:p,liquidity:m,sqrtPriceX96:u,currentTick:d,token0:f,token1:g,currentPrice:w}}async readErc20Decimals(e,t){if(t.toLowerCase()===L)return 18;let n=await this.ethCall(e,t,hs);return Number(Ft(te(n),0))}async getNativePriceUsd(e){let t=z(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 fetch(n);if(!r.ok)return null;let s=(await r.json())[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,f=m*h,y=f+p;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,x=t-P;return{ratio0:k,ratio1:w,amountInFor0Wei:P,amountInFor1Wei:x}}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=ge("mint((address,uint256,address,uint256)[2],(address,bytes,uint256)[2],(uint24,uint256,uint256,int24,int24))"),n=e.paymentInfo.map(h=>cn(h.tokenIn)+re(h.tokenInAmount)+cn(h.tokenOut)+re(h.tokenOutAmount)).join(""),o=e.exchanges.map(h=>{let f=te(h.data||"0x"),g=f.length/2,y=re(g),k=(64-f.length%64)%64,w=f+"0".repeat(k);return{head:cn(h.to)+re(96)+re(h.value),body:y+w}}).map(h=>h.head+h.body),s=o.length*32,a=[];for(let h of o)a.push(re(s)),s+=h.length/2;let i=a.join("")+o.join(""),l=re(e.fee,24)+re(e.amount0Min)+re(e.amount1Min)+Mn(e.tickLower)+Mn(e.tickUpper),m=256+32+160,p=n+re(m)+l+i;return t+p}_keccak(e){return yo(e)}};var gs=/^0x[a-fA-F0-9]{40}$/,ht=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 Q(e),this.pool=new _e(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=z(n);if(!h)return{error:"unsupported_chain",chain:n,_instructions:`Chain ${n} is not supported by the add-liquidity flow.`};let f=pt(n);if(u&&f&&u.toLowerCase()!==f.symbol.toLowerCase())return{error:"unsupported_input_token",requestedToken:u,nativeSymbol:f.symbol,chainName:h.name,_instructions:`The user asked to add liquidity using ${u}, but this app only supports the chain's native coin (${f.symbol} on ${h.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 g=await this.pool.getGatewayAddress(n);if(!g)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(M=>this.matchesPairExact(M,r,o,s)),T=b?[]:w.filter(M=>this.matchesPair(M,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(M=>({address:M.id,token0Symbol:M.token0?.symbol,token1Symbol:M.token1?.symbol,feeTier:M.feeTier,feeTierPercent:M.feeTier!=null?M.feeTier/1e4:void 0,volume24hUsd:M.volume1DayUsd??M.volumeUsd24hr,tvlUsd:M.tvlUsd,...M.apr?{apr:M.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||!gs.test(P.id))return{error:"invalid_pool_address"};let x=await this.pool.readPoolOnchain(n,P.id),[A,I,S]=await Promise.all([this.pool.getNativeBalance(n,y),this.pool.estimateGasReserveWei(n),this.pool.getNativePriceUsd(n)]),_=A>I?A-I:0n,C=pt(n),q=Number(_)/10**C.decimals,K=Number(A)/10**C.decimals,H=S!=null?q*S:null,[ae,G]=await Promise.all([this.pool.readErc20Decimals(n,x.token0),this.pool.readErc20Decimals(n,x.token1)]),be={min:oe(it,ae,G),max:oe(lt,ae,G)},we=null;A===0n?we="no_balance":H!=null&&H<this.minProvideUsd&&(we="insufficient_balance");let ke=P.token0,Ce=P.token1,_n=[25,50,75,100].map(M=>({percent:M,amountWei:(_*BigInt(M)/100n).toString(),amount:q*(M/100)})),Ue=null,ie=null;if(a!=null){ie=a;let M=BigInt(Math.floor(ie*10**C.decimals));M>0n&&(Ue=M.toString())}else if(i!=null&&S!=null&&S>0){ie=i/S;let M=BigInt(Math.floor(ie*10**C.decimals));M>0n&&(Ue=M.toString())}let Re={address:L,isNative:!0,symbol:C.symbol,name:C.name,decimals:C.decimals,priceUsd:S,balanceWei:A.toString(),balance:K,gasReserveWei:I.toString(),spendableWei:_.toString(),spendable:q,spendableUsd:H,quickRates:_n,minProvideUsd:this.minProvideUsd,warning:we,warningMessage:we==="no_balance"?`You have no ${C.symbol} on ${h.name}. Top up to add liquidity.`:we==="insufficient_balance"?`Spendable ${C.symbol} is below the $${this.minProvideUsd} minimum.`:null,prefillAmount:ie,prefillAmountWei:Ue},Vn=this.computePrefillRange({explicitMinPrice:m,explicitMaxPrice:p,rangeStrategy:d,currentTick:x.currentTick,tickSpacing:x.tickSpacing,token0Decimals:ae,token1Decimals:G,priceBounds:be});return{ui:{component:"AddLiquidityForm",props:{chain:{hexId:n,name:h.name},pool:{address:x.poolAddress,fee:x.fee,feePercent:x.fee/1e4,tickSpacing:x.tickSpacing,currentTick:x.currentTick,currentPrice:x.currentPrice,sqrtPriceX96:x.sqrtPriceX96.toString(),token0:{address:x.token0,symbol:ke?.symbol,name:ke?.name,logo:ke?.logo},token1:{address:x.token1,symbol:Ce?.symbol,name:Ce?.name,logo:Ce?.logo}},inputToken:Re,priceBounds:be,prefillRange:Vn,gatewayAddress:g}},summary:this.buildSummary({chainName:h.name,pair:`${ke?.symbol??r}/${Ce?.symbol??o}`,feePercent:x.fee/1e4,warning:we,minProvideUsd:this.minProvideUsd,nativeSymbol:C.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=Fn({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 Po=[{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"}]}],fn=[{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 gt=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 _e(e?.pool),this.defaultSlippageBps=e?.defaultSlippageBps??"auto",this.mintMinPercent=e?.mintMinPercent??80,this.pantograph=new Y}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=z(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=pt(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),[f,g]=await Promise.all([this.pool.readErc20Decimals(r,h.token0),this.pool.readErc20Decimals(r,h.token1)]),y=Pe(o,f,g),k=Pe(s,f,g),w=he(pe(y,h.tickSpacing,"down")),b=he(pe(k,h.tickSpacing,"up"));if(w>=b||w<it||b>lt)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:oe(w,f,g),maxPrice:oe(b,f,g)},[P,x,A]=await Promise.all([this.pool.getNativeBalance(r,d),this.pool.estimateGasReserveWei(r),this.pool.getNativePriceUsd(r)]),I=p+x;if(P<I)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:I.toString(),gasReserveWei:x.toString()};let S=this.pool.splitNativeAmountForRange({nativeAmountWei:p,pool:h,tickLower:w,tickUpper:b,token0Decimals:f,token1Decimals:g}),_=h.token0.toLowerCase()===L,C=h.token1.toLowerCase()===L,q=[];if(_||S.amountInFor0Wei===0n)q.push({tokenIn:L,tokenOut:h.token0,amountIn:S.amountInFor0Wei,tx:{to:L,data:"0x",value:"0"},minAmountOut:_?S.amountInFor0Wei:0n});else{let W=await this.pool.fetchSwapLeg({senderAddress:d,chainId:r,tokenIn:L,tokenInAmount:S.amountInFor0Wei.toString(),tokenOut:h.token0,tokenOutRecipient:u,slippage:typeof i=="number"?i:"auto"});q.push({tokenIn:L,tokenOut:h.token0,amountIn:S.amountInFor0Wei,tx:{to:W.to,data:W.data,value:W.value},minAmountOut:BigInt(W.minAmountOut||"0")})}if(C||S.amountInFor1Wei===0n)q.push({tokenIn:L,tokenOut:h.token1,amountIn:S.amountInFor1Wei,tx:{to:L,data:"0x",value:"0"},minAmountOut:C?S.amountInFor1Wei:0n});else{let W=await this.pool.fetchSwapLeg({senderAddress:d,chainId:r,tokenIn:L,tokenInAmount:S.amountInFor1Wei.toString(),tokenOut:h.token1,tokenOutRecipient:u,slippage:typeof i=="number"?i:"auto"});q.push({tokenIn:L,tokenOut:h.token1,amountIn:S.amountInFor1Wei,tx:{to:W.to,data:W.data,value:W.value},minAmountOut:BigInt(W.minAmountOut||"0")})}let K=W=>W*BigInt(this.mintMinPercent)/100n,H=K(q[0].minAmountOut),ae=K(q[1].minAmountOut),G=q.map(W=>({tokenIn:W.tokenIn,tokenInAmount:BigInt(W.amountIn),tokenOut:W.tokenOut,tokenOutAmount:BigInt(W.minAmountOut)})),be=q.map(W=>({to:W.tx.to,data:W.tx.data,value:BigInt(W.tx.value||"0")}));console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ paymentInfo:",G),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ exchangesInfo:",be),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ args:",{args:[G,be,{fee:Number(h.fee),amount0Min:BigInt(H),amount1Min:BigInt(ae),tickLower:w,tickUpper:b}]});let we=De({abi:Po,functionName:"mint",args:[G,be,{fee:Number(h.fee),amount0Min:BigInt(H),amount1Min:BigInt(ae),tickLower:w,tickUpper:b}]}),ke=a,Ce={chainId:r,to:u,data:we,value:p.toString(),from:d},_n={nativeIn:ke,nativeInUsd:A!=null?ke*A:null,ratio:{token0Percent:S.ratio0,token1Percent:S.ratio1},expectedToken0:Number(q[0].minAmountOut)/10**f,expectedToken1:Number(q[1].minAmountOut)/10**g,amount0Min:H.toString(),amount1Min:ae.toString(),slippageBps:i},Ue=await this.pantograph.getTokensMetadata([h.token0,h.token1],r),ie=Ue[h.token0],Re=Ue[h.token1];return{ui:{component:"ConfirmAddLiquidityTx",props:{chain:{hexId:r,name:l.name},unsignedTx:Ce,summary:_n,pool:{...h,feePercent:h.fee/1e4,token0:{address:h.token0,symbol:ie.symbol,name:ie.name,decimals:ie.decimals,logo:ie.icon_image},token1:{address:h.token1,symbol:Re.symbol,name:Re.name,decimals:Re.decimals,logo:Re.icon_image}},range:T,gatewayAddress:u}},summary:`Ready to add ${ke} ${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 Ao=365,fs=1e3,ys=/^0x[a-fA-F0-9]{40}$/;function bs(c,e){return{depositUsd:c,aprPercent:e*100,dailyUsd:c*e/Ao,weeklyUsd:c*e/52,monthlyUsd:c*e/12,yearlyUsd:c*e}}var ft=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
106
|
\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
107
|
\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
108
|
\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
109
|
\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 Q(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:fs;if(!r)return{error:"token0Symbol is required. Ask the user which pool / token they want to estimate yield for."};let
|
|
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 Q(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:fs;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 chain ${n}. Verify the token symbols with the user.`};let p=this.rankByVolume(m);if(o&&s==null&&new Set(p.map(_=>_.feeTier).filter(_=>typeof _=="number")).size>1){let _=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:_.map(C=>({pair:C.token0?.symbol&&C.token1?.symbol?`${C.token0.symbol}/${C.token1.symbol}`:void 0,feeTierBps:C.feeTier,feeTierPercent:typeof C.feeTier=="number"?C.feeTier/1e4:void 0,volume24hUsd:C.volumeUsd24hr,address:C.id}))}}let h=p[0];if(!h.id||!ys.test(h.id))return{error:"Resolved pool has no valid address."};let f=await this.service.getV3PoolDetail({address:h.id,chain:n});if(!f.success||!f.data)return{error:f.error||"Failed to fetch pool detail."};let g=f.data,y=g.totalLiquidity?.value,k=g.volume24h?.value,w=g.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*Ao:void 0,x={address:g.address,pair:g.token0?.symbol&&g.token1?.symbol?`${g.token0.symbol}/${g.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:g.token0?{symbol:g.token0.symbol}:void 0,token1:g.token1?{symbol:g.token1.symbol}:void 0},A=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:x,autoPickedReason:A,aprUnavailableReason:y?"Volume data unavailable":"TVL is zero or unavailable"};let I=bs(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. ')+(A?'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:x,estimate:I,depositMode:a?"user-specified":"default-1000-example",autoPickedReason:A,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 ws={"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"},ks={"0x1":"ethereum","0xa4b1":"arbitrum","0x89":"polygon","0x38":"bsc","0x2105":"base","0xa":"optimism","0xa86a":"avalanche"},Ts={"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"},Co={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"},Ss=300*1e3,X=class{apiKey;subgraphUrls;coinPoolBaseUrl;keyringPoolBaseUrl;llamaCache=null;llamaCacheAt=0;constructor(e={}){this.apiKey=e.theGraphApiKey||"4c67ac7a75b21befbd28dc9120c709f1",this.subgraphUrls={...ws,...e.subgraphUrls||{}},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 ks[t]||t}numericChainId(e){return Ts[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 fetch(r,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`},body:JSON.stringify({query:t})});if(!o.ok)throw new Error(`Subgraph query failed: HTTP ${o.status}`);let s=await o.json();if(s.errors&&s.errors.length>0)throw new Error(`Subgraph error: ${s.errors[0].message||"Unknown"}`);return s.data}poolFragment(){return`
|
|
111
111
|
id
|
|
112
112
|
token0 { id symbol name }
|
|
113
113
|
token1 { id symbol name }
|
|
@@ -124,16 +124,16 @@ Source: Uniswap ExploreStats (live 24h data). Supports: Ethereum, Optimism, BSC,
|
|
|
124
124
|
volumeToken0
|
|
125
125
|
volumeToken1
|
|
126
126
|
}
|
|
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=Co[r]??r/1e4,s=this.sanitize(parseFloat(n.totalValueLockedUSD||"0")),a=n.poolDayData?.[0],i=parseFloat(a?.volumeUSD||"0"),
|
|
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=Co[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<Ss)return this.llamaCache;try{let t=await fetch("https://yields.llama.fi/pools");if(!t.ok)throw new Error(`DefiLlama HTTP ${t.status}`);let r=((await t.json()).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=>vs[m.chain.toLowerCase()]===r),l=i.find(m=>{if(!m.underlyingTokens||m.underlyingTokens.length<2)return!1;let p=m.underlyingTokens.map(f=>f.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(f=>{if([...f.symbol.split("-").map(k=>k.toUpperCase())].sort().join("-")!==h)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 g=f.toUpperCase(),y=new Set([g]);return g.startsWith("W")||y.add("W"+g),g.startsWith("W")&&g.length>1&&y.add(g.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(g=>`"${g}"`).join(", ")}]`,m=(f,g)=>`{
|
|
128
128
|
pools(
|
|
129
129
|
first: ${g}
|
|
130
|
-
orderBy: ${
|
|
130
|
+
orderBy: ${l}
|
|
131
131
|
orderDirection: ${u}
|
|
132
132
|
where: { ${[...i,...f].join(", ")} }
|
|
133
133
|
) {
|
|
134
134
|
${this.poolFragment()}
|
|
135
135
|
}
|
|
136
|
-
}`,
|
|
136
|
+
}`,p;if(a.length>0){let g,y;a.length>=2?(g=m([`token0_: { ${d(a[0])} }`,`token1_: { ${d(a[1])} }`],100),y=m([`token0_: { ${d(a[1])} }`,`token1_: { ${d(a[0])} }`],100)):(g=m([`token0_: { ${d(a[0])} }`],100),y=m([`token1_: { ${d(a[0])} }`],100));let[k,w]=await Promise.allSettled([this.querySubgraph(s,g),this.querySubgraph(s,y)]),b=k.status==="fulfilled"?(k.value?.pools||[]).filter(x=>this.hasValidVolume(x)).map(x=>this.mapPool(x,s)):[],T=w.status==="fulfilled"?(w.value?.pools||[]).filter(x=>this.hasValidVolume(x)).map(x=>this.mapPool(x,s)):[],P=new Set(b.map(x=>x.poolAddress));p=[...b,...T.filter(x=>!P.has(x.poolAddress))]}else p=((await this.querySubgraph(s,m([],100)))?.pools||[]).filter(g=>this.hasValidVolume(g)).map(g=>this.mapPool(g,s));let h=await this.fetchDefiLlamaUniswap();return h.length>0&&(p=this.enrichWithLlama(p,h)),o?.minTVL!=null&&(p=p.filter(f=>f.tvl>=o.minTVL)),o?.maxTVL!=null&&(p=p.filter(f=>f.tvl<=o.maxTVL)),o?.minAPR!=null&&(p=p.filter(f=>f.apr!=null&&f.apr>=o.minAPR)),o?.maxAPR!=null&&(p=p.filter(f=>f.apr!=null&&f.apr<=o.maxAPR)),r==="apr"?p.sort((f,g)=>(g.apr??0)-(f.apr??0)):r==="liquidity"&&p.sort((f,g)=>{let y=BigInt(g.liquidity||"0")-BigInt(f.liquidity||"0");return y>0n?1:y<0n?-1:0}),p}async getTrendingPools(e){let t=this.resolveChain(e.chain),n=`{
|
|
137
137
|
pools(
|
|
138
138
|
first: 100
|
|
139
139
|
orderBy: volumeUSD
|
|
@@ -166,21 +166,21 @@ Source: Uniswap ExploreStats (live 24h data). Supports: Ethereum, Optimism, BSC,
|
|
|
166
166
|
${this.poolFragment()}
|
|
167
167
|
}
|
|
168
168
|
}
|
|
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,c=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:c,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 fetch(`${this.coinPoolBaseUrl}/pair/pure-list?chainId=${t}`);if(!a.ok)throw new Error(`HTTP ${a.status}`);n=(await a.json())?.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(c=>{let u=c.token0.symbol.toUpperCase(),d=c.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,c=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&&!c)return null;let m=Math.min(i,c),h=Math.max(i,c);if(u<m||u>h)return null;let f=Number(e.fee),g=Co[f]??f/1e4;return{poolId:e.poolId,pair:`${e.token0.symbol}/${e.token1.symbol}`,fee:`${g}%`,apr:n.apr??null,aprAvg:n.aprAvg??null,token0Price:e.token0.price,token1Price:e.token1.price,priceRange:{minPrice:i,maxPrice:c,currentPrice:u,baseToken:d,quoteToken:p}}}async fetchPositionDetails(e,t){if(!this.keyringPoolBaseUrl||t.length===0)return[];let n=xs(t,200);return(await Promise.allSettled(n.map(async o=>{let s=new URLSearchParams({tokenIds:o.join(","),chainId:String(e)}),a=await fetch(`${this.keyringPoolBaseUrl}/user/positions-details/v3?${s}`);if(!a.ok)throw new Error(`HTTP ${a.status}`);let i=await a.json();return Array.isArray(i)?i:i?.data||[]}))).flatMap(o=>o.status==="fulfilled"?o.value:[])}};function xs(l,e){if(e<=0)return[l];let t=[];for(let n=0;n<l.length;n+=e)t.push(l.slice(n,n+e));return t}var Ps=["tvl","volume","apr","fee","liquidity"],yt=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 X(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)),a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],i=typeof e.sortBy=="string"&&Ps.includes(e.sortBy)?e.sortBy:"tvl",c={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,supportedChains:this.service.listSupportedChains(),pools:[],count:0};let p=(await Promise.allSettled(a.map(m=>this.service.searchPools({tokens:n,chain:m,sortBy:i,filters:c})))).flatMap(m=>m.status==="fulfilled"?m.value:[]);return i==="apr"?p.sort((m,h)=>(h.apr??0)-(m.apr??0)):i==="tvl"?p.sort((m,h)=>h.tvl-m.tvl):i==="volume"?p.sort((m,h)=>h.volume24hUsd-m.volume24hUsd):i==="fee"&&p.sort((m,h)=>m.feeTierBps-h.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,unsupportedChains:o,sortBy:i,filters:c,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:se(e)}}};function se(l){return`https://app.uniswap.org/explore/pools/${Gt[l.chain]||l.chain}/${l.poolAddress}`}var Gt={ethereum:"ethereum",arbitrum:"arbitrum",polygon:"polygon",bsc:"bnb",base:"base",optimism:"optimism",avalanche:"avalanche"};var bt=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};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,c=(await Promise.allSettled(s.map(u=>this.service.getTrendingPools({chain:u})))).flatMap(u=>u.status==="fulfilled"?u.value:[]);return c.sort((u,d)=>d.volume24hUsd-u.volume24hUsd),c=c.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:c.length,pools:c.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:se(u)}))}}};var wt=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 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()?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(r))return{_instructions:"The requested chain is not supported by the Subgraph pool service. List the supported chains briefly.",unsupportedChains:[r],supportedChains:this.service.listSupportedChains()};let o=this.service.resolveChain(r),s=await this.service.getPoolByAddress({poolAddress:n,chain:o});return s?{_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:o,pool:{pair:s.pair,poolAddress:s.poolAddress,chain:s.chain,feeTierBps:s.feeTierBps,feeTierPercent:s.feeTierPercent,tvlUsd:s.tvl,volume24hUsd:s.volume24hUsd,fees24hUsd:s.fees24hUsd,apr:s.apr,token0:s.token0,token1:s.token1,uniswapUrl:se(s)}}:{error:`No pool found at ${n} on chain ${o}.`}}};var kt=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()};let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],c=(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 c?{_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:c.pair,poolAddress:c.poolAddress,chain:c.chain,feeTierBps:c.feeTierBps,feeTierPercent:c.feeTierPercent,tvlUsd:c.tvl,volume24hUsd:c.volume24hUsd,fees24hUsd:c.fees24hUsd,apr:c.apr,token0:c.token0,token1:c.token1,uniswapUrl:se(c)}}:{_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 Tt=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()};let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],c=(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(!c)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=Gt[c.position.chain]||c.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:{...c.position,uniswapPositionUrl:`https://app.uniswap.org/positions/v3/${u}/${c.position.positionId}`},pool:{...c.pool,uniswapUrl:se(c.pool)}}}};var vt=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(c=>typeof c=="string"&&c.trim().length>0).map(c=>c.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()?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(r))return{_instructions:"Requested chain not supported. Mention supported chains.",unsupportedChains:[r],supportedChains:this.service.listSupportedChains()};let o=this.service.resolveChain(r),s=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),30):10,a=await this.service.getCoinPoolPairs({chain:o,tokens:n});if(a.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:o,pairs:[],count:0};let i=[...a].sort((c,u)=>(u.apr??-1/0)-(c.apr??-1/0)).slice(0,s);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:o,count:i.length,pairs:i}}};function ue(l){if(typeof l=="number")return Number.isFinite(l)&&l>0?{kind:"token",value:l,raw:String(l)}:null;if(typeof l!="string")return null;let e=l.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=parseFloat(e);return Number.isFinite(r)&&r>0&&/^[0-9]*\.?[0-9]+$/.test(e)?{kind:"token",value:r,raw:e}:null}function Uo(l,e){switch(l.kind){case"token":return{ok:!0,amount:l.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*l.percent/100};case"usd":return e.usdPrice===void 0||!Number.isFinite(e.usdPrice)||e.usdPrice<=0?{ok:!1,reason:"no_price"}:{ok:!0,amount:l.usd/e.usdPrice}}}var As={send:30000n,wrap:40000n,swap:1000000n},_s={"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 Cs(l){let e=l.toLowerCase(),n=[ut(l),..._s[e]??[]].filter(r=>!!r);for(let r of n)try{let o=await fe(r,"eth_gasPrice",[]),s=BigInt(o);if(s>0n)return s}catch{}return 0n}async function St(l,e){let t=await Cs(l);return t<=0n?0n:As[e]*t}function xt(l,e,t){if(e<=0n)return l;let n=z(t)?.native.decimals;if(n===void 0)return l;let r=Number(e)/10**n;return!Number.isFinite(r)||r<=0?l:Math.max(0,l-r)}var J=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=D(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 c=i.data.result.find(s);if(c)return{address:c.token_address,symbol:c.symbol,name:c.name,decimals:typeof c.decimals=="number"?c.decimals:void 0}}}let a=await this.moralis.searchTokensByKey({key:e,chain:t});if(a.success&&a.data?.length){let c=a.data.find(s)??a.data[0];if(c?.token_address)return{address:c.token_address,symbol:c.symbol,name:c.name,decimals:typeof c.decimals=="number"?c.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,c=this.normaliseAddress(t),u=typeof t=="string"&&t.trim()?t.trim():void 0,d=c??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}}:c?{token:{address:c,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 Yn(e.chain,t)}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return D(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=ue(t);if(!a)return null;let i=e.symbol??"the token",c=n.toLowerCase()==="native",u=!!s&&c&&!!r;if(a.kind==="token"&&!u)return{amount:this.toPlainDecimal(a.value)};let d=await this.readTokenBalanceAndPrice(n,r,o),p=d?.balance;if(u&&d?.balance!==void 0&&(p=await this.computeNativeSpendable(r,s,d.balance)),a.kind==="token")return p!==void 0&&a.value>p?{error:"amount_exceeds_spendable",_instructions:`The user wants to send ${this.toPlainDecimal(a.value)} ${i}, but after leaving enough to cover the network fee they can only send up to ${this.toPlainDecimal(p)} ${i}. Tell them, in their language, that ${this.toPlainDecimal(a.value)} ${i} is more than they can send and that the most they can send is ${this.toPlainDecimal(p)} ${i}, and ask them to pick a smaller amount. Do NOT mention gas, fees, tool names, UI, or forms \u2014 just refer to the spendable amount.`}:{amount:this.toPlainDecimal(a.value)};let m=Uo(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 St(e,t);return xt(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 c=i.balance_formatted!=null?Number(i.balance_formatted):void 0;return{balance:Number.isFinite(c)?c: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("."),c=a+i,u;if(s>=0){let d=a.length+s;u=d>=c.length?c.padEnd(d,"0"):`${c.slice(0,d)}.${c.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${c}`;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=Ie(e.chain,t);if(r){let g=this.actionType.replace(/_/g," ");return{error:"wrong_chain",_instructions:`The user asked to ${g} on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to ${g} on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can ${g} 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},c=a.parameters,u=g=>{let y=c[g];return y!=null&&y!==""},d=this.userInputFields.filter(g=>!(Array.isArray(g.key)?g.key:[g.key]).some(u)).map(g=>g.label),p=this.userInputFields.map(g=>{let k=(Array.isArray(g.key)?g.key:[g.key]).find(u);return k?`${g.label}: ${c[k]}`:null}).filter(g=>g!=null),m=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.',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. ${h} 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("; ")}. `:"")+`${h} Wait for the user to confirm or edit before proceeding.`;return{ui:i,action:this.actionType,chainId:o,parameters:a.parameters,_instructions:f}}};var Pt=class extends J{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=zn(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={};if(e.to_address!=null){let a=this.normaliseAddress(e.to_address);a?n.to_address=a:typeof e.to_address=="string"&&e.to_address.trim()&&(n.to_address=e.to_address.trim())}let r=this.requireChain(e,t)??void 0,o=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:"native",symbol:"the native coin",chain:r,walletAddress:t?.walletAddress??void 0,gasKind:"send"});if(o&&"error"in o)return o;o&&(n.amount=o.amount);let s=await this.resolveNativeSpendableFormatted(r,t?.walletAddress??void 0,"send");return s!==void 0&&(n.spendable=s),n}};var At=class extends J{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}),h=(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(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 f=e.to_address!=null?this.normaliseAddress(e.to_address):null,g=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:"",x=w.usd_value?`($${w.usd_value.toFixed(2)})`:"";return{label:`${P}:${T} ${b} ${x}`.trim(),prompt:this.buildSendPrompt(y,b,g,f)}}),_instructions:"Reply briefly in the user's language: ask them to pick which token they want to send from the options below. 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,c={};if(i?.address&&(c.contract_address=i.address),i?.symbol&&(c.token_symbol=i.symbol),i?.decimals!==void 0&&(c.decimals=String(i.decimals)),e.to_address!=null){let u=this.normaliseAddress(e.to_address);u?c.to_address=u:typeof e.to_address=="string"&&e.to_address.trim()&&(c.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&&(c.amount=u.amount)}else{let u=this.normaliseAmount(e.amount);u&&(c.amount=u)}return c}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,c=await this.moralis.getWalletTokenBalances({address:n,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0}),d=(c.success?c.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 _t=class extends J{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(),c=parseFloat(i);Number.isFinite(c)&&c>=0&&(a.amount=i)}else typeof e.amount=="number"&&Number.isFinite(e.amount)&&e.amount>=0&&(a.amount=String(e.amount));return a}};var Ct=class extends J{name="open-wrap-native-form";actionType="wrap_native";component="WrapNativeForm";userInputFields=[{key:"amount",label:"the amount to wrap"}];description='Open the WRAP NATIVE form so the user can wrap the chain\'s native coin into its wrapped ERC-20 (ETH \u2192 WETH, BNB \u2192 WBNB, MATIC \u2192 WMATIC, AVAX \u2192 WAVAX). Triggered by "wrap", "deposit into WETH", "convert ETH to WETH", "b\u1ECDc ETH". Only `amount` is needed. DO NOT use for arbitrary token swaps.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"amount",type:"string",description:'Human-readable amount of native coin to wrap (e.g. "0.5"). Omit when unknown.',required:!1}];async buildParameters(e,t){let n={},r=this.requireChain(e,t)??void 0,o=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:"native",symbol:"the native coin",chain:r,walletAddress:t?.walletAddress??void 0,gasKind:"wrap"});if(o&&"error"in o)return o;o&&(n.amount=o.amount);let s=await this.resolveNativeSpendableFormatted(r,t?.walletAddress??void 0,"wrap");return s!==void 0&&(n.spendable=s),n}};var Ut=class extends J{name="open-unwrap-native-form";actionType="unwrap_native";component="UnwrapNativeForm";userInputFields=[{key:"amount",label:"the amount to unwrap"}];description='Open the UNWRAP NATIVE form so the user can unwrap their wrapped native into the chain\'s native coin (WETH \u2192 ETH, WBNB \u2192 BNB, WMATIC \u2192 MATIC, WAVAX \u2192 AVAX). Triggered by "unwrap", "withdraw from WETH", "convert WETH to ETH", "th\xE1o WETH". Only `amount` is needed.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"amount",type:"string",description:'Human-readable amount of WRAPPED native to unwrap (e.g. "0.5"). Omit when unknown.',required:!1}];async buildParameters(e,t){let n={},r=this.normaliseAmount(e.amount);return r&&(n.amount=r),n}};var Rt=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 Y({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} \u3092\u8CB7\u3046". 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} \u3092\u8CB7\u3046". 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=Ie(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){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,c=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,h=d.decimals,f=await this.resolveBuyAmountSpec(e.buy_amount,i,o,c),g=p?await this.resolvePayAmountSpec(e.pay_with_amount,p,o,s,m):this.normaliseAmount(e.pay_with_amount),y=ue(e.pay_with_amount),k=!!y&&y.kind!=="token",w=null;if(this.dbg("resolved sides",{dest:{destContract:i,destSymbol:c,destDecimals:u},pay:{payAddr:p,paySymbol:m,payDecimals:h},buyAmount:f,payAmount:g}),!i)return this.dbg("\u2192 dest token not resolved"),{error:"dest_token_not_found",_instructions:`Could not find the token "${c||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:c,destContract:i,buyAmount:f,payAmount:g});if(!p)return{error:"pay_token_missing",_instructions:`Ask the user, in their language, which token they want to spend to buy ${c}. Do NOT invent a token. Do NOT mention tool names, UI, or forms.`};if(f&&!g){this.dbg("Branch 1.3: deriving pay amount from buy amount",{buyAmount:f,destSymbol:c,paySymbol:m});let b=await this.derivePayAmountFromBuy({chain:o,buyAmount:f,destContract:i,destSymbol:c,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} ${c} 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.`};g=b,w=f,k=!1,this.dbg("\u2192 Branch 1.3: derived pay amount",{payAmount:g,derivedFromBuy:w})}if(this.moralis&&t?.walletAddress){this.dbg("Branch 1.4: checking pay balance",{payAddr:p,paySymbol:m,payAmount:g});let b=await this.checkPayBalance({args:e,userContext:t,chain:o,destSymbol:c,destContract:i,payAddr:p,paySymbol:m,payAmount:g,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&&!g&&this.moralis&&t?.walletAddress){this.dbg("Branch 1.5: building amount picker");let b=await this.buildAmountPicker({userContext:t,chain:o,destSymbol:c,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(g&&(h!==void 0||p==="native")){this.dbg("Branch 1.75: building confirm tx");let b=await this.buildConfirmTx({userContext:t,chain:o,destContract:i,destSymbol:c,destDecimals:u,payAddr:p,paySymbol:m,payDecimals:h,payAmount:g});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 ${c} 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 ${c} 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 ${c}. 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 D(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=D(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 c=i.data.result.find(s);if(c)return{address:c.token_address,symbol:c.symbol,name:c.name,decimals:typeof c.decimals=="number"?c.decimals:void 0}}}let a=await this.moralis.searchTokensByKey({key:e,chain:t});if(a.success&&a.data?.length){let c=a.data.find(s)??a.data[0];if(c?.token_address)return{address:c.token_address,symbol:c.symbol,name:c.name,decimals:typeof c.decimals=="number"?c.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),c=a||void 0,u=m=>m.toLowerCase()===L?"native":m,d=i??c??(s||void 0);if(!d)return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,resolved:"none"}),{address:null,symbol:c};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:c??p.symbol,decimals:p.decimals}:i?{address:u(i),symbol:c}:{address:null,symbol:c}}async buildConfirmTx(e){let{userContext:t,chain:n,destContract:r,destSymbol:o,destDecimals:s,payAddr:a,paySymbol:i,payDecimals:c,payAmount:u}=e,d=t?.walletAddress;if(!d||!n||!r)return null;let p=z(n),m=Number.parseInt(n,16);if(!p||!Number.isFinite(m))return null;let h=a==="native",f=h?p.native.decimals??18:c;if(f===void 0)return null;let g=this.toRawAmount(u,f);if(!g)return null;let y=h?L:a,k=r==="native"?L:r,w;try{w=await(await ce.getServiceByProvider("debridge")).getQuote({srcChainId:m,srcTokenAddress:y,srcTokenAmount:g,dstChainId:m,dstTokenAddress:k,recipientAddress:d,senderAddress:d,slippage:"auto",isCrossChain:!1})}catch(C){return{quoteError:C instanceof Error?C.message:String(C)}}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),x=P&&s!==void 0?this.trimAmount(Number(P)/10**s):void 0,A;h||(A=await this.buildApproveTx({chain:n,walletAddress:d,payAddr:a,payDec:f,rawAmount:g,quote:w}));let S={component:"BuyTokenConfirmTx",props:{chain:{hexId:n,name:p.name},payToken:{address:h?"native":a,symbol:i,decimals:f,amount:u,rawAmount:g},buyToken:{address:r,symbol:o,decimals:s,amount:x,rawAmount:P},estimatedOut:x,estimatedOutRaw:P,provider:w.provider,swapTx:T,approveTx:A}},_=x?` They will receive about ${x} ${o}.`:"";return{ui:S,_instructions:`A confirmation panel has been opened for buying ${o} with ${u} ${i??"the selected token"}.${_} 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 c=1.02,u=i*s/a;return!Number.isFinite(u)||u<=0?null:this.trimAmount(u*c)}async getUsdPrice(e,t){if(!this.moralis)return null;let n=e==="native"?L: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=ue(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=ue(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 c=await(await ce.getServiceByProvider("debridge")).checkApproval({chain:t,userAddress:n,tokenAddress:r,amount:s,tokenDecimals:o,quoteData:a});if(!c.isNeeded)return;let u=c.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=c.contractAddress;if(!d)return;let p=De({abi:fn,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,c=await a.getWalletTokenBalances({address:i,chain:s,excludeSpam:!0,excludeUnverifiedContracts:!0}),u=c.success?c.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 h=e.buyAmount??this.normaliseAmount(t.buy_amount),f=e.payAmount??this.normaliseAmount(t.pay_with_amount),g=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)})`:"",x=`${b}: ${T}${w}${P}`,A=h?this.buildBuyWithPrompt(g,r,w,h,null):this.buildBuyWithPrompt(g,r,w,null,f);return{label:x,prompt:A}}),_instructions:`Reply briefly in the user's language: ask them to pick which token they want to spend to buy ${r} from the options below. 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:c,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:c,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. Tell them, in their language, that they don't have any ${m}, then say they can instead choose one of the tokens they already hold (shown below) to pay with. 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=Number(i);if(!Number.isFinite(h)||h<=p.balanceNum)return null;let f=this.trimAmount(p.balanceNum),g=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. Tell them, in their language, that buying ${u} ${o} would cost about ${i} ${m}, which is more than their balance of ${f} ${m}, and ask them to buy 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. Tell them, in their language, that ${i} ${m} exceeds their balance of ${f} ${m}, and ask them to pick a smaller amount \u2014 `;return{error:"insufficient_pay_balance",actionButtons:g,_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(c=>({sym:c.symbol,addr:c.token_address,bal:c.balance_formatted}))});let s=t.toLowerCase(),a=o.find(c=>s==="native"?c.native_token===!0:c.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 c=await St(n,"swap"),u=xt(i,c,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 c=i===1?this.cleanAmountString(t):this.trimAmount(n*i);return{label:`${Math.round(i*100)}%`,prompt:this.buildBuyWithPrompt(s,o,r,null,c)}})}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),c=e.paySymbol??i?.symbol;if(!i||!c)return null;let u=this.percentSpendButtons({balanceFormatted:i.balanceFormatted,balanceNum:i.balanceNum,paySymbol:c,destSymbol:r,buyWithTemplate:s}),d=this.trimAmount(i.balanceNum);return{actionButtons:u,_instructions:`The user's spendable balance is ${d} ${c}. Reply briefly in the user's language: tell them their spendable ${c} balance and ask how much they want to spend to buy ${r}, inviting 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%"). Just ask the question. 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("."),c=a+i,u;if(s>=0){let d=a.length+s;u=d>=c.length?c.padEnd(d,"0"):`${c.slice(0,d)}.${c.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${c}`;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),c=typeof e.buy_prompt_template=="string"?e.buy_prompt_template:"",u=i.map(m=>{let h=m.symbol;return{label:h,prompt:this.buildBuyPrompt(c,h)}}),p=i.map((m,h)=>{let f=m.name?.trim()||m.symbol,g=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),b=w?k>0?"\u25B2":k<0?"\u25BC":"\u25AA":"",T=w?` (${b} ${this.formatPercent(k)}%)`:"";return`${h+1}. ${f} (${g})
|
|
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 fetch(`${this.coinPoolBaseUrl}/pair/pure-list?chainId=${t}`);if(!a.ok)throw new Error(`HTTP ${a.status}`);n=(await a.json())?.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,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 f=Number(e.fee),g=Co[f]??f/1e4;return{poolId:e.poolId,pair:`${e.token0.symbol}/${e.token1.symbol}`,fee:`${g}%`,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=xs(t,200);return(await Promise.allSettled(n.map(async o=>{let s=new URLSearchParams({tokenIds:o.join(","),chainId:String(e)}),a=await fetch(`${this.keyringPoolBaseUrl}/user/positions-details/v3?${s}`);if(!a.ok)throw new Error(`HTTP ${a.status}`);let i=await a.json();return Array.isArray(i)?i:i?.data||[]}))).flatMap(o=>o.status==="fulfilled"?o.value:[])}};function xs(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 Ps=["tvl","volume","apr","fee","liquidity"],yt=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 X(e)}async run(e,t){let n=(Array.isArray(e.tokens)?e.tokens:[]).filter(p=>typeof p=="string"&&p.trim().length>0).map(p=>p.trim().toUpperCase()),r=(Array.isArray(e.chains)?e.chains:[]).filter(p=>typeof p=="string"&&p.trim().length>0),o=r.filter(p=>!this.service.isSupported(p)),s=r.filter(p=>this.service.isSupported(p)).map(p=>this.service.resolveChain(p)),a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],i=typeof e.sortBy=="string"&&Ps.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,supportedChains:this.service.listSupportedChains(),pools:[],count:0};let m=(await Promise.allSettled(a.map(p=>this.service.searchPools({tokens:n,chain:p,sortBy:i,filters:l})))).flatMap(p=>p.status==="fulfilled"?p.value:[]);return i==="apr"?m.sort((p,h)=>(h.apr??0)-(p.apr??0)):i==="tvl"?m.sort((p,h)=>h.tvl-p.tvl):i==="volume"?m.sort((p,h)=>h.volume24hUsd-p.volume24hUsd):i==="fee"&&m.sort((p,h)=>p.feeTierBps-h.feeTierBps),m=m.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,unsupportedChains:o,sortBy:i,filters:l,count:m.length,pools:m.map(p=>this.formatPool(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:se(e)}}};function se(c){return`https://app.uniswap.org/explore/pools/${Gt[c.chain]||c.chain}/${c.poolAddress}`}var Gt={ethereum:"ethereum",arbitrum:"arbitrum",polygon:"polygon",bsc:"bnb",base:"base",optimism:"optimism",avalanche:"avalanche"};var bt=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};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:se(u)}))}}};var wt=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 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()?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(r))return{_instructions:"The requested chain is not supported by the Subgraph pool service. List the supported chains briefly.",unsupportedChains:[r],supportedChains:this.service.listSupportedChains()};let o=this.service.resolveChain(r),s=await this.service.getPoolByAddress({poolAddress:n,chain:o});return s?{_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:o,pool:{pair:s.pair,poolAddress:s.poolAddress,chain:s.chain,feeTierBps:s.feeTierBps,feeTierPercent:s.feeTierPercent,tvlUsd:s.tvl,volume24hUsd:s.volume24hUsd,fees24hUsd:s.fees24hUsd,apr:s.apr,token0:s.token0,token1:s.token1,uniswapUrl:se(s)}}:{error:`No pool found at ${n} on chain ${o}.`}}};var kt=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()};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:se(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 Tt=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()};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=Gt[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:se(l.pool)}}}};var vt=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(l=>typeof l=="string"&&l.trim().length>0).map(l=>l.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()?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(r))return{_instructions:"Requested chain not supported. Mention supported chains.",unsupportedChains:[r],supportedChains:this.service.listSupportedChains()};let o=this.service.resolveChain(r),s=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),30):10,a=await this.service.getCoinPoolPairs({chain:o,tokens:n});if(a.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:o,pairs:[],count:0};let i=[...a].sort((l,u)=>(u.apr??-1/0)-(l.apr??-1/0)).slice(0,s);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:o,count:i.length,pairs:i}}};function ue(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=parseFloat(e);return Number.isFinite(r)&&r>0&&/^[0-9]*\.?[0-9]+$/.test(e)?{kind:"token",value:r,raw:e}:null}function Uo(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 As={send:30000n,wrap:40000n,swap:1000000n},_s={"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 Cs(c){let e=c.toLowerCase(),n=[ut(c),..._s[e]??[]].filter(r=>!!r);for(let r of n)try{let o=await fe(r,"eth_gasPrice",[]),s=BigInt(o);if(s>0n)return s}catch{}return 0n}async function St(c,e){let t=await Cs(c);return t<=0n?0n:As[e]*t}function xt(c,e,t){if(e<=0n)return c;let n=z(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 J=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=D(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 Yn(e.chain,t)}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return D(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=ue(t);if(!a)return null;let i=e.symbol??"the token",l=n.toLowerCase()==="native",u=!!s&&l&&!!r;if(a.kind==="token"&&!u)return{amount:this.toPlainDecimal(a.value)};let d=await this.readTokenBalanceAndPrice(n,r,o),m=d?.balance;if(u&&d?.balance!==void 0&&(m=await this.computeNativeSpendable(r,s,d.balance)),a.kind==="token")return m!==void 0&&a.value>m?{error:"amount_exceeds_spendable",_instructions:`The user wants to send ${this.toPlainDecimal(a.value)} ${i}, but after leaving enough to cover the network fee they can only send up to ${this.toPlainDecimal(m)} ${i}. Tell them, in their language, that ${this.toPlainDecimal(a.value)} ${i} is more than they can send and that the most they can send is ${this.toPlainDecimal(m)} ${i}, and ask them to pick a smaller amount. Do NOT mention gas, fees, tool names, UI, or forms \u2014 just refer to the spendable amount.`}:{amount:this.toPlainDecimal(a.value)};let p=Uo(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 St(e,t);return xt(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=Ie(e.chain,t);if(r){let g=this.actionType.replace(/_/g," ");return{error:"wrong_chain",_instructions:`The user asked to ${g} on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to ${g} on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can ${g} 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=g=>{let y=l[g];return y!=null&&y!==""},d=this.userInputFields.filter(g=>!(Array.isArray(g.key)?g.key:[g.key]).some(u)).map(g=>g.label),m=this.userInputFields.map(g=>{let k=(Array.isArray(g.key)?g.key:[g.key]).find(u);return k?`${g.label}: ${l[k]}`:null}).filter(g=>g!=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.',f=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:f}}};var Pt=class extends J{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=zn(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={};if(e.to_address!=null){let a=this.normaliseAddress(e.to_address);a?n.to_address=a:typeof e.to_address=="string"&&e.to_address.trim()&&(n.to_address=e.to_address.trim())}let r=this.requireChain(e,t)??void 0,o=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:"native",symbol:"the native coin",chain:r,walletAddress:t?.walletAddress??void 0,gasKind:"send"});if(o&&"error"in o)return o;o&&(n.amount=o.amount);let s=await this.resolveNativeSpendableFormatted(r,t?.walletAddress??void 0,"send");return s!==void 0&&(n.spendable=s),n}};var At=class extends J{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 f=e.to_address!=null?this.normaliseAddress(e.to_address):null,g=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:"",x=w.usd_value?`($${w.usd_value.toFixed(2)})`:"";return{label:`${P}:${T} ${b} ${x}`.trim(),prompt:this.buildSendPrompt(y,b,g,f)}}),_instructions:"Reply briefly in the user's language: ask them to pick which token they want to send from the options below. 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 _t=class extends J{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 Ct=class extends J{name="open-wrap-native-form";actionType="wrap_native";component="WrapNativeForm";userInputFields=[{key:"amount",label:"the amount to wrap"}];description='Open the WRAP NATIVE form so the user can wrap the chain\'s native coin into its wrapped ERC-20 (ETH \u2192 WETH, BNB \u2192 WBNB, MATIC \u2192 WMATIC, AVAX \u2192 WAVAX). Triggered by "wrap", "deposit into WETH", "convert ETH to WETH", "b\u1ECDc ETH". Only `amount` is needed. DO NOT use for arbitrary token swaps.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"amount",type:"string",description:'Human-readable amount of native coin to wrap (e.g. "0.5"). Omit when unknown.',required:!1}];async buildParameters(e,t){let n={},r=this.requireChain(e,t)??void 0,o=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:"native",symbol:"the native coin",chain:r,walletAddress:t?.walletAddress??void 0,gasKind:"wrap"});if(o&&"error"in o)return o;o&&(n.amount=o.amount);let s=await this.resolveNativeSpendableFormatted(r,t?.walletAddress??void 0,"wrap");return s!==void 0&&(n.spendable=s),n}};var Ut=class extends J{name="open-unwrap-native-form";actionType="unwrap_native";component="UnwrapNativeForm";userInputFields=[{key:"amount",label:"the amount to unwrap"}];description='Open the UNWRAP NATIVE form so the user can unwrap their wrapped native into the chain\'s native coin (WETH \u2192 ETH, WBNB \u2192 BNB, WMATIC \u2192 MATIC, WAVAX \u2192 AVAX). Triggered by "unwrap", "withdraw from WETH", "convert WETH to ETH", "th\xE1o WETH". Only `amount` is needed.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"amount",type:"string",description:'Human-readable amount of WRAPPED native to unwrap (e.g. "0.5"). Omit when unknown.',required:!1}];async buildParameters(e,t){let n={},r=this.normaliseAmount(e.amount);return r&&(n.amount=r),n}};var Rt=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 Y({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} \u3092\u8CB7\u3046". 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} \u3092\u8CB7\u3046". 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=Ie(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){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,f=await this.resolveBuyAmountSpec(e.buy_amount,i,o,l),g=m?await this.resolvePayAmountSpec(e.pay_with_amount,m,o,s,p):this.normaliseAmount(e.pay_with_amount),y=ue(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:f,payAmount:g}),!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:f,payAmount:g});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(f&&!g){this.dbg("Branch 1.3: deriving pay amount from buy amount",{buyAmount:f,destSymbol:l,paySymbol:p});let b=await this.derivePayAmountFromBuy({chain:o,buyAmount:f,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 ${f} ${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.`};g=b,w=f,k=!1,this.dbg("\u2192 Branch 1.3: derived pay amount",{payAmount:g,derivedFromBuy:w})}if(this.moralis&&t?.walletAddress){this.dbg("Branch 1.4: checking pay balance",{payAddr:m,paySymbol:p,payAmount:g});let b=await this.checkPayBalance({args:e,userContext:t,chain:o,destSymbol:l,destContract:i,payAddr:m,paySymbol:p,payAmount:g,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&&!g&&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(g&&(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:g});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 D(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=D(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()===L?"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=z(n),p=Number.parseInt(n,16);if(!m||!Number.isFinite(p))return null;let h=a==="native",f=h?m.native.decimals??18:l;if(f===void 0)return null;let g=this.toRawAmount(u,f);if(!g)return null;let y=h?L:a,k=r==="native"?L:r,w;try{w=await(await ce.getServiceByProvider("debridge")).getQuote({srcChainId:p,srcTokenAddress:y,srcTokenAmount:g,dstChainId:p,dstTokenAddress:k,recipientAddress:d,senderAddress:d,slippage:"auto",isCrossChain:!1})}catch(C){return{quoteError:C instanceof Error?C.message:String(C)}}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),x=P&&s!==void 0?this.trimAmount(Number(P)/10**s):void 0,A;h||(A=await this.buildApproveTx({chain:n,walletAddress:d,payAddr:a,payDec:f,rawAmount:g,quote:w}));let S={component:"BuyTokenConfirmTx",props:{chain:{hexId:n,name:m.name},payToken:{address:h?"native":a,symbol:i,decimals:f,amount:u,rawAmount:g},buyToken:{address:r,symbol:o,decimals:s,amount:x,rawAmount:P},estimatedOut:x,estimatedOutRaw:P,provider:w.provider,swapTx:T,approveTx:A}},_=x?` They will receive about ${x} ${o}.`:"";return{ui:S,_instructions:`A confirmation panel has been opened for buying ${o} with ${u} ${i??"the selected token"}.${_} 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"?L: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=ue(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=ue(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 ce.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=De({abi:fn,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),f=e.payAmount??this.normaliseAmount(t.pay_with_amount),g=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)})`:"",x=`${b}: ${T}${w}${P}`,A=h?this.buildBuyWithPrompt(g,r,w,h,null):this.buildBuyWithPrompt(g,r,w,null,f);return{label:x,prompt:A}}),_instructions:`Reply briefly in the user's language: ask them to pick which token they want to spend to buy ${r} from the options below. 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. Tell them, in their language, that they don't have any ${p}, then say they can instead choose one of the tokens they already hold (shown below) to pay with. 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=Number(i);if(!Number.isFinite(h)||h<=m.balanceNum)return null;let f=this.trimAmount(m.balanceNum),g=this.percentSpendButtons({balanceFormatted:m.balanceFormatted,balanceNum:m.balanceNum,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 ${f} ${p} on this chain \u2014 not enough. Tell them, in their language, that buying ${u} ${o} would cost about ${i} ${p}, which is more than their balance of ${f} ${p}, and ask them to buy a smaller amount or spend less \u2014 `:`The user wants to spend ${i} ${p} to buy ${o}, but they only have ${f} ${p} on this chain \u2014 not enough. Tell them, in their language, that ${i} ${p} exceeds their balance of ${f} ${p}, and ask them to pick a smaller amount \u2014 `;return{error:"insufficient_pay_balance",actionButtons:g,_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 St(n,"swap"),u=xt(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: tell them their spendable ${l} balance and ask how much they want to spend to buy ${r}, inviting 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%"). Just ask the question. 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 f=p.name?.trim()||p.symbol,g=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),b=w?k>0?"\u25B2":k<0?"\u25BC":"\u25AA":"",T=w?` (${b} ${this.formatPercent(k)}%)`:"";return`${h+1}. ${f} (${g})
|
|
170
170
|
${y}${T}`}).join(`
|
|
171
171
|
-----
|
|
172
172
|
`)+`
|
|
173
173
|
---
|
|
174
174
|
Is there any token you want to buy?`;return{actionButtons:u,_instructions:`The trending tokens are on chain id "${n??"the connected chain"}" \u2014 use the human-readable chain name. Reply in the user's language. Start with a one-line header like "\u{1F4C8} Here is the list of tokens that are currently trending upwards on <chain name> chain", then output EXACTLY the following list, preserving the line breaks, ordering, prices, and percentages verbatim (translate only the wording, never the numbers). Do NOT add or remove tokens, and do NOT mention tool names, UI, or forms:
|
|
175
175
|
|
|
176
|
-
${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 Et=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 Y({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=Ie(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"),c=i.address,u=i.symbol??n,d=i.decimals,p=await this.resolveSide(e.to_address,o,s,a,"to"),m=p.address,h=p.symbol??o,f=p.decimals,g=await this.resolveToAmountSpec(e.to_amount,m,s,h),y=c?await this.resolveFromAmountSpec(e.from_amount,c,s,a,u):this.normaliseAmount(e.from_amount),k=ue(e.from_amount),w=!!k&&k.kind!=="token",b=null;if(this.dbg("resolved sides",{from:{fromContract:c,fromSym:u,fromDecimals:d},to:{toContract:m,toSym:h,toDecimals:f},fromAmount:y,toAmount:g}),!c)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:c,fromAmount:y,toAmount:g});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(g&&!y){this.dbg("Branch 1.3: deriving from amount from to amount",{toAmount:g,fromSym:u,toSym:h});let T=await this.deriveFromAmountFromTo({chain:s,toAmount:g,toContract:m,toSym:h,fromContract:c,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 ${g} ${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=g,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:c,fromSym:u,fromAmount:y});let T=await this.checkFromBalance({args:e,userContext:t,chain:s,fromContract:c,fromSym:u,fromAmount:y,toAmount:g,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&&!g&&this.moralis&&t?.walletAddress){this.dbg("Branch 1.5: building amount picker");let T=await this.buildAmountPicker({userContext:t,chain:s,fromContract:c,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||c==="native")){this.dbg("Branch 1.75: building confirm tx");let T=await this.buildConfirmTx({userContext:t,chain:s,toContract:m,toSym:h,toDecimals:f,fromContract:c,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 D(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=D(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 c=i.data.result.find(s);if(c)return{address:c.token_address,symbol:c.symbol,name:c.name,decimals:typeof c.decimals=="number"?c.decimals:void 0}}}let a=await this.moralis.searchTokensByKey({key:e,chain:t});if(a.success&&a.data?.length){let c=a.data.find(s)??a.data[0];if(c?.token_address)return{address:c.token_address,symbol:c.symbol,name:c.name,decimals:typeof c.decimals=="number"?c.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),c=a||void 0,u=m=>m.toLowerCase()===L?"native":m,d=i??c??(s||void 0);if(!d)return this.dbg(`resolveSide:${o}`,{refStr:s,symbolStr:a,chain:n,resolved:"none"}),{address:null,symbol:c};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:c??p.symbol,decimals:p.decimals}:i?{address:u(i),symbol:c}:{address:null,symbol:c}}async buildConfirmTx(e){let{userContext:t,chain:n,toContract:r,toSym:o,toDecimals:s,fromContract:a,fromSym:i,fromDecimals:c,fromAmount:u}=e,d=t?.walletAddress;if(!d||!n||!r)return null;let p=z(n),m=Number.parseInt(n,16);if(!p||!Number.isFinite(m))return null;let h=a==="native",f=h?p.native.decimals??18:c;if(f===void 0)return null;let g=this.toRawAmount(u,f);if(!g)return null;let y=h?L:a,k=r==="native"?L:r,w;try{w=await(await ce.getServiceByProvider("debridge")).getQuote({srcChainId:m,srcTokenAddress:y,srcTokenAmount:g,dstChainId:m,dstTokenAddress:k,recipientAddress:d,senderAddress:d,slippage:"auto",isCrossChain:!1})}catch(C){return{quoteError:C instanceof Error?C.message:String(C)}}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),x=P&&s!==void 0?this.trimAmount(Number(P)/10**s):void 0,A;h||(A=await this.buildApproveTx({chain:n,walletAddress:d,fromAddr:a,fromDec:f,rawAmount:g,quote:w}));let S={component:"SwapTokenConfirmTx",props:{chain:{hexId:n,name:p.name},fromToken:{address:h?"native":a,symbol:i,decimals:f,amount:u,rawAmount:g},toToken:{address:r,symbol:o,decimals:s,amount:x,rawAmount:P},estimatedOut:x,estimatedOutRaw:P,provider:w.provider,swapTx:T,approveTx:A}},_=x?` They will receive about ${x} ${o}.`:"";return{ui:S,_instructions:`A confirmation panel has been opened for swapping ${u} ${i??"the source token"} to ${o}.${_} 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 c=1.02,u=i*s/a;return!Number.isFinite(u)||u<=0?null:this.trimAmount(u*c)}async getUsdPrice(e,t){if(!this.moralis)return null;let n=e==="native"?L: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=ue(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=ue(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 c=await(await ce.getServiceByProvider("debridge")).checkApproval({chain:t,userAddress:n,tokenAddress:r,amount:s,tokenDecimals:o,quoteData:a});if(!c.isNeeded)return;let u=c.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=c.contractAddress;if(!d)return;let p=De({abi:fn,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)} `:"",h=typeof u.usd_value=="number"&&u.usd_value>0?` ($${u.usd_value.toFixed(2)})`:"";return{label:`${p}: ${m}${d}${h}`,prompt:this.buildSwapFromPrompt(i,d)}}),_instructions:"Reply briefly in the user's language: ask them to pick which token they want to swap from the options below. 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,c=Math.max(1,Math.min(10,i)),u=await a.getTrendingTokens({chain:s,limit:c+2}),d=u.success?u.data??[]:[],p=o?.toLowerCase(),m=r.toLowerCase(),h=d.filter(T=>!(!T.symbol||p&&p!=="native"&&T.tokenAddress&&T.tokenAddress.toLowerCase()===p||T.symbol.toLowerCase()===m)).slice(0,c);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 f=e.fromAmount??this.normaliseAmount(t.from_amount),g=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,x=f?this.buildSwapToPrompt(y,r,P,f,null):this.buildSwapToPrompt(y,r,P,null,g);return{label:P,prompt:x}}),b=h.map((T,P)=>{let x=T.name?.trim()||T.symbol,A=T.symbol,I=typeof T.usdPrice=="number"&&T.usdPrice>0?`$${this.formatPrice(T.usdPrice)}`:"N/A",S=T.pricePercentChange?.["24h"],_=typeof S=="number"&&Number.isFinite(S),C=_?S>0?"\u25B2":S<0?"\u25BC":"\u25AA":"",q=_?` (${C} ${this.formatPercent(S)}%)`:"";return`${P+1}. ${x} (${A})
|
|
176
|
+
${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 Et=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 Y({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=Ie(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,f=m.decimals,g=await this.resolveToAmountSpec(e.to_amount,p,s,h),y=l?await this.resolveFromAmountSpec(e.from_amount,l,s,a,u):this.normaliseAmount(e.from_amount),k=ue(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:f},fromAmount:y,toAmount:g}),!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:g});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(g&&!y){this.dbg("Branch 1.3: deriving from amount from to amount",{toAmount:g,fromSym:u,toSym:h});let T=await this.deriveFromAmountFromTo({chain:s,toAmount:g,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 ${g} ${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=g,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:g,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&&!g&&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: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 ${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 D(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=D(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()===L?"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=z(n),p=Number.parseInt(n,16);if(!m||!Number.isFinite(p))return null;let h=a==="native",f=h?m.native.decimals??18:l;if(f===void 0)return null;let g=this.toRawAmount(u,f);if(!g)return null;let y=h?L:a,k=r==="native"?L:r,w;try{w=await(await ce.getServiceByProvider("debridge")).getQuote({srcChainId:p,srcTokenAddress:y,srcTokenAmount:g,dstChainId:p,dstTokenAddress:k,recipientAddress:d,senderAddress:d,slippage:"auto",isCrossChain:!1})}catch(C){return{quoteError:C instanceof Error?C.message:String(C)}}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),x=P&&s!==void 0?this.trimAmount(Number(P)/10**s):void 0,A;h||(A=await this.buildApproveTx({chain:n,walletAddress:d,fromAddr:a,fromDec:f,rawAmount:g,quote:w}));let S={component:"SwapTokenConfirmTx",props:{chain:{hexId:n,name:m.name},fromToken:{address:h?"native":a,symbol:i,decimals:f,amount:u,rawAmount:g},toToken:{address:r,symbol:o,decimals:s,amount:x,rawAmount:P},estimatedOut:x,estimatedOutRaw:P,provider:w.provider,swapTx:T,approveTx:A}},_=x?` They will receive about ${x} ${o}.`:"";return{ui:S,_instructions:`A confirmation panel has been opened for swapping ${u} ${i??"the source token"} to ${o}.${_} 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"?L: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=ue(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=ue(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 ce.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=De({abi:fn,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: ask them to pick which token they want to swap from the options below. 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 f=e.fromAmount??this.normaliseAmount(t.from_amount),g=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,x=f?this.buildSwapToPrompt(y,r,P,f,null):this.buildSwapToPrompt(y,r,P,null,g);return{label:P,prompt:x}}),b=h.map((T,P)=>{let x=T.name?.trim()||T.symbol,A=T.symbol,I=typeof T.usdPrice=="number"&&T.usdPrice>0?`$${this.formatPrice(T.usdPrice)}`:"N/A",S=T.pricePercentChange?.["24h"],_=typeof S=="number"&&Number.isFinite(S),C=_?S>0?"\u25B2":S<0?"\u25BC":"\u25AA":"",q=_?` (${C} ${this.formatPercent(S)}%)`:"";return`${P+1}. ${x} (${A})
|
|
177
177
|
${I}${q}`}).join(`
|
|
178
178
|
-----
|
|
179
179
|
`)+`
|
|
180
180
|
---
|
|
181
181
|
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 chain id "${s??"the connected chain"}" \u2014 use the human-readable chain name. Reply in the user's language. Start with a one-line header like "\u{1F4C8} Here are tokens currently trending on <chain name> that you can swap ${r} into", then output EXACTLY the following list, preserving the line breaks, ordering, prices, and percentages verbatim (translate only the wording, never the numbers). Do NOT add or remove tokens, and do NOT mention tool names, UI, or forms:
|
|
182
182
|
|
|
183
|
-
${b}`}}async checkFromBalance(e){let{args:t,userContext:n,chain:r,fromContract:o,fromAmount:s,derivedFromTo:a}=e,i=n.walletAddress,
|
|
183
|
+
${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. Tell them, in their language, that they don't have any ${u}, then say they can instead choose one of the tokens they already hold (shown below) to swap from. 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 m=this.trimAmount(l.balanceNum),p=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:""}),h=a?`Receiving ${a} of the destination token needs about ${s} ${u}, but they only have ${m} ${u} on this chain \u2014 not enough. Tell them, in their language, that this would cost about ${s} ${u}, which is more than their balance of ${m} ${u}, and ask them to swap a smaller amount \u2014 `:`The user wants to swap ${s} ${u}, but they only have ${m} ${u} on this chain \u2014 not enough. Tell them, in their language, that ${s} ${u} exceeds their balance of ${m} ${u}, and ask them to pick 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 St(n,"swap"),u=xt(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: tell them their spendable ${l} balance and ask how much they want to swap to ${o}, inviting 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%"). Just ask the question. 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 Nt=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 It=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(`
|
|
184
184
|
`),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:
|
|
185
185
|
|
|
186
186
|
${n}`,timestamp:Date.now()}];return(await this.llm.chat(r)).text}};var Us=`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.
|
|
@@ -201,9 +201,9 @@ FAQ ENTRIES:
|
|
|
201
201
|
`,Dt=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(`
|
|
202
202
|
`),r=[{role:"user",content:Us+n+`
|
|
203
203
|
|
|
204
|
-
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]),
|
|
204
|
+
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 Kt=require("@upstash/vector"),Rs="https://perfect-octopus-59684-us1-vector.upstash.io",Es="ABkFMHBlcmZlY3Qtb2N0b3B1cy01OTY4NC11czFhZG1pbk56WXdOV0k0T0RndFpUaGtOeTAwWWpjMkxXRmtNRFV0WW1WaU9UaGtOMlV5T0ROaQ==",Se=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)??Rs,n=e.token??(typeof process<"u"?process.env.UPSTASH_VECTOR_REST_TOKEN:void 0)??Es;this.index=new Kt.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"?Kt.FusionAlgorithm.RRF:Kt.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}
|
|
205
205
|
|
|
206
|
-
${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 de(
|
|
206
|
+
${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 de(c){return c.filter(e=>e.role!=="tool"&&!(e.role==="assistant"&&e.toolCalls&&e.toolCalls.length>0)&&!e._intermediate)}function Ro(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 ye(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 Eo(c,e){return ye(c,e)}function No(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 Io(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(`
|
|
207
207
|
`)}var Ns=`You are a ROUTER.
|
|
208
208
|
|
|
209
209
|
The incoming user query has ALREADY been contextualized upstream \u2014 it is a
|
|
@@ -249,20 +249,20 @@ ${b.description}`).join(`
|
|
|
249
249
|
|
|
250
250
|
User context:
|
|
251
251
|
${a.join(`
|
|
252
|
-
`)}`:"",
|
|
252
|
+
`)}`:"",l=o.length>0?`
|
|
253
253
|
|
|
254
254
|
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=de(t),d=ye(u,this.historyMessages);this.debug&&(console.log(`
|
|
255
|
-
[Router] history window (${d.length} msg${d.length===1?"":"s"}):`),console.log(`[Router] user query: "${e}"`));let
|
|
256
|
-
${s}${i}${
|
|
255
|
+
[Router] history window (${d.length} msg${d.length===1?"":"s"}):`),console.log(`[Router] user query: "${e}"`));let m=[{role:"system",content:Ns,timestamp:0},...d,{role:"user",content:`Available subagents:
|
|
256
|
+
${s}${i}${l}
|
|
257
257
|
|
|
258
|
-
User query: ${e}`,timestamp:Date.now()}],
|
|
258
|
+
User query: ${e}`,timestamp:Date.now()}],p=Date.now(),h=null,f="",g=null;for(let b=1;b<=2;b++){let T=await this.llm.chat(m);f=T.text;try{h=this.parseJSON(T.text);break}catch(P){g=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: ${g}`),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(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(`
|
|
259
259
|
[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(`
|
|
260
|
-
[Router] dispatching ${e.assignments.length} subagent(s) in parallel\u2026`);let o=Date.now(),s=e.assignments.map(async i=>{let
|
|
261
|
-
[${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,
|
|
260
|
+
[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 V=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,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(`
|
|
261
|
+
[${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 m=d.length>0?`User context:
|
|
262
262
|
${d.join(`
|
|
263
|
-
`)}`:"",
|
|
263
|
+
`)}`:"",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(`
|
|
264
264
|
|
|
265
|
-
`),f=Ro(t,this.toolNames),g=ye(f,this.historyMessages),y=[{role:"system",content:this.systemPrompt,timestamp:0},...g,{role:"user",content:h,timestamp:Date.now()}],k=6e3,w=this.maxToolCalls+2;for(let T=0;T<w;T++){let P=
|
|
265
|
+
`),f=Ro(t,this.toolNames),g=ye(f,this.historyMessages),y=[{role:"system",content:this.systemPrompt,timestamp:0},...g,{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,x=P?r:void 0,A=await this.llm.chat(y,x);if(A.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: "${A.text.slice(0,80)}\u2026"`),{subagentName:this.name,steps:o,finalAnswer:A.text,toolResults:s,toolMessages:a}}let I={role:"assistant",content:A.text||"",toolCalls:A.toolCalls,timestamp:Date.now()};y.push(I);let S=[];for(let _ of A.toolCalls){if(!this.toolNames.has(_.toolName)){let G=`Tool "${_.toolName}" is not owned by ${this.name}`;this.debug&&console.log(` [${this.name}] WARN: ${G}`),o.push({phase:"observe",content:G,timestamp:Date.now()}),y.push({role:"tool",content:G,toolName:_.toolName,toolCallId:_.callId,timestamp:Date.now()});continue}let C=`${_.toolName}::${JSON.stringify(_.args)}`;if(i.has(C)){let G=`Skipped duplicate call: ${_.toolName}`;this.debug&&console.log(` [${this.name}] SKIP duplicate: ${_.toolName}`),o.push({phase:"observe",content:G,timestamp:Date.now()}),y.push({role:"tool",content:G,toolName:_.toolName,toolCallId:_.callId,timestamp:Date.now()});continue}i.add(C),l++,this.debug&&console.log(` [${this.name}] call #${l}: ${_.toolName} ${JSON.stringify(_.args)}`),o.push({phase:"think",content:A.text||`Calling ${_.toolName}`,timestamp:Date.now()}),o.push({phase:"act",content:`Calling tool: ${_.toolName}`,toolCall:{toolName:_.toolName,args:_.args},timestamp:Date.now()});let q=Date.now(),K=await this.registry.execute(_.toolName,_.args,n);s.push(K);let H=K.success?JSON.stringify(K.data):`Error: ${K.error}`;if(this.debug){let G=K.success?"OK":"ERR",be=K.success?`${H.slice(0,120)}${H.length>120?"\u2026":""}`:K.error;console.log(` [${this.name}] ${G} (${Date.now()-q}ms): ${be}`)}o.push({phase:"observe",content:H,toolResult:K,timestamp:Date.now()});let ae={role:"tool",content:H,toolName:_.toolName,toolCallId:_.callId,timestamp:Date.now()};y.push(ae),S.push({...ae,content:H.length>k?H.slice(0,k)+"\u2026[truncated]":H})}S.length>0&&(a.push(I),a.push(...S))}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 Is=`You are the Wallet domain expert. You answer questions about a specific wallet's on-chain state.
|
|
266
266
|
|
|
267
267
|
SPECIALISED TOOLS (prefer these when intent matches):
|
|
268
268
|
- get-wallet-token-balances: token + native coin balances
|
|
@@ -313,7 +313,7 @@ PAGINATION:
|
|
|
313
313
|
pass the cursor from the LAST history response in conversation. Always tell the user
|
|
314
314
|
how many results were returned and whether more pages exist.
|
|
315
315
|
|
|
316
|
-
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 .`,yn=["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 jt(
|
|
316
|
+
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 .`,yn=["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 jt(c,e,t){return new V({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:[...yn],systemPrompt:Is,cortexFallbackTool:"gemini-search-ai",llm:c,registry:e,options:t})}var Ds=`You are the Token domain expert. You answer market-wide token questions, not wallet-specific ones.
|
|
317
317
|
|
|
318
318
|
SPECIALISED TOOLS (prefer these when intent matches):
|
|
319
319
|
- get-token-info: metadata (name, symbol, decimals, address, logo)
|
|
@@ -354,7 +354,7 @@ GENERAL RULES:
|
|
|
354
354
|
- Never repeat a tool call with identical arguments.
|
|
355
355
|
- Answer in the user's language. Do not mention tool names.
|
|
356
356
|
|
|
357
|
-
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-token-info","get-token-holders","get-token-analytics","get-token-score","get-trending-tokens","get-top-gainers","gemini-search-ai"];function Yt(
|
|
357
|
+
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-token-info","get-token-holders","get-token-analytics","get-token-score","get-trending-tokens","get-top-gainers","gemini-search-ai"];function Yt(c,e,t){return new V({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:[...bn],systemPrompt:Ds,cortexFallbackTool:"gemini-search-ai",llm:c,registry:e,options:t})}var Ls=`You are the NFT domain expert. You answer questions about NFT holdings and metadata, not transfer history.
|
|
358
358
|
|
|
359
359
|
SPECIALISED TOOLS (prefer these when intent matches):
|
|
360
360
|
- get-wallet-nfts: user wants to view / browse their NFTs / holdings \u2014 returns a fixed website notice
|
|
@@ -399,7 +399,7 @@ GENERAL RULES:
|
|
|
399
399
|
- One tool per turn. Never repeat a tool call with identical arguments.
|
|
400
400
|
- Answer in the user's language. Do not mention tool names.
|
|
401
401
|
|
|
402
|
-
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.`,wn=["get-wallet-nfts","get-nft-metadata","get-nft-contract-info","send-nft","gemini-search-ai"];function zt(
|
|
402
|
+
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.`,wn=["get-wallet-nfts","get-nft-metadata","get-nft-contract-info","send-nft","gemini-search-ai"];function zt(c,e,t){return new V({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:[...wn],systemPrompt:Ls,cortexFallbackTool:"gemini-search-ai",llm:c,registry:e,options:t})}var Ms=`You are the Crypto AI fallback expert powered by a search-grounded Gemini model.
|
|
403
403
|
|
|
404
404
|
You answer ANY blockchain / crypto / token / NFT / DeFi / on-chain question that does not have a
|
|
405
405
|
direct structured tool in the other agents. The underlying model has live web-search grounding \u2014
|
|
@@ -418,7 +418,7 @@ ROUTING:
|
|
|
418
418
|
RULES:
|
|
419
419
|
- ALWAYS call the tool \u2014 NEVER refuse, NEVER answer without calling it.
|
|
420
420
|
- NEVER retry with the same prompt if the call fails \u2014 return what you have.
|
|
421
|
-
- Answer in the user's language. Do NOT mention tool names.`,kn=["gemini-search-ai"];function Qt(
|
|
421
|
+
- Answer in the user's language. Do NOT mention tool names.`,kn=["gemini-search-ai"];function Qt(c,e,t){return new V({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:[...kn],systemPrompt:Ms,cortexFallbackTool:"gemini-search-ai",llm:c,registry:e,options:t})}var Bs=`You are the Pool / DEX Liquidity domain expert.
|
|
422
422
|
|
|
423
423
|
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.
|
|
424
424
|
|
|
@@ -559,7 +559,7 @@ State awareness:
|
|
|
559
559
|
|
|
560
560
|
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.
|
|
561
561
|
|
|
562
|
-
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 .`,Tn=["get-top-pools","get-pool-detail","search-pools","lookup-pool-by-address","open-add-liquidity-form","preview-add-liquidity","estimate_pool_yield"];function Xt(
|
|
562
|
+
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 .`,Tn=["get-top-pools","get-pool-detail","search-pools","lookup-pool-by-address","open-add-liquidity-form","preview-add-liquidity","estimate_pool_yield"];function Xt(c,e,t){return new V({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:[...Tn],systemPrompt:Bs,llm:c,registry:e,options:t})}var Do=`You are the Uniswap V3 Subgraph DEX-pool expert.
|
|
563
563
|
|
|
564
564
|
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.
|
|
565
565
|
|
|
@@ -621,10 +621,10 @@ COINPOOL RANGE GUIDANCE (when using subgraph-coinpool-pairs):
|
|
|
621
621
|
FOLLOW-UPS / CONTINUATIONS:
|
|
622
622
|
- 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.
|
|
623
623
|
|
|
624
|
-
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 Os(
|
|
624
|
+
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 Os(c){return c?`${Do}
|
|
625
625
|
|
|
626
626
|
ADD-LIQUIDITY LINK:
|
|
627
|
-
- 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: ${
|
|
627
|
+
- 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}`:Do}var Lo=["subgraph-search-pools","subgraph-trending-pools","subgraph-pool-by-address","subgraph-pool-by-position-id","subgraph-position-detail","subgraph-coinpool-pairs"];function Wn(c,e,t){return new V({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:[...Lo],systemPrompt:Os(t?.linkAddLiquidity),llm:c,registry:e,options:t})}var $s=`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.
|
|
628
628
|
|
|
629
629
|
Tools:
|
|
630
630
|
- open-send-native-form \u2014 native coin (ETH/BNB/MATIC/\u2026)
|
|
@@ -645,7 +645,7 @@ NFT sends are NOT handled here \u2014 they belong to the nft-agent.
|
|
|
645
645
|
|
|
646
646
|
Skip the tool ONLY if: walletAddress missing (ask to connect) \xB7 user explicitly names a chain different from userContext.chain (ask to switch).
|
|
647
647
|
|
|
648
|
-
After the tool returns: 1 short sentence in the user's language. Don't list missing fields. Don't mention tool names.`,vn=["open-send-native-form","open-send-token-form","open-buy-token-form","open-swap-token-form","open-approve-token-form"];function Jt(
|
|
648
|
+
After the tool returns: 1 short sentence in the user's language. Don't list missing fields. Don't mention tool names.`,vn=["open-send-native-form","open-send-token-form","open-buy-token-form","open-swap-token-form","open-approve-token-form"];function Jt(c,e,t){return new V({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:[...vn],systemPrompt:$s,mustCallTool:!0,llm:c,registry:e,options:t})}function Sn(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(jt(c,e,t)),r("wallet-action",!0)&&i.push(Jt(c,e,t)),r("token",!0)&&i.push(Yt(c,e,t)),r("nft",!0)&&i.push(zt(c,e,t)),a&&i.push(Xt(c,e,t)),o&&i.push(Wn(c,e,t)),r("ai",!0)&&i.push(Qt(c,e,t)),i}var qs=`You are a response synthesizer. Given the user's question and tool execution results, produce a clear, helpful final answer.
|
|
649
649
|
|
|
650
650
|
Rules:
|
|
651
651
|
- Be concise but complete.
|
|
@@ -654,10 +654,10 @@ Rules:
|
|
|
654
654
|
- If tools returned errors, explain honestly what happened.
|
|
655
655
|
- Respond in the same language as the user's message.
|
|
656
656
|
- Do NOT mention internal tool names, JSON structures, or technical implementation details.
|
|
657
|
-
`,Mt=class{llm;constructor(e){this.llm=e}async synthesise(e,t,n,r){if(!t.steps.some(
|
|
658
|
-
`),a=de(n),i=ye(a,8),
|
|
657
|
+
`,Mt=class{llm;constructor(e){this.llm=e}async synthesise(e,t,n,r){if(!t.steps.some(p=>p.phase==="act")&&t.finalAnswer)return Mo(t.finalAnswer);let s=t.steps.filter(p=>p.phase==="observe").map((p,h)=>`[Result ${h+1}]: ${p.content}`).join(`
|
|
658
|
+
`),a=de(n),i=ye(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:qs,timestamp:0},...i,{role:"user",content:[`User question: ${e}`,"",`Agent reasoning: ${t.finalAnswer}`,"",s?`Tool results:
|
|
659
659
|
${s}`:"","",u].filter(Boolean).join(`
|
|
660
|
-
`),timestamp:Date.now()}],
|
|
660
|
+
`),timestamp:Date.now()}],m=await this.llm.chat(d);return Mo(m.text)}};function Mo(c){return c&&c.replace(/^\[[a-z0-9-]+-agent\]\s*/i,"").replace(/\n\n\[[a-z0-9-]+-agent\]\s*/gi,`
|
|
661
661
|
|
|
662
662
|
`)}var Fs=`You are a CONTEXTUALIZER.
|
|
663
663
|
|
|
@@ -809,15 +809,15 @@ Examples:
|
|
|
809
809
|
"\uC548\uB155\uD558\uC138\uC694" \u2192 {"language":"ko"}
|
|
810
810
|
"\u043F\u0440\u0438\u0432\u0435\u0442" \u2192 {"language":"ru"}
|
|
811
811
|
"\u0E2A\u0E27\u0E31\u0E2A\u0E14\u0E35" \u2192 {"language":"th"}
|
|
812
|
-
"123" \u2192 {"language":""}`,xn=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=Io(t),s=de(t),a=ye(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
|
|
812
|
+
"123" \u2192 {"language":""}`,xn=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=Io(t),s=de(t),a=ye(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?`
|
|
813
813
|
|
|
814
814
|
${i.join(`
|
|
815
815
|
|
|
816
816
|
`)}`:"",u=Date.now(),d=[{role:"system",content:Fs,timestamp:0},...a,{role:"user",content:`Latest user message to contextualize:
|
|
817
|
-
${e}${
|
|
817
|
+
${e}${l}
|
|
818
818
|
|
|
819
|
-
Return JSON only.`,timestamp:Date.now()}],
|
|
820
|
-
[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 N=require("@langchain/langgraph");var Z=l=>({reducer:(e,t)=>t,default:l}),Vs=N.Annotation.Root({userMessage:(0,N.Annotation)(),doSuggest:(0,N.Annotation)(Z(()=>!0)),overrideLang:(0,N.Annotation)(Z(()=>"")),conversationMessages:(0,N.Annotation)(Z(()=>[])),lastSubagents:(0,N.Annotation)(Z(()=>[])),messageId:(0,N.Annotation)(Z(()=>"")),rewrite:(0,N.Annotation)(Z(()=>null)),effectiveQuery:(0,N.Annotation)(Z(()=>"")),turnLanguage:(0,N.Annotation)(Z(()=>"")),kbContext:(0,N.Annotation)(Z(()=>"")),decision:(0,N.Annotation)(Z(()=>null)),assignment:(0,N.Annotation)(Z(()=>null)),assignmentIndex:(0,N.Annotation)(Z(()=>0)),subResults:(0,N.Annotation)({reducer:(l,e)=>l.concat(e),default:()=>[]}),response:(0,N.Annotation)(Z(()=>null))});function Bo(l){let e=async d=>{l.addUserMessage(d.userMessage),l.needsSummary()&&await l.compactHistory();let p=l.conversationForTurn(),m=l.lastAssistantSubagents(p);return l.debug&&m.length>0&&console.log(`[AgentCore] previous turn handled by: ${m.join(", ")}`),{conversationMessages:p,lastSubagents:m,messageId:l.generateMessageId()}},t=async d=>{let p=await l.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,h=await l.searchKB(m),f=l.formatKBContext(h),g=await l.tryAnswerFromKB(d.userMessage,h,f,d.messageId);return g?{kbContext:f,response:{...g,messageId:d.messageId,rewrite:p}}:{kbContext:f}},r=async d=>({decision:await l.route(d.effectiveQuery,d.conversationMessages,l.subagentCards(),d.lastSubagents)}),o=async d=>({response:{...await l.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 l.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);l.persistToolMessages(p);let m=l.mergeTrace(p),h=await l.synthesise(d.userMessage,m,l.synthesiserHistory(),d.turnLanguage||void 0),f=p.map(b=>b.subagentName),g=l.collectUiActions(p,d.turnLanguage||null),y=l.collectActionButtons(p,d.turnLanguage||null),k=l.subResultsTriggerNoSuggestions(p);return{response:{...await l.finaliseAnswer({userMessage:d.userMessage,answer:h,subagents:f,uiActions:g,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,...g.length>0?{uiActions:g}:{},...y.length>0?{actionButtons:y}:{}}}},i=async d=>{let p=(d.rewrite?.mentionedChain||String(l.connectedChain()??"")).trim();return{response:{...await l.answerUnsupportedChain(p,d.turnLanguage,d.messageId),messageId:d.messageId,routerDecision:d.decision,rewrite:d.rewrite}}},c=d=>d.response?N.END:"route",u=d=>{let p=d.decision?.assignments??[];if(p.length===0)return"direct";let m=p.some(f=>f.subagent!=="ai-agent"),h=(d.rewrite?.mentionedChain||l.connectedChain()||"").trim();return m&&h!==""&&!jn(h)?"unsupportedChain":p.map((f,g)=>new N.Send("subagent",{...d,assignment:f,assignmentIndex:g}))};return new N.StateGraph(Vs).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(N.START,"setup").addEdge("setup","contextualize").addEdge("contextualize","kb").addConditionalEdges("kb",c,["route",N.END]).addConditionalEdges("route",u,["direct","subagent","unsupportedChain"]).addEdge("unsupportedChain",N.END).addEdge("direct",N.END).addEdge("subagent","synthesize").addEdge("synthesize",N.END).compile()}var Oo=require("js-sha3");function Pn(){let l=Date.now().toString(16),e=Math.floor(Math.random()*65535).toString(16).padStart(4,"0");return(l+e).slice(-8)}var Hs="You are a helpful AI assistant. Answer the user accurately and concisely. Respond in the same language as the user.",An=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,$n(e.rpcUrls),this.llm=new le(e.llm),this.registry=new Ee,this.history=new Nt(e.maxHistoryMessages??50),this.summarizer=new It(this.llm),this.synthesizer=new Mt(this.llm),this.systemPrompt=e.systemPrompt??Hs,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 Dt(e.knowledgeBase),this.kbEntries.setLLM(this.llm);let t=e.vectorKB;t?.enabled?(t.provider?this.kb=t.provider:(this.vectorKB=new Se({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 Fe(u)),this.registry.register(new Le(u)),this.registry.register(new We(u)),this.registry.register(new Ve(u)),this.registry.register(new He(u)),this.registry.register(new Me(u)),this.registry.register(new Ge(u)),this.registry.register(new Ke(u)),this.registry.register(new Xe(u)),this.registry.register(new Ye(u)),this.registry.register(new ze(u)),this.registry.register(new Qe(u)),this.registry.register(new je(u)),this.registry.register(new $e(u)),this.registry.register(new qe(u)),this.registry.register(new Be(u)),this.registry.register(new Oe(u))}let r=e.nftLink;this.registry.register(new Je({url:r})),this.registry.register(new Ze({url:r})),this.registry.register(new et({url:r})),this.registry.register(new tt({url:r})),this.registry.register(new nt(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 ot(u)),this.registry.register(new rt(u)),this.registry.register(new st(u)),this.registry.register(new at(u)),this.registry.register(new ft(u));let d=u?{isProduction:u.isProduction}:void 0;this.registry.register(new ht({baseUrl:u?.baseUrl,pool:d,minProvideUsd:u?.minProvideUsd})),this.registry.register(new gt({pool:d}))}if(o&&e.subgraph!==!1){let u=typeof e.subgraph=="object"?e.subgraph:void 0;this.registry.register(new yt(u)),this.registry.register(new bt(u)),this.registry.register(new wt(u)),this.registry.register(new kt(u)),this.registry.register(new Tt(u)),this.registry.register(new vt(u))}let a=e.moralis===!1?void 0:typeof e.moralis=="object"?e.moralis:{};this.registry.register(new Pt(a)),this.registry.register(new At(a)),this.registry.register(new _t(a)),this.registry.register(new Ct(a)),this.registry.register(new Ut(a)),this.registry.register(new Rt(a,{debug:this.debug})),this.registry.register(new Et(a,{debug:this.debug})),this.router=new Lt(this.llm,{debug:this.debug}),this.rewriter=new xn(this.llm,{debug:this.debug});let i=typeof e.subgraph=="object"?e.subgraph:void 0,c=Sn(this.llm,this.registry,{maxToolCalls:e.maxIterations??5,debug:this.debug,linkAddLiquidity:i?.linkAddLiquidity},e.subagents);this.subagents=new Map(c.map(u=>[u.name,u])),this.chatGraph=Bo(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:Pn()};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"}`,c=Pn(),u=s.success&&s.ui?[this.stampLanguage(s.ui,null)]:[],d={role:"assistant",content:i,timestamp:Date.now(),subagents:["direct-invoke"],messageId:c};return u.length>0&&(d.uiActions=u),this.history.add(d),await this.persistHistory(),{answer:i,messageId:c,...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,Oo.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(`
|
|
819
|
+
Return JSON only.`,timestamp:Date.now()}],m=[{role:"system",content:Ws,timestamp:0},{role:"user",content:e,timestamp:Date.now()}],[p,h]=await Promise.allSettled([this.llm.chat(d),this.llm.chat(m)]),f={};if(p.status==="fulfilled")try{f=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 g="";if(h.status==="fulfilled")try{let k=this.parseJSON(h.value.text);typeof k.language=="string"&&(g=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 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:g,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(`
|
|
820
|
+
[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 N=require("@langchain/langgraph");var Z=c=>({reducer:(e,t)=>t,default:c}),Vs=N.Annotation.Root({userMessage:(0,N.Annotation)(),doSuggest:(0,N.Annotation)(Z(()=>!0)),overrideLang:(0,N.Annotation)(Z(()=>"")),conversationMessages:(0,N.Annotation)(Z(()=>[])),lastSubagents:(0,N.Annotation)(Z(()=>[])),messageId:(0,N.Annotation)(Z(()=>"")),rewrite:(0,N.Annotation)(Z(()=>null)),effectiveQuery:(0,N.Annotation)(Z(()=>"")),turnLanguage:(0,N.Annotation)(Z(()=>"")),kbContext:(0,N.Annotation)(Z(()=>"")),decision:(0,N.Annotation)(Z(()=>null)),assignment:(0,N.Annotation)(Z(()=>null)),assignmentIndex:(0,N.Annotation)(Z(()=>0)),subResults:(0,N.Annotation)({reducer:(c,e)=>c.concat(e),default:()=>[]}),response:(0,N.Annotation)(Z(()=>null))});function Bo(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),f=c.formatKBContext(h),g=await c.tryAnswerFromKB(d.userMessage,h,f,d.messageId);return g?{kbContext:f,response:{...g,messageId:d.messageId,rewrite:m}}:{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 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),f=m.map(b=>b.subagentName),g=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:f,uiActions:g,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,...g.length>0?{uiActions:g}:{},...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?N.END:"route",u=d=>{let m=d.decision?.assignments??[];if(m.length===0)return"direct";let p=m.some(f=>f.subagent!=="ai-agent"),h=(d.rewrite?.mentionedChain||c.connectedChain()||"").trim();return p&&h!==""&&!jn(h)?"unsupportedChain":m.map((f,g)=>new N.Send("subagent",{...d,assignment:f,assignmentIndex:g}))};return new N.StateGraph(Vs).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(N.START,"setup").addEdge("setup","contextualize").addEdge("contextualize","kb").addConditionalEdges("kb",l,["route",N.END]).addConditionalEdges("route",u,["direct","subagent","unsupportedChain"]).addEdge("unsupportedChain",N.END).addEdge("direct",N.END).addEdge("subagent","synthesize").addEdge("synthesize",N.END).compile()}var Oo=require("js-sha3");function Pn(){let c=Date.now().toString(16),e=Math.floor(Math.random()*65535).toString(16).padStart(4,"0");return(c+e).slice(-8)}var Hs="You are a helpful AI assistant. Answer the user accurately and concisely. Respond in the same language as the user.",An=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,$n(e.rpcUrls),this.llm=new le(e.llm),this.registry=new Ee,this.history=new Nt(e.maxHistoryMessages??50),this.summarizer=new It(this.llm),this.synthesizer=new Mt(this.llm),this.systemPrompt=e.systemPrompt??Hs,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 Dt(e.knowledgeBase),this.kbEntries.setLLM(this.llm);let t=e.vectorKB;t?.enabled?(t.provider?this.kb=t.provider:(this.vectorKB=new Se({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 Fe(u)),this.registry.register(new Le(u)),this.registry.register(new We(u)),this.registry.register(new Ve(u)),this.registry.register(new He(u)),this.registry.register(new Me(u)),this.registry.register(new Ge(u)),this.registry.register(new Ke(u)),this.registry.register(new Xe(u)),this.registry.register(new Ye(u)),this.registry.register(new ze(u)),this.registry.register(new Qe(u)),this.registry.register(new je(u)),this.registry.register(new $e(u)),this.registry.register(new qe(u)),this.registry.register(new Be(u)),this.registry.register(new Oe(u))}let r=e.nftLink;this.registry.register(new Je({url:r})),this.registry.register(new Ze({url:r})),this.registry.register(new et({url:r})),this.registry.register(new tt({url:r})),this.registry.register(new nt(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 ot(u)),this.registry.register(new rt(u)),this.registry.register(new st(u)),this.registry.register(new at(u)),this.registry.register(new ft(u));let d=u?{isProduction:u.isProduction}:void 0;this.registry.register(new ht({baseUrl:u?.baseUrl,pool:d,minProvideUsd:u?.minProvideUsd})),this.registry.register(new gt({pool:d}))}if(o&&e.subgraph!==!1){let u=typeof e.subgraph=="object"?e.subgraph:void 0;this.registry.register(new yt(u)),this.registry.register(new bt(u)),this.registry.register(new wt(u)),this.registry.register(new kt(u)),this.registry.register(new Tt(u)),this.registry.register(new vt(u))}let a=e.moralis===!1?void 0:typeof e.moralis=="object"?e.moralis:{};this.registry.register(new Pt(a)),this.registry.register(new At(a)),this.registry.register(new _t(a)),this.registry.register(new Ct(a)),this.registry.register(new Ut(a)),this.registry.register(new Rt(a,{debug:this.debug})),this.registry.register(new Et(a,{debug:this.debug})),this.router=new Lt(this.llm,{debug:this.debug}),this.rewriter=new xn(this.llm,{debug:this.debug});let i=typeof e.subgraph=="object"?e.subgraph:void 0,l=Sn(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=Bo(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:Pn()};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=Pn(),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,Oo.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(`
|
|
821
821
|
${"\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)}
|
|
822
822
|
`)),s.response??{answer:"",messageId:Pn()}}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=>No(e),generateMessageId:()=>Pn(),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 de(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}collectActionButtons(e,t){let n=[],r=new Set;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()){let i=`${a.label}\0${a.prompt}`;if(r.has(i))continue;r.add(i),n.push({label:a.label,prompt:a.prompt,...t?{language:t}:{}})}}return n}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}]
|
|
823
823
|
Q: ${t.entry.question}
|
|
@@ -837,7 +837,7 @@ Answer "no" if ANY of these hold:
|
|
|
837
837
|
- You are uncertain.`,timestamp:0},{role:"user",content:`FAQ question: "${s}"
|
|
838
838
|
User message: "${e}"
|
|
839
839
|
|
|
840
|
-
Are these two questions EQUIVALENT \u2014 asking the same thing such that one answer fully answers both? Answer "yes" only if equivalent, otherwise "no".`,timestamp:Date.now()}],
|
|
840
|
+
Are these two questions EQUIVALENT \u2014 asking the same thing such that one answer fully answers both? Answer "yes" only if equivalent, otherwise "no".`,timestamp:Date.now()}],l=(await this.llm.chat(a)).text.trim().toLowerCase();if(!l.startsWith("yes"))return this.debug&&console.log(`[AgentCore] KB intent guard: verdict="${l}" \u2192 skipping KB, routing to subagent pipeline`),null;this.debug&&console.log('[AgentCore] KB intent guard: verdict="yes" \u2192 answering from KB');let u=[{role:"system",content:this.systemPrompt+`
|
|
841
841
|
|
|
842
842
|
You have reference material below. Use it ONLY if it directly addresses the user's question. Rephrase and adapt the content naturally \u2014 do NOT copy it verbatim. Do NOT supplement with general knowledge or speculate beyond what the reference provides. If the reference does not fully answer the question, say so honestly.
|
|
843
843
|
|
|
@@ -846,7 +846,7 @@ REFERENCE MATERIAL:
|
|
|
846
846
|
`+n+`
|
|
847
847
|
---`,timestamp:0},{role:"user",content:`${e}
|
|
848
848
|
|
|
849
|
-
(You MUST reply in the same language as the question above, regardless of the reference material language.)`,timestamp:Date.now()}],
|
|
849
|
+
(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?"":`
|
|
850
850
|
|
|
851
851
|
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+`
|
|
852
852
|
|
|
@@ -855,9 +855,9 @@ You may use the following reference material if relevant:
|
|
|
855
855
|
---
|
|
856
856
|
REFERENCE MATERIAL:
|
|
857
857
|
`+n+`
|
|
858
|
-
---`:this.systemPrompt)+a,
|
|
858
|
+
---`:this.systemPrompt)+a,l=de(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: ${tn}. 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: ${tn}.`;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(`
|
|
859
859
|
|
|
860
|
-
`);return{steps:t,finalAnswer:n}}async finaliseAnswer(e,t){let{userMessage:n,answer:r,subagents:o,uiActions:s,actionButtons:a,messageId:i}=e??{},{generateSuggestions:
|
|
860
|
+
`);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")?`
|
|
861
861
|
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.
|
|
862
862
|
`:"",o=n.length>0?`
|
|
863
863
|
Handled by subagent(s): ${n.join(", ")}`:"";try{let a=(await this.llm.chat([{role:"user",content:`Analyze this conversation and propose follow-up prompts the user is likely to want NEXT.
|
|
@@ -875,4 +875,4 @@ RULES:
|
|
|
875
875
|
- Suggestions must be diverse \u2014 do not repeat the same intent in different words.
|
|
876
876
|
- Use the same language as the user.
|
|
877
877
|
- Good example (pool): ["Add liquidity to USDC/WETH 0.05%", "Estimate yield for $1000 here", "Compare with 0.3% fee tier"]
|
|
878
|
-
- 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(
|
|
878
|
+
- 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=Eo(o,o.length),a=e.length-s.length,i=de(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 Gs=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 $o(c,e){let t=c.entries??(c.json?Gs(c.json):[]);return t.length===0?{count:0}:new Se(e).upsert(t)}0&&(module.exports={AI_AGENT_TOOL_NAMES,AgentCore,ApproveTokenTool,BaseNftMessageTool,BaseSwapService,BaseTool,BaseWalletActionTool,BuyTokenTool,ChatHistory,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,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,UnwrapNativeTool,UpstashKnowledgeBase,WALLET_ACTION_AGENT_TOOL_NAMES,WALLET_AGENT_TOOL_NAMES,WalletApprovalsTool,WalletDefiPositionsTool,WalletDefiProtocolPositionsTool,WalletDefiSummaryTool,WalletHistoryTool,WalletNFTsTool,WalletNetWorthTool,WalletNftTransfersTool,WalletPnlSummaryTool,WalletPnlTool,WalletTokenBalancesTool,WalletTokenTransfersTool,WrapNativeTool,ZERO_ADDRESS,buildRangePresets,buildUniswapPoolUrl,clampTick,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});
|