@b3dotfun/sdk 0.1.65-alpha.4 → 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.
@@ -35,46 +35,11 @@ const react_1 = require("react");
35
35
  const viem_1 = require("viem");
36
36
  const chains_1 = require("viem/chains");
37
37
  const AnySpendCustom_1 = require("./AnySpendCustom");
38
+ const ccShopAbi_1 = require("./ccShopAbi");
38
39
  // Collector Club Shop contract addresses on Base
39
40
  const CC_SHOP_ADDRESS = "0x47366E64E4917dd4DdC04Fb9DC507c1dD2b87294";
40
41
  const CC_SHOP_ADDRESS_STAGING = "0x8b751143342ac41eB965E55430e3F7Adf6BE01fA";
41
42
  const BASE_CHAIN_ID = 8453;
42
- // ABI for buyPacksFor function only
43
- const BUY_PACKS_FOR_ABI = {
44
- inputs: [
45
- { internalType: "address", name: "user", type: "address" },
46
- { internalType: "uint256", name: "packId", type: "uint256" },
47
- { internalType: "uint256", name: "amount", type: "uint256" },
48
- ],
49
- name: "buyPacksFor",
50
- outputs: [],
51
- stateMutability: "nonpayable",
52
- type: "function",
53
- };
54
- // ABI for buyPacksForWithDiscount function (with discount code)
55
- const BUY_PACKS_FOR_WITH_DISCOUNT_ABI = {
56
- inputs: [
57
- { internalType: "address", name: "user", type: "address" },
58
- { internalType: "uint256", name: "packId", type: "uint256" },
59
- { internalType: "uint256", name: "amount", type: "uint256" },
60
- { internalType: "string", name: "discountCode", type: "string" },
61
- ],
62
- name: "buyPacksForWithDiscount",
63
- outputs: [],
64
- stateMutability: "nonpayable",
65
- type: "function",
66
- };
67
- // ABI for isDiscountCodeValid view function
68
- const IS_DISCOUNT_CODE_VALID_ABI = {
69
- inputs: [{ internalType: "string", name: "code", type: "string" }],
70
- name: "isDiscountCodeValid",
71
- outputs: [
72
- { internalType: "bool", name: "isValid", type: "bool" },
73
- { internalType: "uint256", name: "discountAmount", type: "uint256" },
74
- ],
75
- stateMutability: "view",
76
- type: "function",
77
- };
78
43
  const basePublicClient = (0, viem_1.createPublicClient)({
79
44
  chain: chains_1.base,
80
45
  transport: (0, viem_1.http)(constants_2.PUBLIC_BASE_RPC_URL),
@@ -95,38 +60,78 @@ function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activeTab =
95
60
  const [discountInfo, setDiscountInfo] = (0, react_1.useState)({
96
61
  isValid: false,
97
62
  discountAmount: BigInt(0),
63
+ minPurchaseAmount: BigInt(0),
98
64
  isLoading: false,
99
65
  error: null,
100
66
  });
101
67
  // Validate discount code on-chain when provided
102
68
  (0, react_1.useEffect)(() => {
103
69
  if (!discountCode) {
104
- setDiscountInfo({ isValid: false, discountAmount: BigInt(0), isLoading: false, error: null });
70
+ setDiscountInfo({
71
+ isValid: false,
72
+ discountAmount: BigInt(0),
73
+ minPurchaseAmount: BigInt(0),
74
+ isLoading: false,
75
+ error: null,
76
+ });
105
77
  return;
106
78
  }
107
79
  let cancelled = false;
108
80
  const validateDiscount = async () => {
109
81
  setDiscountInfo(prev => ({ ...prev, isLoading: true, error: null }));
110
82
  try {
111
- const result = await basePublicClient.readContract({
112
- address: ccShopAddress,
113
- abi: [IS_DISCOUNT_CODE_VALID_ABI],
114
- functionName: "isDiscountCodeValid",
115
- args: [discountCode],
116
- });
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
+ ]);
117
98
  if (cancelled)
118
99
  return;
119
- const [isValid, discountAmount] = result;
120
- if (!isValid) {
100
+ const [isValid, discountAmount] = validForPack;
101
+ const { minPurchaseAmount, packId: restrictedPackId, exists } = codeDetails;
102
+ if (!exists) {
121
103
  setDiscountInfo({
122
104
  isValid: false,
123
105
  discountAmount: BigInt(0),
106
+ minPurchaseAmount: BigInt(0),
124
107
  isLoading: false,
125
- error: "Invalid or expired discount code",
108
+ error: "Discount code does not exist",
126
109
  });
127
110
  return;
128
111
  }
129
- setDiscountInfo({ isValid: true, discountAmount, isLoading: false, error: null });
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 });
130
135
  }
131
136
  catch (error) {
132
137
  if (cancelled)
@@ -135,6 +140,7 @@ function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activeTab =
135
140
  setDiscountInfo({
136
141
  isValid: false,
137
142
  discountAmount: BigInt(0),
143
+ minPurchaseAmount: BigInt(0),
138
144
  isLoading: false,
139
145
  error: "Failed to validate discount code",
140
146
  });
@@ -144,7 +150,7 @@ function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activeTab =
144
150
  return () => {
145
151
  cancelled = true;
146
152
  };
147
- }, [discountCode, ccShopAddress]);
153
+ }, [discountCode, ccShopAddress, packId]);
148
154
  // Calculate effective dstAmount after discount
