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

atfi

v1.1.4

Published

TypeScript SDK for ATFi commitment vaults on Base

Readme

ATFi SDK

TypeScript SDK for interacting with ATFi commitment vaults on Base. Stake tokens, verify attendance, earn rewards.

npm version License: MIT

Features

  • Simulation-first - Know results before wallet popup
  • Transaction callbacks - Loading states for UX
  • Read-only mode - Show data before wallet connection
  • User-centric queries - Dashboard, registered events, claimable rewards
  • Real-time watching - Subscribe to live vault events
  • TypeScript - Full type safety

Installation

npm install atfi viem

Quick Start

import { ATFiSDK } from 'atfi';
import { createPublicClient, createWalletClient, http, custom } from 'viem';
import { base } from 'viem/chains';

// Setup clients
const publicClient = createPublicClient({
  chain: base,
  transport: http(),
});

const walletClient = createWalletClient({
  chain: base,
  transport: custom(window.ethereum),
});

// Initialize SDK
const sdk = new ATFiSDK(publicClient, walletClient);

How It Works

Every write function returns { simulation, execute() } - one call gives you everything:

const action = await sdk.register({ vaultAddress: '0x...' });

// 1. Check simulation result (NO wallet popup yet!)
if (!action.simulation.success) {
  console.error(action.simulation.error.message);
  return;
}

// 2. Show user what will happen
console.log(`Stake: ${action.simulation.stakeAmount} ${action.simulation.tokenSymbol}`);
console.log(`Your balance: ${action.simulation.userBalance}`);
console.log(`Gas estimate: ${action.simulation.gasEstimate}`);

// 3. User confirms → execute with loading callbacks
const result = await action.execute({
  onApproving: () => setStatus('Approving token...'),
  onApproved: (hash) => setStatus('Token approved!'),
  onSubmitting: () => setStatus('Submitting...'),
  onSubmitted: (hash) => setStatus(`TX: ${hash}`),
  onConfirming: () => setStatus('Confirming...'),
});
console.log('Done!', result.txHash);

Read-Only Mode

Show data before user connects wallet:

import { ATFiSDK } from 'atfi';
import { createPublicClient, http } from 'viem';
import { base } from 'viem/chains';

// No wallet needed!
const publicClient = createPublicClient({
  chain: base,
  transport: http(),
});

const sdk = ATFiSDK.readOnly(publicClient);

// All read functions work
const events = await sdk.getAllEvents();
const info = await sdk.getEventInfo('0x...');

// Write functions throw helpful error
await sdk.register({ vaultAddress: '0x...' });
// Error: "SDK is in read-only mode. Initialize with a walletClient to perform write operations."

API Reference

Create Event (Organizer)

const action = await sdk.createEvent({
  stakeAmount: '10',        // 10 USDC
  maxParticipants: 50,
  useYield: true,           // Enable Morpho yield
  token: 'USDC',            // 'USDC' or 'IDRX'
});

if (action.simulation.success) {
  console.log('Expected vault ID:', action.simulation.expectedVaultId);
  console.log('Token:', action.simulation.token.symbol);
  console.log('Gas:', action.simulation.gasEstimate);

  const result = await action.execute({
    onSubmitting: () => showLoader('Creating event...'),
    onConfirming: () => showLoader('Confirming...'),
  });
  console.log('Created:', result.vaultAddress);
}

Simulation returns: | Field | Type | Description | |-------|------|-------------| | expectedVaultId | bigint | Next vault ID | | token | object | { address, symbol, decimals } | | stakeAmount | string | Human readable stake | | maxParticipants | number | Max allowed | | useYield | boolean | Yield enabled | | gasEstimate | bigint | Estimated gas |


Register (Participant)

const action = await sdk.register({ vaultAddress: '0x...' });

if (action.simulation.success) {
  console.log(`Stake: ${action.simulation.stakeAmount} ${action.simulation.tokenSymbol}`);
  console.log(`Your balance: ${action.simulation.userBalance}`);
  console.log(`Needs approval: ${action.simulation.needsApproval}`);
  console.log(`Spots left: ${action.simulation.maxParticipants - action.simulation.currentParticipants}`);

  // Callbacks for loading states (approval + stake transaction)
  const result = await action.execute({
    onApproving: () => showLoader('Approving USDC...'),
    onApproved: (hash) => showToast(`Approved! ${hash}`),
    onSubmitting: () => showLoader('Staking...'),
    onConfirming: () => showLoader('Confirming...'),
  });
}

