Prerequisites

Before submitting and verifying loyalty rule completion, you must have a user registered in Snag.

Depending on the social platform, you’ll need the following user information:

  • Twitter: User’s Twitter handle and Twitter user ID

  • Telegram: User’s Telegram handle and Telegram user ID

  • Discord: User’s Discord handle and Discord user ID

For more information on user management, see:

Overview

Use the Snag API to submit and verify loyalty rule completions from your own backend. Because external verification to third-party services (e.g. Twitter, Telegram, etc.) may take up to a couple minutes, we use an asynchronous process to verify rule completion.

Note: The API-based verification process described in this recipe is only required for the following rule types:- Code Entry

  • Text Input
  • Link Click
  • Discord Member
  • Connect Wallet
  • Daily Check-In
  • External Rule
  • Follow X Account
  • X New Tweet
  • X Text in Bio
  • X Text in Username
  • X Text in Comment
  • X Post Reaction
  • Telegram Join
  • TokenHold (only for multiplier reward type)This step is not required for Cadence based rules (e.g., Token Hold, Sold On … etc), as these rules run automatically on the specified start time and on set cadence (daily, weekly, monthly).

Let’s set up a rule to follow a specific Twitter account. The twitter handle or twitter user ID can be configured in the admin dashboard. We can then use the loyalty rule completion endpoint to submit a request to verify the rule completion. We can use the loyalty rule status endpoint to check the status of the rule completion request. When verification is complete, our system then creates a loyalty transaction to reward the user. The rule completion endpoint will return an error if re-called and the rule is already completed for the user.

Here is an example implementation in javascript on how you can use the following three endpoints to submit and verify loyalty rule completion:

const yourServerBaseUrl = "YOUR_SERVER_BASE_URL"
const userId = "USER_ID"
const ruleId = "RULE_ID"
let isLoading = false
let success = false
// After the claim is successful, we store the transaction entries for the user in this array
let transactionEntries = []

async function postCommentOnTwitter() {
  console.log("Posted comment on Twitter!")
}

async function callCompleteEndpoint() {
  isLoading = true
  try {
    const response = await fetch(`${yourServerBaseUrl}/loyalty/complete`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ userId, ruleId }),
    })
    console.log("Complete Endpoint Response:", await response.json())
    pollStatus()
  } catch (error) {
    console.error("Error completing loyalty rule:", error)
    isLoading = false
  }
}

async function pollStatus() {
  const statusUrl = `${yourServerBaseUrl}/loyalty/status?userId=${userId}`
  let attempts = 0

  async function poll() {
    try {
      const response = await fetch(statusUrl, {})
      const statuses = await response.json()
      console.log("Status Endpoint Response:", statuses)

      const ruleStatus = statuses.find((item) => item.loyaltyRuleId === ruleId)

      if (ruleStatus) {
        if (ruleStatus.status === "success") {
          transactionEntries = await fetchTransactionEntries()
          success = true
        } else if (ruleStatus.status === "failed") {
          success = false
        } else if (attempts < 10) {
          attempts++
          setTimeout(poll, 1000)
        } else {
          success = false
        }
      } else if (attempts < 10) {
        attempts++
        setTimeout(poll, 1000)
      } else {
        success = false
      }
    } catch (error) {
      console.error("Error polling status:", error)
      success = false
    } finally {
      isLoading = false
      button.textContent = "Claim"
    }
  }

  poll()
}

async function fetchTransactionEntries() {
  try {
    const response = await fetch(
      `${yourServerBaseUrl}/loyalty/transaction-entries?userId=${userId}&userCompletedLoyaltyRuleId=${ruleId}`,
      {}
    )
    console.log("Transaction Entries Response:", await response.json())
  } catch (error) {
    console.error("Error fetching transaction entries:", error)
  }
}

async function handleClaim() {
  if (isLoading) return
  await postCommentOnTwitter()
  await callCompleteEndpoint()
}