npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dotone/sdk

v2.0.4

Published

Official TypeScript SDK for DotOne Smart Chain

Downloads

90

Readme

@dotone/sdk

TypeScript SDK for the DotOne Staking contracts. Built on viem.

Installation

npm install @dotone/sdk
# or
pnpm add @dotone/sdk

viem is a peer dependency:

npm install viem

Deployed contracts (DotOne mainnet, chainId 505)

| Contract | Address | |---|---| | Staking | 0x136f964c9bE25b75ef608423c61F922BeE022a71 | | StakingNFT | 0x6AC8c90A2331583498FE66A4Fe8772EB9E837b7C | | PrincipalVault | 0x35eca0beFb7F96D57ED9AE4d50A2b888B09C2Cf6 | | RewardVault | 0x7470F58744Fb90A96394b17F4545223fcd6cC6d6 | | WDOT (Wrapped DOT) | 0x044b2ED77214aeA517e811f5c80980511F8ff326 | | MockERC20 (test token) | 0xAD13394f77AEB4E157d8ec8EcaEF2D77f0894516 |

WDOT is the production staking token. You must hold real WDOT (wrap DOT) to stake it.

MockERC20 is a freely-mintable test token on DotOne mainnet — anyone can call mint(address to, uint256 amount). It has the same 3 tiers as WDOT and its reward pool is pre-seeded with 1,000,000 MOCK. Use it to test the full staking flow without acquiring real tokens.

Quick start

ABIs are bundled in the package — no external ABI files needed. Call configure() once at app startup, then createStakingClient() works anywhere in your app.

Browser (MetaMask):

import {
  configure,
  createStakingClient,
  stakingAbi,
  stakingNftAbi,
  rewardVaultAbi,
} from "@dotone/sdk/staking";
import { createPublicClient, createWalletClient, http, custom } from "viem";

configure({
  addresses: {
    staking:        "0x136f964c9bE25b75ef608423c61F922BeE022a71",
    stakingNFT:     "0x6AC8c90A2331583498FE66A4Fe8772EB9E837b7C",
    principalVault: "0x35eca0beFb7F96D57ED9AE4d50A2b888B09C2Cf6",
    rewardVault:    "0x7470F58744Fb90A96394b17F4545223fcd6cC6d6",
  },
  abis: {
    staking:     stakingAbi,
    stakingNFT:  stakingNftAbi,
    rewardVault: rewardVaultAbi,
  },
});

const publicClient = createPublicClient({ transport: http("https://rpc2.dotone.network") });
const walletClient = createWalletClient({ transport: custom(window.ethereum) });

const sdk = createStakingClient({ publicClient, walletClient });

Node.js / CLI (private key):

import {
  configure,
  createStakingClient,
  stakingAbi,
  stakingNftAbi,
  rewardVaultAbi,
} from "@dotone/sdk/staking";
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";

configure({
  addresses: {
    staking:        "0x136f964c9bE25b75ef608423c61F922BeE022a71",
    stakingNFT:     "0x6AC8c90A2331583498FE66A4Fe8772EB9E837b7C",
    principalVault: "0x35eca0beFb7F96D57ED9AE4d50A2b888B09C2Cf6",
    rewardVault:    "0x7470F58744Fb90A96394b17F4545223fcd6cC6d6",
  },
  abis: {
    staking:     stakingAbi,
    stakingNFT:  stakingNftAbi,
    rewardVault: rewardVaultAbi,
  },
});

const account      = privateKeyToAccount("0x...");
const publicClient = createPublicClient({ transport: http("https://rpc2.dotone.network") });
const walletClient = createWalletClient({ account, transport: http("https://rpc2.dotone.network") });

const sdk = createStakingClient({ publicClient, walletClient });

Staking tiers

Tiers are per-token and have a variable count — each accepted ERC20 has its own independent tier array configured when the token is registered via addToken. The contract auto-selects the highest qualifying active tier based on the staked amount. You do not specify a tier manually.

Query tiers at runtime — do not hardcode them:

const tiers = await sdk.getTokenTiers("0xTokenAddress");
// tiers[0].minApy, tiers[0].lockPeriod, tiers[0].minInvestment, ...

WDOT and MockERC20 on DotOne mainnet share this tier config:

| Tier | APY Range | Lock | Min Investment | Perf Fee | Mgmt Fee | |---|---|---|---|---|---| | 0 — Low Risk | 8–18% | 90 days | 1,000 tokens | 15% | 1% | | 1 — Medium Risk | 20–35% | 180 days | 10,000 tokens | 20% | 1.5% | | 2 — High Risk | 35–50% | 365 days | 50,000 tokens | 30% | 2% |

