keyring-agent-core 0.2.7 → 0.2.8
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 +52 -6
- package/dist/index.js +67 -71
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
"use strict";var
|
|
2
|
-
`)}]}:null,contents:o}}toFunctionDeclarations(e){return e.map(t=>{let n={},r=[];for(let o of t.parameters){let s={type:Kn[o.type]??"STRING",description:o.description};o.type==="array"&&(s.items={type:Kn[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 De=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 Jo="0x2105",jn=["0x1","0xa","0x38","0x89","0x2105","0xa4b1","0xa86a","0xe708"],Zo=new Set(jn),nn="Ethereum (0x1), Optimism (0xa), BSC (0x38), Polygon (0x89), Base (0x2105), Arbitrum (0xa4b1), Avalanche (0xa86a), Linea (0xe708)",er={"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 he(c){if(typeof c!="string")return null;let e=c.trim().toLowerCase();if(!e)return null;let t=er[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&&Zo.has(n)?n:null}function zn(c){return he(c)!==null}var Le=class extends Error{constructor(t){super(`Chain "${t}" is not supported. Supported chains: ${nn}.`);this.requested=t;this.name="UnsupportedChainError"}requested;code="unsupported_chain"},Se=c=>typeof c=="string"&&c.trim()!=="";function Qn(c,e){if(Se(c)){let t=he(c);if(t)return t;throw new Le(c.trim())}if(Se(e?.chain)){let t=he(e.chain);if(t)return t;throw new Le(e.chain.trim())}return null}function on(c,e){return he(c)??he(e?.chain)??void 0}var Rn={"0x1":"Ethereum","0xa":"Optimism","0x38":"BSC","0x89":"Polygon","0x2105":"Base","0xa4b1":"Arbitrum","0xa86a":"Avalanche","0xe708":"Linea"};var tr={bnb:"0x38",matic:"0x89",pol:"0x89",avax:"0xa86a"};function Xn(c){if(typeof c!="string")return null;let e=c.trim().toLowerCase();return e?tr[e]??null:null}function Me(c,e){if(!Se(c))return null;let t=he(c),n=he(e?.chain);return!t||!n||t===n?null:{requested:t,connected:n,requestedLabel:Rn[t]??t,connectedLabel:Rn[n]??n}}function nr(){return jn.map(c=>{let e=Rn[c]??c;return{label:e,prompt:e}})}var K=class extends Error{code="needs_chain_selection";buttons;constructor(e){super("Wallet is connected but no chain is set \u2014 ask the user to pick one."),this.name="NeedsChainSelectionError",this.buttons=e??nr()}},Ot=class extends Error{code="no_wallet_connected";constructor(){super("No wallet address available \u2014 ask the user to connect a wallet or supply one."),this.name="NoWalletConnectedError"}};function V(c,e){if(Se(c))return c.trim();if(Se(e?.walletAddress))return e.walletAddress.trim();throw new Ot}function R(c,e){if(Se(c)){let t=he(c);if(t)return t;throw new Le(c.trim())}if(Se(e?.chain)){let t=he(e.chain);if(t)return t;throw new Le(e.chain.trim())}if(Se(e?.walletAddress))throw new K;return Jo}var v=class{category;async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s=or(o);return{toolName:this.name,callId:n,success:!0,data:s.data,duration:Date.now()-r,...s.actionButtons?{actionButtons:s.actionButtons}:{}}}catch(o){return o instanceof K?{toolName:this.name,callId:n,success:!0,data:{_instructions:"The user has a connected wallet but no chain is set. Ask them which chain to use \u2014 the chain-picker buttons rendered below your reply let them choose. Keep the message short in the user's language and do NOT list the chain names yourself (the buttons already show them)."},duration:Date.now()-r,actionButtons:o.buttons}:o instanceof Ot?{toolName:this.name,callId:n,success:!0,data:{_instructions:"The user has not connected a wallet. In the user's language, tell them this action needs a wallet \u2014 they can either connect their wallet or include a wallet address (0x\u2026) directly in their question. Keep it short and friendly."},duration:Date.now()-r}:{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}};function or(c){if(!c||typeof c!="object")return{data:c};let e=c,t=e.actionButtons;if(!Array.isArray(t)||t.length===0)return{data:c};let{actionButtons:n,...r}=e;return{data:r,actionButtons:t}}var Jn=require("js-sha3");function D(c){return typeof c=="string"&&/^0x[0-9a-fA-F]{40}$/.test(c)}function rr(c){return new Uint8Array(Jn.keccak256.arrayBuffer(c))}function $t(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 Zn(c){return"0x"+Array.from(c).map(e=>e.toString(16).padStart(2,"0")).join("")}function eo(c){return new TextEncoder().encode(c)}function _e(...c){let e=c.reduce((r,o)=>r+o.length,0),t=new Uint8Array(e),n=0;for(let r of c)t.set(r,n),n+=r.length;return t}function sr(c,e=32){let t=new Uint8Array(e);return t.set(c,e-c.length),t}function ar(c,e=32){let t=new Uint8Array(e);return t.set(c),t}function to(c){let e=((c%(1n<<256n)+(1n<<256n))%(1n<<256n)).toString(16).padStart(64,"0");return $t(e)}function qt(c){return to(BigInt(c))}function no(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 oo(c){let e=no(c.type);if(e==="tuple"||e.startsWith("tuple[")){let t=(c.components??[]).map(oo).join(","),n=e.startsWith("tuple[")?e.slice(5):"";return`(${t})${n}`}return e}function ir(c){let e=(c.inputs??[]).map(oo).join(",");return`${c.name??""}(${e})`}function En(c,e){let t=ur(c.type);if(t){let[r,o]=t;return lr({...c,type:o},e,r)}if(c.type==="tuple")return cr(c,e);if(c.type==="address"){let r=String(e),o=r.startsWith("0x")?r.slice(2):r;return{dynamic:!1,encoded:sr($t(o.toLowerCase()))}}if(c.type==="bool"){let r=new Uint8Array(32);return r[31]=e?1:0,{dynamic:!1,encoded:r}}let n=no(c.type);if(/^uint\d*$/.test(n))return{dynamic:!1,encoded:qt(BigInt(e))};if(/^int\d*$/.test(n)){let r=BigInt(e);return r<0n&&(r=r+(1n<<256n)),{dynamic:!1,encoded:to(r)}}if(/^bytes(\d+)$/.test(n)){let r=parseInt(n.slice(5),10),o=typeof e=="string"?$t(e):e;if(o.length!==r)throw new Error(`bytes${r} expects exactly ${r} bytes, got ${o.length}`);return{dynamic:!1,encoded:ar(o)}}if(n==="bytes"){let r=typeof e=="string"?$t(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:_e(qt(r.length),s)}}if(n==="string"){let r=typeof e=="string"?eo(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:_e(qt(r.length),s)}}throw new Error(`Unsupported ABI type: ${c.type}`)}function Nn(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(qt(e+r)),n.push(s),r+=s.length):t.push(s);return _e(...t,...n)}function lr(c,e,t){let n=t===null;if(!n&&e.length!==t)throw new Error(`Array length mismatch: expected ${t}, got ${e.length}`);let r=!1,o=[];for(let s of e){let a=En(c,s);a.dynamic&&(r=!0),o.push(a)}if(n||r){let s=Nn(o);if(n){let a=qt(o.length);return{dynamic:!0,encoded:o.length>0?_e(a,s):a}}return{dynamic:!0,encoded:s}}return{dynamic:!1,encoded:_e(...o.map(s=>s.encoded))}}function cr(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=En(s,a);i.dynamic&&(n=!0),r.push(i)}return{dynamic:n,encoded:n?Nn(r):_e(...r.map(o=>o.encoded))}}function ur(c){let e=c.match(/^(.*)\[(\d+)?\]$/);return e?[e[2]?Number(e[2]):null,e[1]]:void 0}function dr(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)=>En(r,e[o])),n=Nn(t);return Zn(n)}function Be({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=ir(n),o=rr(eo(r)).slice(0,4),s=n.inputs??[],a=s.length===0||t.length===0?new Uint8Array(0):$t(dr(s,t).slice(2));return Zn(_e(o,a))}var mr="https://wallet-api.pantograph.app",pr="0x0000000000000000000000000000000000000000",ao={"0x1":"ether","0xa":"optimism","0x38":"bsc","0x89":"matic","0x2105":"base","0xa4b1":"arbitrum","0xa86a":"avax","0xe708":"linea"},io={"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 Ft(c){return ao[c.toLowerCase()]||c}function ro(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 In(c){let e=c,t=e.decimals,n=typeof t=="number"?t:typeof t=="string"?parseInt(t,10):NaN,r=e.price,o=r!=null?parseFloat(String(r)):NaN;return{...e,token_address:e.token_address??e.address??"",decimals:Number.isFinite(n)?n:0,...Number.isFinite(o)?{usd_price:o}:{}}}function z(c){if(c==null)return null;let e=typeof c=="number"?c:parseFloat(String(c));return Number.isFinite(e)?e:null}function so(c,e){let t=c.decimals,n=typeof t=="number"?t:typeof t=="string"?parseInt(t,10):null,r=z(c.price_change_percentage_24h)??z(c.priceChange24h)??z(c.usd_price_24hr_percent_change)??z(c.price_change_24h)??z(c.priceChange);return{chainId:e,tokenAddress:c.address??c.token_address??"",name:c.name??null,symbol:c.symbol??null,decimals:n!=null&&Number.isFinite(n)?n:null,logo:c.logoURI??c.icon_image??c.logo??null,usdPrice:z(c.price)??z(c.usd_price),marketCap:z(c.market_cap)??z(c.marketCap),totalVolume:{"24h":z(c.total_volume)??z(c.volume24h)},pricePercentChange:{"24h":r}}}var Q=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??mr).replace(/\/+$/,"")}async enrichTokenPrices(e,t){if(e.length!==0)try{let n=Ft(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=Ft(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()===pr,l=a.find(u=>u.address?.toLowerCase()===e.toLowerCase()||i&&u.address===""||u.token_address?.toLowerCase()===e.toLowerCase()||i&&u.token_address==="");return l?In(l):null}catch{return null}}async getTokensMetadata(e,t){let n={};if(e.length===0)return n;try{let r=Ft(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]=In(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=ro({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(In)}}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=io[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=>so(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=z(s.market_cap)??z(s.marketCap)),o.totalVolume?.["24h"]==null&&(o.totalVolume={"24h":z(s.total_volume)??z(s.volume24h)}))}}async fetchTopGainers(e,t,n="24h"){let r=Ft(e),o=ro({duration:n,limit:t}),s=`${this.baseUrl}/token-list/top-gainers/${r}${o?`?${o}`:""}`,a=[];for(let i=0;i<5;i++){let l=await fetch(s);if(!l.ok)throw new Error(`HTTP ${l.status}`);let u=await l.json();if(a=Array.isArray(u)?u:Array.isArray(u.data)?u.data:[],a.length>0)break;i<4&&await new Promise(d=>setTimeout(d,1e3))}return a.map(i=>so(i,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 hr="https://nft.keyring.app",gr="https://nft-demo.keyring.app",fr=.01,U=5,B=1e3,yr="0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",br="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 wr(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 Dn(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??hr).replace(/\/+$/,""),this.v1BaseUrl=(e?.v1BaseUrl??gr).replace(/\/+$/,""),this.pantograph=new Q({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()===yr?br:h.token_address})),await this.enrichTokenPrices(p.result,a),p.result=p.result.filter(h=>h.usd_value==null||h.usd_value>=fr)),{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:wr(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:Dn(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:Dn(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:Dn(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 Oe=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 ce({})}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 $e=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 qe=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 Fe=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 We=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 Q({baseUrl:e?.pantographUrl})}async run(e,t){let n=on(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):
|
|
1
|
+
"use strict";var tr=Object.create;var cn=Object.defineProperty;var nr=Object.getOwnPropertyDescriptor;var or=Object.getOwnPropertyNames;var rr=Object.getPrototypeOf,sr=Object.prototype.hasOwnProperty;var ar=(l,e)=>{for(var t in e)cn(l,t,{get:e[t],enumerable:!0})},no=(l,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of or(e))!sr.call(l,r)&&r!==t&&cn(l,r,{get:()=>e[r],enumerable:!(n=nr(e,r))||n.enumerable});return l};var ir=(l,e,t)=>(t=l!=null?tr(rr(l)):{},no(e||!l||!l.__esModule?cn(t,"default",{value:l,enumerable:!0}):t,l)),lr=l=>no(cn({},"__esModule",{value:!0}),l);var ha={};ar(ha,{AI_AGENT_TOOL_NAMES:()=>En,AgentCore:()=>Bn,ApproveTokenTool:()=>Bt,BaseNftMessageTool:()=>he,BaseSwapService:()=>Ue,BaseTool:()=>v,BaseWalletActionTool:()=>ae,BuyTokenTool:()=>Ot,ChatHistory:()=>$t,DEFAULT_CHAIN:()=>pe,DEFAULT_PROVIDER:()=>Jt,DEFAULT_RPC_BY_CHAIN:()=>se,DebridgeAdapter:()=>Tt,EstimatePoolYieldTool:()=>Pt,GeminiProvider:()=>me,GeminiSearchAiTool:()=>mt,HEX_TO_PANTOGRAPH:()=>wo,KnowledgeBase:()=>Vt,MoralisService:()=>N,NFTContractInfoTool:()=>ut,NFTMetadataTool:()=>dt,NFT_AGENT_TOOL_NAMES:()=>Rn,OpenAddLiquidityFormTool:()=>xt,POOL_AGENT_TOOL_NAMES:()=>Nn,PantographService:()=>Z,PoolByAddressTool:()=>ft,PoolDetailTool:()=>ht,PoolSearchTool:()=>gt,PoolService:()=>Me,PreviewAddLiquidityTool:()=>At,RelayAdapter:()=>vt,Router:()=>Ht,SUPPORTED_CHAINS:()=>$e,SUPPORTED_CHAINS_LABEL:()=>Ne,SWAP_PROVIDER_CONFIG:()=>Sn,SendNativeTool:()=>Lt,SendNftTool:()=>Ft,SendTokenTool:()=>Mt,Subagent:()=>Y,SubgraphCoinPoolPairsTool:()=>Nt,SubgraphPoolByAddressTool:()=>Ut,SubgraphPoolByPositionIdTool:()=>Rt,SubgraphPoolSearchTool:()=>_t,SubgraphPositionDetailTool:()=>Et,SubgraphTrendingPoolsTool:()=>Ct,Summarizer:()=>Wt,SwapServiceFactory:()=>Le,SwapTokenTool:()=>qt,Synthesizer:()=>Gt,TOKEN_AGENT_TOOL_NAMES:()=>Un,TRANSFER_TOPIC:()=>Oo,TRENDING_DEFAULT_BY_HEX_CHAIN:()=>ko,TokenAnalyticsTool:()=>je,TokenHoldersTool:()=>Ye,TokenInfoTool:()=>Ke,TokenScoreTool:()=>ze,ToolRegistry:()=>Fe,TopGainersTool:()=>Xe,TopPoolsTool:()=>pt,TransactionByHashTool:()=>lt,TrendingTokensTool:()=>Qe,UNISWAP_CHAIN_SLUG:()=>en,UpstashKnowledgeBase:()=>Re,WALLET_ACTION_AGENT_TOOL_NAMES:()=>In,WALLET_AGENT_TOOL_NAMES:()=>Cn,WalletApprovalsTool:()=>rt,WalletDefiPositionsTool:()=>at,WalletDefiProtocolPositionsTool:()=>it,WalletDefiSummaryTool:()=>st,WalletHistoryTool:()=>Ze,WalletNFTsTool:()=>ct,WalletNetWorthTool:()=>gn,WalletNftTransfersTool:()=>tt,WalletPnlSummaryTool:()=>nt,WalletPnlTool:()=>ot,WalletTokenBalancesTool:()=>Je,WalletTokenTransfersTool:()=>et,ZERO_ADDRESS:()=>O,buildRangePresets:()=>Zn,buildUniswapPoolUrl:()=>ce,clampTick:()=>ke,configureSupportedChains:()=>mn,createAiAgent:()=>sn,createDefaultSubagents:()=>Dn,createNftAgent:()=>rn,createPoolAgent:()=>an,createTokenAgent:()=>on,createWalletActionAgent:()=>ln,createWalletAgent:()=>nn,ethCallAt:()=>Lo,ethCallByChain:()=>Jn,getAffiliateFee:()=>wt,getChainMeta:()=>ee,getDefaultRpcUrl:()=>ws,getGatewayAddress:()=>An,getNativeTokenInfo:()=>St,getPositionManagerAddress:()=>Pn,getProviderByChain:()=>vn,ingestKnowledgeBase:()=>Zo,priceToTick:()=>De,resolveRpcUrl:()=>kt,roundTickToSpacing:()=>we,rpcCall:()=>ve,setRpcOverrides:()=>Xn,swapServiceFactory:()=>ge,tickToPrice:()=>ie,toPantographChain:()=>zt});module.exports=lr(ha);var ro=ir(require("axios"));var oo="0.2.8";var ur=`keyring-agent-core/${oo} (Keyring Chatbot Agent; +https://keyring.app)`,dr=3e4,mr=typeof process<"u"&&process.versions?.node!=null,qn=class{instance;constructor(e={}){this.instance=ro.default.create({baseURL:e.baseURL,timeout:e.timeout??dr,validateStatus:()=>!0,headers:{Accept:"application/json",...mr?{"User-Agent":e.userAgent??ur}:{},...e.headers}})}async request(e,t,n,r={}){let o=await this.instance.request({...r,method:e,url:t,data:n}),s={};for(let[a,i]of Object.entries(o.headers??{}))i!=null&&(s[a.toLowerCase()]=String(i));return{ok:o.status>=200&&o.status<300,status:o.status,statusText:o.statusText,data:o.data,headers:s}}get(e,t){return this.request("GET",e,void 0,t)}post(e,t,n){return this.request("POST",e,t,n)}put(e,t,n){return this.request("PUT",e,t,n)}patch(e,t,n){return this.request("PATCH",e,t,n)}delete(e,t){return this.request("DELETE",e,void 0,t)}},_=new qn;function I(l){let{data:e,status:t,statusText:n}=l,r=n?`HTTP ${t} ${n}`:`HTTP ${t}`;if(typeof e=="string")return e.trim()||r;if(e&&typeof e=="object"){let o=e,s=i=>i&&typeof i=="object"?i.message:void 0,a=[o.message,o.errorMessage,o.error,o.error_description,o.detail,s(o.error),s(o.data)];for(let i of a)if(typeof i=="string"&&i.trim())return i.trim()}return r}var so={string:"STRING",number:"NUMBER",boolean:"BOOLEAN",object:"OBJECT",array:"ARRAY"},pr="https://nft-demo.keyring.app/api/gemini-stable",hr=new Set([408,429,500,502,503,504]),ao=3e4,un=l=>new Promise(e=>setTimeout(e,l));function dn(l,e,t){if(t!=null&&t>0)return Math.min(t,ao);let n=Math.min(e*2**(l-1),ao);return n/2+Math.random()*(n/2)}function gr(l){if(!l)return null;let e=Number(l);if(Number.isFinite(e))return Math.max(0,e*1e3);let t=Date.parse(l);return Number.isNaN(t)?null:Math.max(0,t-Date.now())}var me=class{model;maxTokens;temperature;baseUrl;constructor(e){this.model=e.model??"gemini-2.5-flash-lite",this.maxTokens=e.maxTokens??4096,this.temperature=e.temperature??1,this.baseUrl=e.baseUrl??pr}async chat(e,t,n){let{systemInstruction:r,contents:o}=this.toContents(e),{maxRetries:s=5,retryDelayMs:a=1e3}=n??{},i=o.length>0?o:[{role:"user",parts:[{text:"(continue)"}]}];if(n?._debug){console.log("[GeminiProvider] turn sequence:");for(let p of i){let m=p.parts.map(g=>g.functionCall?`fc:${g.functionCall.name}`:g.functionResponse?`fr:${g.functionResponse.name}`:`text:${(g.text??"").slice(0,40)}`).join(", ");console.log(` ${p.role}: [${m}]`)}}let c={contents:i,generationConfig:{maxOutputTokens:this.maxTokens,temperature:this.temperature}};r&&(c.systemInstruction=r),t?.length?c.tools=[{functionDeclarations:this.toFunctionDeclarations(t)}]:n?.googleSearch&&(c.tools=[{googleSearch:{}}]);let u=`${this.baseUrl}/v1beta/models/${this.model}:generateContent`,d="Unknown error";for(let p=1;p<=s;p++){let m;try{m=await _.post(u,c,{headers:{"Content-Type":"application/json"}})}catch(b){if(d=b instanceof Error?b.message:String(b),p>=s)break;await un(dn(p,a));continue}if(!m.ok){let b=typeof m.data=="string"?m.data:JSON.stringify(m.data)||m.statusText;if(d=`Gemini proxy error ${m.status}: ${b}`,!hr.has(m.status))throw new Error(d);if(p>=s)break;let T=gr(m.headers["retry-after"]??null);await un(dn(p,a,T));continue}if(m.data==null||typeof m.data!="object"){if(d="Gemini proxy returned invalid JSON",p>=s)break;await un(dn(p,a));continue}let g=m.data,f=g.candidates?.[0];if(!f)return{text:"",toolCalls:[]};let h="",y=[],k=f.content?.parts;if(!Array.isArray(k)||k.length===0){let b=f.finishReason;if(!(b==="SAFETY"||b==="RECITATION")&&p<s){d=`Gemini returned an empty turn (finishReason=${b??"none"})`,await un(dn(p,a));continue}return{text:"",toolCalls:[]}}for(let b of k)b.text&&(h+=b.text),b.functionCall&&y.push({toolName:b.functionCall.name,args:b.functionCall.args??{},callId:b.functionCall.id??`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`});let w=g.usageMetadata?{promptTokens:g.usageMetadata.promptTokenCount??0,completionTokens:g.usageMetadata.candidatesTokenCount??0,totalTokens:g.usageMetadata.totalTokenCount??0}:void 0;return{text:h,toolCalls:y,usage:w}}throw new Error(d)}toContents(e){let t=[],n=[];for(let a of e){if(a.role==="system"){t.push(a.content);continue}if(a.role==="tool"){n.push({role:"function",parts:[{functionResponse:{name:a.toolName??"unknown",response:{result:a.content},...a.toolCallId?{id:a.toolCallId}:{}}}]});continue}if(a.role==="assistant"&&a.toolCalls?.length){n.push({role:"model",parts:a.toolCalls.map(i=>({functionCall:{name:i.toolName,args:i.args,...i.callId?{id:i.callId}:{}}}))});continue}n.push({role:a.role==="assistant"?"model":"user",parts:[{text:a.content}]})}let r=[];for(let a of n){let i=r[r.length-1],c=a.parts.some(d=>d.functionCall),u=i?.parts.some(d=>d.functionCall);i&&i.role===a.role&&!c&&!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],c=o[o.length-1];i.role==="function"&&!c?.parts.some(d=>d.functionCall)||i.role==="model"&&i.parts.some(d=>d.functionCall)&&!(r[a+1]?.role==="function")||o.push(i)}for(;o.length>0&&o[0].role!=="user";)o.shift();return{systemInstruction:t.length?{role:"user",parts:[{text:t.join(`
|
|
2
|
+
`)}]}:null,contents:o}}toFunctionDeclarations(e){return e.map(t=>{let n={},r=[];for(let o of t.parameters){let s={type:so[o.type]??"STRING",description:o.description};o.type==="array"&&(s.items={type:so[o.items?.type??"string"]??"STRING"}),n[o.name]=s,o.required&&r.push(o.name)}return{name:t.name,description:t.description,parameters:{type:"OBJECT",properties:n,required:r}}})}};var Fe=class{tools=new Map;register(e){if(this.tools.has(e.name))throw new Error(`Tool "${e.name}" is already registered`);this.tools.set(e.name,e)}unregister(e){return this.tools.delete(e)}get(e){return this.tools.get(e)}has(e){return this.tools.has(e)}getDefinitions(){return Array.from(this.tools.values()).map(e=>({name:e.name,description:e.description,parameters:e.parameters,category:e.category,kind:e.kind}))}getDefinitionsByCategory(e){return this.getDefinitions().filter(t=>t.category===e)}async execute(e,t,n){let r=this.tools.get(e);if(!r)return{toolName:e,callId:`err_${Date.now()}`,success:!1,error:`Tool "${e}" not found in registry`,duration:0};let o=Date.now();try{return{...await r.execute(t,n),duration:Date.now()-o}}catch(s){return{toolName:e,callId:`err_${Date.now()}`,success:!1,error:s instanceof Error?s.message:String(s),duration:Date.now()-o}}}get size(){return this.tools.size}listNames(){return Array.from(this.tools.keys())}};var $e=["0x1","0xa","0x38","0x89","0x2105","0xa4b1","0xa86a","0xe708"],fr=new Set($e),Ee=[...$e],io=new Set(Ee),pe="0x1",Ne="Ethereum (0x1), Optimism (0xa), BSC (0x38), Polygon (0x89), Base (0x2105), Arbitrum (0xa4b1), Avalanche (0xa86a), Linea (0xe708)";function yr(l){return l.map(e=>`${Ve[e]??e} (${e})`).join(", ")}function mn(l){let e=new Set;if(l)for(let n of l){let r=Fn(n);r&&e.add(r)}let t=$e.filter(n=>!e.has(n));Ee=t.length>0?[...t]:[...$e],io=new Set(Ee),pe=Ee.includes("0x1")?"0x1":Ee[0],Ne=yr(Ee)}var br={"0x1":"0x1",1:"0x1",eth:"0x1",ether:"0x1",ethereum:"0x1",mainnet:"0x1","0xa":"0xa",10:"0xa",op:"0xa",optimism:"0xa","0x38":"0x38",56:"0x38",bsc:"0x38",bnb:"0x38",binance:"0x38","0x89":"0x89",137:"0x89",matic:"0x89",polygon:"0x89","0x2105":"0x2105",8453:"0x2105",base:"0x2105","0xa4b1":"0xa4b1",42161:"0xa4b1",arb:"0xa4b1",arbitrum:"0xa4b1","0xa86a":"0xa86a",43114:"0xa86a",avax:"0xa86a",avalanche:"0xa86a","0xe708":"0xe708",59144:"0xe708",linea:"0xe708"};function be(l){let e=Fn(l);return e&&io.has(e)?e:null}function Fn(l){if(typeof l!="string")return null;let e=l.trim().toLowerCase();if(!e)return null;let t=br[e];if(t)return t;let n=null;return/^0x[0-9a-f]+$/.test(e)?n=e:/^[0-9]+$/.test(e)&&(n="0x"+Number(e).toString(16)),n&&fr.has(n)?n:null}function pn(l){return be(l)!==null}var We=class extends Error{constructor(t){super(`Chain "${t}" is not supported. Supported chains: ${Ne}.`);this.requested=t;this.name="UnsupportedChainError"}requested;code="unsupported_chain"},_e=l=>typeof l=="string"&&l.trim()!=="";function lo(l,e){if(_e(l)){let t=be(l);if(t)return t;throw new We(l.trim())}if(_e(e?.chain)){let t=be(e.chain);if(t)return t;throw new We(e.chain.trim())}return null}function hn(l,e){return be(l)??be(e?.chain)??void 0}var Ve={"0x1":"Ethereum","0xa":"Optimism","0x38":"BSC","0x89":"Polygon","0x2105":"Base","0xa4b1":"Arbitrum","0xa86a":"Avalanche","0xe708":"Linea"};function D(l){let e=Fn(l);return e&&Ve[e]?Ve[e]:typeof l=="string"&&l.trim()?l.trim():"the requested chain"}var wr={bnb:"0x38",matic:"0x89",pol:"0x89",avax:"0xa86a"};function co(l){if(typeof l!="string")return null;let e=l.trim().toLowerCase();return e?wr[e]??null:null}function He(l,e){if(!_e(l))return null;let t=be(l),n=be(e?.chain);return!t||!n||t===n?null:{requested:t,connected:n,requestedLabel:Ve[t]??t,connectedLabel:Ve[n]??n}}function kr(){return Ee.map(l=>{let e=Ve[l]??l;return{label:e,prompt:e}})}var z=class extends Error{code="needs_chain_selection";buttons;constructor(e){super("Wallet is connected but no chain is set \u2014 ask the user to pick one."),this.name="NeedsChainSelectionError",this.buttons=e??kr()}},Kt=class extends Error{code="no_wallet_connected";constructor(){super("No wallet address available \u2014 ask the user to connect a wallet or supply one."),this.name="NoWalletConnectedError"}};function K(l,e){if(_e(l))return l.trim();if(_e(e?.walletAddress))return e.walletAddress.trim();throw new Kt}function E(l,e){if(_e(l)){let t=be(l);if(t)return t;throw new We(l.trim())}if(_e(e?.chain)){let t=be(e.chain);if(t)return t;throw new We(e.chain.trim())}if(_e(e?.walletAddress))throw new z;return pe}var v=class{category;async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s=Tr(o);return{toolName:this.name,callId:n,success:!0,data:s.data,duration:Date.now()-r,...s.actionButtons?{actionButtons:s.actionButtons}:{}}}catch(o){return o instanceof z?{toolName:this.name,callId:n,success:!0,data:{_instructions:"The user has a connected wallet but no chain is set. Ask them which chain to use \u2014 the chain-picker buttons rendered below your reply let them choose. Keep the message short in the user's language and do NOT list the chain names yourself (the buttons already show them)."},duration:Date.now()-r,actionButtons:o.buttons}:o instanceof Kt?{toolName:this.name,callId:n,success:!0,data:{_instructions:"The user has not connected a wallet. In the user's language, tell them this action needs a wallet \u2014 they can either connect their wallet or include a wallet address (0x\u2026) directly in their question. Keep it short and friendly."},duration:Date.now()-r}:{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}};function Tr(l){if(!l||typeof l!="object")return{data:l};let e=l,t=e.actionButtons;if(!Array.isArray(t)||t.length===0)return{data:l};let{actionButtons:n,...r}=e;return{data:r,actionButtons:t}}var uo=require("js-sha3");function B(l){return typeof l=="string"&&/^0x[0-9a-fA-F]{40}$/.test(l)}function vr(l){return new Uint8Array(uo.keccak256.arrayBuffer(l))}function Yt(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 mo(l){return"0x"+Array.from(l).map(e=>e.toString(16).padStart(2,"0")).join("")}function po(l){return new TextEncoder().encode(l)}function Ie(...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 Sr(l,e=32){let t=new Uint8Array(e);return t.set(l,e-l.length),t}function xr(l,e=32){let t=new Uint8Array(e);return t.set(l),t}function ho(l){let e=((l%(1n<<256n)+(1n<<256n))%(1n<<256n)).toString(16).padStart(64,"0");return Yt(e)}function jt(l){return ho(BigInt(l))}function go(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 fo(l){let e=go(l.type);if(e==="tuple"||e.startsWith("tuple[")){let t=(l.components??[]).map(fo).join(","),n=e.startsWith("tuple[")?e.slice(5):"";return`(${t})${n}`}return e}function Ar(l){let e=(l.inputs??[]).map(fo).join(",");return`${l.name??""}(${e})`}function $n(l,e){let t=Cr(l.type);if(t){let[r,o]=t;return Pr({...l,type:o},e,r)}if(l.type==="tuple")return _r(l,e);if(l.type==="address"){let r=String(e),o=r.startsWith("0x")?r.slice(2):r;return{dynamic:!1,encoded:Sr(Yt(o.toLowerCase()))}}if(l.type==="bool"){let r=new Uint8Array(32);return r[31]=e?1:0,{dynamic:!1,encoded:r}}let n=go(l.type);if(/^uint\d*$/.test(n))return{dynamic:!1,encoded:jt(BigInt(e))};if(/^int\d*$/.test(n)){let r=BigInt(e);return r<0n&&(r=r+(1n<<256n)),{dynamic:!1,encoded:ho(r)}}if(/^bytes(\d+)$/.test(n)){let r=parseInt(n.slice(5),10),o=typeof e=="string"?Yt(e):e;if(o.length!==r)throw new Error(`bytes${r} expects exactly ${r} bytes, got ${o.length}`);return{dynamic:!1,encoded:xr(o)}}if(n==="bytes"){let r=typeof e=="string"?Yt(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:Ie(jt(r.length),s)}}if(n==="string"){let r=typeof e=="string"?po(e):e,o=Math.ceil(r.length/32)*32,s=new Uint8Array(o);return s.set(r),{dynamic:!0,encoded:Ie(jt(r.length),s)}}throw new Error(`Unsupported ABI type: ${l.type}`)}function Wn(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(jt(e+r)),n.push(s),r+=s.length):t.push(s);return Ie(...t,...n)}function Pr(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=$n(l,s);a.dynamic&&(r=!0),o.push(a)}if(n||r){let s=Wn(o);if(n){let a=jt(o.length);return{dynamic:!0,encoded:o.length>0?Ie(a,s):a}}return{dynamic:!0,encoded:s}}return{dynamic:!1,encoded:Ie(...o.map(s=>s.encoded))}}function _r(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=$n(s,a);i.dynamic&&(n=!0),r.push(i)}return{dynamic:n,encoded:n?Wn(r):Ie(...r.map(o=>o.encoded))}}function Cr(l){let e=l.match(/^(.*)\[(\d+)?\]$/);return e?[e[2]?Number(e[2]):null,e[1]]:void 0}function Ur(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)=>$n(r,e[o])),n=Wn(t);return mo(n)}function Ge({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=Ar(n),o=vr(po(r)).slice(0,4),s=n.inputs??[],a=s.length===0||t.length===0?new Uint8Array(0):Yt(Ur(s,t).slice(2));return mo(Ie(o,a))}var Rr="https://wallet-api.pantograph.app",Er="0x0000000000000000000000000000000000000000",wo={"0x1":"ether","0xa":"optimism","0x38":"bsc","0x89":"matic","0x2105":"base","0xa4b1":"arbitrum","0xa86a":"avax","0xe708":"linea"},ko={"0x1":["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","0x2260fac5e5542a773aa44fbcfedf7c193bc2c599"],"0xa":["0x4200000000000000000000000000000000000006","0x68f180fcce6836688e9084f035309e29bf0a2095","0xc47da4cb96ce65a96844a01bfae509f9d5454534"],"0x38":["0x2170ed0880ac9a755fd29b2688956bd959f933f8","0x7130d2a12b9bcbfae4f2634d864a1ee1ce3ead9c"],"0x89":["0x7ceb23fd6bc0add59e62ac25578270cff1b9f619","0x1bfd67037b42cf73acf2047067bd4f2c47d9bfd6"],"0x2105":["0x4200000000000000000000000000000000000006","0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf","0xc47da4cb96ce65a96844a01bfae509f9d5454534"],"0xa4b1":["0x82af49447d8a07e3bd95bd0d56f35241523fbab1","0x2f2a2543b76a4166549f7aab2e75bef0aefc5b0f"],"0xa86a":["0x49d5c2bdffac6ce2bfdb6640f4f80f226bc10bab","0x0555e30da8f98308edb960aa94c0db47230d2b9c"],"0xe708":["0xe5d7c2a44ffddf6b295a15c148167daaaf5cf34f","0x3aab2285ddcddad8edf438c1bab47e1a9d05a9b4"]};function zt(l){return wo[l.toLowerCase()]||l}function yo(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 Vn(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 bo(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 Z=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??Rr).replace(/\/+$/,"")}async enrichTokenPrices(e,t){if(e.length!==0)try{let n=zt(t),r=e.map(u=>u.token_address).join(","),o=`${this.baseUrl}/keyrings/tokens/${n}/v2?addresses=${encodeURIComponent(r)}`,s=await _.get(o);if(!s.ok)return;let a=s.data,i=Array.isArray(a)?a:Array.isArray(a.data)?a.data:[],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=zt(t),r=`${this.baseUrl}/keyrings/tokens/${n}/v2?addresses=${encodeURIComponent(e)}`,o=await _.get(r);if(!o.ok)return null;let s=o.data,a=Array.isArray(s)?s:Array.isArray(s.data)?s.data:[],i=e.toLowerCase()===Er,c=a.find(u=>u.address?.toLowerCase()===e.toLowerCase()||i&&u.address===""||u.token_address?.toLowerCase()===e.toLowerCase()||i&&u.token_address==="");return c?Vn(c):null}catch{return null}}async getTokensMetadata(e,t){let n={};if(e.length===0)return n;try{let r=zt(t),o=`${this.baseUrl}/keyrings/tokens/${r}/v2?addresses=${encodeURIComponent(e.join(","))}`,s=await _.get(o);if(!s.ok)return n;let a=s.data,i=Array.isArray(a)?a:Array.isArray(a.data)?a.data:[],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]=Vn(u))}}catch{}return n}async searchTokensByKey(e){let{key:t,chain:n}=e;if(!t)return{success:!1,error:"Search key is required"};let o=parseInt(n||pe,16);if(isNaN(o))return{success:!1,error:`Invalid hex chain: ${n}`};try{let s=yo({chainId:o,key:t}),a=`${this.baseUrl}/token-list?${s}`,i=await _.get(a);if(!i.ok)throw new Error(I(i));let c=i.data,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(Vn)}}catch(s){return{success:!1,error:s instanceof Error?s.message:"Unknown error"}}}async getTrendingTokens(e){let t=e?.chain||pe,n=5,r=e?.limit&&e.limit>0?Math.floor(e.limit):void 0;try{let o=ko[t.toLowerCase()]??[],s=r!=null?r+o.length:n+o.length,[a,i]=await Promise.allSettled([this.fetchTopGainers(t,s),o.length>0?this.getTokensMetadata(o,t):Promise.resolve({})]),c=a.status==="fulfilled"?a.value:[],u=i.status==="fulfilled"?o.map(f=>i.value[f.toLowerCase()]).filter(f=>f!=null).map(f=>bo(f,t)):[],d=new Set,p=[];for(let f of u){let h=f.tokenAddress?.toLowerCase();!h||d.has(h)||(d.add(h),p.push(f))}let m=[];for(let f of c){let h=f.tokenAddress?.toLowerCase();!h||d.has(h)||(d.add(h),m.push(f))}let g=r!=null?[...p,...m].slice(0,r):[...p,...m.slice(0,n)];return await this.enrichTrendingMarketData(g,t),{success:!0,data:g}}catch(o){return{success:!1,error:o instanceof Error?o.message:"Unknown error"}}}async enrichTrendingMarketData(e,t){let n=e.filter(o=>o.tokenAddress&&(o.marketCap==null||o.totalVolume?.["24h"]==null));if(n.length===0)return;let r=await this.getTokensMetadata(n.map(o=>o.tokenAddress),t);for(let o of n){let s=r[o.tokenAddress.toLowerCase()];s&&(o.marketCap==null&&(o.marketCap=J(s.market_cap)??J(s.marketCap)),o.totalVolume?.["24h"]==null&&(o.totalVolume={"24h":J(s.total_volume)??J(s.volume24h)}))}}async fetchTopGainers(e,t,n="24h"){let r=zt(e),o=yo({duration:n,limit:t}),s=`${this.baseUrl}/token-list/top-gainers/${r}${o?`?${o}`:""}`,a=[];for(let i=0;i<5;i++){let c=await _.get(s);if(!c.ok)throw new Error(I(c));let u=c.data;if(a=Array.isArray(u)?u:Array.isArray(u.data)?u.data:[],a.length>0)break;i<4&&await new Promise(d=>setTimeout(d,2e3))}return a.map(i=>bo(i,e))}async getTopGainers(e){let t=e?.chain||pe,n=e?.limit&&e.limit>0?Math.floor(e.limit):void 0,r=e?.duration||"24h";try{let o=await this.fetchTopGainers(t,n,r);return await this.enrichTrendingMarketData(o,t),{success:!0,data:o}}catch(o){return{success:!1,error:o instanceof Error?o.message:"Unknown error"}}}};var Nr="https://nft.keyring.app",Ir="https://nft-demo.keyring.app",Dr=.01,R=5,F=1e3,Lr="0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",Mr="0x0000000000000000000000000000000000000000";function V(l){return l||pe}function $(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 W(l){return new Promise(e=>setTimeout(e,l))}function Br(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 Hn(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 N=class{baseUrl;v1BaseUrl;pantograph;constructor(e){this.baseUrl=(e?.baseUrl??Nr).replace(/\/+$/,""),this.v1BaseUrl=(e?.v1BaseUrl??Ir).replace(/\/+$/,""),this.pantograph=new Z({baseUrl:e?.pantographUrl})}async getWalletTokenBalances(e){let{address:t,chain:n,tokenAddresses:r,excludeSpam:o=!1,excludeUnverifiedContracts:s=!1}=e;if(!t)return{success:!1,error:"Address is required"};let a=V(n),i="Unknown error";for(let c=1;c<=R;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 g=$(u),f=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/tokens?${g}`,h=await _.get(f);if(!h.ok)throw new Error(I(h));let y=h.data,k=y?.data??y;k?.result?.length>0&&d.push(...k.result),p=k?.cursor??null}while(p);let m={result:d};return m?.result?.length>0&&(m.result=m.result.filter(g=>parseFloat(g.balance||"0")>0),m.result=m.result.map(g=>({...g,token_address:g.token_address.toLowerCase()===Lr?Mr:g.token_address})),await this.enrichTokenPrices(m.result,a),m.result=m.result.filter(g=>g.usd_value==null||g.usd_value>=Dr)),{success:!0,data:m}}catch(u){i=u instanceof Error?u.message:"Unknown error",c<R&&await W(F)}return{success:!1,error:i}}async getTokenMetadata(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Token address is required"};let r=V(n),o="Unknown error",s=await this.pantograph.getTokenMetadata(t,r);if(s)return{success:!0,data:s};for(let a=1;a<=R;a++)try{let[i,c]=await Promise.allSettled([_.get(`${this.baseUrl}/api/moralis/proxy/erc20/metadata?${$({chain:r,addresses:[t]})}`),_.get(`${this.baseUrl}/api/moralis/proxy/erc20/${encodeURIComponent(t)}/price?${$({chain:r,include:"percent_change"})}`)]),u=null;if(i.status==="fulfilled"&&i.value.ok){let p=i.value.data;u=(Array.isArray(p)?p:Array.isArray(p?.data)?p.data:[])[0]??null}if(!u){o="Token metadata not found",a<R&&await W(F);continue}let d=null;if(c.status==="fulfilled"&&c.value.ok){let p=c.value.data;d=p?.data??p}return{success:!0,data:{...u,...d?.usd_price?{usd_price:d.usd_price}:{},...d?.usd_price_change_percentage_24h?{usd_price_change_percentage_24h:d.usd_price_change_percentage_24h}:{}}}}catch(i){o=i instanceof Error?i.message:"Unknown error",a<R&&await W(F)}return{success:!1,error:o}}async getWalletNFTs(e){let{address:t,chain:n,limit:r=10,excludeSpam:o=!0,cursor:s,tokenAddresses:a,includePrices:i,format:c="decimal"}=e;if(!t)return{success:!1,error:"Address is required"};let u=V(n),d="Unknown error";for(let p=1;p<=R;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 g=$(m),f=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/nft?${g}`,h=await _.get(f);if(!h.ok)throw new Error(I(h));let y=h.data;return{success:!0,data:y?.data??y}}catch(m){d=m instanceof Error?m.message:"Unknown error",p<R&&await W(F)}return{success:!1,error:d}}async getNftMetadata(e){let{address:t,tokenId:n,chain:r,format:o="decimal",normalizeMetadata:s=!0,mediaItems:a=!0,include:i}=e;if(!t)return{success:!1,error:"NFT contract address is required"};if(n==null||n==="")return{success:!1,error:"Token ID is required"};if(!B(t))return{success:!1,error:"NFT metadata lookup by token ID is only supported on EVM chains"};let c=V(r),u="Unknown error";for(let d=1;d<=R;d++)try{let p={chain:c,format:o,normalizeMetadata:s,media_items:a};i&&(p.include=i);let m=$(p),g=`${this.baseUrl}/api/moralis/proxy/nft/${encodeURIComponent(t)}/${encodeURIComponent(n)}?${m}`,f=await _.get(g);if(!f.ok)throw new Error(I(f));let h=f.data;return{success:!0,data:h?.data??h}}catch(p){u=p instanceof Error?p.message:"Unknown error",d<R&&await W(F)}return{success:!1,error:u}}async getNftContractMetadata(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Contract address is required"};let r=V(n),o="Unknown error";for(let s=1;s<=R;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/nft/${encodeURIComponent(t)}/metadata?${a}`,c=await _.get(i);if(!c.ok)throw new Error(I(c));let u=c.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<R&&await W(F)}return{success:!1,error:o}}async getWalletHistory(e){let{address:t,chain:n,limit:r=25,fromDate:o,toDate:s,fromBlock:a,toBlock:i,cursor:c,order:u="DESC",includeInternalTransactions:d,nftMetadata:p}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet history is only supported for EVM addresses"};let m=V(n),g=Math.max(1,Math.min(100,Math.floor(r))),f="Unknown error";for(let h=1;h<=R;h++)try{let y={chain:m,limit:g,order:u};o&&(y.from_date=o),s&&(y.to_date=s),a!=null&&(y.from_block=a),i!=null&&(y.to_block=i),c&&(y.cursor=c),d!=null&&(y.include_internal_transactions=d),p!=null&&(y.nft_metadata=p);let k=$(y),w=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/history?${k}`,b=await _.get(w);if(!b.ok)throw new Error(I(b));let T=b.data;return{success:!0,data:T?.data??T}}catch(y){f=y instanceof Error?y.message:"Unknown error",h<R&&await W(F)}return{success:!1,error:f}}async getWalletTokenTransfers(e){let{address:t,chain:n,contractAddresses:r,limit:o=25,fromDate:s,toDate:a,fromBlock:i,toBlock:c,cursor:u,order:d="DESC"}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"ERC-20 transfers are only supported for EVM addresses"};let p=V(n),m=Math.max(1,Math.min(100,Math.floor(o))),g="Unknown error";for(let f=1;f<=R;f++)try{let h={chain:p,limit:m,order:d};s&&(h.from_date=s),a&&(h.to_date=a),i!=null&&(h.from_block=i),c!=null&&(h.to_block=c),u&&(h.cursor=u),r?.length&&(h.contract_addresses=r);let y=$(h),k=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/erc20/transfers?${y}`,w=await _.get(k);if(!w.ok)throw new Error(I(w));let b=w.data;return{success:!0,data:b?.data??b}}catch(h){g=h instanceof Error?h.message:"Unknown error",f<R&&await W(F)}return{success:!1,error:g}}async getWalletNftTransfers(e){let{address:t,chain:n,contractAddresses:r,limit:o=25,fromDate:s,toDate:a,fromBlock:i,toBlock:c,cursor:u,order:d="DESC",includePrices:p,format:m="decimal"}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"NFT transfers are only supported for EVM addresses"};let g=V(n),f=Math.max(1,Math.min(100,Math.floor(o))),h="Unknown error";for(let y=1;y<=R;y++)try{let k={chain:g,limit:f,order:d,format:m};s&&(k.from_date=s),a&&(k.to_date=a),i!=null&&(k.from_block=i),c!=null&&(k.to_block=c),u&&(k.cursor=u),r?.length&&(k.contract_addresses=r),p!=null&&(k.include_prices=p);let w=$(k),b=`${this.baseUrl}/api/moralis/proxy/${encodeURIComponent(t)}/nft/transfers?${w}`,T=await _.get(b);if(!T.ok)throw new Error(I(T));let A=T.data;return{success:!0,data:A?.data??A}}catch(k){h=k instanceof Error?k.message:"Unknown error",y<R&&await W(F)}return{success:!1,error:h}}async getTokenHolders(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Contract address is required"};if(!B(t))return{success:!1,error:"Token holders is only supported for EVM contract addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=R;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/erc20/${encodeURIComponent(t)}/holders?${a}`,c=await _.get(i);if(!c.ok)throw new Error(I(c));let u=c.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<R&&await W(F)}return{success:!1,error:o}}async getWalletNetWorth(e){let{address:t,chains:n,excludeSpam:r,excludeUnverifiedContracts:o,maxTokenInactivity:s,minPairSideLiquidityUsd:a}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet net worth is only supported for EVM addresses"};let i="Unknown error";for(let c=1;c<=R;c++)try{let u={};n&&n.length>0&&(u.chains=n.map(y=>V(y))),r!=null&&(u.exclude_spam=r),o!=null&&(u.exclude_unverified_contracts=o),s!=null&&(u.max_token_inactivity=s),a!=null&&(u.min_pair_side_liquidity_usd=a);let d=$(u),p=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/net-worth${d?`?${d}`:""}`,m=await _.get(p);if(!m.ok)throw new Error(I(m));let g=m.data;return{success:!0,data:g?.data??g}}catch(u){i=u instanceof Error?u.message:"Unknown error",c<R&&await W(F)}return{success:!1,error:i}}async getWalletPnlSummary(e){let{address:t,chain:n,days:r}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet PnL summary is only supported for EVM addresses"};let o=V(n),s="Unknown error";for(let a=1;a<=R;a++)try{let i={chain:o};r&&(i.days=r);let c=$(i),u=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/profitability/summary?${c}`,d=await _.get(u);if(!d.ok)throw new Error(I(d));let p=d.data;return{success:!0,data:p?.data??p}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<R&&await W(F)}return{success:!1,error:s}}async getWalletPnl(e){let{address:t,chain:n,days:r,tokenAddresses:o}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet PnL is only supported for EVM addresses"};let s=V(n),a="Unknown error";for(let i=1;i<=R;i++)try{let c={chain:s};r&&(c.days=r),o&&o.length>0&&(c.token_addresses=o.slice(0,25));let u=$(c),d=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/profitability?${u}`,p=await _.get(d);if(!p.ok)throw new Error(I(p));let m=p.data;return{success:!0,data:m?.data??m}}catch(c){a=c instanceof Error?c.message:"Unknown error",i<R&&await W(F)}return{success:!1,error:a}}async getTransactionByHash(e){let{transactionHash:t,chain:n}=e;if(!t)return{success:!1,error:"Transaction hash is required"};let r=V(n),o="Unknown error";for(let s=1;s<=R;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/transaction/${encodeURIComponent(t)}/verbose?${a}`,c=await _.get(i);if(!c.ok)throw new Error(I(c));let u=c.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<R&&await W(F)}return{success:!1,error:o}}async getWalletApprovals(e){let{address:t,chain:n,limit:r,cursor:o}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet approvals are only supported for EVM addresses"};let s=V(n),a="Unknown error";for(let i=1;i<=R;i++)try{let c={chain:s};r!=null&&(c.limit=r),o&&(c.cursor=o);let u=$(c),d=`${this.baseUrl}/api/moralis/proxy/wallets/${encodeURIComponent(t)}/approvals?${u}`,p=await _.get(d);if(!p.ok)throw new Error(I(p));let m=p.data;return{success:!0,data:m?.data??m}}catch(c){a=c instanceof Error?c.message:"Unknown error",i<R&&await W(F)}return{success:!1,error:a}}async getWalletDefiSummary(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet DeFi summary is only supported for EVM addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=R;s++)try{let a=$({chains:r}),i=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/summary?${a}`,c=await _.get(i);if(!c.ok)throw new Error(I(c));let u=c.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<R&&await W(F)}return{success:!1,error:o}}async getWalletDefiPositions(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Address is required"};if(!B(t))return{success:!1,error:"Wallet DeFi positions are only supported for EVM addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=R;s++)try{let a=$({chains:r}),i=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/positions?${a}`,c=await _.get(i);if(!c.ok)throw new Error(I(c));let u=c.data,d=u?.data??u;return{success:!0,data:Br(d)}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<R&&await W(F)}return{success:!1,error:o}}async getWalletDefiProtocolPositions(e){let{address:t,protocol:n,chain:r}=e;if(!t)return{success:!1,error:"Address is required"};if(!n)return{success:!1,error:"Protocol identifier is required"};if(!B(t))return{success:!1,error:"Wallet DeFi protocol positions are only supported for EVM addresses"};let o=V(r),s="Unknown error";for(let a=1;a<=R;a++)try{let i=$({chains:o}),c=`${this.v1BaseUrl}/api/moralis-v1/proxy/wallets/${encodeURIComponent(t)}/defi/${encodeURIComponent(n)}/positions?${i}`,u=await _.get(c);if(!u.ok)throw new Error(I(u));let m=u.data.result;return{success:!0,data:{protocol_id:m.protocolId,protocol_name:m.protocolName,protocol_url:m.protocolUrl??"",protocol_logo:m.protocolLogo??"",total_usd_value:m.totalUsd??0,total_unclaimed_usd_value:m.totalUnclaimedUsd??null,positions:m.positions.map(f=>({label:f.label,address:f.address,balance_usd:f.balanceUsd??0,total_unclaimed_usd_value:f.unclaimedUsd??0,position_details:f.details,tokens:f.tokens.map(h=>({token_type:h.tokenType,address:h.address,contract_address:h.address,name:h.name??"",symbol:h.symbol??"",decimals:h.decimals??18,logo:h.logo,balance:h.balance??"0",balance_formatted:h.balanceFormatted??"0",usd_price:h.usdPrice,usd_value:h.usdValue}))}))}}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<R&&await W(F)}return{success:!1,error:s}}async searchTokensByKey(e){return this.pantograph.searchTokensByKey(e)}async getTokenAnalytics(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Token contract address is required"};if(!B(t))return{success:!1,error:"Token analytics is only supported for EVM contract addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=R;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/tokens/${encodeURIComponent(t)}/analytics?${a}`,c=await _.get(i);if(!c.ok)throw new Error(I(c));let u=c.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<R&&await W(F)}return{success:!1,error:o}}async getTokenScore(e){let{address:t,chain:n}=e;if(!t)return{success:!1,error:"Token contract address is required"};if(!B(t))return{success:!1,error:"Token score is only supported for EVM contract addresses"};let r=V(n),o="Unknown error";for(let s=1;s<=R;s++)try{let a=$({chain:r}),i=`${this.baseUrl}/api/moralis/proxy/tokens/${encodeURIComponent(t)}/score?${a}`,c=await _.get(i);if(!c.ok)throw new Error(I(c));let u=c.data;return{success:!0,data:u?.data??u}}catch(a){o=a instanceof Error?a.message:"Unknown error",s<R&&await W(F)}return{success:!1,error:o}}async getTrendingTokens(e){let t=e?.chain,n=e?.limit,r="Unknown error";for(let o=1;o<=R;o++)try{let s={};t&&(s.chain=V(t)),n!=null&&(s.limit=n);let a=$(s),i=`${this.baseUrl}/api/moralis/proxy/tokens/trending${a?`?${a}`:""}`,c=await _.get(i);if(!c.ok)throw new Error(I(c));let u=c.data,d=u?.data??u;return{success:!0,data:Hn(d)}}catch(s){r=s instanceof Error?s.message:"Unknown error",o<R&&await W(F)}return{success:!1,error:r}}async getTopGainers(e){let{chain:t,timeFrame:n,minMarketCap:r,securityScore:o}=e??{},s="Unknown error";for(let a=1;a<=R;a++)try{let i={};t&&(i.chain=V(t)),n&&(i.time_frame=n),r!=null&&(i.min_market_cap=r),o!=null&&(i.security_score=o);let c=$(i),u=`${this.baseUrl}/api/moralis/proxy/discovery/tokens/top-gainers${c?`?${c}`:""}`,d=await _.get(u);if(!d.ok)throw new Error(I(d));let p=d.data,m=p?.data??p;return{success:!0,data:Hn(m)}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<R&&await W(F)}return{success:!1,error:s}}async getTopLosers(e){let{chain:t,timeFrame:n,minMarketCap:r,securityScore:o}=e??{},s="Unknown error";for(let a=1;a<=R;a++)try{let i={};t&&(i.chain=V(t)),n&&(i.time_frame=n),r!=null&&(i.min_market_cap=r),o!=null&&(i.security_score=o);let c=$(i),u=`${this.baseUrl}/api/moralis/proxy/discovery/tokens/top-losers${c?`?${c}`:""}`,d=await _.get(u);if(!d.ok)throw new Error(I(d));let p=d.data,m=p?.data??p;return{success:!0,data:Hn(m)}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<R&&await W(F)}return{success:!1,error:s}}async enrichTokenPrices(e,t){return this.pantograph.enrichTokenPrices(e,t)}};var Ke=class extends v{name="get-token-info";description='Get information about a specific cryptocurrency token: price, metadata, 24h change, market data, and user holdings. Use this when the user asks about: token price, token info, "what is X token", "tell me about X", "how much is X worth", or any token-specific question. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea';category="blockchain-data";parameters=[{name:"keyword",type:"string",description:'Token symbol, name, or contract address (e.g. "USDC", "Pepe", "ETH", "0x1234\u2026"). If a 0x address is provided, performs a precise contract lookup. Otherwise searches by name/symbol.',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"prompt",type:"string",description:`A rich, multi-angle research prompt sent to Gemini with Google Search grounding (real-time web access). The prompt must be self-contained \u2014 Gemini does NOT see the user's original message, only this prompt. ALWAYS include: the token symbol/name, the chain, and an instruction to use web search for up-to-date info. When the user asks an open/broad question (e.g. "show me X", "tell me about X", "what is X"), expand the prompt to cover ALL of these angles in one request: (1) what the token/project is and its core utility, (2) current USD price, 24h change, market cap, FDV, 24h volume, (3) recent news, announcements, or notable events in the last 7-30 days, (4) on-chain activity signals (holders, liquidity, unusual flows), (5) team/protocol updates or roadmap items, (6) key risks, red flags, or controversies, (7) sentiment and notable community discussion. When the user asks a narrow question (e.g. "price of X"), still include 2-3 supporting angles for context. Aim for 3-6 sentences. Be specific to the token. Do not use generic filler. Example for "show me JPYT": "Research the JPYT token on Ethereum using up-to-date web sources. Cover: what JPYT is and who issues it; current USD price, 24h change, market cap, FDV, and 24h volume; recent news or announcements in the last 30 days; on-chain signals such as holder count, liquidity, and unusual volume patterns; team or protocol updates; key risks or red flags; and overall sentiment."`,required:!0},{name:"buy_button_label",type:"string",description:`Label for the "Buy" suggestion button shown under the token info, IN THE USER'S CURRENT LANGUAGE. Use the exact placeholder "{token}" (do not translate the braces) for the token symbol; put the localized verb directly in the text. English example: "Buy {token}". Vietnamese example: "Mua {token}". Japanese example: "{token} \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 N(e),this.llm=new me({})}async run(e,t){let n=e.keyword||"",r=E(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,g=[...d].sort((b,T)=>(m(b)?0:1)-(m(T)?0:1)),f=null,h=g[0];if(h?.token_address){let b=await this.service.getTokenMetadata({address:h.token_address,chain:t});b.success&&b.data&&(f=b.data)}!f&&h&&(f={...h});let y=f?.symbol?.trim(),k=y?[{label:this.fillTemplate(r,y,"Buy {token}"),prompt:this.fillTemplate(o,y,"buy {token}")}]:[];return{_instructions:'Present the token info in a clear summary. Include: token name & symbol, contract address (shortened), current USD price, 24h price change % (prefix "+" for positive, "-" for negative), market cap, fully diluted valuation, 24h trading volume, liquidity, and holder count (if available). If the user holds this token, prominently show their balance and USD value. If research is present, weave its substance naturally into your answer \u2014 fold the relevant points into the description, context, and any notable news or risks. Do NOT add a labelled "AI Analysis" section or any similar heading, and do NOT mention that the analysis came from AI; just write it as part of the token summary. Format numbers in a human-readable way (e.g. $1.23, +5.67%, $12.5M). If a field is null, omit it. A "Buy" button is shown below your reply automatically \u2014 do NOT mention it, list it, or repeat its text.',chain:t,userHoldsToken:!!c,walletBalance:c,token:f,...d.length>1?{searchResults:g.slice(0,10)}:{},research:u,...k.length>0?{actionButtons:k}:{}}}fillTemplate(e,t,n){return(e&&e.includes("{token}")?e:n).replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}async askGeminiWithSearch(e,t){let n=Date.now();try{return(await this.llm.chat([...t?[{role:"system",content:t,timestamp:n}]:[],{role:"user",content:e,timestamp:n}],void 0,{googleSearch:!0,maxRetries:1})).text?.trim()||null}catch{return null}}async findTokenInWallet(e,t,n){if(!n)return null;let r=await this.service.getWalletTokenBalances({address:e,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0});if(!r.success||!r.data?.result)return null;let o=n.toLowerCase(),s=r.data.result.find(a=>a.token_address.toLowerCase()===o||a.symbol?.toLowerCase()===o||a.name?.toLowerCase().includes(o));return s||null}};var Ye=class extends v{name="get-token-holders";description='Get holder statistics and analytics for an ERC-20 token. Returns: total holder count, holder count changes over time (5min/1h/6h/24h/3d/7d/30d), how holders acquired the token (swap/transfer/airdrop), supply concentration (% held by top 10/25/50/100/250/500 holders), and holder size distribution (whales, sharks, dolphins, fish, octopus, crabs, shrimps). Use tokenSymbol (e.g. "USDC") to look up by name \u2014 the tool auto-resolves the contract address. Use contractAddress when you already have the exact 0x address. Use this tool for questions like: "How many holders does USDC have?" (tokenSymbol=USDC), "Is USDC gaining or losing holders?" (tokenSymbol=USDC \u2192 holderChange), "How many new holders did ETH get in the last 7 days?" (tokenSymbol=WETH), "How concentrated is USDC ownership?" (tokenSymbol=USDC \u2192 holderSupply), "What % of supply do the top 10 holders control?" (tokenSymbol=USDC), "How many whales hold USDT?" (tokenSymbol=USDT \u2192 holderDistribution), "Did most holders buy via swap or transfer?" (tokenSymbol=USDC \u2192 holdersByAcquisition). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. ';category="blockchain-data";parameters=[{name:"tokenSymbol",type:"string",description:`Token symbol (e.g. "USDC", "USDT", "WETH"). The tool resolves the contract address automatically: first checks the connected wallet's balances, then searches by symbol. Takes precedence over contractAddress when both are given.`,required:!1},{name:"contractAddress",type:"string",description:"ERC-20 token contract address (0x\u2026). Use when the exact address is already known. Ignored if tokenSymbol is provided.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=E(e.chain,t),r=t?.walletAddress||void 0,o=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,s=typeof e.contractAddress=="string"&&e.contractAddress?e.contractAddress.trim():void 0;if(!o&&!s)return{error:'Provide either tokenSymbol (e.g. "USDC") or contractAddress (0x\u2026).'};let a,i;if(o){let d=await this.resolveContractAddress(o,n,r);if(!d)return{error:`Cannot resolve token symbol "${o}" to a contract address on ${n?D(n):"the selected chain"}. 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 je=class extends v{name="get-token-analytics";description=`Get detailed trading analytics for a single ERC-20 token, broken down across 5min / 1h / 6h / 24h windows. Returns: buy volume, sell volume, unique buyers, unique sellers, buy/sell transaction counts, unique wallets, price % change, current USD price, total liquidity and fully-diluted valuation (FDV). Use tokenSymbol (e.g. "PEPE") to look up by name \u2014 the tool auto-resolves the contract address. Use contractAddress when you already have the exact 0x address. Use this tool for questions like: "How is PEPE trading today?" (tokenSymbol=PEPE), "24h buy vs sell volume for USDC?" (tokenSymbol=USDC \u2192 totalBuyVolume vs totalSellVolume), "Is buying or selling pressure stronger for this token?", "How many unique wallets touched this token in the last hour?" (\u2192 uniqueWallets.1h), "What's the FDV and liquidity of X?" (\u2192 totalFullyDilutedValuation, totalLiquidity), "Price change over 6h?" (\u2192 pricePercentChange.6h). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.`;category="blockchain-data";parameters=[{name:"tokenSymbol",type:"string",description:`Token symbol (e.g. "PEPE", "USDC", "WETH"). The tool resolves the contract address automatically: first checks the connected wallet's balances, then searches by symbol. Takes precedence over contractAddress when both are given.`,required:!1},{name:"contractAddress",type:"string",description:"ERC-20 token contract address (0x\u2026). Use when the exact address is already known. Ignored if tokenSymbol is provided.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=E(e.chain,t),r=t?.walletAddress||void 0,o=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,s=typeof e.contractAddress=="string"&&e.contractAddress?e.contractAddress.trim():void 0;if(!o&&!s)return{error:'Provide either tokenSymbol (e.g. "PEPE") or contractAddress (0x\u2026).'};let a,i;if(o){let d=await this.resolveContractAddress(o,n,r);if(!d)return{error:`Cannot resolve token symbol "${o}" to a contract address on ${n?D(n):"the selected chain"}. 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 ze=class extends v{name="get-token-score";description=`Get a composite 0\u2013100 health/safety score for a single ERC-20 token, plus the metrics behind it. Returns: overall score, last-updated timestamp, current USD price, paired liquidity, multi-timeframe volume buckets (10m / 30m / 1h / 4h / 12h / 1d / 7d / 30d), same buckets for transaction counts, and supply info (total supply + % held by top 10 holders). Use tokenSymbol (e.g. "PEPE") to look up by name \u2014 the tool auto-resolves the contract address. Use contractAddress when you already have the exact 0x address. Use this tool for questions like: "Is PEPE a safe token?" (tokenSymbol=PEPE \u2192 score), "What's the health/score of USDC?", "How concentrated is X's supply among whales?" (\u2192 metrics.supply.top10Percent), "How much volume did this token do over the last 7 days?" (\u2192 metrics.volumeUsd.7d), "Transaction count for this token in the last hour?" (\u2192 metrics.transactions.1h). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.`;category="blockchain-data";parameters=[{name:"tokenSymbol",type:"string",description:`Token symbol (e.g. "PEPE", "USDC", "WETH"). The tool resolves the contract address automatically: first checks the connected wallet's balances, then searches by symbol. Takes precedence over contractAddress when both are given.`,required:!1},{name:"contractAddress",type:"string",description:"ERC-20 token contract address (0x\u2026). Use when the exact address is already known. Ignored if tokenSymbol is provided.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=E(e.chain,t),r=t?.walletAddress||void 0,o=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,s=typeof e.contractAddress=="string"&&e.contractAddress?e.contractAddress.trim():void 0;if(!o&&!s)return{error:'Provide either tokenSymbol (e.g. "PEPE") or contractAddress (0x\u2026).'};let a,i;if(o){let p=await this.resolveContractAddress(o,n,r);if(!p)return{error:`Cannot resolve token symbol "${o}" to a contract address on ${n?D(n):"the selected chain"}. 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 Qe=class extends v{name="get-trending-tokens";description='List tokens trending on-chain right now, ranked by trading activity, volume, liquidity and holder growth. Returns price, market cap, liquidity, holder count, and multi-timeframe buckets (1h / 4h / 12h / 24h) of price change, volume, transaction counts, and unique buyers/sellers. Cross-chain by default \u2014 omit `chain` for a global ranking, or pass it to filter to one network. Use this tool for questions like: "What tokens are trending right now?", "Show me hot tokens", "What\'s popular on Base today?", "Which tokens have the highest 24h volume?", "Biggest movers in the last hour?". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea, and more.';category="blockchain-data";parameters=[{name:"chain",type:"string",description:'Optional hex chain ID to scope results to a single network: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user names a chain. Omit to return cross-chain trending results.',required:!1},{name:"limit",type:"number",description:'Total number of trending tokens to return (chain default tokens + top gainers combined). Only set this when the user asks for a specific count (e.g. "show 20 trending tokens" \u2192 20). Omit otherwise \u2014 when omitted, returns all default tokens plus 5 top gainers.',required:!1},{name:"buy_button_label",type:"string",description:`Label template for the per-token "Buy" suggestion buttons, IN THE USER'S CURRENT LANGUAGE. One button is rendered per trending token, with "{token}" replaced by that token's symbol. Use the exact placeholder "{token}" (do not translate the braces); put the localized verb in the text. English example: "Buy {token}". Vietnamese example: "Mua {token}". Japanese example: "{token} \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 Z({baseUrl:e?.pantographUrl})}async run(e,t){let n=hn(e.chain,t),r=e.buy_button_label||"",o=e.buy_prompt_template||"",s=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.floor(e.limit):void 0,a=await this.service.getTrendingTokens({chain:n,limit:s});if(!a.success)return{error:a.error||"Failed to fetch trending tokens"};let i=a.data??[],c=new Set,u=[];for(let d of i){let p=d.symbol?.trim();!p||c.has(p.toLowerCase())||(c.add(p.toLowerCase()),u.push({label:this.fillTemplate(r,p,"Buy {token}"),prompt:this.fillTemplate(o,p,"buy {token}")}))}return{_instructions:`Start with a one-line header like "Here are some of the top trending tokens on <chain name> right now:". Then present the tokens as a numbered list. For EACH token render exactly these lines (omit any line whose value is null):
|
|
4
4
|
<rank>. <name> (<symbol>)
|
|
5
5
|
- Price: <usdPrice>
|
|
6
6
|
- Market Cap: <marketCap>
|
|
7
7
|
- 24h Price Change: <pricePercentChange.24h>
|
|
8
8
|
- 24h Volume: <totalVolume.24h>
|
|
9
|
-
Format numbers human-readably: price as $0.65 / $0.016 (more decimals for sub-cent), market cap & volume compact ($12.8M, $6,474), price change signed with one or two decimals (+3.56%, -1.6%). Reply in the user's language and use the human-readable chain name. Do NOT mention tool names, UI, or forms. A "Buy" button is shown below your reply for each token automatically \u2014 do NOT mention, list, or repeat them.`,chain:n
|
|
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?D(n):"all",count:i.length,tokens:i,...u.length>0?{actionButtons:u}:{}}}async execute(e,t){let n=await super.execute(e,t),r=n.data;return n.success&&r&&Array.isArray(r.actionButtons)&&r.actionButtons.length>0&&(n.actionButtons=r.actionButtons),n}fillTemplate(e,t,n){return(e&&e.includes("{token}")?e:n).replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}};var vo={"1h":"1h","1d":"24h","1w":"7d","1M":"30d"},To=Object.keys(vo),Xe=class extends v{name="get-top-gainers";description='List tokens with the highest USD price increase over a chosen timeframe (1h / 1d / 1w / 1M). Returns name & symbol, current USD price, market cap, 24h volume, and the price change % over the selected window. Scoped to a single chain \u2014 pass `chain` to pick the network, otherwise the connected chain (or Ethereum) is used. Use this tool for questions like: "What are today\'s top gainers?", "Biggest pumps this week?", "Which tokens went up most in the last hour?", "Top gainers on Base". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. Defaults to the connected chain, or Ethereum (0x1).',required:!1},{name:"timeFrame",type:"string",description:`Window over which the price change is measured. Allowed values: "1h" (last hour), "1d" (last 24h), "1w" (last 7 days), "1M" (last 30 days). Pick the timeframe that matches the user's question \u2014 e.g. "today" \u2192 "1d", "this week" \u2192 "1w". Defaults to "1d".`,required:!1},{name:"limit",type:"number",description:'Number of top gainers to return. Only set this when the user asks for a specific count (e.g. "show 20 top gainers" \u2192 20). Omit otherwise.',required:!1}];service;constructor(e){super(),this.service=new Z({baseUrl:e?.pantographUrl})}async run(e,t){let n=hn(e.chain,t),r=typeof e.timeFrame=="string"?e.timeFrame.trim():void 0;if(r&&!To.includes(r))return{error:`Invalid timeFrame "${r}". Allowed values: ${To.join(", ")}.`};let o=r||void 0,s=o?vo[o]:void 0,a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.floor(e.limit):void 0,i=await this.service.getTopGainers({chain:n,limit:a,duration:s});if(!i.success)return{error:i.error||"Failed to fetch top gainers"};let c=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
|
-
- Price Change: <pricePercentChange.24h> (
|
|
13
|
+
- Price Change: <pricePercentChange.24h> (prefix "+" for positive, "-" for negative)
|
|
14
14
|
- 24h Volume: <totalVolume.24h>
|
|
15
|
-
Format numbers human-readably: price as $0.65 / $0.016 (more decimals for sub-cent), market cap & volume compact ($12.8M, $6,474), price change signed with one or two decimals (+5.67%, -1.6%). Reply in the user's language and use the human-readable chain name. Do NOT mention tool names, UI, or forms.`,chain:n??"all",timeFrame:o??null,count:l.length,tokens:l}}};var He=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=V(e.address,t),r=R(e.chain,t),o=await this.service.getWalletTokenBalances({address:n,chain:r,excludeSpam:!1,excludeUnverifiedContracts:!1});if(!o.success)return{error:o.error||"Failed to fetch token balances"};let s=o.data?.result||[],a=s.map((i,l)=>{let u=i.name||i.symbol||"Unknown",d=i.balance_formatted??"0",m=i.symbol||"",p=typeof i.usd_price=="number"?`$${i.usd_price}`:"$-";return`${l+1}. ${u}: ${d} ${m} (${p})`.trim()});return{walletAddress:n,chain:r||"default",tokenCount:s.length,tokens:a,totalUsdValue:s.reduce((i,l)=>i+(l.usd_value||0),0)}}};var Ge=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=V(e.address,t),r=R(e.chain,t),o=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,s=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,a=typeof e.fromBlock=="number"?e.fromBlock:typeof e.fromBlock=="string"&&e.fromBlock?Number(e.fromBlock):void 0,i=typeof e.toBlock=="number"?e.toBlock:typeof e.toBlock=="string"&&e.toBlock?Number(e.toBlock):void 0,l=Number.isFinite(a)?a:void 0,u=Number.isFinite(i)?i:void 0,d=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,p=(typeof e.order=="string"?e.order.toUpperCase():void 0)==="ASC"?"ASC":"DESC",h=typeof e.includeInternalTransactions=="boolean"?e.includeInternalTransactions:void 0,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 kr(c){if(c.value_decimal)return c.value_decimal;let e=parseInt(c.token_decimals??"18",10);if(isNaN(e)||e===0)return c.value;try{let t=BigInt(c.value),n=BigInt(10)**BigInt(e),r=t/n,s=(t%n).toString().padStart(e,"0").replace(/0+$/,"");return s?`${r}.${s}`:`${r}`}catch{return c.value}}var Ke=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=V(e.address,t),r=R(e.chain,t),o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):25,a=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,i=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,l=this.parseBlock(e.fromBlock),u=this.parseBlock(e.toBlock),d=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,p=(typeof e.order=="string"?e.order.toUpperCase():"")==="ASC"?"ASC":"DESC",h=typeof e.direction=="string"?e.direction.trim().toLowerCase():"all",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,j=_?S.to_address_label??void 0:S.from_address_label??void 0,G=_?S.to_address_entity??void 0:S.from_address_entity??void 0;return{...S,direction:C,amount_formatted:kr(S),counterparty:q,counterparty_label:j,counterparty_entity:G}}),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 Ye=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=V(e.address,t),r=R(e.chain,t),o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):25,a=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,i=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,l=this.parseBlock(e.fromBlock),u=this.parseBlock(e.toBlock),d=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,p=(typeof e.order=="string"?e.order.toUpperCase():"")==="ASC"?"ASC":"DESC",h=typeof e.includePrices=="boolean"?e.includePrices:void 0,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 rn=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 Tr=["all","7","30","60","90"],je=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=V(e.address,t),r=R(e.chain,t),o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=Tr.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 vr=["all","7","30","60","90"],sn=25,ze=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 ${sn}. 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 ${sn}.`,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 ${sn}.`,required:!1,items:{type:"string"}}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=V(e.address,t),r=R(e.chain,t),o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=vr.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=Sr([...l.map(f=>f.address),...i]).slice(0,sn),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 Sr(c){let e=new Set,t=[];for(let n of c){let r=n.toLowerCase();e.has(r)||(e.add(r),t.push(n))}return t}var xr=BigInt(2)**BigInt(255),Qe=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=V(e.address,t),r=R(e.chain,t),o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):100,a=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,i=await this.service.getWalletApprovals({address:n,chain:r,limit:s,cursor:a});if(!i.success)return{error:i.error||"Failed to fetch wallet approvals"};let l=i.data,d=(l.result||[]).map(f=>Pr(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 Pr(c){let e=Ar(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 Ar(c){if(!c)return!1;try{return BigInt(c)>=xr}catch{return!1}}var Xe=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=V(e.address,t),r=R(e.chain,t),o=await this.service.getWalletDefiSummary({address:n,chain:r});if(!o.success)return{error:o.error||"Failed to fetch wallet DeFi summary"};let s=o.data;return{walletAddress:n,chain:r||"default",activeProtocols:s.active_protocols,totalPositions:s.total_positions,totalUsdValue:s.total_usd_value,totalUnclaimedUsdValue:s.total_unclaimed_usd_value,protocols:(s.protocols||[]).map(a=>({protocolName:a.protocol_name,protocolId:a.protocol_id,protocolUrl:a.protocol_url,protocolLogo:a.protocol_logo,positions:a.positions,totalUsdValue:a.total_usd_value,totalUnclaimedUsdValue:a.total_unclaimed_usd_value,accountHealthFactor:a.account_health_factor??null}))}}};var Je=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=V(e.address,t),r=R(e.chain,t),o=await this.service.getWalletDefiPositions({address:n,chain:r});if(!o.success)return{error:o.error||"Failed to fetch wallet DeFi positions"};let s=o.data||[];return{walletAddress:n,chain:r||"default",positionCount:s.length,positions:s.map(a=>_r(a))}}};function _r(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:Cr(c.position)}}function Cr(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 Ze=class extends v{name="get-wallet-defi-protocol-positions";description='Get every position a wallet holds inside a specific DeFi protocol, with full detail. Returns the protocol identity, totals (total_usd_value, total_unclaimed_usd_value), and an array of positions (label, tokens, balance_usd, unclaimed, fee tier, APY, health factor, etc.). Use when the user names a specific protocol, e.g. "my Uniswap v3 positions", "what do I have on Aave v3", "show my Lido staking", "my Curve pools", "Pendle positions". The "protocol" argument is the Moralis protocol id (kebab-case), e.g. uniswap-v2, uniswap-v3, pancakeswap-v2, pancakeswap-v3, quickswap-v2, quickswap-v3, sushiswap-v2, aave-v2, aave-v3, aave-lido, fraxswap-v1, fraxswap-v2, lido, makerdao, eigenlayer, pendle, etherfi, rocketpool, sparkfi, takara-lend, neverland, kintsu. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Do NOT use for: cross-protocol view \u2192 get-wallet-defi-positions; totals only \u2192 get-wallet-defi-summary.';category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"protocol",type:"string",description:'Moralis protocol identifier in kebab-case. Examples: "uniswap-v2", "uniswap-v3", "pancakeswap-v2", "pancakeswap-v3", "quickswap-v2", "quickswap-v3", "sushiswap-v2", "aave-v2", "aave-v3", "aave-lido", "fraxswap-v1", "fraxswap-v2", "lido", "makerdao", "eigenlayer", "pendle", "etherfi", "rocketpool", "sparkfi", "takara-lend", "neverland", "kintsu"Use get-wallet-defi-summary to discover which protocols a wallet uses.',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.protocol?.trim();if(!n)return{error:'Protocol identifier is required (e.g. "uniswap-v3", "aave-v3").'};let r=V(e.address,t),o=R(e.chain,t),s=await this.service.getWalletDefiProtocolPositions({address:r,protocol:n,chain:o});if(!s.success)return{error:s.error||"Failed to fetch wallet DeFi protocol positions"};let a=s.data;return a?{walletAddress:r,chain:o||"default",protocolName:a.protocol_name,protocolId:a.protocol_id,protocolUrl:a.protocol_url,protocolLogo:a.protocol_logo,totalUsdValue:a.total_usd_value,totalUnclaimedUsdValue:a.total_unclaimed_usd_value,accountHealthFactor:a.account_data?.health_factor??null,positionCount:(a.positions||[]).length,positions:(a.positions||[]).map(Ur)}:{error:"No data returned for this protocol"}}};function Ur(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 Rr(c){try{return(Number(BigInt(c))/1e18).toFixed(10).replace(/\.?0+$/,"")||"0"}catch{return c}}var et=class extends v{name="get-transaction-by-hash";description='Get full decoded details of a single EVM transaction by its hash. Returns: status (success/failed), timestamp, from/to addresses with labels and entity names, native value transferred (ETH), transaction fee, gas used, decoded function call with params, and decoded event logs (ERC-20 transfers, swaps, approvals, etc.). Use this when the user asks: "what did this transaction do?", "explain tx 0x\u2026", "was this transaction successful?", "who sent/received in this tx?", "what function was called?". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"transactionHash",type:"string",description:"The transaction hash to look up (0x\u2026).",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new E(e)}async run(e,t){let n=e.transactionHash;if(!n)return{error:"Transaction hash is required."};let r=R(e.chain,t),o=await this.service.getTransactionByHash({transactionHash:n,chain:r});if(!o.success||!o.data)return{error:o.error||"Failed to fetch transaction"};let s=o.data,a=(s.logs??[]).filter(l=>!!l.decoded_event).map(l=>({contract:l.address,signature:l.decoded_event.signature,label:l.decoded_event.label,params:l.decoded_event.params})),i=s.logs.map(l=>({address:l.address,decodedEvent:l.decoded_event?{signature:l.decoded_event.signature,label:l.decoded_event.label,params:l.decoded_event.params}:null}));return{hash:s.hash,status:s.receipt_status==="1"?"success":s.receipt_status==="0"?"failed":"unknown",timestamp:s.block_timestamp,blockNumber:s.block_number,from:{address:s.from_address,label:s.from_address_label||null,entity:s.from_address_entity||null},to:s.to_address?{address:s.to_address,label:s.to_address_label||null,entity:s.to_address_entity||null}:null,valueEth:Rr(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 ue=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?D(n):"all",timeFrame:o??null,count:c.length,tokens:c}}};var Je=class extends v{name="get-wallet-token-balances";description='Get all ERC-20 token balances for a wallet address, including USD prices and 24h changes. Use this when the user asks about: "my tokens", "my balances", "what do I hold", "portfolio", "how much ETH/USDC/\u2026 do I have", or any wallet balance question. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. ';category="blockchain-data";parameters=[{name:"address",type:"string",description:"Wallet address to query (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=K(e.address,t),r=E(e.chain,t),o=await this.service.getWalletTokenBalances({address:n,chain:r,excludeSpam:!1,excludeUnverifiedContracts:!1});if(!o.success)return{error:o.error||"Failed to fetch token balances"};let s=o.data?.result||[],a=s.map((i,c)=>{let u=i.name||i.symbol||"Unknown",d=i.balance_formatted??"0",p=i.symbol||"",m=typeof i.usd_value=="number"?`$${i.usd_value}`:"$-";return`${c+1}. ${u}: ${d} ${p} (${m})`.trim()});return{walletAddress:n,chain:r||"default",tokenCount:s.length,tokens:a,totalUsdValue:s.reduce((i,c)=>i+(c.usd_value||0),0)}}};var Ze=class extends v{name="get-wallet-history";description=`Get a wallet's decoded on-chain transaction history. Always fetches the latest 100 transactions. Each transaction includes: category, summary, from_address, to_address, and nested arrays: native_transfers (each has direction="send"|"receive", from_address, to_address, value_formatted, token_symbol), erc20_transfers (each has direction, token_symbol, value_formatted), nft_transfers. To find ETH sends/receives, inspect native_transfers[].direction \u2014 ETH moves appear across many categories (e.g. "token swap", "send", "receive"), not just category="send". Use this tool for: native coin transfers, swaps, airdrops, deposits, withdrawals, contract interactions, mixed history. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Returns paginated results \u2014 pass cursor to get the next page. Do NOT use for: specific ERC-20 tokens \u2192 get-wallet-token-transfers; NFT transfers \u2192 get-wallet-nft-transfers; balances \u2192 get-wallet-token-balances.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"fromDate",type:"string",description:'Inclusive start date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"toDate",type:"string",description:'Inclusive end date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"fromBlock",type:"number",description:"Minimum block number.",required:!1},{name:"toBlock",type:"number",description:"Maximum block number.",required:!1},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1},{name:"order",type:"string",description:'"DESC" newest first (default) or "ASC" oldest first.',required:!1,default:"DESC"},{name:"includeInternalTransactions",type:"boolean",description:"Include decoded internal transactions on each item.",required:!1},{name:"nftMetadata",type:"boolean",description:"Include NFT normalized metadata in nft_transfers.",required:!1},{name:"limit",type:"number",description:"Number of transactions to return per page. Defaults to 10.",required:!1,default:10}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=K(e.address,t),r=E(e.chain,t),o=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,s=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,a=typeof e.fromBlock=="number"?e.fromBlock:typeof e.fromBlock=="string"&&e.fromBlock?Number(e.fromBlock):void 0,i=typeof e.toBlock=="number"?e.toBlock:typeof e.toBlock=="string"&&e.toBlock?Number(e.toBlock):void 0,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",g=typeof e.includeInternalTransactions=="boolean"?e.includeInternalTransactions:void 0,f=typeof e.nftMetadata=="boolean"?e.nftMetadata:void 0,h=typeof e.limit=="number"?e.limit:typeof e.limit=="string"?Number(e.limit):void 0,y=Number.isFinite(h)&&h>0?h:10,k=await this.service.getWalletHistory({address:n,chain:r,limit:y,fromDate:o,toDate:s,fromBlock:c,toBlock:u,cursor:d,order:m,includeInternalTransactions:g,nftMetadata:f});if(!k.success)return{error:k.error||"Failed to fetch wallet history"};let w=k.data?.result||[];return{walletAddress:n,chain:r||"default",pagination:{cursor:k.data?.cursor??null,hasMore:!!k.data?.cursor},transactionCount:w.length,transactions:w}}};function Or(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 et=class extends v{name="get-wallet-token-transfers";description=`Get all ERC-20 token transfers for a wallet from the dedicated Moralis transfers endpoint. Each transfer includes direction (send/receive), human-readable amount, and the exact counterparty address with its label/entity. The response includes a "counterparties" section that aggregates unique sender/recipient addresses with totals. Use tokenSymbol (e.g. "USDC") to filter by token name \u2014 the tool automatically resolves the contract address by checking the wallet's token balances first, then falling back to token search. Use contractAddresses when you already have the exact contract address. Use this tool for questions like: "When did I last send USDC and to whom?" (tokenSymbol=USDC, direction=send, limit=1), "Who have I sent token 0x\u2026 to?" (contractAddresses=[0x\u2026], direction=send), "Who sent me USDT?" (tokenSymbol=USDT, direction=receive), "Show all USDC transfers" (tokenSymbol=USDC), "What tokens did I receive this month?" (fromDate=..., direction=receive). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Returns paginated results \u2014 pass cursor to get the next page. IMPORTANT \u2014 this tool covers ERC-20 tokens ONLY. Do NOT use it for native coins (ETH, BNB, MATIC, AVAX, etc.) \u2014 native coin transfers are not ERC-20 and will not appear here. For native coin transfer history use get-wallet-history instead.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"tokenSymbol",type:"string",description:'Filter by token symbol (e.g. "USDC", "USDT", "WETH"). The tool resolves the contract address automatically: first checks wallet balances, then searches by symbol on the chain. Use this when the user mentions a token by name/symbol. Takes precedence over contractAddresses when both are given.',required:!1},{name:"contractAddresses",type:"array",description:"Filter by one or more ERC-20 token contract addresses (0x\u2026). Use when the exact contract address is already known. Ignored if tokenSymbol is provided.",required:!1,items:{type:"string"}},{name:"direction",type:"string",description:'Client-side direction filter: "send" (wallet is sender), "receive" (wallet is recipient), "all" (default).',required:!1,default:"all"},{name:"limit",type:"number",description:'Page size (1-100, default 25). Use limit=1 ONLY when the user asks for the single most recent transfer (e.g. "L\u1EA7n cu\u1ED1i c\xF9ng t\xF4i g\u1EEDi USDC l\xE0 khi n\xE0o?"). For "show recent history" or "list my transfers" questions use the default (25). Use 100 for bulk counterparty analysis.',required:!1,default:25},{name:"fromDate",type:"string",description:'Inclusive start date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"toDate",type:"string",description:'Inclusive end date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"fromBlock",type:"number",description:"Minimum block number.",required:!1},{name:"toBlock",type:"number",description:"Maximum block number.",required:!1},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1},{name:"order",type:"string",description:'"DESC" newest first (default) or "ASC" oldest first.',required:!1,default:"DESC"}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=K(e.address,t),r=E(e.chain,t),o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):25,a=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,i=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,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",g=typeof e.direction=="string"?e.direction.trim().toLowerCase():"all",f=g==="send"?"send":g==="receive"?"receive":"all",h=typeof e.tokenSymbol=="string"&&e.tokenSymbol?e.tokenSymbol.trim():void 0,y=Array.isArray(e.contractAddresses)?e.contractAddresses.filter(S=>typeof S=="string"&&!!S).map(S=>S.trim()):void 0,k,w;if(h){let S=await this.resolveContractAddressBySymbol(h,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 C=S.from_address.toLowerCase()===T,U=C?"send":"receive",G=C?S.to_address:S.from_address,X=C?S.to_address_label??void 0:S.from_address_label??void 0,j=C?S.to_address_entity??void 0:S.from_address_entity??void 0;return{...S,direction:U,amount_formatted:Or(S),counterparty:G,counterparty_label:X,counterparty_entity:j}}),P=f==="all"?x:x.filter(S=>S.direction===f);if(h&&!y){let S=h.toLowerCase();P=P.filter(C=>(C.token_symbol??"").toLowerCase()===S)}let M=this.buildCounterparties(P);return{walletAddress:n,chain:r||"default",resolvedToken:k?{symbol:k,name:w,contractAddress:y?.[0]}:null,filters:{tokenSymbol:h||null,contractAddresses:y||null,direction:f,limit:s,order:m,fromDate:a||null,toDate:i||null,fromBlock: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:P.length,transfers:P,counterparties:M}}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 tt=class extends v{name="get-wallet-nft-transfers";description=`Get all NFT transfers for a wallet from the dedicated Moralis NFT transfers endpoint. Each transfer includes direction (send/receive), the exact counterparty address, token ID, collection, contract type (ERC721/ERC1155), and optional last sale price. The response includes a 'counterparties' section aggregating unique senders/recipients with token IDs and collections. Use this tool for questions like: "Which NFTs did I sell and to whom?" (direction=send), "Who sent me NFTs from collection 0x\u2026?" (contractAddresses=[0x\u2026], direction=receive), "Show all transfers for my NFT collection 0x\u2026" (contractAddresses=[0x\u2026]), "What NFTs did I mint?" (direction=receive), "Show NFT sales with prices" (direction=send, includePrices=true). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Returns paginated results \u2014 pass cursor to get the next page.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"contractAddresses",type:"array",description:"Filter by one or more NFT contract addresses (0x\u2026). Use this to scope queries to a specific collection. Omit to return all NFT transfers.",required:!1,items:{type:"string"}},{name:"direction",type:"string",description:'Client-side direction filter: "send" (wallet sent the NFT), "receive" (wallet received it), "all" (default).',required:!1,default:"all"},{name:"limit",type:"number",description:"Page size (1-100, default 25).",required:!1,default:25},{name:"fromDate",type:"string",description:'Inclusive start date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"toDate",type:"string",description:'Inclusive end date (ISO-8601 or "YYYY-MM-DD").',required:!1},{name:"fromBlock",type:"number",description:"Minimum block number.",required:!1},{name:"toBlock",type:"number",description:"Maximum block number.",required:!1},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1},{name:"order",type:"string",description:'"DESC" newest first (default) or "ASC" oldest first.',required:!1,default:"DESC"},{name:"includePrices",type:"boolean",description:"If true, include last_sale price data (buyer, seller, price, USD value, payment token) on each transfer. Useful for questions about NFT sale prices.",required:!1}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=K(e.address,t),r=E(e.chain,t),o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):25,a=typeof e.fromDate=="string"&&e.fromDate?e.fromDate:void 0,i=typeof e.toDate=="string"&&e.toDate?e.toDate:void 0,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",g=typeof e.includePrices=="boolean"?e.includePrices:void 0,f=typeof e.direction=="string"?e.direction.trim().toLowerCase():"all",h=f==="send"?"send":f==="receive"?"receive":"all",y=Array.isArray(e.contractAddresses)?e.contractAddresses.filter(P=>typeof P=="string"&&!!P).map(P=>P.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:g});if(!k.success)return{error:k.error||"Failed to fetch NFT transfers"};let w=n.toLowerCase(),T=(k.data?.result||[]).map(P=>{let M=P.from_address.toLowerCase()===w,S=M?"send":"receive",C=M?P.to_address:P.from_address,U=M?P.to_address_label??void 0:P.from_address_label??void 0,G=M?P.to_address_entity??void 0:P.from_address_entity??void 0;return{...P,direction:S,counterparty:C,counterparty_label:U,counterparty_entity:G}}),A=h==="all"?T:T.filter(P=>P.direction===h),x=this.buildCounterparties(A);return{walletAddress:n,chain:r||"default",filters:{contractAddresses:y||null,direction:h,limit:s,order:m,fromDate:a||null,toDate:i||null,fromBlock:c??null,toBlock:u??null,includePrices:g??null},pagination:{cursor:k.data?.cursor??null,pageSize:k.data?.page_size??s,page:k.data?.page??null,hasMore:!!k.data?.cursor},transferCount:A.length,transfers:A,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 gn=class extends v{name="get-wallet-net-worth";description=`Get a wallet's total net worth in USD, with a per-chain breakdown. Returns: total_networth_usd (grand total across all queried chains), chains[] (each with chain, native_balance_formatted, native_balance_usd, token_balance_usd, networth_usd), and optional unsupported_chain_ids / unavailable_chains lists. Use this tool for questions like: "How much is my wallet worth?", "What is my total portfolio value?", "What is my net worth on Ethereum?", "Portfolio value across all chains?". Pass chains=["0x1","0x2105"] to query multiple chains at once; defaults to the connected chain. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Do NOT use for: individual token balances \u2192 get-wallet-token-balances; realized profit/loss \u2192 get-wallet-pnl-summary.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chains",type:"array",description:'One or more hex chain IDs to aggregate. Values: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea If omitted, defaults to the connected chain.',required:!1,items:{type:"string"}},{name:"excludeSpam",type:"boolean",description:"Exclude tokens flagged as spam. Default true for cleaner totals.",required:!1,default:!0},{name:"excludeUnverifiedContracts",type:"boolean",description:"Exclude unverified token contracts. Default false.",required:!1,default:!1},{name:"maxTokenInactivity",type:"number",description:"Exclude tokens that have been inactive for more than N days.",required:!1},{name:"minPairSideLiquidityUsd",type:"number",description:"Exclude tokens with pair-side liquidity below this USD threshold.",required:!1}];service;constructor(e){super(),this.service=new N(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 qr=["all","7","30","60","90"],nt=class extends v{name="get-wallet-pnl-summary";description=`Get a wallet's aggregate realized profit-and-loss summary across all tokens it has traded. Returns: total_count_of_trades, total_trade_volume (USD), total_realized_profit_usd, total_realized_profit_percentage, total_buys, total_sells, total_sold_volume_usd, total_bought_volume_usd. Use this tool for questions like: "How much profit have I made trading?", "What is my realized PnL?", "How many trades have I done this month?" (days=30), "Am I up or down overall?". IMPORTANT \u2014 this endpoint only supports mainnet: Ethereum ("0x1"), Base ("0x2105"), Polygon ("0x89"). Use days="all" (default), "7", "30", "60", or "90" to scope the time window. Do NOT use for: per-token breakdown \u2192 get-wallet-pnl; current portfolio value \u2192 get-wallet-token-balance; transfer history \u2192 get-wallet-history.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"days",type:"string",description:'Time window for the summary. Values: "all" (default, lifetime), "7", "30", "60", "90". Pass "30" when the user asks about the last month, "7" for the last week, etc.',required:!1,default:"all"}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=K(e.address,t),r=E(e.chain,t),o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=qr.includes(o)?o:void 0,a=await this.service.getWalletPnlSummary({address:n,chain:r,days:s});if(!a.success)return{error:a.error||"Failed to fetch wallet PnL summary"};let i=a.data;return{walletAddress:n,chain:r||"default",days:s??"all",summary:{totalCountOfTrades:i.total_count_of_trades,totalTradeVolume:i.total_trade_volume,totalRealizedProfitUsd:i.total_realized_profit_usd,totalRealizedProfitPercentage:i.total_realized_profit_percentage,totalBuys:i.total_buys,totalSells:i.total_sells,totalBoughtVolumeUsd:i.total_bought_volume_usd,totalSoldVolumeUsd:i.total_sold_volume_usd}}}};var Fr=["all","7","30","60","90"],fn=25,ot=class extends v{name="get-wallet-pnl";description=`Get a wallet's realized profit-and-loss broken down per ERC-20 token. Each entry includes: token_address, name, symbol, decimals, logo, avg_buy_price_usd, avg_sell_price_usd, total_usd_invested, total_tokens_bought, total_tokens_sold, total_sold_usd, avg_cost_of_quantity_sold, count_of_trades, realized_profit_usd, realized_profit_percentage, total_buys, total_sells, possible_spam. Use this tool for questions like: "Which tokens made me the most money?", "What are my best/worst trades?", "How much profit did I make on USDC?" (tokenSymbols=["USDC"]), "PnL for PEPE and DEGEN this month" (tokenSymbols=["PEPE","DEGEN"], days=30), "PnL breakdown for each token I traded this month" (days=30). IMPORTANT \u2014 this endpoint ONLY supports mainnet: Ethereum ("0x1"), Polygon ("0x89"), Base ("0x2105"). Use days="all" (default), "7", "30", "60", or "90" for the time window. Pass tokenSymbols (by ticker, auto-resolved) and/or tokenAddresses (raw 0x\u2026) to restrict to specific tokens \u2014 combined total capped at ${fn}. Do NOT use for: aggregate summary \u2192 get-wallet-pnl-summary; current balances \u2192 get-wallet-token-balances; transfer history \u2192 get-wallet-token-transfers.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"days",type:"string",description:'Time window for the breakdown. Values: "all" (default, lifetime), "7", "30", "60", "90".',required:!1,default:"all"},{name:"tokenSymbols",type:"array",description:`Optional list of token symbols (e.g. ["USDC", "PEPE"]). Auto-resolved to contract addresses via the connected wallet's balances first, then Pantograph search. Symbols that can't be resolved are skipped. Merged with tokenAddresses; combined total capped at ${fn}.`,required:!1,items:{type:"string"}},{name:"tokenAddresses",type:"array",description:`Optional list of raw ERC-20 contract addresses (0x\u2026). Use when the exact addresses are already known. Merged with tokenSymbols; combined total capped at ${fn}.`,required:!1,items:{type:"string"}}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=K(e.address,t),r=E(e.chain,t),o=typeof e.days=="string"?e.days.trim().toLowerCase():"",s=Fr.includes(o)?o:void 0,a=Array.isArray(e.tokenSymbols)?e.tokenSymbols.filter(f=>typeof f=="string"&&!!f.trim()).map(f=>f.trim()):[],i=Array.isArray(e.tokenAddresses)?e.tokenAddresses.filter(f=>typeof f=="string"&&!!f.trim()).map(f=>f.trim()):[],c=[],u=[];a.length>0&&(await Promise.all(a.map(h=>this.resolveContractAddress(h,r,n)))).forEach((h,y)=>{h?c.push({symbol:a[y],address:h.address}):u.push(a[y])});let d=$r([...c.map(f=>f.address),...i]).slice(0,fn),p=d.length>0?d:void 0,m=await this.service.getWalletPnl({address:n,chain:r,days:s,tokenAddresses:p});if(!m.success)return{error:m.error||"Failed to fetch wallet PnL"};let g=m.data?.result??[];return{walletAddress:n,chain:r||"default",days:s??"all",filters:{tokenAddresses:p??null,resolvedSymbols:c.length>0?c:null,unresolvedSymbols:u.length>0?u:null},tokenCount:g.length,tokens:g}}async resolveContractAddress(e,t,n){let r=e.toLowerCase();if(n){let s=await this.service.getWalletTokenBalances({address:n,chain:t});if(s.success&&s.data?.result?.length){let a=s.data.result.find(i=>(i.symbol??"").toLowerCase()===r);if(a)return{address:a.token_address,symbol:a.symbol,name:a.name}}}let o=await this.service.searchTokensByKey({key:e,chain:t});if(o.success&&o.data?.length){let a=o.data.find(i=>(i.symbol??"").toLowerCase()===r)??o.data[0];if(a?.token_address)return{address:a.token_address,symbol:a.symbol,name:a.name}}}};function $r(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 Wr=BigInt(2)**BigInt(255),rt=class extends v{name="get-wallet-approvals";description=`List every active ERC-20 token approval granted by a wallet \u2014 i.e. which contracts can still spend the wallet's tokens. For each approval, returns: the token (address, name, symbol, logo, current balance, USD price, USD at risk, possible_spam, verified_contract), the spender (address, label, entity \u2014 e.g. "Uniswap V3 Router", "Permit2"), the approved amount (value / value_formatted / isUnlimited flag), and when the approval was granted (block number, timestamp, transaction hash). Also returns aggregates: total approvals, unlimited-approval count, total USD at risk, and approvals flagged as risky (spam tokens, unverified contracts, or unlimited allowance to unlabeled spenders). Use for questions like: "What approvals do I have?", "Show my token approvals", "Am I exposed to any risky approvals?", "Which contracts can drain my wallet?", "Do I have any unlimited approvals?", "Revoke approvals / token allowances". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Paginated \u2014 pass cursor to fetch the next page.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"limit",type:"number",description:"Page size (1-100, default 100).",required:!1,default:100},{name:"cursor",type:"string",description:"Pagination cursor from a previous response.",required:!1}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=K(e.address,t),r=E(e.chain,t),o=typeof e.limit=="number"?e.limit:Number(e.limit),s=Number.isFinite(o)&&o>0?Math.min(100,Math.floor(o)):100,a=typeof e.cursor=="string"&&e.cursor?e.cursor:void 0,i=await this.service.getWalletApprovals({address:n,chain:r,limit:s,cursor:a});if(!i.success)return{error:i.error||"Failed to fetch wallet approvals"};let c=i.data,d=(c.result||[]).map(f=>Vr(f)),p=0,m=0,g=[];for(let f of d){f.isUnlimited&&(m+=1);let h=parseFloat(f.token.usdAtRisk??"");Number.isFinite(h)&&(p+=h);let y=f.token.possibleSpam||f.token.verifiedContract===!1,k=!f.spender.label&&!f.spender.entity;(y||f.isUnlimited&&k)&&g.push(f)}return{walletAddress:n,chain:r||"default",filters:{limit:s,cursor:a??null},pagination:{cursor:c.cursor??null,pageSize:c.page_size??s,page:c.page??null,hasMore:!!c.cursor},summary:{totalApprovals:d.length,unlimitedApprovals:m,totalUsdAtRisk:p,riskyApprovalCount:g.length},approvals:d,riskyApprovals:g}}};function Vr(l){let e=Hr(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 Hr(l){if(!l)return!1;try{return BigInt(l)>=Wr}catch{return!1}}var st=class extends v{name="get-wallet-defi-summary";description=`Get a wallet's DeFi protocols summary: which protocols the wallet is using, total USD value locked across all protocols, total unclaimed rewards/fees, number of active protocols, and total number of positions. Returns a per-protocol breakdown (protocol name, logo, url, positions count, usd value, unclaimed). Use for questions like: "Which DeFi protocols am I using?", "What's my total DeFi value?", "How much do I have in DeFi?", "Show my DeFi portfolio overview", "Unclaimed rewards across protocols". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Do NOT use for: detailed per-position data \u2192 get-wallet-defi-positions; positions inside a single protocol \u2192 get-wallet-defi-protocol-positions.`;category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=K(e.address,t),r=E(e.chain,t),o=await this.service.getWalletDefiSummary({address:n,chain:r});if(!o.success)return{error:o.error||"Failed to fetch wallet DeFi summary"};let s=o.data;return{walletAddress:n,chain:r||"default",activeProtocols:s.active_protocols,totalPositions:s.total_positions,totalUsdValue:s.total_usd_value,totalUnclaimedUsdValue:s.total_unclaimed_usd_value,protocols:(s.protocols||[]).map(a=>({protocolName:a.protocol_name,protocolId:a.protocol_id,protocolUrl:a.protocol_url,protocolLogo:a.protocol_logo,positions:a.positions,totalUsdValue:a.total_usd_value,totalUnclaimedUsdValue:a.total_unclaimed_usd_value,accountHealthFactor:a.account_health_factor??null}))}}};var at=class extends v{name="get-wallet-defi-positions";description='Get every DeFi position a wallet holds across all supported protocols, with full detail: protocol identity, position label (e.g. "Liquidity Provision", "Supply", "Borrow", "Staking"), tokens involved (with balances and USD values), balance_usd, total_unclaimed_usd_value, and position_details (fee tier, liquidity, APY, price range, health factor, etc.). Use for questions like: "Show my DeFi positions", "What LP positions do I have?", "What am I staking?", "Do I have any borrows?", "List all my DeFi holdings with details". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea. Do NOT use for: aggregate totals \u2192 get-wallet-defi-summary; positions filtered to a single protocol \u2192 get-wallet-defi-protocol-positions.';category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=K(e.address,t),r=E(e.chain,t),o=await this.service.getWalletDefiPositions({address:n,chain:r});if(!o.success)return{error:o.error||"Failed to fetch wallet DeFi positions"};let s=o.data||[];return{walletAddress:n,chain:r||"default",positionCount:s.length,positions:s.map(a=>Gr(a))}}};function Gr(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:Kr(l.position)}}function Kr(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 it=class extends v{name="get-wallet-defi-protocol-positions";description='Get every position a wallet holds inside a specific DeFi protocol, with full detail. Returns the protocol identity, totals (total_usd_value, total_unclaimed_usd_value), and an array of positions (label, tokens, balance_usd, unclaimed, fee tier, APY, health factor, etc.). Use when the user names a specific protocol, e.g. "my Uniswap v3 positions", "what do I have on Aave v3", "show my Lido staking", "my Curve pools", "Pendle positions". The "protocol" argument is the Moralis protocol id (kebab-case), e.g. uniswap-v2, uniswap-v3, pancakeswap-v2, pancakeswap-v3, quickswap-v2, quickswap-v3, sushiswap-v2, aave-v2, aave-v3, aave-lido, fraxswap-v1, fraxswap-v2, lido, makerdao, eigenlayer, pendle, etherfi, rocketpool, sparkfi, takara-lend, neverland, kintsu. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea (EVM mainnets only). Do NOT use for: cross-protocol view \u2192 get-wallet-defi-positions; totals only \u2192 get-wallet-defi-summary.';category="blockchain-data";parameters=[{name:"address",type:"string",description:"EVM wallet address (0x\u2026). If omitted, uses the connected wallet from user context.",required:!1},{name:"protocol",type:"string",description:'Moralis protocol identifier in kebab-case. Examples: "uniswap-v2", "uniswap-v3", "pancakeswap-v2", "pancakeswap-v3", "quickswap-v2", "quickswap-v3", "sushiswap-v2", "aave-v2", "aave-v3", "aave-lido", "fraxswap-v1", "fraxswap-v2", "lido", "makerdao", "eigenlayer", "pendle", "etherfi", "rocketpool", "sparkfi", "takara-lend", "neverland", "kintsu"Use get-wallet-defi-summary to discover which protocols a wallet uses.',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=e.protocol?.trim();if(!n)return{error:'Protocol identifier is required (e.g. "uniswap-v3", "aave-v3").'};let r=K(e.address,t),o=E(e.chain,t),s=await this.service.getWalletDefiProtocolPositions({address:r,protocol:n,chain:o});if(!s.success)return{error:s.error||"Failed to fetch wallet DeFi protocol positions"};let a=s.data;return a?{walletAddress:r,chain:o||"default",protocolName:a.protocol_name,protocolId:a.protocol_id,protocolUrl:a.protocol_url,protocolLogo:a.protocol_logo,totalUsdValue:a.total_usd_value,totalUnclaimedUsdValue:a.total_unclaimed_usd_value,accountHealthFactor:a.account_data?.health_factor??null,positionCount:(a.positions||[]).length,positions:(a.positions||[]).map(Yr)}:{error:"No data returned for this protocol"}}};function Yr(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 jr(l){try{return(Number(BigInt(l))/1e18).toFixed(10).replace(/\.?0+$/,"")||"0"}catch{return l}}var lt=class extends v{name="get-transaction-by-hash";description='Get full decoded details of a single EVM transaction by its hash. Returns: status (success/failed), timestamp, from/to addresses with labels and entity names, native value transferred (ETH), transaction fee, gas used, decoded function call with params, and decoded event logs (ERC-20 transfers, swaps, approvals, etc.). Use this when the user asks: "what did this transaction do?", "explain tx 0x\u2026", "was this transaction successful?", "who sent/received in this tx?", "what function was called?". Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"transactionHash",type:"string",description:"The transaction hash to look up (0x\u2026).",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new N(e)}async run(e,t){let n=e.transactionHash;if(!n)return{error:"Transaction hash is required."};let r=E(e.chain,t),o=await this.service.getTransactionByHash({transactionHash:n,chain:r});if(!o.success||!o.data)return{error:o.error||"Failed to fetch transaction"};let s=o.data,a=(s.logs??[]).filter(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:jr(s.value||"0"),fee:s.transaction_fee,gasUsed:s.receipt_gas_used,gasPriceGwei:s.gas_price?(Number(s.gas_price)/1e9).toFixed(4):null,decodedCall:s.decoded_call?{signature:s.decoded_call.signature,label:s.decoded_call.label,params:s.decoded_call.params}:null,decodedEvents:a,contractCreated:s.receipt_contract_address||null,logs:i}}};var he=class extends v{kind="data";category="nft";noSuggestions=!0;parameters=[];linkPhrase="this website";url;constructor(e){super();let t=e?.url?.trim();this.url=t||void 0}async run(){let e=this.url?`[${this.linkPhrase}](${this.url})`:this.linkPhrase,t=this.template.replace("{link}",e);return{message:t,_instructions:`This is a fixed notice. Output it to the user EXACTLY as written below \u2014 in English, without translating, rephrasing, summarising, or adding/removing any words. Preserve the Markdown link verbatim (do not change, wrap, or drop the URL or the linked words). Do not mention tools. Reply with only this message:
|
|
16
16
|
|
|
17
|
-
`+t}}};var
|
|
17
|
+
`+t}}};var ct=class extends he{name="get-wallet-nfts";description='Use when the user wants to view their NFTs / NFT collection / holdings ("my NFTs", "show my NFTs", "what NFTs do I own", "NFT collection"). Returns a fixed notice directing them to the NFT website. Takes no arguments.';template="Please view your NFT collection on {link} for more details"};var ut=class extends he{name="get-nft-contract-info";description="Use when the user asks about an NFT collection / contract (name, symbol, type, floor, socials). Returns a fixed notice directing them to the NFT website. Takes no arguments.";template="Please view your NFT collection on {link} for more details"};var dt=class extends he{name="get-nft-metadata";description='Use when the user asks about a specific NFT \u2014 its details, traits, image, or floor price ("show NFT #1234 of collection X", "traits of this NFT", "floor price of this NFT"). Returns a fixed notice directing them to the NFT website. Takes no arguments.';template="Please view your NFT collection on {link} for more details"};var mt=class extends v{name="gemini-search-ai";description="Ask a search-grounded Gemini model for blockchain / crypto insights that no dedicated tool can answer. Use this as a LAST-RESORT fallback when the specialised data tools are insufficient. Backed by live Google Search grounding \u2014 good for: market analysis, category/theme queries (meme, AI, gaming, L2, RWA), DeFi protocol explanations, cross-chain comparisons, trend / sentiment, recent news or announcements, gas insights, NFT collection lore, and any open-ended crypto reasoning question. Chain-agnostic \u2014 write the chain (Ethereum, BSC, Base, \u2026) and any addresses, time range, or token symbols directly into the prompt. Do NOT use this for: token balances \u2192 get-wallet-token-balances; token prices/metadata \u2192 get-token-info; NFT holdings \u2192 get-wallet-nfts; NFT contract info \u2192 get-nft-contract-info; transaction details \u2192 get-transaction-by-hash; wallet history \u2192 get-wallet-history; token transfers \u2192 get-wallet-token-transfers; NFT transfers \u2192 get-wallet-nft-transfers; token holders \u2192 get-token-holders; wallet PnL \u2192 get-wallet-pnl or get-wallet-pnl-summary; pool / liquidity data \u2192 pool-agent tools.";category="blockchain-ai";parameters=[{name:"prompt",type:"string",description:'A clear, self-contained English question for the AI. Must include every detail needed to answer without the conversation context: chain (Ethereum / BSC / Base / \u2026), wallet or contract addresses, token symbols, time range, what to compare or rank. Instruct the model to use up-to-date web sources when relevant. Example: "List the top 10 meme-category tokens on Ethereum right now, ranked by 24h trading volume, using up-to-date market data."',required:!0}];llm;constructor(e){super(),this.llm=new me(e??{})}async run(e,t){let n=typeof e.prompt=="string"?e.prompt.trim():"";if(!n)return{error:"Missing required parameter: prompt"};let r=[];t?.walletAddress&&r.push(`- Connected wallet address: ${t.walletAddress}`),t?.chain&&r.push(`- Current chain: ${t.chain}`),r.push("Use this context only when relevant to the question. Do not invent on-chain values.");let o=Date.now(),s=[{role:"system",content:`[USER CONTEXT]
|
|
18
18
|
${r.join(`
|
|
19
|
-
`)}`,timestamp:o},{role:"user",content:n,timestamp:o}];try{return{source:"gemini-search-ai",answer:(await this.llm.chat(s,void 0,{googleSearch:!0,maxRetries:1})).text?.trim()??""}}catch(a){return{error:a instanceof Error?a.message:String(a)}}}};var
|
|
19
|
+
`)}`,timestamp:o},{role:"user",content:n,timestamp:o}];try{return{source:"gemini-search-ai",answer:(await this.llm.chat(s,void 0,{googleSearch:!0,maxRetries:1})).text?.trim()??""}}catch(a){return{error:a instanceof Error?a.message:String(a)}}}};var zr="https://interface.gateway.uniswap.org",Qr="/v2/uniswap.explore.v1.ExploreStatsService/ExploreStats",So="/v2/Search.v1.SearchService/SearchTokens",Xr="/v1/graphql",Ce=3,yn=800,Jr={"0x1":"ETHEREUM","0xa":"OPTIMISM","0x38":"BNB","0x89":"POLYGON","0x2105":"BASE","0xa4b1":"ARBITRUM","0xa86a":"AVALANCHE","0xe708":"LINEA"},Zr=`query V3Pool($chain: Chain!, $address: String!) {
|
|
20
20
|
v3Pool(chain: $chain, address: $address) {
|
|
21
21
|
id
|
|
22
22
|
protocolVersion
|
|
@@ -49,7 +49,7 @@ fragment TokenPrice on Token {
|
|
|
49
49
|
id
|
|
50
50
|
market(currency: USD) { id price { id value __typename } __typename }
|
|
51
51
|
__typename
|
|
52
|
-
}`,
|
|
52
|
+
}`,_o=`
|
|
53
53
|
fragment SimpleTokenDetails on Token {
|
|
54
54
|
...TokenBasicInfoParts
|
|
55
55
|
project { id isSpam logoUrl name safetyLevel __typename }
|
|
@@ -64,7 +64,7 @@ fragment TokenPrice on Token {
|
|
|
64
64
|
id
|
|
65
65
|
market(currency: USD) { id price { id value __typename } __typename }
|
|
66
66
|
__typename
|
|
67
|
-
}`,
|
|
67
|
+
}`,es=`query V2Pair($chain: Chain!, $address: String!) {
|
|
68
68
|
v2Pair(chain: $chain, address: $address) {
|
|
69
69
|
id
|
|
70
70
|
protocolVersion
|
|
@@ -81,7 +81,7 @@ fragment TokenPrice on Token {
|
|
|
81
81
|
__typename
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
-
${
|
|
84
|
+
${_o}`,ts=`query V4Pool($chain: Chain!, $poolId: String!) {
|
|
85
85
|
v4Pool(chain: $chain, poolId: $poolId) {
|
|
86
86
|
id
|
|
87
87
|
protocolVersion
|
|
@@ -102,12 +102,12 @@ ${go}`,Br=`query V4Pool($chain: Chain!, $poolId: String!) {
|
|
|
102
102
|
__typename
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
${go}`,Or={"0x1":1,"0xa":10,"0x38":56,"0x89":137,"0x2105":8453,"0xa4b1":42161,"0xa86a":43114,"0xe708":59144};function ln(c){return new Promise(e=>setTimeout(e,c))}function Ln(c){if(!c)return 1;let e=c.toLowerCase();return Or[e]??1}function Mn(c){if(!c)return"ETHEREUM";let e=c.toLowerCase();return Dr[e]??"ETHEREUM"}function mo(c,e){if(!c)return[];switch(e){case"V2":return c.poolStatsV2??[];case"V3":return c.poolStatsV3??[];case"V4":return c.poolStatsV4??[]}}function po(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 ho(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 J=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??Er).replace(/\/$/,"")}async getExploreStats(e){let t=Ln(e?.chain),n=e?.multichain===!0,r=encodeURIComponent(JSON.stringify({chainId:String(t),multichain:n})),o=`${this.baseUrl}${Nr}?connect=v1&encoding=json&message=${r}`,s="Unknown error";for(let a=1;a<=xe;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<xe&&await ln(an)}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:mo(o.data.stats,t).map(i=>({...i,apr:ho(i),fee24hUsd:i.volume1Day?.value&&i.feeTier?i.feeTier/1e4*i.volume1Day.value:void 0})).sort((i,l)=>po(l,r)-po(i,r)).slice(0,n)}}async getV3PoolDetail(e){let t=e.address?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V3Pool",query:Lr,variables:{chain:Mn(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:Mr,variables:{chain:Mn(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:Br,variables:{chain:Mn(e.chain),poolId:t},pickField:"v4Pool"}):{success:!1,error:"V4 poolId is required"}}async fetchPoolDetailGraphQL(e){let t=`${this.baseUrl}${Ir}`,n=JSON.stringify({operationName:e.operationName,variables:e.variables,query:e.query}),r="Unknown error";for(let o=1;o<=xe;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<xe&&await ln(an)}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=Ln(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}${uo}`,a=JSON.stringify({searchQuery:t,chainIds:[n],searchType:"TOKEN",page:r,size:o}),i="Unknown error";for(let l=1;l<=xe;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<xe&&await ln(an)}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=Ln(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}${uo}`,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 mo(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=ho(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<=xe;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<xe&&await ln(an)}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 $r=["tvl","volume1Day","volume1Week","volume30Day","txCount","apr"],qr=["V2","V3","V4"],st=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 J(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(qr.includes(t))return t}return"V3"}parseSortKey(e){if(typeof e=="string"){let t=e.trim();if($r.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 Fr=365,Wr=/^0x[a-fA-F0-9]{40}$/,at=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 J(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||!Wr.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*Fr*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 Vr=["V2","V3","V4","ALL"],it=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 J(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(Vr.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 Hr=365,fo=/^0x[a-fA-F0-9]{40}$/,lt=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 J(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"?fo.test(e.id)?await this.service.getV2PoolDetail({address:e.id,chain:t}):{success:!1,error:"Resolved V2 pair has no valid address"}:fo.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*Hr*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 Bn=require("js-sha3"),yo=2n**96n;function bo(c,e,t){let n=c*c,r=yo*yo,o=n*10n**18n/r;return Number(o)/1e18*Math.pow(10,e-t)}function re(c,e,t){return Math.pow(1.0001,c)*Math.pow(10,e-t)}function Ce(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 ge(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 ct=-887272,ut=887272;function fe(c){return c<ct?ct:c>ut?ut:c}var Gr=/^0x[0-9a-f]+$/i;function te(c){return c.startsWith("0x")||c.startsWith("0X")?c.slice(2):c}function cn(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 un(c){if(!Gr.test(c)||te(c).length!==40)throw new Error(`encodeAddress: invalid address ${c}`);return cn(c.toLowerCase())}function se(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 cn(n)}function On(c){let e=typeof c=="bigint"?c:BigInt(c);if(e>=0n)return cn(e.toString(16));let t=1n<<256n;return cn((t+e).toString(16))}function wo(c){return"0x"+(0,Bn.keccak_256)(Kr(c))}function Kr(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 ye(c){return"0x"+(0,Bn.keccak_256)(new TextEncoder().encode(c)).slice(0,8)}function Wt(c,e){let t=c.substring(e,e+64);return t?BigInt("0x"+t):0n}function $n(c,e){let t=c.substring(e,e+64),n=BigInt("0x"+t),r=1n<<256n,o=1n<<23n;return n>=1n<<255n?Number(n-r):(n<o,Number(n))}function qn(c,e){return"0x"+c.substring(e+24,e+64).toLowerCase()}var Pe=class{config;constructor(e){this.config=e}hasIntegratedApproval(){return!1}};var Yr="https://exchange-api.keyring.app/admin/setting?configs=others",jr="https://api.coinpool.app/config/bridgeProviderByChain",zr="https://coinpool-api-op.bacoor-test001.xyz/config/bridgeProviderByChain";function Qr(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 To(c){return await(await fetch(c,{method:"GET",headers:{Accept:"application/json"}})).json()}var Xr=Qr(async()=>(await To(Yr))?.others??null),dn={prod:null,dev:null},Vt={prod:null,dev:null};async function Jr(c){let e=c?"prod":"dev";if(dn[e])return dn[e];if(Vt[e])return Vt[e];let t=c?jr:zr;return Vt[e]=(async()=>{try{let n=await To(t);return dn[e]=n?.data??null,dn[e]}catch{return null}finally{Vt[e]=null}})(),Vt[e]}function Zr(c){if(!c)return null;try{let e=JSON.parse(c);return e&&typeof e=="object"?e:null}catch{return null}}function ko(c){if(c==null)return 0;let e=typeof c=="number"?c:Number(c);return Number.isFinite(e)?e:0}function es(c){return c==="debridge"||c==="relay"}async function mn(c,e){let n=(await Jr(e))?.[String(c)];return es(n)?n:null}async function dt(c,e){let t=await Xr();if(!t)return null;let n,r;if(c==="debridge")n=Zr(t.affiliateRecipient)?.[String(e)],r=ko(t.AFFILIATE_FEE_PERENT);else if(c==="relay")n=t.RELAY_AFFILIATE_FEE_RECIPIENT,r=ko(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 ts(c){return ne[c.toLowerCase()]}var vo={};function Fn(c){let e={};if(c)for(let[t,n]of Object.entries(c))typeof n=="string"&&n.trim()&&(e[t.toLowerCase()]=n.trim());vo=e}function mt(c){let e=c.toLowerCase();return vo[e]??ne[e]}async function be(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 So(c,e,t){return be(c,"eth_call",[{to:e,data:t},"latest"])}async function Wn(c,e,t){let n=mt(c);if(!n)return null;try{return await So(n,e,t)}catch{return null}}var ns="https://dln.debridge.finance/v1.0",os="0x0000000000000000000000000000000000000000",pt=class extends Pe{apiBaseUrl;accessToken;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??ns,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 dt(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()===os)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 Wn(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 rs="https://api.relay.link";function xo(c){return c===7565164||c==="7565164"?792703809:c}var ht=class extends Pe{apiBaseUrl;apiKey;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??rs,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 dt(this.getProviderName(),t),d={user:a||i,originChainId:xo(t),destinationChainId:xo(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 pn={debridge:{apiBaseUrl:"https://dln.debridge.finance/v1.0",accessToken:"d6c45897b8f6"},relay:{apiBaseUrl:"https://api.relay.link"}},Ht="debridge";function ss(c){if(c==null)return null;let e=typeof c=="number"?c:Number(c);return Number.isFinite(e)?e:null}var Ue=class{providerConfig;isProduction;cache=new Map;constructor(e=pn,t=!0){this.providerConfig=e,this.isProduction=t}async getService(e){let t=await this.getActiveProvider(e),n=this.cache.get(t);if(n)return n;let r=this.createService(t);return this.cache.set(t,r),r}async getServiceByProvider(e){let t=this.cache.get(e);if(t)return t;let n=this.createService(e);return this.cache.set(e,n),n}async getActiveProvider(e){let t=ss(e);return t===null?Ht:await mn(t,this.isProduction)??Ht}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 pt(t);case"relay":return new ht(t);default:{let n=e;throw new Error(`Unknown swap provider: ${String(n)}`)}}}},de=new Ue;var as="https://api.coinpool.app/config/addresses",is="https://coinpool-api-op.bacoor-test001.xyz/config/addresses",hn={prod:null,dev:null},Gt={prod:null,dev:null};async function ls(c){let e=c?"prod":"dev";if(hn[e])return hn[e];if(Gt[e])return Gt[e];let t=c?as:is;return Gt[e]=(async()=>{try{let r=await(await fetch(t,{method:"GET",headers:{Accept:"application/json"}})).json();return hn[e]=r?.data??null,hn[e]}catch{return null}finally{Gt[e]=null}})(),Gt[e]}function cs(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 Po(c,e,t){let n=await ls(t);if(!n)return;let r=cs(e);if(!r)return;let o=n[c]?.[r];return Array.isArray(o)?o[0]:typeof o=="string"?o:void 0}async function gn(c,e){return Po("createPoolProxyV3",c,e)}async function fn(c,e){return Po("nftPositionUniswap",c,e)}var L="0x0000000000000000000000000000000000000000",us={"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 X(c){return us[c.toLowerCase()]}function gt(c){return X(c)?.native}var Ao="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";function Vn(c){let e=c.stableTicks??3,t=c.widePercent??{down:50,up:100},n=re(c.currentTick,c.token0Decimals,c.token1Decimals),r=fe(ge(c.currentTick-e*c.tickSpacing,c.tickSpacing,"down")),o=fe(ge(c.currentTick+e*c.tickSpacing,c.tickSpacing,"up")),s=n*(1-t.down/100),a=n*(1+t.up/100),i=Ce(s,c.token0Decimals,c.token1Decimals),l=Ce(a,c.token0Decimals,c.token1Decimals),u=fe(ge(i,c.tickSpacing,"down")),d=fe(ge(l,c.tickSpacing,"up")),m=(p,h,f,g,y)=>{let k=re(g,c.token0Decimals,c.token1Decimals),w=re(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 ds=ye("slot0()"),ms=ye("liquidity()"),ps=ye("tickSpacing()"),hs=ye("fee()"),gs=ye("token0()"),fs=ye("token1()"),ys=ye("decimals()"),Re=class{swapFactory;isProduction;constructor(e){this.isProduction=e?.isProduction??!0,this.swapFactory=e?.swapFactory??(this.isProduction?de:new Ue(void 0,!1))}getRpcUrl(e){let t=mt(e);if(t)return t;let n=X(e);if(!n)throw new Error(`Unsupported chain: ${e}`);return n.defaultRpc}async getGatewayAddress(e){return gn(e,this.isProduction)}async getPositionManagerAddress(e){return fn(e,this.isProduction)}async ethCall(e,t,n){return be(this.getRpcUrl(e),"eth_call",[{to:t,data:n},"latest"])}async getNativeBalance(e,t){let n=await be(this.getRpcUrl(e),"eth_getBalance",[t,"latest"]);return BigInt(n)}async getGasPrice(e){let t=await be(this.getRpcUrl(e),"eth_gasPrice",[]);return BigInt(t)}async getTransactionReceipt(e,t){return await be(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()!==Ao||"0x"+s[1].slice(-40).toLowerCase()!==L))try{return BigInt(s[3]).toString()}catch{return null}}return null}async estimateGasReserveWei(e){let t=X(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,ds),this.ethCall(e,t,ms),this.ethCall(e,t,ps),this.ethCall(e,t,hs),this.ethCall(e,t,gs),this.ethCall(e,t,fs)]),l=te(n),u=Wt(l,0),d=$n(l,64),m=Wt(te(r),0),p=Number($n(te(o),0)),h=Number(Wt(te(s),0)),f=qn(te(a),0),g=qn(te(i),0),[y,k]=await Promise.all([this.readErc20Decimals(e,f),this.readErc20Decimals(e,g)]),w=bo(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,ys);return Number(Wt(te(n),0))}async getNativePriceUsd(e){let t=X(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=ye("mint((address,uint256,address,uint256)[2],(address,bytes,uint256)[2],(uint24,uint256,uint256,int24,int24))"),n=e.paymentInfo.map(h=>un(h.tokenIn)+se(h.tokenInAmount)+un(h.tokenOut)+se(h.tokenOutAmount)).join(""),o=e.exchanges.map(h=>{let f=te(h.data||"0x"),g=f.length/2,y=se(g),k=(64-f.length%64)%64,w=f+"0".repeat(k);return{head:un(h.to)+se(96)+se(h.value),body:y+w}}).map(h=>h.head+h.body),s=o.length*32,a=[];for(let h of o)a.push(se(s)),s+=h.length/2;let i=a.join("")+o.join(""),l=se(e.fee,24)+se(e.amount0Min)+se(e.amount1Min)+On(e.tickLower)+On(e.tickUpper),m=256+32+160,p=n+se(m)+l+i;return t+p}_keccak(e){return wo(e)}};var bs=/^0x[a-fA-F0-9]{40}$/,ft=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 J(e),this.pool=new Re(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=X(n);if(!h)return{error:"unsupported_chain",chain:n,_instructions:`Chain ${n} is not supported by the add-liquidity flow.`};let f=gt(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||!bs.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=gt(n),q=Number(_)/10**C.decimals,j=Number(A)/10**C.decimals,G=S!=null?q*S:null,[ie,Y]=await Promise.all([this.pool.readErc20Decimals(n,x.token0),this.pool.readErc20Decimals(n,x.token1)]),ke={min:re(ct,ie,Y),max:re(ut,ie,Y)},Te=null;A===0n?Te="no_balance":G!=null&&G<this.minProvideUsd&&(Te="insufficient_balance");let ve=P.token0,Ee=P.token1,Cn=[25,50,75,100].map(M=>({percent:M,amountWei:(_*BigInt(M)/100n).toString(),amount:q*(M/100)})),Ne=null,le=null;if(a!=null){le=a;let M=BigInt(Math.floor(le*10**C.decimals));M>0n&&(Ne=M.toString())}else if(i!=null&&S!=null&&S>0){le=i/S;let M=BigInt(Math.floor(le*10**C.decimals));M>0n&&(Ne=M.toString())}let Ie={address:L,isNative:!0,symbol:C.symbol,name:C.name,decimals:C.decimals,priceUsd:S,balanceWei:A.toString(),balance:j,gasReserveWei:I.toString(),spendableWei:_.toString(),spendable:q,spendableUsd:G,quickRates:Cn,minProvideUsd:this.minProvideUsd,warning:Te,warningMessage:Te==="no_balance"?`You have no ${C.symbol} on ${h.name}. Top up to add liquidity.`:Te==="insufficient_balance"?`Spendable ${C.symbol} is below the $${this.minProvideUsd} minimum.`:null,prefillAmount:le,prefillAmountWei:Ne},Gn=this.computePrefillRange({explicitMinPrice:m,explicitMaxPrice:p,rangeStrategy:d,currentTick:x.currentTick,tickSpacing:x.tickSpacing,token0Decimals:ie,token1Decimals:Y,priceBounds:ke});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:ve?.symbol,name:ve?.name,logo:ve?.logo},token1:{address:x.token1,symbol:Ee?.symbol,name:Ee?.name,logo:Ee?.logo}},inputToken:Ie,priceBounds:ke,prefillRange:Gn,gatewayAddress:g}},summary:this.buildSummary({chainName:h.name,pair:`${ve?.symbol??r}/${Ee?.symbol??o}`,feePercent:x.fee/1e4,warning:Te,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=Vn({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 _o=[{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"}]}],yn=[{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 yt=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 Re(e?.pool),this.defaultSlippageBps=e?.defaultSlippageBps??"auto",this.mintMinPercent=e?.mintMinPercent??80,this.pantograph=new Q}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=X(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=gt(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=Ce(o,f,g),k=Ce(s,f,g),w=fe(ge(y,h.tickSpacing,"down")),b=fe(ge(k,h.tickSpacing,"up"));if(w>=b||w<ct||b>ut)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:re(w,f,g),maxPrice:re(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 j=W=>W*BigInt(this.mintMinPercent)/100n,G=j(q[0].minAmountOut),ie=j(q[1].minAmountOut),Y=q.map(W=>({tokenIn:W.tokenIn,tokenInAmount:BigInt(W.amountIn),tokenOut:W.tokenOut,tokenOutAmount:BigInt(W.minAmountOut)})),ke=q.map(W=>({to:W.tx.to,data:W.tx.data,value:BigInt(W.tx.value||"0")}));console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ paymentInfo:",Y),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ exchangesInfo:",ke),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ args:",{args:[Y,ke,{fee:Number(h.fee),amount0Min:BigInt(G),amount1Min:BigInt(ie),tickLower:w,tickUpper:b}]});let Te=Be({abi:_o,functionName:"mint",args:[Y,ke,{fee:Number(h.fee),amount0Min:BigInt(G),amount1Min:BigInt(ie),tickLower:w,tickUpper:b}]}),ve=a,Ee={chainId:r,to:u,data:Te,value:p.toString(),from:d},Cn={nativeIn:ve,nativeInUsd:A!=null?ve*A:null,ratio:{token0Percent:S.ratio0,token1Percent:S.ratio1},expectedToken0:Number(q[0].minAmountOut)/10**f,expectedToken1:Number(q[1].minAmountOut)/10**g,amount0Min:G.toString(),amount1Min:ie.toString(),slippageBps:i},Ne=await this.pantograph.getTokensMetadata([h.token0,h.token1],r),le=Ne[h.token0],Ie=Ne[h.token1];return{ui:{component:"ConfirmAddLiquidityTx",props:{chain:{hexId:r,name:l.name},unsignedTx:Ee,summary:Cn,pool:{...h,feePercent:h.fee/1e4,token0:{address:h.token0,symbol:le.symbol,name:le.name,decimals:le.decimals,logo:le.icon_image},token1:{address:h.token1,symbol:Ie.symbol,name:Ie.name,decimals:Ie.decimals,logo:Ie.icon_image}},range:T,gatewayAddress:u}},summary:`Ready to add ${ve} ${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 Co=365,ws=1e3,ks=/^0x[a-fA-F0-9]{40}$/;function Ts(c,e){return{depositUsd:c,aprPercent:e*100,dailyUsd:c*e/Co,weeklyUsd:c*e/52,monthlyUsd:c*e/12,yearlyUsd:c*e}}var bt=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
|
+
${_o}`,ns={"0x1":1,"0xa":10,"0x38":56,"0x89":137,"0x2105":8453,"0xa4b1":42161,"0xa86a":43114,"0xe708":59144};function bn(l){return new Promise(e=>setTimeout(e,l))}function Gn(l){if(!l)return 1;let e=l.toLowerCase();return ns[e]??1}function Kn(l){if(!l)return"ETHEREUM";let e=l.toLowerCase();return Jr[e]??"ETHEREUM"}function xo(l,e){if(!l)return[];switch(e){case"V2":return l.poolStatsV2??[];case"V3":return l.poolStatsV3??[];case"V4":return l.poolStatsV4??[]}}function Ao(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 Po(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 te=class{baseUrl;constructor(e){this.baseUrl=(e?.baseUrl??zr).replace(/\/$/,"")}async getExploreStats(e){let t=Gn(e?.chain),n=e?.multichain===!0,r=encodeURIComponent(JSON.stringify({chainId:String(t),multichain:n})),o=`${this.baseUrl}${Qr}?connect=v1&encoding=json&message=${r}`,s="Unknown error";for(let a=1;a<=Ce;a++)try{let i=await _.get(o,{headers:{accept:"*/*",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"}});if(!i.ok)throw new Error(I(i));return{success:!0,data:i.data}}catch(i){s=i instanceof Error?i.message:"Unknown error",a<Ce&&await bn(yn)}return{success:!1,error:s}}async getTopPools(e){let t=e?.protocolVersion??"V3",n=e?.limit&&e.limit>0?Math.floor(e.limit):10,r=e?.sortBy??"tvl",o=await this.getExploreStats({chain:e?.chain,multichain:e?.multichain});return!o.success||!o.data?{success:!1,error:o.error||"Failed to fetch ExploreStats"}:{success:!0,data:xo(o.data.stats,t).map(i=>({...i,apr:Po(i),fee24hUsd:i.volume1Day?.value&&i.feeTier?i.feeTier/1e4*i.volume1Day.value:void 0})).sort((i,c)=>Ao(c,r)-Ao(i,r)).slice(0,n)}}async getV3PoolDetail(e){let t=e.address?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V3Pool",query:Zr,variables:{chain:Kn(e.chain),address:t},pickField:"v3Pool"}):{success:!1,error:"Pool address is required"}}async getV2PoolDetail(e){let t=e.address?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V2Pair",query:es,variables:{chain:Kn(e.chain),address:t},pickField:"v2Pair"}):{success:!1,error:"Pair address is required"}}async getV4PoolDetail(e){let t=e.poolId?.trim();return t?this.fetchPoolDetailGraphQL({operationName:"V4Pool",query:ts,variables:{chain:Kn(e.chain),poolId:t},pickField:"v4Pool"}):{success:!1,error:"V4 poolId is required"}}async fetchPoolDetailGraphQL(e){let t=`${this.baseUrl}${Xr}`,n=JSON.stringify({operationName:e.operationName,variables:e.variables,query:e.query}),r="Unknown error";for(let o=1;o<=Ce;o++)try{let s=await _.post(t,n,{headers:{accept:"*/*","content-type":"application/json",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web","_dd-custom-header-graph-ql-operation-name":e.operationName,"_dd-custom-header-graph-ql-operation-type":"query"}});if(!s.ok)throw new Error(I(s));let a=s.data;if(a.errors)throw new Error(`GraphQL error: ${JSON.stringify(a.errors)}`);let i=a.data?.[e.pickField]??void 0;return i?{success:!0,data:i}:{success:!1,error:"Pool not found"}}catch(s){r=s instanceof Error?s.message:"Unknown error",o<Ce&&await bn(yn)}return{success:!1,error:r}}async searchTokens(e){let t=e.query?.trim();if(!t)return{success:!1,error:"Search query is required"};if(B(t))return{success:!1,error:"Address is invalid for token search. If you pasted a 0x address, it may be a Uniswap pool \u2014 try the pool search tool instead."};let n=Gn(e.chain),r=e.page&&e.page>0?Math.floor(e.page):1,o=e.size&&e.size>0?Math.min(Math.floor(e.size),50):15,s=`${this.baseUrl}${So}`,a=JSON.stringify({searchQuery:t,chainIds:[n],searchType:"TOKEN",page:r,size:o}),i="Unknown error";for(let c=1;c<=Ce;c++)try{let u=await _.post(s,a,{headers:{accept:"*/*","connect-protocol-version":"1","content-type":"application/json",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"}});if(!u.ok)throw new Error(I(u));return{success:!0,data:u.data.tokens??[]}}catch(u){i=u instanceof Error?u.message:"Unknown error",c<Ce&&await bn(yn)}return{success:!1,error:i}}async searchPools(e){let t=e.query?.trim();if(!t)return{success:!1,error:"Search query is required"};let n=Gn(e.chain),r=e.page&&e.page>0?Math.floor(e.page):1,o=e.size&&e.size>0?Math.min(Math.floor(e.size),50):15,s=e.protocolVersion??"V3",a=e.feeTier&&e.feeTier>0?Math.floor(e.feeTier):void 0,i=`${this.baseUrl}${So}`,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(h=>h.protocolVersion===s)),a!=null&&(m=m.filter(h=>h.feeTier===a));let g=new Map;for(let h of m)h.id&&g.set(h.id.toLowerCase(),h);if(d.status==="fulfilled"&&d.value.success&&d.value.data){let h=d.value.data.stats,y=s==="ALL"?["V2","V3","V4"]:[s];for(let k of y)for(let w of xo(h,k)){if(!w.id||a!=null&&w.feeTier!==a)continue;let b=(w.token0?.symbol??"").toLowerCase(),T=(w.token1?.symbol??"").toLowerCase(),A=(w.token0?.name??"").toLowerCase(),x=(w.token1?.name??"").toLowerCase();if(!p.some(U=>b.includes(U)||T.includes(U)||A.includes(U)||x.includes(U)))continue;let M=Po(w),S=w.id.toLowerCase(),C=g.get(S);C?g.set(S,{...C,...M!=null?{apr:M}:{},...w.totalLiquidity?.value!=null?{tvlUsd:w.totalLiquidity.value}:{},...w.volume1Day?.value!=null?{volume1DayUsd:w.volume1Day.value}:{},...w.txCount!=null?{txCount:w.txCount}:{}}):g.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,...M!=null?{apr:M}:{},...w.totalLiquidity?.value!=null?{tvlUsd:w.totalLiquidity.value}:{},...w.volume1Day?.value!=null?{volume1DayUsd:w.volume1Day.value}:{},...w.txCount!=null?{txCount:w.txCount}:{}})}}return{success:!0,data:Array.from(g.values()).sort((h,y)=>this.matchScore(y,p)-this.matchScore(h,p))}}async fetchSearchPools(e,t){let n="Unknown error";for(let r=1;r<=Ce;r++)try{let o=await _.post(e,t,{headers:{accept:"*/*","connect-protocol-version":"1","content-type":"application/json",origin:"https://app.uniswap.org",referer:"https://app.uniswap.org/","x-request-source":"uniswap-web"}});if(!o.ok)throw new Error(I(o));return{success:!0,data:o.data.pools??[]}}catch(o){n=o instanceof Error?o.message:"Unknown error",r<Ce&&await bn(yn)}return{success:!1,error:n}}matchScore(e,t){let n=[e.token0?.symbol??"",e.token1?.symbol??""].map(o=>o.toLowerCase()),r=0;for(let o of t){let s=0;for(let a of n)a===o?s=Math.max(s,3):a.startsWith(o)?s=Math.max(s,2):a.includes(o)&&(s=Math.max(s,1));r+=s}return r}};var os=["tvl","volume1Day","volume1Week","volume30Day","txCount","apr"],rs=["V2","V3","V4"],pt=class extends v{name="get-top-pools";description=`List the top Uniswap liquidity pools on a given EVM chain, ranked by TVL by default. Returns each pool's address, fee tier, protocol version, both tokens (symbol, name, address, decimals), TVL, transaction count, and rolling volume buckets (1d / 1w / 30d). Use this tool for questions like: "top pools on Ethereum", "biggest Uniswap V3 pools on Base", "which pool has the most liquidity for USDC", "show pools with highest 24h volume on Arbitrum". Source: https://app.uniswap.org/explore (ExploreStats gateway). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea`;category="blockchain-data";parameters=[{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"protocolVersion",type:"string",description:'Uniswap protocol version to filter by: "V2", "V3", or "V4". Default is "V3" (the dataset with the richest stats). Use "V4" only when the user explicitly asks for V4 pools.',required:!1,default:"V3"},{name:"sortBy",type:"string",description:'Ranking metric: "tvl" (total value locked, default), "volume1Day", "volume1Week", "volume30Day", "txCount", or "apr" (estimated annualized fee yield). Pools are returned in descending order by this metric.',required:!1,default:"tvl"},{name:"limit",type:"number",description:"Maximum number of pools to return after sorting. Defaults to 10.",required:!1,default:10},{name:"multichain",type:"boolean",description:"When true, request Uniswap's multichain aggregated view instead of a single chain. Defaults to false. Most queries should leave this off.",required:!1,default:!1}];service;constructor(e){super(),this.service=new te(e)}async run(e,t){let n=E(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(rs.includes(t))return t}return"V3"}parseSortKey(e){if(typeof e=="string"){let t=e.trim();if(os.includes(t))return t}return"tvl"}formatPool(e){return{apr:e.apr,address:e.id,chain:e.chain,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0,tvlUsd:e.totalLiquidity?.value,volume1DayUsd:e.volume1Day?.value,volume1WeekUsd:e.volume1Week?.value,volume30DayUsd:e.volume30Day?.value,txCount:e.txCount,token0:e.token0?{address:e.token0.address,symbol:e.token0.symbol,name:e.token0.name,decimals:e.token0.decimals,priceUsd:e.token0.price?.value}:void 0,token1:e.token1?{address:e.token1.address,symbol:e.token1.symbol,name:e.token1.name,decimals:e.token1.decimals,priceUsd:e.token1.price?.value}:void 0}}};var ss=365,as=/^0x[a-fA-F0-9]{40}$/,ht=class extends v{name="get-pool-detail";description="Get detailed stats for a single Uniswap V3 pool, identified by token pair (and optional fee tier). Always runs a search first, filters by the provided symbols + fee tier, and only fetches detail when the filter resolves to exactly one pool. Otherwise returns a `candidates` list \u2014 show that to the user, ask which pool they want, then re-call this tool with the missing info. Detail response includes: pool address, fee tier (bps and %), both tokens (symbol, name, address, decimals, USD price), token0/token1 reserves, total TVL with 24h % change, 24h volume, weekly historical volume series, total tx count, and a derived APR estimate (fees \xF7 TVL \xD7 365). NEVER pass a pool address \u2014 the tool resolves the address itself by searching. Token contract addresses are NOT pool addresses. Source: https://app.uniswap.org. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea";category="blockchain-data";parameters=[{name:"token0Symbol",type:"string",description:`Symbol of one of the pool's tokens (e.g. "USDC"). Pair order does not matter \u2014 USDC/WETH and WETH/USDC resolve to the same pool. When both token0Symbol and token1Symbol are provided the search is filtered to that exact pair.`,required:!0},{name:"token1Symbol",type:"string",description:'Symbol of the other pool token (e.g. "WETH"). Optional \u2014 when omitted the tool returns all pools containing token0Symbol so the user can pick.',required:!1},{name:"feeTier",type:"number",description:"Fee tier in basis points (bps): 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. Optional \u2014 when omitted the tool returns all fee tiers for the pair so the user can pick.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new te(e)}async run(e,t){let n=E(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(g=>this.matchesFilter(g,r,o,s,"reversed"));m.length>0&&(d=m,p=!0)}if(d.length===0)return{error:`No V3 pool matched ${this.describeQuery(r,o,s)} on ${D(n)}. Verify the symbols and fee tier with the user.`};if(d.length===1){let m=d[0];if(!m.id||!as.test(m.id))return{error:"Resolved pool has no valid address"};let g=await this.service.getV3PoolDetail({address:m.id,chain:n});return!g.success||!g.data?{error:g.error||"Failed to fetch pool detail"}:{_instructions:"Present pool details in a compact summary. Lead with the pair (token0/token1 symbols), fee tier (%), and chain. Then show: TVL (USD) with 24h % change, 24h volume (USD), 24h fees (USD), estimated APR (%), token0 reserve + USD price, token1 reserve + USD price, and total tx count. Optionally include the weekly historical volume as a sparkline-style bullet list. Format numbers human-readably (e.g. $12.5M, +1.23%, 0.05%). Omit null fields. Do NOT mention tool/API names.",pairOrderMatched:p?"reversed":"ordered",pool:this.formatPool(g.data)}}return{_instructions:"The filter matched multiple pools. Render them as a numbered list (pair, fee tier %, 24h volume, address) and ask the user which one they want to see. When they answer, re-call this tool with their selection \u2014 pass token0Symbol + token1Symbol + feeTier (use the exact feeTier in bps from the chosen candidate). Format numbers human-readably (e.g. $12.5M, 0.05%). Do NOT mention tool/API names.",chain:n,pairOrderMatched:p?"reversed":"ordered",reason:this.candidateReason(r,o,s),missing:this.missingFields(o,s),candidates:this.rankCandidates(d).slice(0,10).map(m=>this.formatCandidate(m))}}matchesFilter(e,t,n,r,o){if(r!=null&&e.feeTier!==r)return!1;let s=(e.token0?.symbol??"").toLowerCase(),a=(e.token1?.symbol??"").toLowerCase(),i=t.toLowerCase();if(n){let 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*ss*100:void 0;return{address:e.address,protocolVersion:e.protocolVersion,feeTierBps:r,feeTierPercent:o,tvlUsd:t,tvlChange24hPercent:e.totalLiquidityPercentChange24h?.value,volume24hUsd:n,fees24hUsd:s,aprPercent:a,txCount:e.txCount,historicalVolumeWeek:this.formatHistorical(e.historicalVolume),token0:e.token0?{address:e.token0.address,symbol:e.token0.symbol,name:e.token0.name,decimals:e.token0.decimals,priceUsd:e.token0.market?.price?.value??e.token0.price?.value,reserve:e.token0Supply,isSpam:e.token0.project?.isSpam,safetyLevel:e.token0.project?.safetyLevel}:void 0,token1:e.token1?{address:e.token1.address,symbol:e.token1.symbol,name:e.token1.name,decimals:e.token1.decimals,priceUsd:e.token1.market?.price?.value??e.token1.price?.value,reserve:e.token1Supply,isSpam:e.token1.project?.isSpam,safetyLevel:e.token1.project?.safetyLevel}:void 0}}formatHistorical(e){if(!(!e||e.length===0))return e.map(t=>({timestamp:t.timestamp,volumeUsd:t.value}))}};var is=["V2","V3","V4","ALL"],gt=class extends v{name="search-pools";description='Search Uniswap liquidity pools on a given chain by free-text query \u2014 a SINGLE token symbol or token name. Returns matching pools with: pool address, fee tier (bps + %), protocol version, both tokens (symbol, name, address, decimals, logo), and short-window USD volume buckets (6h / 12h / 24h). Use this tool when the user names ONE token and wants pools related to it: "find WBTC pools on Optimism", "pools containing PEPE on Ethereum", "show USDC pools". Do NOT use for: (a) two-symbol pair lookups like "USDC/WETH 0.05%" \u2014 use get-pool-detail; (b) raw 0x addresses (token or pool) \u2014 use lookup-pool-by-address; (c) chain-wide rankings with no token mentioned \u2014 use get-top-pools. Source: https://app.uniswap.org search. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.';category="blockchain-data";parameters=[{name:"query",type:"string",description:'Single token symbol or name to search pools by (e.g. "WBTC", "USDC", "pepe"). Do NOT pass a 0x address (use lookup-pool-by-address) or a two-symbol pair (use get-pool-detail).',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea . ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"protocolVersion",type:"string",description:'Filter results by Uniswap protocol version: "V2", "V3", "V4", or "ALL" to include every version. Default is "V3" \u2014 only switch when the user explicitly asks for V2, V4, or all versions.',required:!1,default:"V3"},{name:"page",type:"number",description:"1-based page index. Defaults to 1.",required:!1,default:1},{name:"size",type:"number",description:"Page size (max 50). Defaults to 15.",required:!1,default:15},{name:"feeTier",type:"number",description:'Optional fee tier filter in basis points (bps): 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. When provided, only pools with this exact fee tier are returned. Pass it whenever the user mentions a fee tier in the current message OR earlier in the conversation (e.g. "USDC/WETH 0.05% pool on Base" \u2192 feeTier=500). Omit otherwise.',required:!1}];service;constructor(e){super(),this.service=new te(e)}async run(e,t){let n=typeof e.query=="string"?e.query.trim():"";if(!n)return{error:"Search query is required"};let r=E(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(is.includes(t))return t}return"V3"}formatPool(e){let t={address:e.id,chainId:e.chainId,protocolVersion:e.protocolVersion,feeTierBps:e.feeTier,feeTierPercent:typeof e.feeTier=="number"?e.feeTier/1e4:void 0};e.apr!=null&&(t.apr=e.apr),e.tvlUsd!=null&&(t.tvlUsd=e.tvlUsd);let n=e.volume1DayUsd??e.volumeUsd24hr;n!=null&&(t.volume24hUsd=n),e.volumeUsd6hr!=null&&(t.volume6hUsd=e.volumeUsd6hr),e.volumeUsd12hr!=null&&(t.volume12hUsd=e.volumeUsd12hr),e.txCount!=null&&(t.txCount=e.txCount);let r=e.token0,o=e.token1;return r&&(t.token0={address:r.address,symbol:r.symbol,name:r.name,decimals:r.decimals}),o&&(t.token1={address:o.address,symbol:o.symbol,name:o.name,decimals:o.decimals}),t}};var ls=365,Co=/^0x[a-fA-F0-9]{40}$/,ft=class extends v{name="lookup-pool-by-address";description='Resolve an unknown 0x address as either a Uniswap pool or a token, then return the appropriate data: pool detail when the address is a pool, or the list of pools containing that token when the address is a token. Use this tool when the user pastes a single address and asks about "the pool" without saying which kind. For pool detail (when the address is a pool): pair, fee tier, TVL with 24h % change, 24h volume, 24h fees, estimated APR, token reserves with USD prices, and weekly volume series. For token resolution (when the address is a token): the resolved token (symbol, name, decimals) plus a ranked list of pools that include it (pair, fee tier, 24h volume, pool address). Source: https://app.uniswap.org. Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea';category="blockchain-data";parameters=[{name:"address",type:"string",description:"A 0x-prefixed EVM address. May be a Uniswap pool contract OR a token contract \u2014 the tool figures out which.",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new te(e)}async run(e,t){let n=E(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 ${D(n)}. Verify the chain (the address may exist on a different network) or ask the user for context.`}}async handlePoolHit(e,t){if(!e.id)return{error:"Resolved pool has no identifier"};let n=e.protocolVersion??"V3",r=n==="V4"?await this.service.getV4PoolDetail({poolId:e.id,chain:t}):n==="V2"?Co.test(e.id)?await this.service.getV2PoolDetail({address:e.id,chain:t}):{success:!1,error:"Resolved V2 pair has no valid address"}:Co.test(e.id)?await this.service.getV3PoolDetail({address:e.id,chain:t}):{success:!1,error:"Resolved V3 pool has no valid address"};return!r.success||!r.data?{error:r.error||"Failed to fetch pool detail"}:{_instructions:`The address is a Uniswap ${n} pool. Present pool details in a compact summary. Lead with the pair (token0/token1 symbols), fee tier (%), protocol version, and chain. Then show: TVL (USD) with 24h % change, 24h volume (USD), 24h fees (USD), estimated APR (%), token0 reserve + USD price, token1 reserve + USD price, and total tx count. For V4 pools also mention tickSpacing, isDynamicFee, and the hook address if present. Format numbers human-readably (e.g. $12.5M, +1.23%, 0.05%). Omit null fields. Do NOT mention tool/API names.`,resolvedAs:"pool",chain:t,protocolVersion:n,pool:this.formatPoolDetail(r.data)}}async handleTokenHit(e,t){let n=e.symbol?.trim();if(!n)return{error:"Resolved token has no symbol \u2014 cannot search related pools."};let r=await this.service.searchPools({query:n,chain:t,page:1,size:100,protocolVersion:"V3"});if(!r.success)return{error:r.error||"Failed to fetch pools for resolved token"};let o=(e.address??"").toLowerCase(),a=[...(r.data??[]).filter(i=>{let 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*ls*100:void 0;return{address:e.address??e.poolId,protocolVersion:e.protocolVersion,poolId:e.poolId,tickSpacing:e.tickSpacing,isDynamicFee:e.isDynamicFee,hookAddress:e.hook?.address,feeTierBps:r,feeTierPercent:o,tvlUsd:t,tvlChange24hPercent:e.totalLiquidityPercentChange24h?.value,volume24hUsd:n,fees24hUsd:s,aprPercent:a,txCount:e.txCount,historicalVolumeWeek:this.formatHistorical(e.historicalVolume),token0:e.token0?{address:e.token0.address,symbol:e.token0.symbol,name:e.token0.name,decimals:e.token0.decimals,priceUsd:e.token0.market?.price?.value??e.token0.price?.value,reserve:e.token0Supply}:void 0,token1:e.token1?{address:e.token1.address,symbol:e.token1.symbol,name:e.token1.name,decimals:e.token1.decimals,priceUsd:e.token1.market?.price?.value??e.token1.price?.value,reserve:e.token1Supply}:void 0}}formatHistorical(e){if(!(!e||e.length===0))return e.map(t=>({timestamp:t.timestamp,volumeUsd:t.value}))}};var Yn=require("js-sha3"),Uo=2n**96n;function Ro(l,e,t){let n=l*l,r=Uo*Uo,o=n*10n**18n/r;return Number(o)/1e18*Math.pow(10,e-t)}function ie(l,e,t){return Math.pow(1.0001,l)*Math.pow(10,e-t)}function De(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 we(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 yt=-887272,bt=887272;function ke(l){return l<yt?yt:l>bt?bt:l}var cs=/^0x[0-9a-f]+$/i;function re(l){return l.startsWith("0x")||l.startsWith("0X")?l.slice(2):l}function wn(l){let e=re(l).toLowerCase();if(e.length>64)throw new Error(`pad32: input too long (${e.length})`);return e.padStart(64,"0")}function kn(l){if(!cs.test(l)||re(l).length!==40)throw new Error(`encodeAddress: invalid address ${l}`);return wn(l.toLowerCase())}function le(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 wn(n)}function jn(l){let e=typeof l=="bigint"?l:BigInt(l);if(e>=0n)return wn(e.toString(16));let t=1n<<256n;return wn((t+e).toString(16))}function Eo(l){return"0x"+(0,Yn.keccak_256)(us(l))}function us(l){let e=re(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 Te(l){return"0x"+(0,Yn.keccak_256)(new TextEncoder().encode(l)).slice(0,8)}function Qt(l,e){let t=l.substring(e,e+64);return t?BigInt("0x"+t):0n}function zn(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 Qn(l,e){return"0x"+l.substring(e+24,e+64).toLowerCase()}var Ue=class{config;constructor(e){this.config=e}hasIntegratedApproval(){return!1}};var ds="https://exchange-api.keyring.app/admin/setting?configs=others",ms="https://api.coinpool.app/config/bridgeProviderByChain",ps="https://coinpool-api-op.bacoor-test001.xyz/config/bridgeProviderByChain";function hs(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 Io(l){return(await _.get(l,{headers:{Accept:"application/json"}})).data}var gs=hs(async()=>(await Io(ds))?.others??null),Tn={prod:null,dev:null},Xt={prod:null,dev:null};async function fs(l){let e=l?"prod":"dev";if(Tn[e])return Tn[e];if(Xt[e])return Xt[e];let t=l?ms:ps;return Xt[e]=(async()=>{try{let n=await Io(t);return Tn[e]=n?.data??null,Tn[e]}catch{return null}finally{Xt[e]=null}})(),Xt[e]}function ys(l){if(!l)return null;try{let e=JSON.parse(l);return e&&typeof e=="object"?e:null}catch{return null}}function No(l){if(l==null)return 0;let e=typeof l=="number"?l:Number(l);return Number.isFinite(e)?e:0}function bs(l){return l==="debridge"||l==="relay"}async function vn(l,e){let n=(await fs(e))?.[String(l)];return bs(n)?n:null}async function wt(l,e){let t=await gs();if(!t)return null;let n,r;if(l==="debridge")n=ys(t.affiliateRecipient)?.[String(e)],r=No(t.AFFILIATE_FEE_PERENT);else if(l==="relay")n=t.RELAY_AFFILIATE_FEE_RECIPIENT,r=No(t.RELAY_AFFILIATE_FEE_PERCENT);else return null;return!n||r<=0?null:{affiliateFeeRecipient:n,affiliateFeePercent:r}}var se={"0x1":"https://ethereum-rpc.publicnode.com","0xa":"https://mainnet.optimism.io","0x38":"https://bsc-dataseed.binance.org","0x89":'https://polygon-bor-rpc.publicnode.com"',"0x2105":"https://mainnet.base.org","0xa4b1":"https://arb1.arbitrum.io/rpc","0xa86a":"https://avalanche-c-chain-rpc.publicnode.com","0xe708":"https://rpc.linea.build"};function ws(l){return se[l.toLowerCase()]}var Do={};function Xn(l){let e={};if(l)for(let[t,n]of Object.entries(l))typeof n=="string"&&n.trim()&&(e[t.toLowerCase()]=n.trim());Do=e}function kt(l){let e=l.toLowerCase();return Do[e]??se[e]}async function ve(l,e,t){for(let r=0;r<=5;r++){let o=await _.post(l,{jsonrpc:"2.0",id:Date.now(),method:e,params:t},{headers:{"Content-Type":"application/json"}});if(o.status===429&&r<5){await new Promise(a=>setTimeout(a,2**r*500));continue}if(!o.ok)throw new Error(`RPC ${e}: ${I(o)}`);let s=o.data;if(s.error)throw new Error(`RPC ${e}: ${s.error.message}`);if(s.result===void 0)throw new Error(`RPC ${e}: empty result`);return s.result}throw new Error(`RPC ${e}: exceeded max retries (429)`)}function Lo(l,e,t){return ve(l,"eth_call",[{to:e,data:t},"latest"])}async function Jn(l,e,t){let n=kt(l);if(!n)return null;try{return await Lo(n,e,t)}catch{return null}}var ks="https://dln.debridge.finance/v1.0",Ts="0x0000000000000000000000000000000000000000",Tt=class extends Ue{apiBaseUrl;accessToken;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??ks,this.accessToken=e.accessToken??"d6c45897b8f6"}getProviderName(){return"debridge"}hasIntegratedApproval(){return!1}async getQuote(e){let{srcChainId:t,srcTokenAddress:n,srcTokenAmount:r,dstChainId:o,dstTokenAddress:s,recipientAddress:a,senderAddress:i,slippage:c,isCrossChain:u}=e,d=u??t!==o,p=await wt(this.getProviderName(),t);try{if(d){let f={srcChainId:String(t),srcChainTokenIn:n,srcChainTokenInAmount:r,dstChainId:String(o),dstChainTokenOut:s,dstChainTokenOutRecipient:a,srcChainOrderAuthorityAddress:i,dstChainOrderAuthorityAddress:a,accesstoken:this.accessToken};p&&(f.affiliateFeeRecipient=p.affiliateFeeRecipient,f.affiliateFeePercent=p.affiliateFeePercent);let h=await this.get(`${this.apiBaseUrl}/dln/order/create-tx`,f);return h.error||h.errorMessage?{success:!1,error:h.error??h.errorMessage,errorMessage:h.errorMessage??"deBridge quote failed"}:{success:!0,provider:"debridge",tx:h.tx,estimation:h.estimation,isCrossChain:!0,srcChainId:t,dstChainId:o,raw:h}}let m={chainId:String(t),tokenIn:n,tokenInAmount:r,tokenOut:s,tokenOutRecipient:a,accesstoken:this.accessToken};p&&(m.affiliateFeeRecipient=p.affiliateFeeRecipient,m.affiliateFeePercent=p.affiliateFeePercent),c!==void 0&&(m.slippage=c);let g=await this.get(`${this.apiBaseUrl}/chain/transaction`,m);return g.error||g.errorMessage?{success:!1,error:g.error??g.errorMessage,errorMessage:g.errorMessage??"deBridge quote failed"}:{success:!0,provider:"debridge",tx:g.tx,isCrossChain:!1,srcChainId:t,dstChainId:t,raw:g}}catch(m){return{success:!1,error:m,errorMessage:m instanceof Error?m.message:"Failed to fetch quote"}}}async checkApproval(e){let{chain:t,userAddress:n,tokenAddress:r,amount:o}=e,s=e.quoteData?.tx?.to;if(!s)return{isNeeded:!1,error:"No contract address found in quote"};if(!r||r.toLowerCase()===Ts)return{isNeeded:!1,integrated:!1,contractAddress:s,message:"Native coin needs no approval."};let a=await this.readAllowance(t,r,n,s);if(a===null)return{isNeeded:!0,integrated:!1,contractAddress:s,message:"Could not read allowance; approval required before the swap."};let i;try{i=BigInt(o)}catch{i=0n}return a>=i?{isNeeded:!1,integrated:!1,contractAddress:s,message:"Sufficient allowance already granted."}:{isNeeded:!0,integrated:!1,contractAddress:s,message:"Approval required: current allowance is below the swap amount."}}async readAllowance(e,t,n,r){if(!e)return null;let o=i=>i.toLowerCase().replace(/^0x/,"").padStart(64,"0"),s=`0xdd62ed3e${o(n)}${o(r)}`,a=await Jn(e,t,s);if(typeof a!="string"||!/^0x[0-9a-fA-F]*$/.test(a)||a==="0x")return null;try{return BigInt(a)}catch{return null}}async executeSwap(e){let{quoteData:t}=e;return{success:!0,provider:"debridge",txData:t.tx??void 0,estimation:t.estimation}}async trackTransaction(e){let{requestIdOrTxHash:t}=e;try{let n=await this.get(`${this.apiBaseUrl}/dln/tx/${t}/order-ids`,{accesstoken:this.accessToken});if(!n.orderIds?.length)return{success:!1,status:"PENDING"};let r=n.orderIds[0];switch(((await this.get(`${this.apiBaseUrl}/dln/order/${r}/status`,{accesstoken:this.accessToken})).status??"").toUpperCase()){case"FULFILLED":case"SENTUNLOCK":case"CLAIMEDUNLOCK":return{success:!0,status:"COMPLETED"};case"ORDERCANCELLED":case"SENTORDERCANCEL":case"CLAIMEDORDERCANCEL":return{success:!0,status:"FAILED"};default:return{success:!0,status:"PENDING"}}}catch(n){return{success:!1,status:"ERROR",error:n instanceof Error?n.message:String(n)}}}async get(e,t){let n=Object.entries(t).map(([s,a])=>`${encodeURIComponent(s)}=${encodeURIComponent(String(a))}`).join("&"),r=n?`${e}?${n}`:e;return(await _.get(r,{headers:{Accept:"application/json"}})).data}};var vs="https://api.relay.link";function Mo(l){return l===7565164||l==="7565164"?792703809:l}var vt=class extends Ue{apiBaseUrl;apiKey;constructor(e={}){super(e),this.apiBaseUrl=e.apiBaseUrl??vs,this.apiKey=e.apiKey}getProviderName(){return"relay"}hasIntegratedApproval(){return!0}async getQuote(e){let{srcChainId:t,srcTokenAddress:n,srcTokenAmount:r,dstChainId:o,dstTokenAddress:s,recipientAddress:a,senderAddress:i,slippage:c}=e,u=await wt(this.getProviderName(),t),d={user:a||i,originChainId:Mo(t),destinationChainId:Mo(o),originCurrency:n,destinationCurrency:s,amount:r,recipient:a||i,tradeType:"EXACT_INPUT"};u&&(d.appFees=[{recipient:u.affiliateFeeRecipient,fee:Math.floor(u.affiliateFeePercent*100)}]),typeof 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"),g=p.steps?.find(k=>k.id==="swap"||k.id==="deposit"),f=g?.items?.[0]?.data,h=Array.isArray(f?.instructions);return{success:!0,provider:"relay",tx:f?h?{data:{instructions:f.instructions??[],addressLookupTableAddresses:f.addressLookupTableAddresses??[]},chainId:f.chainId}:{from:f.from,to:f.to,data:f.data,value:f.value,chainId:f.chainId,maxFeePerGas:f.maxFeePerGas,maxPriorityFeePerGas:f.maxPriorityFeePerGas}:null,isCrossChain:t!==o,srcChainId:t,dstChainId:o,raw:{steps:p.steps,fees:p.fees,details:p.details,approveStep:m,requestId:g?.requestId}}}catch(p){return{success:!1,error:p,errorMessage:p instanceof Error?p.message:"Failed to fetch quote"}}}async checkApproval(e){let t=e.quoteData?.raw?.approveStep;if(t){let n=t.items?.[0]?.data;return{isNeeded:!0,integrated:!0,approvalData:n,contractAddress:n?.to,message:"Approval step is included in quote and will be executed first."}}return{isNeeded:!1,integrated:!0,message:"No approval needed or already approved"}}async executeSwap(e){let{quoteData:t}=e,n=t.raw;return n?.steps?{success:!0,provider:"relay",steps:n.steps,estimation:t.estimation}:{success:!1,error:"Invalid quote data"}}async trackTransaction(e){try{let n=(await this.request("/intents/status/v3","GET",{requestId:e.requestIdOrTxHash})).status??"";return n==="success"||n==="refunded"?{success:!0,status:"COMPLETED"}:n==="failure"||n==="expired"?{success:!0,status:"FAILED"}:{success:!0,status:"PENDING"}}catch(t){return{success:!1,status:"ERROR",error:t instanceof Error?t.message:String(t)}}}async request(e,t,n){let r={"Content-Type":"application/json"};this.apiKey&&(r["x-api-key"]=this.apiKey);let o=`${this.apiBaseUrl}${e}`,s;if(t==="GET"&&n){let i=Object.entries(n).map(([c,u])=>`${encodeURIComponent(c)}=${encodeURIComponent(String(u))}`).join("&");o=`${o}?${i}`}else t==="POST"&&n&&(s=n);let a=await _.request(t,o,s,{headers:r});if(!a.ok)throw new Error(I(a));return a.data}};var Sn={debridge:{apiBaseUrl:"https://dln.debridge.finance/v1.0",accessToken:"d6c45897b8f6"},relay:{apiBaseUrl:"https://api.relay.link"}},Jt="debridge";function Ss(l){if(l==null)return null;let e=typeof l=="number"?l:Number(l);return Number.isFinite(e)?e:null}var Le=class{providerConfig;isProduction;cache=new Map;constructor(e=Sn,t=!0){this.providerConfig=e,this.isProduction=t}async getService(e){let t=await this.getActiveProvider(e),n=this.cache.get(t);if(n)return n;let r=this.createService(t);return this.cache.set(t,r),r}async getServiceByProvider(e){let t=this.cache.get(e);if(t)return t;let n=this.createService(e);return this.cache.set(e,n),n}async getActiveProvider(e){let t=Ss(e);return t===null?Jt:await vn(t,this.isProduction)??Jt}async isProviderActive(e,t){return await this.getActiveProvider(t)===e}getAvailableProviders(){return Object.keys(this.providerConfig)}clearCache(){this.cache.clear()}createService(e){let t=this.providerConfig[e]??{};switch(e){case"debridge":return new Tt(t);case"relay":return new vt(t);default:{let n=e;throw new Error(`Unknown swap provider: ${String(n)}`)}}}},ge=new Le;var xs="https://api.coinpool.app/config/addresses",As="https://coinpool-api-op.bacoor-test001.xyz/config/addresses",xn={prod:null,dev:null},Zt={prod:null,dev:null};async function Ps(l){let e=l?"prod":"dev";if(xn[e])return xn[e];if(Zt[e])return Zt[e];let t=l?xs:As;return Zt[e]=(async()=>{try{let r=(await _.get(t,{headers:{Accept:"application/json"}})).data;return xn[e]=r?.data??null,xn[e]}catch{return null}finally{Zt[e]=null}})(),Zt[e]}function _s(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 Bo(l,e,t){let n=await Ps(t);if(!n)return;let r=_s(e);if(!r)return;let o=n[l]?.[r];return Array.isArray(o)?o[0]:typeof o=="string"?o:void 0}async function An(l,e){return Bo("createPoolProxyV3",l,e)}async function Pn(l,e){return Bo("nftPositionUniswap",l,e)}var O="0x0000000000000000000000000000000000000000",Cs={"0x1":{hexId:"0x1",name:"Ethereum",defaultRpc:se["0x1"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0xa":{hexId:"0xa",name:"Optimism",defaultRpc:se["0xa"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0x38":{hexId:"0x38",name:"BNB Smart Chain",defaultRpc:se["0x38"],native:{symbol:"BNB",name:"BNB",decimals:18,coingeckoId:"binancecoin"},defaultMintGasLimit:12e5},"0x89":{hexId:"0x89",name:"Polygon",defaultRpc:se["0x89"],native:{symbol:"MATIC",name:"Polygon",decimals:18,coingeckoId:"matic-network"},defaultMintGasLimit:12e5},"0x2105":{hexId:"0x2105",name:"Base",defaultRpc:se["0x2105"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5},"0xa4b1":{hexId:"0xa4b1",name:"Arbitrum One",defaultRpc:se["0xa4b1"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:22e5},"0xa86a":{hexId:"0xa86a",name:"Avalanche",defaultRpc:se["0xa86a"],native:{symbol:"AVAX",name:"Avalanche",decimals:18,coingeckoId:"avalanche-2"},defaultMintGasLimit:12e5},"0xe708":{hexId:"0xe708",name:"Linea",defaultRpc:se["0xe708"],native:{symbol:"ETH",name:"Ether",decimals:18,coingeckoId:"ethereum"},defaultMintGasLimit:12e5}};function ee(l){return Cs[l.toLowerCase()]}function St(l){return ee(l)?.native}var Oo="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";function Zn(l){let e=l.stableTicks??3,t=l.widePercent??{down:50,up:100},n=ie(l.currentTick,l.token0Decimals,l.token1Decimals),r=ke(we(l.currentTick-e*l.tickSpacing,l.tickSpacing,"down")),o=ke(we(l.currentTick+e*l.tickSpacing,l.tickSpacing,"up")),s=n*(1-t.down/100),a=n*(1+t.up/100),i=De(s,l.token0Decimals,l.token1Decimals),c=De(a,l.token0Decimals,l.token1Decimals),u=ke(we(i,l.tickSpacing,"down")),d=ke(we(c,l.tickSpacing,"up")),p=(m,g,f,h,y)=>{let k=ie(h,l.token0Decimals,l.token1Decimals),w=ie(y,l.token0Decimals,l.token1Decimals);return{id:m,label:g,description:f,tickLower:h,tickUpper:y,minPrice:k,maxPrice:w,minPercentChange:(k-n)/n*100,maxPercentChange:(w-n)/n*100}};return[p("stable","Stable","Good for stablecoins or low volatility pairs",r,o),p("wide","Wide","Good for volatile pairs",u,d)]}var Us=Te("slot0()"),Rs=Te("liquidity()"),Es=Te("tickSpacing()"),Ns=Te("fee()"),Is=Te("token0()"),Ds=Te("token1()"),Ls=Te("decimals()"),Me=class{swapFactory;isProduction;constructor(e){this.isProduction=e?.isProduction??!0,this.swapFactory=e?.swapFactory??(this.isProduction?ge:new Le(void 0,!1))}getRpcUrl(e){let t=kt(e);if(t)return t;let n=ee(e);if(!n)throw new Error(`Unsupported chain: ${e}`);return n.defaultRpc}async getGatewayAddress(e){return An(e,this.isProduction)}async getPositionManagerAddress(e){return Pn(e,this.isProduction)}async ethCall(e,t,n){return ve(this.getRpcUrl(e),"eth_call",[{to:t,data:n},"latest"])}async getNativeBalance(e,t){let n=await ve(this.getRpcUrl(e),"eth_getBalance",[t,"latest"]);return BigInt(n)}async getGasPrice(e){let t=await ve(this.getRpcUrl(e),"eth_gasPrice",[]);return BigInt(t)}async getTransactionReceipt(e,t){return await ve(this.getRpcUrl(e),"eth_getTransactionReceipt",[t])}async extractMintedNftId(e,t){let n=await this.getPositionManagerAddress(e);if(!n)return null;let r=n.toLowerCase();for(let o of t.logs??[]){if((o.address??"").toLowerCase()!==r)continue;let s=o.topics??[];if(!(s.length<4||s[0]?.toLowerCase()!==Oo||"0x"+s[1].slice(-40).toLowerCase()!==O))try{return BigInt(s[3]).toString()}catch{return null}}return null}async estimateGasReserveWei(e){let t=ee(e);if(!t)throw new Error(`Unsupported chain: ${e}`);let n=await this.getGasPrice(e);return BigInt(t.defaultMintGasLimit)*n*18n/10n}async readPoolOnchain(e,t){let[n,r,o,s,a,i]=await Promise.all([this.ethCall(e,t,Us),this.ethCall(e,t,Rs),this.ethCall(e,t,Es),this.ethCall(e,t,Ns),this.ethCall(e,t,Is),this.ethCall(e,t,Ds)]),c=re(n),u=Qt(c,0),d=zn(c,64),p=Qt(re(r),0),m=Number(zn(re(o),0)),g=Number(Qt(re(s),0)),f=Qn(re(a),0),h=Qn(re(i),0),[y,k]=await Promise.all([this.readErc20Decimals(e,f),this.readErc20Decimals(e,h)]),w=Ro(u,y,k);return{poolAddress:t,chainId:e,fee:g,tickSpacing:m,liquidity:p,sqrtPriceX96:u,currentTick:d,token0:f,token1:h,currentPrice:w}}async readErc20Decimals(e,t){if(t.toLowerCase()===O)return 18;let n=await this.ethCall(e,t,Ls);return Number(Qt(re(n),0))}async getNativePriceUsd(e){let t=ee(e);if(!t)return null;try{let n=`https://api.coingecko.com/api/v3/simple/price?ids=${t.native.coingeckoId}&vs_currencies=usd`,r=await _.get(n);if(!r.ok)return null;let s=r.data[t.native.coingeckoId]?.usd;return typeof s=="number"?s:null}catch{return null}}splitNativeAmountForRange(e){let{nativeAmountWei:t,pool:n,tickLower:r,tickUpper:o,token0Decimals:s,token1Decimals:a}=e,i=2**96,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,g=c*c,f=p*g,y=f+m;if(!Number.isFinite(y)||y<=0)throw new Error("splitNativeAmountForRange: invalid total value");let k=f/y,w=1-k,b=1000000000000000n,T=BigInt(Math.floor(k*Number(b))),A=t*T/b,x=t-A;return{ratio0:k,ratio1:w,amountInFor0Wei:A,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=Te("mint((address,uint256,address,uint256)[2],(address,bytes,uint256)[2],(uint24,uint256,uint256,int24,int24))"),n=e.paymentInfo.map(g=>kn(g.tokenIn)+le(g.tokenInAmount)+kn(g.tokenOut)+le(g.tokenOutAmount)).join(""),o=e.exchanges.map(g=>{let f=re(g.data||"0x"),h=f.length/2,y=le(h),k=(64-f.length%64)%64,w=f+"0".repeat(k);return{head:kn(g.to)+le(96)+le(g.value),body:y+w}}).map(g=>g.head+g.body),s=o.length*32,a=[];for(let g of o)a.push(le(s)),s+=g.length/2;let i=a.join("")+o.join(""),c=le(e.fee,24)+le(e.amount0Min)+le(e.amount1Min)+jn(e.tickLower)+jn(e.tickUpper),p=256+32+160,m=n+le(p)+c+i;return t+m}_keccak(e){return Eo(e)}};var Ms=/^0x[a-fA-F0-9]{40}$/,xt=class extends v{name="open-add-liquidity-form";kind="ui";category="pool-action";noSuggestions=!0;description='Open the Add-Liquidity form for a specific Uniswap V3 pool. Use this when the user wants to provide liquidity / add LP / farm / stake / deposit into a pool \u2014 natural-language verbs in any language all map here. Resolves the pool by token symbols (and optional fee tier), reads on-chain state, checks the connected wallet\'s NATIVE balance, then returns a UI payload that the FE renders as the form (with two preset ranges: Stable \xB13 ticks and Wide -50%/+100%). CONSTRAINT: the gateway only accepts the chain\'s NATIVE coin as input (ETH on Ethereum/Base/Arbitrum/Optimism/Linea, BNB on BSC, MATIC on Polygon, AVAX on Avalanche). The native amount is auto-split via deBridge into both pool tokens \u2014 the user never selects a different input. If the user asks to add liquidity using a NON-NATIVE token amount (e.g. "add pool with 5 USDT", "th\xEAm 100 USDC"), pass that symbol via `requestedNonNativeToken` so the tool can return error="unsupported_input_token" \u2014 never silently convert their non-native amount into native or USD. PRECONDITION: a connected wallet (userContext.walletAddress). If absent, the tool returns error="wallet_not_connected" and the agent must ask the user to connect.';parameters=[{name:"token0Symbol",type:"string",description:`Symbol of one of the pool's tokens (e.g. "USDC"). Pair order does not matter.`,required:!0},{name:"token1Symbol",type:"string",description:'Symbol of the other pool token (e.g. "WETH"). Required to uniquely identify the pool.',required:!0},{name:"feeTier",type:"number",description:"Fee tier in basis points: 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. Omit when unknown \u2014 the tool will resolve the best match automatically.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1},{name:"prefillNativeAmount",type:"number",description:"Optional NATIVE-COIN amount (human-readable) to pre-fill the form's input field. ONLY pass this when the user named an amount in the chain's native coin itself \u2014 ETH on Ethereum/Base/Arbitrum/Optimism/Linea, BNB on BSC, MATIC on Polygon, AVAX on Avalanche. Example: \"add 0.1 ETH to USDC/WETH on Base\". Pass the plain decimal number (e.g. 0.1 for 0.1 ETH). DO NOT convert a non-native amount (USDC, USDT, DAI, WBTC, \u2026) into native here \u2014 pass `requestedNonNativeToken` instead so the tool can reject. Mutually exclusive with prefillUsdAmount \u2014 pass one or neither.",required:!1},{name:"prefillUsdAmount",type:"number",description:'Optional USD amount to pre-fill the form\'s input field. Use this ONLY when the user explicitly speaks in USD/dollars \u2014 e.g. "add $10 to this pool", "10 dollars", "10 \u0111\xF4". The tool converts the USD value to native amount automatically using the current native-token price. DO NOT use this as a workaround for non-native token amounts (e.g. "5 USDT" is NOT $5 \u2014 USDT may de-peg, and even if 1:1 the user asked for a token, not USD). For non-native token amounts pass `requestedNonNativeToken` instead. Mutually exclusive with prefillNativeAmount \u2014 pass one or neither.',required:!1},{name:"requestedNonNativeToken",type:"string",description:`Set this ONLY when the user named an AMOUNT in a non-native token \u2014 e.g. "add pool with 5 USDT", "add 100 USDC to USDC/WETH", "th\xEAm 5 USDT v\xE0o pool". The trigger is "<number> <non-native-symbol>" in the user's message, NOT the mere appearance of the symbol. Pass the symbol the user attached the amount to (e.g. "USDT", "USDC", "DAI"). CRITICAL: If the user also names a NATIVE amount in the same message (e.g. "add USDC/WETH with 0.1 ETH", "th\xEAm pool USDC/WETH 0.3% v\u1EDBi 0.000005 ETH"), the native amount is the input \u2014 leave this EMPTY and use prefillNativeAmount. The pool pair symbols (the two tokens after "pool" / "to") are NEVER the input token by themselves \u2014 they only identify which pool. The tool will return error="unsupported_input_token" so the agent can tell the user that only the chain's native coin is accepted as input. DO NOT silently convert the user's non-native amount into a native amount or USD amount \u2014 the user explicitly named a different asset and must be informed, not auto-corrected. Leave empty when the user did name a native amount, a USD amount, or no amount at all.`,required:!1},{name:"rangeStrategy",type:"string",description:'Optional preset that pre-fills the min/max price inputs (FE keeps them editable). One of: "stable" \u2014 \xB13 tickSpacing around the current price (narrow, higher yield, higher out-of-range risk; pick for stablecoin / low-volatility pairs and when the user says "safe", "tight", "high yield", "t\u1EADp trung"); "wide" \u2014 currentPrice \xD7 [0.5, 2.0] (broad, lower yield, less rebalancing; pick when the user says "wide", "set and forget", "r\u1ED9ng"); "full" \u2014 full V3 tick range (V2-style, lowest yield, never goes out of range; pick when the user says "full range", "v2-style", "kh\xF4ng lo out of range"). Omit when the user did not signal a preference AND did not provide explicit minPrice/maxPrice. Mutually exclusive with minPrice/maxPrice \u2014 if both are passed, explicit prices win.',required:!1},{name:"minPrice",type:"number",description:'Optional explicit lower bound for the price range, in token1-per-token0 units (same unit as pool.currentPrice \u2014 decimal-adjusted). Pass when the user named a concrete number, e.g. "range 1800 to 2200" \u2192 minPrice=1800. Must be paired with maxPrice. When set, overrides rangeStrategy.',required:!1},{name:"maxPrice",type:"number",description:'Optional explicit upper bound for the price range, in token1-per-token0 units (same unit as pool.currentPrice \u2014 decimal-adjusted). Pass when the user named a concrete number, e.g. "range 1800 to 2200" \u2192 maxPrice=2200. Must be paired with minPrice. When set, overrides rangeStrategy.',required:!1}];uniswap;pool;minProvideUsd;constructor(e){super(),this.uniswap=new te(e),this.pool=new Me(e?.pool),this.minProvideUsd=e?.minProvideUsd??.01}async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){let n=E(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 g=ee(n);if(!g)return{error:"unsupported_chain",chain:n,_instructions:`Chain ${n} is not supported by the add-liquidity flow.`};let f=St(n);if(u&&f&&u.toLowerCase()!==f.symbol.toLowerCase())return{error:"unsupported_input_token",requestedToken:u,nativeSymbol:f.symbol,chainName:g.name,_instructions:`The user asked to add liquidity using ${u}, but this app only supports the chain's native coin (${f.symbol} on ${g.name}) as the input. Tell the user this in their language and ask them to either restate the amount in ${f.symbol} or in USD. DO NOT silently convert their ${u} amount, do NOT pre-fill the form, and do NOT call this tool again until the user provides a native or USD amount.`};let h=await this.pool.getGatewayAddress(n);if(!h)return{error:"no_gateway_configured",chain:n,_instructions:`No gateway contract is configured for chain ${n} (${g.name}). Tell the user this chain is not yet supported for add-liquidity in this app.`};let y=t?.walletAddress;if(!y)return{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before adding liquidity. Do NOT render the form."};let k=await this.uniswap.searchPools({query:`${r} ${o}`,chain:n,page:1,size:100,protocolVersion:"V3",feeTier:s});if(!k.success)return{error:k.error||"pool_search_failed"};let w=k.data??[],b=w.find(q=>this.matchesPairExact(q,r,o,s)),T=b?[]:w.filter(q=>this.matchesPair(q,r,o,s));if(!b&&T.length===0)return{error:"pool_not_found",_instructions:`No V3 pool found for ${r}/${o}${s!=null?` at ${s}bps`:""} on ${g.name}. Verify the inputs with the user.`};if(!b&&T.length>1)return{candidates:T.map(q=>({address:q.id,token0Symbol:q.token0?.symbol,token1Symbol:q.token1?.symbol,feeTier:q.feeTier,feeTierPercent:q.feeTier!=null?q.feeTier/1e4:void 0,volume24hUsd:q.volume1DayUsd??q.volumeUsd24hr,tvlUsd:q.tvlUsd,...q.apr?{apr:q.apr}:void 0})),_instructions:"Multiple pools matched. Present them as a numbered list (pair, fee %, TVL, 24h volume, address, apr) and ask the user to pick one. Then re-call this tool with the chosen pool's exact token0Symbol, token1Symbol, and feeTier."};let A=b??T[0];if(!A.id||!Ms.test(A.id))return{error:"invalid_pool_address"};let x=await this.pool.readPoolOnchain(n,A.id),[P,M,S]=await Promise.all([this.pool.getNativeBalance(n,y),this.pool.estimateGasReserveWei(n),this.pool.getNativePriceUsd(n)]),C=P>M?P-M:0n,U=St(n),G=Number(C)/10**U.decimals,X=Number(P)/10**U.decimals,j=S!=null?G*S:null,[ue,Q]=await Promise.all([this.pool.readErc20Decimals(n,x.token0),this.pool.readErc20Decimals(n,x.token1)]),xe={min:ie(yt,ue,Q),max:ie(bt,ue,Q)},Ae=null;P===0n?Ae="no_balance":j!=null&&j<this.minProvideUsd&&(Ae="insufficient_balance");let Pe=A.token0,Be=A.token1,On=[25,50,75,100].map(q=>({percent:q,amountWei:(C*BigInt(q)/100n).toString(),amount:G*(q/100)})),Oe=null,de=null;if(a!=null){de=a;let q=BigInt(Math.floor(de*10**U.decimals));q>0n&&(Oe=q.toString())}else if(i!=null&&S!=null&&S>0){de=i/S;let q=BigInt(Math.floor(de*10**U.decimals));q>0n&&(Oe=q.toString())}let qe={address:O,isNative:!0,symbol:U.symbol,name:U.name,decimals:U.decimals,priceUsd:S,balanceWei:P.toString(),balance:X,gasReserveWei:M.toString(),spendableWei:C.toString(),spendable:G,spendableUsd:j,quickRates:On,minProvideUsd:this.minProvideUsd,warning:Ae,warningMessage:Ae==="no_balance"?`You have no ${U.symbol} on ${g.name}. Top up to add liquidity.`:Ae==="insufficient_balance"?`Spendable ${U.symbol} is below the $${this.minProvideUsd} minimum.`:null,prefillAmount:de,prefillAmountWei:Oe},to=this.computePrefillRange({explicitMinPrice:p,explicitMaxPrice:m,rangeStrategy:d,currentTick:x.currentTick,tickSpacing:x.tickSpacing,token0Decimals:ue,token1Decimals:Q,priceBounds:xe});return{ui:{component:"AddLiquidityForm",props:{chain:{hexId:n,name:g.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:Pe?.symbol,name:Pe?.name,logo:Pe?.logo},token1:{address:x.token1,symbol:Be?.symbol,name:Be?.name,logo:Be?.logo}},inputToken:qe,priceBounds:xe,prefillRange:to,gatewayAddress:h}},summary:this.buildSummary({chainName:g.name,pair:`${Pe?.symbol??r}/${Be?.symbol??o}`,feePercent:x.fee/1e4,warning:Ae,minProvideUsd:this.minProvideUsd,nativeSymbol:U.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=Zn({currentTick:e.currentTick,tickSpacing:e.tickSpacing,token0Decimals:e.token0Decimals,token1Decimals:e.token1Decimals}).find(i=>i.id===r);if(a)return{strategy:r,minPrice:a.minPrice,maxPrice:a.maxPrice}}return null}buildSummary(e){let t=[`Add-liquidity form opened for ${e.pair} ${e.feePercent}% on ${e.chainName}.`];return e.warning==="no_balance"?t.push(`No ${e.nativeSymbol} balance \u2014 top up the wallet to add liquidity.`):e.warning==="insufficient_balance"&&t.push(`Balance is below the $${e.minProvideUsd} minimum \u2014 top up before submitting.`),t.join(" ")}};var qo=[{type:"constructor",inputs:[{name:"owner_",type:"address",internalType:"address"},{name:"supportedRouter_",type:"address",internalType:"address"},{name:"nonfungiblePositionManager_",type:"address",internalType:"address"}],stateMutability:"nonpayable"},{type:"function",name:"NATIVE_TOKEN",inputs:[],outputs:[{name:"",type:"address",internalType:"address"}],stateMutability:"view"},{type:"function",name:"getSupportedRouters",inputs:[],outputs:[{name:"",type:"address[]",internalType:"address[]"}],stateMutability:"view"},{type:"function",name:"increaseLiquidity",inputs:[{name:"tokenId_",type:"uint256",internalType:"uint256"},{name:"swapInfo_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapInfo[2]",components:[{name:"tokenIn",type:"address",internalType:"address"},{name:"tokenInAmount",type:"uint256",internalType:"uint256"},{name:"tokenOut",type:"address",internalType:"address"},{name:"tokenOutAmount",type:"uint256",internalType:"uint256"}]},{name:"swapOp_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapOp[2]",components:[{name:"to",type:"address",internalType:"address"},{name:"value",type:"uint256",internalType:"uint256"},{name:"data",type:"bytes",internalType:"bytes"}]},{name:"increaseLiquidityData_",type:"tuple",internalType:"struct CoinPoolGateway.IncreaseLiquidityData",components:[{name:"amount0Min",type:"uint256",internalType:"uint256"},{name:"amount1Min",type:"uint256",internalType:"uint256"}]}],outputs:[],stateMutability:"payable"},{type:"function",name:"mint",inputs:[{name:"swapInfo_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapInfo[2]",components:[{name:"tokenIn",type:"address",internalType:"address"},{name:"tokenInAmount",type:"uint256",internalType:"uint256"},{name:"tokenOut",type:"address",internalType:"address"},{name:"tokenOutAmount",type:"uint256",internalType:"uint256"}]},{name:"swapOp_",type:"tuple[2]",internalType:"struct CoinPoolGateway.SwapOp[2]",components:[{name:"to",type:"address",internalType:"address"},{name:"value",type:"uint256",internalType:"uint256"},{name:"data",type:"bytes",internalType:"bytes"}]},{name:"mintData_",type:"tuple",internalType:"struct CoinPoolGateway.MintData",components:[{name:"fee",type:"uint24",internalType:"uint24"},{name:"tickLower",type:"int24",internalType:"int24"},{name:"tickUpper",type:"int24",internalType:"int24"},{name:"amount0Min",type:"uint256",internalType:"uint256"},{name:"amount1Min",type:"uint256",internalType:"uint256"}]}],outputs:[],stateMutability:"payable"},{type:"function",name:"nonfungiblePositionManager",inputs:[],outputs:[{name:"",type:"address",internalType:"contract INonfungiblePositionManager"}],stateMutability:"view"},{type:"function",name:"owner",inputs:[],outputs:[{name:"",type:"address",internalType:"address"}],stateMutability:"view"},{type:"function",name:"pause",inputs:[],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"paused",inputs:[],outputs:[{name:"",type:"bool",internalType:"bool"}],stateMutability:"view"},{type:"function",name:"renounceOwnership",inputs:[],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"rescue",inputs:[{name:"token_",type:"address",internalType:"address"},{name:"amount_",type:"uint256",internalType:"uint256"},{name:"recipient_",type:"address",internalType:"address"}],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"transferOwnership",inputs:[{name:"newOwner",type:"address",internalType:"address"}],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"unpause",inputs:[],outputs:[],stateMutability:"nonpayable"},{type:"function",name:"updateSupportedRouters",inputs:[{name:"routers_",type:"address[]",internalType:"address[]"},{name:"isSupported_",type:"bool[]",internalType:"bool[]"}],outputs:[],stateMutability:"nonpayable"},{type:"event",name:"LiquidityIncreased",inputs:[{name:"tokenId",type:"uint256",indexed:!1,internalType:"uint256"},{name:"token0",type:"address",indexed:!1,internalType:"address"},{name:"token1",type:"address",indexed:!1,internalType:"address"},{name:"amount0LiquidityIncreased",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1LiquidityIncreased",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0IncreaseLiquidityRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1IncreaseLiquidityRefunded",type:"uint256",indexed:!1,internalType:"uint256"}],anonymous:!1},{type:"event",name:"Minted",inputs:[{name:"tokenId",type:"uint256",indexed:!1,internalType:"uint256"},{name:"token0",type:"address",indexed:!1,internalType:"address"},{name:"token1",type:"address",indexed:!1,internalType:"address"},{name:"amount0Minted",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1Minted",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1SwapRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount0MintRefunded",type:"uint256",indexed:!1,internalType:"uint256"},{name:"amount1MintRefunded",type:"uint256",indexed:!1,internalType:"uint256"}],anonymous:!1},{type:"event",name:"OwnershipTransferred",inputs:[{name:"previousOwner",type:"address",indexed:!0,internalType:"address"},{name:"newOwner",type:"address",indexed:!0,internalType:"address"}],anonymous:!1},{type:"event",name:"Paused",inputs:[{name:"account",type:"address",indexed:!1,internalType:"address"}],anonymous:!1},{type:"event",name:"SupportedRoutersUpdated",inputs:[{name:"routers",type:"address[]",indexed:!1,internalType:"address[]"},{name:"isSupported",type:"bool[]",indexed:!1,internalType:"bool[]"}],anonymous:!1},{type:"event",name:"Unpaused",inputs:[{name:"account",type:"address",indexed:!1,internalType:"address"}],anonymous:!1},{type:"error",name:"AddressEmptyCode",inputs:[{name:"target",type:"address",internalType:"address"}]},{type:"error",name:"EnforcedPause",inputs:[]},{type:"error",name:"ExpectedPause",inputs:[]},{type:"error",name:"FailedCall",inputs:[]},{type:"error",name:"InsufficientAmountOut",inputs:[]},{type:"error",name:"InsufficientBalance",inputs:[{name:"balance",type:"uint256",internalType:"uint256"},{name:"needed",type:"uint256",internalType:"uint256"}]},{type:"error",name:"InvalidAmountIn",inputs:[]},{type:"error",name:"InvalidLength",inputs:[]},{type:"error",name:"InvalidRefundUnusedToken",inputs:[]},{type:"error",name:"InvalidSwapAction",inputs:[]},{type:"error",name:"InvalidTokenInInfo",inputs:[]},{type:"error",name:"InvalidTokenOutInfo",inputs:[]},{type:"error",name:"InvalidTotalAmountIn",inputs:[]},{type:"error",name:"NotSupportedRouter",inputs:[]},{type:"error",name:"OwnableInvalidOwner",inputs:[{name:"owner",type:"address",internalType:"address"}]},{type:"error",name:"OwnableUnauthorizedAccount",inputs:[{name:"account",type:"address",internalType:"address"}]},{type:"error",name:"ReentrancyGuardReentrantCall",inputs:[]},{type:"error",name:"SafeERC20FailedDecreaseAllowance",inputs:[{name:"spender",type:"address",internalType:"address"},{name:"currentAllowance",type:"uint256",internalType:"uint256"},{name:"requestedDecrease",type:"uint256",internalType:"uint256"}]},{type:"error",name:"SafeERC20FailedOperation",inputs:[{name:"token",type:"address",internalType:"address"}]}],_n=[{type:"function",name:"approve",stateMutability:"nonpayable",inputs:[{name:"spender",type:"address"},{name:"amount",type:"uint256"}],outputs:[{name:"",type:"bool"}]},{type:"function",name:"allowance",stateMutability:"view",inputs:[{name:"owner",type:"address"},{name:"spender",type:"address"}],outputs:[{name:"",type:"uint256"}]}];var At=class extends v{name="preview-add-liquidity";kind="action";category="pool-action";noSuggestions=!0;description="Preview the add-liquidity transaction once the user has filled the AddLiquidityForm with a native amount and a price range (min/max price in token1 per token0). The tool converts prices \u2192 ticks (rounded to tickSpacing), fetches deBridge swap quotes for both legs, encodes the gateway.mint call, and returns an unsigned tx the FE will sign. Do NOT call this until the user has filled both prices and an amount.";parameters=[{name:"poolAddress",type:"string",description:"V3 pool contract address (returned by open-add-liquidity-form in props.pool.address).",required:!0},{name:"chain",type:"string",description:`Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea .Must match the pool's chain. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.`,required:!1},{name:"minPrice",type:"number",description:'Lower bound of the position price range, in token1-per-token0 units (decimal-adjusted \u2014 the SAME unit as pool.currentPrice from open-add-liquidity-form). Example: if currentPrice is 1800 (USDC per WETH), minPrice 900 means "the position is in-range while WETH \u2265 900 USDC". Must be > 0 and < maxPrice.',required:!0},{name:"maxPrice",type:"number",description:'Upper bound of the position price range, in the same units as minPrice. Must be > minPrice. Pass a very large number (or props.priceBounds.max) for a "full range upper" position.',required:!0},{name:"nativeAmount",type:"number",description:"Native amount the user wants to spend, as a human-readable decimal number (e.g. 0.1 for 0.1 ETH). The tool converts to wei internally.",required:!0},{name:"slippageBps",type:"number",description:"Slippage tolerance in basis points (100 = 1%). Optional \u2014 defaults to 100.",required:!1}];pool;defaultSlippageBps;mintMinPercent;pantograph;constructor(e){super(),this.pool=new Me(e?.pool),this.defaultSlippageBps=e?.defaultSlippageBps??"auto",this.mintMinPercent=e?.mintMinPercent??80,this.pantograph=new Z}async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){let n=typeof e.poolAddress=="string"?e.poolAddress.trim():"",r=E(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=ee(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=St(r),m=BigInt(Math.floor(a*10**p.decimals));if(m<=0n)return{error:"invalid_native_amount",_instructions:"nativeAmount must be > 0."};let g=await this.pool.readPoolOnchain(r,n),[f,h]=await Promise.all([this.pool.readErc20Decimals(r,g.token0),this.pool.readErc20Decimals(r,g.token1)]),y=De(o,f,h),k=De(s,f,h),w=ke(we(y,g.tickSpacing,"down")),b=ke(we(k,g.tickSpacing,"up"));if(w>=b||w<yt||b>bt)return{error:"invalid_range_after_alignment",_instructions:"After aligning to tickSpacing the range collapsed. Ask the user for a wider price range.",rawTickLower:y,rawTickUpper:k,tickLower:w,tickUpper:b};let T={tickLower:w,tickUpper:b,minPrice:ie(w,f,h),maxPrice:ie(b,f,h)},[A,x,P]=await Promise.all([this.pool.getNativeBalance(r,d),this.pool.estimateGasReserveWei(r),this.pool.getNativePriceUsd(r)]),M=m+x;if(A<M)return{error:"insufficient_balance",_instructions:"User does not have enough native coin (amount + gas reserve). Tell them the shortfall in their language.",balanceWei:A.toString(),requiredWei:M.toString(),gasReserveWei:x.toString()};let S=this.pool.splitNativeAmountForRange({nativeAmountWei:m,pool:g,tickLower:w,tickUpper:b,token0Decimals:f,token1Decimals:h}),C=g.token0.toLowerCase()===O,U=g.token1.toLowerCase()===O,G=[];if(C||S.amountInFor0Wei===0n)G.push({tokenIn:O,tokenOut:g.token0,amountIn:S.amountInFor0Wei,tx:{to:O,data:"0x",value:"0"},minAmountOut:C?S.amountInFor0Wei:0n});else{let H=await this.pool.fetchSwapLeg({senderAddress:d,chainId:r,tokenIn:O,tokenInAmount:S.amountInFor0Wei.toString(),tokenOut:g.token0,tokenOutRecipient:u,slippage:typeof i=="number"?i:"auto"});G.push({tokenIn:O,tokenOut:g.token0,amountIn:S.amountInFor0Wei,tx:{to:H.to,data:H.data,value:H.value},minAmountOut:BigInt(H.minAmountOut||"0")})}if(U||S.amountInFor1Wei===0n)G.push({tokenIn:O,tokenOut:g.token1,amountIn:S.amountInFor1Wei,tx:{to:O,data:"0x",value:"0"},minAmountOut:U?S.amountInFor1Wei:0n});else{let H=await this.pool.fetchSwapLeg({senderAddress:d,chainId:r,tokenIn:O,tokenInAmount:S.amountInFor1Wei.toString(),tokenOut:g.token1,tokenOutRecipient:u,slippage:typeof i=="number"?i:"auto"});G.push({tokenIn:O,tokenOut:g.token1,amountIn:S.amountInFor1Wei,tx:{to:H.to,data:H.data,value:H.value},minAmountOut:BigInt(H.minAmountOut||"0")})}let X=H=>H*BigInt(this.mintMinPercent)/100n,j=X(G[0].minAmountOut),ue=X(G[1].minAmountOut),Q=G.map(H=>({tokenIn:H.tokenIn,tokenInAmount:BigInt(H.amountIn),tokenOut:H.tokenOut,tokenOutAmount:BigInt(H.minAmountOut)})),xe=G.map(H=>({to:H.tx.to,data:H.tx.data,value:BigInt(H.tx.value||"0")}));console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ paymentInfo:",Q),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ exchangesInfo:",xe),console.log("\u{1F680} ~ PreviewAddLiquidityTool ~ run ~ args:",{args:[Q,xe,{fee:Number(g.fee),amount0Min:BigInt(j),amount1Min:BigInt(ue),tickLower:w,tickUpper:b}]});let Ae=Ge({abi:qo,functionName:"mint",args:[Q,xe,{fee:Number(g.fee),amount0Min:BigInt(j),amount1Min:BigInt(ue),tickLower:w,tickUpper:b}]}),Pe=a,Be={chainId:r,to:u,data:Ae,value:m.toString(),from:d},On={nativeIn:Pe,nativeInUsd:P!=null?Pe*P:null,ratio:{token0Percent:S.ratio0,token1Percent:S.ratio1},expectedToken0:Number(G[0].minAmountOut)/10**f,expectedToken1:Number(G[1].minAmountOut)/10**h,amount0Min:j.toString(),amount1Min:ue.toString(),slippageBps:i},Oe=await this.pantograph.getTokensMetadata([g.token0,g.token1],r),de=Oe[g.token0],qe=Oe[g.token1];return{ui:{component:"ConfirmAddLiquidityTx",props:{chain:{hexId:r,name:c.name},unsignedTx:Be,summary:On,pool:{...g,feePercent:g.fee/1e4,token0:{address:g.token0,symbol:de.symbol,name:de.name,decimals:de.decimals,logo:de.icon_image},token1:{address:g.token1,symbol:qe.symbol,name:qe.name,decimals:qe.decimals,logo:qe.icon_image}},range:T,gatewayAddress:u}},summary:`Ready to add ${Pe} ${p.symbol} to pool ${g.token0}/${g.token1} ${g.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 Fo=365,Bs=1e3,Os=/^0x[a-fA-F0-9]{40}$/;function qs(l,e){return{depositUsd:l,aprPercent:e*100,dailyUsd:l*e/Fo,weeklyUsd:l*e/52,monthlyUsd:l*e/12,yearlyUsd:l*e}}var Pt=class extends v{name="estimate_pool_yield";description=`Estimate daily / weekly / monthly / yearly fee yield for a USD deposit into a Uniswap V3 pool. Answers questions like: "If I put $1000 into the USDC/WETH 0.05% pool, how much per day?", "b\u1ECF 500$ v\xE0o pool ETH/USDC 0.3% m\u1ED7i ng\xE0y l\u1EDDi bao nhi\xEAu?", "APR of WBTC/ETH 0.3% on Arbitrum". BEHAVIOR (designed to minimize back-and-forth with the user):
|
|
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
|
|
110
|
+
Source: Uniswap ExploreStats (live 24h data). Supports: Ethereum, Optimism, BSC, Polygon, Base, Arbitrum, Avalanche, Linea.`;category="blockchain-data";parameters=[{name:"token0Symbol",type:"string",description:'Symbol of one of the pool tokens (e.g. "USDC"). Pair order does not matter.',required:!0},{name:"token1Symbol",type:"string",description:'Symbol of the other pool token (e.g. "WETH"). Optional \u2014 if omitted, the tool auto-picks the highest-volume pool containing token0Symbol.',required:!1},{name:"feeTier",type:"number",description:"Fee tier in basis points: 100 = 0.01%, 500 = 0.05%, 3000 = 0.30%, 10000 = 1.00%. Optional \u2014 if omitted and the pair has multiple fee tiers, the tool returns candidates.",required:!1},{name:"depositUsd",type:"number",description:"USD amount to deposit. Optional \u2014 defaults to 1000 and the response is marked as a per-$1000 example.",required:!1},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. ONLY set this when the user EXPLICITLY names a chain; otherwise OMIT it so the connected wallet chain is used.',required:!1}];service;constructor(e){super(),this.service=new te(e)}async run(e,t){let n=E(e.chain,t),r=typeof e.token0Symbol=="string"?e.token0Symbol.trim():"",o=typeof e.token1Symbol=="string"?e.token1Symbol.trim():"",s=typeof e.feeTier=="number"&&Number.isFinite(e.feeTier)?Math.floor(e.feeTier):void 0,a=typeof e.depositUsd=="number"&&Number.isFinite(e.depositUsd)&&e.depositUsd>0,i=a?e.depositUsd:Bs;if(!r)return{error:"token0Symbol is required. Ask the user which pool / token they want to estimate yield for."};let c=o?`${r} ${o}`:r,u=await this.service.searchPools({query:c,chain:n,page:1,size:50,protocolVersion:"V3"});if(!u.success)return{error:u.error||"Pool search failed"};let d=u.data??[],p=this.filterPools(d,r,o,s);if(p.length===0)return{error:`No V3 pool found for ${this.describeQuery(r,o,s)} on ${D(n)}. Verify the token symbols with the user.`};let m=this.rankByVolume(p);if(o&&s==null&&new Set(m.map(C=>C.feeTier).filter(C=>typeof C=="number")).size>1){let C=this.dedupeByFeeTier(m).slice(0,5);return{_instructions:"The pair has multiple fee tiers. Present them as a short numbered list (fee tier %, 24h volume). Ask which fee tier the user wants, then re-call with the same token symbols + chosen feeTier. If the user already mentioned a deposit amount, pass it along as depositUsd. Do NOT mention tool names. Format numbers human-readably (e.g. $12.5M, 0.05%).",reason:`${r}/${o} exists across multiple fee tiers \u2014 user choice required.`,missing:["feeTier"],candidates:C.map(U=>({pair:U.token0?.symbol&&U.token1?.symbol?`${U.token0.symbol}/${U.token1.symbol}`:void 0,feeTierBps:U.feeTier,feeTierPercent:typeof U.feeTier=="number"?U.feeTier/1e4:void 0,volume24hUsd:U.volumeUsd24hr,address:U.id}))}}let g=m[0];if(!g.id||!Os.test(g.id))return{error:"Resolved pool has no valid address."};let f=await this.service.getV3PoolDetail({address:g.id,chain:n});if(!f.success||!f.data)return{error:f.error||"Failed to fetch pool detail."};let h=f.data,y=h.totalLiquidity?.value,k=h.volume24h?.value,w=h.feeTier,b=typeof w=="number"?w/1e4:void 0,T=typeof k=="number"&&typeof b=="number"?k*b/100:void 0,A=typeof T=="number"&&typeof y=="number"&&y>0?T/y*Fo:void 0,x={address:h.address,pair:h.token0?.symbol&&h.token1?.symbol?`${h.token0.symbol}/${h.token1.symbol}`:void 0,feeTierBps:w,feeTierPercent:b,tvlUsd:y,volume24hUsd:k,fees24hUsd:T,aprPercent:typeof A=="number"?A*100:void 0,chain:n,token0:h.token0?{symbol:h.token0.symbol}:void 0,token1:h.token1?{symbol:h.token1.symbol}:void 0},P=o?void 0:`User only provided ${r}; auto-picked the highest-volume V3 pool containing it.`;if(A==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:P,aprUnavailableReason:y?"Volume data unavailable":"TVL is zero or unavailable"};let M=qs(i,A);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. ')+(P?'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:M,depositMode:a?"user-specified":"default-1000-example",autoPickedReason:P,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(),c=(a.token1?.symbol??"").toLowerCase();return s?this.sym(i,o)&&this.sym(c,s)||this.sym(i,s)&&this.sym(c,o):this.sym(i,o)||this.sym(c,o)})}sym(e,t){return!e||!t?!1:e.includes(t)||t.includes(e)}rankByVolume(e){return[...e].sort((t,n)=>(n.volumeUsd24hr??0)-(t.volumeUsd24hr??0))}dedupeByFeeTier(e){let t=new Set,n=[];for(let r of e)typeof r.feeTier=="number"&&(t.has(r.feeTier)||(t.add(r.feeTier),n.push(r)));return n}describeQuery(e,t,n){let r=t?`${e}/${t}`:e;return n!=null?`${r} at ${n} bps`:r}};var Fs={"0x1":"https://gateway.thegraph.com/api/subgraphs/id/5zvR82QoaXYFyDEKLZ9t6v9adgnptxYpKpSbxtgVENFV","0xa4b1":"https://gateway.thegraph.com/api/subgraphs/id/FbCGRftH4a3yZugY7TnbYgPJVEv2LvMT6oF1fxPe9aJM","0x89":"https://gateway.thegraph.com/api/subgraphs/id/3hCPRGf4z88VC5rsBKU5AA9FBBq5nF3jbKJG7VZCbhjm","0x38":"https://gateway.thegraph.com/api/subgraphs/id/GcKPSgHoY42xNYVAkSPDhXSzi6aJDRQSKqBSXezL47gV","0x2105":"https://gateway.thegraph.com/api/subgraphs/id/HMuAwufqZ1YCRmzL2SfHTVkzZovC9VL2UAKhjvRqKiR1","0xa":"https://gateway.thegraph.com/api/subgraphs/id/Cghf4LfVqPiFw6fp6Y5X5Ubc8UpmUhSfJL82zwiBFLaj","0xa86a":"https://gateway.thegraph.com/api/subgraphs/id/GVH9h9KZ9CqheUEL93qMbq7QwgoBu32QXQDPR6bev4Eo"},$s={"0x1":"ethereum","0xa4b1":"arbitrum","0x89":"polygon","0x38":"bsc","0x2105":"base","0xa":"optimism","0xa86a":"avalanche"},Ws={"0x1":1,"0xa4b1":42161,"0x89":137,"0x38":56,"0x2105":8453,"0xa":10,"0xa86a":43114},$o={"0x1":"0x1",1:"0x1",eth:"0x1",ethereum:"0x1",mainnet:"0x1","0xa4b1":"0xa4b1",42161:"0xa4b1",arb:"0xa4b1",arbitrum:"0xa4b1","0x89":"0x89",137:"0x89",matic:"0x89",polygon:"0x89","0x38":"0x38",56:"0x38",bnb:"0x38",bsc:"0x38","0x2105":"0x2105",8453:"0x2105",base:"0x2105","0xa":"0xa",10:"0xa",op:"0xa",optimism:"0xa","0xa86a":"0xa86a",43114:"0xa86a",avax:"0xa86a",avalanche:"0xa86a"},Wo={100:.01,500:.05,3e3:.3,1e4:1},Vs={ethereum:"0x1","arbitrum one":"0xa4b1",arbitrum:"0xa4b1",polygon:"0x89",bsc:"0x38",base:"0x2105",optimism:"0xa","avalanche c-chain":"0xa86a",avalanche:"0xa86a"},Hs=300*1e3,ne=class{apiKey;subgraphUrls;coinPoolBaseUrl;keyringPoolBaseUrl;llamaCache=null;llamaCacheAt=0;constructor(e={}){this.apiKey=e.theGraphApiKey||"4c67ac7a75b21befbd28dc9120c709f1";let t={...Fs,...e.subgraphUrls||{}};this.subgraphUrls=Object.fromEntries(Object.entries(t).filter(([n])=>pn(n))),this.coinPoolBaseUrl=e.coinPoolBaseUrl||"https://api.coinpool.app",this.keyringPoolBaseUrl=e.keyringPoolBaseUrl||"https://pool-data.keyring.app"}resolveChain(e){if(!e)return"0x1";let t=e.toLowerCase();return $o[t]||(this.subgraphUrls[t]?t:"0x1")}isSupported(e){if(!e)return!1;let t=e.toLowerCase(),n=$o[t];return!!(n&&this.subgraphUrls[n])}chainName(e){let t=this.resolveChain(e);return $s[t]||t}numericChainId(e){return Ws[this.resolveChain(e)]}listSupportedChains(){return Object.keys(this.subgraphUrls)}async querySubgraph(e,t){let n=this.resolveChain(e),r=this.subgraphUrls[n];if(!r)throw new Error(`No subgraph available for chain: ${e}`);let o=await _.post(r,{query:t},{headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.apiKey}`}});if(!o.ok)throw new Error(`Subgraph query failed: ${I(o)}`);let s=o.data;if(s.errors&&s.errors.length>0)throw new Error(`Subgraph error: ${s.errors[0].message||"Unknown"}`);return s.data}poolFragment(){return`
|
|
111
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=
|
|
127
|
+
`}hasValidVolume(e){let t=e.poolDayData?.[0];return t?parseFloat(t.volumeToken0||"0")!==0&&parseFloat(t.volumeToken1||"0")!==0&&parseFloat(t.volumeUSD||"0")!==0:!1}sanitize(e){return!isFinite(e)||isNaN(e)||e>1e11?0:e}mapPool(e,t){let n=e,r=Number(n.feeTier||0),o=Wo[r]??r/1e4,s=this.sanitize(parseFloat(n.totalValueLockedUSD||"0")),a=n.poolDayData?.[0],i=parseFloat(a?.volumeUSD||"0"),c=parseFloat(a?.feesUSD||"0"),u=null;return s>0&&c>0&&(u=parseFloat((c*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:c,apr:u,feeTierBps:r,feeTierPercent:o,liquidity:n.liquidity||"0",chain:this.chainName(t)}}async fetchDefiLlamaUniswap(){let e=Date.now();if(this.llamaCache&&e-this.llamaCacheAt<Hs)return this.llamaCache;try{let t=await _.get("https://yields.llama.fi/pools");if(!t.ok)throw new Error(`DefiLlama: ${I(t)}`);let r=(t.data.data||[]).filter(o=>o.project==="uniswap-v3");return this.llamaCache=r,this.llamaCacheAt=e,r}catch{return this.llamaCache??[]}}parseLlamaFeeTier(e){if(!e)return null;let t=e.match(/^([\d.]+)%/);return t?Math.round(parseFloat(t[1])*1e4):null}enrichWithLlama(e,t){return t.length?e.map(n=>{let r=this.resolveChain(n.chain),o=n.feeTierBps,s=n.token0.address.toLowerCase(),a=n.token1.address.toLowerCase(),i=t.filter(p=>Vs[p.chain.toLowerCase()]===r),c=i.find(p=>{if(!p.underlyingTokens||p.underlyingTokens.length<2)return!1;let m=p.underlyingTokens.map(f=>f.toLowerCase());if(!(m.includes(s)&&m.includes(a)))return!1;let g=this.parseLlamaFeeTier(p.poolMeta);return g===null||g===o});if(!c){let p=n.token0.symbol.toUpperCase(),m=n.token1.symbol.toUpperCase(),g=[p,m].sort().join("-");c=i.find(f=>{if([...f.symbol.split("-").map(k=>k.toUpperCase())].sort().join("-")!==g)return!1;let y=this.parseLlamaFeeTier(f.poolMeta);return y===null||y===o})}if(!c)return n;let u=c.apyBase??c.apy??null,d={...n};return u!==null&&(d.apr=parseFloat(u.toFixed(2))),c.tvlUsd!=null&&(d.tvl=this.sanitize(c.tvlUsd)),c.volumeUsd1d!=null&&(d.volume24hUsd=this.sanitize(c.volumeUsd1d)),d}):e}async searchPools(e){let{tokens:t,chain:n,sortBy:r,filters:o}=e,s=this.resolveChain(n),a=t.map(f=>{let h=f.toUpperCase(),y=new Set([h]);return h.startsWith("W")||y.add("W"+h),h.startsWith("W")&&h.length>1&&y.add(h.slice(1)),[...y]}),i=['totalValueLockedUSD_gt: "10000"','liquidity_gt: "0"','volumeUSD_gt: "0"'];o?.feeTier&&i.push(`feeTier: "${o.feeTier}"`);let c="totalValueLockedUSD";r==="volume"?c="volumeUSD":r==="fee"?c="feeTier":r==="liquidity"&&(c="liquidity");let u=r==="fee"?"asc":"desc",d=f=>f.length===1?`symbol: "${f[0]}"`:`symbol_in: [${f.map(h=>`"${h}"`).join(", ")}]`,p=(f,h)=>`{
|
|
128
128
|
pools(
|
|
129
|
-
first: ${
|
|
130
|
-
orderBy: ${
|
|
129
|
+
first: ${h}
|
|
130
|
+
orderBy: ${c}
|
|
131
131
|
orderDirection: ${u}
|
|
132
132
|
where: { ${[...i,...f].join(", ")} }
|
|
133
133
|
) {
|
|
134
134
|
${this.poolFragment()}
|
|
135
135
|
}
|
|
136
|
-
}`,
|
|
136
|
+
}`,m;if(a.length>0){let h,y;a.length>=2?(h=p([`token0_: { ${d(a[0])} }`,`token1_: { ${d(a[1])} }`],100),y=p([`token0_: { ${d(a[1])} }`,`token1_: { ${d(a[0])} }`],100)):(h=p([`token0_: { ${d(a[0])} }`],100),y=p([`token1_: { ${d(a[0])} }`],100));let[k,w]=await Promise.allSettled([this.querySubgraph(s,h),this.querySubgraph(s,y)]),b=k.status==="fulfilled"?(k.value?.pools||[]).filter(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)):[],A=new Set(b.map(x=>x.poolAddress));m=[...b,...T.filter(x=>!A.has(x.poolAddress))]}else m=((await this.querySubgraph(s,p([],100)))?.pools||[]).filter(h=>this.hasValidVolume(h)).map(h=>this.mapPool(h,s));let g=await this.fetchDefiLlamaUniswap();return g.length>0&&(m=this.enrichWithLlama(m,g)),o?.minTVL!=null&&(m=m.filter(f=>f.tvl>=o.minTVL)),o?.maxTVL!=null&&(m=m.filter(f=>f.tvl<=o.maxTVL)),o?.minAPR!=null&&(m=m.filter(f=>f.apr!=null&&f.apr>=o.minAPR)),o?.maxAPR!=null&&(m=m.filter(f=>f.apr!=null&&f.apr<=o.maxAPR)),r==="apr"?m.sort((f,h)=>(h.apr??0)-(f.apr??0)):r==="liquidity"&&m.sort((f,h)=>{let y=BigInt(h.liquidity||"0")-BigInt(f.liquidity||"0");return y>0n?1:y<0n?-1:0}),m}async getTrendingPools(e){let t=this.resolveChain(e.chain),n=`{
|
|
137
137
|
pools(
|
|
138
138
|
first: 100
|
|
139
139
|
orderBy: volumeUSD
|
|
@@ -166,26 +166,22 @@ 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,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=Ro[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=_s(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 _s(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 Cs=["tvl","volume","apr","fee","liquidity"],wt=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 Z(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));if(s.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new K;let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],i=typeof e.sortBy=="string"&&Cs.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:ae(e)}}};function ae(c){return`https://app.uniswap.org/explore/pools/${Kt[c.chain]||c.chain}/${c.poolAddress}`}var Kt={ethereum:"ethereum",arbitrum:"arbitrum",polygon:"polygon",bsc:"bnb",base:"base",optimism:"optimism",avalanche:"avalanche"};var kt=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 Z(e)}async run(e,t){let n=(Array.isArray(e.chains)?e.chains:[]).filter(u=>typeof u=="string"&&u.trim().length>0),r=n.filter(u=>!this.service.isSupported(u)),o=n.filter(u=>this.service.isSupported(u)).map(u=>this.service.resolveChain(u));if(n.length>0&&o.length===0)return{_instructions:"User requested unsupported chains. Politely list them and the supported chains; do not show pool data.",unsupportedChains:r,supportedChains:this.service.listSupportedChains(),pools:[],count:0};if(o.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new K;let s=o.length>0?o:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),20):5,l=(await Promise.allSettled(s.map(u=>this.service.getTrendingPools({chain:u})))).flatMap(u=>u.status==="fulfilled"?u.value:[]);return l.sort((u,d)=>d.volume24hUsd-u.volume24hUsd),l=l.slice(0,a),{_instructions:'Prefix the answer with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). List pools ranked by 24h volume. For each: pair, fee %, 24h volume, TVL, APR (skip if null). For EACH pool render the provided uniswapUrl as a clickable markdown link (e.g. wrap the pair as "[USDC/WETH](uniswapUrl)" or append "[View on Uniswap](uniswapUrl)"). Never omit it. Do not invent URLs.',chains:s,unsupportedChains:r,count:l.length,pools:l.map(u=>({pair:u.pair,poolAddress:u.poolAddress,chain:u.chain,feeTierBps:u.feeTierBps,feeTierPercent:u.feeTierPercent,tvlUsd:u.tvl,volume24hUsd:u.volume24hUsd,fees24hUsd:u.fees24hUsd,apr:u.apr,token0:u.token0,token1:u.token1,uniswapUrl:ae(u)}))}}};var Tt=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 Z(e)}async run(e,t){let n=typeof e.poolAddress=="string"?e.poolAddress.trim():"";if(!/^0x[a-fA-F0-9]{40}$/.test(n))return{error:"This only supports Uniswap V3 pools. Provide a valid Uniswap V3 pool address (a 0x-prefixed 40-character hex address)."};let r=typeof e.chain=="string"&&e.chain.trim();if(!r&&!t?.chain&&t?.walletAddress)throw new K;let o=r?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(o))return{_instructions:"The requested chain is not supported by the Subgraph pool service. List the supported chains briefly.",unsupportedChains:[o],supportedChains:this.service.listSupportedChains()};let s=this.service.resolveChain(o),a=await this.service.getPoolByAddress({poolAddress:n,chain:s});return a?{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). Show pair, fee tier (%), TVL, 24h volume, 24h fees, APR (skip if null), and the pool address. End the response with the provided uniswapUrl as a clickable markdown link, e.g. "[View on Uniswap](uniswapUrl)". Never omit it. Do not invent URLs.',chain:s,pool:{pair:a.pair,poolAddress:a.poolAddress,chain:a.chain,feeTierBps:a.feeTierBps,feeTierPercent:a.feeTierPercent,tvlUsd:a.tvl,volume24hUsd:a.volume24hUsd,fees24hUsd:a.fees24hUsd,apr:a.apr,token0:a.token0,token1:a.token1,uniswapUrl:ae(a)}}:{error:`No pool found at ${n} on chain ${s}.`}}};var vt=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 Z(e)}async run(e,t){let n=typeof e.positionId=="string"?e.positionId.trim():"";if(!/^\d+$/.test(n))return{error:"positionId must be a numeric string."};let r=(Array.isArray(e.chains)?e.chains:[]).filter(u=>typeof u=="string"&&u.trim().length>0),o=r.filter(u=>!this.service.isSupported(u)),s=r.filter(u=>this.service.isSupported(u)).map(u=>this.service.resolveChain(u));if(r.length>0&&s.length===0)return{_instructions:"Requested chains are not supported. List the supported chains.",unsupportedChains:o,supportedChains:this.service.listSupportedChains()};if(s.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new K;let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],l=(await Promise.allSettled(a.map(u=>this.service.getPoolByPositionId({positionId:n,chain:u})))).map(u=>u.status==="fulfilled"?u.value:null).find(u=>u!=null);return l?{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). Show the pool of that position: pair, fee %, TVL, 24h volume, APR. Mention the position id. End the response with the provided uniswapUrl as a clickable markdown link, e.g. "[View on Uniswap](uniswapUrl)". Never omit it. Do not invent URLs.',positionId:n,pool:{pair:l.pair,poolAddress:l.poolAddress,chain:l.chain,feeTierBps:l.feeTierBps,feeTierPercent:l.feeTierPercent,tvlUsd:l.tvl,volume24hUsd:l.volume24hUsd,fees24hUsd:l.fees24hUsd,apr:l.apr,token0:l.token0,token1:l.token1,uniswapUrl:ae(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 St=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 Z(e)}async run(e,t){let n=typeof e.positionId=="string"?e.positionId.trim():"";if(!/^\d+$/.test(n))return{error:"positionId must be a numeric string (Uniswap V3 NFT id)."};let r=(Array.isArray(e.chains)?e.chains:[]).filter(d=>typeof d=="string"&&d.trim().length>0),o=r.filter(d=>!this.service.isSupported(d)),s=r.filter(d=>this.service.isSupported(d)).map(d=>this.service.resolveChain(d));if(r.length>0&&s.length===0)return{_instructions:"User asked about a position on chains we do not support. List unsupported + supported chains.",unsupportedChains:o,supportedChains:this.service.listSupportedChains()};if(s.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new K;let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],l=(await Promise.allSettled(a.map(d=>this.service.getPositionDetail({positionId:n,chain:d})))).map(d=>d.status==="fulfilled"?d.value:null).find(d=>d!=null);if(!l)return{_instructions:"No position with this id was found on the chosen chains. Suggest the user double-check the id or specify another chain.",positionId:n,chains:a,found:!1};let u=Kt[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:ae(l.pool)}}}};var xt=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 Z(e)}async run(e,t){let n=(Array.isArray(e.tokens)?e.tokens:[]).filter(u=>typeof u=="string"&&u.trim().length>0).map(u=>u.trim().toUpperCase());if(n.length<2)return{error:'tokens must contain two token symbols (e.g. ["ETH","USDC"]).'};let r=typeof e.chain=="string"&&e.chain.trim();if(!r&&!t?.chain&&t?.walletAddress)throw new K;let o=r?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(o))return{_instructions:"Requested chain not supported. Mention supported chains.",unsupportedChains:[o],supportedChains:this.service.listSupportedChains()};let s=this.service.resolveChain(o),a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),30):10,i=await this.service.getCoinPoolPairs({chain:s,tokens:n});if(i.length===0)return{_instructions:"No CoinPool range data is available for this pair on this chain. Tell the user this nicely and suggest using general pool data instead (via subgraph-search-pools).",tokens:n,chain:s,pairs:[],count:0};let l=[...i].sort((u,d)=>(d.apr??-1/0)-(u.apr??-1/0)).slice(0,a);return{_instructions:'CRITICAL: APR in each entry is the APR for that SPECIFIC price range only \u2014 NEVER present it as the overall pool APR. Always show the min\u2013max price together with the APR, e.g. "APR X% (range: 1800 \u2013 2200 ETH/USDC)". Sort by APR descending; explain that the top entry is the best concentrated range but narrower = higher out-of-range risk.',tokens:n,chain:s,count:l.length,pairs:l}}};function me(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 Eo(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 Us={send:30000n,swap:1000000n},Rs={"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 Es(c){let e=c.toLowerCase(),n=[mt(c),...Rs[e]??[]].filter(r=>!!r);for(let r of n)try{let o=await be(r,"eth_gasPrice",[]),s=BigInt(o);if(s>0n)return s}catch{}return 0n}async function Pt(c,e){let t=await Es(c);return t<=0n?0n:Us[e]*t}function At(c,e,t){if(e<=0n)return c;let n=X(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 oe=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 Qn(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=me(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=Eo(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 Pt(e,t);return At(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=Me(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 _t=class extends oe{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=Xn(e.native_symbol);if(n)return super.run({...e,chain:n},t)}return super.run(e,t)}description=`Open the SEND NATIVE form for the chain's native coin (ETH/BNB/MATIC/AVAX/\u2026). Use for ANY native-send intent: "send eth", "send 0.1 ETH", "transfer 1 BNB to vitalik.eth", "g\u1EEDi ETH". Call EVEN IF the user did not name an amount or recipient \u2014 the form prompts for them. Do NOT use for ERC-20 tokens \u2014 use open-send-token-form instead.`;parameters=[{name:"chain",type:"string",description:'Hex chain id: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. Defaults to connected chain.',required:!1},{name:"native_symbol",type:"string",description:'The native coin the user named to send, VERBATIM (e.g. "ETH", "BNB", "MATIC", "AVAX"). ALWAYS set this when the user names the coin ("send bnb" \u2192 native_symbol="BNB"); leave blank only when they say just "send" with no coin. A coin that belongs to one specific chain (BNB\u2192BSC, MATIC/POL\u2192Polygon, AVAX\u2192Avalanche) selects that chain, so the tool can detect when the wallet is connected elsewhere.',required:!1},{name:"to_address",type:"string",description:"Recipient EVM address (0x\u2026). Omit when unknown \u2014 the form will prompt the user.",required:!1},{name:"amount",type:"string",description:'Amount of native coin to send. Accepted forms: a plain amount ("0.1"); a PERCENT of their balance ("50%"); the literal word "max" for the WHOLE balance; or a USD value ("5$", "$5", "5 usd"). NORMALIZE natural-language phrasing in ANY language to one of these before passing it: any "send everything / the whole balance / maximum" phrasing \u2014 regardless of language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") \u2014 MUST become the literal "max"; any "half" phrasing ("half", "m\u1ED9t n\u1EEDa", "\u4E00\u534A", "\u534A\u5206") MUST become "50%". Pass plain/percent/USD numbers VERBATIM. The tool converts percent/usd/max into a concrete amount itself (and for the native coin always leaves enough to cover the network fee). Omit when unknown.',required:!1}];async buildParameters(e,t){let n={},r=this.requireChain(e,t)??void 0,o=t?.walletAddress??void 0,s=await this.readTokenBalanceAndPrice("native",r,o);if(s&&(s.balance==null||s.balance<=0)){let l=s.symbol||"the native coin";return{error:"no_native_balance",_instructions:`The user wants to send ${l} but their wallet holds 0 ${l} on this chain. Tell them, in their language, that they do not have any ${l} to send and suggest they top up first. Do NOT open the form. Do NOT mention tool names, UI, or forms.`}}if(e.to_address!=null){let l=this.normaliseAddress(e.to_address);l?n.to_address=l:typeof e.to_address=="string"&&e.to_address.trim()&&(n.to_address=e.to_address.trim())}let a=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:"native",symbol:"the native coin",chain:r,walletAddress:o,gasKind:"send"});if(a&&"error"in a)return a;a&&(n.amount=a.amount);let i=await this.resolveNativeSpendableFormatted(r,o,"send");return i!==void 0&&(n.spendable=i),n}};var Ct=class extends oe{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 with two short sentences using the pattern 'state intent \u2192 ask to choose': first state what the user wants to do (e.g., 'B\u1EA1n mu\u1ED1n g\u1EEDi token.' / 'You want to send a token.'), then ask them to choose which token from the options below (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n g\u1EEDi.' / 'Please choose the token you want to send.'). Do NOT list the tokens in text \u2014 the options already show them. Do NOT mention tool names, UI, or forms."}}}let o=this.requireChain(e,t)??void 0,s=t?.walletAddress??void 0,a=await this.findOwnedTokenForSend(e,o,s);if(a&&"error"in a)return a;let i=a?.token,l={};if(i?.address&&(l.contract_address=i.address),i?.symbol&&(l.token_symbol=i.symbol),i?.decimals!==void 0&&(l.decimals=String(i.decimals)),e.to_address!=null){let u=this.normaliseAddress(e.to_address);u?l.to_address=u:typeof e.to_address=="string"&&e.to_address.trim()&&(l.to_address=e.to_address.trim())}if(i?.address){let u=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:i.address,symbol:i.symbol,chain:o,walletAddress:s});if(u&&"error"in u)return u;u&&(l.amount=u.amount)}else{let u=this.normaliseAmount(e.amount);u&&(l.amount=u)}return l}async findOwnedTokenForSend(e,t,n){let r=this.normaliseAddress(e.contract_address),o=typeof e.token_symbol=="string"&&e.token_symbol.trim()?e.token_symbol.trim():void 0,s=r??o;if(!this.moralis||!n||!s){let p=await this.resolveTokenRef({contractArg:e.contract_address,symbolArg:e.token_symbol,decimalsArg:e.decimals,chain:t,walletAddress:n});return"error"in p?{error:"token_not_found",symbol:p.key,_instructions:`Could not resolve token "${p.key}" to a contract address on this chain. Ask the user to provide the token contract directly.`}:{token:p.token}}let a=s.toLowerCase(),i=r!=null,l=await this.moralis.getWalletTokenBalances({address:n,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0}),d=(l.success?l.data?.result??[]:[]).find(p=>i?(p.token_address??"").toLowerCase()===a:(p.symbol??"").toLowerCase()===a);if(d&&d.balance&&!/^0*$/.test(d.balance.trim())){let p=this.parseDecimals(e.decimals);return{token:{address:d.token_address,symbol:d.symbol??o,name:d.name,decimals:p??(typeof d.decimals=="number"?d.decimals:void 0)}}}let m=d?.symbol??o??s;return{error:"no_balance",symbol:m,_instructions:`The user does not hold any ${m} in their wallet on this chain, so there is nothing to send. Tell them (in their language) that they don't have any ${m} to send, and suggest they top up or pick another token. Do NOT open a form. Do NOT mention tool names, UI, or forms.`}}buildSendPrompt(e,t,n,r){let s=(e&&e.includes("{symbol}")?e:"send {amount} {symbol} to {to}").replace(/\{symbol\}/g,t).replace(/\{amount\}/g,n??"");return r?s=s.replace(/\{to\}/g,r):s=s.replace(/\s*\S+\s*\{to\}/g,"").replace(/\{to\}\s*\S+\s*/g,"").replace(/\{to\}/g,""),s.replace(/\s+/g," ").trim()}};var Ut=class extends oe{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 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 Q({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=Me(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=me(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=X(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 de.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=me(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=me(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 de.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=Be({abi:yn,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 with two short sentences using the pattern 'state intent \u2192 ask to choose': first state what the user wants to do (e.g., 'B\u1EA1n mu\u1ED1n mua ${r}.' / 'You want to buy ${r}.'), then ask them to choose which token they want to spend to buy ${r} from the options below (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n d\xF9ng \u0111\u1EC3 mua.' / 'Please choose the token to spend.'). Do NOT list the tokens in text. Do NOT mention tool names, UI, or forms.`}}async checkPayBalance(e){let{args:t,userContext:n,chain:r,destSymbol:o,destContract:s,payAddr:a,payAmount:i,buyAmount:l,derivedFromBuy:u}=e,d=n.walletAddress,m=await this.readPayBalance(d,a,r),p=e.paySymbol??m?.symbol??"the selected token";if(!m){this.dbg("checkPayBalance: NOT held \u2192 pay picker fallback",{paySymbol:p});let k=await this.buildPayWithPicker({args:t,userContext:n,destSymbol:o,destContract:s,buyAmount:l,payAmount:i});return"actionButtons"in k?{error:"no_pay_balance",actionButtons:k.actionButtons,_instructions:`The user wanted to spend ${p} to buy ${o}, but their wallet holds no ${p} on this chain. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them they don't have any ${p} on this chain (e.g., 'B\u1EA1n kh\xF4ng c\xF3 ${p} tr\xEAn m\u1EA1ng n\xE0y.'), then ask them to choose one of the tokens they already hold to pay with (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n d\xF9ng \u0111\u1EC3 mua ${o} t\u1EEB danh s\xE1ch b\xEAn d\u01B0\u1EDBi.' / 'Please choose a token to pay with from the options below.'). Do NOT list the tokens in text, do NOT invent a balance, and do NOT mention tool names, UI, or forms.`}:k}if(!i||e.amountSizedToBalance)return null;let h=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. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them that buying ${u} ${o} would cost about ${i} ${p}, which is more than their balance of ${f} ${p}, then ask them to choose a smaller amount or spend less \u2014 `:`The user wants to spend ${i} ${p} to buy ${o}, but they only have ${f} ${p} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them ${i} ${p} exceeds their balance of ${f} ${p}, then ask them to choose 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 Pt(n,"swap"),u=At(i,l,n);return this.dbg("readPayBalance: native spendable",{balanceNum:i,spendable:u}),u<=0?(this.dbg("readPayBalance: native spendable \u2264 0 \u2192 null",{balanceNum:i}),null):{balanceFormatted:this.trimAmount(u),balanceNum:u,symbol:a.symbol}}return this.dbg("readPayBalance: held",{payAddr:t,balance:a.balance_formatted,symbol:a.symbol}),{balanceFormatted:a.balance_formatted,balanceNum:i,symbol:a.symbol}}percentSpendButtons(e){let{balanceFormatted:t,balanceNum:n,paySymbol:r,destSymbol:o,buyWithTemplate:s}=e;return[.25,.5,.75,1].map(i=>{let l=i===1?this.cleanAmountString(t):this.trimAmount(n*i);return{label:`${Math.round(i*100)}%`,prompt:this.buildBuyWithPrompt(s,o,r,null,l)}})}async buildAmountPicker(e){let{userContext:t,chain:n,destSymbol:r,payAddr:o,buyWithTemplate:s}=e,a=t.walletAddress,i=await this.readPayBalance(a,o,n),l=e.paySymbol??i?.symbol;if(!i||!l)return null;let u=this.percentSpendButtons({balanceFormatted:i.balanceFormatted,balanceNum:i.balanceNum,paySymbol:l,destSymbol:r,buyWithTemplate:s}),d=this.trimAmount(i.balanceNum);return{actionButtons:u,_instructions:`The user's spendable balance is ${d} ${l}. Reply briefly in the user's language using the pattern 'state intent + balance \u2192 ask to choose': first state what the user wants and their spendable ${l} balance (e.g., 'B\u1EA1n mu\u1ED1n mua ${r} b\u1EB1ng ${l}, hi\u1EC7n c\xF3 ${d} ${l} c\xF3 th\u1EC3 d\xF9ng.' / 'You want to buy ${r} with ${l}; you have ${d} ${l} available.'), then ask how much they want to spend and invite them to choose one of the options below or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT mention tool names, UI, buttons, or forms.`}}trimAmount(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(8))):"0"}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),l=a+i,u;if(s>=0){let d=a.length+s;u=d>=l.length?l.padEnd(d,"0"):`${l.slice(0,d)}.${l.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${l}`;return u.includes(".")&&(u=u.replace(/\.?0+$/,"")),n?`-${u}`:u}cleanAmountString(e){if(!/e/i.test(e))return e;let t=Number(e);return Number.isFinite(t)?this.toPlainDecimal(t):e}buildBuyPrompt(e,t){return(e&&e.includes("{token}")?e:"buy {token}").replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}buildBuyWithPrompt(e,t,n,r,o){return(e&&e.includes("{token}")&&e.includes("{pay}")?e:"buy {buy_amount} {token} with {pay_amount} {pay}").replace(/\{token\}/g,t).replace(/\{pay\}/g,n).replace(/\{buy_amount\}/g,r??"").replace(/\{pay_amount\}/g,o??"").replace(/\s+/g," ").trim()}async buildTrendingPicker(e,t){if(!this.pantograph)return{error:"trending_unavailable",_instructions:"Trending tokens are unavailable in this build (service not configured). Tell the user briefly and ask them to name a token to buy."};let n=this.requireChain(e,t)??void 0,r=typeof e.limit=="number"&&Number.isFinite(e.limit)?Math.floor(e.limit):6,o=Math.max(1,Math.min(10,r)),s=await this.pantograph.getTrendingTokens({chain:n,limit:o}),a=s.success?s.data??[]:[];if(a.length===0)return{error:"no_trending_tokens",_instructions:"No trending tokens are available for this chain right now. Tell the user briefly and suggest they name a token directly."};let i=a.filter(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
|
-
${y}${T}`}).join(`
|
|
171
|
-
-----
|
|
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 _.get(`${this.coinPoolBaseUrl}/pair/pure-list?chainId=${t}`);if(!a.ok)throw new Error(I(a));n=a.data?.pairs||[]}catch{return[]}if(e.tokens&&e.tokens.length>=2){let a=e.tokens[0].toUpperCase(),i=e.tokens[1].toUpperCase();if(n=n.filter(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),g=Math.max(i,c);if(u<m||u>g)return null;let f=Number(e.fee),h=Wo[f]??f/1e4;return{poolId:e.poolId,pair:`${e.token0.symbol}/${e.token1.symbol}`,fee:`${h}%`,apr:n.apr??null,aprAvg:n.aprAvg??null,token0Price:e.token0.price,token1Price:e.token1.price,priceRange:{minPrice:i,maxPrice:c,currentPrice:u,baseToken:d,quoteToken:p}}}async fetchPositionDetails(e,t){if(!this.keyringPoolBaseUrl||t.length===0)return[];let n=Gs(t,200);return(await Promise.allSettled(n.map(async o=>{let s=new URLSearchParams({tokenIds:o.join(","),chainId:String(e)}),a=await _.get(`${this.keyringPoolBaseUrl}/user/positions-details/v3?${s}`);if(!a.ok)throw new Error(I(a));let i=a.data;return Array.isArray(i)?i:i?.data||[]}))).flatMap(o=>o.status==="fulfilled"?o.value:[])}};function Gs(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 Ks=["tvl","volume","apr","fee","liquidity"],_t=class extends v{name="subgraph-search-pools";description='Search Uniswap V3 liquidity pools via The Graph subgraphs by token symbol(s) on one or more chains. Use for: "find ETH/USDC pools on Base", "show OP pools", "stablecoin pools on Arbitrum", "best USDC pool". Returns pools with pair, TVL, 24h volume, fee tier (%), APR (DefiLlama-enriched), and liquidity rank. Supports TVL/APR/fee filters and sort by tvl, volume, apr, fee, or liquidity. Supported chains: Ethereum (0x1), Arbitrum (0xa4b1), Polygon (0x89), BSC (0x38), Base (0x2105), Optimism (0xa), Avalanche (0xa86a).';category="blockchain-data";noSuggestions=!0;parameters=[{name:"tokens",type:"array",items:{type:"string"},description:"Token symbols to match. 0 tokens = top pools by chosen sort. 1 token = pools containing it. 2 tokens = pools with that exact pair (both orderings tried). Wrapped variants (WETH\u2194ETH, WBTC\u2194BTC) auto-handled.",required:!1,default:[]},{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]},{name:"sortBy",type:"string",description:'Sort key: "tvl" (default), "volume", "apr", "fee" (low-to-high), or "liquidity".',required:!1,default:"tvl"},{name:"minTVL",type:"number",description:"Minimum TVL in USD.",required:!1},{name:"maxTVL",type:"number",description:"Maximum TVL in USD.",required:!1},{name:"minAPR",type:"number",description:"Minimum APR percentage (e.g. 5 for 5%).",required:!1},{name:"maxAPR",type:"number",description:"Maximum APR percentage.",required:!1},{name:"feeTier",type:"number",description:"Fee tier in basis points: 100, 500, 3000, or 10000.",required:!1},{name:"limit",type:"number",description:"Max pools to return after sort/filter. Default 5, max 20.",required:!1,default:5}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=(Array.isArray(e.tokens)?e.tokens:[]).filter(m=>typeof m=="string"&&m.trim().length>0).map(m=>m.trim().toUpperCase()),r=(Array.isArray(e.chains)?e.chains:[]).filter(m=>typeof m=="string"&&m.trim().length>0),o=r.filter(m=>!this.service.isSupported(m)),s=r.filter(m=>this.service.isSupported(m)).map(m=>this.service.resolveChain(m));if(s.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new z;let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],i=typeof e.sortBy=="string"&&Ks.includes(e.sortBy)?e.sortBy:"tvl",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.map(D),supportedChains:this.service.listSupportedChains().map(D),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,g)=>(g.apr??0)-(m.apr??0)):i==="tvl"?p.sort((m,g)=>g.tvl-m.tvl):i==="volume"?p.sort((m,g)=>g.volume24hUsd-m.volume24hUsd):i==="fee"&&p.sort((m,g)=>m.feeTierBps-g.feeTierBps),p=p.slice(0,u),{_instructions:'Prefix the answer with: "According to the latest data from Subgraph Uniswap V3," (translate naturally into the user language; keep the word "Subgraph" as-is). Then list the pools with: pair (e.g. USDC/WETH), fee tier (%), TVL (USD), 24h volume (USD), APR (%). Format USD human-readably ($12.5M, $450K). Mention chain and sort metric briefly. If a pool has no APR (null), skip the APR field rather than showing zero. For EACH pool, render the provided uniswapUrl as a clickable markdown link \u2014 e.g. wrap the pair as "[USDC/WETH](uniswapUrl)" or append "[View on Uniswap](uniswapUrl)". Never omit this link. Do not invent URLs. If unsupportedChains is non-empty, mention them once at the end.',tokens:n,chains:a.map(D),unsupportedChains:o.map(D),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:ce(e)}}};function ce(l){return`https://app.uniswap.org/explore/pools/${en[l.chain]||l.chain}/${l.poolAddress}`}var en={ethereum:"ethereum",arbitrum:"arbitrum",polygon:"polygon",bsc:"bnb",base:"base",optimism:"optimism",avalanche:"avalanche"};var Ct=class extends v{name="subgraph-trending-pools";description='List Uniswap V3 pools with the highest 24h volume on one or more chains via The Graph subgraphs. Use for: "what are trending pools?", "most active pools", "hot pools right now", "pools with biggest volume today". Returns pair, TVL, 24h volume, fee tier (%), APR (DefiLlama-enriched) for each. Supported chains: Ethereum (0x1), Arbitrum (0xa4b1), Polygon (0x89), BSC (0x38), Base (0x2105), Optimism (0xa), Avalanche (0xa86a).';category="blockchain-data";noSuggestions=!0;parameters=[{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]},{name:"limit",type:"number",description:"Max pools to return. Default 5, max 20.",required:!1,default:5}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=(Array.isArray(e.chains)?e.chains:[]).filter(u=>typeof u=="string"&&u.trim().length>0),r=n.filter(u=>!this.service.isSupported(u)),o=n.filter(u=>this.service.isSupported(u)).map(u=>this.service.resolveChain(u));if(n.length>0&&o.length===0)return{_instructions:"User requested unsupported chains. Politely list them and the supported chains; do not show pool data.",unsupportedChains:r,supportedChains:this.service.listSupportedChains(),pools:[],count:0};if(o.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new z;let s=o.length>0?o:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),20):5,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:ce(u)}))}}};var Ut=class extends v{name="subgraph-pool-by-address";description='Fetch full pool detail (pair, fee tier, TVL, 24h volume + fees, APR, token addresses) for a specific Uniswap V3 pool by its on-chain address via The Graph subgraphs. Use whenever the user pastes a 0x pool address: "tell me about pool 0xabc\u2026", "details of this pool 0x\u2026".';category="blockchain-data";noSuggestions=!0;parameters=[{name:"poolAddress",type:"string",description:"0x-prefixed Uniswap V3 pool contract address (40 hex chars).",required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Defaults to the connected chain when omitted.',required:!1}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=typeof e.poolAddress=="string"?e.poolAddress.trim():"";if(!/^0x[a-fA-F0-9]{40}$/.test(n))return{error:"This only supports Uniswap V3 pools. Provide a valid Uniswap V3 pool address (a 0x-prefixed 40-character hex address)."};let r=typeof e.chain=="string"&&e.chain.trim();if(!r&&!t?.chain&&t?.walletAddress)throw new z;let o=r?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(o))return{_instructions:"The requested chain is not supported by the Subgraph pool service. List the supported chains briefly.",unsupportedChains:[o],supportedChains:this.service.listSupportedChains()};let s=this.service.resolveChain(o),a=await this.service.getPoolByAddress({poolAddress:n,chain:s});return a?{_instructions:'Prefix with: "According to the latest data from Subgraph Uniswap V3," (translate naturally; keep "Subgraph" as-is). Show pair, fee tier (%), TVL, 24h volume, 24h fees, APR (skip if null), and the pool address. End the response with the provided uniswapUrl as a clickable markdown link, e.g. "[View on Uniswap](uniswapUrl)". Never omit it. Do not invent URLs.',chain:s,pool:{pair:a.pair,poolAddress:a.poolAddress,chain:a.chain,feeTierBps:a.feeTierBps,feeTierPercent:a.feeTierPercent,tvlUsd:a.tvl,volume24hUsd:a.volume24hUsd,fees24hUsd:a.fees24hUsd,apr:a.apr,token0:a.token0,token1:a.token1,uniswapUrl:ce(a)}}:{error:`No pool found at ${n} on ${D(s)}.`}}};var Rt=class extends v{name="subgraph-pool-by-position-id";description='Return the pool underlying a Uniswap V3 NFT position id. Use when the user wants the pool stats for a numeric id WITHOUT the deposit/withdraw/fee history (e.g. "which pool is position 962961 in?", "show the pool of id 12345"). For full position history use subgraph-position-detail instead.';category="blockchain-data";noSuggestions=!0;parameters=[{name:"positionId",type:"string",description:"Numeric Uniswap V3 NFT position id as a string.",required:!0},{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=typeof e.positionId=="string"?e.positionId.trim():"";if(!/^\d+$/.test(n))return{error:"positionId must be a numeric string."};let r=(Array.isArray(e.chains)?e.chains:[]).filter(u=>typeof u=="string"&&u.trim().length>0),o=r.filter(u=>!this.service.isSupported(u)),s=r.filter(u=>this.service.isSupported(u)).map(u=>this.service.resolveChain(u));if(r.length>0&&s.length===0)return{_instructions:"Requested chains are not supported. List the supported chains.",unsupportedChains:o,supportedChains:this.service.listSupportedChains()};if(s.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new z;let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],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:ce(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 Et=class extends v{name="subgraph-position-detail";description=`Look up a Uniswap V3 liquidity position by its numeric NFT id (e.g. 962961) on one or more chains via The Graph subgraphs. Returns the position (deposited/withdrawn token amounts, collected fees, liquidity) AND its underlying pool (pair, fee tier, TVL, 24h volume, APR). Use when the user names a numeric id with phrases like: "position 12345", "pool id 962961", "tell me about position 962961". Does NOT support the user's wallet-wide LP list \u2014 only a specific id.`;category="blockchain-data";noSuggestions=!0;parameters=[{name:"positionId",type:"string",description:'Numeric Uniswap V3 NFT position id as a string (e.g. "962961").',required:!0},{name:"chains",type:"array",items:{type:"string"},description:'Hex chain IDs: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Pass [] when the user did not name a chain \u2014 the tool defaults to the connected chain.',required:!1,default:[]}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=typeof e.positionId=="string"?e.positionId.trim():"";if(!/^\d+$/.test(n))return{error:"positionId must be a numeric string (Uniswap V3 NFT id)."};let r=(Array.isArray(e.chains)?e.chains:[]).filter(d=>typeof d=="string"&&d.trim().length>0),o=r.filter(d=>!this.service.isSupported(d)),s=r.filter(d=>this.service.isSupported(d)).map(d=>this.service.resolveChain(d));if(r.length>0&&s.length===0)return{_instructions:"User asked about a position on chains we do not support. List unsupported + supported chains.",unsupportedChains:o,supportedChains:this.service.listSupportedChains()};if(s.length===0&&!this.service.isSupported(t?.chain)&&t?.walletAddress)throw new z;let a=s.length>0?s:this.service.isSupported(t?.chain)?[this.service.resolveChain(t?.chain)]:["0x1"],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=en[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:ce(c.pool)}}}};var Nt=class extends v{name="subgraph-coinpool-pairs";description='List CoinPool concentrated-liquidity price-range candidates for a token pair on a chain. Each entry shows: pair, fee tier, min/max price range, current price, and APR within that range (NOT the overall pool APR \u2014 strictly per-range). Use ONLY for queries about price ranges or which range to pick: "what price range should I use for ETH/USDC?", "best range for USDC/WETH on Arbitrum", "g\u1EE3i \xFD kho\u1EA3ng gi\xE1 ETH/USDC". Do NOT use for general "show me ETH/USDC pools" \u2014 use subgraph-search-pools for that.';category="blockchain-data";noSuggestions=!0;parameters=[{name:"tokens",type:"array",items:{type:"string"},description:'Two token symbols (e.g. ["ETH","USDC"]).',required:!0},{name:"chain",type:"string",description:'Hex chain ID: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche. Defaults to the connected chain when omitted.',required:!1},{name:"limit",type:"number",description:"Max entries to return (sorted by APR desc). Default 10.",required:!1,default:10}];service;constructor(e){super(),this.service=new ne(e)}async run(e,t){let n=(Array.isArray(e.tokens)?e.tokens:[]).filter(u=>typeof u=="string"&&u.trim().length>0).map(u=>u.trim().toUpperCase());if(n.length<2)return{error:'tokens must contain two token symbols (e.g. ["ETH","USDC"]).'};let r=typeof e.chain=="string"&&e.chain.trim();if(!r&&!t?.chain&&t?.walletAddress)throw new z;let o=r?e.chain.trim():t?.chain||"0x1";if(!this.service.isSupported(o))return{_instructions:"Requested chain not supported. Mention supported chains.",unsupportedChains:[o],supportedChains:this.service.listSupportedChains()};let s=this.service.resolveChain(o),a=typeof e.limit=="number"&&Number.isFinite(e.limit)&&e.limit>0?Math.min(Math.floor(e.limit),30):10,i=await this.service.getCoinPoolPairs({chain:s,tokens:n});if(i.length===0)return{_instructions:"No CoinPool range data is available for this pair on this chain. Tell the user this nicely and suggest using general pool data instead (via subgraph-search-pools).",tokens:n,chain:s,pairs:[],count:0};let c=[...i].sort((u,d)=>(d.apr??-1/0)-(u.apr??-1/0)).slice(0,a);return{_instructions:'CRITICAL: APR in each entry is the APR for that SPECIFIC price range only \u2014 NEVER present it as the overall pool APR. Always show the min\u2013max price together with the APR, e.g. "APR X% (range: 1800 \u2013 2200 ETH/USDC)". Sort by APR descending; explain that the top entry is the best concentrated range but narrower = higher out-of-range risk.',tokens:n,chain:s,count:c.length,pairs:c}}};function fe(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 Vo(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 Ys={send:30000n,swap:1000000n},js={"0x1":["https://ethereum-rpc.publicnode.com","https://eth.drpc.org","https://1rpc.io/eth"],"0xa":["https://optimism-rpc.publicnode.com","https://optimism.drpc.org"],"0x38":["https://bsc-rpc.publicnode.com","https://bsc.drpc.org"],"0x89":["https://polygon-bor-rpc.publicnode.com","https://polygon.drpc.org"],"0x2105":["https://base-rpc.publicnode.com","https://base.drpc.org"],"0xa4b1":["https://arbitrum-one-rpc.publicnode.com","https://arbitrum.drpc.org"],"0xa86a":["https://avalanche-c-chain-rpc.publicnode.com","https://avalanche.drpc.org"],"0xe708":["https://linea-rpc.publicnode.com","https://linea.drpc.org"]};async function zs(l){let e=l.toLowerCase(),n=[kt(l),...js[e]??[]].filter(r=>!!r);for(let r of n)try{let o=await ve(r,"eth_gasPrice",[]),s=BigInt(o);if(s>0n)return s}catch{}return 0n}async function It(l,e){let t=await zs(l);return t<=0n?0n:Ys[e]*t}function Dt(l,e,t){if(e<=0n)return l;let n=ee(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 ae=class extends v{kind="action";category="wallet-action";noSuggestions=!0;userInputFields=[];moralis;constructor(e){super(),e!==void 0&&(this.moralis=new N(e))}async resolveContractAddress(e,t,n){if(!this.moralis||!e)return;let r=e.trim().toLowerCase(),o=B(e.trim()),s=i=>o?(i.token_address??"").toLowerCase()===r:(i.symbol??"").toLowerCase()===r;if(n){let i=await this.moralis.getWalletTokenBalances({address:n,chain:t});if(i.success&&i.data?.result?.length){let 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 lo(e.chain,t)}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return B(t)?t:null}normaliseAmount(e){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return String(e);if(typeof e!="string")return null;let t=e.trim();if(!t)return null;let n=parseFloat(t);return Number.isFinite(n)&&n>0?t:null}async resolveAmountInput(e){let{rawAmount:t,tokenAddress:n,chain:r,walletAddress:o,gasKind:s}=e,a=fe(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=Vo(a,{balance:p,usdPrice:d?.usdPrice});return m.ok?{amount:this.toPlainDecimal(m.amount)}:m.reason==="no_balance"?{error:"amount_balance_unavailable",_instructions:`Could not read the user's ${i} balance to work out ${a.raw} right now. Tell them, in their language, that the balance couldn't be fetched at the moment and ask them to enter an exact ${i} amount instead. Do NOT invent a balance or amount. Do NOT mention tool names, UI, or forms.`}:{error:"amount_price_unavailable",_instructions:`Could not get a USD price for ${i} to work out ${a.raw} right now. Tell them, in their language, that the price couldn't be fetched at the moment and ask them to enter an exact ${i} amount instead. Do NOT invent a price or amount. Do NOT mention tool names, UI, or forms.`}}async computeNativeSpendable(e,t,n){let r=await It(e,t);return Dt(n,r,e)}async resolveNativeSpendableFormatted(e,t,n){if(!(!e||!t))try{let r=await this.readTokenBalanceAndPrice("native",e,t);if(r?.balance===void 0)return;let o=await this.computeNativeSpendable(e,n,r.balance);return this.toPlainDecimal(o)}catch{return}}async readTokenBalanceAndPrice(e,t,n){if(!this.moralis||!n)return;let r=await this.moralis.getWalletTokenBalances({address:n,chain:t,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[],s=e.toLowerCase()==="native",a=e.toLowerCase(),i=o.find(u=>s?u.native_token===!0:(u.token_address??"").toLowerCase()===a);if(!i)return;let 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=He(e.chain,t);if(r){let h=this.actionType.replace(/_/g," ");return{error:"wrong_chain",_instructions:`The user asked to ${h} on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to ${h} on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can ${h} on ${r.connectedLabel} (their current network) instead. Do NOT render the form. Do NOT mention tool names, UI, or forms.`}}let o=this.requireChain(e,t);if(!o)return{error:"missing_chain",_instructions:'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.'};let s=await this.buildParameters(e,t);if(s&&typeof s=="object"&&"error"in s&&typeof s.error=="string"||s&&typeof s=="object"&&"actionButtons"in s&&Array.isArray(s.actionButtons)||s&&typeof s=="object"&&"ui"in s&&s.ui&&typeof s.ui=="object"&&typeof s.ui.component=="string")return s;let a={action:this.actionType,chainId:o,walletAddress:n.address,parameters:s},i={component:this.component,props:a},c=a.parameters,u=h=>{let y=c[h];return y!=null&&y!==""},d=this.userInputFields.filter(h=>!(Array.isArray(h.key)?h.key:[h.key]).some(u)).map(h=>h.label),p=this.userInputFields.map(h=>{let k=(Array.isArray(h.key)?h.key:[h.key]).find(u);return k?`${h.label}: ${c[k]}`:null}).filter(h=>h!=null),m=this.actionType.replace(/_/g," "),g='NEVER invent, guess, or insert a placeholder (e.g. "[recipient address]") for any detail the user has not actually provided \u2014 only state values listed as already provided. Do NOT mention tool names, UI, or forms.',f=d.length>0?`The ${m} is in progress but still needs input from the user. Ask the user, in their language, to provide: ${d.join(", ")}. `+(p.length>0?`Already provided: ${p.join("; ")}. `:"")+`Do NOT say "review and confirm" yet \u2014 there are still missing details to collect. ${g} Wait for the user to provide the missing details before proceeding.`:`The ${m} has all required details. In the user's language, ask the user to review and confirm to complete the ${m}. `+(p.length>0?`Details provided: ${p.join("; ")}. `:"")+`${g} Wait for the user to confirm or edit before proceeding.`;return{ui:i,action:this.actionType,chainId:o,parameters:a.parameters,_instructions:f}}};var Lt=class extends ae{name="open-send-native-form";actionType="send_native";component="SendNativeForm";userInputFields=[{key:"to_address",label:"the recipient address"},{key:"amount",label:"the amount to send"}];constructor(e){super(e)}async run(e,t){if(e.chain==null||e.chain===""){let n=co(e.native_symbol);if(n)return super.run({...e,chain:n},t)}return super.run(e,t)}description=`Open the SEND NATIVE form for the chain's native coin (ETH/BNB/MATIC/AVAX/\u2026). Use for ANY native-send intent: "send eth", "send 0.1 ETH", "transfer 1 BNB to vitalik.eth", "g\u1EEDi ETH". Call EVEN IF the user did not name an amount or recipient \u2014 the form prompts for them. Do NOT use for ERC-20 tokens \u2014 use open-send-token-form instead.`;parameters=[{name:"chain",type:"string",description:'Hex chain id: "0x1" Ethereum, "0xa" Optimism, "0x38" BSC, "0x89" Polygon, "0x2105" Base, "0xa4b1" Arbitrum, "0xa86a" Avalanche, "0xe708" Linea. Defaults to connected chain.',required:!1},{name:"native_symbol",type:"string",description:'The native coin the user named to send, VERBATIM (e.g. "ETH", "BNB", "MATIC", "AVAX"). ALWAYS set this when the user names the coin ("send bnb" \u2192 native_symbol="BNB"); leave blank only when they say just "send" with no coin. A coin that belongs to one specific chain (BNB\u2192BSC, MATIC/POL\u2192Polygon, AVAX\u2192Avalanche) selects that chain, so the tool can detect when the wallet is connected elsewhere.',required:!1},{name:"to_address",type:"string",description:"Recipient EVM address (0x\u2026). Omit when unknown \u2014 the form will prompt the user.",required:!1},{name:"amount",type:"string",description:'Amount of native coin to send. Accepted forms: a plain amount ("0.1"); a PERCENT of their balance ("50%"); the literal word "max" for the WHOLE balance; or a USD value ("5$", "$5", "5 usd"). NORMALIZE natural-language phrasing in ANY language to one of these before passing it: any "send everything / the whole balance / maximum" phrasing \u2014 regardless of language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") \u2014 MUST become the literal "max"; any "half" phrasing ("half", "m\u1ED9t n\u1EEDa", "\u4E00\u534A", "\u534A\u5206") MUST become "50%". Pass plain/percent/USD numbers VERBATIM. The tool converts percent/usd/max into a concrete amount itself (and for the native coin always leaves enough to cover the network fee). Omit when unknown.',required:!1}];async buildParameters(e,t){let n={},r=this.requireChain(e,t)??void 0,o=t?.walletAddress??void 0,s=await this.readTokenBalanceAndPrice("native",r,o);if(!s||s.balance==null||s.balance<=0){let c=s?.symbol||"the native coin";return{error:"no_native_balance",_instructions:`The user wants to send ${c} but their wallet holds 0 ${c} on this chain. Tell them, in their language, that they do not have any ${c} to send and suggest they top up first. Do NOT open the form. Do NOT mention tool names, UI, or forms.`}}if(e.to_address!=null){let c=this.normaliseAddress(e.to_address);c?n.to_address=c:typeof e.to_address=="string"&&e.to_address.trim()&&(n.to_address=e.to_address.trim())}let a=await this.resolveAmountInput({rawAmount:e.amount,tokenAddress:"native",symbol:"the native coin",chain:r,walletAddress:o,gasKind:"send"});if(a&&"error"in a)return a;a&&(n.amount=a.amount);let i=await this.resolveNativeSpendableFormatted(r,o,"send");return i!==void 0&&(n.spendable=i),n}};var Mt=class extends ae{name="open-send-token-form";actionType="send_token";component="SendTokenForm";userInputFields=[{key:"to_address",label:"the recipient address"},{key:"amount",label:"the amount to send"}];constructor(e){super(e)}description=`Open the SEND TOKEN (ERC-20) form. Use for ANY ERC-20 send/transfer intent: "send token", "send USDC", "transfer 50 DAI to 0x\u2026", "g\u1EEDi 10 USDT". Call this tool EVEN IF the user did not name a token, amount, or recipient \u2014 the tool handles missing token via a picker, the form prompts for the rest. Do NOT use for the chain's native coin (ETH/BNB/MATIC/AVAX) \u2014 use open-send-native-form instead.`;parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"contract_address",type:"string",description:"ERC-20 token contract address (0x\u2026). Pass when known. Otherwise leave empty and provide token_symbol \u2014 the tool will look up the address.",required:!1},{name:"token_symbol",type:"string",description:'Token symbol (e.g. "USDC"). ALWAYS set this whenever the user names a token to send ("send usdc" \u2192 token_symbol="USDC"); only leave it blank when the user names NO token at all ("send token"), so the tool can show a wallet picker. Used to auto-resolve the contract address and to display the symbol in the form. (Pass either this or contract_address.)',required:!1},{name:"decimals",type:"number",description:"Token decimals (e.g. 6 for USDC, 18 for most ERC-20s). Optional.",required:!1},{name:"to_address",type:"string",description:"Recipient EVM address (0x\u2026). Omit when unknown.",required:!1},{name:"amount",type:"string",description:'Amount of the token to send. Accepted forms: a plain amount ("100"); a PERCENT of their balance ("50%"); the literal word "max" for the WHOLE balance; or a USD value ("5$", "$5", "5 usd"). NORMALIZE natural-language phrasing in ANY language to one of these before passing it: any "send everything / the whole balance / maximum" phrasing \u2014 regardless of language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") \u2014 MUST become "max"; any "half" phrasing ("half", "m\u1ED9t n\u1EEDa", "\u4E00\u534A", "\u534A\u5206") MUST become "50%". Pass plain/percent/USD numbers VERBATIM. The tool converts percent/usd/max into a concrete amount itself. Omit when unknown.',required:!1},{name:"send_prompt_template",type:"string",description:`A short imperative "send" command IN THE USER'S LANGUAGE, used as the label/command when the user has not yet picked which token to send (token-picker buttons). Write it in the SAME language the user is using right now. Use these exact placeholders (do not translate the braces): "{symbol}" for the token, "{amount}" for the amount, "{to}" for the recipient address. Put the localized verb and the "to" preposition directly in the text. English example: "send {amount} {symbol} to {to}". Vietnamese example: "g\u1EEDi {amount} {symbol} \u0111\u1EBFn {to}". Japanese example: "{to} \u306B {amount} {symbol} \u3092\u9001\u308B". Always include {symbol}. Include {amount}/{to} too \u2014 the tool removes any whose value is unknown.`,required:!0}];async buildParameters(e,t){let n=this.normaliseAddress(e.contract_address)!=null,r=typeof e.token_symbol=="string"&&e.token_symbol.trim().length>0;if(!n&&!r&&this.moralis){let u=this.requireChain(e,t)??void 0,d=t?.walletAddress;if(d){let p=await this.moralis.getWalletTokenBalances({address:d,chain:u,excludeSpam:!0,excludeUnverifiedContracts:!0}),g=(p.success?p.data?.result??[]:[]).filter(w=>w.symbol&&(w.native_token||w.token_address)).sort((w,b)=>(b.usd_value??0)-(w.usd_value??0)).slice(0,8);if(g.length===0)return{error:"no_token_holdings",_instructions:"The wallet does not hold any sendable tokens on this chain. Tell the user they have nothing to send and suggest they top up first. Do NOT open the form."};let f=e.to_address!=null?this.normaliseAddress(e.to_address):null,h=this.normaliseAmount(e.amount),y=typeof e.send_prompt_template=="string"?e.send_prompt_template:"";return{actionButtons:g.map(w=>{let b=w.symbol,T=w.balance_formatted?this.cleanAmountString(w.balance_formatted):"",A=w.name?w.name:"",x=w.usd_value?`($${w.usd_value.toFixed(2)})`:"";return{label:`${A}:${T} ${b} ${x}`.trim(),prompt:this.buildSendPrompt(y,b,h,f)}}),_instructions:"Reply briefly in the user's language with two short sentences using the pattern 'state intent \u2192 ask to choose': first state what the user wants to do (e.g., 'B\u1EA1n mu\u1ED1n g\u1EEDi token.' / 'You want to send a token.'), then ask them to choose which token from the options below (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n g\u1EEDi.' / 'Please choose the token you want to send.'). Do NOT list the tokens in text \u2014 the options already show them. Do NOT mention tool names, UI, or forms."}}}let o=this.requireChain(e,t)??void 0,s=t?.walletAddress??void 0,a=await this.findOwnedTokenForSend(e,o,s);if(a&&"error"in a)return a;let i=a?.token,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 Bt=class extends ae{name="open-approve-token-form";actionType="approve_token";component="ApproveTokenForm";userInputFields=[{key:"spender_address",label:"the spender address to approve"}];constructor(e){super(e)}description='Open the APPROVE TOKEN form so the user can grant a spender allowance on an ERC-20 token. Use when the user says "approve USDC for Uniswap", "c\u1EA5p quy\u1EC1n 100 USDT cho 0x\u2026", "revoke approval". Pass `contract_address` when you have it; otherwise pass `token_symbol` (e.g. "USDC") and the tool auto-resolves the contract. Always pass `spender_address` (the contract being approved). Pass `amount` for a specific allowance; omit for unlimited (max uint256). For approving 0 to revoke, pass amount="0".';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"contract_address",type:"string",description:"ERC-20 token contract address (0x\u2026). Pass when known. Otherwise leave empty and provide token_symbol.",required:!1},{name:"token_symbol",type:"string",description:'Token symbol (e.g. "USDC"). Required when contract_address is not given. Used to auto-resolve the contract.',required:!1},{name:"decimals",type:"number",description:"Token decimals.",required:!1},{name:"spender_address",type:"string",description:"EVM address of the spender contract being approved (0x\u2026). Omit when unknown \u2014 the form will prompt the user.",required:!1},{name:"amount",type:"string",description:'Human-readable allowance amount. Omit for UNLIMITED (max uint256). Pass "0" to revoke.',required:!1}];async buildParameters(e,t){let n=this.requireChain(e,t)??void 0,r=await this.resolveTokenRef({contractArg:e.contract_address,symbolArg:e.token_symbol,decimalsArg:e.decimals,chain:n,walletAddress:t?.walletAddress??void 0});if("error"in r)return{error:"token_not_found",symbol:r.key,_instructions:`Could not resolve token "${r.key}" to a contract address on this chain. Ask the user to provide the token contract directly.`};let o=r.token,s=this.normaliseAddress(e.spender_address),a={};if(o?.address&&(a.contract_address=o.address),s&&(a.spender_address=s),o?.symbol&&(a.token_symbol=o.symbol),o?.decimals!==void 0&&(a.decimals=String(o.decimals)),typeof e.amount=="string"&&e.amount.trim()){let i=e.amount.trim(),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 Ot=class extends v{name="open-buy-token-form";kind="action";category="wallet-action";noSuggestions=!0;moralis;pantograph;debug;constructor(e,t){super(),e!==void 0&&(this.moralis=new N(e),this.pantograph=new Z({baseUrl:e.pantographUrl})),this.debug=t?.debug??!1}dbg(e,t){this.debug&&(t===void 0?console.log(`[BuyTokenTool] ${e}`):console.log(`[BuyTokenTool] ${e}`,JSON.stringify(t)))}description='Open the BUY TOKEN flow for ANY buy/purchase intent. Handles both cases: (a) the user NAMED a token \u2014 "buy PEPE", "mua DOGE", "buy 100 USDC with ETH": pass `token_symbol` (the destination); if you also know the pay token, fill `pay_with_symbol` (or `pay_with_address`), else leave pay_with_* blank and the tool returns a wallet picker. (b) the user did NOT name a token \u2014 "buy a trending token", "mua token trending", "buy something pumping", "buy hot tokens", "mua token t\u0103ng m\u1EA1nh": leave `token_symbol` blank and the tool returns buttons of the chain\'s current trending tokens so the user picks one. Amount: `buy_amount` = destination to RECEIVE; `pay_with_amount` = payment to SPEND. Never both.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"token_symbol",type:"string",description:'Symbol of the token the user wants to BUY (e.g. "PEPE"). Leave EMPTY when the user did not name a token (e.g. "buy a trending token") \u2014 the tool then returns trending tokens to pick from.',required:!1},{name:"contract_address",type:"string",description:`Destination token address (0x\u2026), or "native" for the chain's native coin. Leave empty when unknown \u2014 the tool resolves it from token_symbol.`,required:!1},{name:"buy_amount",type:"string",description:`Quantity of the DESTINATION token (the one being bought) the user wants to RECEIVE. Set this when the amount sits next to the token being bought: "buy 5 PEPE", "mua 5 PEPE", "buy 0.02 PCM with USDC" \u2192 buy_amount = the number next to PEPE/PCM. May also be a USD value when the user sizes the buy in dollars ("buy $5 of PEPE" \u2192 "5$"); pass the user's expression VERBATIM (the tool converts "$" to a token quantity). Mutually exclusive with pay_with_amount \u2014 set at most ONE of the two.`,required:!1},{name:"pay_with_symbol",type:"string",description:'Symbol of the payment token (e.g. "USDC", "ETH").',required:!1},{name:"pay_with_address",type:"string",description:`Address of the payment token (0x\u2026), OR "native" for the chain's native coin.`,required:!1},{name:"pay_with_amount",type:"string",description:'Quantity of the PAYMENT token (the one being spent) the user wants to SPEND. Set this when the amount sits next to the payment token: "buy PEPE with 10 USDC", "mua PEPE b\u1EB1ng 10 USDC", "spend 0.5 ETH on DOGE" \u2192 pay_with_amount = the number next to USDC/ETH. May also be a PERCENT of the pay-token balance ("buy USDC with 50% USDT" \u2192 "50%"), the literal word "max" meaning the WHOLE balance, or a USD value ("buy USDC with 5$ USDT" \u2192 "5$"). NORMALIZE any "whole balance / everything / maximum" phrasing in ANY language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") to the literal "max"; pass plain/percent/USD numbers VERBATIM (the tool converts "%"/"$"/"max"). "max" is an AMOUNT, never a token \u2014 still set pay_with_symbol to the payment token ("ETH" in "buy USDC with max ETH"). Mutually exclusive with buy_amount \u2014 set at most ONE of the two.',required:!1},{name:"limit",type:"number",description:"Only used when no token is named: max number of trending tokens to show as buttons. Default 6. Clamped to [1, 10].",required:!1},{name:"buy_prompt_template",type:"string",description:`A short "buy" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the trending-token picker (when the user named no token). Use the exact placeholder "{token}" (do not translate the braces) for the token symbol; put the localized verb directly in the text. English example: "buy {token}". Vietnamese example: "mua {token}". Japanese example: "{token} \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=He(e.chain,t);return r?{error:"wrong_chain",_instructions:`The user asked to buy on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to buy on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can buy on ${r.connectedLabel} (their current network) instead. Do NOT proceed. Do NOT mention tool names, UI, or forms.`}:this.requireChain(e,t)?this.buildResult(e,t):{error:"missing_chain",_instructions:'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.'}}async buildResult(e,t){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,g=d.decimals,f=await this.resolveBuyAmountSpec(e.buy_amount,i,o,c),h=p?await this.resolvePayAmountSpec(e.pay_with_amount,p,o,s,m):this.normaliseAmount(e.pay_with_amount),y=fe(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:g},buyAmount:f,payAmount:h}),!i)return this.dbg("\u2192 dest token not resolved"),{error:"dest_token_not_found",_instructions:`Could not find the token "${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:h});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&&!h){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.`};h=b,w=f,k=!1,this.dbg("\u2192 Branch 1.3: derived pay amount",{payAmount:h,derivedFromBuy:w})}if(this.moralis&&t?.walletAddress){this.dbg("Branch 1.4: checking pay balance",{payAddr:p,paySymbol:m,payAmount:h});let b=await this.checkPayBalance({args:e,userContext:t,chain:o,destSymbol:c,destContract:i,payAddr:p,paySymbol:m,payAmount:h,buyAmount:f,derivedFromBuy:w,amountSizedToBalance:k});if(b)return this.dbg("\u2192 Branch 1.4: balance shortfall \u2014 returning",{error:b.error,hasButtons:Array.isArray(b.actionButtons)}),b;this.dbg("Branch 1.4: balance OK \u2014 continuing")}if(!f&&!h&&this.moralis&&t?.walletAddress){this.dbg("Branch 1.5: building amount picker");let b=await this.buildAmountPicker({userContext:t,chain:o,destSymbol: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(h&&(g!==void 0||p==="native")){this.dbg("Branch 1.75: building confirm tx");let b=await this.buildConfirmTx({userContext:t,chain:o,destContract:i,destSymbol:c,destDecimals:u,payAddr:p,paySymbol:m,payDecimals:g,payAmount:h});return b&&"ui"in b?(this.dbg("\u2192 Branch 1.75: confirm tx returned"),b):b&&"quoteError"in b?(this.dbg("\u2192 Branch 1.75: quote error from provider",{quoteError:b.quoteError}),{error:"quote_failed",quoteError:b.quoteError,_instructions:`Could not quote buying ${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 B(t)?t:null}normaliseAmount(e){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return String(e);if(typeof e!="string")return null;let t=e.trim();if(!t)return null;let n=parseFloat(t);return Number.isFinite(n)&&n>0?t:null}requireChain(e,t){let n=typeof e.chain=="string"&&e.chain.trim()?e.chain.trim():null;return n||(typeof t?.chain=="string"&&t.chain.trim()?t.chain.trim():null)}async resolveContractAddress(e,t,n){if(!this.moralis||!e)return;let r=e.trim().toLowerCase(),o=B(e.trim()),s=i=>o?(i.token_address??"").toLowerCase()===r:(i.symbol??"").toLowerCase()===r;if(n){let i=await this.moralis.getWalletTokenBalances({address:n,chain:t});if(i.success&&i.data?.result?.length){let 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()===O?"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=ee(n),m=Number.parseInt(n,16);if(!p||!Number.isFinite(m))return null;let g=a==="native",f=g?p.native.decimals??18:c;if(f===void 0)return null;let h=this.toRawAmount(u,f);if(!h)return null;let y=g?O:a,k=r==="native"?O:r,w;try{w=await(await ge.getServiceByProvider("debridge")).getQuote({srcChainId:m,srcTokenAddress:y,srcTokenAmount:h,dstChainId:m,dstTokenAddress:k,recipientAddress:d,senderAddress:d,slippage:"auto",isCrossChain:!1})}catch(U){return{quoteError:U instanceof Error?U.message:String(U)}}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},A=this.extractOutRaw(w),x=A&&s!==void 0?this.trimAmount(Number(A)/10**s):void 0,P;g||(P=await this.buildApproveTx({chain:n,walletAddress:d,payAddr:a,payDec:f,rawAmount:h,quote:w}));let S={component:"BuyTokenConfirmTx",props:{chain:{hexId:n,name:p.name},payToken:{address:g?"native":a,symbol:i,decimals:f,amount:u,rawAmount:h},buyToken:{address:r,symbol:o,decimals:s,amount:x,rawAmount:A},estimatedOut:x,estimatedOutRaw:A,provider:w.provider,swapTx:T,approveTx:P}},C=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"}.${C} 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"?O:e,r=await this.moralis.getTokenMetadata({address:n,chain:t}),o=r.success?r.data?.usd_price:void 0;return typeof o=="number"&&Number.isFinite(o)&&o>0?o:null}async resolvePayAmountSpec(e,t,n,r,o){let s=fe(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=fe(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 ge.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=Ge({abi:_n,functionName:"approve",args:[d,BigInt(s)]});return{chainId:t,to:r,data:p,value:"0",from:n}}catch{return}}toRawAmount(e,t){let n=e.trim();if(!/^\d*\.?\d+$/.test(n))return null;let[r,o=""]=n.split("."),s=o.slice(0,t).padEnd(t,"0");try{let a=BigInt(r||"0")*10n**BigInt(t)+BigInt(s||"0");return a<=0n?null:a.toString()}catch{return null}}extractQuoteError(e){if(e.errorMessage&&e.errorMessage.trim())return e.errorMessage.trim();let t=r=>{if(typeof r=="string"&&r.trim())return r.trim();if(r&&typeof r=="object"){let o=r;for(let s of[o.message,o.errorMessage,o.error])if(typeof s=="string"&&s.trim())return s.trim()}},n=e.raw??{};return t(e.error)??t(n.errorMessage)??t(n.error)??"The swap could not be quoted right now."}extractOutRaw(e){let t=e.raw??{},n=t.tokenOut?.amount??t.tokenOut?.minAmount??t.details?.currencyOut?.amount??t.details?.currencyOut?.minimumAmount??t.estimation?.dstChainTokenOut?.recommendedAmount??t.estimation?.dstChainTokenOut?.amount;return n&&/^\d+$/.test(n)?n:void 0}async buildPayWithPicker(e){let{args:t,userContext:n,destSymbol:r,destContract:o}=e,s=this.requireChain(t,n)??void 0,a=this.moralis,i=n.walletAddress,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 g=e.buyAmount??this.normaliseAmount(t.buy_amount),f=e.payAmount??this.normaliseAmount(t.pay_with_amount),h=typeof t.buy_with_prompt_template=="string"?t.buy_with_prompt_template:"";return{actionButtons:m.map(k=>{let w=k.symbol,b=k.name?.trim()||w,T=k.balance_formatted?`${this.cleanAmountString(k.balance_formatted)} `:"",A=typeof k.usd_value=="number"&&k.usd_value>0?` ($${k.usd_value.toFixed(2)})`:"",x=`${b}: ${T}${w}${A}`,P=g?this.buildBuyWithPrompt(h,r,w,g,null):this.buildBuyWithPrompt(h,r,w,null,f);return{label:x,prompt:P}}),_instructions:`Reply briefly in the user's language with two short sentences using the pattern 'state intent \u2192 ask to choose': first state what the user wants to do (e.g., 'B\u1EA1n mu\u1ED1n mua ${r}.' / 'You want to buy ${r}.'), then ask them to choose which token they want to spend to buy ${r} from the options below (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n d\xF9ng \u0111\u1EC3 mua.' / 'Please choose the token to spend.'). Do NOT list the tokens in text. Do NOT mention tool names, UI, or forms.`}}async checkPayBalance(e){let{args:t,userContext:n,chain:r,destSymbol:o,destContract:s,payAddr:a,payAmount:i,buyAmount: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. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them they don't have any ${m} on this chain (e.g., 'B\u1EA1n kh\xF4ng c\xF3 ${m} tr\xEAn m\u1EA1ng n\xE0y.'), then ask them to choose one of the tokens they already hold to pay with (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n d\xF9ng \u0111\u1EC3 mua ${o} t\u1EEB danh s\xE1ch b\xEAn d\u01B0\u1EDBi.' / 'Please choose a token to pay with from the options below.'). Do NOT list the tokens in text, do NOT invent a balance, and do NOT mention tool names, UI, or forms.`}:k}if(!i||e.amountSizedToBalance)return null;let g=Number(i);if(!Number.isFinite(g)||g<=p.balanceNum)return null;let f=this.trimAmount(p.balanceNum),h=this.percentSpendButtons({balanceFormatted:p.balanceFormatted,balanceNum:p.balanceNum,paySymbol:m,destSymbol:o,buyWithTemplate:typeof t.buy_with_prompt_template=="string"?t.buy_with_prompt_template:""}),y=u?`Buying ${u} ${o} needs about ${i} ${m}, but they only have ${f} ${m} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them that buying ${u} ${o} would cost about ${i} ${m}, which is more than their balance of ${f} ${m}, then ask them to choose a smaller amount or spend less \u2014 `:`The user wants to spend ${i} ${m} to buy ${o}, but they only have ${f} ${m} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them ${i} ${m} exceeds their balance of ${f} ${m}, then ask them to choose a smaller amount \u2014 `;return{error:"insufficient_pay_balance",actionButtons:h,_instructions:y+'invite them to choose one of the options below (sized to their balance) or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT invent numbers, and do NOT mention tool names, UI, buttons, or forms.'}}async readPayBalance(e,t,n){if(!this.moralis)return null;let r=await this.moralis.getWalletTokenBalances({address:e,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[];this.dbg("readPayBalance",{payAddr:t,chain:n,ok:r.success,count:o.length,held:o.map(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 It(n,"swap"),u=Dt(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 using the pattern 'state intent + balance \u2192 ask to choose': first state what the user wants and their spendable ${c} balance (e.g., 'B\u1EA1n mu\u1ED1n mua ${r} b\u1EB1ng ${c}, hi\u1EC7n c\xF3 ${d} ${c} c\xF3 th\u1EC3 d\xF9ng.' / 'You want to buy ${r} with ${c}; you have ${d} ${c} available.'), then ask how much they want to spend and invite them to choose one of the options below or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT mention tool names, UI, buttons, or forms.`}}trimAmount(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(8))):"0"}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),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 g=m.symbol;return{label:g,prompt:this.buildBuyPrompt(c,g)}}),p=i.map((m,g)=>{let f=m.name?.trim()||m.symbol,h=m.symbol,y=typeof m.usdPrice=="number"&&m.usdPrice>0?`$${this.formatPrice(m.usdPrice)}`:"N/A",k=m.pricePercentChange?.["24h"],w=typeof k=="number"&&Number.isFinite(k)?` (${k>0?"+":k<0?"-":""}${this.formatPercent(Math.abs(k))}%)`:"";return`${g+1}. ${f} (${h}) \u2014 ${y}${w}`}).join(`
|
|
172
170
|
`)+`
|
|
173
|
-
---
|
|
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 using the pattern 'state intent \u2192 ask to choose'. Start with a one-line header that states the user's intent (e.g., 'B\u1EA1n mu\u1ED1n mua token \u2014 \u0111\xE2y l\xE0 c\xE1c token \u0111ang trending tr\xEAn <chain name>:' / '\u{1F4C8} You want to buy a token \u2014 here are the tokens currently trending upwards on <chain name>:'), then output EXACTLY the following list, preserving the line breaks, ordering, prices, and percentages verbatim (translate only the wording, never the numbers). The list already ends with a 'choose' prompt. Do NOT add or remove tokens, and do NOT mention tool names, UI, or forms:
|
|
175
171
|
|
|
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 Q({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=Me(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=me(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=X(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 de.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=me(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=me(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 de.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=Be({abi:yn,functionName:"approve",args:[d,BigInt(s)]});return{chainId:t,to:r,data:m,value:"0",from:n}}catch{return}}toRawAmount(e,t){let n=e.trim();if(!/^\d*\.?\d+$/.test(n))return null;let[r,o=""]=n.split("."),s=o.slice(0,t).padEnd(t,"0");try{let a=BigInt(r||"0")*10n**BigInt(t)+BigInt(s||"0");return a<=0n?null:a.toString()}catch{return null}}extractQuoteError(e){if(e.errorMessage&&e.errorMessage.trim())return e.errorMessage.trim();let t=r=>{if(typeof r=="string"&&r.trim())return r.trim();if(r&&typeof r=="object"){let o=r;for(let s of[o.message,o.errorMessage,o.error])if(typeof s=="string"&&s.trim())return s.trim()}},n=e.raw??{};return t(e.error)??t(n.errorMessage)??t(n.error)??"The swap could not be quoted right now."}extractOutRaw(e){let t=e.raw??{},n=t.tokenOut?.amount??t.tokenOut?.minAmount??t.details?.currencyOut?.amount??t.details?.currencyOut?.minimumAmount??t.estimation?.dstChainTokenOut?.recommendedAmount??t.estimation?.dstChainTokenOut?.amount;return n&&/^\d+$/.test(n)?n:void 0}async buildSourcePicker(e,t){if(!this.moralis||!t?.walletAddress)return{error:"source_unavailable",_instructions:"Cannot list the wallet holdings in this build. Tell the user briefly and ask them to name the token they want to swap from."};let n=this.requireChain(e,t)??void 0,r=t.walletAddress,o=await this.moralis.getWalletTokenBalances({address:r,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),a=(o.success?o.data?.result??[]:[]).filter(u=>u.symbol?u.native_token?!0:!!u.token_address:!1).sort((u,d)=>(d.usd_value??0)-(u.usd_value??0)).slice(0,8);if(a.length===0)return{error:"no_swap_holdings",_instructions:"The wallet holds no tokens to swap on this chain. Tell the user to top up first. Do NOT mention tool names, UI, or forms."};let i=typeof e.swap_from_prompt_template=="string"?e.swap_from_prompt_template:"";return{actionButtons:a.map(u=>{let d=u.symbol,m=u.name?.trim()||d,p=u.balance_formatted?`${this.cleanAmountString(u.balance_formatted)} `:"",h=typeof u.usd_value=="number"&&u.usd_value>0?` ($${u.usd_value.toFixed(2)})`:"";return{label:`${m}: ${p}${d}${h}`,prompt:this.buildSwapFromPrompt(i,d)}}),_instructions:"Reply briefly in the user's language with two short sentences using the pattern 'state intent \u2192 ask to choose': first state what the user wants to do (e.g., 'B\u1EA1n mu\u1ED1n swap token.' / 'You want to swap a token.'), then ask them to choose which token to swap from in the options below (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n swap.' / 'Please choose the token to swap from.'). Do NOT list the tokens in text. Do NOT mention tool names, UI, or forms."}}async buildToTrendingPicker(e){let{args:t,userContext:n,fromSym:r,fromContract:o}=e,s=this.requireChain(t,n)??void 0,a=this.pantograph,i=typeof t.limit=="number"&&Number.isFinite(t.limit)?Math.floor(t.limit):6,l=Math.max(1,Math.min(10,i)),u=await a.getTrendingTokens({chain:s,limit:l+2}),d=u.success?u.data??[]:[],m=o?.toLowerCase(),p=r.toLowerCase(),h=d.filter(T=>!(!T.symbol||m&&m!=="native"&&T.tokenAddress&&T.tokenAddress.toLowerCase()===m||T.symbol.toLowerCase()===p)).slice(0,l);if(h.length===0)return{error:"no_trending_tokens",_instructions:`No trending tokens are available to swap ${r} into on this chain right now. Tell the user briefly and suggest they name the token they want to receive directly. Do NOT mention tool names, UI, or forms.`};let 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
|
-
|
|
178
|
-
-----
|
|
172
|
+
Is there any token you want to buy?`;return{actionButtons:u,_instructions:`The trending tokens are on ${n?D(n):"the connected chain"}. Reply in the user's language using the pattern 'state intent \u2192 ask to choose'. Start with a one-line header that states the user's intent (e.g., 'B\u1EA1n mu\u1ED1n mua token \u2014 \u0111\xE2y l\xE0 c\xE1c token \u0111ang trending tr\xEAn <chain name>:' / '\u{1F4C8} You want to buy a token \u2014 here are the tokens currently trending upwards on <chain name>:'), then output EXACTLY the following list, preserving the line breaks, ordering, prices, and percentages verbatim (translate only the wording, never the numbers). The list already ends with a 'choose' prompt. Do NOT add or remove tokens, and do NOT mention tool names, UI, or forms:
|
|
173
|
+
|
|
174
|
+
${p}`}}formatPercent(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(6))):"0"}formatPrice(e){if(e>=1)return e.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});if(e>=.01)return e.toFixed(4);let t=e.toFixed(12),n=t.match(/^0\.0*([1-9]\d{0,3})/);return n?t.slice(0,(n[0].length||0)+0):t}};var qt=class extends v{name="open-swap-token-form";kind="action";category="wallet-action";noSuggestions=!0;moralis;pantograph;debug;constructor(e,t){super(),e!==void 0&&(this.moralis=new N(e),this.pantograph=new Z({baseUrl:e.pantographUrl})),this.debug=t?.debug??!1}dbg(e,t){this.debug&&(t===void 0?console.log(`[SwapTokenTool] ${e}`):console.log(`[SwapTokenTool] ${e}`,JSON.stringify(t)))}description='Open the SWAP TOKEN flow for ANY swap/exchange/convert intent where the user swaps a token they ALREADY HOLD into another token \u2014 "swap USDC to USDT", "swap USDC", "\u0111\u1ED5i USDC sang ETH", "convert DAI to USDC", "exchange 10 USDC for PEPE". The FROM token is the user\'s own holding (the source). Handles both cases: (a) the user named the destination \u2014 "swap USDC to USDT": pass `from_symbol` (the source, e.g. "USDC") and `to_symbol` (the destination, e.g. "USDT"). (b) the user named only the source \u2014 "swap USDC": pass `from_symbol` and leave `to_symbol` blank; the tool returns buttons of the chain\'s current trending tokens so the user picks what to receive. If the user named NO token at all \u2014 "swap" / "\u0111\u1ED5i token": leave both blank and the tool returns the wallet\'s holdings so the user picks what to swap FROM. Amount: `from_amount` = source to SPEND; `to_amount` = destination to RECEIVE. Never both.';parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"from_symbol",type:"string",description:`Symbol of the SOURCE token the user wants to swap FROM \u2014 a token they already hold (e.g. "USDC"). Leave EMPTY only when the user named no token at all (e.g. "swap") \u2014 the tool then returns the wallet's holdings to pick the source from.`,required:!1},{name:"from_address",type:"string",description:`Source token address (0x\u2026), OR "native" for the chain's native coin. Leave empty when unknown \u2014 the tool resolves it from from_symbol.`,required:!1},{name:"from_amount",type:"string",description:'Quantity of the SOURCE token (the one being swapped FROM / spent) the user wants to SPEND. Set this when the amount sits next to the source token: "swap 10 USDC to USDT", "\u0111\u1ED5i 10 USDC sang ETH" \u2192 from_amount = the number next to USDC. May also be a PERCENT of the source balance ("swap 50% USDC to USDT" \u2192 "50%"), the literal word "max" meaning the WHOLE balance, or a USD value ("swap $5 USDC to USDT" \u2192 "5$"). NORMALIZE any "whole balance / everything / maximum" phrasing in ANY language (e.g. "all", "max", "t\u1EA5t c\u1EA3", "to\xE0n b\u1ED9", "\u5168\u90E8", "\u3059\u3079\u3066", "\uC804\uBD80", "todo", "tout") to the literal "max"; pass plain/percent/USD numbers VERBATIM (the tool converts "%"/"$"/"max"). "max" is an AMOUNT, never a token \u2014 still set from_symbol to the source token ("ETH" in "swap max ETH to USDC"). Mutually exclusive with to_amount \u2014 set at most ONE of the two.',required:!1},{name:"to_symbol",type:"string",description:'Symbol of the DESTINATION token the user wants to RECEIVE (e.g. "USDT"). Leave EMPTY when the user did not name a destination (e.g. "swap USDC") \u2014 the tool then returns trending tokens to pick from.',required:!1},{name:"to_address",type:"string",description:`Destination token address (0x\u2026), OR "native" for the chain's native coin. Leave empty when unknown \u2014 the tool resolves it from to_symbol.`,required:!1},{name:"to_amount",type:"string",description:`Quantity of the DESTINATION token (the one being received) the user wants to RECEIVE. Set this when the amount sits next to the destination token: "swap USDC to 5 USDT" \u2192 to_amount = the number next to USDT. May also be a USD value ("swap USDC to $5 USDT" \u2192 "5$"); pass the user's expression VERBATIM (the tool converts "$"). Mutually exclusive with from_amount \u2014 set at most ONE.`,required:!1},{name:"limit",type:"number",description:"Only used when no destination is named: max number of trending tokens to show as buttons. Default 6. Clamped to [1, 10].",required:!1},{name:"swap_from_prompt_template",type:"string",description:`A short "swap {token}" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the source picker (when the user named no token). Use the exact placeholder "{token}" (do not translate the braces) for the source symbol; put the localized verb directly in the text. English example: "swap {token}". Vietnamese example: "\u0111\u1ED5i {token}". Japanese example: "{token} \u3092\u30B9\u30EF\u30C3\u30D7". Always include {token}.`,required:!0},{name:"swap_to_prompt_template",type:"string",description:`A short "swap X to Y" command IN THE USER'S CURRENT LANGUAGE, used as the click-command for the destination (trending) picker and the percentage-spend buttons. Use these exact placeholders (do not translate the braces): "{from}" = source token, "{to}" = destination token, "{from_amount}" = quantity of the SOURCE token (place it next to {from}), "{to_amount}" = quantity of the DESTINATION token (place it next to {to}). Put the localized verb and the "to" preposition directly in the text. English example: "swap {from_amount} {from} to {to_amount} {to}". Vietnamese example: "\u0111\u1ED5i {from_amount} {from} sang {to_amount} {to}". Japanese example: "{from_amount} {from} \u3092 {to_amount} {to} \u306B\u30B9\u30EF\u30C3\u30D7". Always include {from} and {to}. Include both {from_amount} and {to_amount} too \u2014 the tool removes whichever amount is unknown (only one side ever carries an amount).`,required:!0}];async execute(e,t){let n=`call_${Date.now()}_${Math.random().toString(36).slice(2,8)}`,r=Date.now();try{let o=await this.run(e,t),s={toolName:this.name,callId:n,success:!0,data:o,duration:Date.now()-r};return o&&typeof o=="object"&&o.ui&&(s.ui=o.ui),o&&Array.isArray(o.actionButtons)&&o.actionButtons.length>0&&(s.actionButtons=o.actionButtons),s}catch(o){return{toolName:this.name,callId:n,success:!1,error:o instanceof Error?o.message:String(o),duration:Date.now()-r}}}async run(e,t){if(!t?.walletAddress)return{error:"wallet_not_connected",_instructions:"The user is not connected. Ask them to connect their wallet before swapping. Do NOT proceed."};let r=He(e.chain,t);return r?{error:"wrong_chain",_instructions:`The user asked to swap on ${r.requestedLabel}, but their wallet is connected to ${r.connectedLabel}. Tell them, in their language, that to swap on ${r.requestedLabel} they must first switch their wallet's network to ${r.requestedLabel}, or they can swap on ${r.connectedLabel} (their current network) instead. Do NOT proceed. Do NOT mention tool names, UI, or forms.`}:this.requireChain(e,t)?this.buildResult(e,t):{error:"missing_chain",_instructions:'No chain is set. Pass the hex chain id explicitly (e.g. "0x1") or set userContext.chain before calling.'}}async buildResult(e,t){let n=typeof e.from_symbol=="string"?e.from_symbol.trim():"",r=typeof e.from_address=="string"?e.from_address.trim():"",o=typeof e.to_symbol=="string"?e.to_symbol.trim():"",s=this.requireChain(e,t)??void 0,a=t?.walletAddress??void 0;if(this.dbg("buildResult:args",{args:e,chain:s,walletAddress:a,hasMoralis:!!this.moralis}),!n&&!r)return this.dbg("\u2192 Branch 0: source picker (no source token named)"),await this.buildSourcePicker(e,t);let i=await this.resolveSide(e.from_address,n,s,a,"from"),c=i.address,u=i.symbol??n,d=i.decimals,p=await this.resolveSide(e.to_address,o,s,a,"to"),m=p.address,g=p.symbol??o,f=p.decimals,h=await this.resolveToAmountSpec(e.to_amount,m,s,g),y=c?await this.resolveFromAmountSpec(e.from_amount,c,s,a,u):this.normaliseAmount(e.from_amount),k=fe(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:g,toDecimals:f},fromAmount:y,toAmount:h}),!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:h});if(!m)return{error:"to_token_missing",_instructions:`Ask the user, in their language, which token they want to receive when swapping ${u}. Do NOT invent a token. Do NOT mention tool names, UI, or forms.`};if(h&&!y){this.dbg("Branch 1.3: deriving from amount from to amount",{toAmount:h,fromSym:u,toSym:g});let T=await this.deriveFromAmountFromTo({chain:s,toAmount:h,toContract:m,toSym:g,fromContract: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 ${h} ${g} right now. Tell the user, in their language, that the price couldn't be fetched at the moment, and ask how much ${u??"of the source token"} they want to swap instead. Do NOT invent an amount or a price. Do NOT mention tool names, UI, or forms.`};y=T,b=h,w=!1,this.dbg("\u2192 Branch 1.3: derived from amount",{fromAmount:y,derivedFromTo:b})}if(this.moralis&&t?.walletAddress){this.dbg("Branch 1.4: checking source balance",{fromContract:c,fromSym:u,fromAmount:y});let T=await this.checkFromBalance({args:e,userContext:t,chain:s,fromContract:c,fromSym:u,fromAmount:y,toAmount:h,derivedFromTo:b,amountSizedToBalance:w});if(T)return this.dbg("\u2192 Branch 1.4: balance shortfall \u2014 returning",{error:T.error,hasButtons:Array.isArray(T.actionButtons)}),T;this.dbg("Branch 1.4: balance OK \u2014 continuing")}if(!y&&!h&&this.moralis&&t?.walletAddress){this.dbg("Branch 1.5: building amount picker");let T=await this.buildAmountPicker({userContext:t,chain:s,fromContract:c,fromSym:u,toSym:g,swapToTemplate:typeof e.swap_to_prompt_template=="string"?e.swap_to_prompt_template:""});if(T)return this.dbg("\u2192 Branch 1.5: amount picker returned"),T;this.dbg("Branch 1.5: amount picker null \u2014 needs amount")}if(y&&(d!==void 0||c==="native")){this.dbg("Branch 1.75: building confirm tx");let T=await this.buildConfirmTx({userContext:t,chain:s,toContract:m,toSym:g,toDecimals:f,fromContract: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 ${g}. The swap provider reported: "${T.quoteError}". Tell the user, in their language, exactly that reason (translate the wording, keep any numbers/limits verbatim) and suggest they adjust the amount or try again shortly. Do NOT invent numbers or a different reason. Do NOT mention tool names, UI, or forms.`}):(this.dbg("Branch 1.75: confirm tx unavailable (null)"),{error:"quote_failed",_instructions:`Could not get a quote to swap ${u??"the source token"} to ${g} right now. Tell the user, in their language, that the swap could not be quoted at the moment and ask them to try again shortly or with a different amount. Do NOT invent numbers. Do NOT mention tool names, UI, or forms.`})}return this.dbg("\u2192 needs from amount"),{error:"needs_from_amount",_instructions:`Ask the user, in their language, how much ${u??"of the source token"} they want to swap to ${g}. Do NOT invent an amount. Do NOT mention tool names, UI, or forms.`}}normaliseAddress(e){if(typeof e!="string")return null;let t=e.trim();return B(t)?t:null}normaliseAmount(e){if(typeof e=="number"&&Number.isFinite(e)&&e>0)return String(e);if(typeof e!="string")return null;let t=e.trim();if(!t)return null;let n=parseFloat(t);return Number.isFinite(n)&&n>0?t:null}requireChain(e,t){let n=typeof e.chain=="string"&&e.chain.trim()?e.chain.trim():null;return n||(typeof t?.chain=="string"&&t.chain.trim()?t.chain.trim():null)}async resolveContractAddress(e,t,n){if(!this.moralis||!e)return;let r=e.trim().toLowerCase(),o=B(e.trim()),s=i=>o?(i.token_address??"").toLowerCase()===r:(i.symbol??"").toLowerCase()===r;if(n){let i=await this.moralis.getWalletTokenBalances({address:n,chain:t});if(i.success&&i.data?.result?.length){let 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()===O?"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=ee(n),m=Number.parseInt(n,16);if(!p||!Number.isFinite(m))return null;let g=a==="native",f=g?p.native.decimals??18:c;if(f===void 0)return null;let h=this.toRawAmount(u,f);if(!h)return null;let y=g?O:a,k=r==="native"?O:r,w;try{w=await(await ge.getServiceByProvider("debridge")).getQuote({srcChainId:m,srcTokenAddress:y,srcTokenAmount:h,dstChainId:m,dstTokenAddress:k,recipientAddress:d,senderAddress:d,slippage:"auto",isCrossChain:!1})}catch(U){return{quoteError:U instanceof Error?U.message:String(U)}}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},A=this.extractOutRaw(w),x=A&&s!==void 0?this.trimAmount(Number(A)/10**s):void 0,P;g||(P=await this.buildApproveTx({chain:n,walletAddress:d,fromAddr:a,fromDec:f,rawAmount:h,quote:w}));let S={component:"SwapTokenConfirmTx",props:{chain:{hexId:n,name:p.name},fromToken:{address:g?"native":a,symbol:i,decimals:f,amount:u,rawAmount:h},toToken:{address:r,symbol:o,decimals:s,amount:x,rawAmount:A},estimatedOut:x,estimatedOutRaw:A,provider:w.provider,swapTx:T,approveTx:P}},C=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}.${C} 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"?O:e,r=await this.moralis.getTokenMetadata({address:n,chain:t}),o=r.success?r.data?.usd_price:void 0;return typeof o=="number"&&Number.isFinite(o)&&o>0?o:null}async resolveFromAmountSpec(e,t,n,r,o){let s=fe(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=fe(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 ge.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=Ge({abi:_n,functionName:"approve",args:[d,BigInt(s)]});return{chainId:t,to:r,data:p,value:"0",from:n}}catch{return}}toRawAmount(e,t){let n=e.trim();if(!/^\d*\.?\d+$/.test(n))return null;let[r,o=""]=n.split("."),s=o.slice(0,t).padEnd(t,"0");try{let a=BigInt(r||"0")*10n**BigInt(t)+BigInt(s||"0");return a<=0n?null:a.toString()}catch{return null}}extractQuoteError(e){if(e.errorMessage&&e.errorMessage.trim())return e.errorMessage.trim();let t=r=>{if(typeof r=="string"&&r.trim())return r.trim();if(r&&typeof r=="object"){let o=r;for(let s of[o.message,o.errorMessage,o.error])if(typeof s=="string"&&s.trim())return s.trim()}},n=e.raw??{};return t(e.error)??t(n.errorMessage)??t(n.error)??"The swap could not be quoted right now."}extractOutRaw(e){let t=e.raw??{},n=t.tokenOut?.amount??t.tokenOut?.minAmount??t.details?.currencyOut?.amount??t.details?.currencyOut?.minimumAmount??t.estimation?.dstChainTokenOut?.recommendedAmount??t.estimation?.dstChainTokenOut?.amount;return n&&/^\d+$/.test(n)?n:void 0}async buildSourcePicker(e,t){if(!this.moralis||!t?.walletAddress)return{error:"source_unavailable",_instructions:"Cannot list the wallet holdings in this build. Tell the user briefly and ask them to name the token they want to swap from."};let n=this.requireChain(e,t)??void 0,r=t.walletAddress,o=await this.moralis.getWalletTokenBalances({address:r,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),a=(o.success?o.data?.result??[]:[]).filter(u=>u.symbol?u.native_token?!0:!!u.token_address:!1).sort((u,d)=>(d.usd_value??0)-(u.usd_value??0)).slice(0,8);if(a.length===0)return{error:"no_swap_holdings",_instructions:"The wallet holds no tokens to swap on this chain. Tell the user to top up first. Do NOT mention tool names, UI, or forms."};let i=typeof e.swap_from_prompt_template=="string"?e.swap_from_prompt_template:"";return{actionButtons:a.map(u=>{let d=u.symbol,p=u.name?.trim()||d,m=u.balance_formatted?`${this.cleanAmountString(u.balance_formatted)} `:"",g=typeof u.usd_value=="number"&&u.usd_value>0?` ($${u.usd_value.toFixed(2)})`:"";return{label:`${p}: ${m}${d}${g}`,prompt:this.buildSwapFromPrompt(i,d)}}),_instructions:"Reply briefly in the user's language with two short sentences using the pattern 'state intent \u2192 ask to choose': first state what the user wants to do (e.g., 'B\u1EA1n mu\u1ED1n swap token.' / 'You want to swap a token.'), then ask them to choose which token to swap from in the options below (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n swap.' / 'Please choose the token to swap from.'). Do NOT list the tokens in text. Do NOT mention tool names, UI, or forms."}}async buildToTrendingPicker(e){let{args:t,userContext:n,fromSym:r,fromContract:o}=e,s=this.requireChain(t,n)??void 0,a=this.pantograph,i=typeof t.limit=="number"&&Number.isFinite(t.limit)?Math.floor(t.limit):6,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(),g=d.filter(T=>!(!T.symbol||p&&p!=="native"&&T.tokenAddress&&T.tokenAddress.toLowerCase()===p||T.symbol.toLowerCase()===m)).slice(0,c);if(g.length===0)return{error:"no_trending_tokens",_instructions:`No trending tokens are available to swap ${r} into on this chain right now. Tell the user briefly and suggest they name the token they want to receive directly. Do NOT mention tool names, UI, or forms.`};let f=e.fromAmount??this.normaliseAmount(t.from_amount),h=e.toAmount??this.normaliseAmount(t.to_amount),y=typeof t.swap_to_prompt_template=="string"?t.swap_to_prompt_template:"",k=g.map(T=>{let A=T.symbol,x=f?this.buildSwapToPrompt(y,r,A,f,null):this.buildSwapToPrompt(y,r,A,null,h);return{label:A,prompt:x}}),b=g.map((T,A)=>{let x=T.name?.trim()||T.symbol,P=T.symbol,M=typeof T.usdPrice=="number"&&T.usdPrice>0?`$${this.formatPrice(T.usdPrice)}`:"N/A",S=T.pricePercentChange?.["24h"],C=typeof S=="number"&&Number.isFinite(S)?` (${S>0?"+":S<0?"-":""}${this.formatPercent(Math.abs(S))}%)`:"";return`${A+1}. ${x} (${P}) \u2014 ${M}${C}`}).join(`
|
|
179
175
|
`)+`
|
|
180
|
-
---
|
|
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 using the pattern 'state intent \u2192 ask to choose'. Start with a one-line header that states the user's intent (e.g., 'B\u1EA1n mu\u1ED1n swap ${r} sang token kh\xE1c \u2014 \u0111\xE2y l\xE0 c\xE1c token \u0111ang trending tr\xEAn <chain name>:' / '\u{1F4C8} You want to swap ${r} \u2014 here are the tokens currently trending on <chain name>:'), then output EXACTLY the following list, preserving the line breaks, ordering, prices, and percentages verbatim (translate only the wording, never the numbers). The list already ends with a 'choose' prompt. Do NOT add or remove tokens, and do NOT mention tool names, UI, or forms:
|
|
182
176
|
|
|
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. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them they don't have any ${u} on this chain (e.g., 'B\u1EA1n kh\xF4ng c\xF3 ${u} tr\xEAn m\u1EA1ng n\xE0y.'), then ask them to choose one of the tokens they already hold to swap from (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n swap t\u1EEB danh s\xE1ch b\xEAn d\u01B0\u1EDBi.' / 'Please choose a token to swap from the options below.'). Do NOT list the tokens in text, do NOT invent a balance, and do NOT mention tool names, UI, or forms.`}:f}if(!s||e.amountSizedToBalance)return null;let d=Number(s);if(!Number.isFinite(d)||d<=l.balanceNum)return null;let 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. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them this would cost about ${s} ${u}, which is more than their balance of ${m} ${u}, then ask them to choose a smaller amount \u2014 `:`The user wants to swap ${s} ${u}, but they only have ${m} ${u} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them ${s} ${u} exceeds their balance of ${m} ${u}, then ask them to choose a smaller amount \u2014 `;return{error:"insufficient_from_balance",actionButtons:p,_instructions:h+'invite them to choose one of the options below (sized to their balance) or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT invent numbers, and do NOT mention tool names, UI, buttons, or forms.'}}async readFromBalance(e,t,n){if(!this.moralis)return null;let r=await this.moralis.getWalletTokenBalances({address:e,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[];this.dbg("readFromBalance",{fromAddr:t,chain:n,ok:r.success,count:o.length});let s=t.toLowerCase(),a=o.find(l=>s==="native"?l.native_token===!0:l.token_address?.toLowerCase()===s);if(!a?.balance_formatted)return this.dbg("readFromBalance: source token NOT held \u2192 null",{fromAddr:t}),null;let i=Number(a.balance_formatted);if(!Number.isFinite(i)||i<=0)return this.dbg("readFromBalance: zero/invalid balance \u2192 null",{fromAddr:t,balance:a.balance_formatted}),null;if(s==="native"&&n){let l=await Pt(n,"swap"),u=At(i,l,n);return this.dbg("readFromBalance: native spendable",{balanceNum:i,spendable:u}),u<=0?(this.dbg("readFromBalance: native spendable \u2264 0 \u2192 null",{balanceNum:i}),null):{balanceFormatted:this.trimAmount(u),balanceNum:u,symbol:a.symbol}}return this.dbg("readFromBalance: held",{fromAddr:t,balance:a.balance_formatted,symbol:a.symbol}),{balanceFormatted:a.balance_formatted,balanceNum:i,symbol:a.symbol}}percentSpendButtons(e){let{balanceFormatted:t,balanceNum:n,fromSym:r,toSym:o,swapToTemplate:s}=e;return[.25,.5,.75,1].map(i=>{let l=i===1?this.cleanAmountString(t):this.trimAmount(n*i);return{label:`${Math.round(i*100)}%`,prompt:this.buildSwapToPrompt(s,r,o,l,null)}})}async buildAmountPicker(e){let{userContext:t,chain:n,fromContract:r,toSym:o,swapToTemplate:s}=e,a=t.walletAddress,i=await this.readFromBalance(a,r,n),l=e.fromSym??i?.symbol;if(!i||!l)return null;let u=this.percentSpendButtons({balanceFormatted:i.balanceFormatted,balanceNum:i.balanceNum,fromSym:l,toSym:o,swapToTemplate:s}),d=this.trimAmount(i.balanceNum);return{actionButtons:u,_instructions:`The user's spendable balance is ${d} ${l}. Reply briefly in the user's language using the pattern 'state intent + balance \u2192 ask to choose': first state what the user wants and their spendable ${l} balance (e.g., 'B\u1EA1n mu\u1ED1n swap ${l} sang ${o}, hi\u1EC7n c\xF3 ${d} ${l} c\xF3 th\u1EC3 d\xF9ng.' / 'You want to swap ${l} to ${o}; you have ${d} ${l} available.'), then ask how much they want to swap and invite them to choose one of the options below or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT mention tool names, UI, buttons, or forms.`}}trimAmount(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(8))):"0"}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),l=a+i,u;if(s>=0){let d=a.length+s;u=d>=l.length?l.padEnd(d,"0"):`${l.slice(0,d)}.${l.slice(d)}`}else u=`0.${"0".repeat(-s-a.length)}${l}`;return u.includes(".")&&(u=u.replace(/\.?0+$/,"")),n?`-${u}`:u}cleanAmountString(e){if(!/e/i.test(e))return e;let t=Number(e);return Number.isFinite(t)?this.toPlainDecimal(t):e}buildSwapFromPrompt(e,t){return(e&&e.includes("{token}")?e:"swap {token}").replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}buildSwapToPrompt(e,t,n,r,o){return(e&&e.includes("{from}")&&e.includes("{to}")?e:"swap {from_amount} {from} to {to_amount} {to}").replace(/\{from\}/g,t).replace(/\{to\}/g,n).replace(/\{from_amount\}/g,r??"").replace(/\{to_amount\}/g,o??"").replace(/\s+/g," ").trim()}formatPercent(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(6))):"0"}formatPrice(e){if(e>=1)return e.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});if(e>=.01)return e.toFixed(4);let t=e.toFixed(12),n=t.match(/^0\.0*([1-9]\d{0,3})/);return n?t.slice(0,(n[0].length||0)+0):t}};var Nt=class extends oe{name="open-send-nft-form";actionType="send_nft";component="SendNftForm";userInputFields=[{key:"to_address",label:"the recipient address"}];url;constructor(e,t){super(e);let n=t?.url?.trim();this.url=n||void 0}description=`Open the SEND NFT form. Use for ANY NFT send/transfer intent: "send NFT", "transfer my Bored Ape", "send token id 1234 to 0x\u2026", "g\u1EEDi NFT". Call this tool EVEN IF the user did not name a contract, token id, NFT name, or recipient \u2014 the tool resolves missing identifiers from the wallet's NFT holdings and emits picker buttons when more than one match exists. Do NOT use this for ERC-20s (use open-send-token-form) or the native coin (open-send-native-form).`;parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"contract_address",type:"string",description:'NFT COLLECTION contract address (0x\u2026) \u2014 identifies WHICH NFT collection to send from. Only set this when the user explicitly named the collection/contract (e.g. "contract 0x\u2026", "collection 0x\u2026", "from 0x\u2026"). NEVER put the recipient address here. Omit when unknown.',required:!1},{name:"token_id",type:"string",description:'NFT token id as a decimal string (e.g. "1234"). Omit when unknown.',required:!1},{name:"nft_name",type:"string",description:'Free-form NFT or collection name as the user said it (e.g. "Bored Ape"). Used to look up the NFT in the wallet when no contract_address / token_id is provided. Omit when unknown.',required:!1},{name:"token_standard",type:"string",description:'NFT contract type ("ERC721" or "ERC1155"). Optional; the tool re-derives it from the wallet row.',required:!1},{name:"to_address",type:"string",description:'RECIPIENT EVM address (0x\u2026) \u2014 the wallet that will RECEIVE the NFT. Only set this when the user explicitly named a destination (e.g. "send to 0x\u2026", "to 0x\u2026", "g\u1EEDi \u0111\u1EBFn 0x\u2026", "cho 0x\u2026"). NEVER put the NFT collection / contract address here. Omit when unknown.',required:!1},{name:"amount",type:"string",description:"Number of editions to send. Only meaningful for ERC-1155; for ERC-721 the tool forces 1. Pass as a positive integer string. Omit when unknown \u2014 defaults to 1.",required:!1}];async buildParameters(e,t){let n=this.requireChain(e,t)??void 0,r=t?.walletAddress,o=this.normaliseAddress(e.contract_address),s=this.normaliseTokenId(e.token_id),a=this.normaliseString(e.nft_name),i=this.normaliseAddress(e.to_address),l=this.normaliseString(e.token_standard),u=this.normaliseAmountInt(e.amount);if(!o&&!s&&!a)return{error:"no_nft_specified",_instructions:"The user wants to send an NFT but did not name one. In their language, ask them which NFT they would like to send \u2014 by name, by collection contract address, or by token id. Do NOT open the form. Do NOT mention tool names, UI, or forms."};if(!this.moralis||!r)return this.paramsFromInputs({contract:o,tokenId:s,toAddress:i,standardArg:l,nftName:a,amountInt:u});let d=await this.moralis.getWalletNFTs({address:r,chain:n,limit:100,excludeSpam:!1}),m=d.success?d.data?.result??[]:[],p=this.matchNfts(m,{contract:o,tokenId:s,nftName:a});if(p.length===0)return{error:"nft_not_owned",_instructions:`The user asked to send "${a??(o&&s?`#${s}`:o??`#${s??""}`)}" but the wallet does not hold a matching NFT on this chain. Tell them, in their language, that they do not own that NFT here and ask them to pick another one (by name, contract, or token id). Do NOT open the form. Do NOT mention tool names, UI, or forms.`};if(p.length===1)return this.paramsFromNft(p[0],{toAddress:i,amountInt:u});let f=`Please select the NFT you would like to send from the list and proceed via ${this.url?`[this website](${this.url})`:"this website"}`;return{error:"multiple_nfts_matched",message:f,_instructions:`This is a fixed notice. Output it to the user EXACTLY as written below \u2014 in English, without translating, rephrasing, summarising, or adding/removing any words. Preserve the Markdown link verbatim (do not change, wrap, or drop the URL or the linked words). Do NOT open the form. Do NOT mention tool names, UI, or forms. Reply with only this message:
|
|
177
|
+
Which token do you want to receive?`;return{actionButtons:k,_instructions:`The user wants to swap ${r} into another token. The trending tokens are on ${s?D(s):"the connected chain"}. Reply in the user's language using the pattern 'state intent \u2192 ask to choose'. Start with a one-line header that states the user's intent (e.g., 'B\u1EA1n mu\u1ED1n swap ${r} sang token kh\xE1c \u2014 \u0111\xE2y l\xE0 c\xE1c token \u0111ang trending tr\xEAn <chain name>:' / '\u{1F4C8} You want to swap ${r} \u2014 here are the tokens currently trending on <chain name>:'), then output EXACTLY the following list, preserving the line breaks, ordering, prices, and percentages verbatim (translate only the wording, never the numbers). The list already ends with a 'choose' prompt. Do NOT add or remove tokens, and do NOT mention tool names, UI, or forms:
|
|
178
|
+
|
|
179
|
+
${b}`}}async checkFromBalance(e){let{args:t,userContext:n,chain:r,fromContract:o,fromAmount:s,derivedFromTo:a}=e,i=n.walletAddress,c=await this.readFromBalance(i,o,r),u=e.fromSym??c?.symbol??"the selected token";if(!c){this.dbg("checkFromBalance: NOT held \u2192 source picker fallback",{fromSym:u});let f=await this.buildSourcePicker(t,n);return"actionButtons"in f?{error:"no_from_balance",actionButtons:f.actionButtons,_instructions:`The user wanted to swap ${u}, but their wallet holds no ${u} on this chain. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them they don't have any ${u} on this chain (e.g., 'B\u1EA1n kh\xF4ng c\xF3 ${u} tr\xEAn m\u1EA1ng n\xE0y.'), then ask them to choose one of the tokens they already hold to swap from (e.g., 'H\xE3y ch\u1ECDn token b\u1EA1n mu\u1ED1n swap t\u1EEB danh s\xE1ch b\xEAn d\u01B0\u1EDBi.' / 'Please choose a token to swap from the options below.'). Do NOT list the tokens in text, do NOT invent a balance, and do NOT mention tool names, UI, or forms.`}:f}if(!s||e.amountSizedToBalance)return null;let d=Number(s);if(!Number.isFinite(d)||d<=c.balanceNum)return null;let p=this.trimAmount(c.balanceNum),m=this.percentSpendButtons({balanceFormatted:c.balanceFormatted,balanceNum:c.balanceNum,fromSym:u,toSym:typeof t.to_symbol=="string"?t.to_symbol.trim():c.symbol??"the destination token",swapToTemplate:typeof t.swap_to_prompt_template=="string"?t.swap_to_prompt_template:""}),g=a?`Receiving ${a} of the destination token needs about ${s} ${u}, but they only have ${p} ${u} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them this would cost about ${s} ${u}, which is more than their balance of ${p} ${u}, then ask them to choose a smaller amount \u2014 `:`The user wants to swap ${s} ${u}, but they only have ${p} ${u} on this chain \u2014 not enough. Reply in the user's language using the pattern 'state situation \u2192 ask to choose': first tell them ${s} ${u} exceeds their balance of ${p} ${u}, then ask them to choose a smaller amount \u2014 `;return{error:"insufficient_from_balance",actionButtons:m,_instructions:g+'invite them to choose one of the options below (sized to their balance) or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT invent numbers, and do NOT mention tool names, UI, buttons, or forms.'}}async readFromBalance(e,t,n){if(!this.moralis)return null;let r=await this.moralis.getWalletTokenBalances({address:e,chain:n,excludeSpam:!0,excludeUnverifiedContracts:!0}),o=r.success?r.data?.result??[]:[];this.dbg("readFromBalance",{fromAddr:t,chain:n,ok:r.success,count:o.length});let s=t.toLowerCase(),a=o.find(c=>s==="native"?c.native_token===!0:c.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 c=await It(n,"swap"),u=Dt(i,c,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 c=i===1?this.cleanAmountString(t):this.trimAmount(n*i);return{label:`${Math.round(i*100)}%`,prompt:this.buildSwapToPrompt(s,r,o,c,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),c=e.fromSym??i?.symbol;if(!i||!c)return null;let u=this.percentSpendButtons({balanceFormatted:i.balanceFormatted,balanceNum:i.balanceNum,fromSym:c,toSym:o,swapToTemplate: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 using the pattern 'state intent + balance \u2192 ask to choose': first state what the user wants and their spendable ${c} balance (e.g., 'B\u1EA1n mu\u1ED1n swap ${c} sang ${o}, hi\u1EC7n c\xF3 ${d} ${c} c\xF3 th\u1EC3 d\xF9ng.' / 'You want to swap ${c} to ${o}; you have ${d} ${c} available.'), then ask how much they want to swap and invite them to choose one of the options below or type the amount they want. CRITICAL: clickable percentage buttons are rendered separately below your message \u2014 your text must NOT contain any "%", percentage values, or a list of amounts (no "25%", "50%", "75%", "100%"). Do NOT mention tool names, UI, buttons, or forms.`}}trimAmount(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(8))):"0"}toPlainDecimal(e){let t=String(e);if(!/e/i.test(t))return t;let n=e<0,[r,o]=Math.abs(e).toString().split("e"),s=Number(o),[a,i=""]=r.split("."),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}buildSwapFromPrompt(e,t){return(e&&e.includes("{token}")?e:"swap {token}").replace(/\{token\}/g,t).replace(/\s+/g," ").trim()}buildSwapToPrompt(e,t,n,r,o){return(e&&e.includes("{from}")&&e.includes("{to}")?e:"swap {from_amount} {from} to {to_amount} {to}").replace(/\{from\}/g,t).replace(/\{to\}/g,n).replace(/\{from_amount\}/g,r??"").replace(/\{to_amount\}/g,o??"").replace(/\s+/g," ").trim()}formatPercent(e){return Number.isFinite(e)?this.toPlainDecimal(parseFloat(e.toPrecision(6))):"0"}formatPrice(e){if(e>=1)return e.toLocaleString("en-US",{minimumFractionDigits:2,maximumFractionDigits:2});if(e>=.01)return e.toFixed(4);let t=e.toFixed(12),n=t.match(/^0\.0*([1-9]\d{0,3})/);return n?t.slice(0,(n[0].length||0)+0):t}};var Ft=class extends ae{name="open-send-nft-form";actionType="send_nft";component="SendNftForm";userInputFields=[{key:"to_address",label:"the recipient address"}];url;constructor(e,t){super(e);let n=t?.url?.trim();this.url=n||void 0}description=`Open the SEND NFT form. Use for ANY NFT send/transfer intent: "send NFT", "transfer my Bored Ape", "send token id 1234 to 0x\u2026", "g\u1EEDi NFT". Call this tool EVEN IF the user did not name a contract, token id, NFT name, or recipient \u2014 the tool resolves missing identifiers from the wallet's NFT holdings and emits picker buttons when more than one match exists. Do NOT use this for ERC-20s (use open-send-token-form) or the native coin (open-send-native-form).`;parameters=[{name:"chain",type:"string",description:"Hex chain id. Defaults to connected chain.",required:!1},{name:"contract_address",type:"string",description:'NFT COLLECTION contract address (0x\u2026) \u2014 identifies WHICH NFT collection to send from. Only set this when the user explicitly named the collection/contract (e.g. "contract 0x\u2026", "collection 0x\u2026", "from 0x\u2026"). NEVER put the recipient address here. Omit when unknown.',required:!1},{name:"token_id",type:"string",description:'NFT token id as a decimal string (e.g. "1234"). Omit when unknown.',required:!1},{name:"nft_name",type:"string",description:'Free-form NFT or collection name as the user said it (e.g. "Bored Ape"). Used to look up the NFT in the wallet when no contract_address / token_id is provided. Omit when unknown.',required:!1},{name:"token_standard",type:"string",description:'NFT contract type ("ERC721" or "ERC1155"). Optional; the tool re-derives it from the wallet row.',required:!1},{name:"to_address",type:"string",description:'RECIPIENT EVM address (0x\u2026) \u2014 the wallet that will RECEIVE the NFT. Only set this when the user explicitly named a destination (e.g. "send to 0x\u2026", "to 0x\u2026", "g\u1EEDi \u0111\u1EBFn 0x\u2026", "cho 0x\u2026"). NEVER put the NFT collection / contract address here. Omit when unknown.',required:!1},{name:"amount",type:"string",description:"Number of editions to send. Only meaningful for ERC-1155; for ERC-721 the tool forces 1. Pass as a positive integer string. Omit when unknown \u2014 defaults to 1.",required:!1}];async buildParameters(e,t){let n=this.requireChain(e,t)??void 0,r=t?.walletAddress,o=this.normaliseAddress(e.contract_address),s=this.normaliseTokenId(e.token_id),a=this.normaliseString(e.nft_name),i=this.normaliseAddress(e.to_address),c=this.normaliseString(e.token_standard),u=this.normaliseAmountInt(e.amount);if(!o&&!s&&!a)return{error:"no_nft_specified",_instructions:"The user wants to send an NFT but did not name one. In their language, ask them which NFT they would like to send \u2014 by name, by collection contract address, or by token id. Do NOT open the form. Do NOT mention tool names, UI, or forms."};if(!this.moralis||!r)return this.paramsFromInputs({contract:o,tokenId:s,toAddress:i,standardArg:c,nftName:a,amountInt:u});let d=await this.moralis.getWalletNFTs({address:r,chain:n,limit:100,excludeSpam:!1}),p=d.success?d.data?.result??[]:[],m=this.matchNfts(p,{contract:o,tokenId:s,nftName:a});if(m.length===0){let h=a??(o&&s?`#${s}`:o??`#${s??""}`),y=D(n);return{error:"nft_not_owned",_instructions:`The user asked to send "${h}" but the wallet does not hold a matching NFT on ${y}. Tell them, in their language, that they do not own that NFT on ${y} and ask them to pick another one (by name, contract, or token id). Refer to the chain by this name \u2014 never as a hex id. Do NOT open the form. Do NOT mention tool names, UI, or forms.`}}if(m.length===1)return this.paramsFromNft(m[0],{toAddress:i,amountInt:u});let f=`Please select the NFT you would like to send from the list and proceed via ${this.url?`[this website](${this.url})`:"this website"}`;return{error:"multiple_nfts_matched",message:f,_instructions:`This is a fixed notice. Output it to the user EXACTLY as written below \u2014 in English, without translating, rephrasing, summarising, or adding/removing any words. Preserve the Markdown link verbatim (do not change, wrap, or drop the URL or the linked words). Do NOT open the form. Do NOT mention tool names, UI, or forms. Reply with only this message:
|
|
184
180
|
|
|
185
|
-
`+f}}normaliseString(e){if(typeof e!="string")return;let t=e.trim();return t||void 0}normaliseTokenId(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0?String(Math.trunc(e)):this.normaliseString(e)}normaliseAmountInt(e){if(typeof e=="number"&&Number.isFinite(e)&&e>=1)return Math.trunc(e);if(typeof e=="string"&&e.trim()){let t=parseInt(e,10);if(Number.isFinite(t)&&t>=1)return t}}nftDisplayName(e){return(e.normalized_metadata?.name||e.name||`#${e.token_id}`).toString()}matchNfts(e,t){let n=t.contract?t.contract.toLowerCase():void 0,r=t.nftName?t.nftName.toLowerCase():void 0;return e.filter(o=>{if(n&&o.token_address.toLowerCase()!==n||t.tokenId&&o.token_id!==t.tokenId)return!1;if(r&&!n&&!t.tokenId){let s=(o.normalized_metadata?.name??"").toLowerCase(),a=(o.name??"").toLowerCase();if(!s.includes(r)&&!a.includes(r))return!1}return!0})}paramsFromInputs(e){let t={};return e.contract&&(t.contract_address=e.contract),e.tokenId&&(t.token_id=e.tokenId),e.toAddress&&(t.to_address=e.toAddress),e.standardArg&&(t.token_standard=e.standardArg.toUpperCase()),e.nftName&&(t.nft_name=e.nftName),e.amountInt&&(t.amount=String(e.amountInt)),t}paramsFromNft(e,t){let n=(e.contract_type||"").toUpperCase(),r=e.amount&&e.amount.trim()?e.amount.trim():"1",o=n==="ERC1155"?String(Math.min(t.amountInt??1,parseInt(r,10)||1)):"1",s={contract_address:e.token_address,token_id:e.token_id,token_standard:n||"ERC721",amount:o,maxAmount:r,nft_name:this.nftDisplayName(e)}
|
|
181
|
+
`+f}}normaliseString(e){if(typeof e!="string")return;let t=e.trim();return t||void 0}normaliseTokenId(e){return typeof e=="number"&&Number.isFinite(e)&&e>=0?String(Math.trunc(e)):this.normaliseString(e)}normaliseAmountInt(e){if(typeof e=="number"&&Number.isFinite(e)&&e>=1)return Math.trunc(e);if(typeof e=="string"&&e.trim()){let t=parseInt(e,10);if(Number.isFinite(t)&&t>=1)return t}}nftDisplayName(e){return(e.normalized_metadata?.name||e.name||`#${e.token_id}`).toString()}matchNfts(e,t){let n=t.contract?t.contract.toLowerCase():void 0,r=t.nftName?t.nftName.toLowerCase():void 0;return e.filter(o=>{if(n&&o.token_address.toLowerCase()!==n||t.tokenId&&o.token_id!==t.tokenId)return!1;if(r&&!n&&!t.tokenId){let s=(o.normalized_metadata?.name??"").toLowerCase(),a=(o.name??"").toLowerCase();if(!s.includes(r)&&!a.includes(r))return!1}return!0})}paramsFromInputs(e){let t={};return e.contract&&(t.contract_address=e.contract),e.tokenId&&(t.token_id=e.tokenId),e.toAddress&&(t.to_address=e.toAddress),e.standardArg&&(t.token_standard=e.standardArg.toUpperCase()),e.nftName&&(t.nft_name=e.nftName),e.amountInt&&(t.amount=String(e.amountInt)),t}paramsFromNft(e,t){let n=(e.contract_type||"").toUpperCase(),r=e.amount&&e.amount.trim()?e.amount.trim():"1",o=n==="ERC1155"?String(Math.min(t.amountInt??1,parseInt(r,10)||1)):"1",s={contract_address:e.token_address,token_id:e.token_id,token_standard:n||"ERC721",amount:o,maxAmount:r,nft_name:this.nftDisplayName(e)};t.toAddress&&(s.to_address=t.toAddress);let a=e.normalized_metadata?.image||e.media?.original_media_url||"";return a&&(s.nft_image=a),s}};var $t=class{messages=[];fullMessages=[];summary=null;maxMessages;constructor(e=50){this.maxMessages=e}add(e){this.messages.push(e),this.fullMessages.push(e)}addMany(e){this.messages.push(...e),this.fullMessages.push(...e)}addUIOnly(e){this.fullMessages.push(e)}rollbackLastWorkingMessage(){this.messages.pop()}getAll(){return[...this.messages]}getAllFull(){return[...this.fullMessages]}getRecent(e){return this.messages.slice(-e)}getConversation(){let e=[];return this.summary&&e.push({role:"system",content:`[Previous conversation summary]: ${this.summary}`,timestamp:0}),e.push(...this.messages),e}get length(){return this.messages.length}needsSummary(){return this.messages.filter(t=>t.role==="user").length>this.maxMessages}getSummary(){return this.summary}compact(e,t=10){this.summary=e,this.messages=this.messages.slice(-t)}compactWith(e,t){this.summary=e,this.messages=[...t]}clear(){this.messages=[],this.fullMessages=[],this.summary=null}serialise(){return{messages:[...this.messages],summary:this.summary,fullMessages:[...this.fullMessages]}}restore(e){Array.isArray(e)?(this.messages=[...e],this.fullMessages=[...e],this.summary=null):(this.messages=[...e.messages],this.summary=e.summary??null,this.fullMessages=e.fullMessages?[...e.fullMessages]:[...e.messages])}};var Wt=class{llm;constructor(e){this.llm=e}async summarize(e){let t=e.filter(s=>s.role==="user"||s.role==="assistant");if(t.length===0)return"";let n=t.map(s=>`${s.role==="user"?"User":"Assistant"}: ${s.content}`).join(`
|
|
186
182
|
`),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:
|
|
187
183
|
|
|
188
|
-
${n}`,timestamp:Date.now()}];return(await this.llm.chat(r)).text}};var
|
|
184
|
+
${n}`,timestamp:Date.now()}];return(await this.llm.chat(r)).text}};var Qs=`You are a strict FAQ matcher. Given a user query and a list of FAQ entries, determine which FAQ entries the user is SPECIFICALLY asking about.
|
|
189
185
|
|
|
190
186
|
RULES:
|
|
191
187
|
- Only match when the user's question is CLEARLY about the same topic as the FAQ entry.
|
|
@@ -200,13 +196,13 @@ RULES:
|
|
|
200
196
|
- Return ONLY the JSON array, no other text.
|
|
201
197
|
|
|
202
198
|
FAQ ENTRIES:
|
|
203
|
-
`,
|
|
204
|
-
`),r=[{role:"user",content:
|
|
199
|
+
`,Vt=class{entries=[];llm=null;constructor(e){e&&(this.entries=e)}setLLM(e){this.llm=e}setEntries(e){this.entries=e}addEntries(e){this.entries.push(...e)}getEntries(){return this.entries.slice()}get size(){return this.entries.length}async search(e,t=3){return this.entries.length===0?[]:this.llm?this.llmSearch(e,t):this.tokenSearch(e,t,.25)}async llmSearch(e,t){let n=this.entries.map((o,s)=>`[${s}] ${o.question}`).join(`
|
|
200
|
+
`),r=[{role:"user",content:Qs+n+`
|
|
205
201
|
|
|
206
|
-
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]),
|
|
202
|
+
USER QUERY: ${e}`,timestamp:0}];try{let a=(await this.llm.chat(r)).text.trim().match(/\[[\s\S]*\]/);if(!a)return[];let i=JSON.parse(a[0]),c=[];for(let u of i)u.index>=0&&u.index<this.entries.length&&u.score>=.6&&c.push({entry:this.entries[u.index],score:u.score});return c.sort((u,d)=>d.score-u.score),c.slice(0,t)}catch{return this.tokenSearch(e,t,.25)}}tokenSearch(e,t,n){let r=this.tokenize(e);if(r.size===0)return[];let o=[];for(let s of this.entries){let a=this.tokenize(s.question),i=this.jaccardSimilarity(r,a);i>=n&&o.push({entry:s,score:i})}return o.sort((s,a)=>a.score-s.score),o.slice(0,t)}tokenize(e){let t=e.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu," ").split(/\s+/).filter(n=>n.length>1);return new Set(t)}jaccardSimilarity(e,t){if(e.size===0||t.size===0)return 0;let n=0;for(let o of e)t.has(o)&&n++;let r=e.size+t.size-n;return r===0?0:n/r}};var tn=require("@upstash/vector"),Xs="https://perfect-octopus-59684-us1-vector.upstash.io",Js="ABkFMHBlcmZlY3Qtb2N0b3B1cy01OTY4NC11czFhZG1pbk56WXdOV0k0T0RndFpUaGtOeTAwWWpjMkxXRmtNRFV0WW1WaU9UaGtOMlV5T0ROaQ==",Re=class{index;namespace;minScore;defaultTopK;embedStrategy;fusionAlgorithm;debug;constructor(e={}){let t=e.url??(typeof process<"u"?process.env.UPSTASH_VECTOR_REST_URL:void 0)??Xs,n=e.token??(typeof process<"u"?process.env.UPSTASH_VECTOR_REST_TOKEN:void 0)??Js;this.index=new tn.Index({url:t,token:n}),this.namespace=e.namespace,this.minScore=e.minScore??1.3,this.defaultTopK=e.defaultTopK??3,this.embedStrategy=e.embedStrategy??"question",this.fusionAlgorithm=e.fusionAlgorithm==="RRF"?tn.FusionAlgorithm.RRF:tn.FusionAlgorithm.DBSF,this.debug=e.debug??!1}async search(e,t){let n=t??this.defaultTopK;if(!e.trim())return[];let r=this.namespace?this.index.namespace(this.namespace):this.index,o;try{o=await r.query({data:e,topK:n,includeMetadata:!0,fusionAlgorithm:this.fusionAlgorithm})}catch(a){return this.debug&&console.warn("[UpstashKnowledgeBase] query failed:",a),[]}let s=[];for(let a of o){if(a.score<this.minScore)continue;let i=a.metadata;!i?.question||!i?.answer||s.push({entry:{question:i.question,answer:i.answer},score:a.score})}return this.debug&&console.log(`[UpstashKnowledgeBase] query="${e}" \u2192 ${s.length}/${o.length} hit(s) above ${this.minScore}`+(s[0]?` (top: ${s[0].score.toFixed(3)})`:"")),s}async upsert(e){if(e.length===0)return{count:0};let t=this.namespace?this.index.namespace(this.namespace):this.index,n=await Promise.all(e.map(async r=>({id:r.id??await this.deriveId(r.question),data:this.buildEmbedText(r),metadata:{question:r.question,answer:r.answer,...r.metadata??{}}})));return await t.upsert(n),this.debug&&console.log(`[UpstashKnowledgeBase] upserted ${n.length} entr${n.length===1?"y":"ies"} (strategy=${this.embedStrategy})`),{count:n.length}}buildEmbedText(e){return this.embedStrategy==="question-and-answer"?`${e.question}
|
|
207
203
|
|
|
208
|
-
${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
|
|
209
|
-
`)}var
|
|
204
|
+
${e.answer}`:e.question}async delete(e){return e.length===0?{deleted:0}:{deleted:(await(this.namespace?this.index.namespace(this.namespace):this.index).delete(e)).deleted}}async reset(){this.namespace?await this.index.reset({namespace:this.namespace}):await this.index.reset()}async size(){let e=await this.index.info();return this.namespace?e.namespaces?.[this.namespace]?.vectorCount??0:e.vectorCount??0}async deriveId(e){let t=new TextEncoder().encode(e.trim().toLowerCase()),n=await globalThis.crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(n)).map(r=>r.toString(16).padStart(2,"0")).join("").slice(0,32)}};function ye(l){return l.filter(e=>e.role!=="tool"&&!(e.role==="assistant"&&e.toolCalls&&e.toolCalls.length>0)&&!e._intermediate)}function Ho(l,e){let t=[];for(let n=0;n<l.length;n++){let r=l[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<l.length;){let i=l[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 Se(l,e){if(l.length<=e)return[...l];let t=l.length-e;for(;t>0;){if(l[t].role==="tool"){t--;continue}break}let n=l.slice(t),r=0;for(;r<n.length&&n[r].role==="tool";)r++;return n.slice(r)}function Go(l,e){return Se(l,e)}function Ko(l){for(let e=l.length-1;e>=0;e--){let t=l[e];if(t.role==="assistant"&&!(t.toolCalls&&t.toolCalls.length>0))return t.subagents&&t.subagents.length>0?[...t.subagents]:[]}return[]}function Yo(l,e=5){let t=l.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,c=i.pagination;c&&typeof c.cursor=="string"&&c.cursor&&r.push({toolName:s.toolName??"tool",cursor:c.cursor});let u=i.transactions??i.result??i.transfers;if(Array.isArray(u))for(let d of u)typeof d.hash=="string"&&d.hash.startsWith("0x")?n.push(d.hash):typeof d.transaction_hash=="string"&&d.transaction_hash.startsWith("0x")&&n.push(d.transaction_hash)}if(n.length===0&&r.length===0)return"";let o=["[Tool data available from previous calls:]"];if(n.length>0&&o.push(`Transaction hashes (use EXACT values): ${n.slice(0,10).join(", ")}`),r.length>0){let s=r[r.length-1];o.push(`Pagination cursor from ${s.toolName}: ${s.cursor}`)}return o.join(`
|
|
205
|
+
`)}var Zs=`You are a ROUTER.
|
|
210
206
|
|
|
211
207
|
The incoming user query has ALREADY been contextualized upstream \u2014 it is a
|
|
212
208
|
self-contained, standalone question. Your ONLY job: decide which subagent(s)
|
|
@@ -252,29 +248,29 @@ The query may arrive in ANY language (English, Vietnamese, Japanese, Korean, Chi
|
|
|
252
248
|
The subagent descriptions are written in English, but they apply equally to the SAME intent expressed in
|
|
253
249
|
any language. Route by MEANING, never by matching English keywords or script \u2014 a wallet / token / NFT / pool /
|
|
254
250
|
swap / buy request is the same intent however it is phrased or whichever language it is written in. Token
|
|
255
|
-
symbols, contract addresses, and numbers are language-neutral.`,
|
|
251
|
+
symbols, contract addresses, and numbers are language-neutral.`,Ht=class{llm;historyMessages;debug;constructor(e,t){this.llm=e,this.historyMessages=t?.historyMessages??10,this.debug=t?.debug??!1}async route(e,t,n,r,o=[]){let s=n.map(b=>`### ${b.name} (domain: ${b.domain})
|
|
256
252
|
${b.description}`).join(`
|
|
257
253
|
|
|
258
254
|
`),a=[];r?.walletAddress&&a.push(`Connected wallet: ${r.walletAddress}`),r?.chain&&a.push(`Current chain: ${r.chain}`);let i=a.length>0?`
|
|
259
255
|
|
|
260
256
|
User context:
|
|
261
257
|
${a.join(`
|
|
262
|
-
`)}`:"",
|
|
258
|
+
`)}`:"",c=o.length>0?`
|
|
263
259
|
|
|
264
|
-
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=
|
|
265
|
-
[Router] history window (${d.length} msg${d.length===1?"":"s"}):`),console.log(`[Router] user query: "${e}"`));let
|
|
266
|
-
${s}${i}${
|
|
260
|
+
Previous turn handled by: ${o.join(", ")}. If this query is a natural continuation (same topic), prefer the same subagent(s). Override only if the query clearly belongs to a different domain.`:"",u=ye(t),d=Se(u,this.historyMessages);this.debug&&(console.log(`
|
|
261
|
+
[Router] history window (${d.length} msg${d.length===1?"":"s"}):`),console.log(`[Router] user query: "${e}"`));let p=[{role:"system",content:Zs,timestamp:0},...d,{role:"user",content:`Available subagents:
|
|
262
|
+
${s}${i}${c}
|
|
267
263
|
|
|
268
|
-
User query: ${e}`,timestamp:Date.now()}],
|
|
269
|
-
[Router] (${y}ms)`),console.log(` analysis : ${
|
|
270
|
-
[Router] dispatching ${e.assignments.length} subagent(s) in parallel\u2026`);let o=Date.now(),s=e.assignments.map(async i=>{let
|
|
264
|
+
User query: ${e}`,timestamp:Date.now()}],m=Date.now(),g=null,f="",h=null;for(let b=1;b<=2;b++){let T=await this.llm.chat(p);f=T.text;try{g=this.parseJSON(T.text);break}catch(A){h=A,b<2&&(this.debug&&console.log(`[Router] JSON parse failed (attempt ${b}) \u2014 re-prompting for JSON`),p.push({role:"assistant",content:T.text||"",timestamp:Date.now()}),p.push({role:"user",content:'That response was not valid JSON. Do NOT answer the user and do NOT ask any questions. Reply with ONLY the routing JSON object ({"analysis": "...", "assignments": [...]}) and nothing else.',timestamp:Date.now()}))}}let y=Date.now()-m;if(!g)return this.debug&&(console.log(`[Router] JSON parse failed after retry: ${h}`),console.log(`[Router] raw response: ${f.slice(0,300)}`)),{analysis:"Router could not parse response.",assignments:[]};let k=new Set(n.map(b=>b.name)),w=Array.isArray(g.assignments)?g.assignments.filter(b=>!!b&&typeof b.subagent=="string").filter(b=>k.has(b.subagent)).map(b=>({subagent:b.subagent,query:typeof b.query=="string"&&b.query?b.query:e,reasoning:typeof b.reasoning=="string"?b.reasoning:""})):[];if(this.debug){let b=w.length>0?w.map(T=>T.subagent).join(", "):"(none \u2014 direct answer)";if(console.log(`
|
|
265
|
+
[Router] (${y}ms)`),console.log(` analysis : ${g.analysis??""}`),console.log(` dispatch : ${b}`),w.length>0)for(let T of w)console.log(` \u2192 ${T.subagent}: "${T.query}"`),console.log(` reason: ${T.reasoning}`)}return{analysis:typeof g.analysis=="string"?g.analysis:"",assignments:w}}async dispatch(e,t,n,r){if(e.assignments.length===0)return[];this.debug&&console.log(`
|
|
266
|
+
[Router] dispatching ${e.assignments.length} subagent(s) in parallel\u2026`);let o=Date.now(),s=e.assignments.map(async i=>{let c=t.get(i.subagent);if(!c)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 c.run({query:i.query,reasoning:i.reasoning},n,r);if(this.debug){let p=d.toolResults.map(m=>m.toolName).join(", ")||"\u2014";console.log(`[Router] ${i.subagent} done in ${Date.now()-u}ms | tools: ${p}`)}return d}),a=await Promise.all(s);return this.debug&&console.log(`[Router] all subagents done in ${Date.now()-o}ms total`),a}parseJSON(e){let t=e.trim();t.startsWith("```")&&(t=t.replace(/^```(?:json)?\s*/,"").replace(/```\s*$/,"").trim());try{return JSON.parse(t)}catch(n){let r=t.match(/\{[\s\S]*\}/);if(r)return JSON.parse(r[0]);throw n}}};var ea=`
|
|
271
267
|
|
|
272
|
-
LANGUAGE-AGNOSTIC TOOL SELECTION: The user query may arrive in ANY language (English, Vietnamese, Japanese, Korean, Chinese, Thai, Spanish, Arabic, \u2026). Every intent\u2192tool mapping, verb, and example phrase in this prompt is ILLUSTRATIVE ONLY \u2014 written mostly in English for brevity, NOT a list of accepted words. Always pick the tool by the MEANING of the request, treating translated, transliterated, or mixed-language equivalents exactly like the English example (buy / send / swap / approve / show / search / trending / balance, etc. are the same intent in every language). Token symbols, contract addresses, and numbers are language-neutral \u2014 extract them verbatim. Never refuse, misroute, or fall back to plain text merely because the wording is not in English.`,
|
|
273
|
-
[${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,
|
|
268
|
+
LANGUAGE-AGNOSTIC TOOL SELECTION: The user query may arrive in ANY language (English, Vietnamese, Japanese, Korean, Chinese, Thai, Spanish, Arabic, \u2026). Every intent\u2192tool mapping, verb, and example phrase in this prompt is ILLUSTRATIVE ONLY \u2014 written mostly in English for brevity, NOT a list of accepted words. Always pick the tool by the MEANING of the request, treating translated, transliterated, or mixed-language equivalents exactly like the English example (buy / send / swap / approve / show / search / trending / balance, etc. are the same intent in every language). Token symbols, contract addresses, and numbers are language-neutral \u2014 extract them verbatim. Never refuse, misroute, or fall back to plain text merely because the wording is not in English.`,Y=class{name;description;domain;toolNames;systemPrompt;cortexFallbackTool;mustCallTool;llm;registry;maxToolCalls;historyMessages;debug;constructor(e){if(this.name=e.name,this.description=e.description,this.domain=e.domain,this.toolNames=new Set(e.toolNames),this.systemPrompt=e.systemPrompt+ea,this.cortexFallbackTool=e.cortexFallbackTool,this.mustCallTool=e.mustCallTool??!1,this.cortexFallbackTool&&!this.toolNames.has(this.cortexFallbackTool))throw new Error(`Subagent "${e.name}": cortexFallbackTool "${this.cortexFallbackTool}" must be in toolNames.`);this.llm=e.llm,this.registry=e.registry,this.maxToolCalls=e.options?.maxToolCalls??5,this.historyMessages=e.options?.historyMessages??10,this.debug=e.options?.debug??!1}getCard(){return{name:this.name,description:this.description,domain:this.domain}}getTools(){return this.registry.getDefinitions().filter(e=>this.toolNames.has(e.name))}async run(e,t,n){let r=this.getTools();if(this.debug&&(console.log(`
|
|
269
|
+
[${this.name}] query: "${e.query}"`),console.log(` [${this.name}] tools available: ${r.map(T=>T.name).join(", ")}`)),r.length===0)return{subagentName:this.name,steps:[],finalAnswer:`Subagent "${this.name}" has no registered tools available.`,toolResults:[],toolMessages:[]};let o=[],s=[],a=[],i=new Set,c=0,u=!1,d=[];n?.walletAddress&&d.push(`Connected wallet: ${n.walletAddress}`),n?.chain&&d.push(`Current chain: ${n.chain}`);let p=d.length>0?`User context:
|
|
274
270
|
${d.join(`
|
|
275
|
-
`)}`:"",
|
|
271
|
+
`)}`:"",m=e.language?`The user's current language is "${e.language}" (BCP-47). Write every user-facing string you produce \u2014 replies AND any localized tool arguments \u2014 in this language.`:"",g=[p,m,`Routed query: ${e.query}`,e.reasoning?`Router reasoning: ${e.reasoning}`:"","Use only the tools you own. If you already have enough information, respond directly in the user's language."].filter(Boolean).join(`
|
|
276
272
|
|
|
277
|
-
`),f=
|
|
273
|
+
`),f=Ho(t,this.toolNames),h=Se(f,this.historyMessages),y=[{role:"system",content:this.systemPrompt,timestamp:0},...h,{role:"user",content:g,timestamp:Date.now()}],k=6e3,w=this.maxToolCalls+2;for(let T=0;T<w;T++){let A=c<this.maxToolCalls,x=A?r:void 0,P=await this.llm.chat(y,x);if(P.toolCalls.length===0){if(c===0&&this.cortexFallbackTool&&A){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(c===0&&this.mustCallTool&&A&&!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 ${c} tool call(s), answer: "${P.text.slice(0,80)}\u2026"`),{subagentName:this.name,steps:o,finalAnswer:P.text,toolResults:s,toolMessages:a}}let M={role:"assistant",content:P.text||"",toolCalls:P.toolCalls,timestamp:Date.now()};y.push(M);let S=[];for(let C of P.toolCalls){if(!this.toolNames.has(C.toolName)){let Q=`Tool "${C.toolName}" is not owned by ${this.name}`;this.debug&&console.log(` [${this.name}] WARN: ${Q}`),o.push({phase:"observe",content:Q,timestamp:Date.now()}),y.push({role:"tool",content:Q,toolName:C.toolName,toolCallId:C.callId,timestamp:Date.now()});continue}let U=`${C.toolName}::${JSON.stringify(C.args)}`;if(i.has(U)){let Q=`Skipped duplicate call: ${C.toolName}`;this.debug&&console.log(` [${this.name}] SKIP duplicate: ${C.toolName}`),o.push({phase:"observe",content:Q,timestamp:Date.now()}),y.push({role:"tool",content:Q,toolName:C.toolName,toolCallId:C.callId,timestamp:Date.now()});continue}i.add(U),c++,this.debug&&console.log(` [${this.name}] call #${c}: ${C.toolName} ${JSON.stringify(C.args)}`),o.push({phase:"think",content:P.text||`Calling ${C.toolName}`,timestamp:Date.now()}),o.push({phase:"act",content:`Calling tool: ${C.toolName}`,toolCall:{toolName:C.toolName,args:C.args},timestamp:Date.now()});let G=Date.now(),X=await this.registry.execute(C.toolName,C.args,n);s.push(X);let j=X.success?JSON.stringify(X.data):`Error: ${X.error}`;if(this.debug){let Q=X.success?"OK":"ERR",xe=X.success?`${j.slice(0,120)}${j.length>120?"\u2026":""}`:X.error;console.log(` [${this.name}] ${Q} (${Date.now()-G}ms): ${xe}`)}o.push({phase:"observe",content:j,toolResult:X,timestamp:Date.now()});let ue={role:"tool",content:j,toolName:C.toolName,toolCallId:C.callId,timestamp:Date.now()};y.push(ue),S.push({...ue,content:j.length>k?j.slice(0,k)+"\u2026[truncated]":j})}S.length>0&&(a.push(M),a.push(...S))}this.debug&&console.log(` [${this.name}] budget exhausted after ${c} calls, forcing final answer`),y.push({role:"user",content:"Tool budget reached. Give your best answer now based on the data gathered.",timestamp:Date.now()});let b=await this.llm.chat(y);return{subagentName:this.name,steps:o,finalAnswer:b.text,toolResults:s,toolMessages:a}}};var ta=`You are the Wallet domain expert. You answer questions about a specific wallet's on-chain state.
|
|
278
274
|
|
|
279
275
|
SPECIALISED TOOLS (prefer these when intent matches):
|
|
280
276
|
- get-wallet-token-balances: token + native coin balances
|
|
@@ -325,7 +321,7 @@ PAGINATION:
|
|
|
325
321
|
pass the cursor from the LAST history response in conversation. Always tell the user
|
|
326
322
|
how many results were returned and whether more pages exist.
|
|
327
323
|
|
|
328
|
-
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 .`,
|
|
324
|
+
Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea .`,Cn=["get-wallet-token-balances","get-wallet-history","get-wallet-token-transfers","get-wallet-nft-transfers","get-wallet-net-worth","get-wallet-pnl-summary","get-wallet-pnl","get-wallet-approvals","get-wallet-defi-summary","get-wallet-defi-positions","get-wallet-defi-protocol-positions","get-transaction-by-hash","gemini-search-ai"];function nn(l,e,t){return new Y({name:"wallet-agent",domain:"wallet",description:["Wallet-scoped on-chain state. Specialised tools cover balances, net worth, transaction history,","single-tx lookup, ERC-20 / NFT transfers, PnL, DeFi positions, and approvals.","For wallet queries without a direct tool , this agent falls","back to a search-grounded Gemini model internally \u2014 it never refuses a wallet-scoped query","and always calls a tool.","NOT in scope: market-wide token data (\u2192 token-agent), NFT current holdings (\u2192 nft-agent)."].join(" "),toolNames:[...Cn],systemPrompt:ta,cortexFallbackTool:"gemini-search-ai",llm:l,registry:e,options:t})}var na=`You are the Token domain expert. You answer market-wide token questions, not wallet-specific ones.
|
|
329
325
|
|
|
330
326
|
SPECIALISED TOOLS (prefer these when intent matches):
|
|
331
327
|
- get-token-info: metadata (name, symbol, decimals, address, logo)
|
|
@@ -366,7 +362,7 @@ GENERAL RULES:
|
|
|
366
362
|
- Never repeat a tool call with identical arguments.
|
|
367
363
|
- Answer in the user's language. Do not mention tool names.
|
|
368
364
|
|
|
369
|
-
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. `,
|
|
365
|
+
Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea. `,Un=["get-token-info","get-token-holders","get-token-analytics","get-token-score","get-trending-tokens","get-top-gainers","gemini-search-ai"];function on(l,e,t){return new Y({name:"token-agent",domain:"token",description:["Market-wide token data. Specialised tools cover metadata, holders, analytics, risk score,","trending, and top gainers. For token category/theme queries (meme, AI, gaming, L2, RWA),","any token question without a direct tool, this agent falls back to a","search-grounded Gemini model internally \u2014 it never refuses a token query and always calls a tool.","NOT in scope: wallet balances or transfer history (\u2192 wallet-agent), NFT data (\u2192 nft-agent)."].join(" "),toolNames:[...Un],systemPrompt:na,cortexFallbackTool:"gemini-search-ai",llm:l,registry:e,options:t})}var oa=`You are the NFT domain expert. You answer questions about NFT holdings and metadata, not transfer history.
|
|
370
366
|
|
|
371
367
|
SPECIALISED TOOLS (prefer these when intent matches):
|
|
372
368
|
- get-wallet-nfts: user wants to view / browse their NFTs / holdings \u2014 returns a fixed website notice
|
|
@@ -418,7 +414,7 @@ GENERAL RULES:
|
|
|
418
414
|
- One tool per turn. Never repeat a tool call with identical arguments.
|
|
419
415
|
- Answer in the user's language. Do not mention tool names.
|
|
420
416
|
|
|
421
|
-
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.`,
|
|
417
|
+
Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea.`,Rn=["get-wallet-nfts","get-nft-metadata","get-nft-contract-info","open-send-nft-form","gemini-search-ai"];function rn(l,e,t){return new Y({name:"nft-agent",domain:"nft",description:["NFT holdings, metadata, and sending. Specialised tools cover NFT holdings, single-NFT metadata,","collection / contract info, and sending/transferring an NFT. For NFT category, theme, or market-wide","questions without a direct tool, this agent falls back to a search-grounded Gemini model internally \u2014","it never refuses an NFT query and always calls a tool.",'Handles the SEND / TRANSFER-an-NFT action ("send nft", "g\u1EEDi nft"); but past NFT transfer history (events) \u2192 wallet-agent.',"Key distinction: nft-agent = NFT holdings / details / send; wallet-agent = NFT TRANSFER-EVENT history."].join(" "),toolNames:[...Rn],systemPrompt:oa,cortexFallbackTool:"gemini-search-ai",llm:l,registry:e,options:t})}var ra=`You are the Crypto AI fallback expert powered by a search-grounded Gemini model.
|
|
422
418
|
|
|
423
419
|
You answer ANY blockchain / crypto / token / NFT / DeFi / on-chain question that does not have a
|
|
424
420
|
direct structured tool in the other agents. The underlying model has live web-search grounding \u2014
|
|
@@ -437,7 +433,7 @@ ROUTING:
|
|
|
437
433
|
RULES:
|
|
438
434
|
- ALWAYS call the tool \u2014 NEVER refuse, NEVER answer without calling it.
|
|
439
435
|
- NEVER retry with the same prompt if the call fails \u2014 return what you have.
|
|
440
|
-
- Answer in the user's language. Do NOT mention tool names.`,
|
|
436
|
+
- Answer in the user's language. Do NOT mention tool names.`,En=["gemini-search-ai"];function sn(l,e,t){return new Y({name:"ai-agent",domain:"ai",description:["Catch-all crypto / blockchain expert powered by a search-grounded Gemini model (live Google","Search). USE THIS AGENT whenever the query is about blockchain, crypto, tokens, NFTs, DeFi,","wallets, on-chain data, smart contracts, or any supported EVM chain BUT no other agent","(wallet-agent, token-agent, nft-agent, pool-agent) has a direct structured tool for it.","Scope examples: (1) token categories or themes \u2014 meme tokens, AI tokens, gaming tokens, L2 tokens, RWA;",'(2) protocol-specific questions \u2014 "how does X protocol work", "what happened to Y";',"(3) cross-contract or exploratory EVM analysis spanning many contracts;","(4) any open-ended on-chain question without a direct structured tool.","Prefer the specific agents when they clearly match (wallet balances \u2192 wallet-agent,","single-token info/analytics \u2192 token-agent, NFT holdings/metadata \u2192 nft-agent, DEX pools \u2192 pool-agent).","But for anything crypto-related that falls outside those, route HERE \u2014 do not return empty."].join(" "),toolNames:[...En],systemPrompt:ra,cortexFallbackTool:"gemini-search-ai",llm:l,registry:e,options:t})}var sa=`You are the Pool / DEX Liquidity domain expert.
|
|
441
437
|
|
|
442
438
|
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.
|
|
443
439
|
|
|
@@ -578,7 +574,7 @@ State awareness:
|
|
|
578
574
|
|
|
579
575
|
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.
|
|
580
576
|
|
|
581
|
-
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 .`,
|
|
577
|
+
Chain ids: 0x1 Ethereum \xB7 0xa Optimism \xB7 0x38 BSC \xB7 0x89 Polygon \xB7 0x2105 Base \xB7 0xa4b1 Arbitrum \xB7 0xa86a Avalanche \xB7 0xe708 Linea .`,Nn=["get-top-pools","get-pool-detail","search-pools","lookup-pool-by-address","open-add-liquidity-form","preview-add-liquidity","estimate_pool_yield"];function an(l,e,t){return new Y({name:"pool-agent",domain:"pool",description:["Handles questions about decentralized exchange liquidity pools \u2014 Uniswap V2/V3/V4 across EVM chains \u2014","AND walks the user through ADDING LIQUIDITY to a pool (provide LP / farm / stake / deposit / cung c\u1EA5p thanh kho\u1EA3n).","Scope: (1) top pools by chain \u2014 biggest pools ranked by TVL, volume, or transaction count;","(2) pool composition \u2014 which token pairs dominate liquidity on a given network;","(3) pool-level analytics \u2014 fee tier, rolling volume buckets (1d/1w/30d), tx count, both tokens;","(4) pool detail by token pair (and optional fee tier) \u2014 full stats for a single pool including TVL, 24h volume, fees, APR estimate, token reserves, and weekly volume history;","(5) pool search \u2014 find pools that match a free-text query (token symbol, token name, or pool address) on a given chain;","(6) single-address lookup \u2014 when the user pastes one 0x address without saying whether it is a pool or a token, resolve both;",'(7) YIELD ESTIMATION \u2014 estimate daily/weekly/monthly/yearly fee income for a given USD deposit into a specific pool ("if I put $1000 into USDC/WETH 0.05%, how much do I earn per day?", "b\u1ECF 500$ v\xE0o pool n\xE0y l\u1EDDi bao nhi\xEAu?");',"(8) ADD LIQUIDITY \u2014 open the AddLiquidityForm UI for a chosen pool, then build the unsigned mint transaction once the user confirms amount and price range.","NOT in scope: wallet-specific LP positions or DeFi holdings the user already owns (\u2192 wallet-agent),","token metadata or token-level market data (\u2192 token-agent), NFT data (\u2192 nft-agent).",'Key distinction from token-agent: pool-agent answers "which POOLS are biggest / most active" and handles "I want to add LP",','while token-agent answers "what is this TOKEN" and "what is the market doing for this TOKEN".'].join(" "),toolNames:[...Nn],systemPrompt:sa,llm:l,registry:e,options:t})}var jo=`You are the Uniswap V3 Subgraph DEX-pool expert.
|
|
582
578
|
|
|
583
579
|
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.
|
|
584
580
|
|
|
@@ -640,10 +636,10 @@ COINPOOL RANGE GUIDANCE (when using subgraph-coinpool-pairs):
|
|
|
640
636
|
FOLLOW-UPS / CONTINUATIONS:
|
|
641
637
|
- 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.
|
|
642
638
|
|
|
643
|
-
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
|
|
639
|
+
NEVER fabricate pool addresses, token prices, APRs, or position ids. Every value must come from a tool call this turn or the immediately preceding turn.`;function aa(l){return l?`${jo}
|
|
644
640
|
|
|
645
641
|
ADD-LIQUIDITY LINK:
|
|
646
|
-
- 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: ${
|
|
642
|
+
- If the user mentions adding liquidity / adding a pool / adding a position (including phrases like "add liquidity", "add pool", "add position", "create pool", "create pair", "th\xEAm thanh kho\u1EA3n", "th\xEAm pool", "th\xEAm position"), keep your normal answer content and append this exact link at the end of the response: ${l}`:jo}var zo=["subgraph-search-pools","subgraph-trending-pools","subgraph-pool-by-address","subgraph-pool-by-position-id","subgraph-position-detail","subgraph-coinpool-pairs"];function eo(l,e,t){return new Y({name:"pool-subgraph-agent",domain:"pool",description:["Answers questions about Uniswap V3 liquidity pools using The Graph subgraphs as the primary source","(APR is enriched from DefiLlama Yields; concentrated-liquidity price ranges come from CoinPool).","Scope: (1) pool search by token symbols on one or more EVM chains, with TVL/APR/fee filters and sort by tvl/volume/apr/fee/liquidity;","(2) trending pools ranked by 24h volume on a chain;","(3) pool detail for a specific 0x pool address;","(4) lookup the underlying pool for a Uniswap V3 NFT position id (numeric, e.g. 962961);","(5) full position detail (deposits, withdrawals, collected fees) for a numeric position id;","(6) CoinPool concentrated-liquidity price-range candidates with per-range APR for a token pair.",'NOT in scope: wallet-wide LP holdings ("my positions"), sending/transferring LP NFTs, add-liquidity action flows,',"token metadata (\u2192 token-agent), NFT data (\u2192 nft-agent).","Prefer this subagent over pool-agent when the deployment is configured to use Subgraph data (the two are mutually exclusive at config time)."].join(" "),toolNames:[...zo],systemPrompt:aa(t?.linkAddLiquidity),llm:l,registry:e,options:t})}var ia=`Call exactly ONE tool every turn \u2014 ALWAYS, even for a bare verb with no details ("send token", "buy", "approve"). Never reply in text and never ask the user for missing fields (token, amount, recipient): the tool opens a picker/form that collects them.
|
|
647
643
|
|
|
648
644
|
Tools:
|
|
649
645
|
- open-send-native-form \u2014 native coin (ETH/BNB/MATIC/\u2026)
|
|
@@ -664,7 +660,7 @@ NFT sends are NOT handled here \u2014 they belong to the nft-agent.
|
|
|
664
660
|
|
|
665
661
|
Skip the tool ONLY if: walletAddress missing (ask to connect) \xB7 user explicitly names a chain different from userContext.chain (ask to switch).
|
|
666
662
|
|
|
667
|
-
After the tool returns: 1 short sentence in the user's language. Don't list missing fields. Don't mention tool names.`,
|
|
663
|
+
After the tool returns: 1 short sentence in the user's language. Don't list missing fields. Don't mention tool names.`,In=["open-send-native-form","open-send-token-form","open-buy-token-form","open-swap-token-form","open-approve-token-form"];function ln(l,e,t){return new Y({name:"wallet-action-agent",domain:"wallet-action",description:["User wants to PERFORM an on-chain action (send, buy, swap, approve).","This agent picks the matching open-*-form tool which emits a pre-filled form (or, for buy/swap, a confirm panel) for the FE to render.","The FE \u2014 not this agent \u2014 builds and submits the tx after the user confirms.","NOT in scope: read-only wallet/token/NFT queries, sending/transferring an NFT (\u2192 nft-agent), liquidity provisioning."].join(" "),toolNames:[...In],systemPrompt:ia,mustCallTool:!0,llm:l,registry:e,options:t})}function Dn(l,e,t,n){let r=(c,u)=>{let d=n?.[c];return d===void 0?u:d},o=r("pool-subgraph",!1),s=n?.pool===!0,a=o?s:r("pool",!0),i=[];return r("wallet",!0)&&i.push(nn(l,e,t)),r("wallet-action",!0)&&i.push(ln(l,e,t)),r("token",!0)&&i.push(on(l,e,t)),r("nft",!0)&&i.push(rn(l,e,t)),a&&i.push(an(l,e,t)),o&&i.push(eo(l,e,t)),r("ai",!0)&&i.push(sn(l,e,t)),i}var la=`You are a response synthesizer. Given the user's question and tool execution results, produce a clear, helpful final answer.
|
|
668
664
|
|
|
669
665
|
Rules:
|
|
670
666
|
- Be concise but complete.
|
|
@@ -673,12 +669,12 @@ Rules:
|
|
|
673
669
|
- If tools returned errors, explain honestly what happened.
|
|
674
670
|
- Respond in the same language as the user's message.
|
|
675
671
|
- Do NOT mention internal tool names, JSON structures, or technical implementation details.
|
|
676
|
-
`,
|
|
677
|
-
`),a=
|
|
672
|
+
`,Gt=class{llm;constructor(e){this.llm=e}async synthesise(e,t,n,r){if(!t.steps.some(m=>m.phase==="act")&&t.finalAnswer)return Qo(t.finalAnswer);let s=t.steps.filter(m=>m.phase==="observe").map((m,g)=>`[Result ${g+1}]: ${m.content}`).join(`
|
|
673
|
+
`),a=ye(n),i=Se(a,8),c=(r||"").trim().toLowerCase(),u=c?`IMPORTANT: Reply in BCP-47 language "${c}" (the language the user actually typed). Tool results may contain text in other languages \u2014 ignore that and reply ONLY in "${c}".`:`IMPORTANT: The user wrote in the language of this message: "${e}". Reply in that exact language, ignoring any other languages that appear inside tool results.`,d=[{role:"system",content:la,timestamp:0},...i,{role:"user",content:[`User question: ${e}`,"",`Agent reasoning: ${t.finalAnswer}`,"",s?`Tool results:
|
|
678
674
|
${s}`:"","",u].filter(Boolean).join(`
|
|
679
|
-
`),timestamp:Date.now()}],
|
|
675
|
+
`),timestamp:Date.now()}],p=await this.llm.chat(d);return Qo(p.text)}};function Qo(l){return l&&l.replace(/^\[[a-z0-9-]+-agent\]\s*/i,"").replace(/\n\n\[[a-z0-9-]+-agent\]\s*/gi,`
|
|
680
676
|
|
|
681
|
-
`)}var
|
|
677
|
+
`)}var ca=`You are a CONTEXTUALIZER.
|
|
682
678
|
|
|
683
679
|
Your ONLY job: read the conversation history + the user's latest message,
|
|
684
680
|
and produce a SELF-CONTAINED version of that message.
|
|
@@ -800,7 +796,7 @@ When the user references "this transaction", "that tx", "the transaction above",
|
|
|
800
796
|
- Scan the conversation history for tool results (role: "tool") that contain transaction data.
|
|
801
797
|
- Extract the EXACT hash value (starts with "0x", 66 chars) from the most recent such result.
|
|
802
798
|
- Include it verbatim in rewrittenQuery. NEVER invent or modify a hash.
|
|
803
|
-
- If no hash is found in history, set rewrittenQuery to the original message verbatim so the subagent knows to fetch it.`,
|
|
799
|
+
- If no hash is found in history, set rewrittenQuery to the original message verbatim so the subagent knows to fetch it.`,ua=`You detect the language of a single user message.
|
|
804
800
|
|
|
805
801
|
Output STRICT JSON, no prose, no code fences:
|
|
806
802
|
{"language": "<BCP-47 tag>"}
|
|
@@ -828,17 +824,17 @@ Examples:
|
|
|
828
824
|
"\uC548\uB155\uD558\uC138\uC694" \u2192 {"language":"ko"}
|
|
829
825
|
"\u043F\u0440\u0438\u0432\u0435\u0442" \u2192 {"language":"ru"}
|
|
830
826
|
"\u0E2A\u0E27\u0E31\u0E2A\u0E14\u0E35" \u2192 {"language":"th"}
|
|
831
|
-
"123" \u2192 {"language":""}`,
|
|
827
|
+
"123" \u2192 {"language":""}`,Ln=class{llm;historyMessages;debug;constructor(e,t){this.llm=e,this.historyMessages=t?.historyMessages??8,this.debug=t?.debug??!1}async rewrite(e,t,n=[]){let r=t.some(k=>k.role==="user"||k.role==="assistant"),o=Yo(t),s=ye(t),a=Se(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 c=i.length>0?`
|
|
832
828
|
|
|
833
829
|
${i.join(`
|
|
834
830
|
|
|
835
|
-
`)}`:"",u=Date.now(),d=[{role:"system",content:
|
|
836
|
-
${e}${
|
|
831
|
+
`)}`:"",u=Date.now(),d=[{role:"system",content:ca,timestamp:0},...a,{role:"user",content:`Latest user message to contextualize:
|
|
832
|
+
${e}${c}
|
|
837
833
|
|
|
838
|
-
Return JSON only.`,timestamp:Date.now()}],
|
|
839
|
-
[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 ee=c=>({reducer:(e,t)=>t,default:c}),Ys=N.Annotation.Root({userMessage:(0,N.Annotation)(),doSuggest:(0,N.Annotation)(ee(()=>!0)),overrideLang:(0,N.Annotation)(ee(()=>"")),conversationMessages:(0,N.Annotation)(ee(()=>[])),lastSubagents:(0,N.Annotation)(ee(()=>[])),messageId:(0,N.Annotation)(ee(()=>"")),rewrite:(0,N.Annotation)(ee(()=>null)),effectiveQuery:(0,N.Annotation)(ee(()=>"")),turnLanguage:(0,N.Annotation)(ee(()=>"")),kbContext:(0,N.Annotation)(ee(()=>"")),decision:(0,N.Annotation)(ee(()=>null)),assignment:(0,N.Annotation)(ee(()=>null)),assignmentIndex:(0,N.Annotation)(ee(()=>0)),subResults:(0,N.Annotation)({reducer:(c,e)=>c.concat(e),default:()=>[]}),response:(0,N.Annotation)(ee(()=>null))});function $o(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!==""&&!zn(h)?"unsupportedChain":m.map((f,g)=>new N.Send("subagent",{...d,assignment:f,assignmentIndex:g}))};return new N.StateGraph(Ys).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 qo=require("js-sha3");function An(){let c=Date.now().toString(16),e=Math.floor(Math.random()*65535).toString(16).padStart(4,"0");return(c+e).slice(-8)}var js="You are a helpful AI assistant. Answer the user accurately and concisely. Respond in the same language as the user.",_n=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,Fn(e.rpcUrls),this.llm=new ce(e.llm),this.registry=new De,this.history=new It(e.maxHistoryMessages??50),this.summarizer=new Dt(this.llm),this.synthesizer=new Bt(this.llm),this.systemPrompt=e.systemPrompt??js,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 Lt(e.knowledgeBase),this.kbEntries.setLLM(this.llm);let t=e.vectorKB;t?.enabled?(t.provider?this.kb=t.provider:(this.vectorKB=new Ae({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 He(u)),this.registry.register(new Oe(u)),this.registry.register(new Ge(u)),this.registry.register(new Ke(u)),this.registry.register(new Ye(u)),this.registry.register(new $e(u)),this.registry.register(new je(u)),this.registry.register(new ze(u)),this.registry.register(new et(u)),this.registry.register(new Xe(u)),this.registry.register(new Je(u)),this.registry.register(new Ze(u)),this.registry.register(new Qe(u)),this.registry.register(new We(u)),this.registry.register(new Ve(u)),this.registry.register(new qe(u)),this.registry.register(new Fe(u))}let r=e.nftLink;this.registry.register(new tt({url:r})),this.registry.register(new nt({url:r})),this.registry.register(new ot({url:r})),this.registry.register(new rt(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 st(u)),this.registry.register(new at(u)),this.registry.register(new it(u)),this.registry.register(new lt(u)),this.registry.register(new bt(u));let d=u?{isProduction:u.isProduction}:void 0;this.registry.register(new ft({baseUrl:u?.baseUrl,pool:d,minProvideUsd:u?.minProvideUsd})),this.registry.register(new yt({pool:d}))}if(o&&e.subgraph!==!1){let u=typeof e.subgraph=="object"?e.subgraph:void 0;this.registry.register(new wt(u)),this.registry.register(new kt(u)),this.registry.register(new Tt(u)),this.registry.register(new vt(u)),this.registry.register(new St(u)),this.registry.register(new xt(u))}if(e.subagents?.["wallet-action"]!==!1){let u=e.moralis===!1?void 0:typeof e.moralis=="object"?e.moralis:{};this.registry.register(new _t(u)),this.registry.register(new Ct(u)),this.registry.register(new Nt(u,{url:r})),this.registry.register(new Ut(u)),this.registry.register(new Rt(u,{debug:this.debug})),this.registry.register(new Et(u,{debug:this.debug}))}this.router=new Mt(this.llm,{debug:this.debug}),this.rewriter=new Pn(this.llm,{debug:this.debug});let i=typeof e.subgraph=="object"?e.subgraph:void 0,l=xn(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=$o(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:An()};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=An(),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,qo.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(`
|
|
834
|
+
Return JSON only.`,timestamp:Date.now()}],p=[{role:"system",content:ua,timestamp:0},{role:"user",content:e,timestamp:Date.now()}],[m,g]=await Promise.allSettled([this.llm.chat(d),this.llm.chat(p)]),f={};if(m.status==="fulfilled")try{f=this.parseJSON(m.value.text)}catch(k){this.debug&&console.log(`[QueryRewriter] rewrite parse fail: ${k}`)}else this.debug&&console.log(`[QueryRewriter] rewrite call fail: ${m.reason}`);let h="";if(g.status==="fulfilled")try{let k=this.parseJSON(g.value.text);typeof k.language=="string"&&(h=k.language.trim().toLowerCase())}catch(k){this.debug&&console.log(`[QueryRewriter] detect parse fail: ${k}`)}else this.debug&&console.log(`[QueryRewriter] detect call fail: ${g.reason}`);let y={isFollowUp:r&&typeof f.isFollowUp=="boolean"?f.isFollowUp:!1,rewrittenQuery:typeof f.rewrittenQuery=="string"&&f.rewrittenQuery.trim()?f.rewrittenQuery.trim():e,topicChanged:r&&typeof f.topicChanged=="boolean"?f.topicChanged:!1,reasoning:typeof f.reasoning=="string"?f.reasoning:"",language:h,mentionedChain:typeof f.mentionedChain=="string"?f.mentionedChain.trim():"",needsWebSearch:f.needsWebSearch===!0};if(this.debug){let k=Date.now()-u,w=y.rewrittenQuery!==e;console.log(`
|
|
835
|
+
[QueryRewriter] (${k}ms)`),console.log(` original : "${e}"`),console.log(` rewritten : "${y.rewrittenQuery}"${w?"":" (unchanged)"}`),console.log(` followUp : ${y.isFollowUp} topicChanged: ${y.topicChanged}`),y.reasoning&&console.log(` reasoning : ${y.reasoning}`),console.log(` language : ${y.language}`),console.log(` webSearch : ${y.needsWebSearch}`)}return y}parseJSON(e){let t=e.trim();t.startsWith("```")&&(t=t.replace(/^```(?:json)?\s*/,"").replace(/```\s*$/,""));let n=t.match(/\{[\s\S]*\}/);return n&&(t=n[0]),JSON.parse(t)}};var L=require("@langchain/langgraph");var oe=l=>({reducer:(e,t)=>t,default:l}),da=L.Annotation.Root({userMessage:(0,L.Annotation)(),doSuggest:(0,L.Annotation)(oe(()=>!0)),overrideLang:(0,L.Annotation)(oe(()=>"")),conversationMessages:(0,L.Annotation)(oe(()=>[])),lastSubagents:(0,L.Annotation)(oe(()=>[])),messageId:(0,L.Annotation)(oe(()=>"")),rewrite:(0,L.Annotation)(oe(()=>null)),effectiveQuery:(0,L.Annotation)(oe(()=>"")),turnLanguage:(0,L.Annotation)(oe(()=>"")),kbContext:(0,L.Annotation)(oe(()=>"")),decision:(0,L.Annotation)(oe(()=>null)),assignment:(0,L.Annotation)(oe(()=>null)),assignmentIndex:(0,L.Annotation)(oe(()=>0)),subResults:(0,L.Annotation)({reducer:(l,e)=>l.concat(e),default:()=>[]}),response:(0,L.Annotation)(oe(()=>null))});function Xo(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,g=await l.searchKB(m),f=l.formatKBContext(g),h=await l.tryAnswerFromKB(d.userMessage,g,f,d.messageId);return h?{kbContext:f,response:{...h,messageId:d.messageId,rewrite:p}}:{kbContext:f}},r=async d=>({decision:await 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),g=await l.synthesise(d.userMessage,m,l.synthesiserHistory(),d.turnLanguage||void 0),f=p.map(b=>b.subagentName),h=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:g,subagents:f,uiActions:h,actionButtons:y,messageId:d.messageId},{generateSuggestions:d.doSuggest&&!k&&y.length===0}),messageId:d.messageId,trace:m,routerDecision:d.decision,subagentResults:p,rewrite:d.rewrite,...h.length>0?{uiActions:h}:{},...y.length>0?{actionButtons:y}:{}}}},i=async d=>{let p=(d.rewrite?.mentionedChain||String(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?L.END:"route",u=d=>{let p=d.decision?.assignments??[];if(p.length===0)return"direct";let m=p.some(f=>f.subagent!=="ai-agent"),g=(d.rewrite?.mentionedChain||l.connectedChain()||"").trim();return m&&g!==""&&!pn(g)?"unsupportedChain":p.map((f,h)=>new L.Send("subagent",{...d,assignment:f,assignmentIndex:h}))};return new L.StateGraph(da).addNode("setup",e).addNode("contextualize",t).addNode("kb",n).addNode("route",r).addNode("direct",o).addNode("subagent",s).addNode("synthesize",a).addNode("unsupportedChain",i).addEdge(L.START,"setup").addEdge("setup","contextualize").addEdge("contextualize","kb").addConditionalEdges("kb",c,["route",L.END]).addConditionalEdges("route",u,["direct","subagent","unsupportedChain"]).addEdge("unsupportedChain",L.END).addEdge("direct",L.END).addEdge("subagent","synthesize").addEdge("synthesize",L.END).compile()}var Jo=require("js-sha3");function Mn(){let l=Date.now().toString(16),e=Math.floor(Math.random()*65535).toString(16).padStart(4,"0");return(l+e).slice(-8)}var ma="You are a helpful AI assistant. Answer the user accurately and concisely. Respond in the same language as the user.",Bn=class{llm;registry;history;summarizer;router;rewriter;subagents;synthesizer;chatGraph;systemPrompt;debug;secretKey;licenseChecked=!1;userContext={};storage;storageKey;persistHistoryEnabled;kb;kbEntries;vectorKB=null;vectorKBAutoIngested=!1;vectorKBAutoIngestPromise=null;autoIngestEnabled=!1;kbAnswerThreshold;suggestionsEnabled;historyLoaded=!1;historyLoadingPromise=null;historyReady;constructor(e){this.secretKey=e.secretKey,Xn(e.rpcUrls),mn(e.excludeChains),this.llm=new me(e.llm),this.registry=new Fe,this.history=new $t(e.maxHistoryMessages??50),this.summarizer=new Wt(this.llm),this.synthesizer=new Gt(this.llm),this.systemPrompt=e.systemPrompt??ma,this.debug=e.debug??!1,this.suggestionsEnabled=e.generateSuggestions??!0,this.storage=e.storage??null,this.storageKey=e.storageKey??"keyring-agent-history",this.persistHistoryEnabled=e.persistHistory??!1,this.kbEntries=new Vt(e.knowledgeBase),this.kbEntries.setLLM(this.llm);let t=e.vectorKB;t?.enabled?(t.provider?this.kb=t.provider:(this.vectorKB=new Re({url:t.url,token:t.token,namespace:t.namespace,minScore:t.minScore,defaultTopK:t.defaultTopK,debug:this.debug}),this.kb=this.vectorKB,this.autoIngestEnabled=t.autoIngest===!0),this.debug&&console.log(`[AgentCore] vector KB enabled (${t.provider?"custom provider":"Upstash"}`+(t.namespace?`, ns=${t.namespace}`:"")+")")):this.kb=this.kbEntries;let n=e.kbAnswerThreshold;if(typeof n=="number"?this.kbAnswerThreshold=n:t?.enabled&&!t.provider?this.kbAnswerThreshold=t.minScore??1.3:this.kbAnswerThreshold=.8,this.debug&&console.log(`[AgentCore] kbAnswerThreshold = ${this.kbAnswerThreshold}`),e.moralis!==!1){let u=typeof e.moralis=="object"?e.moralis:void 0;this.registry.register(new Je(u)),this.registry.register(new Ke(u)),this.registry.register(new Ze(u)),this.registry.register(new et(u)),this.registry.register(new tt(u)),this.registry.register(new Ye(u)),this.registry.register(new nt(u)),this.registry.register(new ot(u)),this.registry.register(new lt(u)),this.registry.register(new st(u)),this.registry.register(new at(u)),this.registry.register(new it(u)),this.registry.register(new rt(u)),this.registry.register(new Qe(u)),this.registry.register(new Xe(u)),this.registry.register(new je(u)),this.registry.register(new ze(u))}let r=e.nftLink;this.registry.register(new ct({url:r})),this.registry.register(new ut({url:r})),this.registry.register(new dt({url:r})),this.registry.register(new mt(e.llm));let o=e.subagents?.["pool-subgraph"]===!0;if((o?e.subagents?.pool===!0:e.subagents?.pool!==!1)&&e.uniswap!==!1){let u=typeof e.uniswap=="object"?e.uniswap:void 0;this.registry.register(new pt(u)),this.registry.register(new ht(u)),this.registry.register(new gt(u)),this.registry.register(new ft(u)),this.registry.register(new Pt(u));let d=u?{isProduction:u.isProduction}:void 0;this.registry.register(new xt({baseUrl:u?.baseUrl,pool:d,minProvideUsd:u?.minProvideUsd})),this.registry.register(new At({pool:d}))}if(o&&e.subgraph!==!1){let u=typeof e.subgraph=="object"?e.subgraph:void 0;this.registry.register(new _t(u)),this.registry.register(new Ct(u)),this.registry.register(new Ut(u)),this.registry.register(new Rt(u)),this.registry.register(new Et(u)),this.registry.register(new Nt(u))}if(e.subagents?.["wallet-action"]!==!1){let u=e.moralis===!1?void 0:typeof e.moralis=="object"?e.moralis:{};this.registry.register(new Lt(u)),this.registry.register(new Mt(u)),this.registry.register(new Ft(u,{url:r})),this.registry.register(new Bt(u)),this.registry.register(new Ot(u,{debug:this.debug})),this.registry.register(new qt(u,{debug:this.debug}))}this.router=new Ht(this.llm,{debug:this.debug}),this.rewriter=new Ln(this.llm,{debug:this.debug});let i=typeof e.subgraph=="object"?e.subgraph:void 0,c=Dn(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=Xo(this.createGraphHost()),this.historyReady=this.loadHistory()}registerTool(e){this.registry.register(e)}registerTools(e){for(let t of e)this.registry.register(t)}unregisterTool(e){return this.registry.unregister(e)}listTools(){return this.registry.listNames()}async invokeTool(e,t,n){if(this.assertLicense(),await this.loadHistory(),!this.registry.get(e))return{answer:`Unknown tool: "${e}".`,messageId:Mn()};this.debug&&console.log(`[AgentCore] invokeTool ${e} ${JSON.stringify(t)}`);let o=n?.userMessage??`[invoke ${e}]`;this.history.add({role:"user",content:o,timestamp:Date.now()});let s=await this.registry.execute(e,t,this.userContext),a=s.data,i=s.success?a?.summary??"Done.":`Action failed: ${s.error??"unknown error"}`,c=Mn(),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,Jo.sha3_256)(this.secretKey)!==e))throw new Error("AgentCore: invalid or missing secretKey.");this.licenseChecked=!0}async chat(e,t){this.assertLicense();let n=t?.generateSuggestions??this.suggestionsEnabled,r=typeof t?.language=="string"&&t.language.trim()?t.language.trim():"";await this.loadHistory(),await this.maybeAutoIngest(this.autoIngestEnabled);let o=Date.now();this.debug&&(console.log(`
|
|
840
836
|
${"\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)}
|
|
841
|
-
`)),s.response??{answer:"",messageId:
|
|
837
|
+
`)),s.response??{answer:"",messageId:Mn()}}createGraphHost(){return{debug:this.debug,addUserMessage:e=>this.addUserMessage(e),needsSummary:()=>this.history.needsSummary(),compactHistory:()=>this.compactHistory(),conversationForTurn:()=>{let e=this.history.getConversation();return e.length>0&&e[e.length-1].role==="user"?e.slice(0,-1):e},lastAssistantSubagents:e=>Ko(e),generateMessageId:()=>Mn(),rewrite:(e,t,n)=>this.rewriter.rewrite(e,t,n),searchKB:e=>this.searchKB(e),formatKBContext:e=>this.formatKBContext(e),tryAnswerFromKB:(e,t,n,r)=>this.tryAnswerFromKB(e,t,n,r),subagentCards:()=>Array.from(this.subagents.values()).map(e=>e.getCard()),route:(e,t,n,r)=>this.router.route(e,t,n,this.userContext,r),connectedChain:()=>this.userContext.chain,answerUnsupportedChain:(e,t,n)=>this.answerUnsupportedChain(e,t,n),answerDirectly:(e,t,n,r,o,s)=>this.answerDirectly(e,t,n,r,o,s),runSubagent:async(e,t,n)=>{let r=this.subagents.get(e.subagent);if(!r)return this.debug&&console.log(`[AgentCore] WARN: subagent "${e.subagent}" not registered`),{subagentName:e.subagent,steps:[],finalAnswer:`Subagent "${e.subagent}" is not registered.`,toolResults:[],toolMessages:[]};let o=Date.now(),s=await r.run({query:e.query,reasoning:e.reasoning,language:n},t,this.userContext);if(this.debug){let a=s.toolResults.map(i=>i.toolName).join(", ")||"\u2014";console.log(`[AgentCore] ${e.subagent} done in ${Date.now()-o}ms | tools: ${a}`)}return s},persistToolMessages:e=>this.persistToolMessages(e),synthesiserHistory:()=>{let e=this.history.getConversation(),t=e.length>0&&e[e.length-1].role==="user"?e.slice(0,-1):e;return ye(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=new Map,r=[];for(let o of e)for(let s of o.toolResults)if(!(!s.success||!Array.isArray(s.actionButtons)))for(let a of s.actionButtons){if(!a||typeof a!="object"||typeof a.label!="string"||!a.label.trim()||typeof a.prompt!="string"||!a.prompt.trim())continue;let i={label:a.label,prompt:a.prompt,...t?{language:t}:{}},c=n.get(a.prompt);c?i.label.length>c.label.length&&n.set(a.prompt,i):(n.set(a.prompt,i),r.push(a.prompt))}return r.map(o=>n.get(o))}collectUiActions(e,t){let n=[];for(let r of e)for(let o of r.toolResults)o.success&&o.ui&&typeof o.ui=="object"&&typeof o.ui.component=="string"&&n.push(this.stampLanguage(o.ui,t));if(this.debug&&n.length>0){let r=n.map(o=>`${o.component}=${o.language}`).join(", ");console.log(`[AgentCore] uiActions stamped (rewrite.language="${t??""}"): ${r}`)}return n}stampLanguage(e,t){return!t||e.language?e:{...e,language:t}}addUserMessage(e){this.history.add({role:"user",content:e,timestamp:Date.now()})}async searchKB(e){let t=await this.kb.search(e);if(this.debug)if(t.length===0)console.log(`[AgentCore] KB no hits for "${e}"`);else{let n=t.slice(0,3).map(r=>`${r.score.toFixed(3)} ${this.snippet(r.entry.question,60)}`).join(" | ");console.log(`[AgentCore] KB hits (${t.length}): ${n}`)}return t}snippet(e,t){return e.length<=t?e:e.slice(0,t-1)+"\u2026"}formatKBContext(e){return e.length===0?"":e.map((t,n)=>`[Reference ${n+1}]
|
|
842
838
|
Q: ${t.entry.question}
|
|
843
839
|
A: ${t.entry.answer}`).join(`
|
|
844
840
|
|
|
@@ -856,7 +852,7 @@ Answer "no" if ANY of these hold:
|
|
|
856
852
|
- You are uncertain.`,timestamp:0},{role:"user",content:`FAQ question: "${s}"
|
|
857
853
|
User message: "${e}"
|
|
858
854
|
|
|
859
|
-
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()}],
|
|
855
|
+
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()}],c=(await this.llm.chat(a)).text.trim().toLowerCase();if(!c.startsWith("yes"))return this.debug&&console.log(`[AgentCore] KB intent guard: verdict="${c}" \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+`
|
|
860
856
|
|
|
861
857
|
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.
|
|
862
858
|
|
|
@@ -865,7 +861,7 @@ REFERENCE MATERIAL:
|
|
|
865
861
|
`+n+`
|
|
866
862
|
---`,timestamp:0},{role:"user",content:`${e}
|
|
867
863
|
|
|
868
|
-
(You MUST reply in the same language as the question above, regardless of the reference material language.)`,timestamp:Date.now()}],
|
|
864
|
+
(You MUST reply in the same language as the question above, regardless of the reference material language.)`,timestamp:Date.now()}],p=(await this.llm.chat(u)).text,m={role:"assistant",content:p,timestamp:Date.now()};return r&&(m.messageId=r),this.history.add(m),await this.persistHistory(),{answer:p,suggestedPrompts:[]}}async answerDirectly(e,t,n,r=!0,o,s=!1){this.debug&&console.log(`[AgentCore] Router returned no assignments, answering directly via LLM\u2026 (webSearch=${s})`);let a=s?"":`
|
|
869
865
|
|
|
870
866
|
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+`
|
|
871
867
|
|
|
@@ -874,9 +870,9 @@ You may use the following reference material if relevant:
|
|
|
874
870
|
---
|
|
875
871
|
REFERENCE MATERIAL:
|
|
876
872
|
`+n+`
|
|
877
|
-
---`:this.systemPrompt)+a,
|
|
873
|
+
---`:this.systemPrompt)+a,c=ye(t),u=[{role:"system",content:i,timestamp:0},...c,{role:"user",content:e,timestamp:Date.now()}],d=await this.llm.chat(u,void 0,{googleSearch:s});return await this.finaliseAnswer({userMessage:e,answer:d.text,messageId:o},{generateSuggestions:r})}async answerUnsupportedChain(e,t,n){this.debug&&console.log(`[AgentCore] connected chain "${e}" is not supported \u2192 short-circuit with notice`);let r=(t||"").trim(),o=[{role:"system",content:`Write ONE short, polite message telling the user that the blockchain network they want to use is NOT supported by this assistant, and inviting them to use one of the supported networks. List the supported networks: ${Ne}. Do NOT mention or guess the name or chain id of the unsupported network. `+(r?`Reply in BCP-47 language "${r}".`:"Reply in the same language the user has been using.")+" Do NOT mention tools or internal implementation details.",timestamp:0},{role:"user",content:"Is my currently connected network supported?",timestamp:Date.now()}],a=(await this.llm.chat(o)).text?.trim()||`Your currently connected network is not supported yet. Supported networks: ${Ne}.`;return await this.finaliseAnswer({userMessage:`[unsupported chain: ${e}]`,answer:a,messageId:n},{generateSuggestions:!1})}mergeTrace(e){let t=[];for(let r of e)t.push({phase:"think",content:`[${r.subagentName}] begin`,timestamp:Date.now()}),t.push(...r.steps),t.push({phase:"observe",content:`[${r.subagentName}] answer: ${r.finalAnswer}`,timestamp:Date.now()});let n=e.map(r=>`[${r.subagentName}] ${r.finalAnswer}`).join(`
|
|
878
874
|
|
|
879
|
-
`);return{steps:t,finalAnswer:n}}async finaliseAnswer(e,t){let{userMessage:n,answer:r,subagents:o,uiActions:s,actionButtons:a,messageId:i}=e??{},{generateSuggestions:
|
|
875
|
+
`);return{steps:t,finalAnswer:n}}async finaliseAnswer(e,t){let{userMessage:n,answer:r,subagents:o,uiActions:s,actionButtons:a,messageId:i}=e??{},{generateSuggestions:c=!0}=t??{},u=c?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")?`
|
|
880
876
|
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.
|
|
881
877
|
`:"",o=n.length>0?`
|
|
882
878
|
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.
|
|
@@ -894,4 +890,4 @@ RULES:
|
|
|
894
890
|
- Suggestions must be diverse \u2014 do not repeat the same intent in different words.
|
|
895
891
|
- Use the same language as the user.
|
|
896
892
|
- Good example (pool): ["Add liquidity to USDC/WETH 0.05%", "Estimate yield for $1000 here", "Compare with 0.3% fee tier"]
|
|
897
|
-
- 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(
|
|
893
|
+
- Bad example: ["What is a pool?", "How do pools work?", "Tell me more"] (generic, no action, no specifics)`,timestamp:0}])).text.match(/\[[\s\S]*\]/);return a?JSON.parse(a[0]).filter(c=>typeof c=="string"&&c.length>0).slice(0,3):[]}catch{return[]}}getHistory(){return this.history.getAllFull()}getDisplayHistory(){return this.history.getAllFull().filter(e=>!(e.role!=="user"&&e.role!=="assistant"||e._intermediate||e.role==="assistant"&&!e.content?.trim()))}getLastSuggestions(){let e=this.getDisplayHistory();for(let t=e.length-1;t>=0;t--){let n=e[t];if(n.role==="assistant")return n.suggestedPrompts??[]}return[]}async clearHistory(){this.history.clear(),this.historyLoaded=!0,this.historyLoadingPromise=null,await this.persistHistory()}restoreHistory(e){this.history.restore(e)}serialiseHistory(){return this.history.serialise()}async compactHistory(){this.debug&&console.log("[AgentCore] Summarizing conversation history\u2026");let e=this.history.getAll(),t=6,n=0,r=e.length;for(let p=e.length-1;p>=0;p--)if(e[p].role==="user"&&(n++,n>=t)){r=p;break}let o=e.slice(r),s=Go(o,o.length),a=e.length-s.length,i=ye(e.slice(0,a)),c=this.history.getSummary(),u=[];c&&u.push({role:"user",content:`[Previous context summary]: ${c}`,timestamp:0}),u.push(...i);let d=await this.summarizer.summarize(u);this.history.compactWith(d,s)}persistToolMessages(e){for(let t of e)t.toolMessages.length>0&&(this.history.addMany(t.toolMessages),this.history.add({role:"assistant",content:t.finalAnswer,_intermediate:!0,timestamp:Date.now()}),this.debug&&console.log(`[AgentCore] stored ${t.toolMessages.length} tool message(s) + finalAnswer from ${t.subagentName}`))}getLLMProvider(){return this.llm}getToolRegistry(){return this.registry}};var pa=l=>{let e=JSON.parse(l),t=Array.isArray(e)?e:e?.entries;if(!Array.isArray(t))throw new Error("JSON must be an array or { entries: [...] }");return t.map((n,r)=>{if(!n||typeof n.question!="string"||typeof n.answer!="string")throw new Error(`Entry #${r} is missing "question" or "answer"`);return n})};async function Zo(l,e){let t=l.entries??(l.json?pa(l.json):[]);return t.length===0?{count:0}:new Re(e).upsert(t)}0&&(module.exports={AI_AGENT_TOOL_NAMES,AgentCore,ApproveTokenTool,BaseNftMessageTool,BaseSwapService,BaseTool,BaseWalletActionTool,BuyTokenTool,ChatHistory,DEFAULT_CHAIN,DEFAULT_PROVIDER,DEFAULT_RPC_BY_CHAIN,DebridgeAdapter,EstimatePoolYieldTool,GeminiProvider,GeminiSearchAiTool,HEX_TO_PANTOGRAPH,KnowledgeBase,MoralisService,NFTContractInfoTool,NFTMetadataTool,NFT_AGENT_TOOL_NAMES,OpenAddLiquidityFormTool,POOL_AGENT_TOOL_NAMES,PantographService,PoolByAddressTool,PoolDetailTool,PoolSearchTool,PoolService,PreviewAddLiquidityTool,RelayAdapter,Router,SUPPORTED_CHAINS,SUPPORTED_CHAINS_LABEL,SWAP_PROVIDER_CONFIG,SendNativeTool,SendNftTool,SendTokenTool,Subagent,SubgraphCoinPoolPairsTool,SubgraphPoolByAddressTool,SubgraphPoolByPositionIdTool,SubgraphPoolSearchTool,SubgraphPositionDetailTool,SubgraphTrendingPoolsTool,Summarizer,SwapServiceFactory,SwapTokenTool,Synthesizer,TOKEN_AGENT_TOOL_NAMES,TRANSFER_TOPIC,TRENDING_DEFAULT_BY_HEX_CHAIN,TokenAnalyticsTool,TokenHoldersTool,TokenInfoTool,TokenScoreTool,ToolRegistry,TopGainersTool,TopPoolsTool,TransactionByHashTool,TrendingTokensTool,UNISWAP_CHAIN_SLUG,UpstashKnowledgeBase,WALLET_ACTION_AGENT_TOOL_NAMES,WALLET_AGENT_TOOL_NAMES,WalletApprovalsTool,WalletDefiPositionsTool,WalletDefiProtocolPositionsTool,WalletDefiSummaryTool,WalletHistoryTool,WalletNFTsTool,WalletNetWorthTool,WalletNftTransfersTool,WalletPnlSummaryTool,WalletPnlTool,WalletTokenBalancesTool,WalletTokenTransfersTool,ZERO_ADDRESS,buildRangePresets,buildUniswapPoolUrl,clampTick,configureSupportedChains,createAiAgent,createDefaultSubagents,createNftAgent,createPoolAgent,createTokenAgent,createWalletActionAgent,createWalletAgent,ethCallAt,ethCallByChain,getAffiliateFee,getChainMeta,getDefaultRpcUrl,getGatewayAddress,getNativeTokenInfo,getPositionManagerAddress,getProviderByChain,ingestKnowledgeBase,priceToTick,resolveRpcUrl,roundTickToSpacing,rpcCall,setRpcOverrides,swapServiceFactory,tickToPrice,toPantographChain});
|