Docs/Build guides/Issue
Issue
Prepare and store a signed issuance that tokenizes selected offchain points, credit, perks, or campaign value into a customer-owned Loyalty Token balance.
Use issuance when a customer converts eligible offchain points, credit, perks, or campaign value into a tokenized Loyalty Token balance. If the Loyalty Token does not exist yet, the first successful issuance creates it automatically from the included metadata.
Inputs#
| Field | Type | Required | Meaning |
|---|---|---|---|
issuer | address | yes | Brand wallet that signs the action. |
to | address | yes | Customer wallet receiving the tokenized loyalty balance. |
loyaltyId | bytes32 | yes | Brand-scoped program ID. |
amount | uint256 string | yes | Amount of offchain loyalty value to tokenize. Must be greater than zero. |
expiresAt | uint256 string | yes | Use 0 for no expiry, or a Unix timestamp for an expiring bucket. |
deadline | uint256 string | yes | Last Unix timestamp when the issuance can be submitted. Use 0 for no execution deadline. |
nonce | bytes32 | yes | Unique per issuer. Replays are rejected. |
chainId | uint256 string | yes | Expected chain ID. Production uses Base mainnet 8453; non-production uses Base Sepolia 84532. |
verifyingContract | address | yes | The Loyfin factory that will execute the action. |
operationHash | bytes32 | optional | Brand reference hash for correlating this action with an internal database row. Defaults to zero bytes when omitted. |
metadata | Metadata | yes | Program metadata. Required for issuance because the first issuance can create the program. |
data | hex bytes | optional | Brand-defined data. Defaults to 0x. Max 2048 bytes. |
Request#
{
"issuance": {
"issuer": "0x1111111111111111111111111111111111111111",
"to": "0x2222222222222222222222222222222222222222",
"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"
}curl https://api.loyfin.com/issuances \
-X POST \
-H "content-type: application/json" \
-d '{
"issuance": {
"issuer": "0x1111111111111111111111111111111111111111",
"to": "0x2222222222222222222222222222222222222222",
"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"
}'const response = await fetch("https://api.loyfin.com/issuances", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ issuance, signature })
});
if (!response.ok) {
throw new Error(await response.text());
}
const { operation, duplicate } = await response.json();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
chainIdorverifyingContractmakes 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 a Convert to token or Tokenize 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 issuance or conversion row, and uses that row to build the signed issuance.
- Use operationHash to connect the Loyfin action with your internal database row, such as a hash of your internal operation ID.
- After
POST /issuancesreturns successfully, deduct or lock the customer's spendable offchain points so the same balance cannot be tokenized twice. - Keep the row pending until confirmation. Today you can poll
GET /operationsorGET /issuances; issuer webhooks are coming soon; teams that want full independence can also read onchain events directly. - The public API is the default path for performance and efficiency. Brands can also submit the
issuetransaction themselves or run their own relayer for a fully self-custodial issuance path.
State machine#
The brand database remains the source of customer truth.
Eligible
Your backend checks customer auth, fraud rules, spendable offchain balance, and campaign constraints.
Reserved
Create an internal operation row and reserve or deduct the offchain points before publishing the signature.
Created
POST the signed issuance to Loyfin. Store the returned operation ID and keep the brand row pending.
Completed
Poll the operation or issuance receipt. When mined, mark tokenization complete in your own system.