fyuz-sdk 0.1.1

Async Rust client for the Fyuz public REST API β€” bonding-curve token market data on BNB Smart Chain.
Documentation

πŸ¦€ fyuz-sdk

Async Rust client for the Fyuz bonding-curve launchpad on BNB Smart Chain. Market data, quotes, and unsigned transactions β€” it never touches your keys.

crates.io version docs.rs license rust tests unsigned only


⚠️ Important β€” the crate is fyuz-sdk, but the library you import is fyuz.

use fyuz::FyuzClient;   // βœ…  crate `fyuz-sdk`, lib name `fyuz`
use fyuz_sdk::FyuzClient; // ❌ does not exist

This trips up nearly everyone once. It is set by [lib] name = "fyuz" in Cargo.toml, and it is why the rendered API docs at docs.rs/fyuz-sdk are titled fyuz.

Fyuz is a bonding-curve token launchpad on BNB Smart Chain (chain 56). This crate is a typed, async/tokio client for its public REST API β€” 28 unauthenticated endpoints covering discovery, tokens, trades, candles, analytics, wallets, leaderboards and the fee Distributor β€” plus a trading layer that prices swaps against the live contract and hands you unsigned transactions to sign with whatever wallet you already have. Every response is a named struct, every decimal amount stays an exact string, and nothing here holds a private key.


πŸ“¦ Install

cargo add fyuz-sdk
cargo add tokio --features macros,rt-multi-thread

Or by hand:

[dependencies]
fyuz-sdk = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
πŸ“¦ Crate fyuz-sdk
πŸ¦€ Import as use fyuz::…
πŸ“– Docs https://docs.rs/fyuz-sdk
🌐 API https://api.fyuz.fun
🧰 MSRV Rust 1.75
πŸ”“ Auth none β€” every endpoint here is public and safe to poll

πŸš€ Quickstart

use fyuz::{DiscoverParams, DiscoverTab, FyuzClient};

#[tokio::main]
async fn main() -> Result<(), fyuz::Error> {
    let client = FyuzClient::new()?;

    let trending = client
        .discover(&DiscoverParams::new().tab(DiscoverTab::Trending).limit(5))
        .await?;

    for t in &trending {
        let venue = if t.launched { "PancakeSwap" } else { "bonding curve" };
        println!(
            "{:<8} ${:>10.0}  {:>5.1}% to graduation  ({venue})",
            t.symbol, t.marketcap, t.graduation_pct
        );
    }

    Ok(())
}

Real output, run against https://api.fyuz.fun:

PROGI    $      4473   14.9% to graduation  (bonding curve)
TUTACO   $      4473   14.9% to graduation  (bonding curve)
SRIRA    $      4473   14.9% to graduation  (bonding curve)
CROIS    $      4473   14.9% to graduation  (bonding curve)
SLOIN    $      4472   14.9% to graduation  (bonding curve)

FyuzClient is cheap to clone β€” the reqwest::Client, its connection pool and all configuration sit behind an Arc. Build one per process and clone it around. Every knob is optional:

use std::time::Duration;

let client = fyuz::FyuzClient::builder()
    .base_url("https://api.fyuz.fun")?           // default
    .timeout(Duration::from_secs(30))            // default
    .max_retries(3)                              // default; 0 disables retrying
    .retry_base_delay(Duration::from_millis(250))// default
    .max_retry_delay(Duration::from_secs(8))     // default; caps Retry-After too
    .user_agent("acme-indexer/2.1 (ops@acme.example)")
    .build()?;

πŸŽ“ The one thing to understand first

A Fyuz token trades on an internal bonding curve until it reaches a $30,000 market cap, at which point it graduates into a PancakeSwap V2 pair.

Before graduation there is no DEX pool anywhere. pair_address is None, there is no pair to quote, no aggregator has a price, and no on-chain pool read will give you one. For a pre-graduation token this API is the only source of price, liquidity and volume data. If you are building an indexer, a bot or a portfolio tracker, that fact decides your architecture.

graduation_pct (on DiscoverToken) and progress (on Token) tell you how close a token is to that moment. pool_type is 1 for a PancakeSwap V2 pair, 2 for V3 and 3 for V4 β€” direct launches, which skip the bonding curve entirely, also report 3. The API types the field as a plain integer with no enum, so treat the set as open. network is currently "bsc".

βœ… Why this SDK

