> ## Documentation Index
> Fetch the complete documentation index at: https://docs.snagsolutions.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Function templates

> Pre-built Stratus function templates for common use cases like airdrops and token gating.

<head>
  <script type="application/ld+json">
    {JSON.stringify({
            "@context": "https://schema.org",
            "@graph": [
              {
                "@type": "TechArticle",
                "headline": "Function templates",
                "description": "Pre-built Stratus function templates for common use cases like airdrops and token gating.",
                "author": {"@type": "Organization", "name": "Snag Solutions", "url": "https://www.snagsolutions.io/"},
                "publisher": {"@type": "Organization", "name": "Snag Solutions", "url": "https://www.snagsolutions.io/", "logo": {"@type": "ImageObject", "url": "https://assets.snagsolutions.io/public/docs/snag-logo-dark-no-bg.svg"}},
                "mainEntityOfPage": "https://docs.snagsolutions.io/stratus/function-templates"
              },
              {
                "@type": "BreadcrumbList",
                "itemListElement": [
                  {"@type": "ListItem", "position": 1, "name": "Home", "item": "https://docs.snagsolutions.io/welcome"},
                  {"@type": "ListItem", "position": 2, "name": "Stratus", "item": "https://docs.snagsolutions.io/stratus/stratus-overview"},
                  {"@type": "ListItem", "position": 3, "name": "Functions", "item": "https://docs.snagsolutions.io/stratus/functions"},
                  {"@type": "ListItem", "position": 4, "name": "Function templates"}
                ]
              }
            ]
          })}
  </script>
</head>

## Overview

