Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions experiments/test-address-conversion.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* Test address format conversion using @ton/core
*/

import { createRequire } from "node:module";
import { realpathSync } from "node:fs";
const _require = createRequire(realpathSync(process.argv[1]));
const { Address } = _require("@ton/core");

// Test pool address from the issue
const testAddresses = [
"EQCUUQ4JkETDPTRLlNaMBx5vGFhMn0OC1184AfdnBKKaGK2M",
"EQC_R1hCuGK8Q8FfHJFbimp0-EHznTuyJsdJjDl7swWYnrF0",
"0:00c49a30777e2b69dc3a43f93218286e5e8c7fbb303a60195caa3385b838df42",
];

for (const addr of testAddresses) {
try {
const parsed = Address.parse(addr);
const raw = `0:${parsed.hash.toString("hex")}`;
console.log(`Input: ${addr}`);
console.log(`Raw: ${raw}`);
console.log();
} catch (e) {
console.error(`Failed to parse ${addr}: ${e.message}`);
}
}
62 changes: 62 additions & 0 deletions experiments/test-pool-address.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/**
* Test to reproduce the tonco_get_pool_stats pool_address undefined bug
*
* Tests what happens when we query the TONCO GraphQL indexer with
* different address filter field names.
*/

const INDEXER_URL = "https://indexer.tonco.io/graphql";
const TEST_POOL = "EQCUUQ4JkETDPTRLlNaMBx5vGFhMn0OC1184AfdnBKKaGK2M";

async function gqlQuery(query, variables = {}) {
const res = await fetch(INDEXER_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables }),
signal: AbortSignal.timeout(15000),
});
if (!res.ok) {
const text = await res.text().catch(() => "");
throw new Error(`TONCO indexer error: ${res.status} ${text.slice(0, 200)}`);
}
const json = await res.json();
console.log("Raw response:", JSON.stringify(json, null, 2).slice(0, 1000));
return json;
}

// Test 1: Current implementation - query by address
console.log("\n=== Test 1: Query by 'address' field (current implementation) ===");
try {
const query1 = `
query GetPool($where: PoolWhere) {
pools(where: $where) {
address
name
}
}
`;
const result1 = await gqlQuery(query1, { where: { address: TEST_POOL } });
console.log("Result:", JSON.stringify(result1, null, 2).slice(0, 500));
} catch (e) {
console.error("Error:", e.message);
}

// Test 2: Try with isInitialized + address
console.log("\n=== Test 2: Introspection - what fields does PoolWhere have? ===");
try {
const introspect = `
{
__type(name: "PoolWhere") {
name
inputFields {
name
type { name kind }
}
}
}
`;
const result2 = await gqlQuery(introspect, {});
console.log("PoolWhere fields:", JSON.stringify(result2, null, 2).slice(0, 2000));
} catch (e) {
console.error("Error:", e.message);
}
58 changes: 58 additions & 0 deletions experiments/test-pool-address2.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Test different address formats for the pool query
*/

const INDEXER_URL = "https://indexer.tonco.io/graphql";

// Pool from issue: EQCUUQ4JkETDPTRLlNaMBx5vGFhMn0OC1184AfdnBKKaGK2M (bounceable)
// This is the address we need to find in raw format.
// Let's first try to list pools and find this one.

async function gqlQuery(query, variables = {}) {
const res = await fetch(INDEXER_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables }),
signal: AbortSignal.timeout(15000),
});
const json = await res.json();
return json;
}

// First, find the pool by listing pools and check its address format in the response
console.log("=== Test: List pools to see what address format indexer uses ===");
const listQuery = `
query ListPools($where: PoolWhere, $filter: Filter) {
pools(where: $where, filter: $filter) {
address
name
}
}
`;
const listResult = await gqlQuery(listQuery, { where: { isInitialized: true }, filter: { first: 3 } });
console.log("Pool list result:", JSON.stringify(listResult.data?.pools, null, 2));