There are four Fyuz SDKs β€” TypeScript, Go, Python and Rust. They are not four independent guesses at the same API. They are checked against each other and against the real contract:

βœ”οΈ What is actually verified
πŸ” Field-for-field agreement across languages All four SDKs write a conformance record from the same fork and it agrees on every field β€” same quotes, same calldata, same decoded pool state. scripts/fork-test.sh flattens the four records and compares them leaf by leaf; any divergence fails the run.
⛓️ Fork-tested against the deployed contract Not stubs: scripts/fork-test.sh forks BNB Smart Chain and runs the suites against the live Fyuz proxy 0x33a98…7019c.
πŸ’° Quotes checked against the curve Every buy/sell quote is compared with getSwapOutput on the real curve, and exact-tokens pricing was verified correct to the wei by bisecting the deployed contract.
🧨 Reverts classified from real revert data The revert table is driven by bytes the contract actually returned β€” SwapExpired 0x2b32713d, SlippageExceeded 0x8199f5f3 β€” not by selectors someone typed.
πŸ§ͺ Hermetic offline suite cargo test runs 130 tests + 47 doctests with no network at all. TypeScript 164, Go 100, Python 177 do the same.
πŸ” Never signs, never broadcasts The trading layer builds unsigned transactions and stops. No key material enters this crate at any point.

πŸ“š API surface

All methods are async and return fyuz::Result<T>. Optional query parameters live in per-endpoint params structs with chainable setters (DiscoverParams::new().tab(..).limit(..)); anything you do not set is simply not sent, and the server applies its own default.

Market data

Method HTTP Returns
health() GET /health HealthStatus
get_config() GET /config ChainConfig
discover(&DiscoverParams) GET /discover Vec<DiscoverToken>
list_tokens(&ListTokensParams) GET /tokens TokenPage
token_pages(&ListTokensParams) GET /tokens (all pages) TokenPager
get_king() GET /tokens/king Option<Token>
get_token(network, address, &TokenDetailParams) GET /tokens/{network}/{tokenAddress} TokenDetail
get_recent_trades(&RecentTradesParams) GET /trades/recent RecentTrades
get_token_trades(address, &TokenTradesParams) POST /trades Vec<Trade>
get_chart_data(&ChartDataParams) GET /trades/getChartData Vec<Candle>
get_token_analytics(network, address) GET /analytics/token/{network}/{address} TokenAnalytics
get_top_traders(network, address) GET /analytics/top-traders/{network}/{address} Vec<TopTrader>

Wallets, profiles and leaderboards

Method HTTP Returns
get_wallet(address) GET /wallet/{address} WalletStats
get_portfolio(address) GET /portfolio/{address} Portfolio
get_user_leaderboard(Option<limit>) GET /users/leaderboard Vec<LeaderboardEntry>
get_user_profile(address) GET /users/profile/{address} UserProfile
get_top_holders(count, &TopHoldersParams) GET /users/top/{count} Vec<TopHolderEntry>
get_kings_history() GET /kings/history Vec<KingReign>
get_season() GET /season Season
get_referral_leaderboard(Option<limit>) GET /referrals/leaderboard Vec<ReferralLeaderEntry>
get_tier(address) GET /tier/{address} TierInfo
get_rewards(address) GET /rewards/{address} Rewards

Distributor β€” the fee payback ledger

Reached through client.distributor().

Method HTTP Returns
get_stats() GET /distributor/stats PayoutStats
get_pot() GET /distributor/pot Pot
get_shares(&SharesParams) GET /distributor/shares Shares
list_rounds(&RoundsParams) GET /distributor/rounds Vec<RoundReceipt>
get_round(id) GET /distributor/rounds/{id} RoundDetail
get_payouts(address) GET /distributor/payouts/{address} AddressPayouts
get_claimable(address) GET /distributor/claimable/{address} Claimable