149
155
  const effectiveDstAmount = (0, react_1.useMemo)(() => {
150
156
  if (!discountCode || !discountInfo.isValid || discountInfo.discountAmount === BigInt(0)) {
@@ -169,13 +175,13 @@ function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activeTab =
169
175
  try {
170
176
  if (discountCode && discountInfo.isValid) {
171
177
  return (0, viem_1.encodeFunctionData)({
172
- abi: [BUY_PACKS_FOR_WITH_DISCOUNT_ABI],
178
+ abi: [ccShopAbi_1.BUY_PACKS_FOR_WITH_DISCOUNT_ABI],
173
179
  functionName: "buyPacksForWithDiscount",
174
180
  args: [recipientAddress, BigInt(packId), BigInt(packAmount), discountCode],
175
181
  });
176
182
  }
177
183
  return (0, viem_1.encodeFunctionData)({
178
- abi: [BUY_PACKS_FOR_ABI],
184
+ abi: [ccShopAbi_1.BUY_PACKS_FOR_ABI],
179
185
  functionName: "buyPacksFor",
180
186
  args: [recipientAddress, BigInt(packId), BigInt(packAmount)],
181
187
  });
@@ -194,6 +200,12 @@ function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activeTab =
194
200
  if (discountCode && discountInfo.error) {
195
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 }) }));
196
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
+ }
197
209
  if (discountCode && discountInfo.isValid && effectiveDstAmount === "0") {
198
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" }) }));
199
211
  }
@@ -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
+ };
@@ -32,46 +32,11 @@ import { useEffect, useMemo, useState } from "react";
32
32
  import { createPublicClient, encodeFunctionData, http } from "viem";
33
33
  import { base } from "viem/chains";
34
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";
35
36
  // Collector Club Shop contract addresses on Base
36
37
  const CC_SHOP_ADDRESS = "0x47366E64E4917dd4DdC04Fb9DC507c1dD2b87294";
37
38
  const CC_SHOP_ADDRESS_STAGING = "0x8b751143342ac41eB965E55430e3F7Adf6BE01fA";
38
39
  const BASE_CHAIN_ID = 8453;
