Skip to content

Commit 77904ea

Browse files
authored
Merge pull request #142 from konard/issue-140-f6cbcef6866e
fix(tonco-dex): normalize addresses to raw format to fix pool_address undefined crash
2 parents 65ba162 + f907404 commit 77904ea

File tree

6 files changed

+372
-7
lines changed

6 files changed

+372
-7
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* Test address format conversion using @ton/core
3+
*/
4+
5+
import { createRequire } from "node:module";
6+
import { realpathSync } from "node:fs";
7+
const _require = createRequire(realpathSync(process.argv[1]));
8+
const { Address } = _require("@ton/core");
9+
10+
// Test pool address from the issue
11+
const testAddresses = [
12+
"EQCUUQ4JkETDPTRLlNaMBx5vGFhMn0OC1184AfdnBKKaGK2M",
13+
"EQC_R1hCuGK8Q8FfHJFbimp0-EHznTuyJsdJjDl7swWYnrF0",
14+
"0:00c49a30777e2b69dc3a43f93218286e5e8c7fbb303a60195caa3385b838df42",
15+
];
16+
17+
for (const addr of testAddresses) {
18+
try {
19+
const parsed = Address.parse(addr);
20+
const raw = `0:${parsed.hash.toString("hex")}`;
21+
console.log(`Input: ${addr}`);
22+
console.log(`Raw: ${raw}`);
23+
console.log();
24+
} catch (e) {
25+
console.error(`Failed to parse ${addr}: ${e.message}`);
26+
}
27+
}

experiments/test-pool-address.mjs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* Test to reproduce the tonco_get_pool_stats pool_address undefined bug
3+
*
4+
* Tests what happens when we query the TONCO GraphQL indexer with
5+
* different address filter field names.
6+
*/
7+
8+
const INDEXER_URL = "https://indexer.tonco.io/graphql";
9+
const TEST_POOL = "EQCUUQ4JkETDPTRLlNaMBx5vGFhMn0OC1184AfdnBKKaGK2M";
10+
11+
async function gqlQuery(query, variables = {}) {
12+
const res = await fetch(INDEXER_URL, {
13+
method: "POST",
14+
headers: { "Content-Type": "application/json" },
15+
body: JSON.stringify({ query, variables }),
16+
signal: AbortSignal.timeout(15000),
17+
});
18+
if (!res.ok) {
19+
const text = await res.text().catch(() => "");
20+
throw new Error(`TONCO indexer error: ${res.status} ${text.slice(0, 200)}`);
21+
}
22+
const json = await res.json();
23+
console.log("Raw response:", JSON.stringify(json, null, 2).slice(0, 1000));
24+
return json;
25+
}
26+
27+
// Test 1: Current implementation - query by address
28+
console.log("\n=== Test 1: Query by 'address' field (current implementation) ===");
29+
try {
30+
const query1 = `
31+
query GetPool($where: PoolWhere) {
32+
pools(where: $where) {
33+
address
34+
name
35+
}
36+
}
37+
`;
38+
const result1 = await gqlQuery(query1, { where: { address: TEST_POOL } });
39+
console.log("Result:", JSON.stringify(result1, null, 2).slice(0, 500));
40+
} catch (e) {
41+
console.error("Error:", e.message);
42+
}
43+
44+
// Test 2: Try with isInitialized + address
45+
console.log("\n=== Test 2: Introspection - what fields does PoolWhere have? ===");
46+
try {
47+
const introspect = `
48+
{
49+
__type(name: "PoolWhere") {
50+
name
51+
inputFields {
52+
name
53+
type { name kind }
54+
}
55+
}
56+
}
57+
`;
58+
const result2 = await gqlQuery(introspect, {});
59+
console.log("PoolWhere fields:", JSON.stringify(result2, null, 2).slice(0, 2000));
60+
} catch (e) {
61+
console.error("Error:", e.message);
62+
}

