@stellar-snaps/sdk 0.2.0 → 0.3.1

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/index.js CHANGED
@@ -23,8 +23,62 @@ function _interopNamespace(e) {
23
23
 
24
24
  var StellarSdk__namespace = /*#__PURE__*/_interopNamespace(StellarSdk);
25
25
 
26
+ // src/errors.ts
27
+ var StellarSnapError = class extends Error {
28
+ constructor(message, code) {
29
+ super(message);
30
+ this.code = code;
31
+ this.name = "StellarSnapError";
32
+ }
33
+ };
34
+ var InvalidAddressError = class extends StellarSnapError {
35
+ constructor(address) {
36
+ super(`Invalid Stellar address: ${address}`, "INVALID_ADDRESS");
37
+ this.name = "InvalidAddressError";
38
+ }
39
+ };
40
+ var InvalidAmountError = class extends StellarSnapError {
41
+ constructor(amount) {
42
+ super(`Invalid amount: ${amount}. Must be a positive number.`, "INVALID_AMOUNT");
43
+ this.name = "InvalidAmountError";
44
+ }
45
+ };
46
+ var InvalidAssetError = class extends StellarSnapError {
47
+ constructor(message) {
48
+ super(message, "INVALID_ASSET");
49
+ this.name = "InvalidAssetError";
50
+ }
51
+ };
52
+ var InvalidUriError = class extends StellarSnapError {
53
+ constructor(message) {
54
+ super(message, "INVALID_URI");
55
+ this.name = "InvalidUriError";
56
+ }
57
+ };
58
+ var SnapNotFoundError = class extends StellarSnapError {
59
+ constructor(snapId) {
60
+ super(`Snap not found: ${snapId}`, "SNAP_NOT_FOUND");
61
+ this.name = "SnapNotFoundError";
62
+ }
63
+ };
64
+ var SnapUnauthorizedError = class extends StellarSnapError {
65
+ constructor(message = "Unauthorized") {
66
+ super(message, "SNAP_UNAUTHORIZED");
67
+ this.name = "SnapUnauthorizedError";
68
+ }
69
+ };
70
+ var SnapApiError = class extends StellarSnapError {
71
+ constructor(message, statusCode) {
72
+ super(message, "SNAP_API_ERROR");
73
+ this.statusCode = statusCode;
74
+ this.name = "SnapApiError";
75
+ }
76
+ };
77
+
26
78
  // src/create-snap.ts
79
+ var DEFAULT_BASE_URL = "https://stellar-snaps.vercel.app";
27
80
  async function createSnap(options) {
81
+ const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
28
82
  const {
29
83
  creator,
30
84
  title,
@@ -36,20 +90,19 @@ async function createSnap(options) {
36
90
  memo,
37
91
  memoType = "MEMO_TEXT",
38
92
  network = "testnet",
39
- imageUrl,
40
- serverUrl = "https://stellarsnaps.com"
93
+ imageUrl
41
94
  } = options;
42
95
  if (!creator || creator.length !== 56 || !creator.startsWith("G")) {
43
- throw new Error("Invalid creator address");
96
+ throw new InvalidAddressError(creator || "");
44
97
  }
45
98
  if (!title || title.trim().length === 0) {
46
- throw new Error("Title is required");
99
+ throw new SnapApiError("Title is required");
47
100
  }
48
101
  if (!destination || destination.length !== 56 || !destination.startsWith("G")) {
49
- throw new Error("Invalid destination address");
102
+ throw new InvalidAddressError(destination || "");
50
103
  }
51
104
  if (assetCode !== "XLM" && !assetIssuer) {
52
- throw new Error("Asset issuer is required for non-XLM assets");
105
+ throw new SnapApiError("Asset issuer is required for non-XLM assets");
53
106
  }
54
107
  const body = {
55
108
  creator,
@@ -64,52 +117,206 @@ async function createSnap(options) {
64
117
  network,
65
118
  imageUrl
66
119
  };
67
- const response = await fetch(`${serverUrl}/api/snaps`, {
120
+ const response = await fetch(`${baseUrl}/api/snaps`, {
68
121
  method: "POST",
69
- headers: {
70
- "Content-Type": "application/json"
71
- },
122
+ headers: { "Content-Type": "application/json" },
72
123
  body: JSON.stringify(body)
73
124
  });
74
125
  if (!response.ok) {
75
- const error = await response.json().catch(() => ({ error: "Unknown error" }));
76
- throw new Error(error.error || `Failed to create snap: ${response.status}`);
126
+ const data = await response.json().catch(() => ({ error: "Unknown error" }));
127
+ const message = data.error || `Failed to create snap: ${response.status}`;
128
+ throw new SnapApiError(message, response.status);
77
129
  }
78
130
  const snap = await response.json();
79
131
  return {
80
132
  id: snap.id,
81
- url: `${serverUrl}/s/${snap.id}`,
133
+ url: `${baseUrl}/s/${snap.id}`,
82
134
  snap
83
135
  };
84
136
  }
85
137
  async function getSnap(id, options = {}) {
86
- const { serverUrl = "https://stellarsnaps.com" } = options;
87
- const response = await fetch(`${serverUrl}/api/snaps/${id}`);
138
+ const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
139
+ const response = await fetch(`${baseUrl}/api/snap/${id}`);
88
140
  if (response.status === 404) {
89
- return null;
141
+ throw new SnapNotFoundError(id);
90
142
  }
91
143
  if (!response.ok) {
92
- throw new Error(`Failed to fetch snap: ${response.status}`);
144
+ const data = await response.json().catch(() => ({}));
145
+ const message = data.error || `Failed to fetch snap: ${response.status}`;
146
+ throw new SnapApiError(message, response.status);
93
147
  }
94
148
  return response.json();
95
149
  }
96
150
  async function listSnaps(creator, options = {}) {
97
- const { serverUrl = "https://stellarsnaps.com" } = options;
98
- const response = await fetch(`${serverUrl}/api/snaps?creator=${encodeURIComponent(creator)}`);
151
+ const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
152
+ const response = await fetch(
153
+ `${baseUrl}/api/snaps?creator=${encodeURIComponent(creator)}`
154
+ );
99
155
  if (!response.ok) {
100
- throw new Error(`Failed to list snaps: ${response.status}`);
156
+ const data = await response.json().catch(() => ({}));
157
+ const message = data.error || `Failed to list snaps: ${response.status}`;
158
+ throw new SnapApiError(message, response.status);
101
159
  }
102
160
  return response.json();
103
161
  }
104
- async function deleteSnap(id, options = {}) {
105
- const { serverUrl = "https://stellarsnaps.com" } = options;
106
- const response = await fetch(`${serverUrl}/api/snaps/${id}`, {
107
- method: "DELETE"
108
- });
109
- if (!response.ok && response.status !== 404) {
110
- throw new Error(`Failed to delete snap: ${response.status}`);
162
+ async function deleteSnap(id, creator, options = {}) {
163
+ const baseUrl = options.baseUrl ?? DEFAULT_BASE_URL;
164
+ const url = `${baseUrl}/api/snaps?id=${encodeURIComponent(id)}&creator=${encodeURIComponent(creator)}`;
165
+ const response = await fetch(url, { method: "DELETE" });
166
+ if (response.status === 404) {
167
+ throw new SnapNotFoundError(id);
168
+ }
169
+ if (response.status === 403) {
170
+ throw new SnapUnauthorizedError("You do not have permission to delete this snap");
171
+ }
172
+ if (!response.ok) {
173
+ const data = await response.json().catch(() => ({}));
174
+ const message = data.error || `Failed to delete snap: ${response.status}`;
175
+ throw new SnapApiError(message, response.status);
176
+ }
177
+ }
178
+
179
+ // src/snap-id.ts
180
+ var ALPHABET = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
181
+ function generateSnapId(length = 8) {
182
+ let id = "";
183
+ const randomValues = new Uint8Array(length);
184
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
185
+ crypto.getRandomValues(randomValues);
186
+ } else {
187
+ for (let i = 0; i < length; i++) {
188
+ randomValues[i] = Math.floor(Math.random() * 256);
189
+ }
190
+ }
191
+ for (let i = 0; i < length; i++) {
192
+ id += ALPHABET[randomValues[i] % ALPHABET.length];
193
+ }
194
+ return id;
195
+ }
196
+ function isValidSnapId(id) {
197
+ if (!id || typeof id !== "string") return false;
198
+ if (id.length < 4 || id.length > 32) return false;
199
+ return /^[a-zA-Z0-9_-]+$/.test(id);
200
+ }
201
+ function extractSnapId(url, patterns = ["/s/", "/snap/", "/pay/"]) {
202
+ try {
203
+ const parsed = new URL(url);
204
+ const path = parsed.pathname;
205
+ for (const pattern of patterns) {
206
+ const index = path.indexOf(pattern);
207
+ if (index !== -1) {
208
+ const idStart = index + pattern.length;
209
+ const remaining = path.slice(idStart);
210
+ const match = remaining.match(/^([a-zA-Z0-9_-]+)/);
211
+ if (match && isValidSnapId(match[1])) {
212
+ return match[1];
213
+ }
214
+ }
215
+ }
216
+ return null;
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
221
+
222
+ // src/schema.ts
223
+ function validateSnapInput(input) {
224
+ if (!input.creator || input.creator.length !== 56 || !input.creator.startsWith("G")) {
225
+ throw new Error("Invalid creator address");
226
+ }
227
+ if (!input.destination || input.destination.length !== 56 || !input.destination.startsWith("G")) {
228
+ throw new Error("Invalid destination address");
229
+ }
230
+ if (!input.title || input.title.trim().length === 0) {
231
+ throw new Error("Title is required");
232
+ }
233
+ if (input.title.length > 100) {
234
+ throw new Error("Title must be 100 characters or less");
235
+ }
236
+ if (input.description && input.description.length > 500) {
237
+ throw new Error("Description must be 500 characters or less");
111
238
  }
239
+ if (input.assetCode && input.assetCode !== "XLM" && !input.assetIssuer) {
240
+ throw new Error("Asset issuer is required for non-XLM assets");
241
+ }
242
+ if (input.assetIssuer && (input.assetIssuer.length !== 56 || !input.assetIssuer.startsWith("G"))) {
243
+ throw new Error("Invalid asset issuer address");
244
+ }
245
+ if (input.amount) {
246
+ const amountNum = parseFloat(input.amount);
247
+ if (isNaN(amountNum) || amountNum <= 0) {
248
+ throw new Error("Amount must be a positive number");
249
+ }
250
+ }
251
+ if (input.network && !["public", "testnet"].includes(input.network)) {
252
+ throw new Error('Network must be "public" or "testnet"');
253
+ }
254
+ if (input.memoType && !["MEMO_TEXT", "MEMO_ID", "MEMO_HASH", "MEMO_RETURN"].includes(input.memoType)) {
255
+ throw new Error("Invalid memo type");
256
+ }
257
+ }
258
+ function createSnapObject(id, input) {
259
+ validateSnapInput(input);
260
+ const now = (/* @__PURE__ */ new Date()).toISOString();
261
+ return {
262
+ id,
263
+ creator: input.creator,
264
+ title: input.title.trim(),
265
+ description: input.description?.trim() || null,
266
+ imageUrl: input.imageUrl || null,
267
+ destination: input.destination,
268
+ assetCode: input.assetCode || "XLM",
269
+ assetIssuer: input.assetIssuer || null,
270
+ amount: input.amount || null,
271
+ memo: input.memo || null,
272
+ memoType: input.memoType || "MEMO_TEXT",
273
+ network: input.network || "testnet",
274
+ createdAt: now,
275
+ updatedAt: now
276
+ };
112
277
  }
278
+ var POSTGRES_SCHEMA = `
279
+ CREATE TABLE IF NOT EXISTS snaps (
280
+ id TEXT PRIMARY KEY,
281
+ creator TEXT NOT NULL,
282
+ title TEXT NOT NULL,
283
+ description TEXT,
284
+ image_url TEXT,
285
+ destination TEXT NOT NULL,
286
+ asset_code TEXT NOT NULL DEFAULT 'XLM',
287
+ asset_issuer TEXT,
288
+ amount TEXT,
289
+ memo TEXT,
290
+ memo_type TEXT NOT NULL DEFAULT 'MEMO_TEXT',
291
+ network TEXT NOT NULL DEFAULT 'testnet',
292
+ created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
293
+ updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
294
+ );
295
+
296
+ CREATE INDEX IF NOT EXISTS idx_snaps_creator ON snaps(creator);
297
+ CREATE INDEX IF NOT EXISTS idx_snaps_destination ON snaps(destination);
298
+ `;
299
+ var SQLITE_SCHEMA = `
300
+ CREATE TABLE IF NOT EXISTS snaps (
301
+ id TEXT PRIMARY KEY,
302
+ creator TEXT NOT NULL,
303
+ title TEXT NOT NULL,
304
+ description TEXT,
305
+ image_url TEXT,
306
+ destination TEXT NOT NULL,
307
+ asset_code TEXT NOT NULL DEFAULT 'XLM',
308
+ asset_issuer TEXT,
309
+ amount TEXT,
310
+ memo TEXT,
311
+ memo_type TEXT NOT NULL DEFAULT 'MEMO_TEXT',
312
+ network TEXT NOT NULL DEFAULT 'testnet',
313
+ created_at TEXT DEFAULT (datetime('now')),
314
+ updated_at TEXT DEFAULT (datetime('now'))
315
+ );
316
+
317
+ CREATE INDEX IF NOT EXISTS idx_snaps_creator ON snaps(creator);
318
+ CREATE INDEX IF NOT EXISTS idx_snaps_destination ON snaps(destination);
319
+ `;
113
320
 
114
321
  // src/create-payment-snap.ts
115
322
  var NETWORK_PASSPHRASES = {
@@ -175,39 +382,6 @@ var HORIZON_URLS = {
175
382
  testnet: "https://horizon-testnet.stellar.org"
176
383
  };
177
384
 
178
- // src/errors.ts
179
- var StellarSnapError = class extends Error {
180
- constructor(message, code) {
181
- super(message);
182
- this.code = code;
183
- this.name = "StellarSnapError";
184
- }
185
- };
186
- var InvalidAddressError = class extends StellarSnapError {
187
- constructor(address) {
188
- super(`Invalid Stellar address: ${address}`, "INVALID_ADDRESS");
189
- this.name = "InvalidAddressError";
190
- }
191
- };
192
- var InvalidAmountError = class extends StellarSnapError {
193
- constructor(amount) {
194
- super(`Invalid amount: ${amount}. Must be a positive number.`, "INVALID_AMOUNT");
195
- this.name = "InvalidAmountError";
196
- }
197
- };
198
- var InvalidAssetError = class extends StellarSnapError {
199
- constructor(message) {
200
- super(message, "INVALID_ASSET");
201
- this.name = "InvalidAssetError";
202
- }
203
- };
204
- var InvalidUriError = class extends StellarSnapError {
205
- constructor(message) {
206
- super(message, "INVALID_URI");
207
- this.name = "InvalidUriError";
208
- }
209
- };
210
-
211
385
  // src/create-transaction-snap.ts
212
386
  function createTransactionSnap(options) {
213
387
  const { xdr, network = "public", message, callback, pubkey } = options;
@@ -566,32 +740,520 @@ function matchUrlToRule(pathname, rules) {
566
740
  return null;
567
741
  }
568
742
 
743
+ // src/url-resolver.ts
744
+ var SHORTENER_DOMAINS = [
745
+ "t.co",
746
+ "bit.ly",
747
+ "goo.gl",
748
+ "tinyurl.com",
749
+ "ow.ly",
750
+ "is.gd",
751
+ "buff.ly",
752
+ "adf.ly",
753
+ "bit.do",
754
+ "mcaf.ee",
755
+ "su.pr",
756
+ "twit.ac",
757
+ "tiny.cc",
758
+ "lnkd.in",
759
+ "db.tt",
760
+ "qr.ae",
761
+ "cur.lv",
762
+ "ity.im",
763
+ "q.gs",
764
+ "po.st",
765
+ "bc.vc",
766
+ "u.to",
767
+ "j.mp",
768
+ "buzurl.com",
769
+ "cutt.us",
770
+ "u.bb",
771
+ "yourls.org",
772
+ "x.co",
773
+ "prettylinkpro.com",
774
+ "viralurl.com",
775
+ "twitthis.com",
776
+ "shorturl.at",
777
+ "rb.gy",
778
+ "shorturl.com"
779
+ ];
780
+ function isShortenerUrl(url) {
781
+ try {
782
+ const parsed = new URL(url);
783
+ const domain = parsed.hostname.toLowerCase();
784
+ return SHORTENER_DOMAINS.some(
785
+ (shortener) => domain === shortener || domain.endsWith(`.${shortener}`)
786
+ );
787
+ } catch {
788
+ return false;
789
+ }
790
+ }
791
+ function extractDomain(url) {
792
+ try {
793
+ const parsed = new URL(url);
794
+ return parsed.hostname.toLowerCase();
795
+ } catch {
796
+ return "";
797
+ }
798
+ }
799
+ function extractPath(url) {
800
+ try {
801
+ const parsed = new URL(url);
802
+ return parsed.pathname;
803
+ } catch {
804
+ return "";
805
+ }
806
+ }
807
+ async function resolveUrl(url) {
808
+ const originalUrl = url;
809
+ const wasShortened = isShortenerUrl(url);
810
+ if (!wasShortened) {
811
+ return {
812
+ url,
813
+ domain: extractDomain(url),
814
+ originalUrl,
815
+ wasShortened: false
816
+ };
817
+ }
818
+ try {
819
+ const response = await fetch(url, {
820
+ method: "HEAD",
821
+ redirect: "follow"
822
+ });
823
+ const finalUrl = response.url;
824
+ return {
825
+ url: finalUrl,
826
+ domain: extractDomain(finalUrl),
827
+ originalUrl,
828
+ wasShortened: true
829
+ };
830
+ } catch (error) {
831
+ return {
832
+ url,
833
+ domain: extractDomain(url),
834
+ originalUrl,
835
+ wasShortened: true
836
+ };
837
+ }
838
+ }
839
+ async function resolveUrls(urls) {
840
+ return Promise.all(urls.map(resolveUrl));
841
+ }
842
+
843
+ // src/registry.ts
844
+ function createRegistry(domains = []) {
845
+ return {
846
+ domains,
847
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
848
+ version: "1.0.0"
849
+ };
850
+ }
851
+ function addDomain(registry, entry) {
852
+ const existing = registry.domains.findIndex((d) => d.domain === entry.domain);
853
+ const newDomains = existing >= 0 ? registry.domains.map((d, i) => i === existing ? entry : d) : [...registry.domains, entry];
854
+ return {
855
+ ...registry,
856
+ domains: newDomains,
857
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
858
+ };
859
+ }
860
+ function removeDomain(registry, domain) {
861
+ return {
862
+ ...registry,
863
+ domains: registry.domains.filter((d) => d.domain !== domain),
864
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
865
+ };
866
+ }
867
+ function getDomainStatus(registry, domain) {
868
+ const normalizedDomain = domain.toLowerCase().replace(/^www\./, "");
869
+ const entry = registry.domains.find(
870
+ (d) => d.domain.toLowerCase() === normalizedDomain
871
+ );
872
+ return entry || null;
873
+ }
874
+ function isDomainVerified(registry, domain) {
875
+ const entry = getDomainStatus(registry, domain);
876
+ return entry?.status === "verified";
877
+ }
878
+ function isDomainBlocked(registry, domain) {
879
+ const entry = getDomainStatus(registry, domain);
880
+ return entry?.status === "blocked";
881
+ }
882
+ function getVerifiedDomains(registry) {
883
+ return registry.domains.filter((d) => d.status === "verified");
884
+ }
885
+ function getBlockedDomains(registry) {
886
+ return registry.domains.filter((d) => d.status === "blocked");
887
+ }
888
+ function validateRegistry(registry) {
889
+ if (!registry || typeof registry !== "object") return false;
890
+ const r = registry;
891
+ if (!Array.isArray(r.domains)) return false;
892
+ if (typeof r.updatedAt !== "string") return false;
893
+ if (typeof r.version !== "string") return false;
894
+ return r.domains.every((d) => {
895
+ if (!d || typeof d !== "object") return false;
896
+ const entry = d;
897
+ return typeof entry.domain === "string" && ["verified", "unverified", "blocked"].includes(entry.status);
898
+ });
899
+ }
900
+
901
+ // src/explorer.ts
902
+ var EXPLORER_URLS = {
903
+ "stellar.expert": {
904
+ public: "https://stellar.expert/explorer/public",
905
+ testnet: "https://stellar.expert/explorer/testnet"
906
+ },
907
+ stellarchain: {
908
+ public: "https://stellarchain.io",
909
+ testnet: "https://testnet.stellarchain.io"
910
+ },
911
+ horizon: {
912
+ public: "https://horizon.stellar.org",
913
+ testnet: "https://horizon-testnet.stellar.org"
914
+ }
915
+ };
916
+ function getTransactionUrl(hash, network = "testnet", explorer = "stellar.expert") {
917
+ const baseUrl = EXPLORER_URLS[explorer][network];
918
+ switch (explorer) {
919
+ case "stellar.expert":
920
+ return `${baseUrl}/tx/${hash}`;
921
+ case "stellarchain":
922
+ return `${baseUrl}/transactions/${hash}`;
923
+ case "horizon":
924
+ return `${baseUrl}/transactions/${hash}`;
925
+ default:
926
+ return `${baseUrl}/tx/${hash}`;
927
+ }
928
+ }
929
+ function getAccountUrl(address, network = "testnet", explorer = "stellar.expert") {
930
+ const baseUrl = EXPLORER_URLS[explorer][network];
931
+ switch (explorer) {
932
+ case "stellar.expert":
933
+ return `${baseUrl}/account/${address}`;
934
+ case "stellarchain":
935
+ return `${baseUrl}/accounts/${address}`;
936
+ case "horizon":
937
+ return `${baseUrl}/accounts/${address}`;
938
+ default:
939
+ return `${baseUrl}/account/${address}`;
940
+ }
941
+ }
942
+ function getAssetUrl(code, issuer, network = "testnet", explorer = "stellar.expert") {
943
+ const baseUrl = EXPLORER_URLS[explorer][network];
944
+ switch (explorer) {
945
+ case "stellar.expert":
946
+ return `${baseUrl}/asset/${code}-${issuer}`;
947
+ case "stellarchain":
948
+ return `${baseUrl}/assets/${code}:${issuer}`;
949
+ case "horizon":
950
+ return `${baseUrl}/assets?asset_code=${code}&asset_issuer=${issuer}`;
951
+ default:
952
+ return `${baseUrl}/asset/${code}-${issuer}`;
953
+ }
954
+ }
955
+ function getOperationUrl(operationId, network = "testnet", explorer = "stellar.expert") {
956
+ const baseUrl = EXPLORER_URLS[explorer][network];
957
+ switch (explorer) {
958
+ case "stellar.expert":
959
+ return `${baseUrl}/op/${operationId}`;
960
+ case "stellarchain":
961
+ return `${baseUrl}/operations/${operationId}`;
962
+ case "horizon":
963
+ return `${baseUrl}/operations/${operationId}`;
964
+ default:
965
+ return `${baseUrl}/op/${operationId}`;
966
+ }
967
+ }
968
+
969
+ // src/meta-tags.ts
970
+ function generateMetaTags(metadata) {
971
+ const {
972
+ title,
973
+ description,
974
+ imageUrl,
975
+ amount,
976
+ assetCode = "XLM",
977
+ url,
978
+ siteName = "Stellar Snaps"
979
+ } = metadata;
980
+ const autoDescription = description || (amount ? `Pay ${amount} ${assetCode} - ${title}` : `Make a payment - ${title}`);
981
+ return {
982
+ og: {
983
+ "og:title": title,
984
+ "og:description": autoDescription,
985
+ "og:url": url,
986
+ "og:site_name": siteName,
987
+ "og:type": "website",
988
+ ...imageUrl && { "og:image": imageUrl }
989
+ },
990
+ twitter: {
991
+ "twitter:card": imageUrl ? "summary_large_image" : "summary",
992
+ "twitter:title": title,
993
+ "twitter:description": autoDescription,
994
+ ...imageUrl && { "twitter:image": imageUrl }
995
+ },
996
+ standard: {
997
+ title: `${title} | ${siteName}`,
998
+ description: autoDescription
999
+ }
1000
+ };
1001
+ }
1002
+ function metaTagsToHtml(tags) {
1003
+ const lines = [];
1004
+ if (tags.standard.title) {
1005
+ lines.push(`<title>${escapeHtml(tags.standard.title)}</title>`);
1006
+ }
1007
+ if (tags.standard.description) {
1008
+ lines.push(
1009
+ `<meta name="description" content="${escapeHtml(tags.standard.description)}" />`
1010
+ );
1011
+ }
1012
+ for (const [property, content] of Object.entries(tags.og)) {
1013
+ lines.push(
1014
+ `<meta property="${escapeHtml(property)}" content="${escapeHtml(content)}" />`
1015
+ );
1016
+ }
1017
+ for (const [name, content] of Object.entries(tags.twitter)) {
1018
+ lines.push(
1019
+ `<meta name="${escapeHtml(name)}" content="${escapeHtml(content)}" />`
1020
+ );
1021
+ }
1022
+ return lines.join("\n");
1023
+ }
1024
+ function escapeHtml(str) {
1025
+ return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
1026
+ }
1027
+ function generateJsonLd(metadata) {
1028
+ const { title, description, amount, assetCode = "XLM", url, imageUrl } = metadata;
1029
+ return {
1030
+ "@context": "https://schema.org",
1031
+ "@type": "PaymentService",
1032
+ name: title,
1033
+ description: description || `Pay ${amount || "any amount"} ${assetCode}`,
1034
+ url,
1035
+ ...imageUrl && { image: imageUrl },
1036
+ ...amount && {
1037
+ offers: {
1038
+ "@type": "Offer",
1039
+ price: amount,
1040
+ priceCurrency: assetCode
1041
+ }
1042
+ }
1043
+ };
1044
+ }
1045
+
1046
+ // src/server.ts
1047
+ var CORS_HEADERS = {
1048
+ "Access-Control-Allow-Origin": "*",
1049
+ "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
1050
+ "Access-Control-Allow-Headers": "Content-Type, Authorization",
1051
+ "Access-Control-Max-Age": "86400"
1052
+ };
1053
+ var CACHE_HEADERS = {
1054
+ /** No caching */
1055
+ none: {
1056
+ "Cache-Control": "no-store, no-cache, must-revalidate"
1057
+ },
1058
+ /** Short cache (1 minute) */
1059
+ short: {
1060
+ "Cache-Control": "public, max-age=60, stale-while-revalidate=30"
1061
+ },
1062
+ /** Medium cache (5 minutes) */
1063
+ medium: {
1064
+ "Cache-Control": "public, max-age=300, stale-while-revalidate=60"
1065
+ },
1066
+ /** Long cache (1 hour) */
1067
+ long: {
1068
+ "Cache-Control": "public, max-age=3600, stale-while-revalidate=300"
1069
+ },
1070
+ /** Immutable (1 year) */
1071
+ immutable: {
1072
+ "Cache-Control": "public, max-age=31536000, immutable"
1073
+ }
1074
+ };
1075
+ function successResponse(data) {
1076
+ return {
1077
+ success: true,
1078
+ data
1079
+ };
1080
+ }
1081
+ function errorResponse(message, code) {
1082
+ return {
1083
+ success: false,
1084
+ error: message,
1085
+ code
1086
+ };
1087
+ }
1088
+ function validateRequired(body, fields) {
1089
+ for (const field of fields) {
1090
+ if (body[field] === void 0 || body[field] === null || body[field] === "") {
1091
+ throw new Error(`Missing required field: ${field}`);
1092
+ }
1093
+ }
1094
+ }
1095
+ function parseQueryParams(url) {
1096
+ try {
1097
+ const parsed = new URL(url);
1098
+ const params = {};
1099
+ parsed.searchParams.forEach((value, key) => {
1100
+ params[key] = value;
1101
+ });
1102
+ return params;
1103
+ } catch {
1104
+ return {};
1105
+ }
1106
+ }
1107
+ function buildUrl(base, params) {
1108
+ const url = new URL(base);
1109
+ for (const [key, value] of Object.entries(params)) {
1110
+ if (value !== void 0) {
1111
+ url.searchParams.set(key, value);
1112
+ }
1113
+ }
1114
+ return url.toString();
1115
+ }
1116
+ function createRateLimiter(options) {
1117
+ const buckets = /* @__PURE__ */ new Map();
1118
+ return {
1119
+ /**
1120
+ * Checks if a key is within rate limits.
1121
+ * Returns true if allowed, false if rate limited.
1122
+ */
1123
+ check(key) {
1124
+ const now = Date.now();
1125
+ const bucket = buckets.get(key);
1126
+ if (bucket && bucket.resetAt <= now) {
1127
+ buckets.delete(key);
1128
+ }
1129
+ const current = buckets.get(key);
1130
+ if (!current) {
1131
+ buckets.set(key, {
1132
+ count: 1,
1133
+ resetAt: now + options.windowMs
1134
+ });
1135
+ return true;
1136
+ }
1137
+ if (current.count >= options.maxRequests) {
1138
+ return false;
1139
+ }
1140
+ current.count++;
1141
+ return true;
1142
+ },
1143
+ /**
1144
+ * Gets remaining requests for a key.
1145
+ */
1146
+ remaining(key) {
1147
+ const bucket = buckets.get(key);
1148
+ if (!bucket || bucket.resetAt <= Date.now()) {
1149
+ return options.maxRequests;
1150
+ }
1151
+ return Math.max(0, options.maxRequests - bucket.count);
1152
+ },
1153
+ /**
1154
+ * Resets the limiter for a key.
1155
+ */
1156
+ reset(key) {
1157
+ buckets.delete(key);
1158
+ },
1159
+ /**
1160
+ * Clears all rate limit data.
1161
+ */
1162
+ clear() {
1163
+ buckets.clear();
1164
+ }
1165
+ };
1166
+ }
1167
+ function parseAddress(input) {
1168
+ const trimmed = input.trim();
1169
+ if (trimmed.includes("*")) {
1170
+ return {
1171
+ address: trimmed,
1172
+ federation: trimmed
1173
+ };
1174
+ }
1175
+ if (trimmed.startsWith("M") && trimmed.length === 69) {
1176
+ return {
1177
+ address: trimmed,
1178
+ muxedId: trimmed
1179
+ };
1180
+ }
1181
+ if (trimmed.startsWith("G") && trimmed.length === 56) {
1182
+ return {
1183
+ address: trimmed
1184
+ };
1185
+ }
1186
+ throw new Error("Invalid address format");
1187
+ }
1188
+
1189
+ exports.CACHE_HEADERS = CACHE_HEADERS;
1190
+ exports.CORS_HEADERS = CORS_HEADERS;
1191
+ exports.EXPLORER_URLS = EXPLORER_URLS;
569
1192
  exports.HORIZON_URLS = HORIZON_URLS;
570
1193
  exports.InvalidAddressError = InvalidAddressError;
571
1194
  exports.InvalidAmountError = InvalidAmountError;
572
1195
  exports.InvalidAssetError = InvalidAssetError;
573
1196
  exports.InvalidUriError = InvalidUriError;
574
1197
  exports.NETWORK_PASSPHRASES = NETWORK_PASSPHRASES2;
1198
+ exports.POSTGRES_SCHEMA = POSTGRES_SCHEMA;
1199
+ exports.SHORTENER_DOMAINS = SHORTENER_DOMAINS;
1200
+ exports.SQLITE_SCHEMA = SQLITE_SCHEMA;
1201
+ exports.SnapApiError = SnapApiError;
1202
+ exports.SnapNotFoundError = SnapNotFoundError;
1203
+ exports.SnapUnauthorizedError = SnapUnauthorizedError;
575
1204
  exports.StellarSnapError = StellarSnapError;
1205
+ exports.addDomain = addDomain;
576
1206
  exports.buildPaymentTransaction = buildPaymentTransaction;
1207
+ exports.buildUrl = buildUrl;
577
1208
  exports.connectFreighter = connectFreighter;
578
1209
  exports.createAsset = createAsset;
579
1210
  exports.createDiscoveryFile = createDiscoveryFile;
580
1211
  exports.createPaymentSnap = createPaymentSnap;
1212
+ exports.createRateLimiter = createRateLimiter;
1213
+ exports.createRegistry = createRegistry;
581
1214
  exports.createSnap = createSnap;
1215
+ exports.createSnapObject = createSnapObject;
582
1216
  exports.createTransactionSnap = createTransactionSnap;
583
1217
  exports.deleteSnap = deleteSnap;
1218
+ exports.errorResponse = errorResponse;
1219
+ exports.extractDomain = extractDomain;
1220
+ exports.extractPath = extractPath;
1221
+ exports.extractSnapId = extractSnapId;
1222
+ exports.generateJsonLd = generateJsonLd;
1223
+ exports.generateMetaTags = generateMetaTags;
1224
+ exports.generateSnapId = generateSnapId;
1225
+ exports.getAccountUrl = getAccountUrl;
1226
+ exports.getAssetUrl = getAssetUrl;
1227
+ exports.getBlockedDomains = getBlockedDomains;
1228
+ exports.getDomainStatus = getDomainStatus;
584
1229
  exports.getFreighterNetwork = getFreighterNetwork;
1230
+ exports.getOperationUrl = getOperationUrl;
585
1231
  exports.getSnap = getSnap;
1232
+ exports.getTransactionUrl = getTransactionUrl;
1233
+ exports.getVerifiedDomains = getVerifiedDomains;
1234
+ exports.isDomainBlocked = isDomainBlocked;
1235
+ exports.isDomainVerified = isDomainVerified;
586
1236
  exports.isFreighterConnected = isFreighterConnected;
1237
+ exports.isShortenerUrl = isShortenerUrl;
587
1238
  exports.isValidAmount = isValidAmount;
588
1239
  exports.isValidAssetCode = isValidAssetCode;
1240
+ exports.isValidSnapId = isValidSnapId;
589
1241
  exports.isValidStellarAddress = isValidStellarAddress;
590
1242
  exports.listSnaps = listSnaps;
591
1243
  exports.matchUrlToRule = matchUrlToRule;
1244
+ exports.metaTagsToHtml = metaTagsToHtml;
1245
+ exports.parseAddress = parseAddress;
1246
+ exports.parseQueryParams = parseQueryParams;
592
1247
  exports.parseSnapUri = parseSnapUri;
1248
+ exports.removeDomain = removeDomain;
1249
+ exports.resolveUrl = resolveUrl;
1250
+ exports.resolveUrls = resolveUrls;
593
1251
  exports.signWithFreighter = signWithFreighter;
594
1252
  exports.submitTransaction = submitTransaction;
1253
+ exports.successResponse = successResponse;
595
1254
  exports.validateDiscoveryFile = validateDiscoveryFile;
1255
+ exports.validateRegistry = validateRegistry;
1256
+ exports.validateRequired = validateRequired;
1257
+ exports.validateSnapInput = validateSnapInput;
596
1258
  //# sourceMappingURL=index.js.map
597
1259
  //# sourceMappingURL=index.js.map