39
- // ABI for buyPacksFor function only
40
- const BUY_PACKS_FOR_ABI = {
41
- inputs: [
42
- { internalType: "address", name: "user", type: "address" },
43
- { internalType: "uint256", name: "packId", type: "uint256" },
44
- { internalType: "uint256", name: "amount", type: "uint256" },
45
- ],
46
- name: "buyPacksFor",
47
- outputs: [],
48
- stateMutability: "nonpayable",
49
- type: "function",
50
- };
51
- // ABI for buyPacksForWithDiscount function (with discount code)
52
- const BUY_PACKS_FOR_WITH_DISCOUNT_ABI = {
53
- inputs: [
54
- { internalType: "address", name: "user", type: "address" },
55
- { internalType: "uint256", name: "packId", type: "uint256" },
56
- { internalType: "uint256", name: "amount", type: "uint256" },
57
- { internalType: "string", name: "discountCode", type: "string" },
58
- ],
59
- name: "buyPacksForWithDiscount",
60
- outputs: [],
61
- stateMutability: "nonpayable",
62
- type: "function",
63
- };
64
- // ABI for isDiscountCodeValid view function
65
- const IS_DISCOUNT_CODE_VALID_ABI = {
66
- inputs: [{ internalType: "string", name: "code", type: "string" }],
67
- name: "isDiscountCodeValid",
68
- outputs: [
69
- { internalType: "bool", name: "isValid", type: "bool" },
70
- { internalType: "uint256", name: "discountAmount", type: "uint256" },
71
- ],
72
- stateMutability: "view",
73
- type: "function",
74
- };
75
40
  const basePublicClient = createPublicClient({
76
41
  chain: base,
77
42
  transport: http(PUBLIC_BASE_RPC_URL),
@@ -92,38 +57,78 @@ export function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activ
92
57
  const [discountInfo, setDiscountInfo] = useState({
93
58
  isValid: false,
94
59
  discountAmount: BigInt(0),
60
+ minPurchaseAmount: BigInt(0),
95
61
  isLoading: false,
96
62
  error: null,
97
63
  });
98
64
  // Validate discount code on-chain when provided
99
65
  useEffect(() => {
100
66
  if (!discountCode) {
101
- setDiscountInfo({ isValid: false, discountAmount: BigInt(0), isLoading: false, error: null });
67
+ setDiscountInfo({
68
+ isValid: false,
69
+ discountAmount: BigInt(0),
70
+ minPurchaseAmount: BigInt(0),
71
+ isLoading: false,
72
+ error: null,
73
+ });
102
74
  return;
103
75
  }
104
76
  let cancelled = false;
105
77
  const validateDiscount = async () => {
106
78
  setDiscountInfo(prev => ({ ...prev, isLoading: true, error: null }));
107
79
  try {
108
- const result = await basePublicClient.readContract({
109
- address: ccShopAddress,
110
- abi: [IS_DISCOUNT_CODE_VALID_ABI],
111
- functionName: "isDiscountCodeValid",
112
- args: [discountCode],
113
- });
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
+ ]);
114
95
  if (cancelled)
115
96
  return;
116
- const [isValid, discountAmount] = result;
117
- if (!isValid) {
97
+ const [isValid, discountAmount] = validForPack;
98
+ const { minPurchaseAmount, packId: restrictedPackId, exists } = codeDetails;
99
+ if (!exists) {
118
100
  setDiscountInfo({
119
101
  isValid: false,
120
102
  discountAmount: BigInt(0),
103
+ minPurchaseAmount: BigInt(0),
121
104
  isLoading: false,
122
- error: "Invalid or expired discount code",
105
+ error: "Discount code does not exist",
123
106
  });
124
107
  return;
125
108
  }
126
- setDiscountInfo({ isValid: true, discountAmount, isLoading: false, error: null });
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 });
127
132
  }
128
133
  catch (error) {
129
134
  if (cancelled)
@@ -132,6 +137,7 @@ export function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activ
132
137
  setDiscountInfo({
133
138
  isValid: false,
134
139
  discountAmount: BigInt(0),
140
+ minPurchaseAmount: BigInt(0),
135
141
  isLoading: false,
136
142
  error: "Failed to validate discount code",
137
143
  });
@@ -141,7 +147,7 @@ export function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activ
141
147
  return () => {
142
148
  cancelled = true;
143
149
  };
144
- }, [discountCode, ccShopAddress]);
150
+ }, [discountCode, ccShopAddress, packId]);
145
151
  // Calculate effective dstAmount after discount
146
152
  const effectiveDstAmount = useMemo(() => {
147
153
  if (!discountCode || !discountInfo.isValid || discountInfo.discountAmount === BigInt(0)) {
@@ -191,6 +197,12 @@ export function AnySpendCollectorClubPurchase({ loadOrder, mode = "modal", activ
191
197
  if (discountCode && discountInfo.error) {
192
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 }) }));
193
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
+ }
194
206
  if (discountCode && discountInfo.isValid && effectiveDstAmount === "0") {
195
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" }) }));
196
208
  }
