ani-ads-sdk 1.0.0

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/README.md ADDED
@@ -0,0 +1,61 @@
1
+ # Ani Ads SDK
2
+
3
+ SDK für Mini App Creators um Werbung in ihren Apps anzuzeigen.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @ani-ads/sdk
9
+ # oder
10
+ pnpm add @ani-ads/sdk
11
+ ```
12
+
13
+ ## Verwendung
14
+
15
+ ```tsx
16
+ import { AniAds } from '@ani-ads/sdk'
17
+
18
+ function MyApp() {
19
+ return (
20
+ <AniAds
21
+ creator_code="XYZ"
22
+ user_wallet_address="0x..."
23
+ ad_format={["16:9", "9:16"]}
24
+ api_url="https://your-domain.com" // Optional
25
+ onAdClick={(adId, url) => console.log('Ad clicked:', adId, url)} // Optional
26
+ />
27
+ )
28
+ }
29
+ ```
30
+
31
+ ## Props
32
+
33
+ - `creator_code` (string, required): Eindeutiger Code für deine App
34
+ - `user_wallet_address` (string, required): Wallet-Adresse des aktuellen Users
35
+ - `ad_format` (string[] | string, required): Gewünschte Bildformate (z.B. `["16:9", "9:16", "1:1"]`)
36
+ - `api_url` (string, optional): API URL (Standard: Production URL)
37
+ - `onAdClick` (function, optional): Callback wenn Ad geklickt wird
38
+
39
+ ## Bildformate
40
+
41
+ - `"1:1"` oder `"11"` - Quadratisch (Square)
42
+ - `"9:16"` oder `"916"` - Hochformat (Portrait)
43
+ - `"16:9"` oder `"169"` - Querformat (Landscape)
44
+
45
+ ## Wie es funktioniert
46
+
47
+ Die SDK kommuniziert mit der Ani Ads API, die dann:
48
+
49
+ 1. **Datenbank liest**: Prüft verfügbare Ads und ob User bereits geklickt hat (unique user check)
50
+ 2. **Datenbank schreibt**: Speichert Clicks und Earnings
51
+ 3. **Smart Contract schreibt**: Verarbeitet Zahlungen (0.005 USDC an Creator, 0.005 USDC an Platform)
52
+
53
+ **Wichtig**: Die SDK kann nicht direkt auf Datenbank oder Smart Contract zugreifen, da:
54
+ - Smart Contract Write-Operationen müssen vom Contract Creator signiert werden (Private Key im Backend)
55
+ - Datenbank-Zugriff läuft über die API für Sicherheit und Validierung
56
+
57
+ Bei jedem Click wird automatisch:
58
+ - Geprüft ob unique user (nur einmal pro User bezahlt)
59
+ - Zahlung im Smart Contract verarbeitet
60
+ - Earnings in der Datenbank aktualisiert
61
+
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+
3
+ interface AniAdsProps {
4
+ creator_code: string;
5
+ user_wallet_address: string;
6
+ ad_format: string[] | string;
7
+ api_url?: string;
8
+ onAdClick?: (adId: string, destinationUrl: string) => void;
9
+ }
10
+ declare const AniAds: React.FC<AniAdsProps>;
11
+
12
+ export { AniAds, type AniAdsProps, AniAds as default };
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+
3
+ interface AniAdsProps {
4
+ creator_code: string;
5
+ user_wallet_address: string;
6
+ ad_format: string[] | string;
7
+ api_url?: string;
8
+ onAdClick?: (adId: string, destinationUrl: string) => void;
9
+ }
10
+ declare const AniAds: React.FC<AniAdsProps>;
11
+
12
+ export { AniAds, type AniAdsProps, AniAds as default };
package/dist/index.js ADDED
@@ -0,0 +1,194 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.tsx
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AniAds: () => AniAds,
24
+ default: () => index_default
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var import_react = require("react");
28
+ var import_jsx_runtime = require("react/jsx-runtime");
29
+ var AniAds = ({
30
+ creator_code,
31
+ user_wallet_address,
32
+ ad_format,
33
+ api_url = "https://your-domain.com",
34
+ // Replace with your Ani Ads API domain
35
+ onAdClick
36
+ }) => {
37
+ const [ad, setAd] = (0, import_react.useState)(null);
38
+ const [loading, setLoading] = (0, import_react.useState)(true);
39
+ const [error, setError] = (0, import_react.useState)(null);
40
+ const formats = Array.isArray(ad_format) ? ad_format : typeof ad_format === "string" ? ad_format.split(",").map((f) => f.trim()) : [];
41
+ const getImageUrl = (ad2, format) => {
42
+ const normalizedFormat = format.toLowerCase().replace(/[:\s]/g, "");
43
+ if (normalizedFormat === "11" || normalizedFormat === "1:1") {
44
+ return ad2.image_square || null;
45
+ } else if (normalizedFormat === "916" || normalizedFormat === "9:16") {
46
+ return ad2.image_portrait || null;
47
+ } else if (normalizedFormat === "169" || normalizedFormat === "16:9") {
48
+ return ad2.image_landscape || null;
49
+ }
50
+ return null;
51
+ };
52
+ const fetchAd = (0, import_react.useCallback)(async () => {
53
+ try {
54
+ setLoading(true);
55
+ setError(null);
56
+ const response = await fetch(`${api_url}/api/ads/sdk`, {
57
+ method: "POST",
58
+ headers: {
59
+ "Content-Type": "application/json"
60
+ },
61
+ body: JSON.stringify({
62
+ creator_code,
63
+ formats
64
+ })
65
+ });
66
+ if (!response.ok) {
67
+ throw new Error("Failed to fetch ads");
68
+ }
69
+ const data = await response.json();
70
+ if (data.ad) {
71
+ setAd(data.ad);
72
+ } else {
73
+ setAd(null);
74
+ }
75
+ } catch (err) {
76
+ console.error("Error fetching ad:", err);
77
+ setError(err instanceof Error ? err.message : "Failed to load ad");
78
+ setAd(null);
79
+ } finally {
80
+ setLoading(false);
81
+ }
82
+ }, [creator_code, formats, api_url]);
83
+ const handleClick = (0, import_react.useCallback)(async () => {
84
+ if (!ad || !user_wallet_address) return;
85
+ try {
86
+ const response = await fetch(`${api_url}/api/ads/click`, {
87
+ method: "POST",
88
+ headers: {
89
+ "Content-Type": "application/json"
90
+ },
91
+ body: JSON.stringify({
92
+ ad_id: ad.id,
93
+ creator_code,
94
+ user_wallet_address: user_wallet_address.toLowerCase()
95
+ })
96
+ });
97
+ if (response.ok) {
98
+ if (onAdClick) {
99
+ onAdClick(ad.id, ad.destination_url);
100
+ }
101
+ window.open(ad.destination_url, "_blank", "noopener,noreferrer");
102
+ await fetchAd();
103
+ }
104
+ } catch (err) {
105
+ console.error("Error tracking click:", err);
106
+ window.open(ad.destination_url, "_blank", "noopener,noreferrer");
107
+ }
108
+ }, [ad, creator_code, user_wallet_address, api_url, onAdClick, fetchAd]);
109
+ const getBestImage = (ad2) => {
110
+ for (const format of formats) {
111
+ const imageUrl2 = getImageUrl(ad2, format);
112
+ if (imageUrl2) return imageUrl2;
113
+ }
114
+ return ad2.image_square || ad2.image_portrait || ad2.image_landscape || null;
115
+ };
116
+ (0, import_react.useEffect)(() => {
117
+ if (creator_code && formats.length > 0) {
118
+ fetchAd();
119
+ } else {
120
+ setLoading(false);
121
+ }
122
+ }, [creator_code, formats, fetchAd]);
123
+ if (loading) {
124
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
125
+ display: "flex",
126
+ width: "100%",
127
+ minHeight: "100px",
128
+ backgroundColor: "#f3f4f6",
129
+ borderRadius: "8px",
130
+ alignItems: "center",
131
+ justifyContent: "center"
132
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#6b7280" }, children: "Loading ad..." }) });
133
+ }
134
+ if (error) {
135
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: {
136
+ display: "flex",
137
+ width: "100%",
138
+ minHeight: "100px",
139
+ backgroundColor: "#fee2e2",
140
+ borderRadius: "8px",
141
+ alignItems: "center",
142
+ justifyContent: "center",
143
+ padding: "12px"
144
+ }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { color: "#dc2626", fontSize: "14px" }, children: error }) });
145
+ }
146
+ if (!ad) {
147
+ return null;
148
+ }
149
+ const imageUrl = getBestImage(ad);
150
+ if (!imageUrl) {
151
+ return null;
152
+ }
153
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
154
+ "div",
155
+ {
156
+ style: {
157
+ display: "inline-block",
158
+ width: "100%",
159
+ cursor: "pointer",
160
+ borderRadius: "8px",
161
+ overflow: "hidden",
162
+ transition: "transform 0.2s, box-shadow 0.2s"
163
+ },
164
+ onClick: handleClick,
165
+ onMouseEnter: (e) => {
166
+ e.currentTarget.style.transform = "scale(1.02)";
167
+ e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
168
+ },
169
+ onMouseLeave: (e) => {
170
+ e.currentTarget.style.transform = "scale(1)";
171
+ e.currentTarget.style.boxShadow = "none";
172
+ },
173
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
174
+ "img",
175
+ {
176
+ src: imageUrl,
177
+ alt: ad.title,
178
+ style: {
179
+ width: "100%",
180
+ height: "auto",
181
+ display: "block"
182
+ },
183
+ loading: "lazy"
184
+ }
185
+ )
186
+ }
187
+ );
188
+ };
189
+ var index_default = AniAds;
190
+ // Annotate the CommonJS export names for ESM import in node:
191
+ 0 && (module.exports = {
192
+ AniAds
193
+ });
194
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback } from 'react'\r\n\r\nexport interface AniAdsProps {\r\n creator_code: string\r\n user_wallet_address: string\r\n ad_format: string[] | string // Can be array like [\"16:9\", \"9:16\"] or string like \"16:9,9:16\"\r\n api_url?: string // Optional API URL, defaults to production\r\n onAdClick?: (adId: string, destinationUrl: string) => void // Optional click handler\r\n}\r\n\r\ninterface Ad {\r\n id: string\r\n title: string\r\n destination_url: string\r\n image_square: string\r\n image_portrait: string\r\n image_landscape: string\r\n status: 'active' | 'paused' | 'deleting'\r\n remaining_budget: number\r\n}\r\n\r\nexport const AniAds: React.FC<AniAdsProps> = ({\r\n creator_code,\r\n user_wallet_address,\r\n ad_format,\r\n api_url = 'https://your-domain.com', // Replace with your Ani Ads API domain\r\n onAdClick,\r\n}) => {\r\n const [ad, setAd] = useState<Ad | null>(null)\r\n const [loading, setLoading] = useState(true)\r\n const [error, setError] = useState<string | null>(null)\r\n\r\n // Parse ad format\r\n const formats = Array.isArray(ad_format) \r\n ? ad_format \r\n : typeof ad_format === 'string' \r\n ? ad_format.split(',').map(f => f.trim())\r\n : []\r\n\r\n // Map format strings to image keys\r\n const getImageUrl = (ad: Ad, format: string): string | null => {\r\n const normalizedFormat = format.toLowerCase().replace(/[:\\s]/g, '')\r\n \r\n if (normalizedFormat === '11' || normalizedFormat === '1:1') {\r\n return ad.image_square || null\r\n } else if (normalizedFormat === '916' || normalizedFormat === '9:16') {\r\n return ad.image_portrait || null\r\n } else if (normalizedFormat === '169' || normalizedFormat === '16:9') {\r\n return ad.image_landscape || null\r\n }\r\n \r\n return null\r\n }\r\n\r\n // Fetch available ads\r\n const fetchAd = useCallback(async () => {\r\n try {\r\n setLoading(true)\r\n setError(null)\r\n\r\n const response = await fetch(`${api_url}/api/ads/sdk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n creator_code,\r\n formats,\r\n }),\r\n })\r\n\r\n if (!response.ok) {\r\n throw new Error('Failed to fetch ads')\r\n }\r\n\r\n const data = await response.json()\r\n \r\n if (data.ad) {\r\n setAd(data.ad)\r\n } else {\r\n setAd(null)\r\n }\r\n } catch (err) {\r\n console.error('Error fetching ad:', err)\r\n setError(err instanceof Error ? err.message : 'Failed to load ad')\r\n setAd(null)\r\n } finally {\r\n setLoading(false)\r\n }\r\n }, [creator_code, formats, api_url])\r\n\r\n // Track ad click\r\n const handleClick = useCallback(async () => {\r\n if (!ad || !user_wallet_address) return\r\n\r\n try {\r\n // Track click on backend\r\n const response = await fetch(`${api_url}/api/ads/click`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: ad.id,\r\n creator_code,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n\r\n if (response.ok) {\r\n // Call optional click handler\r\n if (onAdClick) {\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n\r\n // Open destination URL\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n\r\n // Fetch new ad after click\r\n await fetchAd()\r\n }\r\n } catch (err) {\r\n console.error('Error tracking click:', err)\r\n // Still open the URL even if tracking fails\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n }\r\n }, [ad, creator_code, user_wallet_address, api_url, onAdClick, fetchAd])\r\n\r\n // Find best matching image for formats\r\n const getBestImage = (ad: Ad): string | null => {\r\n for (const format of formats) {\r\n const imageUrl = getImageUrl(ad, format)\r\n if (imageUrl) return imageUrl\r\n }\r\n // Fallback to any available image\r\n return ad.image_square || ad.image_portrait || ad.image_landscape || null\r\n }\r\n\r\n useEffect(() => {\r\n if (creator_code && formats.length > 0) {\r\n fetchAd()\r\n } else {\r\n setLoading(false)\r\n }\r\n }, [creator_code, formats, fetchAd])\r\n\r\n if (loading) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#f3f4f6',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n }}>\r\n <span style={{ color: '#6b7280' }}>Loading ad...</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#fee2e2',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n padding: '12px',\r\n }}>\r\n <span style={{ color: '#dc2626', fontSize: '14px' }}>{error}</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (!ad) {\r\n return null // Don't render anything if no ad available\r\n }\r\n\r\n const imageUrl = getBestImage(ad)\r\n\r\n if (!imageUrl) {\r\n return null\r\n }\r\n\r\n return (\r\n <div\r\n style={{\r\n display: 'inline-block',\r\n width: '100%',\r\n cursor: 'pointer',\r\n borderRadius: '8px',\r\n overflow: 'hidden',\r\n transition: 'transform 0.2s, box-shadow 0.2s',\r\n }}\r\n onClick={handleClick}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = 'scale(1.02)'\r\n e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = 'scale(1)'\r\n e.currentTarget.style.boxShadow = 'none'\r\n }}\r\n >\r\n <img\r\n src={imageUrl}\r\n alt={ad.title}\r\n style={{\r\n width: '100%',\r\n height: 'auto',\r\n display: 'block',\r\n }}\r\n loading=\"lazy\"\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n\r\n"],"mappings":"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","names":["ad","imageUrl"]}
package/dist/index.mjs ADDED
@@ -0,0 +1,169 @@
1
+ // src/index.tsx
2
+ import { useEffect, useState, useCallback } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var AniAds = ({
5
+ creator_code,
6
+ user_wallet_address,
7
+ ad_format,
8
+ api_url = "https://your-domain.com",
9
+ // Replace with your Ani Ads API domain
10
+ onAdClick
11
+ }) => {
12
+ const [ad, setAd] = useState(null);
13
+ const [loading, setLoading] = useState(true);
14
+ const [error, setError] = useState(null);
15
+ const formats = Array.isArray(ad_format) ? ad_format : typeof ad_format === "string" ? ad_format.split(",").map((f) => f.trim()) : [];
16
+ const getImageUrl = (ad2, format) => {
17
+ const normalizedFormat = format.toLowerCase().replace(/[:\s]/g, "");
18
+ if (normalizedFormat === "11" || normalizedFormat === "1:1") {
19
+ return ad2.image_square || null;
20
+ } else if (normalizedFormat === "916" || normalizedFormat === "9:16") {
21
+ return ad2.image_portrait || null;
22
+ } else if (normalizedFormat === "169" || normalizedFormat === "16:9") {
23
+ return ad2.image_landscape || null;
24
+ }
25
+ return null;
26
+ };
27
+ const fetchAd = useCallback(async () => {
28
+ try {
29
+ setLoading(true);
30
+ setError(null);
31
+ const response = await fetch(`${api_url}/api/ads/sdk`, {
32
+ method: "POST",
33
+ headers: {
34
+ "Content-Type": "application/json"
35
+ },
36
+ body: JSON.stringify({
37
+ creator_code,
38
+ formats
39
+ })
40
+ });
41
+ if (!response.ok) {
42
+ throw new Error("Failed to fetch ads");
43
+ }
44
+ const data = await response.json();
45
+ if (data.ad) {
46
+ setAd(data.ad);
47
+ } else {
48
+ setAd(null);
49
+ }
50
+ } catch (err) {
51
+ console.error("Error fetching ad:", err);
52
+ setError(err instanceof Error ? err.message : "Failed to load ad");
53
+ setAd(null);
54
+ } finally {
55
+ setLoading(false);
56
+ }
57
+ }, [creator_code, formats, api_url]);
58
+ const handleClick = useCallback(async () => {
59
+ if (!ad || !user_wallet_address) return;
60
+ try {
61
+ const response = await fetch(`${api_url}/api/ads/click`, {
62
+ method: "POST",
63
+ headers: {
64
+ "Content-Type": "application/json"
65
+ },
66
+ body: JSON.stringify({
67
+ ad_id: ad.id,
68
+ creator_code,
69
+ user_wallet_address: user_wallet_address.toLowerCase()
70
+ })
71
+ });
72
+ if (response.ok) {
73
+ if (onAdClick) {
74
+ onAdClick(ad.id, ad.destination_url);
75
+ }
76
+ window.open(ad.destination_url, "_blank", "noopener,noreferrer");
77
+ await fetchAd();
78
+ }
79
+ } catch (err) {
80
+ console.error("Error tracking click:", err);
81
+ window.open(ad.destination_url, "_blank", "noopener,noreferrer");
82
+ }
83
+ }, [ad, creator_code, user_wallet_address, api_url, onAdClick, fetchAd]);
84
+ const getBestImage = (ad2) => {
85
+ for (const format of formats) {
86
+ const imageUrl2 = getImageUrl(ad2, format);
87
+ if (imageUrl2) return imageUrl2;
88
+ }
89
+ return ad2.image_square || ad2.image_portrait || ad2.image_landscape || null;
90
+ };
91
+ useEffect(() => {
92
+ if (creator_code && formats.length > 0) {
93
+ fetchAd();
94
+ } else {
95
+ setLoading(false);
96
+ }
97
+ }, [creator_code, formats, fetchAd]);
98
+ if (loading) {
99
+ return /* @__PURE__ */ jsx("div", { style: {
100
+ display: "flex",
101
+ width: "100%",
102
+ minHeight: "100px",
103
+ backgroundColor: "#f3f4f6",
104
+ borderRadius: "8px",
105
+ alignItems: "center",
106
+ justifyContent: "center"
107
+ }, children: /* @__PURE__ */ jsx("span", { style: { color: "#6b7280" }, children: "Loading ad..." }) });
108
+ }
109
+ if (error) {
110
+ return /* @__PURE__ */ jsx("div", { style: {
111
+ display: "flex",
112
+ width: "100%",
113
+ minHeight: "100px",
114
+ backgroundColor: "#fee2e2",
115
+ borderRadius: "8px",
116
+ alignItems: "center",
117
+ justifyContent: "center",
118
+ padding: "12px"
119
+ }, children: /* @__PURE__ */ jsx("span", { style: { color: "#dc2626", fontSize: "14px" }, children: error }) });
120
+ }
121
+ if (!ad) {
122
+ return null;
123
+ }
124
+ const imageUrl = getBestImage(ad);
125
+ if (!imageUrl) {
126
+ return null;
127
+ }
128
+ return /* @__PURE__ */ jsx(
129
+ "div",
130
+ {
131
+ style: {
132
+ display: "inline-block",
133
+ width: "100%",
134
+ cursor: "pointer",
135
+ borderRadius: "8px",
136
+ overflow: "hidden",
137
+ transition: "transform 0.2s, box-shadow 0.2s"
138
+ },
139
+ onClick: handleClick,
140
+ onMouseEnter: (e) => {
141
+ e.currentTarget.style.transform = "scale(1.02)";
142
+ e.currentTarget.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
143
+ },
144
+ onMouseLeave: (e) => {
145
+ e.currentTarget.style.transform = "scale(1)";
146
+ e.currentTarget.style.boxShadow = "none";
147
+ },
148
+ children: /* @__PURE__ */ jsx(
149
+ "img",
150
+ {
151
+ src: imageUrl,
152
+ alt: ad.title,
153
+ style: {
154
+ width: "100%",
155
+ height: "auto",
156
+ display: "block"
157
+ },
158
+ loading: "lazy"
159
+ }
160
+ )
161
+ }
162
+ );
163
+ };
164
+ var index_default = AniAds;
165
+ export {
166
+ AniAds,
167
+ index_default as default
168
+ };
169
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.tsx"],"sourcesContent":["import React, { useEffect, useState, useCallback } from 'react'\r\n\r\nexport interface AniAdsProps {\r\n creator_code: string\r\n user_wallet_address: string\r\n ad_format: string[] | string // Can be array like [\"16:9\", \"9:16\"] or string like \"16:9,9:16\"\r\n api_url?: string // Optional API URL, defaults to production\r\n onAdClick?: (adId: string, destinationUrl: string) => void // Optional click handler\r\n}\r\n\r\ninterface Ad {\r\n id: string\r\n title: string\r\n destination_url: string\r\n image_square: string\r\n image_portrait: string\r\n image_landscape: string\r\n status: 'active' | 'paused' | 'deleting'\r\n remaining_budget: number\r\n}\r\n\r\nexport const AniAds: React.FC<AniAdsProps> = ({\r\n creator_code,\r\n user_wallet_address,\r\n ad_format,\r\n api_url = 'https://your-domain.com', // Replace with your Ani Ads API domain\r\n onAdClick,\r\n}) => {\r\n const [ad, setAd] = useState<Ad | null>(null)\r\n const [loading, setLoading] = useState(true)\r\n const [error, setError] = useState<string | null>(null)\r\n\r\n // Parse ad format\r\n const formats = Array.isArray(ad_format) \r\n ? ad_format \r\n : typeof ad_format === 'string' \r\n ? ad_format.split(',').map(f => f.trim())\r\n : []\r\n\r\n // Map format strings to image keys\r\n const getImageUrl = (ad: Ad, format: string): string | null => {\r\n const normalizedFormat = format.toLowerCase().replace(/[:\\s]/g, '')\r\n \r\n if (normalizedFormat === '11' || normalizedFormat === '1:1') {\r\n return ad.image_square || null\r\n } else if (normalizedFormat === '916' || normalizedFormat === '9:16') {\r\n return ad.image_portrait || null\r\n } else if (normalizedFormat === '169' || normalizedFormat === '16:9') {\r\n return ad.image_landscape || null\r\n }\r\n \r\n return null\r\n }\r\n\r\n // Fetch available ads\r\n const fetchAd = useCallback(async () => {\r\n try {\r\n setLoading(true)\r\n setError(null)\r\n\r\n const response = await fetch(`${api_url}/api/ads/sdk`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n creator_code,\r\n formats,\r\n }),\r\n })\r\n\r\n if (!response.ok) {\r\n throw new Error('Failed to fetch ads')\r\n }\r\n\r\n const data = await response.json()\r\n \r\n if (data.ad) {\r\n setAd(data.ad)\r\n } else {\r\n setAd(null)\r\n }\r\n } catch (err) {\r\n console.error('Error fetching ad:', err)\r\n setError(err instanceof Error ? err.message : 'Failed to load ad')\r\n setAd(null)\r\n } finally {\r\n setLoading(false)\r\n }\r\n }, [creator_code, formats, api_url])\r\n\r\n // Track ad click\r\n const handleClick = useCallback(async () => {\r\n if (!ad || !user_wallet_address) return\r\n\r\n try {\r\n // Track click on backend\r\n const response = await fetch(`${api_url}/api/ads/click`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n ad_id: ad.id,\r\n creator_code,\r\n user_wallet_address: user_wallet_address.toLowerCase(),\r\n }),\r\n })\r\n\r\n if (response.ok) {\r\n // Call optional click handler\r\n if (onAdClick) {\r\n onAdClick(ad.id, ad.destination_url)\r\n }\r\n\r\n // Open destination URL\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n\r\n // Fetch new ad after click\r\n await fetchAd()\r\n }\r\n } catch (err) {\r\n console.error('Error tracking click:', err)\r\n // Still open the URL even if tracking fails\r\n window.open(ad.destination_url, '_blank', 'noopener,noreferrer')\r\n }\r\n }, [ad, creator_code, user_wallet_address, api_url, onAdClick, fetchAd])\r\n\r\n // Find best matching image for formats\r\n const getBestImage = (ad: Ad): string | null => {\r\n for (const format of formats) {\r\n const imageUrl = getImageUrl(ad, format)\r\n if (imageUrl) return imageUrl\r\n }\r\n // Fallback to any available image\r\n return ad.image_square || ad.image_portrait || ad.image_landscape || null\r\n }\r\n\r\n useEffect(() => {\r\n if (creator_code && formats.length > 0) {\r\n fetchAd()\r\n } else {\r\n setLoading(false)\r\n }\r\n }, [creator_code, formats, fetchAd])\r\n\r\n if (loading) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#f3f4f6',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n }}>\r\n <span style={{ color: '#6b7280' }}>Loading ad...</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return (\r\n <div style={{\r\n display: 'flex',\r\n width: '100%',\r\n minHeight: '100px',\r\n backgroundColor: '#fee2e2',\r\n borderRadius: '8px',\r\n alignItems: 'center',\r\n justifyContent: 'center',\r\n padding: '12px',\r\n }}>\r\n <span style={{ color: '#dc2626', fontSize: '14px' }}>{error}</span>\r\n </div>\r\n )\r\n }\r\n\r\n if (!ad) {\r\n return null // Don't render anything if no ad available\r\n }\r\n\r\n const imageUrl = getBestImage(ad)\r\n\r\n if (!imageUrl) {\r\n return null\r\n }\r\n\r\n return (\r\n <div\r\n style={{\r\n display: 'inline-block',\r\n width: '100%',\r\n cursor: 'pointer',\r\n borderRadius: '8px',\r\n overflow: 'hidden',\r\n transition: 'transform 0.2s, box-shadow 0.2s',\r\n }}\r\n onClick={handleClick}\r\n onMouseEnter={(e) => {\r\n e.currentTarget.style.transform = 'scale(1.02)'\r\n e.currentTarget.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)'\r\n }}\r\n onMouseLeave={(e) => {\r\n e.currentTarget.style.transform = 'scale(1)'\r\n e.currentTarget.style.boxShadow = 'none'\r\n }}\r\n >\r\n <img\r\n src={imageUrl}\r\n alt={ad.title}\r\n style={{\r\n width: '100%',\r\n height: 'auto',\r\n display: 'block',\r\n }}\r\n loading=\"lazy\"\r\n />\r\n </div>\r\n )\r\n}\r\n\r\nexport default AniAds\r\n\r\n"],"mappings":"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","names":["ad","imageUrl"]}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "ani-ads-sdk",
3
+ "version": "1.0.0",
4
+ "description": "Ani Ads SDK for Mini App Creators to display ads in their apps",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.esm.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup",
13
+ "dev": "tsup --watch"
14
+ },
15
+ "keywords": [
16
+ "ads",
17
+ "worldchain",
18
+ "world-app",
19
+ "mini-app"
20
+ ],
21
+ "author": "",
22
+ "license": "MIT",
23
+ "devDependencies": {
24
+ "@types/react": "^19",
25
+ "@types/react-dom": "^19",
26
+ "tsup": "^8.0.0",
27
+ "typescript": "^5"
28
+ },
29
+ "peerDependencies": {
30
+ "react": "^18.0.0 || ^19.0.0",
31
+ "react-dom": "^18.0.0 || ^19.0.0"
32
+ },
33
+ "dependencies": {
34
+ "react": "^19.2.0",
35
+ "react-dom": "^19.2.0"
36
+ }
37
+ }
38
+