Reward calculation uses midpoint APY: (minApy + maxApy) / 2.

API

Read

// Tiers — per token, variable count
const tiers = await sdk.getTokenTiers("0xTokenAddress");        // TierInfo[] for this token
const tier  = await sdk.getTokenTier("0xTokenAddress", 0);      // single TierInfo by id

// TierInfo field names (note: fee fields are renamed from the contract's ABI names):
// tier.minApy          — minimum APY in basis points (uint16)
// tier.maxApy          — maximum APY in basis points (uint16)
// tier.perfFeeBps      — performance fee bps  ← SDK name (contract: performanceFee)
// tier.mgmtFeeBps      — management fee bps   ← SDK name (contract: managementFee)
// tier.lockPeriod      — lock duration in seconds (uint32)
// tier.riskScore       — 0=Low Risk, 1=Medium Risk, 2=High Risk (uint8)
// tier.active          — whether this tier accepts new stakes
// tier.minInvestment   — minimum stake amount in wei (uint256 / bigint)

// Which tier will be assigned for a given amount?
const best = await sdk.getBestTier("0xTokenAddress", parseEther("1000"));
// → { tierId: 0, tier: TierInfo } | null

// Supported tokens
const tokens = await sdk.getSupportedTokens(); // address[] of all registered tokens

// Token support
const ok   = await sdk.isTokenSupported("0xTokenAddress");
const pool = await sdk.getRewardPool("0xTokenAddress");     // reward pool balance

// User positions — enriched objects (stake + NFT metadata + pending reward + tier info)
const positions    = await sdk.getUserPositions("0x...");    // active only
const allPositions = await sdk.getUserAllPositions("0x..."); // active + completed badges

// Each position contains:
// position.tokenId           — NFT token ID
// position.owner             — current NFT holder (may differ from original staker after transfer)
// position.stake.token       — staked ERC20 address
// position.stake.amount      — principal locked (wei)
// position.stake.active      — true = locked, false = exited
// position.stake.tierId      — tier index (0/1/2)
// position.stake.stakedAt    — unix timestamp of stake
// position.stake.unlockAt    — unix timestamp when lock expires
// position.stake.claimedGross — cumulative gross rewards claimed mid-lock so far (wei)
// position.stake.snapshotMinApy / snapshotMaxApy   — APY bps locked at stake time
// position.stake.snapshotPerfFee / snapshotMgmtFee — fees locked at stake time
// position.nftMeta.tierId           — tier at mint
// position.nftMeta.snapshotRiskScore — 0=Low, 1=Medium, 2=High
// position.nftMeta.completed        — true once unstaked (permanent badge)
// position.pendingReward     — claimable right now, net after performance fee (wei)
// position.tierInfo          — current tier params {minApy, maxApy, lockPeriod, ...}
// position.uri               — decoded tokenURI JSON {name, description, image, attributes}

// Single enriched position by tokenId
const position = await sdk.getStakePosition(tokenId);

// Raw stake data only (no NFT fetch — lighter call for lists)
const allStakes = await sdk.getUserStakes("0x...");     // StakeInfo[] — all stakes, active + past
const stake     = await sdk.getStakeByToken(tokenId);   // StakeInfo for one tokenId

// Pending reward (net, after performance fee deduction)
const pending = await sdk.getPendingReward(tokenId);

// NFT
const meta  = await sdk.getNFTMeta(tokenId);    // token, amount, tierId, APY snapshot, timestamps, completed
const uri   = await sdk.getTokenURI(tokenId);   // "data:application/json;base64,..." string
const owner = await sdk.getNFTOwner(tokenId);   // current holder address

// NFT tier image URL
const imgUrl = await sdk.getTierImageUrl("0xTokenAddress", 0); // image URL for tier 0 of this token

// ERC20 balance
const balance = await sdk.getTokenBalance("0xTokenAddress", "0xAccount");

Write — user

All write methods return a transaction hash (Hash). Await confirmation with publicClient.waitForTransactionReceipt.

// Stake — approves PrincipalVault automatically if needed, then stakes.
// Tier is auto-selected by the contract based on amount.
const { approveTx, stakeTx, tokenId } = await sdk.stakeWithApproval({
  token:   "0xTokenAddress",
  amount:  parseEther("1000"),
  account: "0x...",
});