Endpoints that can answer 404: get_token, get_token_analytics, get_user_profile, distributor().get_round(). Check with Error::is_not_found().

  • get_token_trades is a POST only because the token address travels in the request body. It is a read, it mutates nothing, and it needs no auth.
  • Leaderboard rank is server-assigned, 1-based, already in returned order. Do not re-sort it client side.
  • RoundDetail is flattened on the wire: the receipt fields sit at the top level next to payouts. In Rust they live under detail.round via #[serde(flatten)], so detail.round.round_id reads the roundId key the API actually sends.
  • get_config() currently lists two chains: BNB Smart Chain (56), which has every token, and Robinhood Chain (4663), which has none yet. All trading and market data in practice is chain 56.
  • get_king() returns Option<Token>. None means the hill is empty β€” a real state, not an error: a token leaves the moment it graduates, so between a graduation and the next launch there is no king. The {"king": …} envelope is unwrapped for you.
  • Timestamps are UNIX seconds (i64) everywhere except Token::created_at / updated_at / launched_at / creation_time and ChatMessage::date, which are RFC-3339 strings. The crate carries no chrono or time dependency; parse those with whatever date library you already use. This is the one convenience the TypeScript, Go and Python SDKs have that this one deliberately does not β€” picking a date crate for you would put a major version of it into every downstream build to save one parse call.

Only the public, unauthenticated surface is implemented. The API-key-gated routes have no client methods β€” including POST /distributor/rounds, which shares a path with distributor().list_rounds() but is internal to the round-runner, and the authenticated /users, /chats, /watchlist, /stream, /paper, /creator, /airdrop and /referrals/{address} routes.

πŸ’± Trading: unsigned transactions only

client.trade() prices bonding-curve swaps against the live contract and builds transactions.

πŸ“ Note β€” it never signs and never broadcasts. You get an UnsignedTransaction { chain_id, to, data, value, from } and hand it to alloy, ethers-rs, a hardware signer or a multisig UI. That library owns the key; this crate never sees one. That is a security property, not a missing feature.

use fyuz::{format_units, parse_units, BuyParams, FyuzClient};

let client = FyuzClient::builder()
    .rpc_url(std::env::var("BSC_RPC_URL")?)   // use your own node in production
    .build()?;

let token = "0x42322852a918f94186b7dfda2e0e3f4ad3528480";
let spend = parse_units("0.01", 18)?;         // 0.01 BNB, in wei

let quote = client.trade().quote_buy("bsc", token, spend).await?;
println!("{} tokens out", format_units(quote.amount_out_wei, 18));
println!("msg.value {}", quote.value_wei);    // amount + getFirstBuyFee(token)

let built = client
    .trade()
    .build_buy(&BuyParams::new(token, spend).slippage_bps(100))  // 1% β€” required
    .await?;
// built.transaction is ready to sign. Nothing has been sent.

Real output from examples/trade.rs, run against BNB Smart Chain:

chain      BNB Smart Chain (56)
contract   0x33a98bef6496684a8dac83734d9ceb0cefc7019c

buy quote
  spending       0.01 BNB
  first-buy fee  0 BNB
  msg.value      0.01 BNB  <- includes the fee
  tokens out     1259164.617412410713942325
  price impact   0.11%

buy transaction (unsigned)
  chainId        56
  to             0x33a98bef6496684a8dac83734d9ceb0cefc7019c
  value          10000000000000000
  data           0x6bf05b0100000000000000000000000042322852a918f94186b7dfda2e0e3f4ad3528480…
  minAmountOut   1246572.971238286606802901 tokens

🧾 Trading API

Method What it does
chain(network) ChainInfo from GET /config β€” contract address, chain id, RPC. Memoised.
quote_buy(network, token, amount_wei) BuyQuote: tokens out, first-buy fee, required msg.value, price impact
quote_sell(network, token, amount_wei) SellQuote: native out net of fees, price impact
build_buy(&BuyParams) BuiltBuy β€” spend an exact amount of BNB
build_buy_exact_tokens(&BuyExactTokensParams) BuiltBuy β€” get an exact number of tokens, capping the spend
build_sell(&SellParams) BuiltSell β€” sell tokens for BNB
build_approve(network, token, amount_wei) UnsignedTransaction β€” the ERC-20 approval a sell needs
is_graduated(network, token) Reads tokenPools(token).launched β€” the contract's flag, not the indexer's view
is_paused(network) Reads the launchpad's paused() flag
first_buy_fee(network, token) getFirstBuyFee(token) in wei
max_sellable_wei(network, token) getMaxSellableETH(token) β€” the ceiling on one sell
allowance(network, token, owner) ERC-20 allowance(owner, launchpad)
balance_of(network, token, owner) ERC-20 balanceOf(owner)

