@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.
- package/dist/cjs/anyspend/react/components/AnySpendCollectorClubPurchase.d.ts +6 -1
- package/dist/cjs/anyspend/react/components/AnySpendCollectorClubPurchase.js +151 -22
- package/dist/cjs/anyspend/react/components/ccShopAbi.d.ts +113 -0
- package/dist/cjs/anyspend/react/components/ccShopAbi.js +63 -0
- package/dist/cjs/global-account/react/stores/useModalStore.d.ts +2 -0
- package/dist/esm/anyspend/react/components/AnySpendCollectorClubPurchase.d.ts +6 -1
- package/dist/esm/anyspend/react/components/AnySpendCollectorClubPurchase.js +152 -23
- package/dist/esm/anyspend/react/components/ccShopAbi.d.ts +113 -0
- package/dist/esm/anyspend/react/components/ccShopAbi.js +60 -0
- package/dist/esm/global-account/react/stores/useModalStore.d.ts +2 -0
- package/dist/types/anyspend/react/components/AnySpendCollectorClubPurchase.d.ts +6 -1
- package/dist/types/anyspend/react/components/ccShopAbi.d.ts +113 -0
- package/dist/types/global-account/react/stores/useModalStore.d.ts +2 -0
- package/package.json +1 -1
- package/src/anyspend/react/components/AnySpendCollectorClubPurchase.tsx +206 -22
- package/src/anyspend/react/components/ccShopAbi.ts +64 -0
- package/src/global-account/react/stores/useModalStore.ts +2 -0
|
@@ -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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
//
|
|
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 (!
|
|
169
|
+
if (!effectiveDstAmount || effectiveDstAmount === "0")
|
|
67
170
|
return "0";
|
|
68
|
-
return (0, number_1.formatUnits)(
|
|
69
|
-
}, [
|
|
70
|
-
// Encode the
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
//
|
|
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 (!
|
|
166
|
+
if (!effectiveDstAmount || effectiveDstAmount === "0")
|
|
64
167
|
return "0";
|
|
65
|
-
return formatUnits(
|
|
66
|
-
}, [
|
|
67
|
-
// Encode the
|
|
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
|
-
|
|
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
|
}
|