Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,13 @@ npm run test:unit # Run only unit tests
- **JSDoc + TypeScript** - Type annotations in comments, TypeScript only for type checking
- **Node.js native test runner** - Uses `node:test` module

## Architecture

### Two-Layer Design

1. **Library Layer** ([index.js](index.js)) - Pure functions, all exported for testing
2. **Application Layer** ([bin/bot.js](bin/bot.js)) - Event loop with error handling

### Key Design Decisions

- **Stateless** - No database, queries contract fresh each iteration
- **Direct wallet bidding** - No account deposit needed
- **Deterministic auction selection** - Always picks first auction with available fees
- **100% bids** - Always bids for all available fees if balance is sufficient
- **Auto-refuel** - Swaps acquired USDFC back to FIL after successful bids (mainnet only)

## Coding Conventions

Expand Down Expand Up @@ -90,13 +84,26 @@ The `createClient` function supports both networks: `mainnet` and `calibration`.

### Sushiswap Integration

The bot uses Sushiswap's REST API for price checking:
The bot uses Sushiswap's SDK for swap quoting.

#### Frontrunning Protection (Nonce+1)

To reduce the frontrunning window, both `burnForFees` and swap transactions are submitted simultaneously:

1. Get current nonce N
2. Submit `burnForFees` with nonce N
3. Immediately submit swap with nonce N+1 (don't wait for bid confirmation)
4. Wait for both receipts in parallel

This ensures both transactions hit the mempool together. If `burnForFees` fails, the swap should also fail due to nonce ordering.

#### Flow on Mainnet

1. At startup: Discover Sushiswap router → Approve USDFC for router
2. Each iteration: Get balances → Get auction info → Get swap quote → Check profitability → Submit bid+swap simultaneously (nonce+1)

#### Error Handling

- **API endpoint**: `https://api.sushi.com/quote/v7/{chainId}`
- **Quote pair**: USDFC → native FIL
- **Quote network**: Always queries mainnet (chain ID 314) for accurate pricing
- **Quote function**: REST API with `tokenIn`, `tokenOut`, `amount`, `maxSlippage`
- **Max slippage**: Default 0.005 (0.5%)
- **Profitability logic**: Bot bids only if market price >= auction price
- **Error handling**: Quote failures skip auction and log warning, continue monitoring
- **Implementation**: Uses Sushi's EVM SDK `getQuote` function from `sushi/evm`
- If bid+swap both fail, USDFC (if any) is held for next iteration
- Quote failures skip the auction (no bid placed)
- If bid succeeds but swap fails, USDFC held for next iteration
34 changes: 32 additions & 2 deletions bin/bot.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,44 @@
import 'dotenv/config'
import { setTimeout } from 'node:timers/promises'
import { initializeConfig, processAuctions } from '../index.js'
import { discoverSushiswapRouter } from '../lib/swap.js'
import { initializeConfig, ensureApproval } from '../lib/config.js'
import { processAuctions } from '../lib/auction.js'

const config = await initializeConfig(process.env)
const sushiswapRouterAddress = await discoverSushiswapRouter({
chainId: config.chainId,
tokenIn: config.usdfcAddress,
sender: config.account.address,
})

if (!sushiswapRouterAddress && config.chainId === 314) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it not fatal if this happens on testnet?

Copy link
Member Author

@pyropy pyropy Feb 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not since there is no calibration deployment for sushiswap and so do not perform swaps there.

However, should we leave the mainnet bot running even if we're not able to find the router?

console.error(
'Error: Could not discover Sushiswap router address on Filecoin mainnet.',
)
process.exit(1)
}

if (sushiswapRouterAddress) {
await ensureApproval({
publicClient: config.publicClient,
walletClient: config.walletClient,
account: config.account,
tokenAddress: config.usdfcAddress,
spenderAddress: /** @type {import('viem').Address} */ (
sushiswapRouterAddress
),
})
}

console.log()
console.log('Starting auction monitoring...')
console.log()

while (true) {
console.log(`Starting auction check...`)

try {
await processAuctions(config)
await processAuctions({ ...config, sushiswapRouterAddress })
} catch (error) {
const err = /** @type {Error} */ (error)
console.log()
Expand Down
Loading