Simulation returns: | Field | Type | Description | |-------|------|-------------| | stakeAmount | string | Amount to stake | | tokenSymbol | string | USDC or IDRX | | userBalance | string | Your token balance | | currentAllowance | string | Current approval | | needsApproval | boolean | Need approve TX first | | currentParticipants | number | Current count | | maxParticipants | number | Max allowed |


Start Event (Organizer)

const action = await sdk.startEvent({ vaultAddress: '0x...' });

if (action.simulation.success) {
  console.log(`Total staked: ${action.simulation.totalStaked}`);
  console.log(`Participants: ${action.simulation.participantCount}`);
  console.log(`Has yield: ${action.simulation.hasYield}`);

  const result = await action.execute();
}

Verify Participants (Organizer)

const action = await sdk.verifyParticipant({
  vaultAddress: '0x...',
  participants: ['0xAddr1', '0xAddr2', '0xAddr3'],
});

if (action.simulation.success) {
  console.log(`Will verify: ${action.simulation.toVerify.length}`);
  console.log(`Already verified: ${action.simulation.alreadyVerified.length}`);
  console.log(`Not staked: ${action.simulation.notStaked.length}`);

  const result = await action.execute();
}

Simulation returns: | Field | Type | Description | |-------|------|-------------| | toVerify | Address[] | Will be verified | | alreadyVerified | Address[] | Already done | | notStaked | Address[] | Not registered (will fail) | | currentVerified | number | Current count | | newVerifiedCount | number | Count after |


Settle Event (Organizer)

const action = await sdk.settleEvent({ vaultAddress: '0x...' });

if (action.simulation.success) {
  console.log(`Verified: ${action.simulation.verifiedCount}`);
  console.log(`No-shows: ${action.simulation.noShowCount}`);
  console.log(`Yield earned: ${action.simulation.estimatedYield}`);
  console.log(`Protocol fees: ${action.simulation.estimatedProtocolFees}`);
  console.log(`Reward per attendee: ${action.simulation.estimatedRewardPerParticipant}`);

  const result = await action.execute();
}

Simulation returns: | Field | Type | Description | |-------|------|-------------| | totalStaked | string | Total staked | | verifiedCount | number | Attendees | | noShowCount | number | No-shows | | estimatedYield | string | Yield earned | | estimatedProtocolFees | string | Protocol fees | | estimatedNoShowFees | string | No-show fees | | estimatedRewardPerParticipant | string | Reward each |


Claim Rewards (Participant)

const action = await sdk.claim({ vaultAddress: '0x...' });

if (action.simulation.success) {
  console.log(`Total: ${action.simulation.claimableAmount} ${action.simulation.tokenSymbol}`);
  console.log(`Your stake: ${action.simulation.stakeAmount}`);
  console.log(`Bonus: ${action.simulation.bonusShare}`);

  const result = await action.execute();
}

Simulation returns: | Field | Type | Description | |-------|------|-------------| | claimableAmount | string | Total to claim | | tokenSymbol | string | Token symbol | | stakeAmount | string | Original stake | | bonusShare | string | Bonus from no-shows + yield |


Read Functions

// Get event details
const info = await sdk.getEventInfo('0xVaultAddress');

// Get participant status
const status = await sdk.getParticipantStatus('0xVault', '0xParticipant');

// Get all events (batched for performance)
const events = await sdk.getAllEvents();

// Get vault by ID
const vault = await sdk.getVaultAddress(1);

Token Balance Helpers

// Get single token balance
const usdcBalance = await sdk.getTokenBalance('0xUserAddress', 'USDC');
console.log(`USDC: ${usdcBalance}`); // "125.50"

// Get all supported token balances
const balances = await sdk.getAllTokenBalances('0xUserAddress');
console.log(balances);
// { USDC: "125.50", IDRX: "1000000.00" }

User-Centric Queries

Get events filtered by user:

// Events created by user
const myEvents = await sdk.getEventsByOwner('0xUserAddress');

// Events user registered for
const registered = await sdk.getRegisteredEvents('0xUserAddress');