experiments/test-pool-address2.mjs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Test different address formats for the pool query
3+
*/
4+
5+
const INDEXER_URL = "https://indexer.tonco.io/graphql";
6+
7+
// Pool from issue: EQCUUQ4JkETDPTRLlNaMBx5vGFhMn0OC1184AfdnBKKaGK2M (bounceable)
8+
// This is the address we need to find in raw format.
9+
// Let's first try to list pools and find this one.
10+
11+
async function gqlQuery(query, variables = {}) {
12+
const res = await fetch(INDEXER_URL, {
13+
method: "POST",
14+
headers: { "Content-Type": "application/json" },
15+
body: JSON.stringify({ query, variables }),
16+
signal: AbortSignal.timeout(15000),
17+
});
18+
const json = await res.json();
19+
return json;
20+
}
21+
22+
// First, find the pool by listing pools and check its address format in the response
23+
console.log("=== Test: List pools to see what address format indexer uses ===");
24+
const listQuery = `
25+
query ListPools($where: PoolWhere, $filter: Filter) {
26+
pools(where: $where, filter: $filter) {
27+
address
28+
name
29+
}
30+
}
31+
`;
32+
const listResult = await gqlQuery(listQuery, { where: { isInitialized: true }, filter: { first: 3 } });
33+
console.log("Pool list result:", JSON.stringify(listResult.data?.pools, null, 2));
34+
35+
// The pool from the issue is EQCUUQ4JkETDPTRLlNaMBx5vGFhMn0OC1184AfdnBKKaGK2M
36+
// Let's try the address in raw format: figure out if it's EQ (bounceable) or raw
37+
// EQ... is bounceable Base64url encoded. The raw format would be 0:...
38+
39+
// Try with the actual address from the issue - EQ format
40+
console.log("\n=== Test: Query with bounceable EQ address format ===");
41+
const eq1 = await gqlQuery(`
42+
query GetPool($where: PoolWhere) {
43+
pools(where: $where) { address name }
44+
}
45+
`, { where: { address: "EQCUUQ4JkETDPTRLlNaMBx5vGFhMn0OC1184AfdnBKKaGK2M" } });
46+
console.log("Result (EQ format):", JSON.stringify(eq1, null, 2).slice(0, 600));
47+
48+
// Try from the list result - use a known valid address
49+
if (listResult.data?.pools?.[0]?.address) {
50+
const knownAddr = listResult.data.pools[0].address;
51+
console.log(`\n=== Test: Query with known address from list: ${knownAddr} ===`);
52+
const knownResult = await gqlQuery(`
53+
query GetPool($where: PoolWhere) {
54+
pools(where: $where) { address name }
55+
}
56+
`, { where: { address: knownAddr } });
57+
console.log("Result:", JSON.stringify(knownResult, null, 2).slice(0, 600));
58+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Test fix for tonco_get_pool_stats pool_address undefined bug
3+
*
4+
* The fix: convert any user-provided address (EQ.../UQ.../raw) to raw 0:xxx format
5+
* before passing to the TONCO indexer, which only accepts raw format.
6+
*/
7+
8+
import { createRequire } from "node:module";
9+
import { realpathSync } from "node:fs";
10+
const _require = createRequire(realpathSync(process.argv[1]));
11+
const { Address } = _require("@ton/core");
12+
13+
const INDEXER_URL = "https://indexer.tonco.io/graphql";
14+
15+
async function gqlQuery(query, variables = {}) {
16+
const res = await fetch(INDEXER_URL, {
17+
method: "POST",
18+
headers: { "Content-Type": "application/json" },
19+
body: JSON.stringify({ query, variables }),
20+
signal: AbortSignal.timeout(15000),
21+
});
22+
const json = await res.json();
23+
return json;
24+
}
25+
26+
/**
27+
* Normalize a TON address to raw format (0:xxxx) that TONCO indexer accepts.
28+
* The indexer's server-side resolvers call Address.parseRaw() which crashes
29+
* on bounceable (EQ.../UQ...) or non-standard formats.
30+
*/
31+
function normalizeToRaw(addr) {
32+
if (!addr) return addr;
33+
try {
34+
const parsed = Address.parse(addr.trim());
35+
return `0:${parsed.hash.toString("hex")}`;
36+
} catch {
37+
// If parse fails, return as-is (indexer will give its own error)
38+
return addr.trim();
39+
}
40+
}
41+
42+
const query = `
43+
query GetPool($where: PoolWhere) {
44+
pools(where: $where) {
45+
address
46+
name
47+
totalValueLockedUsd
48+
}
49+
}
50+
`;
51+
52+
// Test with bounceable address (EQ... format) - the bug case
53+
const testCases = [
54+
"EQCUUQ4JkETDPTRLlNaMBx5vGFhMn0OC1184AfdnBKKaGK2M",
55+
"EQC_R1hCuGK8Q8FfHJFbimp0-EHznTuyJsdJjDl7swWYnrF0",
56+
"0:00c49a30777e2b69dc3a43f93218286e5e8c7fbb303a60195caa3385b838df42",
57+
];
58+
59+
for (const addr of testCases) {
60+
const normalized = normalizeToRaw(addr);
61+
console.log(`\nInput: ${addr}`);
62+
console.log(`Normalized: ${normalized}`);
63+
64+
const result = await gqlQuery(query, { where: { address: normalized } });
65+
if (result.errors) {
66+
console.log(`Error: ${result.errors[0].message}`);
67+
} else {
68+
const pools = result.data?.pools ?? [];
69+
if (pools.length > 0) {
70+
console.log(`Found pool: ${pools[0].name}, TVL: $${parseFloat(pools[0].totalValueLockedUsd || 0).toFixed(2)}`);
71+
} else {
72+
console.log(`No pool found with this address`);
73+
}
74+
}
75+
}

plugins/tonco-dex/index.js

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,32 @@ const TON_RAW_ADDR = "0:00000000000000000000000000000000000000000000000000000000
6060
/** Module-level SDK reference (set in tools(sdk) factory) */
6161
let _sdk = null;
6262

63+
// ---------------------------------------------------------------------------
64+
// Address helpers
65+
// ---------------------------------------------------------------------------
66+
67+
/**
68+
* Normalize any valid TON address to raw format (0:xxxx…hex) required by
69+
* the TONCO GraphQL indexer. The indexer's server-side resolver calls
70+
* Address.parseRaw() internally, which crashes with "The first argument must
71+
* be of type string…" when it receives a bounceable/user-friendly address
72+
* (EQ…/UQ…) instead of the raw 0:hex form.
73+
*
74+
* @param {string} addr - Any TON address (EQ…, UQ…, or 0:hex)
75+
* @returns {string} Raw address in "0:xxxx…" form, or the original string
76+
* if parsing fails (the indexer will then return its own error)
77+
*/
78+
function normalizeToRaw(addr) {
79+
if (typeof addr !== "string" || !addr.trim()) return addr;
80+
try {
81+
const parsed = Address.parse(addr.trim());
82+
return `0:${parsed.hash.toString("hex")}`;
83+
} catch (e) {
84+
console.warn(`[tonco-dex] normalizeToRaw: failed to parse address "${addr}", using as-is:`, e.message);
85+
return addr.trim();
86+
}
87+
}
88+
6389
// ---------------------------------------------------------------------------
6490
// GraphQL helper
6591
// ---------------------------------------------------------------------------
@@ -340,7 +366,7 @@ const toncoGetPoolStats = {
340366

341367
execute: async (params) => {
342368
try {
343-
const poolAddr = params.pool_address.trim();
369+
const poolAddr = normalizeToRaw(params.pool_address);
344370

345371
const query = `
346372
query GetPool($where: PoolWhere) {
@@ -625,8 +651,8 @@ const toncoSwapQuote = {
625651
const isTonIn = tokenInAddr.toUpperCase() === "TON";
626652
const isTonOut = tokenOutAddr.toUpperCase() === "TON";
627653

628-
const resolvedInAddr = isTonIn ? TON_RAW_ADDR : tokenInAddr;
629-
const resolvedOutAddr = isTonOut ? TON_RAW_ADDR : tokenOutAddr;
654+
const resolvedInAddr = isTonIn ? TON_RAW_ADDR : normalizeToRaw(tokenInAddr);
655+
const resolvedOutAddr = isTonOut ? TON_RAW_ADDR : normalizeToRaw(tokenOutAddr);
630656

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

836862
// Use the same TON_RAW_ADDR constant as tonco_swap_quote for consistent address resolution
837-
const resolvedInAddr = isTonIn ? TON_RAW_ADDR : tokenInAddr;
838-
const resolvedOutAddr = isTonOut ? TON_RAW_ADDR : tokenOutAddr;
863+
const resolvedInAddr = isTonIn ? TON_RAW_ADDR : normalizeToRaw(tokenInAddr);
864+
const resolvedOutAddr = isTonOut ? TON_RAW_ADDR : normalizeToRaw(tokenOutAddr);
839865

840866
// Fetch pool from indexer
841867
const query = `
@@ -1076,7 +1102,7 @@ const toncoGetPositions = {
10761102

10771103
const where = {
10781104
owner: ownerAddr,
1079-
...(params.pool_address ? { pool: params.pool_address.trim() } : {}),
1105+
...(params.pool_address ? { pool: normalizeToRaw(params.pool_address) } : {}),
10801106
};
10811107

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

12221248
// Get pool address from indexer if not provided
1223-
let poolAddress = params.pool_address?.trim();
1249+
let poolAddress = params.pool_address ? normalizeToRaw(params.pool_address) : undefined;
12241250
if (!poolAddress) {
12251251
const nftData = await positionContract.getData();
12261252
const collectionAddr = nftData?.collection?.toString();

0 commit comments

Comments
 (0)