@@ -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,60 @@
1
+ // CCShop contract ABI fragments used by AnySpendCollectorClubPurchase
2
+ export const BUY_PACKS_FOR_ABI = {
3
+ inputs: [
4
+ { internalType: "address", name: "user", type: "address" },
5
+ { internalType: "uint256", name: "packId", type: "uint256" },
6
+ { internalType: "uint256", name: "amount", type: "uint256" },
7
+ ],
8
+ name: "buyPacksFor",
9
+ outputs: [],
10
+ stateMutability: "nonpayable",
11
+ type: "function",
12
+ };
13
+ export const BUY_PACKS_FOR_WITH_DISCOUNT_ABI = {
14
+ inputs: [
15
+ { internalType: "address", name: "user", type: "address" },
16
+ { internalType: "uint256", name: "packId", type: "uint256" },
17
+ { internalType: "uint256", name: "amount", type: "uint256" },
18
+ { internalType: "string", name: "discountCode", type: "string" },
19
+ ],
20
+ name: "buyPacksForWithDiscount",
21
+ outputs: [],
22
+ stateMutability: "nonpayable",
23
+ type: "function",
24
+ };
25
+ export const IS_DISCOUNT_CODE_VALID_FOR_PACK_ABI = {
26
+ inputs: [
27
+ { internalType: "string", name: "code", type: "string" },
28
+ { internalType: "uint256", name: "packId", type: "uint256" },
29
+ ],
30
+ name: "isDiscountCodeValidForPack",
31
+ outputs: [
32
+ { internalType: "bool", name: "isValid", type: "bool" },
33
+ { internalType: "uint256", name: "discountAmount", type: "uint256" },
34
+ ],
35
+ stateMutability: "view",
36
+ type: "function",
37
+ };
38
+ export const GET_DISCOUNT_CODE_ABI = {
39
+ inputs: [{ internalType: "string", name: "code", type: "string" }],
40
+ name: "getDiscountCode",
41
+ outputs: [
42
+ {
43
+ components: [
44
+ { internalType: "uint256", name: "discountAmount", type: "uint256" },
45
+ { internalType: "uint256", name: "expiresAt", type: "uint256" },
46
+ { internalType: "bool", name: "used", type: "bool" },
47
+ { internalType: "bool", name: "exists", type: "bool" },
48
+ { internalType: "uint256", name: "maxUses", type: "uint256" },
49
+ { internalType: "uint256", name: "usedCount", type: "uint256" },
50
+ { internalType: "uint256", name: "packId", type: "uint256" },
51
+ { internalType: "uint256", name: "minPurchaseAmount", type: "uint256" },
52
+ ],
53
+ internalType: "struct CCShop.DiscountCode",
54
+ name: "",
55
+ type: "tuple",
56
+ },
57
+ ],
58
+ stateMutability: "view",
59
+ type: "function",
60
+ };
@@ -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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@b3dotfun/sdk",
3
- "version": "0.1.65-alpha.4",
3
+ "version": "0.1.65-alpha.5",
4
4
  "source": "src/index.ts",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "react-native": "./dist/cjs/index.native.js",
@@ -33,51 +33,18 @@ import React, { useEffect, useMemo, useState } from "react";
33
33
  import { createPublicClient, encodeFunctionData, http } from "viem";
34
34
  import { base } from "viem/chains";
35
35
  import { AnySpendCustom } from "./AnySpendCustom";
36
+ import {
37
+ BUY_PACKS_FOR_ABI,
38
+ BUY_PACKS_FOR_WITH_DISCOUNT_ABI,
39
+ GET_DISCOUNT_CODE_ABI,
40
+ IS_DISCOUNT_CODE_VALID_FOR_PACK_ABI,
41
+ } from "./ccShopAbi";
36
42
 
37
43
  // Collector Club Shop contract addresses on Base
38
44
  const CC_SHOP_ADDRESS = "0x47366E64E4917dd4DdC04Fb9DC507c1dD2b87294";
39
45
  const CC_SHOP_ADDRESS_STAGING = "0x8b751143342ac41eB965E55430e3F7Adf6BE01fA";
40
46
  const BASE_CHAIN_ID = 8453;
41
47
 