// The pool from the issue is EQCUUQ4JkETDPTRLlNaMBx5vGFhMn0OC1184AfdnBKKaGK2M
// Let's try the address in raw format: figure out if it's EQ (bounceable) or raw
// EQ... is bounceable Base64url encoded. The raw format would be 0:...

// Try with the actual address from the issue - EQ format
console.log("\n=== Test: Query with bounceable EQ address format ===");
const eq1 = await gqlQuery(`
query GetPool($where: PoolWhere) {
pools(where: $where) { address name }
}
`, { where: { address: "EQCUUQ4JkETDPTRLlNaMBx5vGFhMn0OC1184AfdnBKKaGK2M" } });
console.log("Result (EQ format):", JSON.stringify(eq1, null, 2).slice(0, 600));

// Try from the list result - use a known valid address
if (listResult.data?.pools?.[0]?.address) {
const knownAddr = listResult.data.pools[0].address;
console.log(`\n=== Test: Query with known address from list: ${knownAddr} ===`);
const knownResult = await gqlQuery(`
query GetPool($where: PoolWhere) {
pools(where: $where) { address name }
}
`, { where: { address: knownAddr } });
console.log("Result:", JSON.stringify(knownResult, null, 2).slice(0, 600));
}
75 changes: 75 additions & 0 deletions experiments/test-pool-stats-fix.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* Test fix for tonco_get_pool_stats pool_address undefined bug
*
* The fix: convert any user-provided address (EQ.../UQ.../raw) to raw 0:xxx format
* before passing to the TONCO indexer, which only accepts raw format.
*/

import { createRequire } from "node:module";
import { realpathSync } from "node:fs";
const _require = createRequire(realpathSync(process.argv[1]));
const { Address } = _require("@ton/core");

const INDEXER_URL = "https://indexer.tonco.io/graphql";

async function gqlQuery(query, variables = {}) {
const res = await fetch(INDEXER_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables }),
signal: AbortSignal.timeout(15000),
});
const json = await res.json();
return json;
}

/**
* Normalize a TON address to raw format (0:xxxx) that TONCO indexer accepts.
* The indexer's server-side resolvers call Address.parseRaw() which crashes
* on bounceable (EQ.../UQ...) or non-standard formats.
*/
function normalizeToRaw(addr) {
if (!addr) return addr;
try {
const parsed = Address.parse(addr.trim());
return `0:${parsed.hash.toString("hex")}`;
} catch {
// If parse fails, return as-is (indexer will give its own error)
return addr.trim();
}
}

const query = `
query GetPool($where: PoolWhere) {
pools(where: $where) {
address
name
totalValueLockedUsd
}
}
`;

// Test with bounceable address (EQ... format) - the bug case
const testCases = [
"EQCUUQ4JkETDPTRLlNaMBx5vGFhMn0OC1184AfdnBKKaGK2M",
"EQC_R1hCuGK8Q8FfHJFbimp0-EHznTuyJsdJjDl7swWYnrF0",
"0:00c49a30777e2b69dc3a43f93218286e5e8c7fbb303a60195caa3385b838df42",
];

for (const addr of testCases) {
const normalized = normalizeToRaw(addr);
console.log(`\nInput: ${addr}`);
console.log(`Normalized: ${normalized}`);

const result = await gqlQuery(query, { where: { address: normalized } });
if (result.errors) {
console.log(`Error: ${result.errors[0].message}`);
} else {
const pools = result.data?.pools ?? [];
if (pools.length > 0) {
console.log(`Found pool: ${pools[0].name}, TVL: $${parseFloat(pools[0].totalValueLockedUsd || 0).toFixed(2)}`);
} else {
console.log(`No pool found with this address`);
}
}
}
40 changes: 33 additions & 7 deletions plugins/tonco-dex/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,32 @@ const TON_RAW_ADDR = "0:00000000000000000000000000000000000000000000000000000000
/** Module-level SDK reference (set in tools(sdk) factory) */
let _sdk = null;

