Skip to content

Commit 94071e0

Browse files
authored
feat: Add ZIGChain blockchain and OroSwap adapter (#16910)
1 parent dc52cc9 commit 94071e0

File tree

4 files changed

+197
-2
lines changed

4 files changed

+197
-2
lines changed

projects/helper/chain/cosmos.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ const endPoints = {
7171
civitia: 'https://rest-civitia-1.anvil.asia-southeast.initia.xyz',
7272
echelon_initia: 'https://rest-echelon-1.anvil.asia-southeast.initia.xyz',
7373
inertia: 'https://rest.inrt.fi',
74-
union: 'https://rest.union.build'
74+
union: 'https://rest.union.build',
75+
zigchain: 'https://public-zigchain-lcd.numia.xyz'
7576
};
7677

7778
const chainSubpaths = {

projects/helper/chains.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@
457457
"zeniq",
458458
"zero_network",
459459
"zeta",
460+
"zigchain",
460461
"zilliqa",
461462
"zircuit",
462463
"zkcro",

projects/helper/tokenMapping.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ coreAssets = JSON.parse(JSON.stringify(coreAssets))
2020

2121
const ibcChains = ['ibc', 'terra', 'terra2', 'crescent', 'osmosis', 'kujira', 'stargaze', 'juno', 'injective', 'cosmos', 'provenance', 'comdex', 'umee', 'orai', 'persistence', 'fxcore', 'neutron', 'quasar', 'chihuahua', 'sei', 'archway', 'migaloo', 'secret', 'aura', 'xpla', 'bostrom', 'joltify', 'nibiru',
2222
'kopi', 'elys', "pryzm", "mantra", 'agoric', 'band',
23-
'celestia', 'dydx', 'carbon', 'milkyway', 'regen', 'sommelier', 'stride', 'prom', 'babylon', 'xion'
23+
'celestia', 'dydx', 'carbon', 'milkyway', 'regen', 'sommelier', 'stride', 'prom', 'babylon', 'xion', 'zigchain'
2424
]
2525
const caseSensitiveChains = [...ibcChains, ...svmChains, 'tezos', 'ton', 'algorand', 'aptos', 'near', 'bitcoin', 'waves', 'tron', 'litecoin', 'polkadot', 'ripple', 'elrond', 'cardano', 'stacks', 'sui', 'ergo', 'mvc', 'renec', 'doge', 'stellar', 'massa',
2626
'eclipse', 'acala', 'aelf', 'aeternity', 'alephium', 'bifrost', 'bittensor', 'verus',
@@ -134,6 +134,9 @@ const fixBalancesTokens = {
134134
ethereal: {
135135
[ADDRESSES.null]: { coingeckoId: 'ethena-usde', decimals: 18 },
136136
'0xb6fc4b1bff391e5f6b4a3d2c7bda1fee3524692d': { coingeckoId: 'ethena-usde', decimals: 18 },
137+
},
138+
'zigchain': {
139+
'uzig': { coingeckoId: 'zignaly', decimals: 6 }, // Native ZIG token
137140
}
138141
}
139142

projects/oroswap/index.js

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
const { queryContract } = require('../helper/chain/cosmos');
2+
const { transformBalances } = require('../helper/portedTokens');
3+
4+
const FACTORY_CONTRACT = 'zig1xx3aupmgv3ce537c0yce8zzd3sz567syaltr2tdehu3y803yz6gsc6tz85';
5+
const API_CALL_DELAY = 10; // Rate limit throttling for public REST API
6+
const MAX_RETRIES = 3;
7+
const RETRY_DELAY = 1000; // Initial retry delay in ms
8+
9+
const STZIG_DENOM = 'coin.zig109f7g2rzl2aqee7z6gffn8kfe9cpqx0mjkk7ethmx8m2hq4xpe9snmaam2.stzig';
10+
11+
// Valdora Staker contract address (https://docs.valdora.finance/smart-contracts)
12+
const VALDORA_STAKER_CONTRACT = 'zig18nnde5tpn76xj3wm53n0tmuf3q06nruj3p6kdemcllzxqwzkpqzqk7ue55';
13+
14+
/**
15+
* Fetches the conversion rate from stZIG to uZIG using the redeem-side quote from the Valdora Staker contract
16+
*
17+
* How it works:
18+
* 1. Query the contract with reverse_st_zig_price: "If I redeem X uZIG worth of stZIG, how much stZIG do I get?"
19+
* 2. The contract responds with stzig_amount (amount of stZIG received for probe_uzig uZIG)
20+
* 3. We invert this: uzig_per_stzig = probe_uzig / stzig_amount
21+
*
22+
* We use a large probe amount (1,000 ZIG) for precision:
23+
* - Larger probe = more precision in integer division
24+
* - Reduces rounding errors when calculating the ratio
25+
* - Result is scaled by 1e6 to maintain precision (returns ratio * 1,000,000)
26+
*
27+
* Returns the ratio of uZIG per 1 stZIG, scaled by 1e6 (or null if query fails)
28+
* Example: if 1 stZIG = 0.989 ZIG, returns ~989,000
29+
*/
30+
async function fetchUzigPerStzig() {
31+
// Use 1,000 ZIG (1 billion uZIG) as probe for better precision in integer math
32+
// This is the amount of uZIG we're "simulating" a redeem with
33+
const probeUzig = 1_000_000_000; // 1,000 ZIG in uZIG (base units with 6 decimals)
34+
35+
// Query: "If I redeem this much uZIG, how much stZIG do I get?"
36+
// The contract uses reverse pricing logic (redeem path)
37+
const { stzig_amount } = await queryContract({
38+
contract: VALDORA_STAKER_CONTRACT,
39+
chain: 'zigchain',
40+
data: { reverse_st_zig_price: { amount: String(probeUzig) } },
41+
});
42+
43+
if (!stzig_amount || stzig_amount === '0') return null;
44+
45+
// Calculate: uzig_per_stzig = probe_uzig / stzig_amount
46+
// We scale by 1e6 to maintain precision: (probe * 1e6) / stzig_amount
47+
// This gives us the ratio scaled by 1,000,000
48+
// Example: if probe=1e9 and stzig_amount=989580475, then ratio_scaled = ~1,010,528
49+
return (BigInt(probeUzig) * 1_000_000n) / BigInt(stzig_amount);
50+
}
51+
52+
function sleep(ms) {
53+
return new Promise(resolve => setTimeout(resolve, ms));
54+
}
55+
56+
async function withRetry(fn, maxAttempts = MAX_RETRIES) {
57+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
58+
try {
59+
return await fn();
60+
} catch (error) {
61+
if (attempt === maxAttempts) {
62+
throw error;
63+
}
64+
const delay = RETRY_DELAY * attempt; // Exponential backoff
65+
console.error(`Attempt ${attempt} failed, retrying in ${delay}ms:`, error.message);
66+
await sleep(delay);
67+
}
68+
}
69+
}
70+
71+
async function getAllPairs() {
72+
const allPairs = [];
73+
let startAfter = undefined;
74+
const limit = 30;
75+
76+
while (true) {
77+
const query = { pairs: { limit } };
78+
if (startAfter) {
79+
query.pairs.start_after = startAfter;
80+
}
81+
82+
try {
83+
const response = await withRetry(() => queryContract({
84+
contract: FACTORY_CONTRACT,
85+
chain: 'zigchain',
86+
data: query
87+
}));
88+
89+
if (!response.pairs || response.pairs.length === 0) {
90+
break;
91+
}
92+
93+
allPairs.push(...response.pairs);
94+
95+
const lastPair = response.pairs[response.pairs.length - 1];
96+
const nextCursor = { asset_infos: lastPair.asset_infos, pair_type: lastPair.pair_type };
97+
if (response.pairs.length < limit) {
98+
break;
99+
}
100+
if (startAfter && JSON.stringify(startAfter) === JSON.stringify(nextCursor)) {
101+
break;
102+
}
103+
startAfter = nextCursor;
104+
await sleep(API_CALL_DELAY);
105+
} catch (error) {
106+
console.error('Failed to fetch pairs from factory:', error);
107+
throw error;
108+
}
109+
}
110+
111+
return allPairs;
112+
}
113+
114+
async function getPoolInfo(contractAddr) {
115+
return await withRetry(() => queryContract({
116+
contract: contractAddr,
117+
chain: 'zigchain',
118+
data: { pool: {} }
119+
}));
120+
}
121+
122+
function getAssetKey(assetInfo) {
123+
if (assetInfo.native_token) {
124+
return assetInfo.native_token.denom;
125+
} else if (assetInfo.token) {
126+
return assetInfo.token.contract_addr;
127+
}
128+
return null;
129+
}
130+
131+
async function tvl(api) {
132+
try {
133+
const pairs = await getAllPairs();
134+
135+
for (const pair of pairs) {
136+
const poolInfo = await getPoolInfo(pair.contract_addr);
137+
if (!poolInfo || !poolInfo.assets) {
138+
continue;
139+
}
140+
141+
for (const asset of poolInfo.assets) {
142+
const assetKey = getAssetKey(asset.info);
143+
if (assetKey && asset.amount) {
144+
api.add(assetKey, asset.amount);
145+
}
146+
}
147+
148+
await sleep(API_CALL_DELAY);
149+
}
150+
const balances = api.getBalances();
151+
152+
// Convert stZIG balances to uZIG equivalent for TVL calculation
153+
const stzigKeyRaw = STZIG_DENOM;
154+
const stzigKeyPrefixed = `zigchain:${STZIG_DENOM}`;
155+
const stzigBalStr = balances[stzigKeyPrefixed] || balances[stzigKeyRaw];
156+
157+
if (stzigBalStr) {
158+
// Fetch the current conversion rate from on-chain quote (redeem path)
159+
const ratioScaled = await fetchUzigPerStzig();
160+
161+
if (ratioScaled) {
162+
// Convert stZIG balance to uZIG equivalent
163+
// Formula: uzig_equivalent = (stzig_balance * ratio_scaled) / 1_000_000
164+
// We divide by 1_000_000 to remove the scaling we added in fetchUzigPerStzig
165+
const stzigBal = BigInt(stzigBalStr);
166+
const uzigEq = (stzigBal * ratioScaled) / 1_000_000n;
167+
168+
// Remove stZIG from balances (we've converted it to uZIG)
169+
delete balances[stzigKeyPrefixed];
170+
delete balances[stzigKeyRaw];
171+
172+
// Add the uZIG equivalent to the existing uZIG balance
173+
const uzigKey = 'zigchain:uzig';
174+
const currentUzig = balances[uzigKey] ? BigInt(balances[uzigKey]) : 0n;
175+
balances[uzigKey] = (currentUzig + uzigEq).toString();
176+
}
177+
}
178+
return transformBalances('zigchain', balances);
179+
} catch (error) {
180+
console.error('Error calculating TVL:', error);
181+
throw error;
182+
}
183+
}
184+
185+
module.exports = {
186+
timetravel: false,
187+
misrepresentedTokens: false,
188+
methodology: 'TVL is calculated by summing all assets locked in OroSwap liquidity pools on ZIGChain',
189+
zigchain: { tvl }
190+
};

0 commit comments

Comments
 (0)