// Claim accrued rewards mid-lock (principal stays locked)
await sdk.claimReward({ tokenId, account: "0x..." });
await sdk.claimRewards({ tokenIds: [id1, id2], account: "0x..." }); // batch

// Unstake after lock expires — returns principal + all remaining rewards in one tx
await sdk.unstake({ tokenId, account: "0x..." });
await sdk.unstakeMultiple({ tokenIds: [id1, id2], account: "0x..." }); // batch

// Emergency unstake — returns principal minus mgmt fee, forfeits all rewards.
// Safe to call even when the reward pool is empty. Lock must still have expired.
await sdk.emergencyUnstake({ tokenId, account: "0x..." });

Write — admin

// Add/remove accepted ERC20 tokens
// addToken registers the token and its tiers atomically — tiers cannot be added separately.
// Each tier object shape (all fields required):
await sdk.addToken({
  token: "0xTokenAddress",
  tiers: [
    {
      minApy:         800,              // minimum APY in basis points (800 = 8%)
      maxApy:         1800,             // maximum APY in basis points (1800 = 18%)
      performanceFee: 1500,             // performance fee bps on gross rewards (1500 = 15%)
      managementFee:  100,              // management fee bps on principal (100 = 1%)
      lockPeriod:     90 * 24 * 60 * 60, // lock duration in seconds
      riskScore:      0,                // 0=Low Risk, 1=Medium Risk, 2=High Risk
      active:         true,
      minInvestment:  parseEther("1000"),
    },
    // add more tiers as needed — contract supports any count
  ],
  account: "0x...",
});
await sdk.removeToken({ token: "0xTokenAddress", account: "0x..." });

// Tier management — all tier functions require the token address (tiers are per-token)
await sdk.setTierActive({ token: "0xTokenAddress", tierId: 0, active: false, account: "0x..." });
// Note: updateTier is available directly on the contract but not wrapped by the SDK.
// Call it via viem writeContract with stakingAbi if needed.

// NFT tier images (sequential-only append; triggers EIP-4906 marketplace refresh)
await sdk.setTierImageUrl({ token: "0xTokenAddress", tierId: 0, url: "https://...", account: "0x..." });

// Fund reward pool — approves RewardVault automatically if needed, then funds.
const { approveTx, fundTx } = await sdk.fundWithApproval({
  token:   "0xTokenAddress",
  amount:  parseEther("100000"),
  account: "0x...",
});

// Emergency withdraw from reward pool (capped to rewardPools[token] — cannot touch principal)
await sdk.emergencyWithdraw({ token: "0xTokenAddress", to: "0x...", amount, account: "0x..." });

// System management
await sdk.setPaused({ paused: true, account: "0x..." });
await sdk.setFeeCollector({ collector: "0x...", account: "0x..." });

ABIs

All four contract ABIs are exported from @dotone/sdk/staking — no ABI JSON files required.

import {
  stakingAbi,        // Staking.sol
  stakingNftAbi,     // StakingNFT.sol
  rewardVaultAbi,    // RewardVault.sol
  principalVaultAbi, // PrincipalVault.sol (rarely needed directly)
} from "@dotone/sdk/staking";

Pass them to configure() as shown in Quick start, or use them directly with viem's readContract / writeContract if you need lower-level access.

Utility

import { decodeTokenURI } from "@dotone/sdk/staking";

const uri  = await sdk.getTokenURI(tokenId);
const meta = decodeTokenURI(uri);
// → { name, description, image, attributes: [{ trait_type, value }, ...] }

Vault architecture

The staking system uses two vault contracts for fund isolation:

  • PrincipalVault — holds all staked principal. Only the Staking contract can move funds out. No admin drain function — user principal is safe even if the deployer is compromised. Users must approve PrincipalVault (not Staking) before stakingstakeWithApproval handles this automatically.
  • RewardVault — holds admin-funded reward pools per token. fundWithApproval handles the approval + funding in one call.

NFT position tokens

Every stake mints a transferable ERC721 position NFT. The NFT holder owns the position — rewards and principal go to whoever holds the NFT at claim/unstake time, even if it was transferred.

  • On-chain base64 JSON metadata — no IPFS dependency
  • NFTs are never burned — marked completed: true after unstake (permanent badge)
  • Implements EIP-4906 so marketplaces auto-refresh on status changes

Networks

| Network | Chain ID | |---|---| | DotOne Smart Chain | 505 | | Ethereum Sepolia | 11155111 |

Building from source

pnpm install
pnpm run build   # compiles src/ → dist/