@b3dotfun/sdk 0.1.65-alpha.3 → 0.1.65-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -69,5 +69,10 @@ export interface AnySpendCollectorClubPurchaseProps {
69
69
  * Force fiat payment
70
70
  */
71
71
  forceFiatPayment?: boolean;
72
+ /**
73
+ * Optional discount code to apply to the purchase.
74
+ * When provided, validates on-chain and adjusts the price accordingly.
75
+ */
76
+ discountCode?: string;
72
77
  }
73
- export declare function AnySpendCollectorClubPurchase({ loadOrder, mode, activeTab, packId, packAmount, pricePerPack, paymentToken, recipientAddress, spenderAddress, isStaging, onSuccess, header, showRecipient, vendingMachineId, packType, forceFiatPayment, }: AnySpendCollectorClubPurchaseProps): import("react/jsx-runtime").JSX.Element;
78
+ export declare function AnySpendCollectorClubPurchase({ loadOrder, mode, activeTab, packId, packAmount, pricePerPack, paymentToken, recipientAddress, spenderAddress, isStaging, onSuccess, header, showRecipient, vendingMachineId, packType, forceFiatPayment, discountCode, }: AnySpendCollectorClubPurchaseProps): import("react/jsx-runtime").JSX.Element;
@@ -29,27 +29,22 @@ const jsx_runtime_1 = require("react/jsx-runtime");
29
29
  * ```
30
30
  */
31
31
  const constants_1 = require("../../../anyspend/constants");
32
+ const constants_2 = require("../../../shared/constants");
32
33
  const number_1 = require("../../../shared/utils/number");
33
34
  const react_1 = require("react");
34
35
  const viem_1 = require("viem");
36
+ const chains_1 = require("viem/chains");
35
37
  const AnySpendCustom_1 = require("./AnySpendCustom");
38
+ const ccShopAbi_1 = require("./ccShopAbi");
36
39
  // Collector Club Shop contract addresses on Base
37
40
  const CC_SHOP_ADDRESS = "0x47366E64E4917dd4DdC04Fb9DC507c1dD2b87294";
38
41
  const CC_SHOP_ADDRESS_STAGING = "0x8b751143342ac41eB965E55430e3F7Adf6BE01fA";
39
42
  const BASE_CHAIN_ID = 8453;
40
- // ABI for buyPacksFor function only
41
- const BUY_PACKS_FOR_ABI = {
42
- inputs: [
43
- { internalType: "address", name: "user", type: "address" },
44
- { internalType: "uint256", name: "packId", type: "uint256" },
45
- { internalType: "uint256", name: "amount", type: "uint256" },
46
- ],
47
- name: "buyPacksFor",
48
- outputs: [],
49
- stateMutability: "nonpayable",
50
- type: "function",
51
- };
52
- function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activeTab = "crypto", packId, packAmount, pricePerPack, paymentToken = constants_1.USDC_BASE, recipientAddress, spenderAddress, isStaging = false, onSuccess, header, showRecipient = true, vendingMachineId, packType, forceFiatPayment, }) {
43
+ const basePublicClient = (0, viem_1.createPublicClient)({
44
+ chain: chains_1.base,
45
+ transport: (0, viem_1.http)(constants_2.PUBLIC_BASE_RPC_URL),
46
+ });
47
+ function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activeTab = "crypto", packId, packAmount, pricePerPack, paymentToken = constants_1.USDC_BASE, recipientAddress, spenderAddress, isStaging = false, onSuccess, header, showRecipient = true, vendingMachineId, packType, forceFiatPayment, discountCode, }) {
53
48
  const ccShopAddress = isStaging ? CC_SHOP_ADDRESS_STAGING : CC_SHOP_ADDRESS;
54
49
  // Calculate total amount needed (pricePerPack * packAmount)
55
50
  const totalAmount = (0, react_1.useMemo)(() => {
@@ -61,33 +56,167 @@ function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activeTab =
61
56
  return "0";
62
57
  }
63
58
  }, [pricePerPack, packAmount]);
64
- // Calculate fiat amount (totalAmount in USD, assuming USDC with 6 decimals)
59
+ // Discount code validation state
60
+ const [discountInfo, setDiscountInfo] = (0, react_1.useState)({
61
+ isValid: false,
62
+ discountAmount: BigInt(0),
63
+ minPurchaseAmount: BigInt(0),
64
+ isLoading: false,
65
+ error: null,
66
+ });
67
+ // Validate discount code on-chain when provided
68
+ (0, react_1.useEffect)(() => {
69
+ if (!discountCode) {
70
+ setDiscountInfo({
71
+ isValid: false,
72
+ discountAmount: BigInt(0),
73
+ minPurchaseAmount: BigInt(0),
74
+ isLoading: false,
75
+ error: null,
76
+ });
77
+ return;
78
+ }
79
+ let cancelled = false;
80
+ const validateDiscount = async () => {
81
+ setDiscountInfo(prev => ({ ...prev, isLoading: true, error: null }));
82
+ try {
83
+ // Validate against specific pack and fetch full details in parallel
84
+ const [validForPack, codeDetails] = await Promise.all([
85
+ basePublicClient.readContract({
86
+ address: ccShopAddress,
87
+ abi: [ccShopAbi_1.IS_DISCOUNT_CODE_VALID_FOR_PACK_ABI],
88
+ functionName: "isDiscountCodeValidForPack",
89
+ args: [discountCode, BigInt(packId)],
90
+ }),
91
+ basePublicClient.readContract({
92
+ address: ccShopAddress,
93
+ abi: [ccShopAbi_1.GET_DISCOUNT_CODE_ABI],
94
+ functionName: "getDiscountCode",
95
+ args: [discountCode],
96
+ }),
97
+ ]);
98
+ if (cancelled)
99
+ return;
100
+ const [isValid, discountAmount] = validForPack;
101
+ const { minPurchaseAmount, packId: restrictedPackId, exists } = codeDetails;
102
+ if (!exists) {
103
+ setDiscountInfo({
104
+ isValid: false,
105
+ discountAmount: BigInt(0),
106
+ minPurchaseAmount: BigInt(0),
107
+ isLoading: false,
108
+ error: "Discount code does not exist",
109
+ });
110
+ return;
111
+ }
112
+ if (!isValid) {
113
+ // Provide specific error based on code details
114
+ if (restrictedPackId !== BigInt(0) && restrictedPackId !== BigInt(packId)) {
115
+ setDiscountInfo({
116
+ isValid: false,
117
+ discountAmount: BigInt(0),
118
+ minPurchaseAmount: BigInt(0),
119
+ isLoading: false,
120
+ error: "Discount code is not valid for this pack",
121
+ });
122
+ }
123
+ else {
124
+ setDiscountInfo({
125
+ isValid: false,
126
+ discountAmount: BigInt(0),
127
+ minPurchaseAmount: BigInt(0),
128
+ isLoading: false,
129
+ error: "Invalid or expired discount code",
130
+ });
131
+ }
132
+ return;
133
+ }
134
+ setDiscountInfo({ isValid: true, discountAmount, minPurchaseAmount, isLoading: false, error: null });
135
+ }
136
+ catch (error) {
137
+ if (cancelled)
138
+ return;
139
+ console.error("Failed to validate discount code", { discountCode, error });
140
+ setDiscountInfo({
141
+ isValid: false,
142
+ discountAmount: BigInt(0),
143
+ minPurchaseAmount: BigInt(0),
144
+ isLoading: false,
145
+ error: "Failed to validate discount code",
146
+ });
147
+ }
148
+ };
149
+ validateDiscount();
150
+ return () => {
151
+ cancelled = true;
152
+ };
153
+ }, [discountCode, ccShopAddress, packId]);
154
+ // Calculate effective dstAmount after discount
155
+ const effectiveDstAmount = (0, react_1.useMemo)(() => {
156
+ if (!discountCode || !discountInfo.isValid || discountInfo.discountAmount === BigInt(0)) {
157
+ return totalAmount;
158
+ }
159
+ const total = BigInt(totalAmount);
160
+ const discount = discountInfo.discountAmount;
161
+ if (discount >= total) {
162
+ console.error("Discount exceeds total price", { totalAmount, discountAmount: discount.toString() });
163
+ return "0";
164
+ }
165
+ return (total - discount).toString();
166
+ }, [totalAmount, discountCode, discountInfo.isValid, discountInfo.discountAmount]);
167
+ // Calculate fiat amount (effectiveDstAmount in USD, assuming USDC with 6 decimals)
65
168
  const srcFiatAmount = (0, react_1.useMemo)(() => {
66
- if (!totalAmount || totalAmount === "0")
169
+ if (!effectiveDstAmount || effectiveDstAmount === "0")
67
170
  return "0";
68
- return (0, number_1.formatUnits)(totalAmount, constants_1.USDC_BASE.decimals);
69
- }, [totalAmount]);
70
- // Encode the buyPacksFor function call
171
+ return (0, number_1.formatUnits)(effectiveDstAmount, constants_1.USDC_BASE.decimals);
172
+ }, [effectiveDstAmount]);
173
+ // Encode the contract function call (with or without discount)
71
174
  const encodedData = (0, react_1.useMemo)(() => {
72
175
  try {
176
+ if (discountCode && discountInfo.isValid) {
177
+ return (0, viem_1.encodeFunctionData)({
178
+ abi: [ccShopAbi_1.BUY_PACKS_FOR_WITH_DISCOUNT_ABI],
179
+ functionName: "buyPacksForWithDiscount",
180
+ args: [recipientAddress, BigInt(packId), BigInt(packAmount), discountCode],
181
+ });
182
+ }
73
183
  return (0, viem_1.encodeFunctionData)({
74
- abi: [BUY_PACKS_FOR_ABI],
184
+ abi: [ccShopAbi_1.BUY_PACKS_FOR_ABI],
75
185
  functionName: "buyPacksFor",
76
186
  args: [recipientAddress, BigInt(packId), BigInt(packAmount)],
77
187
  });
78
188
  }
79
189
  catch (error) {
80
- console.error("Failed to encode function data", { recipientAddress, packId, packAmount, error });
190
+ console.error("Failed to encode function data", { recipientAddress, packId, packAmount, discountCode, error });
81
191
  return "0x";
82
192
  }
83
- }, [recipientAddress, packId, packAmount]);
193
+ }, [recipientAddress, packId, packAmount, discountCode, discountInfo.isValid]);
84
194
  // Default header if not provided
85
195
  const defaultHeader = () => ((0, jsx_runtime_1.jsx)("div", { className: "mb-4 flex flex-col items-center gap-3 text-center", children: (0, jsx_runtime_1.jsxs)("div", { children: [(0, jsx_runtime_1.jsx)("h1", { className: "text-as-primary text-xl font-bold", children: "Buy Collector Club Packs" }), (0, jsx_runtime_1.jsxs)("p", { className: "text-as-secondary text-sm", children: ["Purchase ", packAmount, " pack", packAmount !== 1 ? "s" : "", " using any token"] })] }) }));
86
- return ((0, jsx_runtime_1.jsx)(AnySpendCustom_1.AnySpendCustom, { loadOrder: loadOrder, mode: mode, activeTab: activeTab, recipientAddress: recipientAddress, spenderAddress: spenderAddress ?? ccShopAddress, orderType: "custom", dstChainId: BASE_CHAIN_ID, dstToken: paymentToken, dstAmount: totalAmount, contractAddress: ccShopAddress, encodedData: encodedData, metadata: {
196
+ // Don't render AnySpendCustom while discount is being validated (avoids showing wrong price)
197
+ if (discountCode && discountInfo.isLoading) {
198
+ return ((0, jsx_runtime_1.jsx)("div", { className: "mb-4 flex flex-col items-center gap-3 text-center", children: (0, jsx_runtime_1.jsx)("p", { className: "text-as-secondary text-sm", children: "Validating discount code..." }) }));
199
+ }
200
+ if (discountCode && discountInfo.error) {
201
+ return ((0, jsx_runtime_1.jsx)("div", { className: "mb-4 flex flex-col items-center gap-3 text-center", children: (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-red-500", children: discountInfo.error }) }));
202
+ }
203
+ if (discountCode &&
204
+ discountInfo.isValid &&
205
+ discountInfo.minPurchaseAmount > BigInt(0) &&
206
+ BigInt(packAmount) < discountInfo.minPurchaseAmount) {
207
+ return ((0, jsx_runtime_1.jsx)("div", { className: "mb-4 flex flex-col items-center gap-3 text-center", children: (0, jsx_runtime_1.jsxs)("p", { className: "text-sm text-red-500", children: ["Minimum purchase of ", discountInfo.minPurchaseAmount.toString(), " pack", discountInfo.minPurchaseAmount > BigInt(1) ? "s" : "", " required for this discount code"] }) }));
208
+ }
209
+ if (discountCode && discountInfo.isValid && effectiveDstAmount === "0") {
210
+ return ((0, jsx_runtime_1.jsx)("div", { className: "mb-4 flex flex-col items-center gap-3 text-center", children: (0, jsx_runtime_1.jsx)("p", { className: "text-sm text-red-500", children: "Discount exceeds total price" }) }));
211
+ }
212
+ return ((0, jsx_runtime_1.jsx)(AnySpendCustom_1.AnySpendCustom, { loadOrder: loadOrder, mode: mode, activeTab: activeTab, recipientAddress: recipientAddress, spenderAddress: spenderAddress ?? ccShopAddress, orderType: "custom", dstChainId: BASE_CHAIN_ID, dstToken: paymentToken, dstAmount: effectiveDstAmount, contractAddress: ccShopAddress, encodedData: encodedData, metadata: {
87
213
  packId,
88
214
  packAmount,
89
215
  pricePerPack,
90
216
  vendingMachineId,
91
217
  packType,
218
+ ...(discountCode && discountInfo.isValid
219
+ ? { discountCode, discountAmount: discountInfo.discountAmount.toString() }
220
+ : {}),
92
221
  }, header: header || defaultHeader, onSuccess: onSuccess, showRecipient: showRecipient, srcFiatAmount: srcFiatAmount, forceFiatPayment: forceFiatPayment }));
93
222
  }
@@ -0,0 +1,113 @@
1
+ export declare const BUY_PACKS_FOR_ABI: {
2
+ readonly inputs: readonly [{
3
+ readonly internalType: "address";
4
+ readonly name: "user";
5
+ readonly type: "address";
6
+ }, {
7
+ readonly internalType: "uint256";
8
+ readonly name: "packId";
9
+ readonly type: "uint256";
10
+ }, {
11
+ readonly internalType: "uint256";
12
+ readonly name: "amount";
13
+ readonly type: "uint256";
14
+ }];
15
+ readonly name: "buyPacksFor";
16
+ readonly outputs: readonly [];
17
+ readonly stateMutability: "nonpayable";
18
+ readonly type: "function";
19
+ };
20
+ export declare const BUY_PACKS_FOR_WITH_DISCOUNT_ABI: {
21
+ readonly inputs: readonly [{
22
+ readonly internalType: "address";
23
+ readonly name: "user";
24
+ readonly type: "address";
25
+ }, {
26
+ readonly internalType: "uint256";
27
+ readonly name: "packId";
28
+ readonly type: "uint256";
29
+ }, {
30
+ readonly internalType: "uint256";
31
+ readonly name: "amount";
32
+ readonly type: "uint256";
33
+ }, {
34
+ readonly internalType: "string";
35
+ readonly name: "discountCode";
36
+ readonly type: "string";
37
+ }];
38
+ readonly name: "buyPacksForWithDiscount";
39
+ readonly outputs: readonly [];
40
+ readonly stateMutability: "nonpayable";
41
+ readonly type: "function";
42
+ };
43
+ export declare const IS_DISCOUNT_CODE_VALID_FOR_PACK_ABI: {
44
+ readonly inputs: readonly [{
45
+ readonly internalType: "string";
46
+ readonly name: "code";
47
+ readonly type: "string";
48
+ }, {
49
+ readonly internalType: "uint256";
50
+ readonly name: "packId";
51
+ readonly type: "uint256";
52
+ }];
53
+ readonly name: "isDiscountCodeValidForPack";
54
+ readonly outputs: readonly [{
55
+ readonly internalType: "bool";
56
+ readonly name: "isValid";
57
+ readonly type: "bool";
58
+ }, {
59
+ readonly internalType: "uint256";
60
+ readonly name: "discountAmount";
61
+ readonly type: "uint256";
62
+ }];
63
+ readonly stateMutability: "view";
64
+ readonly type: "function";
65
+ };
66
+ export declare const GET_DISCOUNT_CODE_ABI: {
67
+ readonly inputs: readonly [{
68
+ readonly internalType: "string";
69
+ readonly name: "code";
70
+ readonly type: "string";
71
+ }];
72
+ readonly name: "getDiscountCode";
73
+ readonly outputs: readonly [{
74
+ readonly components: readonly [{
75
+ readonly internalType: "uint256";
76
+ readonly name: "discountAmount";
77
+ readonly type: "uint256";
78
+ }, {
79
+ readonly internalType: "uint256";
80
+ readonly name: "expiresAt";
81
+ readonly type: "uint256";
82
+ }, {
83
+ readonly internalType: "bool";
84
+ readonly name: "used";
85
+ readonly type: "bool";
86
+ }, {
87
+ readonly internalType: "bool";
88
+ readonly name: "exists";
89
+ readonly type: "bool";
90
+ }, {
91
+ readonly internalType: "uint256";
92
+ readonly name: "maxUses";
93
+ readonly type: "uint256";
94
+ }, {
95
+ readonly internalType: "uint256";
96
+ readonly name: "usedCount";
97
+ readonly type: "uint256";
98
+ }, {
99
+ readonly internalType: "uint256";
100
+ readonly name: "packId";
101
+ readonly type: "uint256";
102
+ }, {
103
+ readonly internalType: "uint256";
104
+ readonly name: "minPurchaseAmount";
105
+ readonly type: "uint256";
106
+ }];
107
+ readonly internalType: "struct CCShop.DiscountCode";
108
+ readonly name: "";
109
+ readonly type: "tuple";
110
+ }];
111
+ readonly stateMutability: "view";
112
+ readonly type: "function";
113
+ };
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ // CCShop contract ABI fragments used by AnySpendCollectorClubPurchase
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.GET_DISCOUNT_CODE_ABI = exports.IS_DISCOUNT_CODE_VALID_FOR_PACK_ABI = exports.BUY_PACKS_FOR_WITH_DISCOUNT_ABI = exports.BUY_PACKS_FOR_ABI = void 0;
5
+ exports.BUY_PACKS_FOR_ABI = {
6
+ inputs: [
7
+ { internalType: "address", name: "user", type: "address" },
8
+ { internalType: "uint256", name: "packId", type: "uint256" },
9
+ { internalType: "uint256", name: "amount", type: "uint256" },
10
+ ],
11
+ name: "buyPacksFor",
12
+ outputs: [],
13
+ stateMutability: "nonpayable",
14
+ type: "function",
15
+ };
16
+ exports.BUY_PACKS_FOR_WITH_DISCOUNT_ABI = {
17
+ inputs: [
18
+ { internalType: "address", name: "user", type: "address" },
19
+ { internalType: "uint256", name: "packId", type: "uint256" },
20
+ { internalType: "uint256", name: "amount", type: "uint256" },
21
+ { internalType: "string", name: "discountCode", type: "string" },
22
+ ],
23
+ name: "buyPacksForWithDiscount",
24
+ outputs: [],
25
+ stateMutability: "nonpayable",
26
+ type: "function",
27
+ };
28
+ exports.IS_DISCOUNT_CODE_VALID_FOR_PACK_ABI = {
29
+ inputs: [
30
+ { internalType: "string", name: "code", type: "string" },
31
+ { internalType: "uint256", name: "packId", type: "uint256" },
32
+ ],
33
+ name: "isDiscountCodeValidForPack",
34
+ outputs: [
35
+ { internalType: "bool", name: "isValid", type: "bool" },
36
+ { internalType: "uint256", name: "discountAmount", type: "uint256" },
37
+ ],
38
+ stateMutability: "view",
39
+ type: "function",
40
+ };
41
+ exports.GET_DISCOUNT_CODE_ABI = {
42
+ inputs: [{ internalType: "string", name: "code", type: "string" }],
43
+ name: "getDiscountCode",
44
+ outputs: [
45
+ {
46
+ components: [
47
+ { internalType: "uint256", name: "discountAmount", type: "uint256" },
48
+ { internalType: "uint256", name: "expiresAt", type: "uint256" },
49
+ { internalType: "bool", name: "used", type: "bool" },
50
+ { internalType: "bool", name: "exists", type: "bool" },
51
+ { internalType: "uint256", name: "maxUses", type: "uint256" },
52
+ { internalType: "uint256", name: "usedCount", type: "uint256" },
53
+ { internalType: "uint256", name: "packId", type: "uint256" },
54
+ { internalType: "uint256", name: "minPurchaseAmount", type: "uint256" },
55
+ ],
56
+ internalType: "struct CCShop.DiscountCode",
57
+ name: "",
58
+ type: "tuple",
59
+ },
60
+ ],
61
+ stateMutability: "view",
62
+ type: "function",
63
+ };
@@ -470,6 +470,8 @@ export interface AnySpendCollectorClubPurchaseProps extends BaseModalProps {
470
470
  forceFiatPayment?: boolean;
471
471
  /** Staging environment support */
472
472
  isStaging?: boolean;
473
+ /** Optional discount code to apply to the purchase */
474
+ discountCode?: string;
473
475
  }
474
476
  /**
475
477
  * Props for the AnySpend Deposit modal
@@ -69,5 +69,10 @@ export interface AnySpendCollectorClubPurchaseProps {
69
69
  * Force fiat payment
70
70
  */
71
71
  forceFiatPayment?: boolean;
72
+ /**
73
+ * Optional discount code to apply to the purchase.
74
+ * When provided, validates on-chain and adjusts the price accordingly.
75
+ */
76
+ discountCode?: string;
72
77
  }
73
- export declare function AnySpendCollectorClubPurchase({ loadOrder, mode, activeTab, packId, packAmount, pricePerPack, paymentToken, recipientAddress, spenderAddress, isStaging, onSuccess, header, showRecipient, vendingMachineId, packType, forceFiatPayment, }: AnySpendCollectorClubPurchaseProps): import("react/jsx-runtime").JSX.Element;
78
+ export declare function AnySpendCollectorClubPurchase({ loadOrder, mode, activeTab, packId, packAmount, pricePerPack, paymentToken, recipientAddress, spenderAddress, isStaging, onSuccess, header, showRecipient, vendingMachineId, packType, forceFiatPayment, discountCode, }: AnySpendCollectorClubPurchaseProps): import("react/jsx-runtime").JSX.Element;
@@ -26,27 +26,22 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
26
26
  * ```
