Quick start
Connect to LitVM, pin the deployment, and keep all transaction decisions grounded in live RPC state.
npm install viemimport { createPublicClient, defineChain, http } from "viem";
export const litvmTestnet = defineChain({
id: 4441,
name: "LitVM LiteForge",
nativeCurrency: { name: "zkLTC", symbol: "zkLTC", decimals: 18 },
rpcUrls: {
default: { http: ["https://liteforge.rpc.caldera.xyz/infra-partner-http"] },
},
blockExplorers: {
default: { name: "LiteForge Explorer", url: "https://liteforge.explorer.caldera.xyz" },
},
});
export const publicClient = createPublicClient({
chain: litvmTestnet,
transport: http(),
});
export const contracts = {
pixel: "0x33A32b9b2BEe864f9e42BFa39cA7BDC72f655988",
pixelMarketplace: "0xd219EfFe076B68934e212313C8d468b994d6e52E",
nusd: "0x69320A82ACCeC1b8A5EF59fd02b217e7ea4aEa19",
pump: "0x0239baEc3c695A6EA893Ad90a7608112e0580526",
fiRouter: "0x1cda4793122a223A09be54ED5f664ba222eEe042",
fiFactory: "0xc6B675d701a168789A5cc7Dd04Ef9e531886114c",
wzkLtc: "0xE93d4373CE1eDA3df6c3Ab7ed3ab07A07aA5939F",
nbtc: "0xAA612e178ff9c4Ea044eF507670C7502D58CbA46",
neth: "0x3f3B4F9DfE1AE3CDaa89788810557194c662a8Ef",
} as const;Network and contracts
These public addresses are the checked LitVM testnet deployment used by the current web application.
Core ecosystem
0x33A32b9b2BEe864f9e42BFa39cA7BDC72f6559880xd219EfFe076B68934e212313C8d468b994d6e52E0x69320A82ACCeC1b8A5EF59fd02b217e7ea4aEa190xAea1Ef82Dbe800BB3683009630813457D990a9B00x59589d9a5840D2CEF7A6e4A8cB55C258cc73E4830x0239baEc3c695A6EA893Ad90a7608112e05805260x935ec3B5ED5ef330895C87139e0a905B009187a50xFi
0x1cda4793122a223A09be54ED5f664ba222eEe0420xc6B675d701a168789A5cc7Dd04Ef9e531886114c0xE93d4373CE1eDA3df6c3Ab7ed3ab07A07aA5939F0xB300Faa8AE7a36eFa17e028dE325dA0c121109040x6Cc96c044321b3205Ac108107Fc6d2d47e0B3E440xAA612e178ff9c4Ea044eF507670C7502D58CbA460x3f3B4F9DfE1AE3CDaa89788810557194c662a8EfCanonical 0xFi pools
0x7a9295742b237802571E80148C2CEc629C5D6E670x1D7e66D1301154146467dC7E3fc2916aa1a8Bc4c0x1890bb89CB57551575ccc84D0079d6A8A245285BEcosystem architecture
Each product is useful on its own. Together they form a path from creation to markets, liquidity, and persistent digital ownership.
0xPixel
A fully onchain pixel-art studio, ERC-721 collection, provenance registry, gallery, and NUSD NFT marketplace.
Pixel runs, artwork name, grid size, creator, mint time, and artwork hash live in contract storage.
The contract generates SVG and base64 JSON metadata. The collectible does not depend on an offchain image host.
The registry hashes packed pixel data with the grid size and rejects an artwork that was already registered.
Listings and escrowed offers settle in NUSD. Each sale burns 0.1% of the price and pays 0.4% to the artwork's creator; there is no platform fee. Sale proceeds and refunds are reusable credits. zkLTC only pays gas.
import { parseAbi } from "viem";
export const pixelAbi = parseAbi([
"function checkOriginal(string pixelData, uint256 grid) view returns (bool)",
"function mint(string artName, uint256 grid, string pixelData) returns (uint256 tokenId)",
"function tokenData(uint256 tokenId) view returns (string artName, uint256 gridSize, string pixelData, address creator, uint256 mintedAt, bytes32 artworkHash)",
"function tokenURI(uint256 tokenId) view returns (string)",
]);
// Supported grids: 8, 16, 32, 64.
// pixelData is packed horizontal runs: x, y, count, r, g, b.
const isOriginal = await publicClient.readContract({
address: contracts.pixel,
abi: pixelAbi,
functionName: "checkOriginal",
args: [pixelData, 32n],
});0xPump
A NUSD launchpad with reserved metadata, virtual constant-product pricing, explicit lifecycle states, and protected graduation.
reserveMarket(contentHash)Pay the nonrefundable creation reservation.createMarket(...)Create the fixed-supply token and curve.quoteBuy / buyQuote and execute NUSD input.quoteSell / sellQuote and execute token input.0xFi
The liquidity and risk layer for swaps, pools, LP farms, fixed-rate NUSD lending, borrowing, and DIA-priced synthetic assets.
| Swap | LP fee | Protocol fee | Nominal total |
|---|---|---|---|
| TOKEN ↔ NUSD or zkLTC | 0.5% | 0.1% | 0.6% |
| Via NUSD or zkLTC, one end a base | 1.0% | 0.1% | 1.1% |
| TOKEN → base → TOKEN | 1.0% | 0.3% | 1.3% |
getAmountsOut already includes LP fees, the protocol fee, and price impact. Do not subtract them twice. Every pool pairs a token with NUSD or zkLTC, and the protocol fee is always paid in one of them.
0xUniverse
A persistent game world where Web3 finance and GameFi connect virtual property, creator economies, and compliant RWA representations.
Characters, land, items, art, permissions, and provenance remain portable assets controlled by users.
Markets, crafting, quests, resource sinks, liquidity, lending, and settlement become parts of one world economy.
Digital assets can connect to compliant real-world representations through attestations, oracles, custody, and jurisdiction-aware access.
0xPixel supplies culture, 0xPump forms communities, and 0xFi supplies settlement and financial rails.
Integrate 0xFi swaps
Choose the correct execution surface, quote against current state, simulate the exact request, then ask the wallet to sign.
| User action | Contract | Method |
|---|---|---|
| zkLTC to NUSD | NUSD | quoteMint + mintAtOracle |
| NUSD to zkLTC | NUSD | quoteRedeem + redeemAtOracle |
| ERC-20 to ERC-20 | Router | swapExactTokensForTokens |
| zkLTC to ERC-20 | Router | swapExactNativeForTokens |
| ERC-20 to zkLTC | Router | swapExactTokensForNative |
Path begins with WzkLTCSend the exact input as transaction value. No ERC-20 approval.Path ends with WzkLTCApprove the ERC-20 input. The router unwraps before transfer.import { parseAbi, type Address, type Hash, type PublicClient, type WalletClient } from "viem";
const routerAbi = parseAbi([
"function getAmountsOut(uint256 amountIn, address[] path) view returns (uint256[] amounts)",
"function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to, uint256 deadline) returns (uint256[] amounts)",
]);
const erc20Abi = parseAbi([
"function allowance(address owner, address spender) view returns (uint256)",
"function approve(address spender, uint256 amount) returns (bool)",
]);
type Quote = { path: Address[]; amountOut: bigint };
export async function swapErc20(params: {
publicClient: PublicClient;
walletClient: WalletClient;
account: Address;
tokenIn: Address;
tokenOut: Address;
amountIn: bigint;
slippageBps?: bigint;
}) {
const {
publicClient,
walletClient,
account,
tokenIn,
tokenOut,
amountIn,
slippageBps = 50n,
} = params;
if (await walletClient.getChainId() !== 4441) {
throw new Error("Switch wallet to LitVM chain 4441");
}
if (amountIn <= 0n || slippageBps < 0n || slippageBps >= 10_000n) {
throw new Error("Invalid swap parameters");
}
// A wallet cancellation can have a successful receipt. Confirm the intended
// action, allowing only gas-price speed-ups with unchanged calldata/value.
async function confirmAction(hash: Hash) {
let changedIntent = false;
let repricedHash: Hash | undefined;
const receipt = await publicClient.waitForTransactionReceipt({
hash,
checkReplacement: true,
onReplaced: ({ reason, transactionReceipt }) => {
if (reason !== "repriced") changedIntent = true;
else repricedHash = transactionReceipt.transactionHash;
},
});
if (changedIntent || receipt.status !== "success") {
throw new Error("The requested transaction did not complete");
}
if (receipt.transactionHash !== hash && receipt.transactionHash !== repricedHash) {
throw new Error("Unverified replacement transaction");
}
return receipt;
}
const paths: Address[][] = [[tokenIn, tokenOut]];
const usesNusd =
tokenIn.toLowerCase() === contracts.nusd.toLowerCase() ||
tokenOut.toLowerCase() === contracts.nusd.toLowerCase();
if (!usesNusd) paths.push([tokenIn, contracts.nusd, tokenOut]);
const quotes = await Promise.all(paths.map(async (path): Promise<Quote | undefined> => {
try {
const amounts = await publicClient.readContract({
address: contracts.fiRouter,
abi: routerAbi,
functionName: "getAmountsOut",
args: [amountIn, path],
});
return { path, amountOut: amounts.at(-1)! };
} catch {
return undefined;
}
}));
const best = quotes
.filter((quote): quote is Quote => Boolean(quote))
.sort((a, b) => a.amountOut === b.amountOut ? 0 : a.amountOut > b.amountOut ? -1 : 1)[0];
if (!best) throw new Error("No executable 0xFi route");
const amountOutMin = best.amountOut * (10_000n - slippageBps) / 10_000n;
const allowance = await publicClient.readContract({
address: tokenIn,
abi: erc20Abi,
functionName: "allowance",
args: [account, contracts.fiRouter],
});
if (allowance < amountIn) {
const approval = await publicClient.simulateContract({
account,
address: tokenIn,
abi: erc20Abi,
functionName: "approve",
args: [contracts.fiRouter, amountIn],
});
const approvalHash = await walletClient.writeContract(approval.request);
await confirmAction(approvalHash);
}
const deadline = BigInt(Math.floor(Date.now() / 1_000) + 20 * 60);
const simulation = await publicClient.simulateContract({
account,
address: contracts.fiRouter,
abi: routerAbi,
functionName: "swapExactTokensForTokens",
args: [amountIn, amountOutMin, best.path, account, deadline],
});
const hash = await walletClient.writeContract(simulation.request);
const receipt = await confirmAction(hash);
return { hash: receipt.transactionHash, receipt, path: best.path, quotedOut: best.amountOut, amountOutMin };
}Native zkLTC and NUSD
The exact zkLTC/NUSD route uses OracleNUSD. It does not use an AMM pool and still requires minimum-output protection.
const nusdAbi = parseAbi([
"function quoteMint(uint256 collateralWei) view returns (uint256 amountNusd)",
"function mintAtOracle(uint256 minNusdOut, address recipient) payable returns (uint256 amountNusd)",
"function quoteRedeem(uint256 amountNusd) view returns (uint256 collateralOutWei)",
"function redeemAtOracle(uint256 amountNusd, uint256 minCollateralOutWei, address recipient) returns (uint256 collateralOutWei)",
]);
const minimumOut = (quote: bigint, slippageBps = 50n) =>
quote * (10_000n - slippageBps) / 10_000n;
// Native zkLTC to NUSD
const quotedNusd = await publicClient.readContract({
address: contracts.nusd,
abi: nusdAbi,
functionName: "quoteMint",
args: [zkLtcIn],
});
const mint = await publicClient.simulateContract({
account,
address: contracts.nusd,
abi: nusdAbi,
functionName: "mintAtOracle",
args: [minimumOut(quotedNusd), account],
value: zkLtcIn,
});
// NUSD to native zkLTC. No ERC-20 approval is required.
const quotedZkLtc = await publicClient.readContract({
address: contracts.nusd,
abi: nusdAbi,
functionName: "quoteRedeem",
args: [nusdIn],
});
const redeem = await publicClient.simulateContract({
account,
address: contracts.nusd,
abi: nusdAbi,
functionName: "redeemAtOracle",
args: [nusdIn, minimumOut(quotedZkLtc), account],
});Vibe-code command
Paste this specification into a coding agent to generate a complete testnet swap integration without losing protocol constraints.
Build a complete 0xFi swap integration for an existing Next.js TypeScript app.
Target network
- Network: LitVM LiteForge Testnet
- Chain ID: 4441
- Native asset: zkLTC, 18 decimals
- RPC: https://liteforge.rpc.caldera.xyz/infra-partner-http
- Explorer: https://liteforge.explorer.caldera.xyz
Pinned contracts
- NUSD / OracleNUSD: 0x69320A82ACCeC1b8A5EF59fd02b217e7ea4aEa19
- WzkLTC: 0xE93d4373CE1eDA3df6c3Ab7ed3ab07A07aA5939F
- 0xFi Factory: 0xc6B675d701a168789A5cc7Dd04Ef9e531886114c
- 0xFi Router: 0x1cda4793122a223A09be54ED5f664ba222eEe042
Implementation requirements
- Use viem and the wallet layer already installed by the app. Do not add a server-side signer.
- Create typed chain config, minimal ABIs, quote service, transaction service, React hook, and accessible swap UI.
- Accept bigint amounts internally. Read token decimals, balance, and allowance over RPC.
- For ERC-20 routes, probe [tokenIn, tokenOut] and [tokenIn, NUSD, tokenOut]. Choose the successful quote with the largest final output.
- Use getAmountsOut for executable AMM quotes. It already includes LP fees, the protocol fee, and price impact.
- Use swapExactTokensForTokens for ERC-20 pairs.
- Use swapExactNativeForTokens when input is native zkLTC. The path must start with WzkLTC and the transaction must send value.
- Use swapExactTokensForNative when output is native zkLTC. The path must end with WzkLTC.
- For the exact zkLTC/NUSD route, use quoteMint plus mintAtOracle, or quoteRedeem plus redeemAtOracle. Do not route this exact pair through the AMM.
- Request exact ERC-20 approval only when allowance is insufficient. Wait for the approval receipt before the swap.
- Apply user-controlled slippage in basis points. Never use zero minimum output.
- Use a short deadline, simulate every write, wait for the final receipt, and reject reverted receipts.
- Verify chain ID 4441 before every write. Keep enough zkLTC for gas.
- Show route, fee summary, quoted output, minimum received, price impact, recipient, pending state, transaction hash, and actionable errors.
- Requote after token, amount, account, chain, or block-relevant state changes.
- Use Goldsky only for history and charts. Never use indexed reserves for an executable quote.
- Reject unsupported fee-on-transfer tokens and repeated or zero-address paths.
Required delivery
- Return complete files with no placeholders or omitted sections.
- Include loading, empty, wrong-network, approval, signing, confirming, success, and error states.
- Include unit tests for route selection, slippage math, native path rules, allowance branching, and failed simulation.
- Mark the integration as testnet-only. Do not describe it as audited or mainnet-ready.Indexed data
Goldsky powers discovery, history, charts, and activity. RPC remains authoritative for balances, quotes, allowances, and transaction preflight.
https://api.goldsky.com/api/public/project_cmqmpust19i8v01t595z8hpq4/subgraphs/pixel-nusd-market/1.1.0/gnMarketplace activity and burn totals. NFT ownership and listings are read from the contracts through RPC.
https://api.goldsky.com/api/public/project_cmqmpust19i8v01t595z8hpq4/subgraphs/zeroxpump-testnet/1.0.0/gnhttps://api.goldsky.com/api/public/project_cmqmpust19i8v01t595z8hpq4/subgraphs/zeroxfi-testnet/1.0.0/gnconst INDEXERS = {
pixelMarket: "https://api.goldsky.com/api/public/project_cmqmpust19i8v01t595z8hpq4/subgraphs/pixel-nusd-market/1.1.0/gn",
pump: "https://api.goldsky.com/api/public/project_cmqmpust19i8v01t595z8hpq4/subgraphs/zeroxpump-testnet/1.0.0/gn",
fi: "https://api.goldsky.com/api/public/project_cmqmpust19i8v01t595z8hpq4/subgraphs/zeroxfi-testnet/1.0.0/gn",
} as const;
export async function queryIndexer<T>(endpoint: string, query: string, variables = {}) {
const response = await fetch(endpoint, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ query, variables }),
});
if (!response.ok) throw new Error("Indexer HTTP " + response.status);
const payload = await response.json();
if (payload.errors?.length) throw new Error(payload.errors[0].message);
return payload.data as T;
}Testnet safety
This deployment is for integration testing. A successful transaction or build does not represent a security audit.