@xona-labs/xpay 0.2.24 → 0.2.25
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/CHANGELOG.md +101 -78
- package/README.md +71 -69
- package/SKILL.md +22 -22
- package/dist/agenc/api.d.ts +8 -8
- package/dist/agenc/api.js +11 -11
- package/dist/agenc/hire.d.ts +4 -4
- package/dist/agenc/hire.js +17 -17
- package/dist/agenc/price.d.ts +1 -1
- package/dist/agenc/price.js +1 -1
- package/dist/biometric/index.d.ts +3 -3
- package/dist/biometric/index.js +6 -6
- package/dist/cli/accounts.d.ts +1 -1
- package/dist/cli/accounts.js +1 -1
- package/dist/cli/agenc.d.ts +2 -2
- package/dist/cli/agenc.js +4 -4
- package/dist/cli/balance.d.ts +1 -1
- package/dist/cli/balance.js +4 -4
- package/dist/cli/bento.d.ts +3 -3
- package/dist/cli/bento.js +8 -8
- package/dist/cli/bento.js.map +1 -1
- package/dist/cli/biometric.d.ts +1 -1
- package/dist/cli/biometric.js +8 -8
- package/dist/cli/common.js +6 -6
- package/dist/cli/common.js.map +1 -1
- package/dist/cli/discover.d.ts +2 -2
- package/dist/cli/discover.js +4 -4
- package/dist/cli/guardrail.d.ts +1 -1
- package/dist/cli/guardrail.js +3 -3
- package/dist/cli/index.d.ts +2 -2
- package/dist/cli/index.js +12 -12
- package/dist/cli/init.d.ts +1 -1
- package/dist/cli/init.js +2 -2
- package/dist/cli/magicblock.d.ts +1 -1
- package/dist/cli/magicblock.js +4 -4
- package/dist/cli/mcp-server.d.ts +2 -2
- package/dist/cli/mcp-server.js +21 -21
- package/dist/cli/pay.d.ts +1 -1
- package/dist/cli/pay.js +1 -1
- package/dist/cli/report.d.ts +2 -2
- package/dist/cli/report.js +4 -4
- package/dist/cli/report.js.map +1 -1
- package/dist/cli/sana.d.ts +1 -1
- package/dist/cli/sana.js +3 -3
- package/dist/cli/shop.d.ts +1 -1
- package/dist/cli/shop.js +1 -1
- package/dist/cli/swap.d.ts +1 -1
- package/dist/cli/swap.js +3 -3
- package/dist/cli/token.d.ts +2 -2
- package/dist/cli/token.js +2 -2
- package/dist/cli/trade.d.ts +2 -2
- package/dist/cli/trade.js +8 -8
- package/dist/cli/trade.js.map +1 -1
- package/dist/cli/transfer.d.ts +1 -1
- package/dist/cli/transfer.js +1 -1
- package/dist/cli/x.d.ts +1 -1
- package/dist/cli/x.js +1 -1
- package/dist/cli/zauth.d.ts +1 -1
- package/dist/cli/zauth.js +5 -5
- package/dist/discover/cache.js +1 -1
- package/dist/discover/index.d.ts +2 -2
- package/dist/discover/index.js +6 -6
- package/dist/discover/orbitx402.d.ts +2 -2
- package/dist/discover/orbitx402.js +1 -1
- package/dist/discover/payai.js +1 -1
- package/dist/do/index.d.ts +2 -2
- package/dist/do/index.js +1 -1
- package/dist/guardrail/index.d.ts +4 -4
- package/dist/guardrail/index.js +14 -14
- package/dist/guardrail/index.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +2 -2
- package/dist/magicblock/client.d.ts +2 -2
- package/dist/magicblock/client.js +4 -4
- package/dist/profile/index.d.ts +3 -3
- package/dist/profile/index.js +3 -3
- package/dist/profile/storage.d.ts +1 -1
- package/dist/profile/storage.js +4 -4
- package/dist/profile/types.d.ts +6 -6
- package/dist/profile/types.js +1 -1
- package/dist/report/index.d.ts +1 -1
- package/dist/report/index.js +2 -2
- package/dist/sana/tools.js +8 -8
- package/dist/signers/raw-evm.d.ts +1 -1
- package/dist/signers/raw-evm.js +5 -5
- package/dist/signers/raw-solana.d.ts +1 -1
- package/dist/signers/raw-solana.js +4 -4
- package/dist/swap/index.d.ts +6 -6
- package/dist/swap/index.js +12 -12
- package/dist/token/index.d.ts +8 -8
- package/dist/token/index.js +14 -14
- package/dist/token/rwa.d.ts +3 -3
- package/dist/token/rwa.js +3 -3
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +31 -31
- package/dist/trading/discovery.d.ts +6 -6
- package/dist/trading/discovery.js +7 -7
- package/dist/trading/index.d.ts +6 -6
- package/dist/trading/index.js +13 -13
- package/dist/transfer/index.d.ts +4 -4
- package/dist/transfer/index.js +8 -8
- package/dist/types.d.ts +7 -7
- package/dist/use/index.d.ts +5 -5
- package/dist/use/index.js +20 -20
- package/dist/wallet/index.d.ts +2 -2
- package/dist/wallet/index.js +3 -3
- package/dist/x402/evm-payment.d.ts +4 -4
- package/dist/x402/evm-payment.js +6 -6
- package/dist/x402/extract.d.ts +1 -1
- package/dist/x402/extract.js +1 -1
- package/dist/x402/svm-payment.d.ts +2 -2
- package/dist/x402/svm-payment.js +2 -2
- package/dist/zauth/index.d.ts +4 -4
- package/dist/zauth/index.js +5 -5
- package/package.json +4 -3
package/dist/token/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Solana token discovery
|
|
2
|
+
* Solana token discovery - Jupiter Token API v2.
|
|
3
3
|
*
|
|
4
4
|
* Keyless read-only search by ticker, name, or mint address. Returns live
|
|
5
|
-
* market data (price, mcap, liquidity) plus Jupiter's verification flag
|
|
5
|
+
* market data (price, mcap, liquidity) plus Jupiter's verification flag -
|
|
6
6
|
* the main scam signal agents must check before swapping (anyone can mint a
|
|
7
7
|
* token reusing a real project's ticker).
|
|
8
8
|
*/
|
|
@@ -10,23 +10,23 @@ import { Connection, PublicKey } from "@solana/web3.js";
|
|
|
10
10
|
import { getMint } from "@solana/spl-token";
|
|
11
11
|
import { SOLANA_TOKENS } from "../transfer/index.js";
|
|
12
12
|
const DEFAULT_ENDPOINT = "https://api.jup.ag";
|
|
13
|
-
/** Wrapped-SOL mint
|
|
13
|
+
/** Wrapped-SOL mint - Jupiter's sentinel for native SOL (wraps/unwraps automatically). */
|
|
14
14
|
export const NATIVE_SOL_MINT = "So11111111111111111111111111111111111111112";
|
|
15
|
-
/** Thrown when a bare ticker matches several plausible tokens
|
|
15
|
+
/** Thrown when a bare ticker matches several plausible tokens - retry with the exact mint. */
|
|
16
16
|
export class AmbiguousTokenError extends Error {
|
|
17
17
|
candidates;
|
|
18
18
|
constructor(input, candidates) {
|
|
19
19
|
const list = candidates
|
|
20
20
|
.map((c) => ` ${c.symbol} (${c.name}) mint ${c.mint}${c.verified ? " [verified]" : " [UNVERIFIED]"} liquidity $${Math.round(c.liquidity ?? 0).toLocaleString()}`)
|
|
21
21
|
.join("\n");
|
|
22
|
-
super(`Token "${input}" is ambiguous
|
|
22
|
+
super(`Token "${input}" is ambiguous - pass the exact mint address of the one you mean:\n${list}`);
|
|
23
23
|
this.name = "AmbiguousTokenError";
|
|
24
24
|
this.candidates = candidates;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Search tokens by ticker, name, or mint. Ranked verified-first, then by
|
|
29
|
-
* liquidity. Read-only
|
|
29
|
+
* liquidity. Read-only - no wallet, no signing.
|
|
30
30
|
*/
|
|
31
31
|
export async function findTokens(query, opts = {}) {
|
|
32
32
|
const raw = await searchJupiter(query, opts);
|
|
@@ -46,7 +46,7 @@ export async function findTokens(query, opts = {}) {
|
|
|
46
46
|
}
|
|
47
47
|
/**
|
|
48
48
|
* Deterministic single-token resolution for trading. Accepts a symbol or a
|
|
49
|
-
* mint address; never silently picks between plausible candidates
|
|
49
|
+
* mint address; never silently picks between plausible candidates - throws
|
|
50
50
|
* {@link AmbiguousTokenError} instead so the caller can pass the exact mint.
|
|
51
51
|
*/
|
|
52
52
|
export async function resolveTradeToken(input, opts = {}) {
|
|
@@ -57,7 +57,7 @@ export async function resolveTradeToken(input, opts = {}) {
|
|
|
57
57
|
return { ...(info ?? fallbackToken(NATIVE_SOL_MINT, 9)), symbol: "SOL", verified: true };
|
|
58
58
|
}
|
|
59
59
|
// Well-known symbols pin to a canonical mint (avoids the ~20 lookalike
|
|
60
|
-
// "BONK"s Jupiter returns)
|
|
60
|
+
// "BONK"s Jupiter returns) - then still fetch live data by that mint.
|
|
61
61
|
const pinned = SOLANA_TOKENS[key] ?? Object.values(SOLANA_TOKENS).find((t) => t.symbol.toUpperCase() === key);
|
|
62
62
|
if (pinned) {
|
|
63
63
|
const [info] = await searchJupiter(pinned.mint, opts);
|
|
@@ -87,16 +87,16 @@ export async function resolveTradeToken(input, opts = {}) {
|
|
|
87
87
|
throw new AmbiguousTokenError(input, sorted.slice(0, 5));
|
|
88
88
|
}
|
|
89
89
|
if (symbolMatches.length > 0) {
|
|
90
|
-
// Only unverified candidates
|
|
90
|
+
// Only unverified candidates - never auto-trade those by ticker.
|
|
91
91
|
throw new AmbiguousTokenError(input, symbolMatches.sort((a, b) => (b.liquidity ?? 0) - (a.liquidity ?? 0)).slice(0, 5));
|
|
92
92
|
}
|
|
93
|
-
throw new Error(`Unknown token "${input}"
|
|
93
|
+
throw new Error(`Unknown token "${input}" - no exact ticker match on Jupiter. ` +
|
|
94
94
|
`Try \`xpay token find ${input}\` to search, or pass the mint address directly.`);
|
|
95
95
|
}
|
|
96
96
|
/**
|
|
97
97
|
* Label and price wallet balances via one batched Jupiter lookup (comma-
|
|
98
98
|
* separated mints, max 100). Unknown mints get their real symbol/name; every
|
|
99
|
-
* priceable token gets `usdPrice`/`usdValue`. Never throws
|
|
99
|
+
* priceable token gets `usdPrice`/`usdValue`. Never throws - on any Jupiter
|
|
100
100
|
* failure the input balances are returned unchanged (a balance display must
|
|
101
101
|
* not break because a market-data API hiccuped).
|
|
102
102
|
*/
|
|
@@ -186,7 +186,7 @@ export async function jupiterFetch(url, apiKey, init) {
|
|
|
186
186
|
reason = body?.error ?? body?.errorMessage ?? "";
|
|
187
187
|
}
|
|
188
188
|
catch { /* no body */ }
|
|
189
|
-
throw new Error(`Jupiter API ${res.status} ${res.statusText}${reason ? `
|
|
189
|
+
throw new Error(`Jupiter API ${res.status} ${res.statusText}${reason ? ` - ${reason}` : ""}` +
|
|
190
190
|
(res.status === 429 ? " (rate-limited; set JUPITER_API_KEY for higher limits)" : ""));
|
|
191
191
|
}
|
|
192
192
|
return res.json();
|
|
@@ -198,7 +198,7 @@ function numOrUndef(v) {
|
|
|
198
198
|
function fallbackToken(mint, decimals) {
|
|
199
199
|
return { mint, symbol: mint.slice(0, 6) + "…", name: "", decimals, verified: false };
|
|
200
200
|
}
|
|
201
|
-
/** Last resort for mints Jupiter has never indexed
|
|
201
|
+
/** Last resort for mints Jupiter has never indexed - decimals from chain, no price. */
|
|
202
202
|
async function onChainToken(mint) {
|
|
203
203
|
const rpc = process.env.XPAY_SOLANA_RPC ?? "https://solana-mainnet.g.alchemy.com/v2/Ug5mqBVIbSHoa8ZHgTUSJ";
|
|
204
204
|
try {
|
|
@@ -207,7 +207,7 @@ async function onChainToken(mint) {
|
|
|
207
207
|
return fallbackToken(mint, info.decimals);
|
|
208
208
|
}
|
|
209
209
|
catch {
|
|
210
|
-
throw new Error(`Token mint ${mint} not found on Jupiter or on-chain
|
|
210
|
+
throw new Error(`Token mint ${mint} not found on Jupiter or on-chain - check the address.`);
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
//# sourceMappingURL=index.js.map
|
package/dist/token/rwa.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RWA (real-world asset) discovery on Solana
|
|
2
|
+
* RWA (real-world asset) discovery on Solana - Jupiter Token API v2.
|
|
3
3
|
*
|
|
4
4
|
* "RWA" covers every tokenized off-chain asset: equities, ETFs, treasuries,
|
|
5
5
|
* commodities, private credit. What is actually TRADABLE on Solana DEXes
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* so it is pinned here explicitly).
|
|
13
13
|
*
|
|
14
14
|
* Permissioned funds (BlackRock BUIDL, Ondo OUSG) exist on Solana but are
|
|
15
|
-
* KYC-gated: unverified on Jupiter, zero DEX liquidity, not swappable
|
|
15
|
+
* KYC-gated: unverified on Jupiter, zero DEX liquidity, not swappable - they
|
|
16
16
|
* are deliberately excluded.
|
|
17
17
|
*
|
|
18
18
|
* Jupiter's tag ENDPOINT rejects `rwa`/`stocks`/`xstocks` as queries (only
|
|
@@ -34,7 +34,7 @@ export interface RwaFindOptions extends TokenApiOptions {
|
|
|
34
34
|
category?: RwaCategory;
|
|
35
35
|
/** Max results. Default 20. */
|
|
36
36
|
limit?: number;
|
|
37
|
-
/** Include unverified tokens (excluded by default
|
|
37
|
+
/** Include unverified tokens (excluded by default - same scam caveat as any ticker). */
|
|
38
38
|
includeUnverified?: boolean;
|
|
39
39
|
}
|
|
40
40
|
/**
|
package/dist/token/rwa.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RWA (real-world asset) discovery on Solana
|
|
2
|
+
* RWA (real-world asset) discovery on Solana - Jupiter Token API v2.
|
|
3
3
|
*
|
|
4
4
|
* "RWA" covers every tokenized off-chain asset: equities, ETFs, treasuries,
|
|
5
5
|
* commodities, private credit. What is actually TRADABLE on Solana DEXes
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* so it is pinned here explicitly).
|
|
13
13
|
*
|
|
14
14
|
* Permissioned funds (BlackRock BUIDL, Ondo OUSG) exist on Solana but are
|
|
15
|
-
* KYC-gated: unverified on Jupiter, zero DEX liquidity, not swappable
|
|
15
|
+
* KYC-gated: unverified on Jupiter, zero DEX liquidity, not swappable - they
|
|
16
16
|
* are deliberately excluded.
|
|
17
17
|
*
|
|
18
18
|
* Jupiter's tag ENDPOINT rejects `rwa`/`stocks`/`xstocks` as queries (only
|
|
@@ -28,7 +28,7 @@ import { searchTokens } from "./index.js";
|
|
|
28
28
|
* its top-100 between runs.
|
|
29
29
|
*/
|
|
30
30
|
const SWEEP_QUERIES = ["xstock", "Ondo Tokenized", "tokenized", "treasury", "remora"];
|
|
31
|
-
/** Ondo USDY
|
|
31
|
+
/** Ondo USDY - treasury-backed yieldcoin, tagged yield/yb on Jupiter rather than rwa. */
|
|
32
32
|
const USDY_MINT = "A1KLoBrKBde8Ty9qtNQUtq3C2ortoC3u7twggz7sEto6";
|
|
33
33
|
const CACHE_TTL_MS = 5 * 60_000;
|
|
34
34
|
let sweepCache;
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export interface ClaudeToolDef {
|
|
|
21
21
|
input_schema: Record<string, unknown>;
|
|
22
22
|
}
|
|
23
23
|
export interface ToolOptions {
|
|
24
|
-
/** Sana API key
|
|
24
|
+
/** Sana API key - when present, registers sana_* tools alongside xpay_* tools. */
|
|
25
25
|
sanaApiKey?: string;
|
|
26
26
|
}
|
|
27
27
|
/** Anthropic Claude tool definitions. */
|
package/dist/tools/index.js
CHANGED
|
@@ -27,7 +27,7 @@ export function forClaude(xpay, opts = {}) {
|
|
|
27
27
|
name: "xpay_discover",
|
|
28
28
|
description: "Find paid HTTP services across the agentic-commerce catalog (PayAI + others). " +
|
|
29
29
|
"Returns ranked candidates with price, network, and payment recipient. " +
|
|
30
|
-
"Results may include AgenC marketplace agent listings (metadata.source === 'agenc')
|
|
30
|
+
"Results may include AgenC marketplace agent listings (metadata.source === 'agenc') - " +
|
|
31
31
|
"those are priced in SOL lamports and execute as on-chain escrow hires, not HTTP calls. " +
|
|
32
32
|
"When the user asks specifically about the AgenC marketplace, pass sources: ['agenc'] " +
|
|
33
33
|
"(optionally with no query) to list ALL its listings instead of the few slots it gets " +
|
|
@@ -49,12 +49,12 @@ export function forClaude(xpay, opts = {}) {
|
|
|
49
49
|
{
|
|
50
50
|
name: "xpay_use",
|
|
51
51
|
description: "Call a specific resource from the catalog. Handles x402 payment automatically. " +
|
|
52
|
-
"Prefer passing the full `resource` object returned by xpay_discover
|
|
52
|
+
"Prefer passing the full `resource` object returned by xpay_discover - it includes " +
|
|
53
53
|
"pre-fetched payment requirements so payment goes through without a probe round-trip. " +
|
|
54
54
|
"Fall back to `resourceUrl` only when you have a URL but no catalog entry. " +
|
|
55
55
|
"If the resource is an AgenC marketplace listing it executes as a Solana escrow hire " +
|
|
56
56
|
"instead: SOL is escrowed on-chain and the result is a hire receipt (task PDA + tx " +
|
|
57
|
-
"signature), NOT an HTTP response
|
|
57
|
+
"signature), NOT an HTTP response - the provider works asynchronously; poll progress " +
|
|
58
58
|
"with xpay_agenc_status.",
|
|
59
59
|
input_schema: {
|
|
60
60
|
type: "object",
|
|
@@ -119,7 +119,7 @@ export function forClaude(xpay, opts = {}) {
|
|
|
119
119
|
},
|
|
120
120
|
{
|
|
121
121
|
name: "xpay_report",
|
|
122
|
-
description: "Comprehensive USDC activity report for the wallet
|
|
122
|
+
description: "Comprehensive USDC activity report for the wallet - totals, net flow, daily timeline, top counterparties, and biggest transactions. " +
|
|
123
123
|
"Powered by OrbitX402 (on-chain data fetched server-side, no RPC exposed). " +
|
|
124
124
|
"Use this instead of history for a full picture of spending and income.",
|
|
125
125
|
input_schema: {
|
|
@@ -144,10 +144,10 @@ export function forClaude(xpay, opts = {}) {
|
|
|
144
144
|
},
|
|
145
145
|
{
|
|
146
146
|
name: "xpay_token_find",
|
|
147
|
-
description: "Find Solana tokens by ticker, name, or mint address (Jupiter registry). Read-only
|
|
147
|
+
description: "Find Solana tokens by ticker, name, or mint address (Jupiter registry). Read-only - no " +
|
|
148
148
|
"wallet, no spending. Returns mint, price, market cap, liquidity, and a `verified` flag. " +
|
|
149
149
|
"ALWAYS check `verified` before suggesting a swap: unverified tokens can be scams reusing a " +
|
|
150
|
-
"real token's ticker. This tool alone answers price/info questions
|
|
150
|
+
"real token's ticker. This tool alone answers price/info questions - only proceed to a swap " +
|
|
151
151
|
"if the user explicitly asked to trade, and then use the xpay_swap tool with the chosen mint " +
|
|
152
152
|
"(do NOT write code or call DEX APIs yourself).",
|
|
153
153
|
input_schema: {
|
|
@@ -161,17 +161,17 @@ export function forClaude(xpay, opts = {}) {
|
|
|
161
161
|
},
|
|
162
162
|
{
|
|
163
163
|
name: "xpay_swap",
|
|
164
|
-
description: "THE ONLY way to swap tokens
|
|
164
|
+
description: "THE ONLY way to swap tokens - a single tool call that quotes, signs, and executes inside the " +
|
|
165
165
|
"user's own xpay wallet via Jupiter (Solana only). NEVER write code or scripts (Python/JS/curl) " +
|
|
166
166
|
"to swap, and never call Jupiter or DEX APIs directly: custom code bypasses the user's guardrail " +
|
|
167
167
|
"caps and token-verification safety, and has no access to the wallet key anyway. When the user " +
|
|
168
|
-
"actually wants to swap, this one tool call does everything (quote + sign + execute)
|
|
168
|
+
"actually wants to swap, this one tool call does everything (quote + sign + execute) - but " +
|
|
169
169
|
"xpay_token_find alone answers informational questions; do NOT follow it with a swap unless the " +
|
|
170
170
|
"user asked to trade. Swaps are irreversible and guardrail caps are enforced before signing. Before calling, show the " +
|
|
171
171
|
"user: the input amount + USD value, the expected output amount, and the output token's mint + " +
|
|
172
|
-
"verification status
|
|
172
|
+
"verification status - and get their explicit approval. Never swap unprompted, and never swap " +
|
|
173
173
|
"into an unverified token without the user confirming the exact mint. Ambiguous tickers return " +
|
|
174
|
-
"an error listing candidate mints
|
|
174
|
+
"an error listing candidate mints - pass the exact mint to disambiguate.",
|
|
175
175
|
input_schema: {
|
|
176
176
|
type: "object",
|
|
177
177
|
properties: {
|
|
@@ -189,7 +189,7 @@ export function forClaude(xpay, opts = {}) {
|
|
|
189
189
|
"fun.noxa.fi memecoin scene) right now, with price, market cap, 24h volume, and the token's " +
|
|
190
190
|
"contract address. FREE, read-only, no wallet. Use this to answer 'what's hot on Robinhood " +
|
|
191
191
|
"Chain' and to get the exact contract address to pass to xpay_trade. Pass newOnly:true for " +
|
|
192
|
-
"the freshest launches (higher risk). This is Robinhood *Chain* on-chain data
|
|
192
|
+
"the freshest launches (higher risk). This is Robinhood *Chain* on-chain data - unrelated to " +
|
|
193
193
|
"the Robinhood brokerage app or stock trading.",
|
|
194
194
|
input_schema: {
|
|
195
195
|
type: "object",
|
|
@@ -201,7 +201,7 @@ export function forClaude(xpay, opts = {}) {
|
|
|
201
201
|
},
|
|
202
202
|
{
|
|
203
203
|
name: "xpay_trade_quote",
|
|
204
|
-
description: "Quote a Robinhood Chain trade WITHOUT executing
|
|
204
|
+
description: "Quote a Robinhood Chain trade WITHOUT executing - no signing, no funds moved, no guardrail. " +
|
|
205
205
|
"Buy a token with native ETH or sell it back to ETH, routed through Uniswap V3 (NOXA Fun " +
|
|
206
206
|
"pools). Returns expected output, min output after slippage, pool fee, USD estimate, and " +
|
|
207
207
|
"whether the token is a verified NOXA launch. Use this to preview a trade before calling " +
|
|
@@ -220,16 +220,16 @@ export function forClaude(xpay, opts = {}) {
|
|
|
220
220
|
},
|
|
221
221
|
{
|
|
222
222
|
name: "xpay_trade",
|
|
223
|
-
description: "Trade tokens on Robinhood Chain (Arbitrum L2) via Uniswap V3
|
|
223
|
+
description: "Trade tokens on Robinhood Chain (Arbitrum L2) via Uniswap V3 - quotes, signs, and executes " +
|
|
224
224
|
"in ONE call from the user's own xpay wallet. Buy a memecoin with native ETH, or sell it back " +
|
|
225
225
|
"to ETH (v1 supports ETH↔token only). This is distinct from xpay_swap, which is Solana/Jupiter. " +
|
|
226
|
-
"NEVER write code or call DEX/router contracts yourself
|
|
226
|
+
"NEVER write code or call DEX/router contracts yourself - custom code bypasses the guardrail and " +
|
|
227
227
|
"has no wallet key. Trades are IRREVERSIBLE; guardrail caps are enforced before signing. Before " +
|
|
228
228
|
"calling: show the user the input amount + USD value, expected output, the token's contract " +
|
|
229
229
|
"address, and whether it's a verified NOXA launch (unverified tokens can be scams reusing a " +
|
|
230
|
-
"ticker)
|
|
230
|
+
"ticker) - and get explicit approval. Prefer passing the exact contract address from " +
|
|
231
231
|
"xpay_trending_tokens over a symbol (memecoin tickers aren't unique). Fund the wallet with ETH " +
|
|
232
|
-
"on Robinhood Chain first (bridge via Across or Uniswap)
|
|
232
|
+
"on Robinhood Chain first (bridge via Across or Uniswap) - the wallet pays its own gas here.",
|
|
233
233
|
input_schema: {
|
|
234
234
|
type: "object",
|
|
235
235
|
properties: {
|
|
@@ -243,8 +243,8 @@ export function forClaude(xpay, opts = {}) {
|
|
|
243
243
|
},
|
|
244
244
|
{
|
|
245
245
|
name: "xpay_x_user",
|
|
246
|
-
description: "Realtime X (Twitter) profile lookup
|
|
247
|
-
"call (~$0.01 USDC from the wallet via x402, at cost
|
|
246
|
+
description: "Realtime X (Twitter) profile lookup - followers, bio, verification status. This is a PAID " +
|
|
247
|
+
"call (~$0.01 USDC from the wallet via x402, at cost - no markup); guardrail caps apply. " +
|
|
248
248
|
"Great for due diligence on a token's or project's X account before a swap. Don't spam it: " +
|
|
249
249
|
"results barely change minute to minute, so one call per account per conversation is enough.",
|
|
250
250
|
input_schema: {
|
|
@@ -258,7 +258,7 @@ export function forClaude(xpay, opts = {}) {
|
|
|
258
258
|
{
|
|
259
259
|
name: "xpay_x_posts",
|
|
260
260
|
description: "Recent posts from an X (Twitter) account (up to 10, excludes retweets/replies) with " +
|
|
261
|
-
"engagement metrics. PAID call (~$0.06 USDC from the wallet via x402, at cost
|
|
261
|
+
"engagement metrics. PAID call (~$0.06 USDC from the wallet via x402, at cost - no markup); " +
|
|
262
262
|
"guardrail caps apply. Use for checking what a project/account is actually saying right now " +
|
|
263
263
|
"(e.g. before swapping into their token). One call per account per conversation is enough.",
|
|
264
264
|
input_schema: {
|
|
@@ -272,11 +272,11 @@ export function forClaude(xpay, opts = {}) {
|
|
|
272
272
|
},
|
|
273
273
|
{
|
|
274
274
|
name: "xpay_zauth_reposcan",
|
|
275
|
-
description: "Repository security scan via zauth (partner)
|
|
275
|
+
description: "Repository security scan via zauth (partner) - scans a git repo for code provenance and " +
|
|
276
276
|
"vulnerabilities. PAID call (~$0.05 USDC from the wallet via x402; guardrail caps apply). " +
|
|
277
277
|
"Scans can take a while: if the response still says status \"scanning\", check later with " +
|
|
278
278
|
"xpay_zauth_scan_status using the returned sessionToken (the long JWT string, NOT the short " +
|
|
279
|
-
"scanId
|
|
279
|
+
"scanId - the token is valid ~1 hour). Do NOT call this tool again for the same repo, that " +
|
|
280
280
|
"pays for a second scan. Results are informational: report them to the user, never " +
|
|
281
281
|
"auto-remediate or trigger further payments based on findings.",
|
|
282
282
|
input_schema: {
|
|
@@ -290,15 +290,15 @@ export function forClaude(xpay, opts = {}) {
|
|
|
290
290
|
{
|
|
291
291
|
name: "xpay_zauth_scan_status",
|
|
292
292
|
description: "Check a running zauth repo scan by sessionToken (returned by xpay_zauth_reposcan). " +
|
|
293
|
-
"FREE, read-only, no wallet
|
|
293
|
+
"FREE, read-only, no wallet - always use this to follow up on a pending scan instead of " +
|
|
294
294
|
"re-calling xpay_zauth_reposcan, which would pay again. Pass the sessionToken (the long " +
|
|
295
|
-
"JWT starting with \"eyJ\"), NOT the scanId
|
|
295
|
+
"JWT starting with \"eyJ\"), NOT the scanId - the scanId is rejected with a 401.",
|
|
296
296
|
input_schema: {
|
|
297
297
|
type: "object",
|
|
298
298
|
properties: {
|
|
299
299
|
sessionToken: {
|
|
300
300
|
type: "string",
|
|
301
|
-
description: "sessionToken from a pending xpay_zauth_reposcan result
|
|
301
|
+
description: "sessionToken from a pending xpay_zauth_reposcan result - the long JWT string " +
|
|
302
302
|
"(starts with \"eyJ\"), not the scanId. Valid ~1 hour after the scan started.",
|
|
303
303
|
},
|
|
304
304
|
},
|
|
@@ -497,7 +497,7 @@ export function forClaude(xpay, opts = {}) {
|
|
|
497
497
|
const sources = input.sources;
|
|
498
498
|
return xpay.discover({
|
|
499
499
|
query: input.query,
|
|
500
|
-
// Browsing a single catalog implies "show me what's there"
|
|
500
|
+
// Browsing a single catalog implies "show me what's there" - don't
|
|
501
501
|
// truncate to the mixed-results default.
|
|
502
502
|
limit: input.limit ?? (sources?.length === 1 ? 50 : 5),
|
|
503
503
|
networks: input.network ? [input.network] : undefined,
|
|
@@ -505,7 +505,7 @@ export function forClaude(xpay, opts = {}) {
|
|
|
505
505
|
});
|
|
506
506
|
},
|
|
507
507
|
xpay_use: async (input) => {
|
|
508
|
-
// Prefer the full resource object from xpay_discover
|
|
508
|
+
// Prefer the full resource object from xpay_discover - it carries
|
|
509
509
|
// pre-fetched accepts[] so we take the catalog path (no probe round-trip).
|
|
510
510
|
if (input.resource && typeof input.resource === "object") {
|
|
511
511
|
const parsed = ResourceSchema.safeParse(input.resource);
|
|
@@ -513,7 +513,7 @@ export function forClaude(xpay, opts = {}) {
|
|
|
513
513
|
return xpay.use(parsed.data, { body: input.body });
|
|
514
514
|
}
|
|
515
515
|
}
|
|
516
|
-
// Fallback: only a URL was provided
|
|
516
|
+
// Fallback: only a URL was provided - probe the resource for a 402 challenge.
|
|
517
517
|
const url = input.resourceUrl;
|
|
518
518
|
if (!url)
|
|
519
519
|
throw new Error("xpay_use: provide either `resource` (full object from xpay_discover) or `resourceUrl`");
|
|
@@ -544,13 +544,13 @@ export function forClaude(xpay, opts = {}) {
|
|
|
544
544
|
continue;
|
|
545
545
|
const signer = xpay.wallet.signer(n);
|
|
546
546
|
if (typeof signer.tokenBalances === "function") {
|
|
547
|
-
// Full token breakdown
|
|
547
|
+
// Full token breakdown - same path as the CLI. Solana balances are
|
|
548
548
|
// enriched via Jupiter: unknown mints get real symbols/names, and
|
|
549
549
|
// priceable tokens carry usdPrice/usdValue.
|
|
550
550
|
const raw = await signer.tokenBalances().catch(() => []);
|
|
551
551
|
let tokens = n === "solana" ? await enrichTokenBalances(raw) : raw;
|
|
552
552
|
// Robinhood Chain: the signer only knows a hardcoded token list, so
|
|
553
|
-
// pull the wallet's full ERC-20 holdings from the chain explorer
|
|
553
|
+
// pull the wallet's full ERC-20 holdings from the chain explorer -
|
|
554
554
|
// this is how memecoins bought via xpay_trade show up. Native ETH
|
|
555
555
|
// still comes from the signer.
|
|
556
556
|
if (n === "robinhood") {
|
|
@@ -635,7 +635,7 @@ export function forClaude(xpay, opts = {}) {
|
|
|
635
635
|
});
|
|
636
636
|
if (!isScanPending(result.data))
|
|
637
637
|
return { ...result, data: compactScanReport(result.data) };
|
|
638
|
-
// Paid + scan kicked off
|
|
638
|
+
// Paid + scan kicked off - poll the free status endpoint for a while.
|
|
639
639
|
// Poll responses don't echo the sessionToken, so keep the kickoff's
|
|
640
640
|
// copy and re-attach it if the scan outlives our window.
|
|
641
641
|
const sessionToken = result.data.sessionToken;
|
|
@@ -646,7 +646,7 @@ export function forClaude(xpay, opts = {}) {
|
|
|
646
646
|
data: {
|
|
647
647
|
...data,
|
|
648
648
|
sessionToken,
|
|
649
|
-
note: "Scan still running
|
|
649
|
+
note: "Scan still running - check later with xpay_zauth_scan_status, passing this " +
|
|
650
650
|
"sessionToken (the long JWT, NOT the scanId). Do not re-run xpay_zauth_reposcan " +
|
|
651
651
|
"for this repo; that pays again.",
|
|
652
652
|
},
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Robinhood Chain token discovery
|
|
2
|
+
* Robinhood Chain token discovery - GeckoTerminal public API.
|
|
3
3
|
*
|
|
4
4
|
* GeckoTerminal indexes Robinhood Chain under the network id "robinhood"
|
|
5
5
|
* (keyless, ~30 req/min). Used for the trending/new token tools and for the
|
|
6
|
-
* USD pricing attached to trade quotes. Discovery is read-only market data
|
|
6
|
+
* USD pricing attached to trade quotes. Discovery is read-only market data -
|
|
7
7
|
* the trade path itself never depends on it.
|
|
8
8
|
*/
|
|
9
9
|
export interface DiscoveredToken {
|
|
@@ -18,7 +18,7 @@ export interface DiscoveredToken {
|
|
|
18
18
|
priceChange24hPct?: number;
|
|
19
19
|
/** Liquidity pool: 20-byte address (Uniswap v3) or 32-byte id (v4). */
|
|
20
20
|
pool: string;
|
|
21
|
-
/** True for v4 pool ids
|
|
21
|
+
/** True for v4 pool ids - xpay_trade only routes v3 pools today. */
|
|
22
22
|
poolIsV4: boolean;
|
|
23
23
|
dex?: string;
|
|
24
24
|
poolCreatedAt?: string;
|
|
@@ -27,7 +27,7 @@ export interface DiscoveredToken {
|
|
|
27
27
|
export declare function trendingTokens(opts?: {
|
|
28
28
|
limit?: number;
|
|
29
29
|
}): Promise<DiscoveredToken[]>;
|
|
30
|
-
/** Tokens from the most recently created pools (fresh launches
|
|
30
|
+
/** Tokens from the most recently created pools (fresh launches - high risk). */
|
|
31
31
|
export declare function newTokens(opts?: {
|
|
32
32
|
limit?: number;
|
|
33
33
|
}): Promise<DiscoveredToken[]>;
|
|
@@ -44,14 +44,14 @@ export interface TokenHolding {
|
|
|
44
44
|
/**
|
|
45
45
|
* All ERC-20 balances a wallet holds on Robinhood Chain, via the chain's
|
|
46
46
|
* Blockscout explorer API (keyless). This is what surfaces memecoins bought
|
|
47
|
-
* through `xpay trade`
|
|
47
|
+
* through `xpay trade` - the on-chain signer only knows a hardcoded token list.
|
|
48
48
|
* Best-effort: returns [] if the explorer is unreachable.
|
|
49
49
|
*/
|
|
50
50
|
export declare function robinhoodHoldings(address: string): Promise<TokenHolding[]>;
|
|
51
51
|
/**
|
|
52
52
|
* Resolve a token symbol to its contract address via trending + new pools.
|
|
53
53
|
* Throws when the symbol is unknown or matches several distinct contracts
|
|
54
|
-
* (memecoin symbols are not unique
|
|
54
|
+
* (memecoin symbols are not unique - pass the address instead).
|
|
55
55
|
*/
|
|
56
56
|
export declare function resolveTokenBySymbol(symbol: string): Promise<DiscoveredToken>;
|
|
57
57
|
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Robinhood Chain token discovery
|
|
2
|
+
* Robinhood Chain token discovery - GeckoTerminal public API.
|
|
3
3
|
*
|
|
4
4
|
* GeckoTerminal indexes Robinhood Chain under the network id "robinhood"
|
|
5
5
|
* (keyless, ~30 req/min). Used for the trending/new token tools and for the
|
|
6
|
-
* USD pricing attached to trade quotes. Discovery is read-only market data
|
|
6
|
+
* USD pricing attached to trade quotes. Discovery is read-only market data -
|
|
7
7
|
* the trade path itself never depends on it.
|
|
8
8
|
*/
|
|
9
9
|
const DEFAULT_ENDPOINT = "https://api.geckoterminal.com/api/v2";
|
|
@@ -31,7 +31,7 @@ function poolsToTokens(payload, limit) {
|
|
|
31
31
|
const out = [];
|
|
32
32
|
for (const pool of data) {
|
|
33
33
|
const attrs = pool.attributes ?? {};
|
|
34
|
-
// base_token id looks like "robinhood_0x020b…"
|
|
34
|
+
// base_token id looks like "robinhood_0x020b…" - address after the prefix.
|
|
35
35
|
const baseId = pool.relationships?.base_token?.data?.id ?? "";
|
|
36
36
|
const address = baseId.includes("_") ? baseId.slice(baseId.indexOf("_") + 1) : "";
|
|
37
37
|
if (!address.startsWith("0x"))
|
|
@@ -64,7 +64,7 @@ export async function trendingTokens(opts = {}) {
|
|
|
64
64
|
const payload = await gtFetch(`/networks/${GT_NETWORK}/trending_pools?page=1`);
|
|
65
65
|
return poolsToTokens(payload, opts.limit ?? 10);
|
|
66
66
|
}
|
|
67
|
-
/** Tokens from the most recently created pools (fresh launches
|
|
67
|
+
/** Tokens from the most recently created pools (fresh launches - high risk). */
|
|
68
68
|
export async function newTokens(opts = {}) {
|
|
69
69
|
const payload = await gtFetch(`/networks/${GT_NETWORK}/new_pools?page=1`);
|
|
70
70
|
return poolsToTokens(payload, opts.limit ?? 10);
|
|
@@ -83,7 +83,7 @@ export async function tokenPriceUsd(address) {
|
|
|
83
83
|
/**
|
|
84
84
|
* All ERC-20 balances a wallet holds on Robinhood Chain, via the chain's
|
|
85
85
|
* Blockscout explorer API (keyless). This is what surfaces memecoins bought
|
|
86
|
-
* through `xpay trade`
|
|
86
|
+
* through `xpay trade` - the on-chain signer only knows a hardcoded token list.
|
|
87
87
|
* Best-effort: returns [] if the explorer is unreachable.
|
|
88
88
|
*/
|
|
89
89
|
export async function robinhoodHoldings(address) {
|
|
@@ -132,7 +132,7 @@ function atomsToHuman(atoms, decimals) {
|
|
|
132
132
|
/**
|
|
133
133
|
* Resolve a token symbol to its contract address via trending + new pools.
|
|
134
134
|
* Throws when the symbol is unknown or matches several distinct contracts
|
|
135
|
-
* (memecoin symbols are not unique
|
|
135
|
+
* (memecoin symbols are not unique - pass the address instead).
|
|
136
136
|
*/
|
|
137
137
|
export async function resolveTokenBySymbol(symbol) {
|
|
138
138
|
const wanted = symbol.trim().toUpperCase();
|
|
@@ -146,7 +146,7 @@ export async function resolveTokenBySymbol(symbol) {
|
|
|
146
146
|
matches.set(t.address.toLowerCase(), t);
|
|
147
147
|
}
|
|
148
148
|
if (matches.size === 0) {
|
|
149
|
-
throw new Error(`xpay.trade: unknown token "${symbol}"
|
|
149
|
+
throw new Error(`xpay.trade: unknown token "${symbol}" - not in Robinhood Chain trending/new pools. ` +
|
|
150
150
|
"Pass the token's contract address instead.");
|
|
151
151
|
}
|
|
152
152
|
if (matches.size > 1) {
|
package/dist/trading/index.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Robinhood Chain token trading
|
|
2
|
+
* Robinhood Chain token trading - direct Uniswap V3 (NOXA Fun launchpad).
|
|
3
3
|
*
|
|
4
4
|
* NOXA Fun tokens (fun.noxa.fi) launch straight into Uniswap V3 pools quoted
|
|
5
5
|
* in WETH, so trading them is plain V3: quote via QuoterV2, execute via
|
|
6
|
-
* SwapRouter02. No third-party API or key
|
|
6
|
+
* SwapRouter02. No third-party API or key - everything is on-chain against the
|
|
7
7
|
* chain's own RPC. GMGN was ruled out (no chain-4663 support, gated API).
|
|
8
8
|
*
|
|
9
9
|
* Scope v1: ETH ⇄ token only (buy with native ETH, sell back to native ETH).
|
|
10
10
|
* The router auto-wraps ETH on buys; sells unwrap WETH → ETH via multicall.
|
|
11
11
|
*
|
|
12
|
-
* A trade is irreversible and risks wallet value, so
|
|
12
|
+
* A trade is irreversible and risks wallet value, so, exactly like `swap`,
|
|
13
13
|
* it runs through the guardrail *before* signing.
|
|
14
14
|
*/
|
|
15
15
|
import type { Network } from "../types.js";
|
|
@@ -24,7 +24,7 @@ export declare const RH_CONTRACTS: {
|
|
|
24
24
|
readonly weth: "0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73";
|
|
25
25
|
readonly noxaLauncher: "0xD9eC2db5f3D1b236843925949fe5bd8a3836FCcB";
|
|
26
26
|
};
|
|
27
|
-
/** Trading settings
|
|
27
|
+
/** Trading settings - profile `config.trading`, overridable per call. */
|
|
28
28
|
export interface TradeConfig {
|
|
29
29
|
/** Default max slippage in bps (100 = 1%). */
|
|
30
30
|
slippageBps?: number;
|
|
@@ -75,7 +75,7 @@ export interface TradeQuote {
|
|
|
75
75
|
/** Input-side USD estimate (GeckoTerminal spot). Best-effort. */
|
|
76
76
|
usdValue?: number;
|
|
77
77
|
/**
|
|
78
|
-
* True when the counterparty token was NOT confirmed as a NOXA Fun launch
|
|
78
|
+
* True when the counterparty token was NOT confirmed as a NOXA Fun launch -
|
|
79
79
|
* it may still be tradeable, but treat the pool/price with more suspicion.
|
|
80
80
|
*/
|
|
81
81
|
unverified: boolean;
|
|
@@ -86,7 +86,7 @@ export interface TradeResult extends TradeQuote {
|
|
|
86
86
|
/** Transaction hash on Robinhood Chain. */
|
|
87
87
|
txHash: string;
|
|
88
88
|
}
|
|
89
|
-
/** Quote a trade without executing
|
|
89
|
+
/** Quote a trade without executing - no guardrail, no signing, no funds moved. */
|
|
90
90
|
export declare function tradeQuote(args: Omit<TradeArgs, "guardrail">): Promise<TradeQuote>;
|
|
91
91
|
/** Execute a trade on Robinhood Chain. Guardrail runs before signing. */
|
|
92
92
|
export declare function trade(args: TradeArgs): Promise<TradeResult>;
|
package/dist/trading/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Robinhood Chain token trading
|
|
2
|
+
* Robinhood Chain token trading - direct Uniswap V3 (NOXA Fun launchpad).
|
|
3
3
|
*
|
|
4
4
|
* NOXA Fun tokens (fun.noxa.fi) launch straight into Uniswap V3 pools quoted
|
|
5
5
|
* in WETH, so trading them is plain V3: quote via QuoterV2, execute via
|
|
6
|
-
* SwapRouter02. No third-party API or key
|
|
6
|
+
* SwapRouter02. No third-party API or key - everything is on-chain against the
|
|
7
7
|
* chain's own RPC. GMGN was ruled out (no chain-4663 support, gated API).
|
|
8
8
|
*
|
|
9
9
|
* Scope v1: ETH ⇄ token only (buy with native ETH, sell back to native ETH).
|
|
10
10
|
* The router auto-wraps ETH on buys; sells unwrap WETH → ETH via multicall.
|
|
11
11
|
*
|
|
12
|
-
* A trade is irreversible and risks wallet value, so
|
|
12
|
+
* A trade is irreversible and risks wallet value, so, exactly like `swap`,
|
|
13
13
|
* it runs through the guardrail *before* signing.
|
|
14
14
|
*/
|
|
15
15
|
import { Contract, MaxUint256, getAddress, isAddress } from "ethers";
|
|
@@ -25,7 +25,7 @@ export const RH_CONTRACTS = {
|
|
|
25
25
|
};
|
|
26
26
|
/** Fee tiers probed (in bps*100) when the pool fee isn't known up front. NOXA uses 10000 (1%). */
|
|
27
27
|
const FEE_TIERS = [10000, 3000, 500, 100];
|
|
28
|
-
const DEFAULT_SLIPPAGE_BPS = 100; // 1%
|
|
28
|
+
const DEFAULT_SLIPPAGE_BPS = 100; // 1% - memecoin pools with a 1% fee move fast.
|
|
29
29
|
const MAX_SLIPPAGE_BPS = 5000; // 50% hard ceiling.
|
|
30
30
|
const QUOTER_ABI = [
|
|
31
31
|
"function quoteExactInputSingle((address tokenIn,address tokenOut,uint256 amountIn,uint24 fee,uint160 sqrtPriceLimitX96)) returns (uint256 amountOut,uint160 sqrtPriceX96After,uint32 initializedTicksCrossed,uint256 gasEstimate)",
|
|
@@ -42,7 +42,7 @@ const ERC20_ABI = [
|
|
|
42
42
|
"function allowance(address owner,address spender) view returns (uint256)",
|
|
43
43
|
"function approve(address spender,uint256 amount) returns (bool)",
|
|
44
44
|
];
|
|
45
|
-
// NOXA Fun token surface
|
|
45
|
+
// NOXA Fun token surface - non-standard getters a plain ERC-20 lacks.
|
|
46
46
|
const NOXA_TOKEN_ABI = [
|
|
47
47
|
"function liquidityPool() view returns (address)",
|
|
48
48
|
"function pairToken() view returns (address)",
|
|
@@ -54,14 +54,14 @@ const NOXA_TOKEN_ABI = [
|
|
|
54
54
|
// SwapRouter02 recipient constant: keep output in the router for a follow-up unwrap.
|
|
55
55
|
const ROUTER_ADDRESS_THIS = "0x0000000000000000000000000000000000000002";
|
|
56
56
|
const NATIVE_ETH = { symbol: "ETH", decimals: 18 };
|
|
57
|
-
/** Quote a trade without executing
|
|
57
|
+
/** Quote a trade without executing - no guardrail, no signing, no funds moved. */
|
|
58
58
|
export async function tradeQuote(args) {
|
|
59
59
|
return (await prepare(args)).quote;
|
|
60
60
|
}
|
|
61
61
|
/** Execute a trade on Robinhood Chain. Guardrail runs before signing. */
|
|
62
62
|
export async function trade(args) {
|
|
63
63
|
const { quote, ethersWallet, side, token } = await prepare(args);
|
|
64
|
-
// Guardrail *before* signing
|
|
64
|
+
// Guardrail *before* signing - same security boundary as swap/pay/transfer.
|
|
65
65
|
// Reuse the "swap" scheme so host-whitelist is skipped and the USD estimate
|
|
66
66
|
// is priced from `extra.usdEstimate` (see Guardrail.estimateUsd).
|
|
67
67
|
await args.guardrail.check({
|
|
@@ -140,7 +140,7 @@ async function prepare(args) {
|
|
|
140
140
|
throw new Error(`xpay.trade: only Robinhood Chain ("robinhood") is supported (got "${network}")`);
|
|
141
141
|
}
|
|
142
142
|
if (!args.wallet.has(ROBINHOOD_NETWORK)) {
|
|
143
|
-
throw new Error('xpay.trade: no "robinhood" signer configured
|
|
143
|
+
throw new Error('xpay.trade: no "robinhood" signer configured - add it to your profile networks');
|
|
144
144
|
}
|
|
145
145
|
if (!Number.isFinite(args.amount) || args.amount <= 0) {
|
|
146
146
|
throw new Error("xpay.trade: amount must be a positive number");
|
|
@@ -180,7 +180,7 @@ async function prepare(args) {
|
|
|
180
180
|
// A buy that would breach a cap is failed up front with a clear message
|
|
181
181
|
// rather than reverting opaquely on-chain. The cap info is surfaced on the
|
|
182
182
|
// quote only when this trade is actually within striking distance of a cap
|
|
183
|
-
// (>25% of it)
|
|
183
|
+
// (>25% of it) - established tokens leave a far-future endBlock set with wide
|
|
184
184
|
// caps, so reporting it unconditionally would be misleading noise.
|
|
185
185
|
let boundRestriction;
|
|
186
186
|
if (restriction && side === "buy") {
|
|
@@ -232,7 +232,7 @@ function isEth(ref) {
|
|
|
232
232
|
async function resolveToken(wallet, ref) {
|
|
233
233
|
let address = ref.trim();
|
|
234
234
|
if (!isAddress(address)) {
|
|
235
|
-
// Treat as a symbol
|
|
235
|
+
// Treat as a symbol - resolve via GeckoTerminal trending/new pools.
|
|
236
236
|
const found = await resolveTokenBySymbol(address);
|
|
237
237
|
address = found.address;
|
|
238
238
|
}
|
|
@@ -244,7 +244,7 @@ async function resolveToken(wallet, ref) {
|
|
|
244
244
|
]);
|
|
245
245
|
return { address, symbol, decimals };
|
|
246
246
|
}
|
|
247
|
-
/** Probe a token's NOXA Fun surface
|
|
247
|
+
/** Probe a token's NOXA Fun surface - pool, fee tier, launch restrictions. */
|
|
248
248
|
async function inspectToken(wallet, address) {
|
|
249
249
|
const t = new Contract(address, NOXA_TOKEN_ABI, wallet);
|
|
250
250
|
try {
|
|
@@ -277,7 +277,7 @@ async function inspectToken(wallet, address) {
|
|
|
277
277
|
return meta;
|
|
278
278
|
}
|
|
279
279
|
catch {
|
|
280
|
-
// Not a NOXA token (getters reverted)
|
|
280
|
+
// Not a NOXA token (getters reverted) - still tradeable if a V3 pool exists.
|
|
281
281
|
return { isNoxa: false };
|
|
282
282
|
}
|
|
283
283
|
}
|
|
@@ -287,7 +287,7 @@ async function quoteBestFee(wallet, tokenIn, tokenOut, amountIn, fees) {
|
|
|
287
287
|
let lastErr;
|
|
288
288
|
for (const fee of fees) {
|
|
289
289
|
try {
|
|
290
|
-
// QuoterV2 quote fns are non-view (revert-to-return)
|
|
290
|
+
// QuoterV2 quote fns are non-view (revert-to-return) - must use staticCall.
|
|
291
291
|
const res = await quoter.quoteExactInputSingle.staticCall({
|
|
292
292
|
tokenIn,
|
|
293
293
|
tokenOut,
|