πŸ’Έ Fees, slippage and the things that revert

  • msg.value on a buy is amount_in + getFirstBuyFee(token). The fee rides on top of the swap amount and is not deducted from it β€” quote.value_wei is the number to send. Sending only the amount reverts with InsufficientEthValue().
  • The buy fee is PLATFORM_BUY_FEE_BPS (80) + TOKEN_OWNER_FEE_BPS (20) = 100 bps. The SDK never hardcodes those: both are plain storage with owner setters, so they are read from the contract on every exact-tokens quote. A stale figure would produce a maxAmountIn under the real cost, and the swap would revert with MaxInputExceeded().
  • Slippage is never chosen for you. build_buy, build_buy_exact_tokens and build_sell all require either slippage_bps (100 = 1%) or an explicit limit_wei. A default here would be a number picked by someone who cannot see the trade.
  • Selling needs an approval first β€” the contract pulls tokens with transferFrom:
use fyuz::SellParams;

if client.trade().allowance("bsc", token, owner).await? < amount {
    let approve = client.trade().build_approve("bsc", token, amount).await?;
    // sign and send `approve`, wait for it, then continue
}
let sell = client
    .trade()
    .build_sell(&SellParams::new(token, amount).slippage_bps(150))
    .await?;
  • A graduated token fails fast. Quoting or building for one returns an error satisfying Error::is_graduated() instead of a transaction that would burn gas to revert.
  • is_paused() is not called automatically. That would double the RPC cost of every quote to check a flag that is almost never set, and a paused contract already surfaces as RevertKind::Paused when the swap is simulated. Call it when you want the reason before asking someone to sign, rather than after their transaction reverted.

Every other quote in this crate is a single eth_call, because reimplementing the curve locally would mean a second formula to keep in step with an upgradeable contract. This one cannot be: getSwapOutput answers "what do I get for X" in both directions, and the contract publishes no view for "what does Y cost" β€” getAmountIn is internal.

So build_buy_exact_tokens mirrors Fyuz.getAmountIn exactly β€” including its +1 after the floor division and its fee gross-up β€” over reserves and fee rates read from the same contract in the same breath. That is four eth_calls (tokenPools, both fee constants, getFirstBuyFee) against build_buy's two. Only the multiply is local; every input is the chain's.

Pricing it off the sell side instead would be one call and the wrong answer: it divides by virtual_token + wanted where the buy divides by virtual_token βˆ’ wanted, and it subtracts the sell fees where the buy adds the buy fees.

The JSON-RPC transport takes headers and a pinned block, and shares one connection pool across every chain. Headers set here go only to the RPC endpoint, never to the Fyuz API:

use std::time::Duration;

let client = fyuz::FyuzClient::builder()
    .rpc_url("https://bsc.example-provider.io/v1")
    .rpc_header("x-api-key", &std::env::var("RPC_KEY")?)?  // never sent to the Fyuz API
    .rpc_block("0x6c5b1e8")                                // pin a height so two quotes compare
    .rpc_timeout(Duration::from_secs(5))
    .build()?;

Without rpc_url, the trading layer uses whatever GET /config publishes. That endpoint belongs to the API operator, is shared by every caller and can change without notice β€” set your own for anything in production.

Amounts use fyuz::U256, a big-endian [u8; 32] with exactly the operations this crate performs (from_dec_str, checked_add, checked_sub, mul_div, div_rem_small, to_hex_word, …). u128 would not do: an ERC-20 allowance is 2^256 βˆ’ 1 for every wallet that ever approved unlimited spending, and that is the most ordinary value there is.

parse_units("0.5", 18) and format_units(value, 18) convert to and from human decimal strings without ever going through f64.

🧯 Errors and rate limiting

The API allows 120 requests per minute per IP and answers 429 with {"error": "Too many requests"} past that.

The client retries 429, 5xx and transport failures automatically with exponential backoff and full jitter, honouring Retry-After when the server sends one β€” bounded by max_retry_delay (default 8s) in both directions, so a hostile or buggy header cannot park your task. Other 4xx statuses are never retried. Once the retries are spent you get a typed error:

match client.get_user_profile(address).await {
    Ok(profile) => { /* … */ }
    Err(e) if e.is_rate_limited() => {
        // A distinct variant precisely so backpressure can match on it.
        eprintln!("throttled, retry after {:?}", e.retry_after());
    }
    Err(e) if e.is_not_found() => eprintln!("no such profile"),
    Err(fyuz::Error::Api { status, message }) => eprintln!("HTTP {status}: {message}"),
    Err(e) => return Err(e),
}
Variant Raised when
Api { status, message } non-2xx other than 429; message is the server's {"error": …} text
RateLimited { message, retry_after } 429 survived every retry
Transport(reqwest::Error) DNS, connection, TLS or timeout β€” no HTTP response at all
Decode { message, body } 2xx arrived but did not deserialise; body is the first 256 bytes
InvalidBaseUrl { url, message } base_url was not a usable http/https base
InvalidArgument { message } bad address, zero amount, or a build with no slippage protection
ContractRevert { kind, message, error_name, selector, data, retryable, token_address } the contract rejected a simulated call β€” costs no gas
Rpc { method, message, code, data } the JSON-RPC node returned an error object
RpcTransport { method, source } the RPC endpoint was unreachable

