@baseline-markets/sdk
v1.3.0
Published
TypeScript SDK for Baseline — the end-to-end asset issuance protocol where tokens own their liquidity.
Readme
@baseline-markets/sdk
A typed TypeScript SDK for interacting with Baseline Protocol contracts via viem.
Baseline is an onchain AMM for tokens with built-in liquidity — @baseline-markets/sdk exposes a single BaselineSDK class covering token launches, swaps, borrowing, leverage, staking, and rewards.
Install
npm install @baseline-markets/sdk viem
# or: bun add @baseline-markets/sdk viemviem is a peer dependency — bring your own.
Quickstart
import { BaselineSDK } from '@baseline-markets/sdk';
import { createPublicClient, createWalletClient, http, custom } from 'viem';
import { base } from 'viem/chains';
const publicClient = createPublicClient({
chain: base,
transport: http(),
});
// Wallet client is optional — only needed for write actions
const walletClient = createWalletClient({
chain: base,
transport: custom(window.ethereum),
});
const sdk = new BaselineSDK(publicClient, walletClient);Each SDK instance is bound to a single chain — the chain comes from publicClient.chain. To talk to a different chain, build a new publicClient (and walletClient if writing) for that chain and instantiate a new BaselineSDK. The SDK has no chainId parameter on its methods by design; chain selection lives in the clients you pass to the constructor. See Switching chains below for the React + wagmi pattern.
Read-only
const bToken = '0x...' as const;
const price = await sdk.activePrice(bToken);
const reserve = await sdk.getReserve(bToken);
const quote = await sdk.quoteBuyExactOut(bToken, 100n);Swaps
// Buy exactly 100 bTokens, capped at `quote.amountIn` reserve in
const hash = await sdk.buyTokensExactOut(bToken, 100n, quote.amountIn, {
confirmations: 1,
});
// Sell 100 bTokens, accept at least `minOut` reserve out
const hash2 = await sdk.sellTokensExactIn(bToken, 100n, minOut);Borrow / leverage / stake
await sdk.deposit(bToken, collateral);
await sdk.borrow(bToken, debtAmount, recipient);
const leverageQuote = await sdk.quoteLeverage(bToken, collateralIn, leverageFactor);
await sdk.leverage(
bToken,
leverageQuote.targetCollateral,
collateralIn,
leverageQuote.maxSwapReservesIn,
);
await sdk.repay(bToken, reservesIn, recipient);
await sdk.claim(bToken);See src/baseline-sdk.ts for the full method list.
Error handling
All write methods throw SDKError, a single error type with a .kind discriminator so you don't have to sniff messages:
import { SDKError } from '@baseline-markets/sdk';
try {
await sdk.buyTokensExactOut(bToken, 100n, maxIn);
} catch (err) {
if (err instanceof SDKError) {
switch (err.kind) {
case 'user_rejected': // user cancelled in their wallet
case 'insufficient_funds': // not enough native balance for gas
case 'reverted': // contract reverted (decoded reason in .message)
case 'network': // RPC/socket/timeout — retry-worthy
case 'wallet': // SDK misuse: no wallet, no account
case 'unknown': // anything else
}
}
}The original viem error is preserved on err.cause if you need it.
React + wagmi
wagmi's usePublicClient / useWalletClient return viem clients, so they plug straight into BaselineSDK. Wrap construction in a hook and memoize — re-creating the SDK on every render is fine but wasteful.
import { useMemo } from 'react';
import { useChainId, usePublicClient, useWalletClient } from 'wagmi';
import { BaselineSDK } from '@baseline-markets/sdk';
export function useBaselineSDK(chainId?: number) {
const walletChainId = useChainId();
const targetChainId = chainId ?? walletChainId;
const publicClient = usePublicClient({ chainId: targetChainId });
const { data: walletClient } = useWalletClient({ chainId: targetChainId });
return useMemo(() => {
if (!publicClient) return null;
return new BaselineSDK(publicClient, walletClient ?? undefined);
}, [publicClient, walletClient]);
}useWalletClient() returns undefined until the user connects — the SDK still works for reads in that state, and sdk.hasWallet tells you whether write actions are available.
Switching chains
The SDK is single-chain: the chain is baked into publicClient, so a BaselineSDK instance can only talk to one network. When the user switches network, you need new clients and a new SDK.
With the hook above this is automatic — passing chainId to wagmi's usePublicClient/useWalletClient returns different client references per chain, the useMemo deps change, and a fresh BaselineSDK is constructed. You don't have to do anything special; just trust that:
- Chain change → new clients → new SDK. Construction is cheap (the SDK just holds client references and resolves a proxy address from the chain id), so don't try to skip the rebuild.
- Don't mix chains in one SDK. Never pass a
publicClientfor one chain and awalletClientfor another — reads and writes will target different networks. - Read across chains? Call the hook with an explicit
chainIdper component (e.g.useBaselineSDK(mainnet.id)anduseBaselineSDK(base.id)) — you'll get two independent SDK instances, one per chain.
Reads with useQuery
import { useQuery } from '@tanstack/react-query';
function Price({ bToken }: { bToken: `0x${string}` }) {
const sdk = useBaselineSDK();
const { data: price } = useQuery({
queryKey: ['baseline', 'activePrice', sdk?.chainId, bToken],
queryFn: () => sdk!.activePrice(bToken),
enabled: !!sdk,
});
return <div>{price?.toString()}</div>;
}Include sdk.chainId in the query key so cached data is scoped per network.
Writes with useMutation
import { useMutation } from '@tanstack/react-query';
import { SDKError } from '@baseline-markets/sdk';
function BuyButton({ bToken, amount, maxIn }: {
bToken: `0x${string}`;
amount: bigint;
maxIn: bigint;
}) {
const sdk = useBaselineSDK();
const buy = useMutation({
mutationFn: () =>
sdk!.buyTokensExactOut(bToken, amount, maxIn, { confirmations: 1 }),
onError: (err) => {
if (err instanceof SDKError && err.kind === 'user_rejected') return;
// surface other kinds: insufficient_funds, reverted, network, ...
},
});
return (
<button
disabled={!sdk?.hasWallet || buy.isPending}
onClick={() => buy.mutate()}
>
{buy.isPending ? 'Confirming…' : 'Buy'}
</button>
);
}The confirmations option resolves the promise only after the receipt reaches the requested confirmation count, so buy.isPending covers both the wallet prompt and the on-chain confirmation.
Configuration
BaselineSDK takes an optional third argument:
new BaselineSDK(publicClient, walletClient, {
defaultUseNative: true, // use native ETH paths by default where supported
});Per-call options live on TxOpts:
await sdk.buyTokensExactOut(bToken, 100n, maxIn, {
account: '0x...', // override which address sends the tx
confirmations: 2, // wait for N confirmations before returning
onSimulateError: (e) => {}, // hook for pre-simulation failures
});Supported networks
- Ethereum mainnet (
mainnet, chain id1) - Base (
base, chain id8453)
The SDK reads the chain from your publicClient and throws if it isn't one of the supported chains — no separate chain config needed.
import { supportedChainIds } from '@baseline-markets/sdk';
if (!supportedChainIds.includes(chainId)) {
// unsupported network — prompt user to switch
}Development
bun install
bun run build # bundle ESM + CJS + .d.ts via tsup
bun testLicense
MIT
