Project: Build a Loyalty Points Program
A complete, runnable project that registers your own token as a first-class asset, mints rewards to users, and burns on redemption — entirely outside the airtime/stablecoin path. This is the simplest way to add a brand-new token type without touching engine code — every generic /assets/:symbol/* endpoint works on it immediately once registered. What "register" means differs by chain, though: on an EVM chain you deploy a contract yourself first; on Stellar there's no contract to deploy at all — you register by issuer account instead.
What you'll build
A points.js script that registers a POINTS asset (once), then rewards and redeems it repeatably — the same reward/redeem cycle a real loyalty program runs on every purchase and checkout.
Prerequisites
- Node.js 18+ (for global
fetch) - A running comet-engine with a tenant API key/secret — see Authentication
- A real on-chain artifact to register against: deploy your own ERC-20 with any standard tool (Hardhat/Foundry) on Celo, or decide on an issuer account on Stellar — neither is something this project does for you
Project setup
mkdir loyalty-points && cd loyalty-points && npm init -y && npm install dotenv.env:
COMET_BASE_URL=http://localhost:8100
COMET_API_KEY=your-tenant-api-key
COMET_API_SECRET=your-tenant-api-secret
CONTRACT_OR_ISSUER=0xYourDeployedERC20OrStellarIssuerAccountCreate points.js with this shared helper at the top — every step below adds to the same file:
import 'dotenv/config';
const base = process.env.COMET_BASE_URL;
const headers = {
'Content-Type': 'application/json',
'X-API-Key': process.env.COMET_API_KEY,
'X-API-Secret': process.env.COMET_API_SECRET,
};
async function comet(method, path, body) {
const res = await fetch(base + path, { method, headers, body: body ? JSON.stringify(body) : undefined });
const data = await res.json();
if (!res.ok) throw new Error(`${method} ${path} -> ${res.status}: ${JSON.stringify(data)}`);
return data;
}Step 1: Register your token (once)
You deploy the ERC-20 contract yourself (any standard implementation) — Comet Engine never deploys token contracts on your behalf. Once it exists on-chain, register it:
🔒 Admin operation — always requires a Service Token, regardless of the panel above.
curl -X POST "http://localhost:8100/api/v1/admin/assets/POINTS/register?chain=celo" \
-H "Content-Type: application/json" \
-H "X-Service-Token: YOUR_SERVICE_TOKEN" \
-d '{
"contract": "0x9f8b2C4A1e6D53F0a7B4c6E2D1a9F3b8C5d7E1a0",
"decimals": 18,
"name": "FusionPoints",
"type": "utility",
"backing": "none"
}'{
"message": "asset POINTS registered on celo",
"asset": {
"symbol": "POINTS",
"contract": "0x9f8b...E1a0"
}
}Step 2: Reward a user
curl -X POST "http://localhost:8100/api/v1/assets/points/mint" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-API-Secret: YOUR_API_SECRET" \
-d '{
"userId": 24,
"amountBase": "1000000000000000000000",
"externalId": "reward-001"
}'{
"amount": 1000,
"chain": "celo",
"symbol": "POINTS",
"txHash": "0x..."
}Step 3: Redeem points
curl -X POST "http://localhost:8100/api/v1/assets/points/burn-by-holder" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-H "X-API-Secret: YOUR_API_SECRET" \
-d '{
"userId": 24,
"amountBase": "500000000000000000000",
"externalId": "redeem-001"
}'{
"txHash": "0x...",
"chain": "celo",
"symbol": "POINTS",
"amount": 500
}Step 4: Run registration + reward + redeem as one script
Add main() to points.js:
async function main() {
await comet('POST', '/api/v1/admin/assets/POINTS/register?chain=celo', {
contract: process.env.CONTRACT_OR_ISSUER, decimals: 18, name: 'FusionPoints', type: 'utility', backing: 'none',
});
console.log('POINTS registered on celo');
const reward = await comet('POST', '/api/v1/assets/points/mint', { userId: 24, amountBase: '1000000000000000000000', externalId: `reward-${Date.now()}` });
console.log('rewarded 1000 points:', reward.txHash);
const redeem = await comet('POST', '/api/v1/assets/points/burn-by-holder', { userId: 24, amountBase: '500000000000000000000', externalId: `redeem-${Date.now()}` });
console.log('redeemed 500 points:', redeem.txHash);
}
main().catch((e) => { console.error(e); process.exit(1); });node points.jsWhat you built
- A brand-new token type, registered without touching engine code.
- A working reward/redeem cycle any checkout or purchase flow can call directly.
Next steps: wire Step 2 into your own purchase/checkout event, or see Receipt Tokenization if you need your own schema, a verification workflow, or a supply cap tied to something real rather than "register and mint against it."