42
- // ABI for buyPacksFor function only
43
- const BUY_PACKS_FOR_ABI = {
44
- inputs: [
45
- { internalType: "address", name: "user", type: "address" },
46
- { internalType: "uint256", name: "packId", type: "uint256" },
47
- { internalType: "uint256", name: "amount", type: "uint256" },
48
- ],
49
- name: "buyPacksFor",
50
- outputs: [],
51
- stateMutability: "nonpayable",
52
- type: "function",
53
- } as const;
54
-
55
- // ABI for buyPacksForWithDiscount function (with discount code)
56
- const BUY_PACKS_FOR_WITH_DISCOUNT_ABI = {
57
- inputs: [
58
- { internalType: "address", name: "user", type: "address" },
59
- { internalType: "uint256", name: "packId", type: "uint256" },
60
- { internalType: "uint256", name: "amount", type: "uint256" },
61
- { internalType: "string", name: "discountCode", type: "string" },
62
- ],
63
- name: "buyPacksForWithDiscount",
64
- outputs: [],
65
- stateMutability: "nonpayable",
66
- type: "function",
67
- } as const;
68
-
69
- // ABI for isDiscountCodeValid view function
70
- const IS_DISCOUNT_CODE_VALID_ABI = {
71
- inputs: [{ internalType: "string", name: "code", type: "string" }],
72
- name: "isDiscountCodeValid",
73
- outputs: [
74
- { internalType: "bool", name: "isValid", type: "bool" },
75
- { internalType: "uint256", name: "discountAmount", type: "uint256" },
76
- ],
77
- stateMutability: "view",
78
- type: "function",
79
- } as const;
80
-
81
48
  const basePublicClient = createPublicClient({
82
49
  chain: base,
83
50
  transport: http(PUBLIC_BASE_RPC_URL),
@@ -193,11 +160,13 @@ export function AnySpendCollectorClubPurchase({
193
160
  const [discountInfo, setDiscountInfo] = useState<{
194
161
  isValid: boolean;
195
162
  discountAmount: bigint;
163
+ minPurchaseAmount: bigint;
196
164
  isLoading: boolean;
197
165
  error: string | null;
198
166
  }>({
199
167
  isValid: false,
200
168
  discountAmount: BigInt(0),
169
+ minPurchaseAmount: BigInt(0),
201
170
  isLoading: false,
202
171
  error: null,
203
172
  });
@@ -205,7 +174,13 @@ export function AnySpendCollectorClubPurchase({
205
174
  // Validate discount code on-chain when provided
206
175
  useEffect(() => {
207
176
  if (!discountCode) {
208
- setDiscountInfo({ isValid: false, discountAmount: BigInt(0), isLoading: false, error: null });
177
+ setDiscountInfo({
178
+ isValid: false,
179
+ discountAmount: BigInt(0),
180
+ minPurchaseAmount: BigInt(0),
181
+ isLoading: false,
182
+ error: null,
183
+ });
209
184
  return;
210
185
  }
211
186
 
@@ -215,34 +190,68 @@ export function AnySpendCollectorClubPurchase({
215
190
  setDiscountInfo(prev => ({ ...prev, isLoading: true, error: null }));
216
191
 
217
192
  try {
218
- const result = await basePublicClient.readContract({
219
- address: ccShopAddress as `0x${string}`,
220
- abi: [IS_DISCOUNT_CODE_VALID_ABI],
221
- functionName: "isDiscountCodeValid",
222
- args: [discountCode],
223
- });
193
+ // Validate against specific pack and fetch full details in parallel
194
+ const [validForPack, codeDetails] = await Promise.all([
195
+ basePublicClient.readContract({
196
+ address: ccShopAddress as `0x${string}`,
197
+ abi: [IS_DISCOUNT_CODE_VALID_FOR_PACK_ABI],
198
+ functionName: "isDiscountCodeValidForPack",
199
+ args: [discountCode, BigInt(packId)],
200
+ }),
201
+ basePublicClient.readContract({
202
+ address: ccShopAddress as `0x${string}`,
203
+ abi: [GET_DISCOUNT_CODE_ABI],
204
+ functionName: "getDiscountCode",
205
+ args: [discountCode],
206
+ }),
207
+ ]);
224
208
 
225
209
  if (cancelled) return;
226
210
 
227
- const [isValid, discountAmount] = result;
211
+ const [isValid, discountAmount] = validForPack;
212
+ const { minPurchaseAmount, packId: restrictedPackId, exists } = codeDetails;
228
213
 
229
- if (!isValid) {
214
+ if (!exists) {
230
215
  setDiscountInfo({
231
216
  isValid: false,
232
217
  discountAmount: BigInt(0),
218
+ minPurchaseAmount: BigInt(0),
233
219
  isLoading: false,
234
- error: "Invalid or expired discount code",
220
+ error: "Discount code does not exist",
235
221
  });
236
222
  return;
237
223
  }
238
224
 
239
- setDiscountInfo({ isValid: true, discountAmount, isLoading: false, error: null });
225
+ if (!isValid) {
226
+ // Provide specific error based on code details
227
+ if (restrictedPackId !== BigInt(0) && restrictedPackId !== BigInt(packId)) {
228
+ setDiscountInfo({
229
+ isValid: false,
230
+ discountAmount: BigInt(0),
231
+ minPurchaseAmount: BigInt(0),
232
+ isLoading: false,
233
+ error: "Discount code is not valid for this pack",
234
+ });
235
+ } else {
236
+ setDiscountInfo({
237
+ isValid: false,
238
+ discountAmount: BigInt(0),
239
+ minPurchaseAmount: BigInt(0),
240
+ isLoading: false,
241
+ error: "Invalid or expired discount code",
242
+ });
243
+ }
244
+ return;
245
+ }
246
+
247
+ setDiscountInfo({ isValid: true, discountAmount, minPurchaseAmount, isLoading: false, error: null });
240
248
  } catch (error) {
241
249
  if (cancelled) return;
242
250
  console.error("Failed to validate discount code", { discountCode, error });
243
251
  setDiscountInfo({
244
252
  isValid: false,
245
253
  discountAmount: BigInt(0),
254
+ minPurchaseAmount: BigInt(0),
246
255
  isLoading: false,
247
256
  error: "Failed to validate discount code",
248
257
  });
@@ -254,7 +263,7 @@ export function AnySpendCollectorClubPurchase({
254
263
  return () => {
255
264
  cancelled = true;
256
265
  };
257
- }, [discountCode, ccShopAddress]);
266
+ }, [discountCode, ccShopAddress, packId]);
258
267
 
259
268
  // Calculate effective dstAmount after discount
260
269
  const effectiveDstAmount = useMemo(() => {
@@ -330,6 +339,22 @@ export function AnySpendCollectorClubPurchase({
330
339
  );
331
340
  }
332
341
 
342
+ if (
343
+ discountCode &&
344
+ discountInfo.isValid &&
345
+ discountInfo.minPurchaseAmount > BigInt(0) &&
346
+ BigInt(packAmount) < discountInfo.minPurchaseAmount
347
+ ) {
348
+ return (
349
+ <div className="mb-4 flex flex-col items-center gap-3 text-center">
350
+ <p className="text-sm text-red-500">
351
+ Minimum purchase of {discountInfo.minPurchaseAmount.toString()} pack
352
+ {discountInfo.minPurchaseAmount > BigInt(1) ? "s" : ""} required for this discount code
353
+ </p>
354
+ </div>
355
+ );
356
+ }
357
+
333
358
  if (discountCode && discountInfo.isValid && effectiveDstAmount === "0") {
334
359
  return (
335
360
  <div className="mb-4 flex flex-col items-center gap-3 text-center">
@@ -0,0 +1,64 @@
1
+ // CCShop contract ABI fragments used by AnySpendCollectorClubPurchase
2
+
3
+ export const BUY_PACKS_FOR_ABI = {
4
+ inputs: [
5
+ { internalType: "address", name: "user", type: "address" },
6
+ { internalType: "uint256", name: "packId", type: "uint256" },
7
+ { internalType: "uint256", name: "amount", type: "uint256" },
8
+ ],
9
+ name: "buyPacksFor",
10
+ outputs: [],
11
+ stateMutability: "nonpayable",
12
+ type: "function",
13
+ } as const;
14
+
15
+ export const BUY_PACKS_FOR_WITH_DISCOUNT_ABI = {
16
+ inputs: [
17
+ { internalType: "address", name: "user", type: "address" },
18
+ { internalType: "uint256", name: "packId", type: "uint256" },
19
+ { internalType: "uint256", name: "amount", type: "uint256" },
20
+ { internalType: "string", name: "discountCode", type: "string" },
21
+ ],
22
+ name: "buyPacksForWithDiscount",
23
+ outputs: [],
24
+ stateMutability: "nonpayable",
25
+ type: "function",
26
+ } as const;
27
+
28
+ export const 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
+ } as const;
41
+
42
+ export const GET_DISCOUNT_CODE_ABI = {
43
+ inputs: [{ internalType: "string", name: "code", type: "string" }],
44
+ name: "getDiscountCode",
45
+ outputs: [
46
+ {
47
+ components: [
48
+ { internalType: "uint256", name: "discountAmount", type: "uint256" },
49
+ { internalType: "uint256", name: "expiresAt", type: "uint256" },
50
+ { internalType: "bool", name: "used", type: "bool" },
51
+ { internalType: "bool", name: "exists", type: "bool" },
52
+ { internalType: "uint256", name: "maxUses", type: "uint256" },
53
+ { internalType: "uint256", name: "usedCount", type: "uint256" },
54
+ { internalType: "uint256", name: "packId", type: "uint256" },
55
+ { internalType: "uint256", name: "minPurchaseAmount", type: "uint256" },
56
+ ],
57
+ internalType: "struct CCShop.DiscountCode",
58
+ name: "",
59
+ type: "tuple",
60
+ },
61
+ ],
62
+ stateMutability: "view",
63
+ type: "function",
64
+ } as const;