Docs/Integrate your program/Add to wallet

Add to wallet

Create a signed issuance to convert a customer’s eligible points into tokens in their wallet.

GoalConvert a customer’s eligible points into tokens in their wallet.

When a customer chooses Add to wallet, your backend reserves or deducts the selected points and creates a signed issuance. The customer completes checkout to receive those points as tokens. Your existing loyalty system stays in place. The first successful issuance creates the program contract from the included metadata if it does not exist yet.

Inputs#

FieldTypeRequiredMeaning
issueraddressexternal signingBrand wallet that signs the action. Omit together with the issuer signature for enabled API signing.
loyaltyIdbytes32yesBrand-scoped program ID.
amountuint256 stringyesNumber of eligible points to convert into wallet tokens. Must be greater than zero.
expiresAtuint256 stringyesUse 0 for no expiry, or a Unix timestamp for an expiring bucket.
deadlineuint256 stringyesLast Unix timestamp when the issuance can be submitted. Use 0 for no execution deadline.
noncebytes32yesUnique per issuer. Replays are rejected.
chainIduint256 stringyesExpected chain ID. Loyfin uses Base 8453.
verifyingContractaddressyesThe Loyfin factory that will execute the action.
operationHashbytes32optionalBrand reference hash for correlating this action with an internal database row. Defaults to zero bytes when omitted.
metadataMetadatayesProgram metadata. Required for issuance because the first issuance can create the program.
datahex bytesoptionalBrand-defined data. Defaults to 0x. Max 2048 bytes.

Request#

Issuance bodyjson
{
  "issuance": {
    "issuer": "0x1111111111111111111111111111111111111111",
    "loyaltyId": "0x4242424242424242424242424242424242424242424242424242424242424242",
    "amount": "1000",
    "expiresAt": "0",
    "deadline": "1790965800",
    "nonce": "0x7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a",
    "chainId": "8453",
    "verifyingContract": "0x3333333333333333333333333333333333333333",
    "operationHash": "0x9999999999999999999999999999999999999999999999999999999999999999",
    "metadata": {
      "loyaltyId": "0x4242424242424242424242424242424242424242424242424242424242424242",
      "name": "Bloom Coffee Rewards",
      "symbol": "BLOOM",
      "media": "ipfs://bafy.../bloom.png",
      "description": "Rewards for Bloom Coffee customers."
    },
    "data": "0x"
  },
  "signature": "0xababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab",
  "to": "0x2222222222222222222222222222222222222222"
}
JavaScript fetchjavascript
// Run on your backend. Sign the issuance as shown in Signing.
// verifiedWallet is the recipient saved with this customer’s conversion.
const response = await fetch("https://api.loyfin.com/issuances", {
  method: "POST",
  headers: {
    "content-type": "application/json",
    "x-loyfin-api-key": process.env.LOYFIN_API_KEY
  },
  body: JSON.stringify({
    issuance,
    signature,
    to: verifiedWallet
  })
});

if (!response.ok) {
  throw new Error(await response.text());
}

const { issuance: session, duplicate } = await response.json();

Next step#

Store the issuance and show Awaiting wallet authorization, then give the private checkout URL to the intended customer. Checkout collects the recipient authorization and asks the customer wallet to submit the transaction with the action fee and gas. When mined, the indexer writes a completed Issuance event object.

Common errors#

  • Wrong chainId or verifyingContract makes the signature invalid onchain.
  • Expired buckets cannot be issued, and actions cannot execute after their deadline.
  • A reused issuer nonce is treated as duplicate or rejected before balances change.

Brand database flow#

Loyfin tokenizes selected balances; it does not replace your primary loyalty ledger.

Add an Add to wallet button to your existing app. When a logged-in customer chooses an amount, your backend checks eligibility and the available balance, creates a conversion record, and reserves or deducts those points. It then signs the issuance using that record.

  • Use operationHash to connect the Loyfin action with your internal database row. Use a secret-keyed HMAC or a stored random bytes32 value; do not publish a guessable raw-ID hash.
  • Reserve or deduct the customer's spendable points in the brand account before publishing the signed issuance, so two fast requests cannot tokenize the same balance twice.
  • Keep the conversion pending until finalized completion. Use verified completion webhooks by default. Polling or direct chain reads are fallbacks and must verify finality before completing the conversion.
  • The session API is the default coordination path. The customer submits through Loyfin Checkout, or the brand can embed the same wallet-paid issue flow in its own interface.

Double-spend safety#

The brand ledger must reserve value before a signed issuance can be published.

Loyfin prevents replay of the same issuer nonce, but it cannot know whether two different nonces both spend the same points. The brand backend should use a row lock, serializable transaction, or atomic update that succeeds only when the customer has enough spendable balance.

  • Create one internal operation row with a stable ID, operationHash, nonce, wallet address, amount, and status.
  • Retry transient Loyfin API failures with the same signed payload instead of creating a fresh nonce for the same offchain deduction.
  • Release reserved points only after verified expiry with reservationReleaseSafe: true, or equivalent finalized chain checks. Reconcile other failures before restoring points.

State machine#

The brand database remains the source of customer truth.

01

Eligible

Your backend checks customer auth, fraud rules, spendable offchain balance, and campaign constraints.

02

Reserved

Create an internal operation row and reserve or deduct the points in the brand account before publishing the signature.

03

Awaiting authorization

POST the issuance, store its operation ID, and give the customer the checkout link. Keep your internal conversion pending.

04

Completed

After finalized completion, mark the conversion complete in your database.