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


- 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
``bash`
npm install atfi viem
`typescript
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);
`
Every write function returns { simulation, execute() } - one call gives you everything:
`typescript
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});Your balance: ${action.simulation.userBalance}
console.log();Gas estimate: ${action.simulation.gasEstimate}
console.log();
// 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);
---
Show data before user connects wallet:
`typescript
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."
`
---
`typescript
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 |
---
`typescript
const action = await sdk.register({ vaultAddress: '0x...' });
if (action.simulation.success) {
console.log(Stake: ${action.simulation.stakeAmount} ${action.simulation.tokenSymbol});Your balance: ${action.simulation.userBalance}
console.log();Needs approval: ${action.simulation.needsApproval}
console.log();Spots left: ${action.simulation.maxParticipants - action.simulation.currentParticipants}
console.log();
// 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 |
---
`typescript
const action = await sdk.startEvent({ vaultAddress: '0x...' });
if (action.simulation.success) {
console.log(Total staked: ${action.simulation.totalStaked});Participants: ${action.simulation.participantCount}
console.log();Has yield: ${action.simulation.hasYield}
console.log();
const result = await action.execute();
}
`
---
`typescript
const action = await sdk.verifyParticipant({
vaultAddress: '0x...',
participants: ['0xAddr1', '0xAddr2', '0xAddr3'],
});
if (action.simulation.success) {
console.log(Will verify: ${action.simulation.toVerify.length});Already verified: ${action.simulation.alreadyVerified.length}
console.log();Not staked: ${action.simulation.notStaked.length}
console.log();
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 |
---
`typescript
const action = await sdk.settleEvent({ vaultAddress: '0x...' });
if (action.simulation.success) {
console.log(Verified: ${action.simulation.verifiedCount});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}
console.log();
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 |
---
`typescript
const action = await sdk.claim({ vaultAddress: '0x...' });
if (action.simulation.success) {
console.log(Total: ${action.simulation.claimableAmount} ${action.simulation.tokenSymbol});Your stake: ${action.simulation.stakeAmount}
console.log();Bonus: ${action.simulation.bonusShare}
console.log();
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 |
---
`typescript
// 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);
`
---
`typescriptUSDC: ${usdcBalance}
// Get single token balance
const usdcBalance = await sdk.getTokenBalance('0xUserAddress', 'USDC');
console.log(); // "125.50"
// Get all supported token balances
const balances = await sdk.getAllTokenBalances('0xUserAddress');
console.log(balances);
// { USDC: "125.50", IDRX: "1000000.00" }
`
---
Get events filtered by user:
`typescript
// 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" }
// }
`
---
All write functions accept callbacks for loading states:
`typescript
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)}...);Submitted! TX: ${hash.slice(0, 10)}...
},
onSubmitting: () => {
setStatus('submitting');
setMessage('Please confirm transaction in wallet...');
},
onSubmitted: (hash) => {
setMessage();`
setTxHash(hash);
},
onConfirming: () => {
setStatus('confirming');
setMessage('Waiting for blockchain confirmation...');
},
});
setStatus('success');
---
Subscribe to vault events for live UI updates:
`typescript${address} joined! Total: ${total}
const unwatch = sdk.watchVault('0xVaultAddress', {
onParticipantJoined: (address, total) => {
console.log();${address} verified!
updateParticipantList();
},
onParticipantVerified: (address, totalVerified) => {
console.log();Event settled! Yield: ${totalYield}
updateVerifiedCount(totalVerified);
},
onSettled: (totalYield, protocolFee) => {
console.log();${address} claimed ${amount}
showClaimButton();
},
onClaimed: (address, amount) => {
console.log();
},
onStakingStatusChanged: (isOpen) => {
updateRegistrationStatus(isOpen);
},
onError: (error) => {
console.error('Watch error:', error);
},
});
// Stop watching when done
unwatch();
`
---
Errors are returned in simulation, not thrown:
`typescript
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 endedALREADY_STAKED
- - Already registeredINSUFFICIENT_BALANCE
- - Not enough tokensMAX_PARTICIPANTS_REACHED
- - Event fullNOT_OWNER
- - Not event ownerEVENT_ALREADY_STARTED
- - Already startedVAULT_ALREADY_SETTLED
- - Already settledVAULT_NOT_SETTLED
- - Not settled yetNOT_VERIFIED
- - Not verified for attendanceALREADY_CLAIMED
- - Already claimed
---
| Token | Address | Decimals | Yield |
|-------|---------|----------|-------|
| USDC | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 | 6 | Yes |0x18Bc5bcC660cf2B9cE3cd51a404aFe1a0cBD3C22
| IDRX | | 2 | No |
| Contract | Address |
|----------|---------|
| FactoryATFi | 0x0be05a5fa7116c1b33f2b0036eb0d9690db9075f |0x050cE30b927Da55177A4914EC73480238BAD56f0` |
| Morpho Vault |
MIT