// Events with unclaimed rewards
const claimable = await sdk.getClaimableEvents('0xUserAddress');

// Complete dashboard data (all in one call)
const dashboard = await sdk.getUserDashboard('0xUserAddress');
console.log(dashboard);
// {
//   asOrganizer: [...],    // Events user created
//   asParticipant: [...],  // Events user joined
//   claimable: [...],      // Ready to claim
//   balances: { USDC: "125.50", IDRX: "1000000.00" }
// }

Transaction Callbacks

All write functions accept callbacks for loading states:

interface TransactionCallbacks {
  onApproving?: () => void;           // Starting token approval
  onApproved?: (txHash: Hash) => void; // Approval submitted
  onSubmitting?: () => void;           // Starting main transaction
  onSubmitted?: (txHash: Hash) => void; // Transaction submitted
  onConfirming?: () => void;           // Waiting for confirmation
}

// Example: Full loading state management
const result = await action.execute({
  onApproving: () => {
    setStatus('approving');
    setMessage('Please approve token in wallet...');
  },
  onApproved: (hash) => {
    setMessage(`Approved! TX: ${hash.slice(0, 10)}...`);
  },
  onSubmitting: () => {
    setStatus('submitting');
    setMessage('Please confirm transaction in wallet...');
  },
  onSubmitted: (hash) => {
    setMessage(`Submitted! TX: ${hash.slice(0, 10)}...`);
    setTxHash(hash);
  },
  onConfirming: () => {
    setStatus('confirming');
    setMessage('Waiting for blockchain confirmation...');
  },
});
setStatus('success');

Real-Time Event Watching

Subscribe to vault events for live UI updates:

const unwatch = sdk.watchVault('0xVaultAddress', {
  onParticipantJoined: (address, total) => {
    console.log(`${address} joined! Total: ${total}`);
    updateParticipantList();
  },
  onParticipantVerified: (address, totalVerified) => {
    console.log(`${address} verified!`);
    updateVerifiedCount(totalVerified);
  },
  onSettled: (totalYield, protocolFee) => {
    console.log(`Event settled! Yield: ${totalYield}`);
    showClaimButton();
  },
  onClaimed: (address, amount) => {
    console.log(`${address} claimed ${amount}`);
  },
  onStakingStatusChanged: (isOpen) => {
    updateRegistrationStatus(isOpen);
  },
  onError: (error) => {
    console.error('Watch error:', error);
  },
});

// Stop watching when done
unwatch();

Error Handling

Errors are returned in simulation, not thrown:

const action = await sdk.register({ vaultAddress: '0x...' });

if (!action.simulation.success) {
  switch (action.simulation.error.code) {
    case 'STAKING_CLOSED':
      showMessage('Registration ended');
      break;
    case 'ALREADY_STAKED':
      showMessage('Already registered');
      break;
    case 'INSUFFICIENT_BALANCE':
      showMessage(`Need ${action.simulation.stakeAmount} ${action.simulation.tokenSymbol}`);
      break;
    case 'MAX_PARTICIPANTS_REACHED':
      showMessage('Event is full');
      break;
    default:
      showMessage(action.simulation.error.message);
  }
  return;
}

Error Codes:

  • STAKING_CLOSED - Registration ended
  • ALREADY_STAKED - Already registered
  • INSUFFICIENT_BALANCE - Not enough tokens
  • MAX_PARTICIPANTS_REACHED - Event full
  • NOT_OWNER - Not event owner
  • EVENT_ALREADY_STARTED - Already started
  • VAULT_ALREADY_SETTLED - Already settled
  • VAULT_NOT_SETTLED - Not settled yet
  • NOT_VERIFIED - Not verified for attendance
  • ALREADY_CLAIMED - Already claimed

Supported Tokens

| Token | Address | Decimals | Yield | |-------|---------|----------|-------| | USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | 6 | Yes | | IDRX | 0x18Bc5bcC660cf2B9cE3cd51a404aFe1a0cBD3C22 | 2 | No |

Contract Addresses (Base Mainnet)

| Contract | Address | |----------|---------| | FactoryATFi | 0x0be05a5fa7116c1b33f2b0036eb0d9690db9075f | | Morpho Vault | 0x050cE30b927Da55177A4914EC73480238BAD56f0 |

License

MIT