forked from Prompt-Hash-Stellar/prompt-hash
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathupload.ts
More file actions
105 lines (89 loc) · 3.45 KB
/
Copy pathupload.ts
File metadata and controls
105 lines (89 loc) · 3.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/**
* Client-side IPFS upload via Pinata.
*
* Prompt plaintext is always encrypted in the browser first; only opaque
* ciphertext is uploaded here. The returned `ipfs://<cid>` reference is what
* gets stored on-chain in place of the full payload, letting listings exceed
* the 4KB on-chain encrypted-payload limit.
*
* Upload is optional: when no `PUBLIC_PINATA_JWT` is configured the create flow
* falls back to inline on-chain storage, so existing deployments keep working.
*/
import { toIpfsUri } from "./reference";
const PINATA_PIN_FILE_URL = "https://api.pinata.cloud/pinning/pinFileToIPFS";
function readPinataJwt(): string | undefined {
const jwt = import.meta.env.PUBLIC_PINATA_JWT;
return typeof jwt === "string" && jwt.trim() ? jwt.trim() : undefined;
}
/** True when a Pinata JWT is configured and client-side IPFS upload is available. */
export function isIpfsUploadConfigured(): boolean {
return Boolean(readPinataJwt());
}
export interface IpfsUploadResult {
cid: string;
uri: string;
}
/**
* Uploads encrypted ciphertext to IPFS via Pinata and returns the resulting CID
* plus a canonical `ipfs://` reference suitable for on-chain storage.
*/
export async function uploadCiphertextToIpfs(
ciphertextBase64: string,
options?: { name?: string },
): Promise<IpfsUploadResult> {
const jwt = readPinataJwt();
if (!jwt) {
throw new Error(
"IPFS upload is not configured. Set PUBLIC_PINATA_JWT to enable off-chain payload storage.",
);
}
const name = options?.name ?? "prompt-ciphertext";
const form = new FormData();
form.append("file", new Blob([ciphertextBase64], { type: "text/plain" }), `${name}.txt`);
form.append("pinataMetadata", JSON.stringify({ name }));
const response = await fetch(PINATA_PIN_FILE_URL, {
method: "POST",
headers: { Authorization: `Bearer ${jwt}` },
body: form,
});
if (!response.ok) {
const detail = await response.text().catch(() => "");
throw new Error(
`Pinata upload failed (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}.`,
);
}
const data = (await response.json()) as { IpfsHash?: string };
if (!data.IpfsHash) {
throw new Error("Pinata upload succeeded but did not return an IPFS hash.");
}
return { cid: data.IpfsHash, uri: toIpfsUri(data.IpfsHash) };
}
/**
* Uploads a standard image file to IPFS via Pinata.
* Useful for user avatars and other static assets.
*/
export async function uploadImageToIpfs(file: File): Promise<IpfsUploadResult> {
const jwt = readPinataJwt();
if (!jwt) {
throw new Error("IPFS upload is not configured. Set PUBLIC_PINATA_JWT.");
}
const form = new FormData();
form.append("file", file);
form.append("pinataMetadata", JSON.stringify({ name: file.name }));
const response = await fetch(PINATA_PIN_FILE_URL, {
method: "POST",
headers: { Authorization: `Bearer ${jwt}` },
body: form,
});
if (!response.ok) {
const detail = await response.text().catch(() => "");
throw new Error(`Pinata upload failed (${response.status} ${response.statusText})${detail ? `: ${detail}` : ""}.`);
}
const data = (await response.json()) as { IpfsHash?: string };
if (!data.IpfsHash) {
throw new Error("Pinata upload succeeded but did not return an IPFS hash.");
}
// We want the gateway URL for images, not just ipfs://
// But we can return both and let the caller construct the gateway URL if needed.
return { cid: data.IpfsHash, uri: toIpfsUri(data.IpfsHash) };
}