27
27
  */
28
28
  import { USDC_BASE } from "../../../anyspend/constants/index.js";
29
+ import { PUBLIC_BASE_RPC_URL } from "../../../shared/constants/index.js";
29
30
  import { formatUnits } from "../../../shared/utils/number.js";
30
- import { useMemo } from "react";
31
- import { encodeFunctionData } from "viem";
31
+ import { useEffect, useMemo, useState } from "react";
32
+ import { createPublicClient, encodeFunctionData, http } from "viem";
33
+ import { base } from "viem/chains";
32
34
  import { AnySpendCustom } from "./AnySpendCustom.js";
35
+ import { BUY_PACKS_FOR_ABI, BUY_PACKS_FOR_WITH_DISCOUNT_ABI, GET_DISCOUNT_CODE_ABI, IS_DISCOUNT_CODE_VALID_FOR_PACK_ABI, } from "./ccShopAbi.js";
33
36
  // Collector Club Shop contract addresses on Base
34
37
  const CC_SHOP_ADDRESS = "0x47366E64E4917dd4DdC04Fb9DC507c1dD2b87294";
35
38
  const CC_SHOP_ADDRESS_STAGING = "0x8b751143342ac41eB965E55430e3F7Adf6BE01fA";
36
39
  const BASE_CHAIN_ID = 8453;