// ---------------------------------------------------------------------------
// Address helpers
// ---------------------------------------------------------------------------

/**
* Normalize any valid TON address to raw format (0:xxxx…hex) required by
* the TONCO GraphQL indexer. The indexer's server-side resolver calls
* Address.parseRaw() internally, which crashes with "The first argument must
* be of type string…" when it receives a bounceable/user-friendly address
* (EQ…/UQ…) instead of the raw 0:hex form.
*
* @param {string} addr - Any TON address (EQ…, UQ…, or 0:hex)
* @returns {string} Raw address in "0:xxxx…" form, or the original string
* if parsing fails (the indexer will then return its own error)
*/
function normalizeToRaw(addr) {
if (typeof addr !== "string" || !addr.trim()) return addr;
try {
const parsed = Address.parse(addr.trim());
return `0:${parsed.hash.toString("hex")}`;
} catch (e) {
console.warn(`[tonco-dex] normalizeToRaw: failed to parse address "${addr}", using as-is:`, e.message);
return addr.trim();
}
}

// ---------------------------------------------------------------------------
// GraphQL helper
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -340,7 +366,7 @@ const toncoGetPoolStats = {

execute: async (params) => {
try {
const poolAddr = params.pool_address.trim();
const poolAddr = normalizeToRaw(params.pool_address);

const query = `
query GetPool($where: PoolWhere) {
Expand Down Expand Up @@ -625,8 +651,8 @@ const toncoSwapQuote = {
const isTonIn = tokenInAddr.toUpperCase() === "TON";
const isTonOut = tokenOutAddr.toUpperCase() === "TON";

const resolvedInAddr = isTonIn ? TON_RAW_ADDR : tokenInAddr;
const resolvedOutAddr = isTonOut ? TON_RAW_ADDR : tokenOutAddr;
const resolvedInAddr = isTonIn ? TON_RAW_ADDR : normalizeToRaw(tokenInAddr);
const resolvedOutAddr = isTonOut ? TON_RAW_ADDR : normalizeToRaw(tokenOutAddr);

// Fetch pool data for this pair from indexer.
// We query both orderings (jetton0/jetton1 vs jetton1/jetton0) because the indexer
Expand Down Expand Up @@ -834,8 +860,8 @@ const toncoExecuteSwap = {
const isTonOut = tokenOutAddr.toUpperCase() === "TON";

// Use the same TON_RAW_ADDR constant as tonco_swap_quote for consistent address resolution
const resolvedInAddr = isTonIn ? TON_RAW_ADDR : tokenInAddr;
const resolvedOutAddr = isTonOut ? TON_RAW_ADDR : tokenOutAddr;
const resolvedInAddr = isTonIn ? TON_RAW_ADDR : normalizeToRaw(tokenInAddr);
const resolvedOutAddr = isTonOut ? TON_RAW_ADDR : normalizeToRaw(tokenOutAddr);

// Fetch pool from indexer
const query = `
Expand Down Expand Up @@ -1076,7 +1102,7 @@ const toncoGetPositions = {

const where = {
owner: ownerAddr,
...(params.pool_address ? { pool: params.pool_address.trim() } : {}),
...(params.pool_address ? { pool: normalizeToRaw(params.pool_address) } : {}),
};

// NOTE: orderDirection is broken server-side; fetch more and sort client-side.
Expand Down Expand Up @@ -1220,7 +1246,7 @@ const toncoGetPositionFees = {
const { liquidity, tickLow, tickHigh, feeGrowthInside0LastX128, feeGrowthInside1LastX128 } = positionInfo;

// Get pool address from indexer if not provided
let poolAddress = params.pool_address?.trim();
let poolAddress = params.pool_address ? normalizeToRaw(params.pool_address) : undefined;
if (!poolAddress) {
const nftData = await positionContract.getData();
const collectionAddr = nftData?.collection?.toString();
Expand Down
Loading
Loading