@artblocks/abx-cli 0.1.0-alpha.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/LICENSE +21 -0
- package/assets/renderer-scaffold/README.md +59 -0
- package/assets/renderer-scaffold/foundry.toml +15 -0
- package/assets/renderer-scaffold/remappings.txt +3 -0
- package/assets/renderer-scaffold/script/Deploy.s.sol +23 -0
- package/assets/renderer-scaffold/src/MyRenderer.sol +123 -0
- package/assets/renderer-scaffold/src/MyTraits.sol +75 -0
- package/assets/renderer-scaffold/src/interfaces/IAbxFieldRenderer.sol +32 -0
- package/assets/renderer-scaffold/src/interfaces/IAbxParams.sol +26 -0
- package/assets/renderer-scaffold/test/MyRenderer.t.sol +110 -0
- package/dist/config.d.ts +69 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +184 -0
- package/dist/config.js.map +1 -0
- package/dist/deps.d.ts +46 -0
- package/dist/deps.d.ts.map +1 -0
- package/dist/deps.js +90 -0
- package/dist/deps.js.map +1 -0
- package/dist/flags.d.ts +25 -0
- package/dist/flags.d.ts.map +1 -0
- package/dist/flags.js +34 -0
- package/dist/flags.js.map +1 -0
- package/dist/inspect.d.ts +48 -0
- package/dist/inspect.d.ts.map +1 -0
- package/dist/inspect.js +184 -0
- package/dist/inspect.js.map +1 -0
- package/dist/main.d.ts +3 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/main.js +5102 -0
- package/dist/main.js.map +1 -0
- package/dist/migrate.d.ts +65 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +180 -0
- package/dist/migrate.js.map +1 -0
- package/dist/mintpage.d.ts +46 -0
- package/dist/mintpage.d.ts.map +1 -0
- package/dist/mintpage.js +461 -0
- package/dist/mintpage.js.map +1 -0
- package/dist/onchain-uri.d.ts +97 -0
- package/dist/onchain-uri.d.ts.map +1 -0
- package/dist/onchain-uri.js +243 -0
- package/dist/onchain-uri.js.map +1 -0
- package/dist/ownerops.d.ts +195 -0
- package/dist/ownerops.d.ts.map +1 -0
- package/dist/ownerops.js +1270 -0
- package/dist/ownerops.js.map +1 -0
- package/dist/provision.d.ts +86 -0
- package/dist/provision.d.ts.map +1 -0
- package/dist/provision.js +372 -0
- package/dist/provision.js.map +1 -0
- package/dist/remote.d.ts +58 -0
- package/dist/remote.d.ts.map +1 -0
- package/dist/remote.js +54 -0
- package/dist/remote.js.map +1 -0
- package/dist/schema.d.ts +15 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +125 -0
- package/dist/schema.js.map +1 -0
- package/dist/series-traits.d.ts +30 -0
- package/dist/series-traits.d.ts.map +1 -0
- package/dist/series-traits.js +103 -0
- package/dist/series-traits.js.map +1 -0
- package/dist/signer.d.ts +80 -0
- package/dist/signer.d.ts.map +1 -0
- package/dist/signer.js +520 -0
- package/dist/signer.js.map +1 -0
- package/dist/upload.d.ts +28 -0
- package/dist/upload.d.ts.map +1 -0
- package/dist/upload.js +41 -0
- package/dist/upload.js.map +1 -0
- package/package.json +55 -0
- package/skill/SKILL.md +304 -0
- package/skill/reference/code-projects.md +211 -0
- package/skill/reference/hosting.md +138 -0
- package/skill/reference/operating.md +116 -0
- package/skill/reference/setup.md +36 -0
- package/skill/reference/troubleshooting.md +28 -0
package/dist/signer.js
ADDED
|
@@ -0,0 +1,520 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { writeFileSync } from 'node:fs';
|
|
3
|
+
import { makePublicClient, makeWalletClient, resolveChain, } from '@artblocks/abx-sdk';
|
|
4
|
+
/** Announce the wallet sign-page URL: a stable, greppable line for humans/agents, plus an
|
|
5
|
+
* optional file write so a backgrounding agent can read the URL without parsing stdout. */
|
|
6
|
+
function announceSignUrl(signUrl, file) {
|
|
7
|
+
// A machine-stable marker line (distinct from the prose below) — easy to grep from a
|
|
8
|
+
// backgrounded process's output: `ABX_SIGN_URL=http://localhost:8799`.
|
|
9
|
+
console.log(` ${dim('ABX_SIGN_URL=')}${bold(signUrl)}`);
|
|
10
|
+
if (file) {
|
|
11
|
+
try {
|
|
12
|
+
writeFileSync(file, signUrl + '\n');
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
/* best-effort — the printed line is the fallback */
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const DEFAULT_SIGN_PORT = 8799;
|
|
20
|
+
const TESTNETS = new Set(['sepolia', 'base-sepolia']);
|
|
21
|
+
// ── tiny ANSI (kept local so this module stands alone) ───────────────────────
|
|
22
|
+
const C = { reset: '\x1b[0m', dim: '\x1b[2m', bold: '\x1b[1m', green: '\x1b[38;5;115m', purple: '\x1b[38;5;141m', orange: '\x1b[38;5;215m' };
|
|
23
|
+
const dim = (s) => `${C.dim}${s}${C.reset}`;
|
|
24
|
+
const bold = (s) => `${C.bold}${s}${C.reset}`;
|
|
25
|
+
const purple = (s) => `${C.purple}${s}${C.reset}`;
|
|
26
|
+
const green = (s) => `${C.green}${s}${C.reset}`;
|
|
27
|
+
function explorerFor(chainKey) {
|
|
28
|
+
const chain = resolveChain(chainKey);
|
|
29
|
+
return chain.blockExplorers?.default?.url ?? '';
|
|
30
|
+
}
|
|
31
|
+
async function resolveTx(provider, signer) {
|
|
32
|
+
return typeof provider === 'function' ? await provider(signer) : provider;
|
|
33
|
+
}
|
|
34
|
+
const delay = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
35
|
+
/**
|
|
36
|
+
* Bind the sign page to a *fresh* port, robustly.
|
|
37
|
+
*
|
|
38
|
+
* Each owner op is a separate short-lived CLI process, and they all prefer the same port
|
|
39
|
+
* (8799) — nice for humans, and it lets the wallet remember the connection across a
|
|
40
|
+
* sequence. But that also means a previous op's server can still be draining on it (a
|
|
41
|
+
* finished session lingers ~1s before its socket drops), or a stuck/abandoned session can
|
|
42
|
+
* still be holding it. Binding naively then either (a) hands the new action to a server that
|
|
43
|
+
* is *already finished* — so the page connects and instantly shows "done" (last op's success,
|
|
44
|
+
* not this one) — or (b) throws EADDRINUSE and the process dies before its page ever appears.
|
|
45
|
+
*
|
|
46
|
+
* So: try the preferred port with a few quick retries (covers a previous session draining),
|
|
47
|
+
* then fall back to an OS-assigned ephemeral port. A new action therefore ALWAYS gets its own
|
|
48
|
+
* fresh server — never one a finished session is still answering. Callers must announce the
|
|
49
|
+
* ACTUAL bound port (returned here), never the requested one.
|
|
50
|
+
*/
|
|
51
|
+
async function listenAvailable(server, preferred) {
|
|
52
|
+
const tryListen = (port) => new Promise((resolve) => {
|
|
53
|
+
const onError = () => {
|
|
54
|
+
server.removeListener('error', onError);
|
|
55
|
+
resolve(false); // EADDRINUSE (or any bind failure) → unavailable; caller retries / falls back
|
|
56
|
+
};
|
|
57
|
+
server.once('error', onError);
|
|
58
|
+
server.listen(port, () => {
|
|
59
|
+
server.removeListener('error', onError);
|
|
60
|
+
resolve(true);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
// ~1.8s of quick retries: reclaim the friendly port the moment a draining prior session lets go.
|
|
64
|
+
for (let i = 0; i < 6; i++) {
|
|
65
|
+
if (await tryListen(preferred))
|
|
66
|
+
return preferred;
|
|
67
|
+
await delay(300);
|
|
68
|
+
}
|
|
69
|
+
// Still held (a stuck/abandoned session) → don't block or crash; take a fresh ephemeral port.
|
|
70
|
+
if (await tryListen(0))
|
|
71
|
+
return server.address().port;
|
|
72
|
+
throw new Error('could not bind a local port for the wallet signing page');
|
|
73
|
+
}
|
|
74
|
+
function printIntent(prepared, opts) {
|
|
75
|
+
console.log(`\n ${purple('◆')} ${bold(prepared.summary)}`);
|
|
76
|
+
for (const [k, v] of Object.entries(prepared.fields))
|
|
77
|
+
console.log(` ${dim(k.padEnd(10))} ${v}`);
|
|
78
|
+
if (opts.expectedSigner)
|
|
79
|
+
console.log(` ${dim('sign as'.padEnd(10))} ${opts.expectedSigner}`);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Sign and (for hot/wallet) broadcast a prepared tx via the chosen lane. Returns
|
|
83
|
+
* the broadcast tx hash, or `null` for the cold lane (nothing is sent — the
|
|
84
|
+
* caller hands the printed tx to an external signer).
|
|
85
|
+
*/
|
|
86
|
+
export async function signTx(provider, opts) {
|
|
87
|
+
switch (opts.lane) {
|
|
88
|
+
case 'send':
|
|
89
|
+
return signHot(provider, opts);
|
|
90
|
+
case 'sign':
|
|
91
|
+
return signWallet(provider, opts);
|
|
92
|
+
case 'unsigned':
|
|
93
|
+
return signCold(provider, opts);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// ── hot lane: env key signs + broadcasts ─────────────────────────────────────
|
|
97
|
+
async function signHot(provider, opts) {
|
|
98
|
+
if (!TESTNETS.has(opts.chainKey) && !opts.yes) {
|
|
99
|
+
throw new Error(`Refusing to sign on '${opts.chainKey}' with the env key without --yes. ` +
|
|
100
|
+
`For real value, prefer the wallet lane (--sign) so the key never touches this process.`);
|
|
101
|
+
}
|
|
102
|
+
const { wallet, account } = makeWalletClient({ chainKey: opts.chainKey });
|
|
103
|
+
const publicClient = makePublicClient({ chainKey: opts.chainKey });
|
|
104
|
+
const prepared = await resolveTx(provider, account.address);
|
|
105
|
+
printIntent(prepared, opts);
|
|
106
|
+
if (opts.expectedSigner && opts.expectedSigner.toLowerCase() !== account.address.toLowerCase()) {
|
|
107
|
+
console.log(`\n ${C.orange}⚠${C.reset} env key ${account.address} is not the expected signer ${opts.expectedSigner} — this will likely revert.`);
|
|
108
|
+
}
|
|
109
|
+
console.log(`\n ${dim(`signing with env key ${account.address} …`)}`);
|
|
110
|
+
const txHash = await wallet.sendTransaction({
|
|
111
|
+
account,
|
|
112
|
+
chain: wallet.chain,
|
|
113
|
+
to: prepared.to ?? undefined,
|
|
114
|
+
data: prepared.data,
|
|
115
|
+
value: BigInt(prepared.value),
|
|
116
|
+
});
|
|
117
|
+
console.log(` ${dim('tx')} ${explorerFor(opts.chainKey)}/tx/${txHash}`);
|
|
118
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
119
|
+
console.log(` ${green('✓')} confirmed`);
|
|
120
|
+
return { txHash, prepared, blockNumber: receipt.blockNumber };
|
|
121
|
+
}
|
|
122
|
+
// ── cold lane: print the tx for an external signer ───────────────────────────
|
|
123
|
+
async function signCold(provider, opts) {
|
|
124
|
+
if (typeof provider === 'function' && !opts.expectedSigner) {
|
|
125
|
+
throw new Error('This op needs the signer address to build its tx. Pass --from <address> with --unsigned.');
|
|
126
|
+
}
|
|
127
|
+
const prepared = await resolveTx(provider, opts.expectedSigner ?? '0x0000000000000000000000000000000000000000');
|
|
128
|
+
printIntent(prepared, opts);
|
|
129
|
+
console.log(`\n ${bold('unsigned transaction')} ${dim('— sign + broadcast this with your own signer (multisig / offline):')}\n`);
|
|
130
|
+
console.log(JSON.stringify({ to: prepared.to, data: prepared.data, value: prepared.value, chainId: prepared.chainId }, null, 2)
|
|
131
|
+
.split('\n')
|
|
132
|
+
.map((l) => ' ' + l)
|
|
133
|
+
.join('\n'));
|
|
134
|
+
console.log(`\n ${dim('Then run')} ${bold('abx index <address>')} ${dim('to reflect it once mined.')}\n`);
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
// ── wallet lane: a single tx, on the SAME hardened session as staged deploys ──
|
|
138
|
+
// Owner ops and off-chain-custody deploys are a single tx, but they get the exact same
|
|
139
|
+
// guard-rich page: connect once, network + signer verified (and re-verified live right
|
|
140
|
+
// before signing), then approve. A builder provider (deploy) is resolved against the
|
|
141
|
+
// CONNECTED signer — which the session pins to `expectedSigner` (page + server both refuse a
|
|
142
|
+
// mismatch) — so the owner/mintTo can never be a wallet you didn't intend.
|
|
143
|
+
async function signWallet(provider, opts) {
|
|
144
|
+
const session = await openWalletSession({
|
|
145
|
+
chainKey: opts.chainKey,
|
|
146
|
+
expectedSigner: opts.expectedSigner,
|
|
147
|
+
port: opts.port,
|
|
148
|
+
signUrlFile: opts.signUrlFile,
|
|
149
|
+
total: 1,
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
const signer = await session.connect();
|
|
153
|
+
const prepared = await resolveTx(provider, signer);
|
|
154
|
+
const { txHash, receipt } = await session.send(prepared);
|
|
155
|
+
return { txHash, prepared, blockNumber: receipt.blockNumber };
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
session.close();
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function readBody(req) {
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
let data = '';
|
|
164
|
+
req.on('data', (c) => (data += c));
|
|
165
|
+
req.on('end', () => resolve(data));
|
|
166
|
+
req.on('error', reject);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
/** Uint8Array → `0x…` hex (for handing a data-item's signature-data to the browser to personal_sign). */
|
|
170
|
+
function bytesToHex(b) {
|
|
171
|
+
let s = '0x';
|
|
172
|
+
for (const byte of b)
|
|
173
|
+
s += byte.toString(16).padStart(2, '0');
|
|
174
|
+
return s;
|
|
175
|
+
}
|
|
176
|
+
export async function openWalletSession(opts) {
|
|
177
|
+
const preferredPort = opts.port ?? DEFAULT_SIGN_PORT;
|
|
178
|
+
const chain = resolveChain(opts.chainKey);
|
|
179
|
+
const publicClient = makePublicClient({ chainKey: opts.chainKey });
|
|
180
|
+
const explorer = explorerFor(opts.chainKey);
|
|
181
|
+
let resolveConnect;
|
|
182
|
+
let rejectAll;
|
|
183
|
+
const connected = new Promise((res, rej) => {
|
|
184
|
+
resolveConnect = res;
|
|
185
|
+
rejectAll = rej;
|
|
186
|
+
});
|
|
187
|
+
// The single item the page should currently show; `consumed` flips once it's signed, so `/next`
|
|
188
|
+
// returns idle until the CLI queues the next. An item is either an EVM tx (eth_sendTransaction →
|
|
189
|
+
// txHash) or a message to sign (personal_sign → signature) — the latter is how the wallet signs
|
|
190
|
+
// Turbo (Arweave) upload data-items so its own credits pay. Both resolve `pendingResolve` with a
|
|
191
|
+
// Hex value (a txHash or a signature); only one item is ever in flight.
|
|
192
|
+
let current = null;
|
|
193
|
+
let pendingResolve = null;
|
|
194
|
+
let pendingReject = null;
|
|
195
|
+
let index = 0;
|
|
196
|
+
let finished = false;
|
|
197
|
+
const fail = (e) => {
|
|
198
|
+
pendingReject?.(e);
|
|
199
|
+
rejectAll?.(e);
|
|
200
|
+
};
|
|
201
|
+
const server = createServer(async (req, res) => {
|
|
202
|
+
res.setHeader('access-control-allow-origin', '*');
|
|
203
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
204
|
+
try {
|
|
205
|
+
if (url.pathname === '/') {
|
|
206
|
+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
|
|
207
|
+
// A finished session must NOT re-serve the live connect page: a stale hit (an old tab, a
|
|
208
|
+
// reused bookmark, a page opened while this server drains) would connect and instantly
|
|
209
|
+
// read `/next` → done, looking like THIS action succeeded when it was the last one. Serve
|
|
210
|
+
// an unambiguous ended-state instead — the next action opens its own fresh link.
|
|
211
|
+
res.end(finished ? endedPage() : sessionPage({ chainId: chain.id, chainName: chain.name, explorer, expectedSigner: opts.expectedSigner, total: opts.total }));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (url.pathname === '/connect' && req.method === 'POST') {
|
|
215
|
+
const { signer } = JSON.parse((await readBody(req)) || '{}');
|
|
216
|
+
// Hard server-side guard: never let the deployer/owner resolve to anything but the
|
|
217
|
+
// expected signer (the page enforces this too, but the CLI builds owner/mintTo from
|
|
218
|
+
// whatever connects, so a wrong signer here would silently mis-own the contract).
|
|
219
|
+
if (signer && opts.expectedSigner && signer.toLowerCase() !== opts.expectedSigner.toLowerCase()) {
|
|
220
|
+
res.writeHead(409, { 'content-type': 'application/json' });
|
|
221
|
+
res.end(JSON.stringify({ ok: false, error: `connected ${signer} but this is prepared for ${opts.expectedSigner}` }));
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
225
|
+
res.end(JSON.stringify({ ok: true }));
|
|
226
|
+
if (signer)
|
|
227
|
+
resolveConnect(signer);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (url.pathname === '/next') {
|
|
231
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
232
|
+
if (finished)
|
|
233
|
+
res.end(JSON.stringify({ done: true }));
|
|
234
|
+
else if (current && !current.consumed) {
|
|
235
|
+
const base = { index: current.index, total: opts.total ?? null };
|
|
236
|
+
res.end(JSON.stringify(current.kind === 'msg' ? { ...base, msg: current.msg } : { ...base, tx: current.tx }));
|
|
237
|
+
}
|
|
238
|
+
else
|
|
239
|
+
res.end(JSON.stringify({ idle: true }));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
if (url.pathname === '/signed' && req.method === 'POST') {
|
|
243
|
+
// A tx item returns `txHash`; a message item returns `signature`. Both are Hex; the awaiting
|
|
244
|
+
// caller (send vs signMessage) knows which it queued.
|
|
245
|
+
const { index: i, txHash, signature } = JSON.parse((await readBody(req)) || '{}');
|
|
246
|
+
const value = (txHash ?? signature);
|
|
247
|
+
res.writeHead(200, { 'content-type': 'application/json' });
|
|
248
|
+
res.end(JSON.stringify({ ok: true }));
|
|
249
|
+
if (current && current.index === i && value && pendingResolve) {
|
|
250
|
+
current.consumed = true;
|
|
251
|
+
pendingResolve(value);
|
|
252
|
+
}
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
if (url.pathname === '/cancel' && req.method === 'POST') {
|
|
256
|
+
res.writeHead(200);
|
|
257
|
+
res.end('ok');
|
|
258
|
+
fail(new Error('signing cancelled in the browser'));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
res.writeHead(404);
|
|
262
|
+
res.end('not found');
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
res.writeHead(500, { 'content-type': 'application/json' });
|
|
266
|
+
res.end(JSON.stringify({ error: err.message }));
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
const boundPort = await listenAvailable(server, preferredPort);
|
|
270
|
+
const signUrl = `http://localhost:${boundPort}`;
|
|
271
|
+
console.log(`\n ${purple('◆')} ${bold('wallet signing')} ${dim(`(${chain.name}${opts.total ? `, ${opts.total} transactions` : ''})`)}`);
|
|
272
|
+
console.log(`\n Open ${bold(signUrl)} , connect a wallet${opts.expectedSigner ? ` (${opts.expectedSigner})` : ''}, and approve each step.`);
|
|
273
|
+
announceSignUrl(signUrl, opts.signUrlFile);
|
|
274
|
+
console.log(` ${dim('The key never touches this process — only signed tx hashes come back. Ctrl-C to cancel.')}\n`);
|
|
275
|
+
const onSigint = () => fail(new Error('cancelled'));
|
|
276
|
+
process.once('SIGINT', onSigint);
|
|
277
|
+
return {
|
|
278
|
+
connect: () => connected,
|
|
279
|
+
async send(tx) {
|
|
280
|
+
index += 1;
|
|
281
|
+
current = { index, kind: 'tx', tx, consumed: false };
|
|
282
|
+
console.log(` ${dim(`tx ${opts.total ? `${index}/${opts.total}` : index}:`)} ${tx.summary} ${dim('— approve in your wallet …')}`);
|
|
283
|
+
const txHash = await new Promise((resolve, reject) => {
|
|
284
|
+
pendingResolve = resolve;
|
|
285
|
+
pendingReject = reject;
|
|
286
|
+
});
|
|
287
|
+
pendingResolve = null;
|
|
288
|
+
pendingReject = null;
|
|
289
|
+
console.log(` ${dim('tx')} ${explorer}/tx/${txHash} ${dim('— confirming …')}`);
|
|
290
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
291
|
+
console.log(` ${green('✓')} confirmed`);
|
|
292
|
+
current = null; // back to idle until the next send()
|
|
293
|
+
return { txHash, receipt };
|
|
294
|
+
},
|
|
295
|
+
async signMessage(message, summary) {
|
|
296
|
+
index += 1;
|
|
297
|
+
current = { index, kind: 'msg', msg: { messageHex: bytesToHex(message), summary: summary ?? 'Sign storage upload (Arweave/Turbo)' }, consumed: false };
|
|
298
|
+
console.log(` ${dim(`sign ${opts.total ? `${index}/${opts.total}` : index}:`)} ${current.msg.summary} ${dim('— approve in your wallet …')}`);
|
|
299
|
+
const sig = await new Promise((resolve, reject) => {
|
|
300
|
+
pendingResolve = resolve;
|
|
301
|
+
pendingReject = reject;
|
|
302
|
+
});
|
|
303
|
+
pendingResolve = null;
|
|
304
|
+
pendingReject = null;
|
|
305
|
+
console.log(` ${green('✓')} signed`);
|
|
306
|
+
current = null; // back to idle
|
|
307
|
+
return sig;
|
|
308
|
+
},
|
|
309
|
+
close() {
|
|
310
|
+
finished = true;
|
|
311
|
+
process.removeListener('SIGINT', onSigint);
|
|
312
|
+
// Brief grace so the page's next poll can render "all done" before the socket drops.
|
|
313
|
+
setTimeout(() => server.close(), 1200).unref();
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
/** The page a *finished* session serves on a fresh load — so a stale/reused hit reads as
|
|
318
|
+
* "over", never as the current action's success. Deliberately inert: no wallet JS, no poll. */
|
|
319
|
+
function endedPage() {
|
|
320
|
+
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
321
|
+
<title>ABX · session ended</title><style>
|
|
322
|
+
:root{color-scheme:dark}
|
|
323
|
+
body{margin:0;font:15px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace;background:#0c0c10;color:#e7e7ea;display:flex;min-height:100vh;align-items:center;justify-content:center}
|
|
324
|
+
.card{width:min(560px,92vw);background:#15151c;border-radius:14px;padding:26px 28px;box-shadow:0 12px 40px #0008}
|
|
325
|
+
h1{font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:#a98bff;margin:0 0 10px}
|
|
326
|
+
p{margin:0 0 8px;color:#c9c9d0}
|
|
327
|
+
.dim{color:#7d7d88;font-size:13px}
|
|
328
|
+
</style></head><body><div class="card">
|
|
329
|
+
<h1>ABX · signing session ended</h1>
|
|
330
|
+
<p>This signing session is finished — nothing is pending here.</p>
|
|
331
|
+
<p class="dim">This is <b>not</b> a fresh action. Each new action opens its own link: return to the terminal, wait for the next sign URL, and open that one. Don't reuse this tab.</p>
|
|
332
|
+
</div></body></html>`;
|
|
333
|
+
}
|
|
334
|
+
function sessionPage(o) {
|
|
335
|
+
const cfg = JSON.stringify({
|
|
336
|
+
chainId: o.chainId,
|
|
337
|
+
chainName: o.chainName,
|
|
338
|
+
explorer: o.explorer,
|
|
339
|
+
expectedSigner: o.expectedSigner ?? null,
|
|
340
|
+
total: o.total ?? null,
|
|
341
|
+
});
|
|
342
|
+
return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
343
|
+
<title>ABX · sign</title><style>
|
|
344
|
+
:root{color-scheme:dark}
|
|
345
|
+
body{margin:0;font:15px/1.55 ui-monospace,SFMono-Regular,Menlo,monospace;background:#0c0c10;color:#e7e7ea;display:flex;min-height:100vh;align-items:center;justify-content:center}
|
|
346
|
+
.card{width:min(560px,92vw);background:#15151c;border:1px solid #26263200;border-radius:14px;padding:26px 28px;box-shadow:0 12px 40px #0008}
|
|
347
|
+
h1{font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:#a98bff;margin:0 0 4px}
|
|
348
|
+
.prog{font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:#7d7d88;margin:0 0 10px;min-height:14px}
|
|
349
|
+
.ctx{margin:0 0 16px;font-size:12.5px}
|
|
350
|
+
.ctx .row{display:flex;align-items:flex-start;gap:8px;padding:3px 0}
|
|
351
|
+
.ctx .dot{flex:none;width:8px;height:8px;border-radius:50%;margin-top:6px;background:#5a5a66}
|
|
352
|
+
.ctx .dot.good{background:#7fe0a0}.ctx .dot.bad{background:#ff8b8b}
|
|
353
|
+
.ctx .v{word-break:break-all}
|
|
354
|
+
.ctx .good{color:#9be8b6}.ctx .bad{color:#ff9d9d}
|
|
355
|
+
.summary{font-size:18px;font-weight:600;margin:10px 0 4px}
|
|
356
|
+
.tlabel{font-size:11px;letter-spacing:.1em;text-transform:uppercase;color:#8a8a96;margin:8px 0 2px}
|
|
357
|
+
table{width:100%;border-collapse:collapse;margin:0 0 16px;font-size:13px}
|
|
358
|
+
td{padding:5px 0;vertical-align:top;border-bottom:1px solid #ffffff10}
|
|
359
|
+
td.k{color:#8a8a96;width:104px;padding-right:12px}
|
|
360
|
+
td.v{word-break:break-all;color:#d6d6dc}
|
|
361
|
+
button{font:inherit;font-weight:600;border:0;border-radius:9px;padding:12px 18px;width:100%;cursor:pointer;background:#7c5cff;color:#fff;margin-top:6px}
|
|
362
|
+
button:disabled{opacity:.4;cursor:not-allowed}
|
|
363
|
+
button.secondary{background:#23232e;color:#cfcfd6}
|
|
364
|
+
.note{font-size:12px;color:#7d7d88;margin-top:14px}
|
|
365
|
+
.status{font-size:13px;margin-top:14px;padding:10px 12px;border-radius:8px;background:#1c1c25;display:none}
|
|
366
|
+
.status.show{display:block}
|
|
367
|
+
.ok{color:#7fe0a0}.err{color:#ff8b8b}.warn{color:#ffcf8b}
|
|
368
|
+
a{color:#a98bff}
|
|
369
|
+
</style></head><body><div class="card">
|
|
370
|
+
<h1>ABX · approve to sign</h1>
|
|
371
|
+
<div class="prog" id="progress"></div>
|
|
372
|
+
<div class="ctx">
|
|
373
|
+
<div class="row"><span class="dot" id="netDot"></span><span class="v" id="netText">Network: …</span></div>
|
|
374
|
+
<div class="row"><span class="dot" id="acctDot"></span><span class="v" id="acctText">Wallet: not connected</span></div>
|
|
375
|
+
</div>
|
|
376
|
+
<div class="tlabel" id="tlabel" style="display:none">This transaction does</div>
|
|
377
|
+
<div class="summary" id="summary">Connect your wallet to begin.</div>
|
|
378
|
+
<table id="fields"></table>
|
|
379
|
+
<button id="connect">Connect wallet</button>
|
|
380
|
+
<button id="switch" class="secondary" style="display:none">Switch network to ${o.chainName}</button>
|
|
381
|
+
<button id="sign" disabled style="display:none">Sign & send</button>
|
|
382
|
+
<div class="status" id="status"></div>
|
|
383
|
+
<div class="note" id="note"></div>
|
|
384
|
+
</div>
|
|
385
|
+
<script>
|
|
386
|
+
const CFG = ${cfg};
|
|
387
|
+
const $ = (id) => document.getElementById(id);
|
|
388
|
+
let account=null, chainId=null, connected=false, curTx=null, curMsg=null, curKind=null, curIndex=0, signing=false, stopped=false;
|
|
389
|
+
const toHexChain = (n) => '0x' + n.toString(16);
|
|
390
|
+
const eq = (a,b) => a && b && a.toLowerCase() === b.toLowerCase();
|
|
391
|
+
function status(msg, cls){ const s=$('status'); s.className='status show '+(cls||''); s.innerHTML=msg; }
|
|
392
|
+
function progress(i, label){ $('progress').textContent = i ? ((label||'Step') + ' ' + i + (CFG.total ? ' of ' + CFG.total : '')) : ''; }
|
|
393
|
+
const signerOk = () => !CFG.expectedSigner || eq(account, CFG.expectedSigner);
|
|
394
|
+
const chainOk = () => chainId === CFG.chainId;
|
|
395
|
+
// A message signature (personal_sign, for Arweave/Turbo uploads) is chain-agnostic; only a tx needs the network right.
|
|
396
|
+
const needChain = () => curKind !== 'msg';
|
|
397
|
+
|
|
398
|
+
// Every signature is gated on THREE things being right: the network, the signing wallet,
|
|
399
|
+
// and a tx to sign. The page renders all three and refuses to enable signing unless they hold.
|
|
400
|
+
function renderCtx(){
|
|
401
|
+
const nd=$('netDot'), nt=$('netText');
|
|
402
|
+
if (chainId == null){ nd.className='dot'; nt.className='v'; nt.textContent='Network: connect to check'; }
|
|
403
|
+
else if (chainOk()){ nd.className='dot good'; nt.className='v good'; nt.textContent='Network: '+CFG.chainName+' (chainId '+CFG.chainId+') ✓'; }
|
|
404
|
+
else { nd.className='dot bad'; nt.className='v bad'; nt.innerHTML='Wrong network: wallet is on chainId '+chainId+', this needs <b>'+CFG.chainName+' ('+CFG.chainId+')</b>'; }
|
|
405
|
+
const ad=$('acctDot'), at=$('acctText');
|
|
406
|
+
if (!account){ ad.className='dot'; at.className='v'; at.textContent='Wallet: not connected'; }
|
|
407
|
+
else if (signerOk()){ ad.className='dot good'; at.className='v good'; at.innerHTML='Wallet: '+account+' ✓'+(CFG.expectedSigner?'':' — this wallet will be the owner'); }
|
|
408
|
+
else { ad.className='dot bad'; at.className='v bad'; at.innerHTML='Wrong wallet: connected <b>'+account+'</b>, but this was prepared for <b>'+CFG.expectedSigner+'</b>. Switch accounts in your wallet.'; }
|
|
409
|
+
$('switch').style.display = (account && !chainOk()) ? 'block' : 'none';
|
|
410
|
+
gateSign();
|
|
411
|
+
}
|
|
412
|
+
function gateSign(){
|
|
413
|
+
const hasItem = !!curTx || !!curMsg;
|
|
414
|
+
const ready = hasItem && !signing && signerOk() && (!needChain() || chainOk());
|
|
415
|
+
$('sign').disabled = !ready;
|
|
416
|
+
if (hasItem && !signing && !ready){
|
|
417
|
+
if (needChain() && !chainOk()) status('Can\\'t sign: wrong network. Switch your wallet to '+CFG.chainName+' ('+CFG.chainId+').','err');
|
|
418
|
+
else if (!signerOk()) status('Can\\'t sign: wrong wallet. Switch to '+CFG.expectedSigner+'.','err');
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
function renderTx(t,i){
|
|
422
|
+
curTx=t; curMsg=null; curKind='tx'; curIndex=i; progress(i,'Transaction');
|
|
423
|
+
$('tlabel').style.display='block'; $('tlabel').textContent='This transaction does';
|
|
424
|
+
$('summary').textContent=t.summary;
|
|
425
|
+
$('fields').innerHTML=Object.entries(t.fields).map(([k,v]) =>
|
|
426
|
+
'<tr><td class="k">'+k+'</td><td class="v">'+v+'</td></tr>').join('');
|
|
427
|
+
$('connect').style.display='none';
|
|
428
|
+
$('sign').textContent='Sign & send';
|
|
429
|
+
$('sign').style.display='block';
|
|
430
|
+
if (signerOk() && chainOk()) status('Review the details above, then approve in your wallet.','');
|
|
431
|
+
gateSign();
|
|
432
|
+
}
|
|
433
|
+
function renderMsg(m,i){
|
|
434
|
+
curMsg=m; curTx=null; curKind='msg'; curIndex=i; progress(i,'Signature');
|
|
435
|
+
$('tlabel').style.display='block'; $('tlabel').textContent='This signature authorizes';
|
|
436
|
+
$('summary').textContent=m.summary;
|
|
437
|
+
$('fields').innerHTML='<tr><td class="k">action</td><td class="v">Sign a storage upload (Arweave / Turbo). This is a message signature — <b>no gas, no funds move</b>; the upload is paid from your Turbo credits.</td></tr>';
|
|
438
|
+
$('connect').style.display='none';
|
|
439
|
+
$('sign').textContent='Sign message';
|
|
440
|
+
$('sign').style.display='block';
|
|
441
|
+
if (signerOk()) status('Review, then sign in your wallet (a signature, not a transaction).','');
|
|
442
|
+
gateSign();
|
|
443
|
+
}
|
|
444
|
+
async function readChain(){
|
|
445
|
+
try { chainId = parseInt(await window.ethereum.request({method:'eth_chainId'}),16); } catch(e){ chainId=null; }
|
|
446
|
+
}
|
|
447
|
+
async function trySwitch(){
|
|
448
|
+
try { await window.ethereum.request({method:'wallet_switchEthereumChain', params:[{chainId: toHexChain(CFG.chainId)}]}); }
|
|
449
|
+
catch(e){ status('Couldn\\'t switch automatically — switch to '+CFG.chainName+' ('+CFG.chainId+') in your wallet manually.','err'); }
|
|
450
|
+
await readChain(); renderCtx(); maybeConnect();
|
|
451
|
+
}
|
|
452
|
+
$('switch').onclick = trySwitch;
|
|
453
|
+
// Tell the CLI which wallet is signing — but ONLY once it's the right wallet on the right
|
|
454
|
+
// network, so the deployer/owner the CLI builds can never be a wallet you didn't intend.
|
|
455
|
+
async function maybeConnect(){
|
|
456
|
+
if (connected || !account) return;
|
|
457
|
+
if (!signerOk() || !chainOk()){ gateSign(); return; }
|
|
458
|
+
const r = await fetch('/connect', {method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify({signer: account})});
|
|
459
|
+
if (!r.ok){ const e = await r.json().catch(()=>({})); status('Rejected: '+(e.error||'signer mismatch'),'err'); return; }
|
|
460
|
+
connected=true;
|
|
461
|
+
status('Connected '+account+' on '+CFG.chainName+'. Waiting for the first transaction…','ok');
|
|
462
|
+
poll();
|
|
463
|
+
}
|
|
464
|
+
$('connect').onclick = async () => {
|
|
465
|
+
const eth = window.ethereum;
|
|
466
|
+
if (!eth){ status('No injected wallet found. Install MetaMask (or use the hot lane: <code>--send</code>).','err'); return; }
|
|
467
|
+
try {
|
|
468
|
+
const accs = await eth.request({method:'eth_requestAccounts'});
|
|
469
|
+
account = accs[0] || null;
|
|
470
|
+
await readChain();
|
|
471
|
+
if (!chainOk()) await trySwitch(); else { renderCtx(); maybeConnect(); }
|
|
472
|
+
} catch(e){ status(e.message||String(e),'err'); }
|
|
473
|
+
};
|
|
474
|
+
$('sign').onclick = async () => {
|
|
475
|
+
if ((!curTx && !curMsg) || signing) return;
|
|
476
|
+
// Re-verify LIVE against the wallet right before signing — the user may have switched the
|
|
477
|
+
// account or network since we last checked. Never send a tx to the wrong place.
|
|
478
|
+
try {
|
|
479
|
+
const live = (await window.ethereum.request({method:'eth_accounts'}))[0] || null;
|
|
480
|
+
if (live) account = live;
|
|
481
|
+
await readChain(); renderCtx();
|
|
482
|
+
} catch(e){}
|
|
483
|
+
if (!signerOk() || (needChain() && !chainOk())){ gateSign(); return; }
|
|
484
|
+
signing=true; $('sign').disabled=true;
|
|
485
|
+
try {
|
|
486
|
+
if (curKind==='msg'){
|
|
487
|
+
// Sign the Arweave data-item's signature-data with the wallet (EIP-191). No gas, no chain.
|
|
488
|
+
const signature = await window.ethereum.request({method:'personal_sign', params:[curMsg.messageHex, account]});
|
|
489
|
+
await fetch('/signed', {method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify({index: curIndex, signature})});
|
|
490
|
+
status('Signed ✓ — continuing…','ok');
|
|
491
|
+
$('sign').style.display='none'; curMsg=null; signing=false;
|
|
492
|
+
} else {
|
|
493
|
+
const params = {from: account, data: curTx.data, value: curTx.value};
|
|
494
|
+
if (curTx.to) params.to = curTx.to;
|
|
495
|
+
const txHash = await window.ethereum.request({method:'eth_sendTransaction', params:[params]});
|
|
496
|
+
await fetch('/signed', {method:'POST', headers:{'content-type':'application/json'}, body: JSON.stringify({index: curIndex, txHash})});
|
|
497
|
+
const link = CFG.explorer ? '<a href="'+CFG.explorer+'/tx/'+txHash+'" target="_blank">'+txHash+'</a>' : txHash;
|
|
498
|
+
status('Sent ✓ '+link+'<br>Confirming on-chain…','ok');
|
|
499
|
+
$('sign').style.display='none'; curTx=null; signing=false;
|
|
500
|
+
}
|
|
501
|
+
} catch(e){ status((e&&e.message)||String(e),'err'); signing=false; gateSign(); }
|
|
502
|
+
};
|
|
503
|
+
if (window.ethereum){
|
|
504
|
+
window.ethereum.on && window.ethereum.on('accountsChanged', (accs)=>{ account=(accs&&accs[0])||null; renderCtx(); maybeConnect(); });
|
|
505
|
+
window.ethereum.on && window.ethereum.on('chainChanged', async ()=>{ await readChain(); renderCtx(); maybeConnect(); });
|
|
506
|
+
}
|
|
507
|
+
renderCtx();
|
|
508
|
+
async function poll(){
|
|
509
|
+
if (stopped) return;
|
|
510
|
+
try {
|
|
511
|
+
const r = await fetch('/next'); const d = await r.json();
|
|
512
|
+
if (d.done){ stopped=true; progress(0); $('tlabel').style.display='none'; $('summary').textContent='All done — everything signed.'; $('fields').innerHTML=''; status('Done ✓ — you can close this tab.','ok'); return; }
|
|
513
|
+
if (d.tx && d.index !== curIndex && !signing) renderTx(d.tx, d.index);
|
|
514
|
+
else if (d.msg && d.index !== curIndex && !signing) renderMsg(d.msg, d.index);
|
|
515
|
+
} catch(e){ /* server finishing/closing — keep trying briefly */ }
|
|
516
|
+
if (!stopped) setTimeout(poll, 700);
|
|
517
|
+
}
|
|
518
|
+
</script></body></html>`;
|
|
519
|
+
}
|
|
520
|
+
//# sourceMappingURL=signer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signer.js","sourceRoot":"","sources":["../src/signer.ts"],"names":[],"mappings":"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"}
|
package/dist/upload.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `abx storage upload` core — upload ONE file and return a public locator that PRESERVES the
|
|
3
|
+
* filename, so the declared mimeType survives when the locator is later attached (`abx attach`).
|
|
4
|
+
* The on-chain metadata field has no MIME slot: the URL extension IS the type declaration, so a
|
|
5
|
+
* bare content-addressed locator (`ar://<txid>`, `<gw>/ipfs/<cid>`) — which has no extension —
|
|
6
|
+
* would make the attached artifact `application/octet-stream`. Extracted from main.ts so the
|
|
7
|
+
* capability-based branch (the exact bug this guards) is unit-testable with fake backends.
|
|
8
|
+
*/
|
|
9
|
+
import type { StorageBackend, StoredContent } from '@artblocks/abx-storage';
|
|
10
|
+
export interface UploadResult {
|
|
11
|
+
/** A fetchable public URL for the file. */
|
|
12
|
+
locator: string;
|
|
13
|
+
/** True when `locator` carries the filename (extension → correct declared type on attach). */
|
|
14
|
+
filenamePreserved: boolean;
|
|
15
|
+
/** Set when we had to fall back to a bare (extension-less) locator — the caller warns. */
|
|
16
|
+
fallbackReason?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Store `content` under `name` and return the best available public locator. Preference order:
|
|
20
|
+
* 1. path-addressed host (cloud/S3/R2): the object key IS the filename → `<publicBase>/<name>`.
|
|
21
|
+
* 2. content-addressed host with directory support (ipfs/arweave): wrap the single file in a
|
|
22
|
+
* one-entry directory → `<base>/<name>` (filename preserved).
|
|
23
|
+
* 3. bare content-addressed locator (e.g. kubo IPFS with no dir-add): `<gw>/ipfs/<cid>` — no
|
|
24
|
+
* extension; `filenamePreserved: false` so the caller can warn about the octet-stream type.
|
|
25
|
+
* Throws for a backend that can't produce any public URL (e.g. `fs`).
|
|
26
|
+
*/
|
|
27
|
+
export declare function uploadAndLocate(backend: StorageBackend, name: string, content: StoredContent): Promise<UploadResult>;
|
|
28
|
+
//# sourceMappingURL=upload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAC,cAAc,EAAE,aAAa,EAAC,MAAM,wBAAwB,CAAC;AAG1E,MAAM,WAAW,YAAY;IAC3B,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,8FAA8F;IAC9F,iBAAiB,EAAE,OAAO,CAAC;IAC3B,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAID;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAyB1H"}
|
package/dist/upload.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { hashContent } from '@artblocks/abx-storage';
|
|
2
|
+
const trimSlash = (s) => s.replace(/\/+$/, '');
|
|
3
|
+
/**
|
|
4
|
+
* Store `content` under `name` and return the best available public locator. Preference order:
|
|
5
|
+
* 1. path-addressed host (cloud/S3/R2): the object key IS the filename → `<publicBase>/<name>`.
|
|
6
|
+
* 2. content-addressed host with directory support (ipfs/arweave): wrap the single file in a
|
|
7
|
+
* one-entry directory → `<base>/<name>` (filename preserved).
|
|
8
|
+
* 3. bare content-addressed locator (e.g. kubo IPFS with no dir-add): `<gw>/ipfs/<cid>` — no
|
|
9
|
+
* extension; `filenamePreserved: false` so the caller can warn about the octet-stream type.
|
|
10
|
+
* Throws for a backend that can't produce any public URL (e.g. `fs`).
|
|
11
|
+
*/
|
|
12
|
+
export async function uploadAndLocate(backend, name, content) {
|
|
13
|
+
if (backend.publicBase && backend.putObject) {
|
|
14
|
+
await backend.putObject(name, content);
|
|
15
|
+
return { locator: `${trimSlash(backend.publicBase)}/${name}`, filenamePreserved: true };
|
|
16
|
+
}
|
|
17
|
+
if (backend.putDirectory && backend.locator) {
|
|
18
|
+
try {
|
|
19
|
+
const { base } = await backend.putDirectory([{ name, bytes: content.bytes, contentType: content.contentType }]);
|
|
20
|
+
return { locator: `${trimSlash(base)}/${name}`, filenamePreserved: true };
|
|
21
|
+
}
|
|
22
|
+
catch (e) {
|
|
23
|
+
const hash = hashContent(content.bytes);
|
|
24
|
+
await backend.put(hash, content);
|
|
25
|
+
const loc = await backend.locator(hash);
|
|
26
|
+
if (!loc)
|
|
27
|
+
throw e;
|
|
28
|
+
return { locator: loc, filenamePreserved: false, fallbackReason: e.message };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (backend.locator) {
|
|
32
|
+
const hash = hashContent(content.bytes);
|
|
33
|
+
await backend.put(hash, content);
|
|
34
|
+
const loc = await backend.locator(hash);
|
|
35
|
+
if (!loc)
|
|
36
|
+
throw new Error(`backend '${backend.id}' stored the file but returned no durable locator`);
|
|
37
|
+
return { locator: loc, filenamePreserved: false };
|
|
38
|
+
}
|
|
39
|
+
throw new Error(`backend '${backend.id}' can't produce a public URL for an attached file — use ipfs, arweave, or cloud (with a public base).`);
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=upload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload.js","sourceRoot":"","sources":["../src/upload.ts"],"names":[],"mappings":"AASA,OAAO,EAAC,WAAW,EAAC,MAAM,wBAAwB,CAAC;AAWnD,MAAM,SAAS,GAAG,CAAC,CAAS,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AAEvD;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAuB,EAAE,IAAY,EAAE,OAAsB;IACjG,IAAI,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QAC5C,MAAM,OAAO,CAAC,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACvC,OAAO,EAAC,OAAO,EAAE,GAAG,SAAS,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,IAAI,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAC,CAAC;IACxF,CAAC;IACD,IAAI,OAAO,CAAC,YAAY,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QAC5C,IAAI,CAAC;YACH,MAAM,EAAC,IAAI,EAAC,GAAG,MAAM,OAAO,CAAC,YAAY,CAAC,CAAC,EAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,CAAC,WAAW,EAAC,CAAC,CAAC,CAAC;YAC5G,OAAO,EAAC,OAAO,EAAE,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,iBAAiB,EAAE,IAAI,EAAC,CAAC;QAC1E,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YACxC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACjC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,CAAC,GAAG;gBAAE,MAAM,CAAC,CAAC;YAClB,OAAO,EAAC,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAE,cAAc,EAAG,CAAW,CAAC,OAAO,EAAC,CAAC;QACxF,CAAC;IACH,CAAC;IACD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACxC,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACjC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,OAAO,CAAC,EAAE,mDAAmD,CAAC,CAAC;QACrG,OAAO,EAAC,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,KAAK,EAAC,CAAC;IAClD,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,YAAY,OAAO,CAAC,EAAE,uGAAuG,CAAC,CAAC;AACjJ,CAAC"}
|