37
- // ABI for buyPacksFor function only
38
- const BUY_PACKS_FOR_ABI = {
39
- inputs: [
40
- { internalType: "address", name: "user", type: "address" },
41
- { internalType: "uint256", name: "packId", type: "uint256" },
42
- { internalType: "uint256", name: "amount", type: "uint256" },
43
- ],
44
- name: "buyPacksFor",
45
- outputs: [],
46
- stateMutability: "nonpayable",
47
- type: "function",
48
- };
49
- export function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activeTab = "crypto", packId, packAmount, pricePerPack, paymentToken = USDC_BASE, recipientAddress, spenderAddress, isStaging = false, onSuccess, header, showRecipient = true, vendingMachineId, packType, forceFiatPayment, }) {
40
+ const basePublicClient = createPublicClient({
41
+ chain: base,
42
+ transport: http(PUBLIC_BASE_RPC_URL),
43
+ });
44
+ export function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activeTab = "crypto", packId, packAmount, pricePerPack, paymentToken = USDC_BASE, recipientAddress, spenderAddress, isStaging = false, onSuccess, header, showRecipient = true, vendingMachineId, packType, forceFiatPayment, discountCode, }) {
50
45
  const ccShopAddress = isStaging ? CC_SHOP_ADDRESS_STAGING : CC_SHOP_ADDRESS;
51
46
  // Calculate total amount needed (pricePerPack * packAmount)
52
47
  const totalAmount = useMemo(() => {
@@ -58,15 +53,130 @@ export function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activ
58
53
  return "0";
59
54
  }
60
55
  }, [pricePerPack, packAmount]);
