Docs/Build guides/Add to wallet

Add to wallet

Prepare and store a signed issuance that tokenizes selected existing offchain points or loyalty value into a customer-owned Loyalty Token balance.

GoalAdd selected existing loyalty value to a customer wallet.

Use issuance when a logged-in customer converts eligible offchain points or loyalty value from the brand's existing ledger into a tokenized Loyalty Token balance. Do not use this as the default path for every ecommerce purchase. If the Loyalty Token does not exist yet, the first successful issuance creates it automatically from the included metadata.

Inputs#

FieldTypeRequiredMeaning
issueraddressyesBrand wallet that signs the action.
toaddresssessionCustomer wallet receiving tokenized loyalty value. Not part of the issuer-signed terms; add it with the recipient signature or collect it on checkout.
loyaltyIdbytes32yesBrand-scoped program ID.
amountuint256 stringyesAmount of offchain loyalty value to tokenize. 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": "0",
    "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.",
      "contractURI": "",
      "tokenURI": ""
    },
    "data": "0x"
  },
  "signature": "0xababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab"
}
TypeScript SDK — Upcoming releasetypescript
import { Loyfin, issuanceTypedData } from "@loyfin/server";

const loyfin = new Loyfin({ apiKey: process.env.LOYFIN_API_KEY });

const typedData = issuanceTypedData(issuance);
const signature = await issuerSigner.signTypedData(typedData);

const { issuance: session, duplicate } = await loyfin.issuances.create({
  issuance,
  signature
});

// Show session.checkoutUrl when the recipient still needs to sign.

Next step#

Store the signed tokenization issuance and show it as Created. The issuer does not need to submit the transaction: Loyfin's official relayer can relay it, or an independent relayer can submit first and earn the relayer fee. 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 points ledger.

Most loyalty points still live in the brand's database. A common integration is an Add to wallet button inside the brand app. When the customer clicks it, the app calls your brand API; your backend checks auth, checks the spendable points balance, creates an internal conversion row, reserves or deducts the selected offchain points, and uses that row to build the signed issuance.

  • 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 offchain points before publishing the signed issuance, so two fast requests cannot tokenize the same balance twice.
  • Keep the row pending until confirmation. Loyfin sends account-level webhooks for completed issuance receipts; teams can also poll GET /operations/:id or GET /issuances, or read onchain events directly.
  • The public API is the default path for performance and efficiency. Brands can also submit the issue transaction themselves or run their own relayer for a fully self-custodial issuance path.

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 came from the same offchain 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.
  • If the signed operation expires or is rejected, release the reservation or move it to manual review based on your support policy.

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 offchain points before publishing the signature.

03

Created

POST the signed issuance to Loyfin. Store the returned operation ID and keep the brand row pending.

04

Completed

Poll the operation or issuance receipt. When mined, mark tokenization complete in your own system.

Loyfin is built onBase