Capabilities Reference — @swarm-ai-labs/turnkey-extensions
Every public capability of the package, grouped by subpath export. These are the building blocks. If you are looking for "how do I read my portfolio" rather than "which function reads a TON jetton balance", start with the product API in SDK.md — or, at the terminal, CLI.md.
For design rationale see ARCHITECTURE.md; for runnable snippets see examples/.
Chain coverage: EVM (any chain id; Ethereum, Base, Arbitrum, Optimism, BSC, Polygon, Avalanche, … via the catalog), Bitcoin, Solana, TRON, TON, Sui, NEAR. Integrations: Turnkey (signing), Hyperliquid (perps), Polymarket (prediction markets), Sparkling Inventory/Indexer, Aurora Intents swap widget, NEAR Intents (1Click), Uniswap v3, gas.zip, LI.FI, CoinGecko, 0G Compute.
Module map
| Subpath | Capability |
|---|---|
. (root) | Barrel of everything below except react, polymarket, prices, zgcompute, holdings, adi |
./config | Endpoint/runtime configuration (configure / getConfig) |
./types | Shared chain & wallet types |
./format | Currency / percent / address formatting |
./validate | Per-network recipient address validation |
./catalog | EVM network catalog, swap-token lists, explorer & logo URL builders |
./signers | Turnkey signer adapters (EIP-1193, Solana, NEAR derivation, EVM tx prep) |
./senders | Native-coin send flows per chain (prepare → confirm) |
./balances | Multi-chain native + token balance reads with cache/retry |
./prices | Merged price layer (Sparkling Indexer → CoinGecko fallback) |
./holdings | Portfolio valuation (computeHoldingsTotals, 24h delta) + list view (applyHoldingsView) |
./adi | ADI PredictStreet: markets, book, orders (place/cancel/open), vault deposit/withdraw, balances |
./hyperliquid | Perps: market data, positions, order planning + placement, TP/SL, leverage, isolated margin, dead-man switch, book-walk pricing, WebSocket feeds, Arbitrum USDC bridge, withdrawals |
./polymarket | CLOB client, L1/L2 auth, orders, Safe proxy + relayer, market data |
./inventory | Sparkling inventory client (tokens, pairs, chains, deployments) |
./indexer | Ticker search, price graph, wallet summary, portfolio total, balances |
./swap | Swap providers (1Click, Uniswap v3, gas.zip, LI.FI) + Aurora swap-widget bridge + NEAR Intents (NEP-413) signing |
./activity | Multi-chain on-chain activity feed with opaque cursors |
./zgcompute | 0G Compute broker + TEE-verifiable chat over a Turnkey wallet |
./signals | Envy AI signals: live feed, SSE transitions, strategy catalogue + rules, equity curves, presentation labels |
./venue | One position/order model across Hyperliquid, Polymarket and ADI; failure-isolating sweep; rebalance planning + route journal |
./execution | Order slicing (TWAP/ladder/iceberg) with derived client order ids, a resumable journal, a client-side trailing stop |
./strategy | Declarative recipe schema, shared firing logic, backtest over historical closes |
./alerts | Condition grammar, edge-triggered firing with cooldown, live metric reader, delivery channels |
./risk | Standing limits that outlive one invocation (daily loss, order notional, leverage, withdrawal whitelist) |
./paper | Paper trading ledger: fill application, book replay, mark-to-market |
./screen | Screeners: perp funding (annualised) and prediction-market price inconsistency |
./cow | CoW Protocol limit orders: EIP-712 order building, integer price maths, order-book client |
./cex | Binance USD-M futures as a VenueAdapter; HMAC-SHA256 request signing over Web Crypto |
./evm | eth_call revert check and an anvil-forked transaction simulation |
./wallet | Batch payout parsing, ERC-20 allowance model, stuck-transaction replacement maths |
./chart | Terminal line charts, sparklines, time-axis labels |
./progress | TxProgress — the live status contract every write path reports through |
./runtime | The product API — createSwarm, markets, identity, store, policy, operations (see SDK.md) |
./runtime/node | fsStore(), turnkeyBinaryIdentity() — the parts needing node:fs / node:child_process |
./react | React hooks/providers (accounts, Hyperliquid, search, price graph) |
./config — configuration
Every RPC/API endpoint in the library is read through getConfig(). Mainnet defaults are baked in, so the package works zero-config; override once at app startup.
configure(partial)— deep-merge a partial config; trailing slashes are normalized; safe to call repeatedly to layer overrides.getConfig()— the activeTurnkeyExtensionsConfig.resetConfig()— restore baked-in defaults (tests).- Types:
TurnkeyExtensionsConfig,RpcConfig,ApiBasesConfig,PricesModeConfig,DeepPartial.
Configurable endpoints and their defaults:
| Key | Default | Used by |
|---|---|---|
rpc.sol | Solana mainnet-beta | balances, senders, activity |
rpc.near | free.rpc.fastnear.com | balances, senders |
rpc.tron | api.trongrid.io | balances, senders, activity |
rpc.tonV2 / rpc.tonV3 | toncenter v2 / v3 | balances, senders, activity |
rpc.sui | Sui mainnet full node | balances, senders, activity |
rpc.btcMempool | mempool.space/api | balances, senders, activity |
rpc.evm[chainId] | per-chain override → catalog fallback | everything EVM |
apiBases.inventory | Sparkling inventory stage host | inventory |
apiBases.indexer | "" (same-origin proxy) | indexer search/price-graph |
apiBases.coingecko | CoinGecko simple API | prices |
apiBases.customIndexer | Sparkling Indexer stage host | prices, indexer balances/summary |
apiBases.nearblocks | api.nearblocks.io/v1 | NEAR activity |
prices.indexerType | "custom" ("public" = CoinGecko-only) | prices |
./types — shared types
DepositChainId— deposit/withdraw chain slug ("ethereum","solana", …).ChainKey— canonical signable chains:ethereum | bitcoin | solana | tron | ton | sui.WalletAccount— re-export of the Turnkey kit type (type-only, erased at build).EvmCaip2— CAIP-2 ids accepted by Turnkey'sEthTransaction.
Chain helpers (root export)
From src/chains.ts, exported via the root barrel:
EVM_CHAIN_IDS— deposit-chain slug → EVM chain id map.isEvmNetwork(network)— whether the slug is an EVM chain.nativeSymbolForNetwork(network)/isNativeSymbolForNetwork(network, symbol)— native coin symbol resolution (incl. legacy GRAM/TON routing).NATIVE_DECIMALS/nativeDecimals(network)— native coin decimals.networkLabelFor(network)— human-readable chain label.
(src/cache.ts's createPromiseCache is internal — it backs the inventory client's caches but is not part of the public surface.)
./format — formatting
formatUsd(value, opts?)— USD amount formatting.formatUsdPrice(value)— price formatting (small-price precision).formatCompactUsd(value)— compact$1.2M-style output.splitCurrency(value)— split into integer/fraction parts for styled UIs.formatSignedPercent(value)— signed percent label.signColor(value)+POSITIVE_COLOR/NEGATIVE_COLOR— up/down coloring.formatAddress(address)— middle-truncated address.setActiveCurrencyProvider(fn)/ActiveCurrency— plug in a non-USD display currency (rate + symbol) that all formatters honor.
./validate — address validation
isValidAddressForNetwork(network, address)— pragmatic per-network shape check catching "wrong format / wrong network" mistakes:- EVM:
viem.isAddress(checksum not required) - Bitcoin: legacy/P2SH base58 + bech32
bc1… - Solana: base58, 32–44 chars
- TRON:
T+ 33 base58check chars - TON: user-friendly (
EQ/UQ/kQ/0Q+ base64url) or rawwc:hex - Sui:
0x+ 64 hex - NEAR: named account or 64-hex implicit
- Unknown chains pass (don't block a potentially valid send).
- EVM:
./catalog — network & token catalog
EVM networks (cryptoNetworks.ts):
POPULAR_EVM_NETWORKS/EvmNetworkDefinition— curated EVM network list (chain id, name, RPC, explorer, logo…).DEFAULT_EVM_CHAIN_ID.findEvmNetwork(chainId)— catalog lookup.isEvmTestnetChain(chainId)— testnet detection.getDefaultEthRpcUrlForChain(chainId)/getDefaultEthRpcUrl(chainId)— RPC resolution (config override → catalog).explorerTxUrlEthereum(chainId, txHash)/explorerTxUrlForKind(...)— explorer deep links.buildTrustWalletLogoUrl(chainKey)/buildTrustWalletTokenLogoUrl(chainKey, contract)— TrustWallet asset-repo logo URLs;getNetworkLogoMetaFromCatalog(...)/NetworkLogoMeta— chain logo metadata.getDefaultSolRpcUrl(useDevnet)/getBtcMempoolApiBase(useTestnet)— config-aware non-EVM endpoint helpers.
Swap tokens (evmSwapTokens.ts):
swapTokensForEvmChain(chainId)/currentWalletSwapTokens(chainId)— per-chain swappable token lists (EvmSwapToken).wethAddressForEvmChain(chainId)— wrapped-native address.SEPOLIA_USDC_ADDRESS/SEPOLIA_USDC_DECIMALS— testnet USDC constants.
./signers — Turnkey signer adapters
TurnkeySigning— structural interface over the Turnkey kit client (type-only import; any compatible client works — no runtime kit dependency).createTurnkeyEip1193Provider({...})→Eip1193Like— EVM EIP-1193 provider backed by Turnkey signing (works with viem/ethers/widget stacks).createTurnkeySolanaProvider({...})— Solana wallet-adapter-style provider over Turnkey (CreateTurnkeySolanaProviderInput).nearImplicitFromSolanaAddress(addr)/nearPublicKeyFromSolanaAddress(addr)— NEAR implicit account + ed25519 public key derived from the Solana (ed25519) Turnkey address.prepareTurnkeyEthSendTransaction(input)/prepareTurnkeyEvmTransaction(input)— build an EVM transaction (gas, nonce, fees) and return a prepared object whose confirm step signs & broadcasts via Turnkey (PreparedTurnkeyEthSend,PreparedTurnkeyEvmTransaction).sendEvmContractCall({account, chainId, to, data, value?})→EvmSendResult— prepare, sign and broadcast one contract call; the sender isaccount.address. ThrowsEvmBroadcastErrorwhen the RPC refuses it.waitForEvmReceipt(chainId, txHash)waits for it to be mined, andviemChainFor(chainId)resolves the chain both use.turnkeyViemAccountFor({client, organizationId, address})→LocalAccount— a viem account backed by Turnkey, built synchronously from a known address (no round-trip, unlikecreateAccount).
./senders — native-coin sending
Uniform two-phase send across all supported chains:
const sender = senderForNetwork(network); // null if unsupported
const prepared = await sender.prepare(input); // fees estimated, tx built
const { txHash } = await prepared.confirm(); // signed via Turnkey + broadcastsenderForNetwork(network)— factory: all EVM slugs shareevmSender; non-EVM slugs map to their sender.- Concrete senders:
evmSender,solanaSender,bitcoinSender,tronSender,tonSender,nearSender,suiSender. - Contract types:
NetworkSender,SendInput(account, toAddress, amount, symbol, network, Turnkey client…),PreparedSend,SendResult,AccountChain,TurnkeySigning,HandleSendTransactionParams. - Shared utils:
toBaseUnits/fromBaseUnits(decimal ↔ base units per network),explorerTxUrl(network, txHash),accountAddress(account),ed25519SignatureBytes(sig),strip0x(v), plus config-aware endpoint getters (solRpcUrl,btcMempoolApi,nearRpcUrl,tronApiBase,tonCenterApi,suiRpcUrl).
./balances — balances & legacy prices
All reads are cached (BALANCE_TTL_MS) and retried; EVM/SOL reads try the Sparkling Indexer first (one shared /v1/balance call per chain) and fall back to raw RPC.
Native balances:
fetchEvmNativeAmount(chainId, address)— any EVM chain.fetchBtcAmount(address)— via mempool.space.fetchSolAmount(address)·fetchNearAmount(accountId)·fetchTronAmount(address)·fetchTonAmount(address)·fetchSuiAmount(address).
Token balances:
fetchErc20Amount(chainId, contract, decimals, address)— ERC-20.fetchTronTokenAmount(...)— TRC-20 (constant-callbalanceOf).fetchNearTokenAmount(...)— NEP-141 (ft_balance_of).fetchTonTokenAmount(master, decimals, owner)— TON jettons (toncenter v3).fetchSuiTokenAmount(coinType, decimals, owner)— Sui coins.
Cache & plumbing:
clearBalanceCache()— drop all cached balances (incl. the indexer cache); pull-to-refresh / post-swap.fetchWithRetry,rpc,cachedBalance,weiToNumber,BALANCE_TTL_MS— reusable low-level helpers.
Pair resolution & portfolio:
resolvePairs(pairs, addresses, { testnet? })— resolvechain:SYMBOLpairs against the inventory intoResolvedPairs (namespace, decimals, deployment).fetchBalancesForPairs(pairs, addresses, { testnet? })reads + prices them into aPortfolioResult;readAmount(pair)reads a single pre-resolved pair (the routing every fetcher shares — reuse it for a fixed portfolio scan).parsePairs,PairSyntaxError,UnknownPairError,resolveTokenAlias,TOKEN_ALIASES,isContractAddress.
Chain overlay (chains the mainnet inventory can't supply):
overlayChainsFor(testnet),hydrateOverlayChains(chains),testnetConfig(chainIds),TESTNET_CHAINS,ADI_MAINNET_CHAIN— a local registry giving ADI (chainId 36900) on every run and a testnet variant of every chain. In testnet moderesolvePairsresolves entirely from it, soadi:USDCand<chain>:<SYMBOL> --testnetread without an inventory entry.
Legacy price helper (kept for the source app):
fetchPricesForSymbols(symbols)+COINGECKO_IDS,PriceInfo— direct CoinGecko read. New code should prefer./prices.
./holdings — portfolio valuation & view
Pure, headless layer above ./balances — no React, no network.
computeHoldingsTotals(resolved, prices)— value each{ symbol, amount }against aPriceInfomap intoHoldingsTotals: per-assetpriceUsd/valueUsd/change24h, atotalUsd(settles to$0, never null, when unpriceable), and atotalUsd24hAgofor the 24h delta.applyHoldingsView(assets, view)— network filter + zero-balance toggle + sort (balance|price|popular, either direction; unvalued assets sort last).- Types/const:
HoldingsView,DEFAULT_HOLDINGS_VIEW,HoldingsSortKey,HoldingsSortDir,SortableHolding,HoldingAmountLike,ValuedHolding,HoldingsTotals.
./prices — merged price layer
Sparkling Indexer first, CoinGecko fallback, stablecoins defaulting to $1. Subpath-only (not in the root barrel — its COINGECKO_IDS/PriceInfo would clash with the legacy helpers in ./balances).
fetchPrices(symbols)→PricesResponse— merged symbol→price map with 24h change (PriceInfo), per-provider result metadata (ProviderResult).fetchTokenPrices(refs)— price by token coordinates (TokenPriceRef, chain + address) rather than symbol.- Providers, callable directly:
fetchCoinGeckoPrices(symbols),fetchCustomPrices(symbols),fetchCustomPricesByAddress(coords)(TokenCoord). - Mode/config helpers:
resolveIndexerType(),useCustomIndexer(),coingeckoBase(),customBase(),PRICES_REVALIDATE_SEC. - Data:
COINGECKO_IDS,CUSTOM_PRICE_TOKENS(CustomToken),STABLECOINS,NATIVE_TOKEN_ADDRESS; typesIndexerType,PriceMap.
./hyperliquid — perps
Clients (@nktkas/hyperliquid):
getHyperliquidTransport()/getHyperliquidInfo()— shared HTTP transport + info client.getHyperliquidExchange(wallet)— exchange (trading) client over any abstract wallet (e.g. a Turnkey viem account).
Market data (perps.ts):
fetchPerpMarket(symbol)→PerpMarket— metadata + live context (mark, funding, OI, leverage caps…).fetchMidPrice(coin)— cheap poll for the live chart tip.fetchCandleSeries(coin, range)→CandleSeries;CHART_RANGES,CHART_RANGE_KEYS,ChartRangeKey;buildLiveLabels(timestamps).- Presentation:
perpIconSrc(symbol),formatFunding,computeDayChange/formatDayChange(DayChange),formatFundingCountdown(nowMs).
Order math (orderMath.ts):
coinSizeFromUsd,roundPerpPx,marketPx,perpOrderSizingFromUsd(PerpOrderSizing),minUsdForPerpOrderNotional,ceilUsdToCents.leverageLadder(maxLeverage)/nearestLadderValue.positionMargin,liquidationPrice,estimateFee.- Constants:
MIN_ORDER_USD,MARKET_MAX_SLIPPAGE.
Arbitrum USDC bridge (bridge.ts):
HL_BRIDGE2,ARBITRUM_USDC,ARBITRUM_CHAIN_ID,USDC_DECIMALS,MIN_DEPOSIT_USDC.usdcToBaseUnits(amount),buildUsdcTransferData(...)— deposit tx calldata.validateDepositAmount({...})→DepositValidation.
Deposit execution (deposit.ts) — the composition around those pure helpers:
depositToHyperliquid({account, amountUsdc, walletUsdc?, waitForConfirmation?})→{txHash, amountUsdc}— gas check, validation, send, wait. Refusals throwHyperliquidDepositErrorcarrying aDepositFailureReason(no-gas|empty|below-min|insufficient) rather than a sentence, so the CLI and a UI word them for their own audience.fetchArbitrumUsdc(address)→ native USDC the wallet holds on Arbitrum.
Order context (context.ts):
fetchPerpOrderContext({address, asset, withBook?})→PerpOrderContextResult— the five readsbuildPerpOrderPlanneeds, plus the derived current leverage, margin mode and other positions' maintenance margin. Returns the rawaccountandmarketalongside thecontext, because the close path reads the position rather than the context. An optional read that fails degrades tonull; no live market throwsPerpMarketUnavailableError.
Order responses:
interpretPerpOrderResponse(res)→PerpOrderPlacement— normalize the exchange response (filled / resting / error).
./polymarket — prediction markets
Subpath-only (name collisions with other modules). Everything needed to trade the Polymarket CLOB from a Turnkey EVM wallet on Polygon:
- Client:
getPolymarketClobClient(input)(PolymarketClobClientInput),POLYMARKET_CLOB_HOST. - Collateral: orders settle in pUSD, not USDC.e —
POLYMARKET_PUSD_ADDRESS,polymarketSettlementAddress(),fetchPredictionPusdBalance(owner). USDC.e remains the deposit asset (POLYMARKET_USDC_ADDRESS,polymarketCollateralAddress(),fetchPredictionUsdcBalance(owner)); it is wrapped into pUSD by the onramp. AlsoPOLYMARKET_CHAIN_ID,POLYMARKET_USDC_DECIMALS. - Deposit Wallets (the only wallet type that can trade since 2026-05-04):
deriveDepositWallet(signer),createDepositWallet(signer, opts)— deployed by the relayer on builder credentials alone, with no signature from the user —isDepositWalletDeployed(...),execDepositWalletBatch(...)(gasless batch),depositWalletBatchTypedData(...),buildDepositWalletApprovalCalls(),DEPOSIT_WALLET_FACTORY,DEPOSIT_WALLET_BEACON. - Deposit Wallet signing (ERC-7739 / POLY_1271):
depositWalletOrderTypedData(...),wrapDepositWalletSignature(...),exchangeDomainSeparator(...),orderContentsHash(...),ORDER_TYPE_STRING,SIGNATURE_TYPE_DEPOSIT_WALLET. - Onramp:
fetchDepositAddresses(wallet),fetchEvmDepositAddress(wallet),parseDepositAddresses(raw),BRIDGE_BASE— per-chain deposit addresses that convert incoming USDC/USDC.e into pUSD. - Account lifecycle — build on this, not on the pieces below:
fetchPolymarketAccount({eoa, account})→ status + readiness verdict;onboardPolymarketAccount(ctx)(deploy + approvals, idempotent);fundPolymarketAccount({amountUsd, route})withroute: "bridge" | "uniswap";wrapWalletCollateral(...);withdrawFromPolymarket({amountUsd, to, token}). Everything environment-specific is injected — signing as a viemLocalAccount, broadcasting as anEvmSender+EvmConfirmer, transports andbuilderCredsas options — so a CLI, a server and a browser share one implementation. Progress arrives viaonStep(PolymarketStep). - Server proxy (a browser cannot reach the relayer, CLOB or bridge — all CORS-closed, and the relayer needs credentials that must not ship to a browser):
forwardPolymarketRequest(config, request), plus the boundrelayerProxy(creds),clobProxy(paths),bridgeProxy(builderCode?).RELAYER_PROXY_PATHS/BRIDGE_PROXY_PATHSare the allow-lists, andneedsBuilderAuth(path)encodes which requests are signed — including that/v1/*signatures must cover the query string, which returns 401 otherwise. - Readiness:
assessPolymarketReadiness(input)(pure) andfetchClobCollateral(account)— distinguishes "no wallet", "unfunded", "funded in the wrong token" and "the exchange cannot see it", which the CLOB's own error messages do not. - Protocol constants (
clob.ts):CLOB_BASE,CTF_EXCHANGE_V2,NEG_RISK_EXCHANGE_V2,USDC,CONDITIONAL_TOKENS, exchange EIP-712 domain,exchangeFor(negRisk),roundingFor(tickSize). - Auth:
clobAuthTypedData(...)+CLOB_AUTH_MESSAGE(L1 EIP-712 login),deriveClobCreds(...)→ClobCreds(L2 API creds),hmacL2(...)(L2 request signing). - Orders:
buildPolymarketOrder(args)(BuildOrderArgs→PostOrder),ORDER_TYPES(EIP-712 order struct). - Safe (LEGACY proxy wallet — pre-2026-05-04 accounts; holds funds but the CLOB refuses its orders):
deriveSafeAddress(...)+SAFE_FACTORY,SAFE_MULTISEND,SAFE_INIT_CODE_HASH; Safe tx plumbing —safeTxStructHash,splitAndPackSig,encodeMultisend,aggregateSafeTx(SafeInnerTx,OP_CALL,OP_DELEGATE_CALL). - Approvals:
buildApprovalTxns()+USDC_SPENDERS,CTF_OPERATORS,PRIMARY_USDC_SPENDER,PRIMARY_CTF_OPERATOR. - Relayer (gasless Safe ops):
isSafeDeployed(...),deploySafe(...),execSafeBatch(...),waitForRelayerTx(...)(RelayerOptions,WaitForRelayerTxOptions,PolymarketBuilderCreds,RELAYER_BASE). - Market data (Gamma API):
fetchTopEvent(...),fetchOutcomeSeries(...),fetchLatestPrice(...),parseEvent(raw)(PmMarket,PmOutcome,OutcomeSeries,RawEvent,RawEventMarket); chart rangesCHART_RANGES,CHART_RANGE_KEYS,ChartRange,rangeToParams(...)(RangeParams); presentationformatProbability(price),formatCloses(endDate);GAMMA_BASE. - Transport:
directTransport(base)(PolymarketTransport,PolymarketProxyRequest) — call upstreams directly or route through your own proxy.
./adi — ADI PredictStreet
Prediction markets with per-user on-chain vaults, an off-chain orderbook, and EIP-712-signed orders on ADI (chainId 36900). The partner API key never enters the package — pass creds (apiKey, userWallet) / a transport per call, the same injectable model as ./polymarket. Signing is caller-injected.
- Environment:
fetchAdiContracts()reads the live exchange/USDC addresses + chain id from the platform (never hardcode).ADI_CHAIN_ID,ADI_RPC_URL,ADI_TESTNET_API_BASE,adiChain(viem). - Market data (public):
fetchAdiMarkets,fetchAdiMarket(by slug),fetchAdiOrderbook. - Orders:
buildOrder(pure EIP-712 build;orderDomain,ORDER_EIP712_TYPES,computeOrderAmounts,parseUnits6,randomSalt,OrderSide,SignatureType) →placeAdiOrder(input, signTypedData). Lifecycle:cancelAdiOrder,cancelAdiOrders(≤100),cancelAllAdiOrders,fetchAdiOpenOrders,fetchAdiOrderHistory. - Portfolio:
fetchAdiVault,fetchAdiPositions,fetchAdiBalances(spendable/unsettled/locked/quarantined per token). - Vault funds:
depositToAdiVault({ account, … })(approve + deposit). Withdrawals (dual-signedWithdrawERC20):buildWithdrawTypedData(pure) →withdrawFromAdiVault(params, signWithdraw), plusfetchAdiDepositSources(cleared destinations),fetchAdiWithdrawals,fetchAdiWithdrawal,fetchAdiWithdrawalFee,cancelAdiWithdrawal. - Errors:
AdiError(carries the upstream rejectcode; a 2xx carrying a rejection still throws, so a refused order is never mistaken for a placed one).
./signals — Envy AI signals
Read-only client for the envy-stream proxy, the strategy engine behind the AI screens. A strategy is a set of entry/exit rules evaluated upstream on 15-minute candles; a firing rule becomes a signal. Envy executes nothing — acting on a signal is a Hyperliquid perp order the caller places (./hyperliquid).
The proxy holds the house subscription key server-side, so no credential leaves this package. It has no public host: apiBases.envyStream is "" by default (version segment included when set, e.g. https://envy-dev.edgetrad.ing/v1), and every call is a no-op returning null / [] until it is set — envyConfigured() reports which.
- Feed:
getSignals(limit = SIGNALS_LIMIT)— newest first across every owned strategy; 200 is the proxy's cap, a recent window of a durable history rather than "everything".describeSignal(strategyId, coin, rule)returns the one/describerule that fired, with the coin's risk frame folded in. - Stream:
streamSignals({ signal })— an async generator of[event, payload]over SSE (snapshot,open,refresh,close;pingswallowed). The snapshot replays on every connect, so a consumer must be idempotent per signal id. Reconnect policy is the caller's; a socket quiet forSTREAM_READ_TIMEOUT_MS(60s, against a 15s heartbeat) ends the generator.parseSseChunkis exported for callers driving their own transport. - Catalogue:
listStrategies({ category, window, limit })(ranked by PnL;categoryis a display label filtered client-side viainCategory, because upstream categories are comma-joined free text),getStrategy(falls back to a listing scan on 503catalogue_incomplete),describeStrategy/describe,openPositions(the strategy's virtual book, not the user's),strategyCoinCount/strategyCoinCounts(capped atDESCRIBE_CONCURRENCY). - Equity:
getEquityCurve,getEquity,equitySparks(firstSPARK_ROWSrows only,SPARK_CONCURRENCYat a time — the key is shared app-wide and must not be spent on decoration). - Parsers:
signalFromRow,signalsFromPayload,strategiesFromPayload,descriptionFromPayload,equityFromPayload— pure and defensive; a row missingidorcoinis dropped rather than half-rendered. - Labels:
signalSide,priceLabel(precision steps with magnitude, matchingformatUsdPrice),signalFiredLabel,startedLabel,riskBand(grades on max drawdown; an unproven strategy gradesHigh),strategySubtitle,axisLabels,allocationSharePct(upstreamallocationPctis a budget slice, not a share — a single-coin strategy reports 250). - Acting on one:
planSignalTrade({ signal, market, availableUsd, amountUsd })is a pure function returning the whole trade ticket — side, the leverage clamp (strategy × coin ×SIGNAL_MAX_LEVERAGE), the market order's protective limit and exact px/sz, the margin the position commits, and a singleblocker(expired→not_tradable→no_amount→below_min→insufficient_margin) orready.signalPerpOrderRequest(plan)hands a ready plan to./hyperliquid's existing pipeline. Helpers:signalSideFor,formatCountdown,amountForPercent/percentForAmount.below_mincompares the notional the exchange will see after size flooring, never the typed amount, andminOrderUsdreports what to type instead (SPRK-529).insufficient_margincomparesamountUsd / leverageagainst the free collateral — what the exchange actually enforces.- There is deliberately no
executeSignalTrade: submission belongs to the two paths that already exist, so a third cannot bypass the CLI's spend cap.
- Caching: successful payloads for 120s, failures for 15s under a separate memo so a dead upstream is dialled once, not once per row.
clearEnvyCache()drops both. - Nothing throws. An unreachable proxy, a 401 (the proxy's own key), a 4xx/5xx or unparseable JSON all read as an empty result. Where that ambiguity matters — a terminal must not report "no signals" for a proxy that is simply down —
getSignalsOrNull/listStrategiesOrNullreturnnullinstead, sharing the same cache entry as their[]-returning siblings.
./inventory — Sparkling inventory client
searchTicker(query)— resolve one ticker from free text.getTokenInformation(id)— canonical token info (TokenInformation).listTokens()/listPairs()/listChains()— full listings (keyset pagination walked to the end).loadInventoryUniverse()/prefetchInventoryUniverse()— cached combined load of tokens+deployments (InventoryUniverse); prefetch swallows errors.resolveDeploymentForSymbol(symbol, chain)— pick the best deployment (verification tier, non-bridge preference).loadChains— cached chain list.- Plumbing:
inventoryRequest<T>(...)(InventoryRequestOptions),InventoryError(aliasInventoryClientError). - Types:
Token,Deployment,Pair,Chain,ProviderMapping,TokenWithRelations,InventoryPage<T>,TokensPage,EntityStatus, ….
./indexer — Sparkling indexer client
searchTickers(query, opts)→TickerSearchResponse— ticker search (TickerSearchItem; throws with the server error code).getTokenPriceGraph(opts)→PriceGraphResponse— price series (Timeframe,PriceGraphPoint); works through a same-origin proxy or direct.fetchWalletSummary(...)→WalletSummaryResult— per-chain wallet USD totals; fails fast (8s) so callers can fall back.WALLET_SUMMARY_CHAINS.- Portfolio:
EVM_INDEXER_CHAINS,sumWalletTotals(values),combinePortfolioTotal({...})(EvmIndexerNetwork,IndexerChain). - Balances backing
./balances:fetchIndexerBalances(...)(IndexerBalances),indexerEvmNativeAmount,indexerErc20Amount,indexerSolAmount(null = "fall back to RPC", not zero),indexerEvmSlug,clearIndexerBalanceCache(),INDEXER_BALANCE_CHAINS.
./swap — swap providers, Aurora bridge & NEAR Intents
Provider layer
Four venues behind one SwapProvider interface. They do not share a way of moving funds — 1Click hands out a deposit address, everything else hands back a transaction to sign — so SwapExecution keeps the two models apart instead of pretending they are one shape.
| Provider | Reach |
|---|---|
1click | cross-chain only, 34 chains from NEAR Intents' own registry |
uniswap | same-chain only, on the 7 chains with a verified v3 deployment |
gaszip | native coin → native coin, cross-chain, 190+ EVM chains |
lifi | EVM ↔ EVM, 69 chains, same-chain and cross-chain (Stargate/gas.zip) |
quoteAllSwaps(req, { providers? })→QuoteSweep— quote every provider concurrently;routesbest-first,refusalswith a reason per venue, both in a deterministic order.quoteSwap(req, opts?)→SwapRoute— the single best route, or an error naming every refusal.rankRoutes(routes)— the ordering: rawtoAmountdescending, ties broken bydurationSec. Deliberately not USD: every provider is quoted for the same destination asset, so the integers compare directly, while USD would import noise from whichever price feed each provider happens to use.planSwap(route, ctx)→SwapExecution—{ kind: "evm-call", chainId, to, data, value }or{ kind: "deposit", address, chain, amountBase }.PlanContextcarriesoneClickJwtwhen 1Click needs it.listSwapProviders()/findSwapProvider(name)/SWAP_PROVIDERS— the registry.routableBy(req, providers?)— names of providers whosesupports()accepts the pair. Local and synchronous, so a failure message can teach without a network round trip.resolveChain(slug, opts?)→ResolvedChain— the layered chain vocabulary: Sparkling inventory first, then the local EVM catalog. This is what lets a provider reach a chain the inventory has never heard of.resolveEvmSwapToken(...)/fetchTokenDecimals(...)/EvmTokenUnresolved— ticker or contract address → a token with fetched decimals. Assuming 18 is right for native coins and wrong by six orders of magnitude for USDC.- Types:
SwapRequest,SwapEndpoint,SwapRoute,SwapExecution,SwapProvider,PlanContext,QuoteSweep,ProviderInfo, andSwapUnsupported(a provider declining, with the reason shown to the user). - gas.zip:
gasZipQuoteUrl(...),GAS_ZIP_MIN_USD. Its quote URL takes the native chain id while the deposit calldata carries gas.zip's own short code; swapping them returns "Insufficent Liquidity", which reads as a market condition and is not one. - LI.FI:
LIFI_NATIVE(the zero address names a chain's own coin),LifiToken.
1Click (NEAR Intents)
fetchOneClickTokens / clearOneClickTokenCache, resolveOneClickAsset → ResolvedAsset, fetchOneClickQuote (dry for a real quote that moves nothing), liveDepositAddress, fetchOneClickStatus, oneClickBlockchainFor, ONE_CLICK_CHAIN_ALIASES / ONE_CLICK_SYMBOL_ALIASES, OneClickError.
Uniswap v3 (same-chain)
hasUniswapDeployment(chainId) / uniswapChainIds() / uniswapDeployment(), quoteFeeTier and bestUniswapQuote over UNISWAP_V3_FEE_TIERS, minimumOut, buildSwapCall / buildApproveCall / fetchAllowance, UniswapError.
Aurora bridge
buildAuroraTurnkeyProviders({...})— EVM + Solana providers for the Aurora Intents swap widget from a single Turnkey session (wire into the widget'sprovidersconfig).QUOTE_REFRESH_MS/QUOTE_REFRESH_SECONDS— quote refresh cadence.- NEAR Intents (NEP-413):
hashNep413(input)— deterministic 32-byte signing digest (golden-tested against the defuse SDK).tokenDiffIntent(...)/Intent—token_diffswap intent (defuse asset ids → signed deltas).signNearIntent(input)→SignedNearIntent— sign with a Turnkey wallet (SignNearIntentInput).publishNearIntent(...)— publish to the solver relay.INTENTS_CONTRACT,SOLVER_RELAY_BASE.
./activity — on-chain activity feed
Paged per-chain fetchers returning ActivityPage ({ items, nextCursor } of normalized ActivityItems: direction, amount, symbol, counterparty, time, tx link). Cursors are opaque and chain-specific; pass nextCursor back to continue.
fetchEvmActivity(chainId, address, opts?)— native (txlist) or, withcontract, ERC-20 transfers (tokentx); Etherscan-compatible or Blockscout backends (txListApiBase(chainId)).fetchBtcActivity(address, opts?)— mempool.space, incl. unconfirmed on page 1.fetchTronActivity(address, opts?)— native or TRC-20 (TronGrid fingerprint cursor).fetchTonActivity(address, opts?)— TON transfers + jetton transfers.fetchSolActivity(address, opts?)— SOL / SPL token balance deltas.fetchSuiActivity(address, opts?)— from/to digests merged (mergeSuiItems).fetchNearActivity(account, opts?)— via nearblocks (native + FT).- Pure mappers, usable against your own upstream responses:
mapExplorerRows,mapBtcTxs,mapTronTrc20Rows,mapTronNativeRows,mapTonTxs,mapTonJettonTransfers,mapSolTxs,mapSuiTxs,mapNearTxns,mapNearFtTxns,tronAddressToHex; row types (ExplorerRow,BtcTx,SolTx, …) and constants (PAGE_SIZE, tx-URL prefixes,SUI_COIN_TYPE).
./zgcompute — 0G Compute (AI inference)
Subpath-only; heavy peers (@0gfoundation/0g-compute-ts-sdk, ethers, openai) load lazily.
- Brokers:
createZGBroker(eip1193, chainId)(browser wallet),createZGBrokerFromAccount(viemAccount, env)(CLI/server),createZGBrokerFromSigner(signer)(raw ethers),createZGReadOnlyBroker(env)(public reads, no wallet) —ZGBroker,ZGReadOnlyBroker,Eip1193Like. - Networks:
ZgEnv("mainnet" | "testnet"),zgChainId(env),zgRpcUrl(env),zgEnvForChainId(id),zgContracts(env); constantsZG_MAINNET_CHAIN_ID(16661),ZG_TESTNET_CHAIN_ID(16602),ZG_NEURON_PER_OG,ZG_MIN_LEDGER_FUND(3),ZG_MIN_TRANSFER(1),ZG_DEFAULT_FUND_AMOUNT— the MAINNET minimums, and the fallback forfetchZgLimits. - Limits:
fetchZgLimits(env)→ZgLimits— the deployment's ownMIN_ACCOUNT_BALANCE/MIN_TRANSFER_AMOUNT(mainnet 3/1 0G, testnet 0.1/0.01), cached per env;suggestedFundAmount(limits),clearZgLimitsCache(). Anything deciding whether an amount is allowed must use these, not the mainnet constants. fundZgLedgerViaContract({account, env, amount, exists})→ZgFundResult— opens or tops up the ledger by calling the contract directly, which is what makes testnet funding possible: the SDK's own funding methods refuse anything below a hardcoded 3 0G.transferZgFundViaContract({account, env, provider, amount})— open or top up a provider sub-account at a chosen amount;zgServiceName(env, service)reads the registry name (inference-v1.0) the ledger'stransferFundexpects.ZG_PROVIDER_MIN_LOCKED(1 0G) is what the provider's proxy demands before it serves a request — a floor no client change can lower.- Ledger:
fetchZgLedger(broker)→ZgLedger(total / locked / available plus per-providerZgSubAccounts for inference and fine-tuning);fundZgLedger,refundZgLedger,retrieveZgFunds({service, provider}),deleteZgLedger;getAvailableFund,isFunded,isNoLedgerError. Amounts areZgAmount { og, neuron }—neuronToOg,zgAmount. - Portfolio:
fetchZgLedgerHolding(broker)/zgLedgerHolding(ledger)→ZgLedgerHoldingon networkZG_LEDGER_NETWORK("0g-compute"), ready forcomputeHoldingsTotals. Committed funds, not the wallet's on-chain 0G. - Providers:
listZgProviders({env|broker, search})→ZGProvider[]with prices per 1M tokens, TEE flag and health;pickProvider(broker),findZgProvider(address). - Inference:
ensureProviderAcknowledged,isProviderAcknowledged,getZgServiceMetadata,getZgRequestHeaders(may transfer funds on-chain),verifyZgResponse→true | false | null. - Fine-tuning:
listZgFineTuningProviders(env)→ZgFineTuningProvider[]. Read-only by construction — running a job needs the raw private key the SDK reads off anethers.Wallet, which Turnkey does not expose. streamChat(opts)→StreamChatResult— OpenAI-client streaming chat against a 0G provider endpoint with TEE verification (ChatTurn).checkZgFunding({address, env, amount})→ZgFundingCheck— what a top-up costs the wallet before anything signs: native 0G held, amount, gas reserve (ZG_GAS_RESERVE_OG),shortfall,canFund, and the chain/coin to send.isInsufficientFundsError(err)classifies the failure if one still happens.zgAttestation(result)→"ok" | "invalid" | "unverified" | null— the one claim a UI can honestly make about a reply.nullmeans say nothing (no attestation was on offer); a check that could not run isunverified, neverinvalid.runZgTurn(broker, opts)→ZgTurnResult— one whole turn: the funding gate, provider pick + acknowledgement, headers, the streamed reply, attestation. The order is the point — acknowledging and minting headers sign transfers, so the balance is checked before the user is asked for a signature. Refusals are results (unfunded,empty), not exceptions, and a verification that itself errors leavesverified: null+verifyErrorrather than discarding a reply that was already paid for.- Lazy loaders:
loadZgSdk()(ZGSdk),loadEthers(),loadOpenAI().
./venue — one model across venues
The only place that says what a position and an order have in common, so a portfolio view does not have to know three dialects. Adapters translate; they do not recompute — the arithmetic already lives in ./hyperliquid, ./polymarket and ./adi.
- Types:
UnifiedPosition,UnifiedOrder,UnifiedFill,CollateralView,VenuePnl,VenueCapabilities,CancelSelector,VenueAdapter,VenueContext(signeris a THUNK, so a public read never mints a key). - Adapters:
hyperliquidAdapter,polymarketAdapter,adiAdapter,ALL_VENUES. sweepVenues(adapters, ctx, read)→VenueSweep<T>— concurrent reads where a failure becomes THAT VENUE'sproblem, never the sweep's. "Holds nothing" and "could not be asked" are different answers and never render alike.sortPositions,totalPositions,totalResting— missing numbers are counted (unpriced), never summed as zero.selectVenues(adapters, filter)— an unknown name is returned, not ignored.parseWindow("7d" | "30s" | "2w")→ ms.fillsToCsv(fills)/csvCell— RFC 4180 quoting, because a prediction-market question contains commas.planRebalance({target, usd, source?, collateral})→RebalancePlan— the route as existing commands. Refuses an unreadable source balance (unknown is not "enough") and a target whose settlement chain is only known at runtime.- Route journal:
newRouteJournal,nextStep(a SENT step comes back before any pending one — resume waits, it never skips),hasSettled,recordStep,routeProgress,settleChecksFor. Settlement is a BALANCE question, which survives a dropped receipt in a way a transaction hash does not.
./execution — order slicing & supervision
planSlices(parent, parentId)→SlicePlan— TWAP (timed), ladder (immediate), iceberg (sequential). Notionals sum EXACTLY to the parent; the last slice absorbs the remainder.cloidFor(parentId, index)— a DERIVED client order id (24 hex of parent + 8 of index). This is what makes resume safe: a child resent after a crash carries the same id and the exchange refuses the duplicate.- Journal:
newJournal,outstanding(asentchild counts as done — its cloid is spent),recordSent,cancel,progress. A parent with any rejected child finishesfailed, neverdone. - Trailing stop:
startTrail,updateTrail,parseDistance. The stop only moves favourably, and a tick that both sets a new extreme and breaches the old stop TRIGGERS — it is one price, not two.
./strategy — recipes & backtest
parseStrategy(json)→Strategy— strict, and separate from running, so a bad recipe fails atstrategy checkrather than at 3am. A rule fires ONCE unless"maxFires": null(DEFAULT_MAX_FIRES).describeStrategy(strategy)— one line per rule, with its firing budget.shouldFire(rule, state, value, now)— delegates the edge/cooldown decision to./alertsrather than reimplementing it, so a live run and a backtest cannot disagree about what "has crossed" means.backtest({strategy, series, feeRate})→BacktestResult— trades, equity curve, realized/fees/open. ReturnsCAVEATSalongside the numbers, and puts a rule it cannot replay inunsupportedrather than dropping it.
./alerts — threshold rules
parseCondition("price:BTC < 90000")/formatCondition/compare— strict grammar (SOURCE_METRICS); a rule that parsed into something other than what was meant would look armed and never fire.decide(rule, actual, now)→Verdict— EDGE-triggered with a cooldown. Anactualof null leaves the state UNTOUCHED: recording "false" would re-arm the edge and invent a crossing.liveReader({adapters, ctx})— prices, venue position metrics and perp funding, cached per sweep.deliver(rule, value, deps)— sequential, and a failed channel is reported: an alert nobody received looks exactly like one that never fired.
./risk — standing limits
checkDailyLoss,checkOrderNotional,checkLeverage,checkWithdrawTarget(case-insensitive — an EVM address is the same address in any casing),isEmpty,describeLimits.- Each check is separate so it can be applied where its information exists: the CLI runs the first three before signing and the notional one per reservation.
./paper — paper trading ledger
applyFill(positions, fill)— opening, adding (average in), reducing (realize) and FLIPPING through zero are handled separately; one formula for all four is where a paper book drifts from a real one.bookFromFills,markToMarket(an unpriced coin marksnull, never zero),PAPER_CAVEATS,PAPER_FEE_RATE.
./screen — finding opportunities
screenFunding(markets, filter)/toFundingRow— ANNUALISED, because an hourly rate hides the point; ranked by |APR| because a negative rate is as tradable from the other side; open interest valued in USD because coin units put a memecoin above BTC.screenEdge(markets, filter)— prediction markets whose outcome prices do not sum to 1. A market with an unquoted outcome is DROPPED, not ranked as underpriced.EDGE_CAVEATS,DEFAULT_MIN_EDGE.parseRate("20%" | "0.2" | "20").
./cow — CoW Protocol limit orders
buildLimitOrder(...)→CowOrder;buyAmountFor(...)in integer maths, on the number that decides whether an order is worth signing.cowDomain(chainId),COW_ORDER_TYPES,GPV2_SETTLEMENT,GPV2_VAULT_RELAYER— the relayer is what must be APPROVED; approving settlement instead is the classic integration bug, where the signature is valid and the order simply sits.postLimitOrder,fetchCowOrders,cancelCowOrders,COW_NETWORKS.toTokenUnits/fromTokenUnits— explicit decimals, unlike the network-based pair in./senders.
./wallet — batch payouts, allowances, stuck transactions
parsePayouts(text)/splitCsvLine/summarize— one bad row refuses the WHOLE file, listing every problem: a half-executed payout is worse than none, because the output cannot say which half went.isUnlimited(anything ≥ 2^128 — several contracts approve 2^255, and calling those "limited" overstates safety),formatAllowance,revokeCallData,allowanceCallData,byRisk,APPROVE_ABI.bumpFees/planReplacement— a replacement must clear the node's minimum or the original stays stuck while the operator believes otherwise. A cancel sends 0 to the wallet's OWN address.
./cex — centralized exchanges
Binance USD-M futures, chosen because a futures position maps ONTO UnifiedPosition — entry, leverage, liquidation, unrealized — rather than into it. Spot balances would have to be reported as positions with a null entry, which is a shape, not a position.
binanceAdapter— a fullVenueAdapter: positions, orders, cancel, collateral, windowed PnL from the income ledger, per-symbol fills.fetchBinanceFunding()— PUBLIC, no key. Divided byBINANCE_FUNDING_INTERVAL_HOURSat the boundary, because Binance quotes per 8-hour interval and Hyperliquid quotes hourly; comparing them as printed overstates Binance eightfold.fetchBinancePermissions(credentials)— what the key may actually do, asked of the exchange. The CLI refuses to store a key that can withdraw.hmacSha256Hex,toQuery(insertion order — the signature covers the exact string sent),signedQuery. Web Crypto, sosrc/stays browser-safe.toUnifiedPosition/toUnifiedOrder— Binance returns every symbol including flat ones, and a zero row is dropped rather than shown as a position; a0liquidation price means "cannot be liquidated" and becomes null rather than reading as "liquidates at zero".
./evm — will this transaction work?
Two answers to two different questions.
preflight(client, {from, to, data, value})—eth_callagainst the LIVE chain. Free, instant, nothing to install: "would this revert right now?". A node that cannot be reached returnsok: true, because refusing a transaction over a diagnostic that could not run turns a diagnostic into a gate.decodeRevert/revertDataFrom—Error(string),Panic(uint256)and a custom-error selector. The data is found through viem's typed error chain (BaseError.walk→RawContractError), never scraped from a message: a client's message embeds the request it sent, so the first hex in it is the CALLDATA, and reading that reports a revert for a call that succeeded.anvilArgs/forkRpcUrl/receiptStatus/describeFork— the pure parts of running a transaction on a forked chain, which answers whateth_callcannot: gas used, state changes, and several transactions in sequence. The outcome issuccess | reverted | unknown, never a boolean: a fork can accept a transaction and produce no readable receipt, and calling that either "succeeded" or "reverted" states a result it never gave.
./chart — terminal charts
renderLineChart(values, opts)— a flat series draws on one row instead of dividing by zero.downsample(values, width)— averages each bucket; sampling would drop the spike the chart was drawn to show.sparkline(values, width),buildTimeLabels.
./react — React bindings
Optional peers: react, @turnkey/react-wallet-kit (+ @turnkey/viem for the viem hook). Non-React consumers never pull React.
useTurnkeyAccounts({...})→ChainAccount[]— the session's per-chain accounts (address, chain key, format).useTurnkeyViemAccount()— a viemAccountbacked by Turnkey signing.useHyperliquidAddress()→HyperliquidAddress | null— the EVM address used for Hyperliquid.usePlacePerpOrder()— place a perp order (PlacePerpOrderArgs,PerpOrderSideLabel). Plans throughbuildPerpOrderPlanfirst, so the order is validated before it is signed.useHyperliquidExchangeClient(address)→HyperliquidExchangeClientState— a Turnkey-signedExchangeClient, built synchronously.useHyperliquidDeposit(account)— deposit USDC from Arbitrum onto the exchange.useHyperliquidPerps({ intervalMs, byVolume })→AsyncState<PerpMarket[]>— the polled perp board. A failed poll keeps the rows already on screen.useHyperliquidMargin(address, { intervalMs })→AsyncState<number>— the polled free collateral. Anulladdress is the resting state, not an error.useSignals({ limit, intervalMs })→AsyncState<EnvySignal[]>— the polled Envy feed.useSignalStream({ enabled, reconnectMs })→SignalStreamState— the live set over SSE, held in a Map keyed by signal id so the proxy's replayed snapshot overwrites rather than duplicates. Reconnects; never connects at all withoutapiBases.envyStream.useSignalTrade({ signal, amountUsd, address, account })→SignalTradeState— the market, the collateral, theplanSignalTraderesult andopen(), composed. Submission goes throughusePlacePerpOrder.useTickerSearch(query, opts?)→TickerSearchState— debounced indexer ticker search.useTokenPriceGraph(...)→TokenPriceGraphState— price-graph data for a token/timeframe.chainFromAddressFormat(addressFormat)→ChainKey | null— map a Turnkey address format to a chain key.useZgBroker(eip1193, env)→AsyncState<ZGBroker>— the 0G broker for a connected wallet, rebuilt only when the wallet or network changes.useZgLedger(broker, {pollMs})→UseZgLedgerResult— balances plusfunded,fund(amount)andfunding. A missing ledger is data, not an error.useZgProviders(env, search?)→AsyncState<ZGProvider[]>— public listing, usable before a wallet is connected.useZgChat(broker, provider, {verify})→UseZgChatResult— streaming reply,abort(), and a three-stateverified(true | false | null).useTurnkeyZgBroker(env)→TurnkeyZgBroker— the broker for the wallet the Turnkey kit holds, built on the firstensureBroker()rather than on mount, so rendering a chat costs nothing until someone sends. The resolved broker is published as state foruseZgLedgerto hang off.useSwapPanel({rail, slippageBps, oneClickJwt, onError})→UseSwapPanelResult— quote/review/sign for any rail:"auto"sweeps all and keeps the best route, or pin"1click" | "uniswap" | "gaszip" | "lifi". Reportsrefusalsper rail so a dead end can say which venue declined and why.runSwapRoute(...)is the shared plan-and-sign step underneath.useZgRefuel(env)→UseZgRefuelResult— getting native 0G into a wallet that has none: the chains where it already holds gas, a gas.zip quote into 0G, andrefuel()to sign it. gas.zip is the only rail here that reaches 0G at all (1Click doesn't list the chain, Uniswap can't leave its own).useZgConversation({env, onError, verify})→UseZgConversationResult— a chat whose transcript stays with the app:ask(messages, sink)opens the reply on the first token (so a turn that fails before any output leaves no orphaned bubble), returns an outcome (replied/unfunded/empty/failed/aborted) instead of throwing, and exposesattestationById,needsFundingandfund. This is the one to reach for in a UI.useZgAssistant(env)→UseZgAssistantResult— broker + ledger +runZgTurnbehind oneask(messages, {onToken}). Turns are serialized (acknowledgement and header minting sign from the same address, so overlapping turns would race nonces) and the provider is pinned per wallet after the first turn. The app above keeps only its transcript.
Cross-cutting behaviors
- Caching. Balance reads share a TTL cache (
BALANCE_TTL_MS); the indexer balance path caches one/v1/balanceper chain; inventory universe/chains use in-flight-deduped promise caches.clearBalanceCache()/clearIndexerBalanceCache()force fresh reads. - Indexer-first, RPC-fallback. EVM/SOL balances try the Sparkling Indexer and silently fall back to raw RPC (
nullfrom the indexer path means "fall back", never "zero"). - Retry. Upstream HTTP goes through
fetchWithRetry. - No host env. Endpoints come only from
configure()— neverNEXT_PUBLIC_*. - No kit lock-in. Turnkey signing is the structural
TurnkeySigninginterface; any compatible client works. - Tree-shaking. Subpath exports +
sideEffects: false; chain SDKs are optional peers, so unused chains never enter the bundle. The root barrel excludesreact,polymarket,prices, andzgcompute(name collisions / optional-dep isolation) — import those from their subpaths.
Peer-dependency matrix
Hard deps: viem (peer), @noble/curves, @scure/*, borsh. Everything else is an optional peer — install only what you use:
| Capability | Install |
|---|---|
| EVM signing / Hyperliquid | @turnkey/viem, viem |
| Solana | @solana/web3.js, @turnkey/solana |
| NEAR | @near-js/crypto, @near-js/providers, @near-js/transactions |
| TON | @ton/core, @ton/ton, @ton/crypto |
| Sui | @mysten/sui |
| Tron | tronweb |
| Hyperliquid | @nktkas/hyperliquid |
| Polymarket | @polymarket/clob-client-v2 |
Aurora swap (./swap) | @aurora-is-near/intents-swap-widget* |
0G Compute (./zgcompute) | @0gfoundation/0g-compute-ts-sdk, ethers, openai |
React hooks (./react) | react, react-dom, @turnkey/react-wallet-kit |