Helpers: status(), is_rate_limited(), is_not_found(), is_graduated(), is_retryable_revert(), is_timeout(), retry_after(). Error is #[non_exhaustive] β€” match with a _ arm.

A reverted eth_call comes back as a bare 4-byte selector. The mapping from 0xcfa6d878 to "the token graduated, trade the DEX pair" exists nowhere on-chain, so this crate carries the table β€” checked against shared/test-vectors/trade-errors.json, which all four SDKs share.

RevertKind variants: Graduated, NoPool, Slippage, Expired, PriceImpact, SellTooLarge, InsufficientValue, InsufficientBalance, InsufficientInput, InsufficientLiquidity, ZeroAmount, StaleFeed, Paused, RevertString, Unknown.

An unrecognised selector is reported verbatim rather than swallowed β€” a later contract version will add errors this table does not know about, and having the selector in the message is at least something to search for.

πŸ“„ Auto-pagination

GET /tokens is the one paginated endpoint. token_pages() walks it and stops once the server's tokenCount is covered β€” or a page comes back empty, so it terminates even if the total shifts underneath you:

use fyuz::ListTokensParams;

// Token at a time…
let mut pager = client.token_pages(&ListTokensParams::new().page_size(100));
while let Some(token) = pager.next_token().await? {
    println!("{}", token.token_symbol);
}

// …or page at a time.
let mut pager = client.token_pages(&ListTokensParams::new());
while let Some(page) = pager.next_page().await? {
    println!("{} rows of {:?}", page.len(), pager.total_count());
}

TokenPager also offers collect_all() and fetched_count(). It is a plain async iterator rather than a futures::Stream: the crate has no futures dependency, and while let Some(..) reads the same.

πŸ”’ Decimal strings are strings. Keep them that way.

Token balances, prices, market caps and every *_wei field come back as decimal strings and stay String in this crate's models. An 18-decimal token amount, or a uint256 wei total, does not survive f64:

let wire = "123456789012345678901234567890";
let lossy = wire.parse::<f64>().unwrap();
assert_ne!(format!("{lossy:.0}"), wire);  // silently wrong

If you need arithmetic, parse at the edge of your code with a big-integer or decimal type (rust_decimal, bigdecimal, primitive_types::U256). This SDK deliberately does not choose one for you and never parses these into f64 behind your back.

Aggregates the server already computed in floating point β€” volume_usd, points, reward_eth, pot_bnb, PnL, percentages β€” arrive as JSON numbers and are modelled as f64. RoundReceipt::vrf_random is a decimal string too (a uint256 VRF word), not hex and not a number.

⚠️ None is not 0

Pot::pot_bnb, Pot::total_points, Claimable::claimable_wei, TokenDetail::curve_holding and the nullable *_wei fields on RoundReceipt are None when the value is unknown β€” the RPC was unreachable, or the settlement is not indexed yet. Rendering 0 there tells a user something false. Hide the figure instead.

πŸ“ Deployed addresses

Trading reads its contract address and rpcUrl from GET /config at runtime, so a proxy upgrade or a new chain needs no SDK release. That stays the authority. The verified proxy addresses also ship as compile-time constants for the cases /config cannot cover:

let bsc = fyuz::deployment_for_chain_id(56).expect("mainnet is in the book");
assert_eq!(bsc.fyuz, "0x33a98bef6496684a8dac83734d9ceb0cefc7019c");

// Or by slug, or by a chain id spelled as a string β€” the same forms the trading
// layer accepts as a `network` everywhere else.
let testnet = fyuz::deployment_for_network("bsc-testnet").unwrap();
Chain Id Fyuz proxy Served by /config
BNB Smart Chain 56 0x33a98bef6496684a8dac83734d9ceb0cefc7019c βœ…
BNB Smart Chain Testnet 97 0x24febdcc41c804873dbbfe703cd9d2d4b771c236 ❌ β€” book only