61
- // Calculate fiat amount (totalAmount in USD, assuming USDC with 6 decimals)
56
+ // Discount code validation state
57
+ const [discountInfo, setDiscountInfo] = useState({
58
+ isValid: false,
59
+ discountAmount: BigInt(0),
60
+ minPurchaseAmount: BigInt(0),
61
+ isLoading: false,
62
+ error: null,
63
+ });
64
+ // Validate discount code on-chain when provided
65
+ useEffect(() => {
66
+ if (!discountCode) {
67
+ setDiscountInfo({
68
+ isValid: false,
69
+ discountAmount: BigInt(0),
70
+ minPurchaseAmount: BigInt(0),
71
+ isLoading: false,
72
+ error: null,
73
+ });
74
+ return;
75
+ }
76
+ let cancelled = false;
77
+ const validateDiscount = async () => {
78
+ setDiscountInfo(prev => ({ ...prev, isLoading: true, error: null }));
79
+ try {
80
+ // Validate against specific pack and fetch full details in parallel
81
+ const [validForPack, codeDetails] = await Promise.all([
82
+ basePublicClient.readContract({
83
+ address: ccShopAddress,
84
+ abi: [IS_DISCOUNT_CODE_VALID_FOR_PACK_ABI],
85
+ functionName: "isDiscountCodeValidForPack",
86
+ args: [discountCode, BigInt(packId)],
87
+ }),
88
+ basePublicClient.readContract({
89
+ address: ccShopAddress,
90
+ abi: [GET_DISCOUNT_CODE_ABI],
91
+ functionName: "getDiscountCode",
92
+ args: [discountCode],
93
+ }),
94
+ ]);
95
+ if (cancelled)
96
+ return;
97
+ const [isValid, discountAmount] = validForPack;
98
+ const { minPurchaseAmount, packId: restrictedPackId, exists } = codeDetails;
99
+ if (!exists) {
100
+ setDiscountInfo({
101
+ isValid: false,
102
+ discountAmount: BigInt(0),
103
+ minPurchaseAmount: BigInt(0),
104
+ isLoading: false,
105
+ error: "Discount code does not exist",
106
+ });
107
+ return;
108
+ }
109
+ if (!isValid) {
110
+ // Provide specific error based on code details
111
+ if (restrictedPackId !== BigInt(0) && restrictedPackId !== BigInt(packId)) {
112
+ setDiscountInfo({
113
+ isValid: false,
114
+ discountAmount: BigInt(0),
115
+ minPurchaseAmount: BigInt(0),
116
+ isLoading: false,
117
+ error: "Discount code is not valid for this pack",
118
+ });
119
+ }
120
+ else {
121
+ setDiscountInfo({
122
+ isValid: false,
123
+ discountAmount: BigInt(0),
124
+ minPurchaseAmount: BigInt(0),
125
+ isLoading: false,
126
+ error: "Invalid or expired discount code",
127
+ });
128
+ }
129
+ return;
130
+ }
131
+ setDiscountInfo({ isValid: true, discountAmount, minPurchaseAmount, isLoading: false, error: null });
132
+ }
133
+ catch (error) {
134
+ if (cancelled)
135
+ return;
136
+ console.error("Failed to validate discount code", { discountCode, error });
137
+ setDiscountInfo({
138
+ isValid: false,
139
+ discountAmount: BigInt(0),
140
+ minPurchaseAmount: BigInt(0),
141
+ isLoading: false,
142
+ error: "Failed to validate discount code",
143
+ });
144
+ }
145
+ };
146
+ validateDiscount();
147
+ return () => {
148
+ cancelled = true;
149
+ };
150
+ }, [discountCode, ccShopAddress, packId]);
151
+ // Calculate effective dstAmount after discount
152
+ const effectiveDstAmount = useMemo(() => {
153
+ if (!discountCode || !discountInfo.isValid || discountInfo.discountAmount === BigInt(0)) {
154
+ return totalAmount;
155
+ }
156
+ const total = BigInt(totalAmount);
157
+ const discount = discountInfo.discountAmount;
158
+ if (discount >= total) {
159
+ console.error("Discount exceeds total price", { totalAmount, discountAmount: discount.toString() });
160
+ return "0";
161
+ }
162
+ return (total - discount).toString();
163
+ }, [totalAmount, discountCode, discountInfo.isValid, discountInfo.discountAmount]);
164
+ // Calculate fiat amount (effectiveDstAmount in USD, assuming USDC with 6 decimals)
62
165
  const srcFiatAmount = useMemo(() => {
63
- if (!totalAmount || totalAmount === "0")
166
+ if (!effectiveDstAmount || effectiveDstAmount === "0")
64
167
  return "0";
65
- return formatUnits(totalAmount, USDC_BASE.decimals);
66
- }, [totalAmount]);
67
- // Encode the buyPacksFor function call
168
+ return formatUnits(effectiveDstAmount, USDC_BASE.decimals);
169
+ }, [effectiveDstAmount]);
170
+ // Encode the contract function call (with or without discount)
68
171
  const encodedData = useMemo(() => {
69
172
  try {
173
+ if (discountCode && discountInfo.isValid) {
174
+ return encodeFunctionData({
175
+ abi: [BUY_PACKS_FOR_WITH_DISCOUNT_ABI],
176
+ functionName: "buyPacksForWithDiscount",
177
+ args: [recipientAddress, BigInt(packId), BigInt(packAmount), discountCode],
178
+ });
179
+ }
70
180
  return encodeFunctionData({
71
181
  abi: [BUY_PACKS_FOR_ABI],
72
182
  functionName: "buyPacksFor",
@@ -74,17 +184,36 @@ export function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activ
74
184
  });
75
185
  }
76
186
  catch (error) {
77
- console.error("Failed to encode function data", { recipientAddress, packId, packAmount, error });
187
+ console.error("Failed to encode function data", { recipientAddress, packId, packAmount, discountCode, error });
78
188
  return "0x";
79
189
  }
80
- }, [recipientAddress, packId, packAmount]);
190
+ }, [recipientAddress, packId, packAmount, discountCode, discountInfo.isValid]);
81
191
  // Default header if not provided
