Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

VifRouter

The VifRouter class is the main entry point for building and executing transactions with the Vif protocol.

Initialization

import { VifRouter } from 'vifdk'
 
const router = new VifRouter(
  routerAddress,  // VifRouter contract address
  coreAddress,    // Vif core contract address
  chainId         // Network chain ID
)

Creating Actions

VifRouter provides two methods for creating action builders:

createTypedActions()

Use when actions are known in advance - provides better TypeScript support and autocomplete:

const actions = router.createTypedActions()
  .orderSingle({ /* typed params */ })
  .settleAll(/* typed token */)
  .build()

createActions()

Use when actions are unknown in advance - for dynamic flows (loops, conditionals, user-driven):

const actions = router.createActions()
  // Build actions dynamically
  .build()

Building Transactions

Actions use a fluent builder pattern:

const actions = router
  .createTypedActions()
  .action1(params)
  .action2(params)
  .action3(params)
  .build(options)

Build Options

.build({
  addRecommendedActions?: boolean  // Auto-add settlements
  receiver?: Address               // Default receiver for takes
})

addRecommendedActions: Automatically adds necessary settleAll and takeAll actions based on the operations performed.

const actions = router
  .createTypedActions()
  .orderSingle({
    market: market.asks,
    fillVolume: USDC.amount('1000')
  })
  .build({
    addRecommendedActions: true,
    receiver: walletAddress
  })
// Automatically adds settleAll(USDC) and takeAll(WETH)

Getting Transaction Data

txData()

Returns the encoded commands and arguments for the VifRouter contract:

const actions = router.createTypedActions()
  .orderSingle({ /* ... */ })
  .build()
 
const { commands, args } = actions.txData()

Use with viem to execute the transaction:

import { execute } from 'vifdk'
 
const hash = await walletClient.writeContract({
  address: routerAddress,
  ...execute(commands, args),
  value: actions.expectedValue({ globalProvision })
})

expectedValue()

Calculates the native token value needed for the transaction:

const value = actions.expectedValue({
  globalProvision: Token.PROVISION_TOKEN.amount('0.001')
})

This includes:

  • Provisions for limit orders with expiry
  • Native token wrapping amounts
  • Any other native token settlements

expectedAllowances()

Returns required token approvals:

const approvals = actions.expectedAllowances()
 
for (const approval of approvals) {
  console.log(`Approve ${approval.amountString} ${approval.token.symbol}`)
 
  // Check and approve if needed
  const currentAllowance = await client.readContract({
    address: approval.token.address,
    abi: erc20Abi,
    functionName: 'allowance',
    args: [walletAddress, coreAddress]
  })
 
  if (currentAllowance < approval.amount) {
    await walletClient.writeContract({
      address: approval.token.address,
      abi: erc20Abi,
      functionName: 'approve',
      args: [coreAddress, approval.amount]
    })
  }
}

Parsing Results

parseSimulationResult()

Parse the result from a simulation:

const { result } = await client.simulateContract({
  address: routerAddress,
  ...execute(commands, args)
})
 
const parsed = actions.parseSimulationResult(result)
 
for (const actionResult of parsed) {
  console.log(`Action: ${actionResult.type}`)
  console.log(`Success: ${actionResult.success}`)
 
  if (actionResult.data) {
    // Data is typed based on action type
    console.log('Data:', actionResult.data)
  }
 
  if (actionResult.error) {
    console.log('Error:', actionResult.error)
  }
}

parseLogs()

Parse transaction receipt logs:

const receipt = await client.waitForTransactionReceipt({ hash })
 
const results = actions.parseLogs(receipt.logs)
 
// results[0] contains the first action's result
if (results[0].type === Action.ORDER_SINGLE && results[0].data) {
  console.log('Gave:', results[0].data.gave.amountString)
  console.log('Got:', results[0].data.got.amountString)
}

Complete Example

import { VifRouter, Token, Market, Action } from 'vifdk'
import { createWalletClient } from 'viem'
 
// Initialize
const router = new VifRouter(routerAddress, coreAddress, 1)
const market = Market.from({
  base: WETH,
  quote: USDC,
  tickSpacing: 1n
})
 
// Build actions
const actions = router
  .createTypedActions()
  .orderSingle({
    market: market.asks,
    fillVolume: USDC.amount('1000'),
    maxTick: market.asks.price(4000)
  })
  .build({
    addRecommendedActions: true,
    receiver: walletAddress
  })
 
// Check approvals
const approvals = actions.expectedAllowances()
for (const approval of approvals) {
  // Approve tokens...
}
 
// Simulate
const { result, request } = await client.simulateContract({
  address: routerAddress,
  ...execute(...actions.txData())
})
 
// Parse simulation
const simResult = actions.parseSimulationResult(result)
console.log('Expected output:', simResult[0].data.got.amountString)
 
// Execute
const hash = await walletClient.writeContract(request)
 
// Parse receipt
const receipt = await client.waitForTransactionReceipt({ hash })
const results = actions.parseLogs(receipt.logs)
 
console.log('Actual output:', results[0].data.got.amountString)

Authorization

For signature-based authorization (EIP-712):

authorizationData()

Create authorization data for signing:

const auth = router.authorizationData(
  userAddress,
  nonce,
  deadline
)

singatureDataForAuthorization()

Get typed data for wallet signing:

const typedData = router.singatureDataForAuthorization(auth)
 
const signature = await walletClient.signTypedData(typedData)

Then use the signature with the authorize action.

Error Handling

Actions can be marked as failable:

import { Action, toFailableAction } from 'vifdk'
 
const actions = router
  .createActions()
  .add(Action.ORDER_SINGLE, params)  // Will revert if fails
  .add(toFailableAction(Action.ORDER_SINGLE), params)  // Won't revert
  .build()

Results indicate success/failure:

const results = actions.parseSimulationResult(result)
 
for (const res of results) {
  if (!res.success) {
    console.error(`Action ${res.type} failed:`, res.error)
  }
}

Best Practices

Always Simulate First

// ✓ Good: Simulate before executing
const { result, request } = await client.simulateContract({ /* ... */ })
const simResult = actions.parseSimulationResult(result)
// Check simResult
await walletClient.writeContract(request)
 
// ✗ Bad: Execute without simulation
await walletClient.writeContract({ /* ... */ })

Use Typed Actions

// ✓ Good: Type-safe
const actions = router.createTypedActions()
  .orderSingle({ /* autocomplete works */ })
 
// ✗ Less ideal: Manual action adding
const actions = router.createActions()
  .add(Action.ORDER_SINGLE, params)

Add Recommended Actions

// ✓ Good: Automatic settlements
.build({ addRecommendedActions: true, receiver })
 
// ✗ Manual: Forgetting settlements causes reverts
.build()

Check Expected Values

// ✓ Good: Calculate exact value needed
const value = actions.expectedValue({ globalProvision })
await walletClient.writeContract({ value, /* ... */ })
 
// ✗ Bad: Guessing value
await walletClient.writeContract({ value: parseEther('1'), /* ... */ })

Related Documentation