> ## 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.

# Write a Stratus function for loyalty rule completion

> Learn the input/output contract and build a handler that awards points or badges when a loyalty rule is completed.

<head>
  <script type="application/ld+json">
    {JSON.stringify({
            "@context": "https://schema.org",
            "@graph": [
              {
                "@type": "TechArticle",
                "headline": "Write a Stratus function for loyalty rule completion",
                "description": "Learn the input/output contract and build a handler that awards points or badges when a loyalty rule is completed.",
                "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-loyalty-rule"
              },
              {
                "@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 loyalty rule"}
                ]
              }
            ]
          })}
  </script>
</head>

## Overview

Use a Stratus function to decide if a user should be rewarded for a specific loyalty rule and return the reward details. This page explains when your function runs, the required input and output shapes, validation rules, and includes a copy‑paste handler template.

<Info>
  If you're new to Stratus functions, read the [Function
  Syntax](https://docs.snagsolutions.io/stratus/syntax) first for required
  export patterns, allowed modules, and validation behavior.
</Info>

## When your function runs

Your function executes during a loyalty rule completion attempt for rules of type `stratus_function`. The platform calls your function with a structured `input` object containing the current user, userGroupUsers and the `loyaltyRule` being completed. Your function must return a parsed output describing the reward to grant.

## Input contract

Your handler receives two parameters `(input, output)` and must accept the following input shape. `input` may be a stringified JSON; parse it if needed.

```javascript Input shape theme={null}
{
  user: {
    id: string,
    walletAddress: string,
    walletType: string,
  },
  loyaltyRule: {
    id: string,
    name: string,
    type: string,
    amount: number,
    metadata: object,
    startTime: string,
    endTime: string,
    interval: string,
    rewardType: string,
  }
}
```

<Note>
  `input` is passed exactly as shown above by the completion API for
  `stratus_function` rules.
</Note>

## Output contract

Set the result to an array of one reward object and return `output.buildOutput()`.

* **walletAddress**: string (required) – Address to reward
* **amount**: number (required if awarding fixed points) – Points to grant
* **idempotencyKey**: string (optional) – Only pass if you want to override the idempotency key for the rule that we set for managing how often a user can be rewarded for the same rule.
* **data**: string (optional) – A JSON.stringify()-ed object with extra context; must be valid JSON and under 1KB
* **loyaltyBadgeId**: string UUID (optional) – Badge to award instead of points or in addition to range-based rules
* **loyaltyMultiplierAmount**: number (optional) – Multiplier for range-based rules

<Warning>
  For now, only a single reward item is allowed. If you return more than one,
  validation will fail.
</Warning>

### Validation rules enforced by the platform

* Exactly one `handler` export; must call `output.setResult(...)` and then `return output.buildOutput()`
* Result must be an array with at most one reward object
* `data` must be stringified JSON and \< 1KB
* Allowed modules: `axios`, `lodash`, `viem` and `viem/chains` only (see [Function Syntax](https://docs.snagsolutions.io/stratus/syntax))

## Copy‑paste handler template

Below is a ready‑to‑use template based on the attached snippet. Replace the eligibility logic and values with your own.

```javascript loyalty-rule.js theme={null}
module.exports.handler = async (input, output) => {
  try {
    // Parse input if it was passed as a string
    let parsed = typeof input === 'string' ? JSON.parse(input) : input
    parsed = typeof parsed === 'string' ? JSON.parse(parsed) : parsed;
    parsed = typeof parsed === 'string' ? JSON.parse(parsed) : parsed;

    /**
     * input = {
     *   user: { id, walletAddress, walletType },
     *   userGroupUsers?: { id, walletAddress, walletType }[],
     *   loyaltyRule: { id, name, type, amount?, ... }
     * }
     */
    const { user, loyaltyRule } = parsed

    // Compute reward details
    const amount = 2 // Example fixed points
    const idempotencyKey = `rule:${loyaltyRule.id}:user:${user.id}` // Replace with a transaction hash or another unique key

    // Optional structured context under 1KB
    const data = JSON.stringify({
      reason: 'eligible via custom function',
      ruleType: loyaltyRule.type,
    })

    // Set result (single reward item only)
    output.setResult([
      {
        walletAddress: user.walletAddress,
        amount,
        idempotencyKey,
        data,
        // loyaltyBadgeId: '00000000-0000-0000-0000-000000000000',
        // loyaltyMultiplierAmount: 1.25,
      },
    ])

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

## How to add and test your function

<Steps>
  <Step title="Open the Loyalty Rule form">
    * Go to Admin → Loyalty → Rules
    * Click "Create Rule" or edit an existing rule
  </Step>

  <Step title="Choose rule type and add your code">
    * Set the rule type to `stratus_function` - In the function editor within the
      rule form, paste the handler template and customize it - Save the rule; no
      separate Stratus function is required
  </Step>

  <Step title="Trigger a completion attempt">
    * From your site or via API, attempt to complete the rule for a user
    * The platform invokes your function with the input shown above
    * If your function returns a valid reward, the user is queued for fulfillment

    <Check>
      Verify that your function run succeeded and a reward was recorded for the user.
    </Check>
  </Step>
</Steps>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Validation error: Multiple rewards are not allowed">
    Return exactly one reward object in the array passed to `output.setResult`.
  </Accordion>

  <Accordion title="Data must be a valid JSON object and less than 1kb">
    Ensure `data` is produced by `JSON.stringify({ ... })` and the string length is under 1024 characters.
  </Accordion>

  <Accordion title="Handler must call output.setResult / output.buildOutput()">
    Confirm your handler sets a result and returns `output.buildOutput()` as the
    final statement.
  </Accordion>

  <Accordion title="Module 'X' is not approved">
    Import only `axios`, `lodash`, `viem`, or `viem/chains`. Remove Node built-ins like `fs`.
  </Accordion>
</AccordionGroup>

## See also

* [Function Syntax](https://docs.snagsolutions.io/stratus/syntax)
* [Function Templates](https://docs.snagsolutions.io/stratus/function-templates)
* Loyalty rule API reference for response data: `https://docs.snagsolutions.io/api-reference/loyalty/get-loyalty-rules#response-data`