The book is used in exactly two cases: BSC testnet, which the public API does not serve at all (so fork and testnet work with the Fyuz API unreachable), and a /config response that lists a chain with no contractAddress. Everywhere else /config wins, and the address it publishes is validated as 20 bytes on the way in, so a malformed one fails at chain() rather than becoming a shifted argument list in calldata.

Constants are transcribed from ../shared/addresses rather than parsed at runtime: a published crate has no sibling shared/ directory, and include_str! plus a parse would turn a typo into a runtime panic instead of a compile error. tests/addresses.rs asserts every constant against both that directory and shared/test-vectors/addresses.json, so the transcription cannot drift silently.

πŸ“¦ Dependencies

The TypeScript, Go and Python SDKs have zero runtime dependencies. Rust has five, and that is a deliberate choice rather than an oversight β€” async HTTP with TLS, and JSON into typed structs, are not in std:

Crate Why
reqwest HTTP. default-features = false + rustls, so the crate builds with no OpenSSL headers on the box β€” the single biggest source of "it does not build in CI/Alpine/cross-compile". http2 and system-proxy keep behaviour on par with reqwest's defaults behind a corporate proxy, without dragging in native-tls or charset.
serde + serde_json Response models and the error envelope
thiserror The Error enum
tokio time only β€” retry backoff sleeps. default-features = false, so you pick your own runtime features.

Dev-dependencies (a stub HTTP server on a plain TcpListener, no wiremock) reach nobody depending on the published crate.

The published crate excludes tests/. Those suites read fixtures from ../../shared, which lives above the crate root and cannot be packaged β€” shipping the tests would ship files that do not compile. Clone the repository to run them.

πŸ§ͺ Tests

cargo test                    # 130 tests + 47 doctests, no network
cargo test --test integration

The default suite never touches the network. tests/common/mod.rs is a stub HTTP server on a plain tokio::net::TcpListener bound to 127.0.0.1:0. It covers the happy path, 429-then-success retry, 5xx retry, "4xx is never retried", error-envelope parsing, decimal-string round-tripping without precision loss, null vs 0, the flattened RoundDetail, query/body encoding and auto-pagination.

tests/conformance.rs pulls in ../shared/test-vectors with include_str! β€” the same fixtures drive the TypeScript, Go and Python suites, so a change to the wire contract fails all four at once. tests/trade.rs covers calldata and revert vectors; tests/addresses.rs covers the address book against both files that describe it.

Against the real chain, from this crate's directory:

../scripts/fork-test.sh           # all four SDKs against a BSC fork
../scripts/fork-test.sh rust      # just this one
../scripts/test-all.sh            # every SDK, offline

tests/fork.rs skips itself when FYUZ_FORK_RPC is unset, which is what keeps cargo test hermetic. Nothing in it compares the SDK with itself: each expectation is built by hand, posted to the node as plain JSON, and sliced from the 32-byte answer β€” then the SDK is asked the same question.

πŸ§‘β€πŸ’» Examples

Runnable programs in examples/, compiled by cargo build --examples:

Example What it shows
quickstart Health, chain config, the trending feed, king of the hill
graduation_watch Tokens closest to the $30k graduation threshold
token_deep_dive One token: detail, holders, trades, candles
export_tokens Auto-paginate every token to CSV with exact decimals
resilient_polling Incremental trade polling, retry tuning, error matching
trade Quote a buy and a sell, build the unsigned transactions, handle the approval
live_smoke Minimal check against the production API
cargo run --example quickstart
cargo run --example token_deep_dive -- 0x42322852a918f94186b7dfda2e0e3f4ad3528480
cargo run --example export_tokens -- dog > tokens.csv
cargo run --example trade -- 0x42322852a918f94186b7dfda2e0e3f4ad3528480

🌍 The other three SDKs

Same API, same behaviour, same conformance record:

Language Install Import Runtime deps
πŸ¦€ Rust cargo add fyuz-sdk use fyuz::… reqwest, serde, serde_json, thiserror, tokio
🟦 TypeScript pnpm add fyuz-sdk fyuz-sdk none
🐍 Python pip install fyuz-sdk import fyuz none
🐹 Go go get github.com/fyuz-launchpad/fyuz-sdk/go fyuz none

See ../README.md for the side-by-side.

πŸ”— Links

πŸ“œ License

MIT β€” see LICENSE.