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