82
192
  const defaultHeader = () => (_jsx("div", { className: "mb-4 flex flex-col items-center gap-3 text-center", children: _jsxs("div", { children: [_jsx("h1", { className: "text-as-primary text-xl font-bold", children: "Buy Collector Club Packs" }), _jsxs("p", { className: "text-as-secondary text-sm", children: ["Purchase ", packAmount, " pack", packAmount !== 1 ? "s" : "", " using any token"] })] }) }));
83
- return (_jsx(AnySpendCustom, { loadOrder: loadOrder, mode: mode, activeTab: activeTab, recipientAddress: recipientAddress, spenderAddress: spenderAddress ?? ccShopAddress, orderType: "custom", dstChainId: BASE_CHAIN_ID, dstToken: paymentToken, dstAmount: totalAmount, contractAddress: ccShopAddress, encodedData: encodedData, metadata: {
193
+ // Don't render AnySpendCustom while discount is being validated (avoids showing wrong price)
194
+ if (discountCode && discountInfo.isLoading) {
195
+ return (_jsx("div", { className: "mb-4 flex flex-col items-center gap-3 text-center", children: _jsx("p", { className: "text-as-secondary text-sm", children: "Validating discount code..." }) }));
196
+ }
197
+ if (discountCode && discountInfo.error) {
198
+ return (_jsx("div", { className: "mb-4 flex flex-col items-center gap-3 text-center", children: _jsx("p", { className: "text-sm text-red-500", children: discountInfo.error }) }));
199
+ }
200
+ if (discountCode &&
201
+ discountInfo.isValid &&
202
+ discountInfo.minPurchaseAmount > BigInt(0) &&
203
+ BigInt(packAmount) < discountInfo.minPurchaseAmount) {
204
+ return (_jsx("div", { className: "mb-4 flex flex-col items-center gap-3 text-center", children: _jsxs("p", { className: "text-sm text-red-500", children: ["Minimum purchase of ", discountInfo.minPurchaseAmount.toString(), " pack", discountInfo.minPurchaseAmount > BigInt(1) ? "s" : "", " required for this discount code"] }) }));
205
+ }
206
+ if (discountCode && discountInfo.isValid && effectiveDstAmount === "0") {
207
+ return (_jsx("div", { className: "mb-4 flex flex-col items-center gap-3 text-center", children: _jsx("p", { className: "text-sm text-red-500", children: "Discount exceeds total price" }) }));
208
+ }
209
+ return (_jsx(AnySpendCustom, { loadOrder: loadOrder, mode: mode, activeTab: activeTab, recipientAddress: recipientAddress, spenderAddress: spenderAddress ?? ccShopAddress, orderType: "custom", dstChainId: BASE_CHAIN_ID, dstToken: paymentToken, dstAmount: effectiveDstAmount, contractAddress: ccShopAddress, encodedData: encodedData, metadata: {
84
210
  packId,
85
211
  packAmount,
86
212
  pricePerPack,
87
213
  vendingMachineId,
88
214
  packType,
215
+ ...(discountCode && discountInfo.isValid
216
+ ? { discountCode, discountAmount: discountInfo.discountAmount.toString() }
217
+ : {}),
89
218
  }, header: header || defaultHeader, onSuccess: onSuccess, showRecipient: showRecipient, srcFiatAmount: srcFiatAmount, forceFiatPayment: forceFiatPayment }));
90
219
  }