Check out the [Function Syntax](https://docs.snagsolutions.io/stratus/syntax) page for more information on how to create and validate Stratus functions and the [Overview](https://docs.snagsolutions.io/stratus/functions) page before you start..

<Warning>
  These templates are starting points. You should modify them based on your specific requirements and add appropriate error handling.
</Warning>

Here are some common function templates you can use as starting points for your Stratus functions. Simply copy the code and replace the placeholder values with your specific parameters.

## Track Relay.Link Bridge Activity

This function rewards users for bridging assets through [relay.link](https://relay.link). Replace the constants in the first lines of the function with your actual values.

There are two core steps to this function both of which can exist as part of any function:

1. It tracks all incoming transfers to the `DESTINATION_CHAIN_ID`.
2. It uses the Coingecko API for price tracking. It requires a minimum of 1\$ worth of coins to be bridged over and the dollar amount is multiplied by the REWARD\_MULTIPLIER variable.

### Function Code

<CodeGroup>
  ```javascript bridge-rewards.js theme={null}
  const axios = require('axios')

  /**
   * Required consts for the function
   */
  const LOYALTY_API_KEY = 'REPLACE_WITH_YOUR_API_KEY'
  const LOYALTY_RULE_ID = 'REPLACE_WITH_YOUR_LOYALTY_RULE_ID'
  const REWARD_MULTIPLIER = '1' // USD value will be multiplied by 1
  const DESTINATION_CHAIN_ID = 'REPLACE_WITH_CHAINID'
  const GAS_TOKEN_ID = 'eth' // replace with the gas token ID of the destination chain

  /**
   * Additional logger functionality
   */
  const logs = []
  const errors = []
  const log = Object.assign(
    (message) => {
      logs.push(message)
    },
    {
      error: (errorMessage) => {
        errors.push(errorMessage)
      },
      logs,
      errors,
    }
  )

  /**
   * Get reward amount
   */
  function rewardAmount(amount) {
    const number = parseFloat(amount)
    if (isNaN(number) || number < 1) return 0
    return Math.floor(number * REWARD_MULTIPLIER)
  }

  /**
   * Mark loyalty rule as completed to reward user
   */
  async function completeLoyaltyRule(reward, log) {
    try {
      if (!LOYALTY_API_KEY) {
        throw new Error('LOYALTY_API_KEY is not defined')
      }

      await axios.post(
        `https://admin.snagsolutions.io/api/loyalty/rules/${LOYALTY_RULE_ID}/complete`,
        {
          verifyOnly: 'false',
          amount: reward.reward,
          walletAddress: reward.walletAddress,
          idempotencyKey: reward.txHash,
        },
        {
          headers: {
            'Content-Type': 'application/json',
            'X-API-KEY': LOYALTY_API_KEY,
          },
        }
      )

      log(
        `${reward.walletAddress} rewarded with ${reward.reward} points for $${reward.amount} transaction, rule ${LOYALTY_RULE_ID}`
      )
    } catch (error) {
      log.error(`Failed for ${reward.walletAddress}: ${error.message}`)
    }
  }

  /**
   * Get recent relay requests
   */
  async function getRelayRequests() {
    const limit = 20
    const endTimestamp = Math.floor(Date.now() / 1000)
    const startTimestamp = endTimestamp - 10 * 60

    const allResults = []
    let continuation = null

    try {
      if (!DESTINATION_CHAIN_ID) {
        throw new Error('DESTINATION_CHAIN_ID is not defined')
      }

      do {
        let baseUrl = 'https://api.relay.link/requests/v2'
        const queryParams = [
          `sortBy=createdAt`,
          `destinationChainId=${DESTINATION_CHAIN_ID}`,
          `startTimestamp=${startTimestamp}`,
          `endTimestamp=${endTimestamp}`,
          `limit=${limit}`,
        ]

        if (continuation) {
          queryParams.push(`continuation=${encodeURIComponent(continuation)}`)
        }

        const url = `${baseUrl}?${queryParams.join('&')}`

        const response = await axios.get(url)
        const data = response.data
        if (Array.isArray(data.requests)) {
          allResults.push(...data.requests)
        }
        continuation = data.continuation
      } while (continuation)

      return allResults
    } catch (error) {
      log.error(`[getRelayRequests] Error fetching relay requests`)
      log.error(error)
      return []
    }
  }

  /**
   * Get recent relay rewards
   */
  async function getRecentRewards() {
    const txArray = await getRelayRequests()
    const rewards = []

    try {
      if (!txArray || txArray.length === 0) {
        log('No new rewards found')
        return rewards
      }

      for (const tx of txArray) {
        if (tx?.status === 'success' && tx?.user) {
          const currencyInChainId =
            tx?.data?.metadata?.currencyIn?.currency?.chainId

          if (currencyInChainId === DESTINATION_CHAIN_ID) {
            continue
          }
          const amountUsd =
            tx?.data?.metadata?.currencyOut?.amountUsdCurrent ||
            tx?.data?.metadata?.currencyIn?.amountUsdCurrent ||
            0
          if (!amountUsd) continue

          const currency = tx?.data?.currency
          if (!currency || !['usdc', GAS_TOKEN_ID].includes(currency)) continue

          const txHash = tx?.data?.outTxs?.[0]?.hash

          const amountToReward = rewardAmount(amountUsd)

          if (amountToReward > 0) {
            rewards.push({
              walletAddress: tx.recipient,
              amount: amountUsd,
              reward: amountToReward,
              txHash: txHash,
            })
          }
        }
      }
    } catch (e) {
      log.error(`[getRecentRewards] Error fetching relay requests`)
      log.error(e)
    }
    return rewards
  }

  /**
   * Reward users
   */
  async function rewardUsers(rewards, log) {
    for (const reward of rewards) {
      await completeLoyaltyRule(reward, log)
    }
  }

  module.exports.handler = async (input, output) => {
    try {
      const rewards = await getRecentRewards()
      if (rewards.length === 0) {
        log('No rewards found')
      } else {
        log(`Found ${rewards.length} rewards`)
        await rewardUsers(rewards, log)
      }

      // Set result
      output.setResult({
        logs,
        rewards,
        errors,
      })
      return output.buildOutput()
    } catch (error) {
      log.error('Error in user code:', error)
      throw error
    }
  }
  ```
</CodeGroup>

## Transaction Entries On-Chain

Some projects prefer to host points onchain to generate transaction volume and decentralize points data. This function listens to transaction entries and mints/burns tokens based on account balance changes. It can be adjusted to any token contract.

1. Click `Add New Function`
2. In the `Subscription` field, either select an existing Snag Subscription, or create a new one, as in the image below.

<Frame>
  ![Stratus function editor showing the subscription field for adding a new one](https://assets.snagsolutions.io/public/docs/new-snag-subscription.png)
</Frame>

3. Finally, replace the `LOYALTY_CURRENCY_ID` with your loyalty currency ID and the `ERC20_CONTRACT_ADDRESS` with your token contract address in the first lines.
4. Click `Save` and you're good to go.

### Function Code

<CodeGroup>
  ```javascript mint-burn.js theme={null}
  const { encodeFunctionData } = require('viem')

  // Replace with your contract address
  const ERC20_CONTRACT_ADDRESS = '0x0000000000000000000000000000000000000000'
  // Replace with your loyalty currency ID
  const LOYALTY_CURRENCY_ID = 'YOUR_LOYALTY_CURRENCY_ID'

  module.exports.handler = async (input, output) => {
    let operations = []

    // Parse input if it's a string
    if (typeof input === 'string') {
      try {
        let parsed = JSON.parse(input)
        if (typeof parsed === 'string') {
          parsed = JSON.parse(parsed)
        }
        input = parsed
      } catch (err) {
        output.setResult({
          message: 'Invalid JSON input',
          error: err,
          input: input,
          operations,
        })
        return output.buildOutput()
      }
    }

    if (!Array.isArray(input)) {
      output.setResult({
        message: 'Expected input to be an array of events',
        input: input,
        operations,
      })
      return output.buildOutput()
    }

    const mintABI = [
      {
        inputs: [
          { internalType: 'address', name: 'to', type: 'address' },
          { internalType: 'uint256', name: 'amount', type: 'uint256' },
        ],
        name: 'mint',
        outputs: [],
        stateMutability: 'nonpayable',
        type: 'function',
      },
    ]

    const burnABI = [
      {
        inputs: [
          { internalType: 'address', name: 'from', type: 'address' },
          { internalType: 'uint256', name: 'amount', type: 'uint256' },
        ],
        name: 'burn',
        outputs: [],
        stateMutability: 'nonpayable',
        type: 'function',
      },
    ]

    try {
      for (const event of input) {
        if (!event.data) continue

        const dataItem = event.data
        const tokenAmount = BigInt(dataItem.amount) * BigInt(10) ** BigInt(18)

        // Replace with your loyalty currency ID
        if (dataItem.loyaltyCurrencyId !== LOYALTY_CURRENCY_ID) {
          continue
        }

        let operationType = null
        if (dataItem.direction === 'credit') {
          operationType = 'mint'
        } else if (dataItem.direction === 'debit') {
          operationType = 'burn'
        } else {
          continue
        }

        const walletAddress = dataItem.loyaltyAccount?.user?.walletAddress
        if (!walletAddress) continue

        let data
        if (operationType === 'mint') {
          data = encodeFunctionData({
            abi: mintABI,
            functionName: 'mint',
            args: [walletAddress, tokenAmount.toString()],
          })
        } else if (operationType === 'burn') {
          data = encodeFunctionData({
            abi: burnABI,
            functionName: 'burn',
            args: [walletAddress, tokenAmount.toString()],
          })
        }

        output.addTransaction({
          to: ERC20_CONTRACT_ADDRESS,
          data: data,
          value: '0x0',
        })
      }

      output.setResult({
        message: 'Token transactions created.',
        operations,
      })

      return output.buildOutput()
    } catch (error) {
      console.error('Error in user code:', error)
      throw error
    }
  }
  ```
</CodeGroup>

## DEX Trading Rewards

This function tracks and rewards users based on their trading volume, using Redis for persistence. It is based on a subscription to a USDC based uniswap pool, tracking the `Swap` event.

Replace the constants in the first lines of the function with your actual values.

### Function Code

<CodeGroup>
  ```javascript trading-rewards.js theme={null}
  const { Redis } = require('ioredis')
  const axios = require('axios')

  const LOYALTY_RULE_ID = 'YOUR_LOYALTY_RULE_ID'
  const LOYALTY_API_KEY = 'YOUR_API_KEY'
  const REDIS_URL = 'YOUR_REDIS_URL'
  const VOLUME_THRESHOLD = 10 // $10 USD threshold

  const redis = new Redis(REDIS_URL)

  /**
   * Mark loyalty rule as completed to reward user
   */
  async function completeLoyaltyRule(walletAddress) {
    if (!LOYALTY_API_KEY) {
      throw new Error('LOYALTY_API_KEY is not defined')
    }

    await axios.post(
      `https://admin.snagsolutions.io/api/loyalty/rules/${LOYALTY_RULE_ID}/complete`,
      {
        verifyOnly: 'false',
        walletAddress: walletAddress,
      },
      {
        headers: {
          'Content-Type': 'application/json',
          'X-API-KEY': LOYALTY_API_KEY,
        },
      }
    )
  }

  module.exports.handler = async (input, output) => {
    const operations = []
    try {
      operations.push('Starting handler execution: parsing input')
      if (typeof input === 'string') {
        let parsed = JSON.parse(input)
        if (typeof parsed === 'string') parsed = JSON.parse(parsed)
        input = parsed
      }

      if (!Array.isArray(input) || input.length === 0) {
        output.setResult({ message: 'No event data provided', input, operations })
        return output.buildOutput()
      }

      const event = input[0]
      if (!event.decodedEvent || !event.decodedEvent.args) {
        throw new Error('Event data missing decodedEvent.args')
      }
      const args = event.decodedEvent.args

      // Process swap events (assuming token0 is USDC with 6 decimals)
      if (args.hasOwnProperty('amount0')) {
        const { sender, amount0 } = args
        const traderWallet = sender
        const tradeDollarValue = Math.abs(Number(amount0)) / 1e6

        const redisVolumeKey = `${traderWallet}-swapVolume`
        const redisRewardedKey = `${traderWallet}-swapRewarded`

        const alreadyRewarded = await redis.get(redisRewardedKey)
        if (alreadyRewarded) {
          output.setResult({
            message: `User ${traderWallet} has already been rewarded for swap volume.`,
            operations,
          })
          return output.buildOutput()
        }

        const storedVolumeStr = await redis.get(redisVolumeKey)
        const storedVolume = storedVolumeStr ? parseFloat(storedVolumeStr) : 0
        const newVolume = storedVolume + tradeDollarValue

        await redis.set(redisVolumeKey, newVolume.toString())

        if (storedVolume < redisRewardedKey && newVolume >= VOLUME_THRESHOLD) {
          // Complete loyalty rule when threshold is reached
          await completeLoyaltyRule(traderWallet)
          await redis.set(redisRewardedKey, 'true')

          output.setResult({
            message: `User ${traderWallet} rewarded for reaching $${VOLUME_THRESHOLD} swap volume.`,
            cumulativeSwapVolume: newVolume,
            operations,
          })
          return output.buildOutput()
        }

        output.setResult({
          message: `User ${traderWallet} cumulative swap volume updated to $${newVolume.toFixed(2)}.`,
          cumulativeSwapVolume: newVolume,
          operations,
        })
        return output.buildOutput()
      }

      output.setResult({ message: 'Unknown event type', operations })
      return output.buildOutput()
    } catch (error) {
      operations.push(`Error encountered: ${error.message}`)
      console.error('Error in handler:', error)
      output.setError(error.message)
      output.setResult({ operations })
      return output.buildOutput()
    }
  }
  ```
</CodeGroup>

## Usage Notes

1. Replace placeholder values (marked with `YOUR_*`) with your specific parameters
2. Update Redis URLs with your instance details
3. Adjust reward multipliers and thresholds as needed
4. Test thoroughly in development before deploying to production

<Note>
  Remember to handle your API keys and sensitive data securely. Never commit them directly in your code.
</Note>

## Common Parameters to Replace

* `LOYALTY_RULE_ID`: Your specific loyalty rule identifier
* `REDIS_URL`: Your Redis instance connection string
* `ERC20_CONTRACT_ADDRESS`: The address of your token contract
* `VOLUME_THRESHOLD`: Minimum volume required for rewards
* `MULTIPLIER`: Reward calculation multiplier

<Warning>
  These templates are starting points. You should modify them based on your specific requirements and add appropriate error handling.
</Warning>
