@verxioprotocol/core
Version:
Opensource infrastructure powering on-chain loyalty across Solana with Metaplex Core
1,470 lines (1,469 loc) • 89.4 kB
JavaScript
var Q = Object.defineProperty;
var ee = (e, t, r) => t in e ? Q(e, t, { enumerable: !0, configurable: !0, writable: !0, value: r }) : e[t] = r;
var K = (e, t, r) => ee(e, typeof t != "symbol" ? t + "" : t, r);
import { publicKey as L, sol as te, createGenericFile as re, generateSigner as T } from "@metaplex-foundation/umi";
import { createCollection as O, writeCollectionExternalPluginAdapterDataV1 as D, ExternalPluginAdapterSchema as v, create as S, writeData as E, fetchAsset as f, collectionAddress as b, fetchAssetsByOwner as x, fetchCollection as U, updateCollectionPlugin as G, transferV1 as J } from "@metaplex-foundation/mpl-core";
import "@solana/web3.js";
import { transferSol as ae } from "@metaplex-foundation/mpl-toolbox";
const F = {
name: "Grind",
rewards: ["nothing for you!"],
xpRequired: 0
}, Y = {
totalBroadcasts: 0,
broadcasts: []
}, H = {
totalVouchersIssued: 0,
totalVouchersRedeemed: 0,
totalValueRedeemed: 0,
voucherStats: []
}, $ = {
xp: 0,
lastAction: null,
actionHistory: [],
messageHistory: [],
currentTier: F.name,
tierUpdatedAt: Date.now(),
rewards: F.rewards
}, y = {
ATTRIBUTES: "Attributes",
APP_DATA: "AppData",
PERMANENT_TRANSFER_DELEGATE: "PermanentTransferDelegate",
UPDATE_DELEGATE: "UpdateDelegate",
FREEZE_DELEGATE: "FreezeDelegate"
}, h = {
PROGRAM_TYPE: "programType",
TIERS: "tiers",
POINTS_PER_ACTION: "pointsPerAction",
CREATOR: "creator",
TYPE: "type",
XP: "xp",
METADATA: "metadata",
// Voucher-specific keys
VOUCHER_TYPES: "voucherTypes",
MERCHANT_ID: "merchantId"
};
class ie extends Error {
constructor(r, i, a) {
const s = `Expected a string of base ${i}, got [${r}].`;
super(s);
K(this, "name", "InvalidBaseStringError");
this.cause = a;
}
}
const se = (e) => {
const t = e.length, r = BigInt(t);
return {
description: `base${t}`,
fixedSize: null,
maxSize: null,
serialize(i) {
if (!i.match(new RegExp(`^[${e}]*$`)))
throw new ie(i, t);
if (i === "") return new Uint8Array();
const a = [...i];
let s = a.findIndex((m) => m !== e[0]);
s = s === -1 ? a.length : s;
const o = Array(s).fill(0);
if (s === a.length) return Uint8Array.from(o);
const n = a.slice(s);
let d = 0n, u = 1n;
for (let m = n.length - 1; m >= 0; m -= 1)
d += u * BigInt(e.indexOf(n[m])), u *= r;
const c = [];
for (; d > 0n; )
c.unshift(Number(d % 256n)), d /= 256n;
return Uint8Array.from(o.concat(c));
},
deserialize(i, a = 0) {
if (i.length === 0) return ["", 0];
const s = i.slice(a);
let o = s.findIndex((c) => c !== 0);
o = o === -1 ? s.length : o;
const n = e[0].repeat(o);
if (o === s.length) return [n, i.length];
let d = s.slice(o).reduce((c, m) => c * 256n + BigInt(m), 0n);
const u = [];
for (; d > 0n; )
u.unshift(e[Number(d % r)]), d /= r;
return [n + u.join(""), i.length];
}
};
}, oe = se("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");
function P(e) {
return oe.deserialize(e)[0];
}
function R(e) {
if (!e)
throw new Error("assertValidContext: Context is undefined");
if (!e.umi)
throw new Error("assertValidContext: UMI is undefined");
if (!e.programAuthority)
throw new Error("assertValidContext: Program authority is undefined");
return !0;
}
const ne = {
CREATE_LOYALTY_PROGRAM: 2e-3,
// 0.002 SOL
CREATE_VOUCHER_COLLECTION: 2e-3,
// 0.002 SOL (same as loyalty program)
LOYALTY_OPERATIONS: 1e-3,
// 0.001 SOL
VERXIO_INTERACTION: 4e-4
// 0.0004 SOL
}, de = L("3DdcJkvjW7KLtMeko3Zr57jEJWhqRHuPsEBFm1XJYh7W");
function p(e, t, r) {
return ae(e, {
source: t,
destination: de,
amount: te(ne[r])
});
}
async function q(e, t) {
try {
if (!t || !t.imageUri || !t.creator)
throw new Error("Missing required data for metadata generation");
const r = {
name: t.loyaltyProgramName,
symbol: "VERXIO",
description: `Loyalty Program for ${t.metadata.organizationName}`,
image: t.imageUri,
properties: {
files: [
{
uri: t.imageUri,
type: t.mimeType || "image/png"
}
],
category: "image",
creators: [
{
address: t.creator.toString(),
share: 100
}
]
},
attributes: [
{
trait_type: "Organization",
value: t.metadata.organizationName
},
{
trait_type: "Program Type",
value: "Loyalty Program"
},
{
trait_type: "Tiers",
value: t.tiers.length.toString()
},
...t.metadata.brandColor ? [
{
trait_type: "Brand Color",
value: t.metadata.brandColor
}
] : []
],
program: {
name: t.loyaltyProgramName,
metadata: t.metadata,
tiers: t.tiers,
pointsPerAction: t.pointsPerAction
}
};
return await e.umi.uploader.uploadJson(r);
} catch (r) {
throw console.error("Error generating loyalty program metadata:", r), new Error("Failed to generate loyalty program metadata");
}
}
async function z(e, t) {
try {
if (!t || !t.imageUri || !t.creator)
throw new Error("Missing required data for pass metadata generation");
const r = {
name: t.passName,
symbol: "VERXIO-PASS",
description: `Loyalty Pass for ${t.organizationName}`,
image: t.imageUri,
properties: {
files: [
{
uri: t.imageUri,
type: t.mimeType || "image/png"
}
],
category: "image",
creators: [
{
address: t.creator.toString(),
share: 100
}
]
},
attributes: [
{
trait_type: "Organization",
value: t.organizationName
},
{
trait_type: "Pass Type",
value: "Loyalty Pass"
},
{
trait_type: "Status",
value: "Active"
}
]
};
return await e.umi.uploader.uploadJson(r);
} catch (r) {
throw console.error("Error generating loyalty pass metadata:", r), new Error("Failed to generate loyalty pass metadata");
}
}
async function W(e, t) {
try {
if (!t || !t.imageUri || !t.creator)
throw new Error("Missing required data for collection metadata generation");
const r = {
name: t.collectionName,
symbol: "VERXIO-VOUCHER",
description: t.description,
image: t.imageUri,
properties: {
files: [
{
uri: t.imageUri,
type: t.mimeType || "image/png"
}
],
category: "image",
creators: [
{
address: t.creator.toString(),
share: 100
}
]
},
attributes: [
{
trait_type: "Merchant",
value: t.merchantName
},
{
trait_type: "Collection Type",
value: "Voucher Collection"
},
{
trait_type: "Status",
value: "Active"
}
]
};
return await e.umi.uploader.uploadJson(r);
} catch (r) {
throw console.error("Error generating voucher collection metadata:", r), new Error("Failed to generate voucher collection metadata");
}
}
async function X(e, t) {
try {
if (!t || !t.imageUri || !t.creator)
throw new Error("Missing required data for voucher metadata generation");
const r = {
name: t.voucherName,
symbol: "VERXIO-VOUCHER",
description: t.voucherData.description,
image: t.imageUri,
properties: {
files: [
{
uri: t.imageUri,
type: t.mimeType || "image/png"
}
],
category: "image",
creators: [
{
address: t.creator.toString(),
share: 100
}
]
},
attributes: [
{
trait_type: "Voucher Type",
value: t.voucherData.type
},
{
trait_type: "Value",
value: t.voucherData.value.toString()
},
{
trait_type: "Merchant ID",
value: t.voucherData.merchantId
},
{
trait_type: "Status",
value: "Active"
}
]
};
return await e.umi.uploader.uploadJson(r);
} catch (r) {
throw console.error("Error generating voucher metadata:", r), new Error("Failed to generate voucher metadata");
}
}
async function g(e, t, r, i = "image/png") {
try {
if (!t)
throw new Error("Image buffer is required");
if (!r || typeof r != "string")
throw new Error("Valid filename is required");
if (!e || !e.umi || !e.umi.uploader)
throw new Error("Valid context with uploader is required");
const a = re(t, r, {
contentType: i
}), [s] = await e.umi.uploader.upload([a]);
return s;
} catch (a) {
throw console.error("Error uploading image:", a), new Error("Failed to upload image");
}
}
async function je(e, t) {
R(e), le(t);
const r = t.collectionSigner ?? T(e.umi), i = t.updateAuthority ?? T(e.umi);
try {
let a = t.metadataUri;
if (!a) {
if (!t.imageBuffer || !t.imageFilename)
throw new Error("Either metadataUri or imageBuffer with imageFilename must be provided");
const d = await g(e, t.imageBuffer, t.imageFilename, t.imageContentType);
a = await q(e, {
loyaltyProgramName: t.loyaltyProgramName,
metadata: t.metadata,
tiers: t.tiers,
pointsPerAction: t.pointsPerAction,
imageUri: d,
creator: t.programAuthority,
mimeType: t.imageContentType
});
}
const s = p(e.umi, e.umi.identity.publicKey, "CREATE_LOYALTY_PROGRAM"), n = await O(e.umi, {
collection: r,
name: t.loyaltyProgramName,
plugins: ue(t, i.publicKey),
uri: a,
updateAuthority: i.publicKey
}).add(s).add(
D(e.umi, {
collection: r.publicKey,
authority: i,
key: {
__kind: y.APP_DATA,
fields: [
{
__kind: "Address",
address: i.publicKey
}
]
},
data: new TextEncoder().encode(JSON.stringify(Y))
})
).sendAndConfirm(e.umi, {
confirm: { commitment: "confirmed" }
});
return { collection: r, signature: P(n.signature), updateAuthority: i };
} catch (a) {
throw new Error(`Failed to create loyalty program: ${a}`);
}
}
function ue(e, t) {
return [
{
type: y.ATTRIBUTES,
attributeList: [
{ key: h.PROGRAM_TYPE, value: "loyalty" },
{ key: h.TIERS, value: JSON.stringify(e.tiers) },
{ key: h.POINTS_PER_ACTION, value: JSON.stringify(e.pointsPerAction) },
{ key: h.CREATOR, value: e.programAuthority.toString() },
{ key: h.METADATA, value: JSON.stringify(e.metadata) }
]
},
{
type: y.PERMANENT_TRANSFER_DELEGATE,
authority: {
address: e.programAuthority,
type: "Address"
}
},
{
type: y.UPDATE_DELEGATE,
authority: {
address: t,
type: "Address"
},
additionalDelegates: []
},
{
type: y.APP_DATA,
dataAuthority: {
type: "Address",
address: t
},
schema: v.Json
}
];
}
function le(e) {
if (!e)
throw new Error("assertValidCreateLoyaltyProgramConfig: Config is undefined");
if (!e.loyaltyProgramName || !e.loyaltyProgramName.trim() || !e.loyaltyProgramName.trim().length)
throw new Error("assertValidCreateLoyaltyProgramConfig: Loyalty program name is undefined");
if (e.metadataUri) {
if (!e.metadataUri.trim() || !e.metadataUri.trim().length)
throw new Error("assertValidCreateLoyaltyProgramConfig: Metadata URI is empty");
if (!e.metadataUri.startsWith("https://") && !e.metadataUri.startsWith("http://"))
throw new Error("assertValidCreateLoyaltyProgramConfig: Metadata URI is not a valid URL");
} else {
if (!e.imageBuffer)
throw new Error(
"assertValidCreateLoyaltyProgramConfig: Image buffer is required when metadataUri is not provided"
);
if (!e.imageFilename || !e.imageFilename.trim())
throw new Error(
"assertValidCreateLoyaltyProgramConfig: Image filename is required when metadataUri is not provided"
);
}
if (!e.programAuthority)
throw new Error("assertValidCreateLoyaltyProgramConfig: Program authority is undefined");
if (!e.updateAuthority)
throw new Error("assertValidCreateLoyaltyProgramConfig: Update authority is undefined");
if (!e.tiers || e.tiers.length === 0)
throw new Error("assertValidCreateLoyaltyProgramConfig: Tiers configuration is missing");
if (!e.pointsPerAction || Object.keys(e.pointsPerAction).length === 0)
throw new Error("assertValidCreateLoyaltyProgramConfig: Points per action configuration is missing");
if (!e.metadata)
throw new Error("assertValidCreateLoyaltyProgramConfig: Metadata is undefined");
if (!e.metadata.organizationName || !e.metadata.organizationName.trim() || !e.metadata.organizationName.trim().length)
throw new Error("assertValidCreateLoyaltyProgramConfig: Organization name is undefined");
}
async function Ze(e, t) {
R(e), me(t);
const r = t.collectionSigner ?? T(e.umi), i = t.updateAuthority ?? T(e.umi);
try {
let a = t.metadataUri;
if (!a) {
if (!t.imageBuffer || !t.imageFilename)
throw new Error("Either metadataUri or imageBuffer with imageFilename must be provided");
const d = await g(e, t.imageBuffer, t.imageFilename, t.imageContentType);
a = await W(e, {
collectionName: t.voucherCollectionName,
merchantName: t.metadata.merchantName,
description: t.description || `Voucher collection for ${t.metadata.merchantName}`,
imageUri: d,
creator: t.programAuthority,
mimeType: t.imageContentType
});
}
const s = p(
e.umi,
e.umi.identity.publicKey,
"CREATE_VOUCHER_COLLECTION"
), n = await O(e.umi, {
collection: r,
name: t.voucherCollectionName,
plugins: ce(t, i.publicKey),
uri: a,
updateAuthority: i.publicKey
}).add(s).add(
D(e.umi, {
collection: r.publicKey,
authority: i,
key: {
__kind: y.APP_DATA,
fields: [
{
__kind: "Address",
address: i.publicKey
}
]
},
data: new TextEncoder().encode(JSON.stringify(H))
})
).sendAndConfirm(e.umi, {
confirm: { commitment: "confirmed" }
});
return { collection: r, signature: P(n.signature), updateAuthority: i };
} catch (a) {
throw new Error(`Failed to create voucher collection: ${a}`);
}
}
function ce(e, t) {
return [
{
type: y.ATTRIBUTES,
attributeList: [
{ key: h.PROGRAM_TYPE, value: "voucher" },
{ key: h.CREATOR, value: e.programAuthority.toString() },
{ key: h.METADATA, value: JSON.stringify(e.metadata) },
{ key: "voucherTypes", value: JSON.stringify(e.metadata.voucherTypes) },
{ key: "merchantId", value: e.metadata.merchantAddress }
]
},
{
type: y.PERMANENT_TRANSFER_DELEGATE,
authority: {
address: e.programAuthority,
type: "Address"
}
},
{
type: y.UPDATE_DELEGATE,
authority: {
address: t,
type: "Address"
},
additionalDelegates: []
},
{
type: y.APP_DATA,
dataAuthority: {
type: "Address",
address: t
},
schema: v.Json
}
];
}
function me(e) {
if (!e)
throw new Error("assertValidCreateVoucherCollectionConfig: Config is undefined");
if (!e.voucherCollectionName || !e.voucherCollectionName.trim() || !e.voucherCollectionName.trim().length)
throw new Error("assertValidCreateVoucherCollectionConfig: Voucher collection name is undefined");
if (e.metadataUri) {
if (!e.metadataUri.trim() || !e.metadataUri.trim().length)
throw new Error("assertValidCreateVoucherCollectionConfig: Metadata URI is empty");
if (!e.metadataUri.startsWith("https://") && !e.metadataUri.startsWith("http://"))
throw new Error("assertValidCreateVoucherCollectionConfig: Metadata URI is not a valid URL");
} else {
if (!e.imageBuffer)
throw new Error(
"assertValidCreateVoucherCollectionConfig: Image buffer is required when metadataUri is not provided"
);
if (!e.imageFilename || !e.imageFilename.trim())
throw new Error(
"assertValidCreateVoucherCollectionConfig: Image filename is required when metadataUri is not provided"
);
}
if (!e.programAuthority)
throw new Error("assertValidCreateVoucherCollectionConfig: Program authority is undefined");
if (!e.metadata)
throw new Error("assertValidCreateVoucherCollectionConfig: Metadata is undefined");
if (!e.metadata.merchantName || !e.metadata.merchantName.trim() || !e.metadata.merchantName.trim().length)
throw new Error("assertValidCreateVoucherCollectionConfig: Merchant name is undefined");
if (!e.metadata.merchantAddress || !e.metadata.merchantAddress.trim() || !e.metadata.merchantAddress.trim().length)
throw new Error("assertValidCreateVoucherCollectionConfig: Merchant address is undefined");
if (!e.metadata.voucherTypes || !Array.isArray(e.metadata.voucherTypes) || e.metadata.voucherTypes.length === 0)
throw new Error("assertValidCreateVoucherCollectionConfig: Voucher types are undefined or empty");
}
async function Qe(e, t) {
ye(t);
try {
const r = t.assetSigner ?? T(e.umi);
let i = t.voucherMetadataUri;
if (!i) {
if (!t.imageBuffer || !t.imageFilename)
throw new Error("Either voucherMetadataUri or imageBuffer with imageFilename must be provided");
const d = await g(e, t.imageBuffer, t.imageFilename, t.imageContentType);
i = await X(e, {
voucherName: t.voucherName,
voucherData: t.voucherData,
imageUri: d,
creator: t.updateAuthority.publicKey,
mimeType: t.imageContentType
});
}
const a = p(e.umi, e.umi.identity.publicKey, "LOYALTY_OPERATIONS"), s = {
type: t.voucherData.type,
value: t.voucherData.value,
description: t.voucherData.description,
expiryDate: t.voucherData.expiryDate,
maxUses: t.voucherData.maxUses,
currentUses: 0,
transferable: t.voucherData.transferable ?? !1,
status: "active",
issuedAt: Date.now(),
merchantId: t.voucherData.merchantId,
conditions: t.voucherData.conditions || [],
redemptionHistory: []
}, n = await S(e.umi, {
asset: r,
name: t.voucherName,
uri: i,
owner: t.recipient,
authority: t.updateAuthority,
collection: {
publicKey: t.collectionAddress
},
plugins: [
{
type: y.APP_DATA,
dataAuthority: {
type: "Address",
address: t.updateAuthority.publicKey
},
schema: v.Json
},
{
type: y.ATTRIBUTES,
attributeList: [
{ key: h.TYPE, value: "voucher" },
{ key: "voucherType", value: t.voucherData.type },
{ key: "merchantId", value: t.voucherData.merchantId },
{ key: "transferable", value: s.transferable.toString() }
]
},
{
type: y.FREEZE_DELEGATE,
authority: {
type: "Address",
address: t.updateAuthority.publicKey
},
frozen: !s.transferable
// Freeze if non-transferable
}
]
}).add(a).add(
E(e.umi, {
key: {
type: y.APP_DATA,
dataAuthority: {
type: "Address",
address: t.updateAuthority.publicKey
}
},
authority: t.updateAuthority,
data: new TextEncoder().encode(JSON.stringify(s)),
asset: L(r.publicKey),
collection: t.collectionAddress
})
).sendAndConfirm(e.umi, { confirm: { commitment: "confirmed" } });
return {
asset: r,
signature: P(n.signature),
voucherAddress: r.publicKey
};
} catch (r) {
throw new Error(`Failed to mint voucher: ${r}`);
}
}
function ye(e) {
if (!e)
throw new Error("assertValidMintVoucherConfig: Config is undefined");
if (!e.collectionAddress)
throw new Error("assertValidMintVoucherConfig: Collection address is undefined");
if (!e.recipient)
throw new Error("assertValidMintVoucherConfig: Recipient is undefined");
if (!e.voucherName || !e.voucherName.trim() || !e.voucherName.trim().length)
throw new Error("assertValidMintVoucherConfig: Voucher name is undefined");
if (!e.updateAuthority)
throw new Error("assertValidMintVoucherConfig: Update authority is undefined");
if (!e.voucherData)
throw new Error("assertValidMintVoucherConfig: Voucher data is undefined");
if (!e.voucherData.type)
throw new Error("assertValidMintVoucherConfig: Voucher type is undefined");
if (typeof e.voucherData.value != "number" || e.voucherData.value < 0)
throw new Error("assertValidMintVoucherConfig: Voucher value must be a non-negative number");
if (!e.voucherData.description || !e.voucherData.description.trim())
throw new Error("assertValidMintVoucherConfig: Voucher description is undefined");
if (typeof e.voucherData.expiryDate != "number" || e.voucherData.expiryDate <= Date.now())
throw new Error("assertValidMintVoucherConfig: Voucher expiry date must be in the future");
if (typeof e.voucherData.maxUses != "number" || e.voucherData.maxUses <= 0)
throw new Error("assertValidMintVoucherConfig: Voucher max uses must be a positive number");
if (!e.voucherData.merchantId || !e.voucherData.merchantId.trim())
throw new Error("assertValidMintVoucherConfig: Merchant ID is undefined");
if (e.voucherMetadataUri) {
if (!e.voucherMetadataUri.trim() || !e.voucherMetadataUri.trim().length)
throw new Error("assertValidMintVoucherConfig: Voucher metadata URI is empty");
if (!e.voucherMetadataUri.startsWith("https://") && !e.voucherMetadataUri.startsWith("http://"))
throw new Error("assertValidMintVoucherConfig: Voucher metadata URI is not a valid URL");
} else {
if (!e.imageBuffer)
throw new Error("assertValidMintVoucherConfig: Image buffer is required when voucherMetadataUri is not provided");
if (!e.imageFilename || !e.imageFilename.trim())
throw new Error(
"assertValidMintVoucherConfig: Image filename is required when voucherMetadataUri is not provided"
);
}
}
async function B(e, t) {
var i;
const r = {
errors: []
};
try {
const s = (i = (await f(e.umi, t.voucherAddress)).appDatas) == null ? void 0 : i[0];
if (!s || !s.data)
return r.errors.push("Voucher data not found"), r;
const o = s.data;
return r.voucher = o, r;
} catch (a) {
return r.errors.push(`Failed to fetch voucher: ${a}`), r;
}
}
async function et(e, t) {
const r = {
success: !1,
validation: { errors: [] },
errors: []
};
try {
const i = await B(e, { voucherAddress: t.voucherAddress });
if (r.validation = i, !i.voucher)
return r.errors.push("Voucher validation failed"), r;
if (i.voucher.merchantId !== t.merchantId)
return r.errors.push("Voucher is not valid for merchant " + t.merchantId), r;
const a = await f(e.umi, t.voucherAddress), s = b(a);
console.log("Collection address:", s);
const o = he(i.voucher, t.redemptionAmount);
r.redemptionValue = o;
const n = pe(i.voucher, o, t.redemptionDetails);
r.updatedVoucher = n;
const d = p(e.umi, e.umi.identity.publicKey, "LOYALTY_OPERATIONS"), c = await E(e.umi, {
key: {
type: y.APP_DATA,
dataAuthority: {
type: "Address",
address: t.updateAuthority.publicKey
}
},
authority: t.updateAuthority,
data: new TextEncoder().encode(JSON.stringify(n)),
asset: t.voucherAddress,
collection: s
}).add(d).sendAndConfirm(e.umi, { confirm: { commitment: "confirmed" } });
return r.success = !0, r.signature = P(c.signature), r;
} catch (i) {
return r.errors.push(`Failed to redeem voucher: ${i}`), r;
}
}
function he(e, t) {
switch (e.type) {
case "percentage_off":
if (!t)
throw new Error("Redemption amount required for percentage_off voucher");
return t * e.value / 100;
case "fixed_verxio_credits":
return e.value;
case "free_item":
return e.value;
// Could represent item value or just 1 for quantity
case "buy_one_get_one":
return e.value;
// Could represent value of free item
case "custom_reward":
return e.value;
// Custom value as defined
default:
return 0;
}
}
function pe(e, t, r) {
const i = {
...e,
currentUses: e.currentUses + 1,
usedAt: Date.now()
};
i.currentUses >= i.maxUses && (i.status = "used");
const a = {
timestamp: Date.now(),
redemptionValue: t,
transactionId: r == null ? void 0 : r.transactionId,
items: r == null ? void 0 : r.items,
totalAmount: r == null ? void 0 : r.totalAmount,
discountApplied: r == null ? void 0 : r.discountApplied,
creditsUsed: r == null ? void 0 : r.creditsUsed
};
return i.redemptionHistory || (i.redemptionHistory = []), i.redemptionHistory.push(a), i;
}
function tt(e) {
switch (e.type) {
case "percentage_off":
return `${e.value}% off`;
case "fixed_verxio_credits":
return `${e.value} Verxio Credits`;
case "free_item":
return `Free ${e.description}`;
case "buy_one_get_one":
return "Buy One Get One Free";
case "custom_reward":
return e.description;
default:
return e.description;
}
}
function rt(e) {
return e.maxUses > 1 && e.currentUses < e.maxUses;
}
function at(e) {
return Math.max(0, e.maxUses - e.currentUses);
}
async function it(e, t) {
var i;
const r = {
success: !1,
errors: []
};
try {
const a = await f(e.umi, t.voucherAddress), s = b(a), o = (i = a.appDatas) == null ? void 0 : i[0];
if (!o || !o.data)
return r.errors.push("Voucher data not found"), r;
const n = o.data;
if (n.status === "cancelled")
return r.errors.push("Voucher is already cancelled"), r;
if (n.status === "used")
return r.errors.push("Cannot cancel a fully used voucher"), r;
const d = {
...n,
status: "cancelled",
usedAt: Date.now()
};
r.updatedVoucher = d;
const u = p(e.umi, e.umi.identity.publicKey, "LOYALTY_OPERATIONS"), m = await E(e.umi, {
key: {
type: y.APP_DATA,
dataAuthority: {
type: "Address",
address: t.updateAuthority.publicKey
}
},
authority: t.updateAuthority,
data: new TextEncoder().encode(JSON.stringify(d)),
asset: t.voucherAddress,
collection: s
}).add(u).sendAndConfirm(e.umi, { confirm: { commitment: "confirmed" } });
return r.success = !0, r.signature = P(m.signature), r;
} catch (a) {
return r.errors.push(`Failed to cancel voucher: ${a}`), r;
}
}
async function st(e, t) {
var i;
Ae(t);
const r = {
success: !1,
errors: []
};
try {
const a = await f(e.umi, t.voucherAddress), s = b(a), o = (i = a.appDatas) == null ? void 0 : i[0];
if (!o || !o.data)
return r.errors.push("Voucher data not found"), r;
const n = o.data;
if (n.status === "cancelled")
return r.errors.push("Cannot extend expiry of a cancelled voucher"), r;
if (n.status === "used")
return r.errors.push("Cannot extend expiry of a fully used voucher"), r;
if (t.newExpiryDate <= Date.now())
return r.errors.push("New expiry date must be in the future"), r;
if (t.newExpiryDate <= n.expiryDate)
return r.errors.push("New expiry date must be later than current expiry date"), r;
r.previousExpiryDate = n.expiryDate;
const d = {
...n,
expiryDate: t.newExpiryDate,
// If voucher was expired, reactivate it
status: n.status === "expired" ? "active" : n.status
};
r.updatedVoucher = d;
const u = p(e.umi, e.umi.identity.publicKey, "LOYALTY_OPERATIONS"), m = await E(e.umi, {
key: {
type: y.APP_DATA,
dataAuthority: {
type: "Address",
address: t.updateAuthority.publicKey
}
},
authority: t.updateAuthority,
data: new TextEncoder().encode(JSON.stringify(d)),
asset: t.voucherAddress,
collection: s
}).add(u).sendAndConfirm(e.umi, { confirm: { commitment: "confirmed" } });
return r.success = !0, r.signature = P(m.signature), r;
} catch (a) {
return r.errors.push(`Failed to extend voucher expiry: ${a}`), r;
}
}
function we(e) {
const t = Date.now(), r = e.expiryDate - t;
if (r <= 0)
return {
isExpired: !0,
timeRemaining: 0,
daysRemaining: 0,
hoursRemaining: 0,
minutesRemaining: 0
};
const a = Math.floor(r / (24 * 60 * 60 * 1e3)), s = Math.floor(r % (24 * 60 * 60 * 1e3) / (60 * 60 * 1e3)), o = Math.floor(r % (60 * 60 * 1e3) / (60 * 1e3));
return {
isExpired: !1,
timeRemaining: r,
daysRemaining: a,
hoursRemaining: s,
minutesRemaining: o
};
}
function ot(e, t = 7) {
const { daysRemaining: r } = we(e);
return r <= t && r > 0;
}
function Ae(e) {
if (!e)
throw new Error("assertValidExtendVoucherExpiryConfig: Config is undefined");
if (!e.voucherAddress)
throw new Error("assertValidExtendVoucherExpiryConfig: Voucher address is undefined");
if (!e.updateAuthority)
throw new Error("assertValidExtendVoucherExpiryConfig: Update authority is undefined");
if (!e.newExpiryDate || e.newExpiryDate <= 0)
throw new Error("assertValidExtendVoucherExpiryConfig: New expiry date must be a positive number");
}
async function j(e, t) {
var r;
try {
const a = (await x(e.umi, t.userAddress)).filter((c) => {
const m = c.attributes;
if (!m || !m.attributeList) return !1;
const l = m.attributeList.find((w) => w.key === h.TYPE);
return l && l.value === "voucher";
}), s = [], o = {
activeVouchers: 0,
expiredVouchers: 0,
usedVouchers: 0,
cancelledVouchers: 0,
totalValue: 0,
byType: {},
byMerchant: {}
};
for (const c of a)
try {
const m = (r = c.appDatas) == null ? void 0 : r[0];
if (!m || !m.data) continue;
const l = m.data, w = Date.now(), A = l.expiryDate <= w, C = l.status === "active" && !A && l.currentUses < l.maxUses, k = Math.max(0, l.maxUses - l.currentUses), I = Math.max(0, l.expiryDate - w);
if (t.collectionAddress && c.collection !== t.collectionAddress || t.merchantId && l.merchantId !== t.merchantId || t.status && l.status !== t.status || t.voucherType && l.type !== t.voucherType) continue;
const Z = {
voucherAddress: c.publicKey,
voucherData: l,
collectionAddress: c.collection,
name: c.name,
uri: c.uri,
isExpired: A,
canRedeem: C,
remainingUses: k,
timeUntilExpiry: I
};
switch (s.push(Z), l.status) {
case "active":
A ? o.expiredVouchers++ : o.activeVouchers++;
break;
case "used":
o.usedVouchers++;
break;
case "cancelled":
o.cancelledVouchers++;
break;
case "expired":
o.expiredVouchers++;
break;
}
o.totalValue += l.value, o.byType[l.type] = (o.byType[l.type] || 0) + 1, o.byMerchant[l.merchantId] = (o.byMerchant[l.merchantId] || 0) + 1;
} catch (m) {
console.warn("Error processing voucher asset:", m);
continue;
}
s.sort((c, m) => c.voucherData.status === "active" && m.voucherData.status !== "active" ? -1 : c.voucherData.status !== "active" && m.voucherData.status === "active" ? 1 : c.voucherData.expiryDate - m.voucherData.expiryDate);
const n = t.offset || 0, d = t.limit || 50;
return {
vouchers: s.slice(n, n + d),
totalCount: s.length,
hasMore: n + d < s.length,
summary: o
};
} catch (i) {
throw new Error(`Failed to get user vouchers: ${i}`);
}
}
async function nt(e, t) {
const r = await j(e, t), a = (t.expiryWarningDays || 7) * 24 * 60 * 60 * 1e3;
return r.vouchers.filter(
(s) => s.voucherData.status === "active" && s.timeUntilExpiry > 0 && s.timeUntilExpiry <= a
);
}
async function dt(e, t) {
return (await j(e, t)).vouchers.filter((i) => i.canRedeem);
}
function N(e) {
if (!e.collectionAddress)
throw new Error("Collection not initialized");
}
async function V(e, t) {
var a, s;
N(e);
const i = (s = (a = (await U(e.umi, e.collectionAddress)).attributes) == null ? void 0 : a.attributeList.find((o) => o.key === t)) == null ? void 0 : s.value;
return i ? JSON.parse(i) : null;
}
async function _(e, t) {
return (await V(e, h.TIERS) || []).reduce((i, a) => t >= a.xpRequired && (!i || a.xpRequired > i.xpRequired) ? a : i, F);
}
async function M(e, t, r, i, a) {
const s = p(e.umi, e.umi.identity.publicKey, "VERXIO_INTERACTION"), o = {
xp: a.xp,
lastAction: a.action,
currentTier: a.newTier.name,
tierUpdatedAt: a.newTier.name !== a.currentData.currentTier ? Date.now() : a.currentData.tierUpdatedAt,
actionHistory: a.currentData.actionHistory || [],
messageHistory: a.messageHistory || a.currentData.messageHistory || [],
rewards: a.newTier.rewards || []
}, n = new TextEncoder().encode(JSON.stringify(o)), u = await E(e.umi, {
key: {
type: y.APP_DATA,
dataAuthority: i.dataAuthority
},
authority: r,
data: n,
asset: t,
collection: e.collectionAddress
}).add(s).sendAndConfirm(e.umi, { confirm: { commitment: "confirmed" } });
return {
points: a.xp,
signature: P(u.signature)
};
}
async function fe(e, t, r, i, a) {
const s = p(e.umi, e.umi.identity.publicKey, "VERXIO_INTERACTION"), o = {
broadcasts: a.broadcasts,
totalBroadcasts: a.totalBroadcasts
}, n = new TextEncoder().encode(JSON.stringify(o)), u = await D(e.umi, {
collection: t,
authority: r,
key: {
__kind: y.APP_DATA,
fields: [
{
__kind: "Address",
address: i.dataAuthority.address
}
]
},
data: n
}).add(s).sendAndConfirm(e.umi, {
confirm: { commitment: "confirmed" }
});
return {
signature: P(u.signature)
};
}
async function ut(e, t) {
try {
Ee(t);
const r = t.assetSigner ?? T(e.umi);
let i = t.passMetadataUri;
if (!i) {
if (!t.imageBuffer || !t.imageFilename)
throw new Error("Either passMetadataUri or imageBuffer with imageFilename must be provided");
const n = await g(e, t.imageBuffer, t.imageFilename, t.imageContentType);
i = await z(e, {
passName: t.passName,
organizationName: t.organizationName,
imageUri: n,
creator: t.updateAuthority.publicKey,
mimeType: t.imageContentType
});
}
const a = p(e.umi, e.umi.identity.publicKey, "LOYALTY_OPERATIONS"), o = await S(e.umi, {
asset: r,
name: t.passName,
uri: i,
owner: t.recipient,
authority: t.updateAuthority,
collection: {
publicKey: t.collectionAddress
},
plugins: [
{
type: y.APP_DATA,
dataAuthority: {
type: "Address",
address: t.updateAuthority.publicKey
},
schema: v.Json
},
{
type: y.ATTRIBUTES,
attributeList: [{ key: h.TYPE, value: `${t.passName} loyalty pass` }]
}
]
}).add(a).add(
E(e.umi, {
key: {
type: y.APP_DATA,
dataAuthority: {
type: "Address",
address: t.updateAuthority.publicKey
}
},
authority: t.updateAuthority,
data: new TextEncoder().encode(JSON.stringify($)),
asset: L(r.publicKey),
collection: t.collectionAddress
})
).sendAndConfirm(e.umi, { confirm: { commitment: "confirmed" } });
return {
asset: r,
signature: P(o.signature)
};
} catch (r) {
throw console.error("Error in issueLoyaltyPass:", r), new Error(`Failed to issue loyalty pass: ${r}`);
}
}
function Ee(e) {
if (!e)
throw console.error("Validation failed: config is undefined"), new Error("assertValidIssueLoyaltyPassConfig: Config is undefined");
if (!e.collectionAddress)
throw console.error("Validation failed: collectionAddress is undefined", e), new Error("assertValidIssueLoyaltyPassConfig: Collection address is undefined");
if (!e.recipient)
throw console.error("Validation failed: recipient is undefined", e), new Error("assertValidIssueLoyaltyPassConfig: Recipient is undefined");
if (!e.passName || !e.passName.trim() || !e.passName.trim().length)
throw console.error("Validation failed: passName is invalid", e), new Error("assertValidIssueLoyaltyPassConfig: Pass name is undefined");
if (!e.organizationName || !e.organizationName.trim())
throw console.error("Validation failed: organizationName is invalid", e), new Error("assertValidIssueLoyaltyPassConfig: Organization name is undefined");
if (e.passMetadataUri) {
if (!e.passMetadataUri.trim() || !e.passMetadataUri.trim().length)
throw console.error("Validation failed: passMetadataUri is empty", e), new Error("assertValidIssueLoyaltyPassConfig: Pass metadata URI is empty");
if (!e.passMetadataUri.startsWith("https://") && !e.passMetadataUri.startsWith("http://"))
throw console.error("Validation failed: passMetadataUri is not a valid URL", e), new Error("assertValidIssueLoyaltyPassConfig: Pass metadata URI is not a valid URL");
} else {
if (!e.imageBuffer)
throw console.error("Validation failed: imageBuffer is required", e), new Error(
"assertValidIssueLoyaltyPassConfig: Image buffer is required when passMetadataUri is not provided"
);
if (!e.imageFilename || !e.imageFilename.trim())
throw console.error("Validation failed: imageFilename is required", e), new Error(
"assertValidIssueLoyaltyPassConfig: Image filename is required when passMetadataUri is not provided"
);
}
}
async function lt(e, t) {
var r, i;
try {
let a;
try {
a = await f(e.umi, t.passAddress), console.log("asset", a);
} catch {
throw new Error("Failed to award points: Pass not found");
}
const s = (r = a.appDatas) == null ? void 0 : r[0];
if (!s)
throw new Error("AppData plugin not found");
const o = s.data || {}, n = o.xp || 0, d = await V(e, h.POINTS_PER_ACTION);
if (!d || Object.keys(d).length === 0 || Object.values(d).every((l) => l === 0))
throw new Error("Points per action configuration not found");
if (!d[t.action] || d[t.action] === 0)
throw new Error(`Action '${t.action}' is not defined in points per action configuration`);
const u = d[t.action] * (t.multiplier || 1), c = n + u, m = await _(e, c);
try {
const l = await M(e, t.passAddress, t.signer, s, {
xp: c,
action: t.action,
points: u,
currentData: o,
newTier: m
});
return {
points: c,
signature: l.signature
};
} catch (l) {
throw (i = l.message) != null && i.includes("Invalid Authority") ? new Error("Failed to award points: Signer is not the pass owner") : l;
}
} catch (a) {
throw a instanceof Error ? a : new Error(`Failed to award points: ${a}`);
}
}
async function ct(e, t) {
var r;
Ce(t);
try {
const i = await f(e.umi, t.passAddress);
if (!i)
throw new Error("Failed to gift points: Pass not found");
if (!V(e, h.TYPE))
throw new Error("Pass does not belong to a collection");
const s = (r = i.appDatas) == null ? void 0 : r[0];
if (!s)
throw new Error("AppData plugin not found");
const o = s.data || {}, d = (o.xp || 0) + t.pointsToGift, u = await _(e, d), c = await M(e, t.passAddress, t.signer, s, {
xp: d,
action: t.action,
points: t.pointsToGift,
currentData: o,
newTier: u
});
return {
points: d,
signature: c.signature,
newTier: u
};
} catch (i) {
throw i instanceof Error ? i : new Error(`Failed to gift points: ${i}`);
}
}
function Ce(e) {
if (!e)
throw new Error("assertValidGiftLoyaltyPointsConfig: Config is undefined");
if (!e.passAddress)
throw new Error("assertValidGiftLoyaltyPointsConfig: Pass address is undefined");
if (typeof e.pointsToGift != "number" || e.pointsToGift <= 0)
throw new Error("assertValidGiftLoyaltyPointsConfig: Points to gift must be a positive number");
if (!e.signer)
throw new Error("assertValidGiftLoyaltyPointsConfig: Signer is undefined");
if (!e.action)
throw new Error("assertValidGiftLoyaltyPointsConfig: Action is undefined");
}
async function mt(e, t) {
var r, i;
Ve(t);
try {
let a;
try {
a = await f(e.umi, t.passAddress);
} catch {
throw new Error("Failed to revoke points: Pass not found");
}
if (!a)
throw new Error("Failed to revoke points: Pass not found");
if (!V(e, h.TYPE))
throw new Error("Pass does not belong to a collection");
const o = (r = a.appDatas) == null ? void 0 : r[0];
if (!o)
throw new Error("AppData plugin not found");
const n = o.data || {}, d = n.xp || 0, u = Math.max(0, d - t.pointsToRevoke), c = d - u, m = await _(e, u);
try {
const l = await M(e, t.passAddress, t.signer, o, {
xp: u,
action: "revoke",
points: -c,
currentData: n,
newTier: m
});
return {
points: u,
signature: l.signature,
newTier: m
};
} catch (l) {
throw console.error("Error in updatePassData:", l), (i = l.message) != null && i.includes("Invalid Authority") ? new Error("Failed to revoke points: Signer is not the pass owner") : l;
}
} catch (a) {
throw a instanceof Error ? a : new Error(`Failed to revoke points: ${a}`);
}
}
function Ve(e) {
if (!e)
throw new Error("assertValidRevokeLoyaltyPointsConfig: Config is undefined");
if (!e.passAddress)
throw new Error("assertValidRevokeLoyaltyPointsConfig: Pass address is undefined");
if (typeof e.pointsToRevoke != "number" || e.pointsToRevoke <= 0)
throw new Error("assertValidRevokeLoyaltyPointsConfig: Points to revoke must be a positive number");
if (!e.signer)
throw new Error("assertValidRevokeLoyaltyPointsConfig: Signer is undefined");
}
async function yt(e, t) {
R(e), Ie(t);
try {
const r = await V(e, h.TIERS), i = await V(e, h.POINTS_PER_ACTION);
if (!r || !i)
throw new Error("Failed to update loyalty program: Current configuration not found");
let a = r;
t.newTiers && (a = Te(r, t.newTiers));
let s = i;
t.newPointsPerAction && (s = Pe(i, t.newPointsPerAction));
const o = p(e.umi, e.umi.identity.publicKey, "LOYALTY_OPERATIONS"), d = await G(e.umi, {
collection: t.collectionAddress,
plugin: {
type: y.ATTRIBUTES,
attributeList: [
{ key: h.PROGRAM_TYPE, value: "loyalty" },
{ key: h.TIERS, value: JSON.stringify(a) },
{ key: h.POINTS_PER_ACTION, value: JSON.stringify(s) },
{ key: h.CREATOR, value: t.programAuthority.toString() }
]
},
authority: t.updateAuthority
}).add(o).sendAndConfirm(e.umi, { confirm: { commitment: "confirmed" } });
return { signature: P(d.signature) };
} catch (r) {
throw new Error(`Failed to update loyalty program: ${r}`);
}
}
function Te(e, t) {
if (!e.find((o) => o.name === "Grind"))
throw new Error("Grind tier must exist and cannot be removed");
const i = new Map(e.map((o) => [o.name, o])), a = t.map((o) => {
const n = i.get(o.name);
return n ? {
...n,
xpRequired: o.xpRequired,
rewards: o.rewards
} : o;
}), s = a.findIndex((o) => o.name === "Grind");
if (s !== 0) {
const o = a.splice(s, 1)[0];
a.unshift(o);
}
if (!a.find((o) => o.name === "Grind"))
throw new Error("Grind tier must exist");
return a;
}
function Pe(e, t) {
const r = { ...e };
return Object.entries(t).forEach(([i, a]) => {
if (a < 0)
throw new Error(`Points for action '${i}' cannot be negative`);
r[i] = a;
}), r;
}
function Ie(e) {
if (!e)
throw new Error("assertValidUpdateLoyaltyProgramConfig: Config is undefined");
if (!e.collectionAddress)
throw new Error("assertValidUpdateLoyaltyProgramConfig: Collection address is undefined");
if (!e.programAuthority)
throw new Error("assertValidUpdateLoyaltyProgramConfig: Program authority is undefined");
if (!e.updateAuthority)
throw new Error("assertValidUpdateLoyaltyProgramConfig: Update authority is undefined");
if (e.newTiers && !Array.isArray(e.newTiers))
throw new Error("assertValidUpdateLoyaltyProgramConfig: New tiers must be an array");
if (e.newPointsPerAction && typeof e.newPointsPerAction != "object")
throw new Error("assertValidUpdateLoyaltyProgramConfig: New points per action must be an object");
}
async function ht(e, t) {
var r;
ve(t);
try {
const i = await f(e.umi, t.passAddress);
if (!i)
throw new Error("Failed to send message: Pass not found");
const a = (r = i.appDatas) == null ? void 0 : r[0];
if (!a)
throw new Error("AppData plugin not found");
const s = a.data || {}, o = s.messageHistory || [], n = {
id: crypto.randomUUID(),
content: t.message,
sender: t.sender.toString(),
timestamp: Date.now(),
read: !1
};
return {
signature: (await M(e, t.passAddress, t.signer, a, {
xp: s.xp,
action: "message",
points: 0,
currentData: {
...s,
messageHistory: [...o, n]
},
newTier: {
name: s.currentTier,
rewards: s.rewards
}
})).signature,
message: n
};
} catch (i) {
throw i instanceof Error ? i : new Error(`Failed to send message: ${i}`);
}
}
function ve(e) {
if (!e)
throw new Error("assertValidSendMessageConfig: Config is undefined");
if (!e.passAddress)
throw new Error("assertValidSendMessageConfig: Pass address is undefined");
if (!e.message || !e.message.trim())
throw new Error("assertValidSendMessageConfig: Message is undefined or empty");
if (!e.sender)
throw new Error("assertValidSendMessageConfig: Sender is undefined");
if (!e.signer)
throw new Error("assertValidSendMessageConfig: Signer is undefined");
}
async function pt(e, t) {
var r;
ge(t);
try {
const i = await U(e.umi, t.collectionAddress);
if (!i)
throw new Error("Failed to send broadcast: Collection not found");
const a = (r = i.appDatas) == null ? void 0 : r[0];
if (console.log("appDataPlugin", a), !a)
throw new Error("AppData plugin not found");
const s = a.data, o = s.broadcasts, n = {
id: crypto.randomUUID(),
content: t.message,
sender: t.sender.toString(),
timestamp: Date.now(),
read: !1
};
return {
signature: (await fe(e, t.collectionAddress, t.signer, a, {
totalBroadcasts: (s.totalBroadcasts || 0) + 1,
broadcasts: [...o, n]
})).signature,
broadcast: n
};
} catch (i) {
throw i instanceof Error ? i : new Error(`Failed to send broadcast: ${i}`);
}
}
function ge(e) {
if (!e)
throw new Error("assertValidSendBroadcastConfig: Config is undefined");
if (!e.collectionAddress)
throw new Error("assertValidSendBroadcastConfig: Collection address is undefined");
if (!e.message || !e.message.trim())
throw new Error("assertValidSendBroadcastConfig: Message is undefined or empty");
if (!e.sender)
throw new Error("assertValidSendBroadcastConfig: Sender is undefined");
if (!e.signer)
throw new Error("assertValidSendBroadcastConfig: Signer is undefined");
}
async function wt(e) {
N(e);
try {
return await V(e, h.TIERS) || [];
} catch (t) {
throw new Error(`Failed to fetch program tiers: ${t}`);
}
}
async function At(e) {
var t, r, i, a, s, o;
N(e);
try {
const n = await U(e.umi, e.collectionAddress), d = ((t = n.attributes) == null ? void 0 : t.attributeList) || [], u = d.find((I) => I.key === h.TIERS), c = d.find((I) => I.key === h.POINTS_PER_ACTION), m = d.find((I) => I.key === h.METADATA), l = u ? JSON.parse(u.value) : [], w = c ? JSON.parse(c.value) : {}, A = m ? JSON.parse(m.value) : {}, C = (r = n.appDatas) == null ? void 0 : r[0], k = C == null ? void 0 : C.data;
return {
name: n.name,
uri: n.uri,
collectionAddress: n.publicKey,
updateAuthority: n.updateAuthority,
numMinted: n.numMinted,
transferAuthority: (a = (i = n.permanentTransferDelegate) == null ? void 0 : i.authority.address) == null ? void 0 : a.toString(),
creator: (o = (s = n.attributes) == null ? void 0 : s.attributeList.find((I) => I.key === h.CREATOR)) == null ? void 0 : o.value,
tiers: l,
pointsPerAction: w,
metadata: A,
broadcasts: k
};
} catch (n) {
throw new Error(`Failed to fetch program details: ${n}`);
}
}
async function ft(e, t) {
var r, i, a, s;
try {
const o = await f(e.umi, t), n = (r = o.appDatas) == null ? void 0 : r.find((C) => C.type === y.APP_DATA);
if (!n || !n.data)