@1sat/permission-module 0.0.50 → 0.0.51
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/apply.d.ts +1 -1
- package/dist/apply.d.ts.map +1 -1
- package/dist/apply.js +3 -3
- package/dist/apply.js.map +1 -1
- package/dist/apply.test.js +29 -29
- package/dist/apply.test.js.map +1 -1
- package/dist/basketAccess.d.ts +38 -0
- package/dist/basketAccess.d.ts.map +1 -0
- package/dist/basketAccess.js +237 -0
- package/dist/basketAccess.js.map +1 -0
- package/dist/buildPromptIntent.d.ts +15 -0
- package/dist/buildPromptIntent.d.ts.map +1 -0
- package/dist/buildPromptIntent.js +575 -0
- package/dist/buildPromptIntent.js.map +1 -0
- package/dist/createOneSatPermissionModule.d.ts +10 -5
- package/dist/createOneSatPermissionModule.d.ts.map +1 -1
- package/dist/createOneSatPermissionModule.js +28 -6
- package/dist/createOneSatPermissionModule.js.map +1 -1
- package/dist/enrichIntent.d.ts +121 -5
- package/dist/enrichIntent.d.ts.map +1 -1
- package/dist/enrichIntent.js +576 -137
- package/dist/enrichIntent.js.map +1 -1
- package/dist/handlers.d.ts +12 -32
- package/dist/handlers.d.ts.map +1 -1
- package/dist/handlers.js +120 -155
- package/dist/handlers.js.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/listOutputsScope.test.d.ts +2 -0
- package/dist/listOutputsScope.test.d.ts.map +1 -0
- package/dist/listOutputsScope.test.js +116 -0
- package/dist/listOutputsScope.test.js.map +1 -0
- package/dist/promptModel.d.ts +115 -0
- package/dist/promptModel.d.ts.map +1 -0
- package/dist/promptModel.js +7 -0
- package/dist/promptModel.js.map +1 -0
- package/dist/types.d.ts +36 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/verifyIntent.d.ts +4 -4
- package/dist/verifyIntent.d.ts.map +1 -1
- package/dist/verifyIntent.js +120 -40
- package/dist/verifyIntent.js.map +1 -1
- package/dist/viewScope.d.ts +51 -0
- package/dist/viewScope.d.ts.map +1 -0
- package/dist/viewScope.js +113 -0
- package/dist/viewScope.js.map +1 -0
- package/dist/viewScope.test.d.ts +2 -0
- package/dist/viewScope.test.d.ts.map +1 -0
- package/dist/viewScope.test.js +60 -0
- package/dist/viewScope.test.js.map +1 -0
- package/package.json +42 -42
package/dist/enrichIntent.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* before dispatching to permission modules — so we can't read them.
|
|
8
8
|
* What's left:
|
|
9
9
|
*
|
|
10
|
-
* - `args.labels` — unencrypted; carries our `'p
|
|
11
|
-
*
|
|
10
|
+
* - `args.labels` — unencrypted; carries our `'p <scheme> input <key>'`
|
|
11
|
+
* lookup keys
|
|
12
12
|
* - `args.outputs[i].lockingScript` — decode P2PKH for recipient address
|
|
13
13
|
* - `args.outputs[i].satoshis`
|
|
14
14
|
*
|
|
@@ -18,10 +18,15 @@
|
|
|
18
18
|
* committed to the final tx). The dApp can't lie about what the user
|
|
19
19
|
* is being asked to approve.
|
|
20
20
|
*/
|
|
21
|
-
import { BAP_BASKET, BSOCIAL_BASKET,
|
|
22
|
-
import { Lock, OrdLock, outpointFromBytes } from '@1sat/templates';
|
|
21
|
+
import { BAP_BASKET, BSOCIAL_BASKET, BSV21_BASKET, LOCK_BASKET, ORDFS_HOST, OPNS_BASKET, ORDINALS_BASKET, SIGMA_BASKET, formatOrdinalOutpoint, parseInputAssetLabels, } from '@1sat/types';
|
|
22
|
+
import { BSV21, Inscription, Lock, OrdLock, outpointFromBytes, Sigma, } from '@1sat/templates';
|
|
23
23
|
import { parseAddress } from '@1sat/wallet';
|
|
24
24
|
import { LockingScript, PushDrop, Script, Utils } from '@bsv/sdk';
|
|
25
|
+
/** Same protocol WPM uses for description/CI encryption. */
|
|
26
|
+
const METADATA_ENCRYPTION_PROTOCOL = [
|
|
27
|
+
2,
|
|
28
|
+
'admin metadata encryption',
|
|
29
|
+
];
|
|
25
30
|
/**
|
|
26
31
|
* Presentation fields on an OpNS bind: field 0 is the identity key and the
|
|
27
32
|
* last is the signature, so field 1 is the display name and field 2 the
|
|
@@ -52,7 +57,6 @@ function decodeOpnsProfile(script) {
|
|
|
52
57
|
const ASSET_BASKETS = [
|
|
53
58
|
ORDINALS_BASKET,
|
|
54
59
|
BSV21_BASKET,
|
|
55
|
-
BSV21_AUTH_BASKET,
|
|
56
60
|
LOCK_BASKET,
|
|
57
61
|
OPNS_BASKET,
|
|
58
62
|
BSOCIAL_BASKET,
|
|
@@ -67,19 +71,27 @@ export async function enrichIntent(wallet, args, opts = {}) {
|
|
|
67
71
|
? (origin) => ordfs.getContentUrl?.(origin)
|
|
68
72
|
: undefined;
|
|
69
73
|
const labels = args.labels ?? [];
|
|
70
|
-
const
|
|
71
|
-
const inputRefs = parseAssetLabels(labels, P1SAT_INPUT_LABEL_PREFIX);
|
|
74
|
+
const inputRefs = parseInputAssetLabels(labels);
|
|
72
75
|
const inputs = (await Promise.all(inputRefs.map((ref) => lookupAsset(wallet, ref.basket, ref.id)))).filter((a) => a !== null);
|
|
73
|
-
const outputs = (args.outputs ?? []).map((out, i) =>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
76
|
+
const outputs = await Promise.all((args.outputs ?? []).map(async (out, i) => {
|
|
77
|
+
const decoded = decodeOutput(out, i, chain);
|
|
78
|
+
if (decoded.customInstructions) {
|
|
79
|
+
decoded.customInstructions = await maybeDecryptCustomInstructions(wallet, decoded.customInstructions);
|
|
80
|
+
}
|
|
81
|
+
return decoded;
|
|
82
|
+
}));
|
|
83
|
+
const kind = detectKind(inputs, outputs);
|
|
84
|
+
const summary = buildSummary(kind, inputs, outputs);
|
|
85
|
+
const trust = initialTrust(kind);
|
|
77
86
|
const fee = extractIndexerFee(args, outputs);
|
|
87
|
+
const ordinalEdges = buildOrdinalEdges(inputs, outputs);
|
|
88
|
+
const legs = buildLegs(inputs, outputs, ordinalEdges);
|
|
78
89
|
return {
|
|
79
90
|
kind,
|
|
80
|
-
p1satIntent,
|
|
81
91
|
inputs,
|
|
82
92
|
outputs,
|
|
93
|
+
legs,
|
|
94
|
+
ordinalEdges,
|
|
83
95
|
labels,
|
|
84
96
|
summary,
|
|
85
97
|
contentUrlForOrigin: (origin) => getContentUrl?.(origin) ?? contentUrlFromOrigin(ORDFS_HOST, origin),
|
|
@@ -96,14 +108,11 @@ export async function enrichIntent(wallet, args, opts = {}) {
|
|
|
96
108
|
* meaningless or a lie, and outputs are dApp-authored besides. Nothing
|
|
97
109
|
* persisted may influence the badge.
|
|
98
110
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
111
|
+
* Purchases and BSV21 transfers start `unverified`; `verifyIntent` upgrades
|
|
112
|
+
* them when the overlay positively answers (token active + inputs valid).
|
|
101
113
|
*/
|
|
102
|
-
function initialTrust(
|
|
103
|
-
|
|
104
|
-
p1satIntent === 'opns.purchase' ||
|
|
105
|
-
p1satIntent === 'bsv21.purchase';
|
|
106
|
-
if (!isPurchase)
|
|
114
|
+
function initialTrust(kind) {
|
|
115
|
+
if (kind !== 'purchase' && kind !== 'token-transfer')
|
|
107
116
|
return undefined;
|
|
108
117
|
return { state: 'unverified' };
|
|
109
118
|
}
|
|
@@ -143,26 +152,43 @@ function extractIndexerFee(args, outputs) {
|
|
|
143
152
|
: `${feeSats.toLocaleString()} sats overlay fee`;
|
|
144
153
|
return { indexerFeeSats: feeSats, indexerFeeNote: note };
|
|
145
154
|
}
|
|
146
|
-
function parseAssetLabels(labels, prefix) {
|
|
147
|
-
const refs = [];
|
|
148
|
-
for (const label of labels) {
|
|
149
|
-
if (!label.startsWith(prefix))
|
|
150
|
-
continue;
|
|
151
|
-
const payload = label.slice(prefix.length).trim();
|
|
152
|
-
const sep = payload.indexOf(' ');
|
|
153
|
-
if (sep <= 0)
|
|
154
|
-
continue;
|
|
155
|
-
const suffix = payload.slice(0, sep);
|
|
156
|
-
const id = payload.slice(sep + 1).trim();
|
|
157
|
-
if (!suffix || !id)
|
|
158
|
-
continue;
|
|
159
|
-
refs.push({ basket: `${P1SAT_BASKET_PREFIX}${suffix}`, id });
|
|
160
|
-
}
|
|
161
|
-
return refs;
|
|
162
|
-
}
|
|
163
155
|
// ---------------------------------------------------------------------------
|
|
164
156
|
// Asset lookup — indexed `listOutputs` query by `id:<id>` tag.
|
|
165
157
|
// ---------------------------------------------------------------------------
|
|
158
|
+
/**
|
|
159
|
+
* WPM may encrypt customInstructions before storage / before module onRequest.
|
|
160
|
+
* Base-wallet listOutputs returns ciphertext. Never mutate plaintext:
|
|
161
|
+
* 1. If it already parses as JSON → return as-is (base-wallet writes).
|
|
162
|
+
* 2. Else try decrypt; on failure return original unchanged.
|
|
163
|
+
*/
|
|
164
|
+
async function maybeDecryptCustomInstructions(wallet, value) {
|
|
165
|
+
if (!value)
|
|
166
|
+
return undefined;
|
|
167
|
+
// Fast path: plaintext JSON (or anything that is already valid JSON).
|
|
168
|
+
try {
|
|
169
|
+
JSON.parse(value);
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// not JSON — may be WPM base64 ciphertext
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
const { plaintext } = await wallet.decrypt({
|
|
177
|
+
ciphertext: Utils.toArray(value, 'base64'),
|
|
178
|
+
protocolID: METADATA_ENCRYPTION_PROTOCOL,
|
|
179
|
+
keyID: '1',
|
|
180
|
+
counterparty: 'self',
|
|
181
|
+
});
|
|
182
|
+
const text = Utils.toUTF8(plaintext);
|
|
183
|
+
// Only accept decrypt if result is usable JSON — avoids "decrypting"
|
|
184
|
+
// random strings into garbage we then treat as CI.
|
|
185
|
+
JSON.parse(text);
|
|
186
|
+
return text;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return value;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
166
192
|
async function lookupAsset(wallet, basket, id) {
|
|
167
193
|
// Only query baskets the module knows about — guards against malicious
|
|
168
194
|
// labels pointing at unrelated baskets.
|
|
@@ -180,13 +206,15 @@ async function lookupAsset(wallet, basket, id) {
|
|
|
180
206
|
const match = result.outputs[0];
|
|
181
207
|
if (!match)
|
|
182
208
|
return null;
|
|
209
|
+
const rawCi = match.customInstructions;
|
|
210
|
+
const customInstructions = await maybeDecryptCustomInstructions(wallet, rawCi);
|
|
183
211
|
return {
|
|
184
212
|
basket,
|
|
185
213
|
id,
|
|
186
214
|
outpoint: match.outpoint,
|
|
187
215
|
satoshis: match.satoshis,
|
|
188
216
|
tags: match.tags ?? [],
|
|
189
|
-
customInstructions
|
|
217
|
+
customInstructions,
|
|
190
218
|
};
|
|
191
219
|
}
|
|
192
220
|
catch {
|
|
@@ -202,6 +230,9 @@ function decodeOutput(out, index, chain) {
|
|
|
202
230
|
satoshis: out.satoshis ?? 0,
|
|
203
231
|
basket: out.basket,
|
|
204
232
|
tags: out.tags ?? [],
|
|
233
|
+
template: 'unrecognized',
|
|
234
|
+
customInstructions: out.customInstructions,
|
|
235
|
+
...(out.lockingScript ? { lockingScript: out.lockingScript } : {}),
|
|
205
236
|
};
|
|
206
237
|
if (!out.lockingScript)
|
|
207
238
|
return enriched;
|
|
@@ -212,32 +243,511 @@ function decodeOutput(out, index, chain) {
|
|
|
212
243
|
catch {
|
|
213
244
|
return enriched;
|
|
214
245
|
}
|
|
215
|
-
const
|
|
246
|
+
const classified = classifyScript(script, chain, out.basket);
|
|
247
|
+
Object.assign(enriched, classified);
|
|
248
|
+
Object.assign(enriched, inscriptionPreviewFromScript(script));
|
|
249
|
+
if (out.basket === OPNS_BASKET || classified.template === 'pushdrop') {
|
|
250
|
+
Object.assign(enriched, decodeOpnsProfile(script));
|
|
251
|
+
}
|
|
252
|
+
// Tag fallbacks when script didn't carry sym/id (self-kept rows).
|
|
253
|
+
if (!enriched.tokenId) {
|
|
254
|
+
const tid = tagValue(enriched.tags, 'bsv21');
|
|
255
|
+
if (tid && tid !== 'deploy')
|
|
256
|
+
enriched.tokenId = tid;
|
|
257
|
+
}
|
|
258
|
+
if (!enriched.tokenAmt) {
|
|
259
|
+
const amt = tagValue(enriched.tags, 'amt');
|
|
260
|
+
if (amt)
|
|
261
|
+
enriched.tokenAmt = amt;
|
|
262
|
+
}
|
|
263
|
+
if (!enriched.tokenSym) {
|
|
264
|
+
const sym = displayFieldFrom('sym', enriched.tags, enriched.customInstructions);
|
|
265
|
+
if (sym)
|
|
266
|
+
enriched.tokenSym = sym;
|
|
267
|
+
}
|
|
268
|
+
if (enriched.tags.includes('fee:overlay') ||
|
|
269
|
+
enriched.tags.some((t) => t.startsWith('indexer-fee'))) {
|
|
270
|
+
enriched.isIndexerFee = true;
|
|
271
|
+
}
|
|
272
|
+
return enriched;
|
|
273
|
+
}
|
|
274
|
+
function classifyScript(script, chain, basket) {
|
|
275
|
+
const main = chain === 'mainnet';
|
|
276
|
+
const ordLock = OrdLock.decode(script, main);
|
|
216
277
|
if (ordLock) {
|
|
217
|
-
|
|
218
|
-
|
|
278
|
+
return {
|
|
279
|
+
template: 'ordlock',
|
|
280
|
+
listingPriceSats: Number(ordLock.price),
|
|
281
|
+
listingSeller: ordLock.seller,
|
|
282
|
+
};
|
|
219
283
|
}
|
|
220
|
-
const lock = Lock.decode(script,
|
|
284
|
+
const lock = Lock.decode(script, main);
|
|
221
285
|
if (lock) {
|
|
222
|
-
|
|
286
|
+
return {
|
|
287
|
+
template: 'lock',
|
|
288
|
+
lockUntilHeight: lock.until,
|
|
289
|
+
};
|
|
223
290
|
}
|
|
224
|
-
|
|
225
|
-
|
|
291
|
+
const bsv21 = BSV21.decode(script);
|
|
292
|
+
if (bsv21) {
|
|
293
|
+
const d = bsv21.tokenData;
|
|
294
|
+
// amt is the BSV21 inscription amount (base units), never satoshis.
|
|
295
|
+
const tokenAmt = d.amt != null && String(d.amt).length > 0 ? String(d.amt) : undefined;
|
|
296
|
+
return {
|
|
297
|
+
template: 'bsv21',
|
|
298
|
+
...(d.id ? { tokenId: d.id } : {}),
|
|
299
|
+
...(tokenAmt ? { tokenAmt } : {}),
|
|
300
|
+
...(d.sym ? { tokenSym: d.sym } : {}),
|
|
301
|
+
...(d.op ? { tokenOp: d.op } : {}),
|
|
302
|
+
...p2pkhRecipient(script, chain),
|
|
303
|
+
};
|
|
226
304
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
305
|
+
if (scriptContainsAscii(script, 'SIGMA') || Sigma.countInstances(script) > 0) {
|
|
306
|
+
const sealPending = hasZeroedSigmaSig(script);
|
|
307
|
+
return {
|
|
308
|
+
template: 'sigma',
|
|
309
|
+
sealKind: 'sigma',
|
|
310
|
+
...(sealPending ? { sealPending: true } : {}),
|
|
311
|
+
...p2pkhRecipient(script, chain),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
const fields = PushDrop.decode(LockingScript.fromHex(script.toHex())).fields;
|
|
316
|
+
if (fields.length >= 2) {
|
|
317
|
+
const last = fields[fields.length - 1];
|
|
318
|
+
const sealPending = !!last?.length && last.every((b) => b === 0);
|
|
319
|
+
return {
|
|
320
|
+
template: 'pushdrop',
|
|
321
|
+
...(sealPending
|
|
322
|
+
? { sealPending: true, sealKind: 'pushdrop' }
|
|
323
|
+
: {}),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch {
|
|
328
|
+
// not PushDrop
|
|
329
|
+
}
|
|
330
|
+
const recipient = p2pkhRecipient(script, chain);
|
|
331
|
+
if (recipient.recipient) {
|
|
332
|
+
return { template: 'p2pkh', ...recipient };
|
|
231
333
|
}
|
|
232
|
-
|
|
233
|
-
|
|
334
|
+
return { template: basket ? 'unrecognized' : 'unrecognized' };
|
|
335
|
+
}
|
|
336
|
+
const PREVIEW_TEXT_MAX = 800;
|
|
337
|
+
function inscriptionPreviewFromScript(script) {
|
|
338
|
+
const insc = Inscription.decode(script);
|
|
339
|
+
if (!insc?.file)
|
|
340
|
+
return {};
|
|
341
|
+
const type = insc.file.type.split(';')[0]?.trim() || '';
|
|
342
|
+
const bytes = Array.from(insc.file.content);
|
|
343
|
+
if (bytes.length === 0) {
|
|
344
|
+
return type ? { inscriptionType: type } : {};
|
|
345
|
+
}
|
|
346
|
+
const lower = type.toLowerCase();
|
|
347
|
+
if (lower.startsWith('image/')) {
|
|
348
|
+
return {
|
|
349
|
+
inscriptionType: type,
|
|
350
|
+
inscriptionDataUrl: `data:${type};base64,${Utils.toBase64(bytes)}`,
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
try {
|
|
354
|
+
let text = Utils.toUTF8(bytes);
|
|
355
|
+
if (lower.includes('json')) {
|
|
356
|
+
try {
|
|
357
|
+
text = JSON.stringify(JSON.parse(text), null, 2);
|
|
358
|
+
}
|
|
359
|
+
catch {
|
|
360
|
+
// keep raw
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
inscriptionType: type,
|
|
365
|
+
inscriptionText: text.slice(0, PREVIEW_TEXT_MAX),
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return type ? { inscriptionType: type } : {};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function p2pkhRecipient(script, chain) {
|
|
373
|
+
const recipient = parseAddress(script, 0, chain);
|
|
374
|
+
if (recipient)
|
|
375
|
+
return { recipient };
|
|
234
376
|
const endifIndex = script.chunks.findIndex((c) => c.op === 0x68);
|
|
235
377
|
if (endifIndex > 0) {
|
|
236
378
|
const after = parseAddress(script, endifIndex + 1, chain);
|
|
237
379
|
if (after)
|
|
238
|
-
|
|
380
|
+
return { recipient: after };
|
|
239
381
|
}
|
|
240
|
-
return
|
|
382
|
+
return {};
|
|
383
|
+
}
|
|
384
|
+
function scriptContainsAscii(script, ascii) {
|
|
385
|
+
const needle = Utils.toArray(ascii);
|
|
386
|
+
const hay = script.toBinary();
|
|
387
|
+
if (hay.length < needle.length)
|
|
388
|
+
return false;
|
|
389
|
+
outer: for (let i = 0; i <= hay.length - needle.length; i++) {
|
|
390
|
+
for (let j = 0; j < needle.length; j++) {
|
|
391
|
+
if (hay[i + j] !== needle[j])
|
|
392
|
+
continue outer;
|
|
393
|
+
}
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* True when a SIGMA tape's signature push is still the zero placeholder.
|
|
400
|
+
* Parses the real tape (`SIGMA · algo · address · sig · vin`) via
|
|
401
|
+
* {@link Sigma.parseFromScript} — not a raw zero-run hunt after the marker
|
|
402
|
+
* (BSM + address sit between SIGMA and the sig field).
|
|
403
|
+
*/
|
|
404
|
+
function hasZeroedSigmaSig(script) {
|
|
405
|
+
try {
|
|
406
|
+
const sigs = Sigma.parseFromScript(script);
|
|
407
|
+
for (const s of sigs) {
|
|
408
|
+
const bytes = Utils.toArray(s.signature, 'base64');
|
|
409
|
+
if (bytes.length > 0 && bytes.every((b) => b === 0))
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch {
|
|
414
|
+
// unparseable tape
|
|
415
|
+
}
|
|
416
|
+
return false;
|
|
417
|
+
}
|
|
418
|
+
/** Prefer case-preserving CI name over lowercased `name:` tags. */
|
|
419
|
+
function displayNameFrom(tags, customInstructions, scriptName) {
|
|
420
|
+
if (scriptName)
|
|
421
|
+
return scriptName;
|
|
422
|
+
if (customInstructions) {
|
|
423
|
+
try {
|
|
424
|
+
const n = JSON.parse(customInstructions).name;
|
|
425
|
+
if (typeof n === 'string' && n.trim())
|
|
426
|
+
return n.trim();
|
|
427
|
+
}
|
|
428
|
+
catch {
|
|
429
|
+
// ignore
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return tagValue(tags, 'name');
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Case-preserving field from customInstructions (BRC-100 lowercases tags).
|
|
436
|
+
* Falls back to the matching tag when CI is missing.
|
|
437
|
+
*/
|
|
438
|
+
function displayFieldFrom(field, tags, customInstructions) {
|
|
439
|
+
if (customInstructions) {
|
|
440
|
+
try {
|
|
441
|
+
const v = JSON.parse(customInstructions)[field];
|
|
442
|
+
if (typeof v === 'string' && v.trim())
|
|
443
|
+
return v.trim();
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
// ignore
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
return tagValue(tags, field);
|
|
450
|
+
}
|
|
451
|
+
function isCollectableBasket(basket) {
|
|
452
|
+
return (basket === ORDINALS_BASKET ||
|
|
453
|
+
basket === OPNS_BASKET ||
|
|
454
|
+
// plain-name future + legacy
|
|
455
|
+
basket === '1sat' ||
|
|
456
|
+
!!basket?.includes('ordinal') ||
|
|
457
|
+
!!basket?.includes('opns'));
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Pair collectable spends with their next tip/listing (BRC-303 shapes).
|
|
461
|
+
*/
|
|
462
|
+
function buildOrdinalEdges(inputs, outputs) {
|
|
463
|
+
const edges = [];
|
|
464
|
+
const usedOut = new Set();
|
|
465
|
+
const takeOut = (pred) => {
|
|
466
|
+
const hit = outputs.find((o) => !usedOut.has(o.index) && pred(o));
|
|
467
|
+
if (hit)
|
|
468
|
+
usedOut.add(hit.index);
|
|
469
|
+
return hit;
|
|
470
|
+
};
|
|
471
|
+
const collectableIns = inputs.filter((i) => isCollectableBasket(i.basket) || hasListingTags(i.tags));
|
|
472
|
+
const usedIn = new Set();
|
|
473
|
+
for (const inp of collectableIns) {
|
|
474
|
+
usedIn.add(inp.id);
|
|
475
|
+
const name = displayNameFrom(inp.tags, inp.customInstructions);
|
|
476
|
+
const origin = tagValue(inp.tags, 'origin');
|
|
477
|
+
const spend = {
|
|
478
|
+
basket: inp.basket,
|
|
479
|
+
id: inp.id,
|
|
480
|
+
outpoint: inp.outpoint,
|
|
481
|
+
satoshis: inp.satoshis,
|
|
482
|
+
tags: inp.tags,
|
|
483
|
+
...(name ? { name } : {}),
|
|
484
|
+
...(origin ? { origin } : {}),
|
|
485
|
+
isListing: hasListingTags(inp.tags),
|
|
486
|
+
};
|
|
487
|
+
if (hasListingTags(inp.tags)) {
|
|
488
|
+
// cancel: listing in → held collectable out (not ordlock)
|
|
489
|
+
const create = takeOut((o) => isCollectableBasket(o.basket) &&
|
|
490
|
+
o.template !== 'ordlock' &&
|
|
491
|
+
o.satoshis === 1);
|
|
492
|
+
edges.push(edge('cancel-listing', spend, create, name ? `Cancel listing of “${name}”` : 'Cancel listing', 'Return collectable from marketplace lock'));
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
const listOut = takeOut((o) => o.template === 'ordlock' && o.satoshis === 1);
|
|
496
|
+
if (listOut) {
|
|
497
|
+
const price = listOut.listingPriceSats;
|
|
498
|
+
edges.push(edge('list', spend, listOut, name ? `List “${name}”` : 'List collectable', price != null
|
|
499
|
+
? `List for ${price} sats`
|
|
500
|
+
: 'List on marketplace lock'));
|
|
501
|
+
continue;
|
|
502
|
+
}
|
|
503
|
+
// OpNS register / unregister.
|
|
504
|
+
const isOpnsSpend = inp.basket === OPNS_BASKET ||
|
|
505
|
+
!!inp.basket?.includes('opns') ||
|
|
506
|
+
inp.tags.some((t) => t.startsWith('opns:') || t === 'opns');
|
|
507
|
+
if (isOpnsSpend) {
|
|
508
|
+
const registerOut = takeOut((o) => o.template === 'pushdrop' &&
|
|
509
|
+
(o.sealPending ||
|
|
510
|
+
o.tags.includes('opns:published') ||
|
|
511
|
+
o.basket === OPNS_BASKET ||
|
|
512
|
+
!!o.basket?.includes('opns')));
|
|
513
|
+
if (registerOut) {
|
|
514
|
+
// Domain is the owned OpNS name; profile is presentation only.
|
|
515
|
+
const domain = name;
|
|
516
|
+
edges.push(edge('register', spend, registerOut, domain ? `Publish “${domain}”` : 'Publish OpNS name', registerOut.sealPending
|
|
517
|
+
? 'Sign PushDrop identity bind'
|
|
518
|
+
: 'OpNS identity bind'));
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
521
|
+
// Unpublish: was bound → plain P2PKH keep in OPNS basket (drops bind).
|
|
522
|
+
// Self-P2PKH still decodes a recipient address — basket + dropped
|
|
523
|
+
// published tag is what distinguishes this from an external send.
|
|
524
|
+
const wasPublished = inp.tags.includes('opns:published');
|
|
525
|
+
if (wasPublished) {
|
|
526
|
+
const unregOut = takeOut((o) => o.satoshis === 1 &&
|
|
527
|
+
o.template === 'p2pkh' &&
|
|
528
|
+
(o.basket === OPNS_BASKET || !!o.basket?.includes('opns')) &&
|
|
529
|
+
!o.tags.includes('opns:published'));
|
|
530
|
+
if (unregOut) {
|
|
531
|
+
edges.push(edge('unregister', spend,
|
|
532
|
+
// Don't surface self-P2PKH address as a "To".
|
|
533
|
+
{ ...unregOut, recipient: undefined }, name ? `Unpublish “${name}”` : 'Unpublish OpNS name', 'Remove identity bind'));
|
|
534
|
+
continue;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
const next = takeOut((o) => (isCollectableBasket(o.basket) || o.satoshis === 1) &&
|
|
539
|
+
o.template !== 'ordlock');
|
|
540
|
+
if (!next) {
|
|
541
|
+
edges.push(edge('burn', spend, undefined, name ? `Burn “${name}”` : 'Burn collectable', 'No collectable output — tip ends'));
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
// transfer: external only when the collectable leaves the wallet basket
|
|
545
|
+
const external = Boolean(next.recipient) && !next.basket;
|
|
546
|
+
edges.push(edge('transfer', spend, external ? next : { ...next, recipient: undefined }, name
|
|
547
|
+
? external
|
|
548
|
+
? `Send “${name}”`
|
|
549
|
+
: `Move “${name}”`
|
|
550
|
+
: external
|
|
551
|
+
? 'Send collectable'
|
|
552
|
+
: 'Move collectable', external && next.recipient
|
|
553
|
+
? `To ${truncate(next.recipient, 18)}`
|
|
554
|
+
: 'Move collectable'));
|
|
555
|
+
}
|
|
556
|
+
// purchase / inscribe: collectable outs with no matching wallet spend
|
|
557
|
+
for (const out of outputs) {
|
|
558
|
+
if (usedOut.has(out.index))
|
|
559
|
+
continue;
|
|
560
|
+
if (!isCollectableBasket(out.basket) && out.template !== 'ordlock') {
|
|
561
|
+
// 1-sat sigma/inscribe without basket still counts
|
|
562
|
+
if (!(out.satoshis === 1 && (out.template === 'sigma' || out.sealPending))) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (out.template === 'ordlock')
|
|
567
|
+
continue;
|
|
568
|
+
const name = displayNameFrom(out.tags, out.customInstructions, out.opnsProfileName);
|
|
569
|
+
const origin = tagValue(out.tags, 'origin');
|
|
570
|
+
const hasSellerPay = outputs.some((o) => !o.basket && o.recipient && o.satoshis > 1);
|
|
571
|
+
const bareOrigin = out.tags.includes('origin');
|
|
572
|
+
if (hasSellerPay && collectableIns.length === 0) {
|
|
573
|
+
usedOut.add(out.index);
|
|
574
|
+
edges.push(edge('purchase', undefined, out, name ? `Buy “${name}”` : 'Buy collectable', 'Receive collectable from marketplace'));
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (collectableIns.length === 0 || bareOrigin || out.template === 'sigma') {
|
|
578
|
+
usedOut.add(out.index);
|
|
579
|
+
edges.push(edge('inscribe', undefined, out, name ? `Inscribe “${name}”` : 'Create inscription', out.sealPending
|
|
580
|
+
? 'New origin (signature seal pending)'
|
|
581
|
+
: 'New collectable output'));
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return edges;
|
|
585
|
+
}
|
|
586
|
+
function edge(operation, spend, create, title, summary) {
|
|
587
|
+
const name = displayNameFrom(create?.tags, create?.customInstructions, create?.opnsProfileName) ?? spend?.name;
|
|
588
|
+
const origin = tagValue(create?.tags, 'origin') ?? spend?.origin;
|
|
589
|
+
const contentType = contentTypeFromTags(create?.tags ?? spend?.tags);
|
|
590
|
+
return {
|
|
591
|
+
operation,
|
|
592
|
+
title,
|
|
593
|
+
summary,
|
|
594
|
+
...(spend ? { spend } : {}),
|
|
595
|
+
...(create
|
|
596
|
+
? {
|
|
597
|
+
create: {
|
|
598
|
+
index: create.index,
|
|
599
|
+
satoshis: create.satoshis,
|
|
600
|
+
basket: create.basket,
|
|
601
|
+
tags: create.tags,
|
|
602
|
+
template: create.template,
|
|
603
|
+
sealPending: create.sealPending,
|
|
604
|
+
sealKind: create.sealKind,
|
|
605
|
+
recipient: create.recipient,
|
|
606
|
+
listingPriceSats: create.listingPriceSats,
|
|
607
|
+
listingSeller: create.listingSeller,
|
|
608
|
+
opnsProfileName: create.opnsProfileName,
|
|
609
|
+
opnsAvatarOrigin: create.opnsAvatarOrigin,
|
|
610
|
+
...(name ? { name } : {}),
|
|
611
|
+
...(origin ? { origin } : {}),
|
|
612
|
+
...(contentType || create.inscriptionType
|
|
613
|
+
? {
|
|
614
|
+
contentType: create.inscriptionType ?? contentType,
|
|
615
|
+
}
|
|
616
|
+
: {}),
|
|
617
|
+
...(create.inscriptionText
|
|
618
|
+
? { inscriptionText: create.inscriptionText }
|
|
619
|
+
: {}),
|
|
620
|
+
...(create.inscriptionDataUrl
|
|
621
|
+
? { inscriptionDataUrl: create.inscriptionDataUrl }
|
|
622
|
+
: {}),
|
|
623
|
+
},
|
|
624
|
+
}
|
|
625
|
+
: {}),
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
/** Prefer specific MIME (`image/png`) over bare category (`image`). */
|
|
629
|
+
function contentTypeFromTags(tags) {
|
|
630
|
+
if (!tags?.length)
|
|
631
|
+
return undefined;
|
|
632
|
+
const types = tags
|
|
633
|
+
.filter((t) => t.startsWith('type:'))
|
|
634
|
+
.map((t) => t.slice(5));
|
|
635
|
+
if (!types.length)
|
|
636
|
+
return undefined;
|
|
637
|
+
const specific = types.find((t) => t.includes('/'));
|
|
638
|
+
return specific ?? types[types.length - 1];
|
|
639
|
+
}
|
|
640
|
+
function buildLegs(inputs, outputs, ordinalEdges) {
|
|
641
|
+
const edgeOutIdx = new Set(ordinalEdges
|
|
642
|
+
.map((e) => e.create?.index)
|
|
643
|
+
.filter((i) => i != null));
|
|
644
|
+
const edgeInIds = new Set(ordinalEdges.map((e) => e.spend?.id).filter((id) => !!id));
|
|
645
|
+
const legs = [];
|
|
646
|
+
for (const [i, inp] of inputs.entries()) {
|
|
647
|
+
const name = displayNameFrom(inp.tags, inp.customInstructions);
|
|
648
|
+
const origin = tagValue(inp.tags, 'origin');
|
|
649
|
+
const listing = hasListingTags(inp.tags);
|
|
650
|
+
const inOrdinalEdge = edgeInIds.has(inp.id);
|
|
651
|
+
const tokenId = tagValue(inp.tags, 'bsv21');
|
|
652
|
+
const tokenAmt = tagValue(inp.tags, 'amt');
|
|
653
|
+
// sym/name-like fields: CI preserves case; tags are lowercased by BRC-100.
|
|
654
|
+
const tokenSym = displayFieldFrom('sym', inp.tags, inp.customInstructions);
|
|
655
|
+
legs.push({
|
|
656
|
+
side: 'input',
|
|
657
|
+
index: i,
|
|
658
|
+
satoshis: inp.satoshis,
|
|
659
|
+
template: listing
|
|
660
|
+
? 'ordlock'
|
|
661
|
+
: tokenId || inp.basket === BSV21_BASKET
|
|
662
|
+
? 'bsv21'
|
|
663
|
+
: 'unrecognized',
|
|
664
|
+
label: legLabelInput(inp, name, listing),
|
|
665
|
+
basket: inp.basket,
|
|
666
|
+
tags: inp.tags,
|
|
667
|
+
id: inp.id,
|
|
668
|
+
outpoint: inp.outpoint,
|
|
669
|
+
inOrdinalEdge,
|
|
670
|
+
...(name ? { name } : {}),
|
|
671
|
+
...(origin ? { origin } : {}),
|
|
672
|
+
...(tokenId && tokenId !== 'deploy' ? { tokenId } : {}),
|
|
673
|
+
...(tokenAmt ? { tokenAmt } : {}),
|
|
674
|
+
...(tokenSym ? { tokenSym } : {}),
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
for (const out of outputs) {
|
|
678
|
+
const name = displayNameFrom(out.tags, out.customInstructions, out.opnsProfileName);
|
|
679
|
+
const origin = tagValue(out.tags, 'origin');
|
|
680
|
+
const inOrdinalEdge = edgeOutIdx.has(out.index);
|
|
681
|
+
legs.push({
|
|
682
|
+
side: 'output',
|
|
683
|
+
index: out.index,
|
|
684
|
+
satoshis: out.satoshis,
|
|
685
|
+
template: out.template ?? 'unrecognized',
|
|
686
|
+
label: legLabelOutput(out, name),
|
|
687
|
+
inOrdinalEdge,
|
|
688
|
+
...(out.sealPending
|
|
689
|
+
? { sealPending: true, sealKind: out.sealKind }
|
|
690
|
+
: {}),
|
|
691
|
+
basket: out.basket,
|
|
692
|
+
tags: out.tags,
|
|
693
|
+
recipient: out.recipient,
|
|
694
|
+
listingPriceSats: out.listingPriceSats,
|
|
695
|
+
listingSeller: out.listingSeller,
|
|
696
|
+
lockUntilHeight: out.lockUntilHeight,
|
|
697
|
+
opnsProfileName: out.opnsProfileName,
|
|
698
|
+
opnsAvatarOrigin: out.opnsAvatarOrigin,
|
|
699
|
+
...(name ? { name } : {}),
|
|
700
|
+
...(origin ? { origin } : {}),
|
|
701
|
+
...(out.tokenId ? { tokenId: out.tokenId } : {}),
|
|
702
|
+
...(out.tokenAmt ? { tokenAmt: out.tokenAmt } : {}),
|
|
703
|
+
...(out.tokenSym ? { tokenSym: out.tokenSym } : {}),
|
|
704
|
+
...(out.tokenOp ? { tokenOp: out.tokenOp } : {}),
|
|
705
|
+
...(out.isIndexerFee ? { isIndexerFee: true } : {}),
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
return legs;
|
|
709
|
+
}
|
|
710
|
+
function legLabelInput(inp, name, listing) {
|
|
711
|
+
const what = name ? `“${name}”` : inp.basket;
|
|
712
|
+
if (listing)
|
|
713
|
+
return `Spend listing ${what}`;
|
|
714
|
+
return `Spend ${what} (${inp.satoshis} sats)`;
|
|
715
|
+
}
|
|
716
|
+
function legLabelOutput(out, name) {
|
|
717
|
+
const parts = [];
|
|
718
|
+
if (out.sealPending && out.sealKind === 'sigma') {
|
|
719
|
+
parts.push('Sign Sigma on output');
|
|
720
|
+
}
|
|
721
|
+
else if (out.sealPending && out.sealKind === 'pushdrop') {
|
|
722
|
+
parts.push('Sign PushDrop on output');
|
|
723
|
+
}
|
|
724
|
+
else if (out.template === 'ordlock' && out.listingPriceSats != null) {
|
|
725
|
+
parts.push(`List for ${out.listingPriceSats} sats`);
|
|
726
|
+
}
|
|
727
|
+
else if (out.template === 'lock' && out.lockUntilHeight != null) {
|
|
728
|
+
parts.push(`Lock until height ${out.lockUntilHeight}`);
|
|
729
|
+
}
|
|
730
|
+
else if (out.template === 'sigma') {
|
|
731
|
+
parts.push('Sigma-signed output');
|
|
732
|
+
}
|
|
733
|
+
else if (out.template === 'pushdrop') {
|
|
734
|
+
parts.push(name ? `PushDrop “${name}”` : 'PushDrop output');
|
|
735
|
+
}
|
|
736
|
+
else if (out.recipient) {
|
|
737
|
+
parts.push(`Pay ${truncate(out.recipient, 18)}`);
|
|
738
|
+
}
|
|
739
|
+
else if (out.basket) {
|
|
740
|
+
parts.push(`File to ${out.basket}`);
|
|
741
|
+
}
|
|
742
|
+
else {
|
|
743
|
+
parts.push(out.template === 'unrecognized'
|
|
744
|
+
? 'Unrecognized script'
|
|
745
|
+
: 'Output');
|
|
746
|
+
}
|
|
747
|
+
if (name && !parts[0]?.includes(name))
|
|
748
|
+
parts.push(`“${name}”`);
|
|
749
|
+
parts.push(`${out.satoshis} sats`);
|
|
750
|
+
return parts.join(' · ');
|
|
241
751
|
}
|
|
242
752
|
// ---------------------------------------------------------------------------
|
|
243
753
|
// Intent classification — driven by input asset basket membership.
|
|
@@ -245,30 +755,7 @@ function decodeOutput(out, index, chain) {
|
|
|
245
755
|
function hasListingTags(tags) {
|
|
246
756
|
return tags.some((t) => t === 'ordlock' || t.startsWith('price:'));
|
|
247
757
|
}
|
|
248
|
-
function detectKind(inputs, outputs
|
|
249
|
-
// Prefer explicit intent labels over heuristics.
|
|
250
|
-
if (p1satIntent?.startsWith('opns.'))
|
|
251
|
-
return 'opns';
|
|
252
|
-
if (p1satIntent === 'ordinal.list' || p1satIntent === 'bsv21.list')
|
|
253
|
-
return 'listing';
|
|
254
|
-
if (p1satIntent === 'ordinal.cancel-listing' ||
|
|
255
|
-
p1satIntent === 'opns.cancel-listing')
|
|
256
|
-
return 'cancel-listing';
|
|
257
|
-
if (p1satIntent === 'ordinal.purchase' ||
|
|
258
|
-
p1satIntent === 'opns.purchase' ||
|
|
259
|
-
p1satIntent === 'bsv21.purchase')
|
|
260
|
-
return 'purchase';
|
|
261
|
-
if (p1satIntent === 'ordinal.transfer')
|
|
262
|
-
return 'ordinal-transfer';
|
|
263
|
-
if (p1satIntent === 'bsv21.transfer')
|
|
264
|
-
return 'token-transfer';
|
|
265
|
-
if (p1satIntent === 'lock.lock')
|
|
266
|
-
return 'lock';
|
|
267
|
-
if (p1satIntent === 'lock.unlock')
|
|
268
|
-
return 'unlock';
|
|
269
|
-
if (p1satIntent === 'ordinal.inscribe' ||
|
|
270
|
-
p1satIntent === 'ordinal.inscribe-sigma')
|
|
271
|
-
return 'inscription';
|
|
758
|
+
function detectKind(inputs, outputs) {
|
|
272
759
|
// Cancel listing: spending an ordlock-tagged input back to a P2PKH owner.
|
|
273
760
|
// Listings live in the basket of the listed asset (ordinals, opns, …),
|
|
274
761
|
// so the ordlock/price tags are the marker, not the basket.
|
|
@@ -309,12 +796,7 @@ function detectKind(inputs, outputs, p1satIntent) {
|
|
|
309
796
|
return 'token-transfer';
|
|
310
797
|
return 'unknown';
|
|
311
798
|
}
|
|
312
|
-
function buildSummary(kind, inputs, outputs
|
|
313
|
-
if (intentId) {
|
|
314
|
-
const named = summaryFromIntentId(intentId, inputs, outputs);
|
|
315
|
-
if (named)
|
|
316
|
-
return named;
|
|
317
|
-
}
|
|
799
|
+
function buildSummary(kind, inputs, outputs) {
|
|
318
800
|
switch (kind) {
|
|
319
801
|
case 'ordinal-transfer': {
|
|
320
802
|
const recipient = outputs.find((o) => o.recipient)?.recipient;
|
|
@@ -358,64 +840,22 @@ function buildSummary(kind, inputs, outputs, intentId) {
|
|
|
358
840
|
case 'social-post':
|
|
359
841
|
return `Create social post`;
|
|
360
842
|
case 'opns': {
|
|
361
|
-
const
|
|
362
|
-
|
|
363
|
-
|
|
843
|
+
const out = outputs.find((o) => o.basket === OPNS_BASKET);
|
|
844
|
+
const name = tagValue(inputs[0]?.tags, 'name') ?? tagValue(out?.tags, 'name');
|
|
845
|
+
const published = out?.tags.includes('opns:published');
|
|
846
|
+
if (published) {
|
|
847
|
+
return name ? `Publish name “${name}”` : 'Publish OpNS name';
|
|
848
|
+
}
|
|
849
|
+
return name ? `OpNS operation on “${name}”` : 'OpNS operation';
|
|
850
|
+
}
|
|
851
|
+
case 'purchase': {
|
|
852
|
+
const name = tagValue(outputs[0]?.tags, 'name') ?? tagValue(inputs[0]?.tags, 'name');
|
|
853
|
+
return name ? `Buy “${name}”` : 'Buy asset';
|
|
364
854
|
}
|
|
365
855
|
default:
|
|
366
856
|
return `Approve transaction`;
|
|
367
857
|
}
|
|
368
858
|
}
|
|
369
|
-
function summaryFromIntentId(intentId, inputs, outputs) {
|
|
370
|
-
const name = tagValue(inputs[0]?.tags, 'name') ??
|
|
371
|
-
tagValue(outputs.find((o) => o.basket === OPNS_BASKET)?.tags, 'name');
|
|
372
|
-
const what = name ? `“${name}”` : undefined;
|
|
373
|
-
switch (intentId) {
|
|
374
|
-
case 'opns.register':
|
|
375
|
-
return what ? `Publish name ${what}` : 'Publish OpNS name';
|
|
376
|
-
case 'opns.deregister':
|
|
377
|
-
return what ? `Unpublish name ${what}` : 'Unpublish OpNS name';
|
|
378
|
-
case 'opns.list':
|
|
379
|
-
return what ? `List name ${what} for sale` : 'List OpNS name for sale';
|
|
380
|
-
case 'opns.transfer':
|
|
381
|
-
return what ? `Transfer name ${what}` : 'Transfer OpNS name';
|
|
382
|
-
case 'opns.cancel-listing':
|
|
383
|
-
return what ? `Cancel listing of ${what}` : 'Cancel OpNS listing';
|
|
384
|
-
case 'opns.purchase':
|
|
385
|
-
return what ? `Buy name ${what}` : 'Buy OpNS name';
|
|
386
|
-
case 'ordinal.transfer':
|
|
387
|
-
return 'Send ordinal';
|
|
388
|
-
case 'ordinal.list':
|
|
389
|
-
return 'List ordinal for sale';
|
|
390
|
-
case 'ordinal.cancel-listing':
|
|
391
|
-
return 'Cancel ordinal listing';
|
|
392
|
-
case 'ordinal.purchase':
|
|
393
|
-
return 'Buy ordinal';
|
|
394
|
-
case 'ordinal.burn':
|
|
395
|
-
return 'Burn ordinal';
|
|
396
|
-
case 'ordinal.inscribe':
|
|
397
|
-
case 'ordinal.inscribe-sigma':
|
|
398
|
-
return 'Create inscription';
|
|
399
|
-
case 'ordinal.mint-collection':
|
|
400
|
-
return 'Mint collection';
|
|
401
|
-
case 'ordinal.mint-item':
|
|
402
|
-
return 'Mint collection item';
|
|
403
|
-
case 'lock.lock':
|
|
404
|
-
return 'Lock BSV';
|
|
405
|
-
case 'lock.unlock':
|
|
406
|
-
return 'Unlock BSV';
|
|
407
|
-
case 'bsv21.transfer':
|
|
408
|
-
return 'Send tokens';
|
|
409
|
-
case 'bsv21.purchase':
|
|
410
|
-
return 'Buy tokens';
|
|
411
|
-
case 'bsv21.mint':
|
|
412
|
-
return 'Mint tokens';
|
|
413
|
-
case 'bsv21.deploy':
|
|
414
|
-
return 'Deploy token';
|
|
415
|
-
default:
|
|
416
|
-
return undefined;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
859
|
// ---------------------------------------------------------------------------
|
|
420
860
|
// Helpers
|
|
421
861
|
// ---------------------------------------------------------------------------
|
|
@@ -436,7 +876,6 @@ function truncate(s, max) {
|
|
|
436
876
|
}
|
|
437
877
|
function contentUrlFromOrigin(host, origin) {
|
|
438
878
|
const trimmed = host.replace(/\/$/, '');
|
|
439
|
-
|
|
440
|
-
return `${trimmed}/${formatted}`;
|
|
879
|
+
return `${trimmed}/${formatOrdinalOutpoint(origin)}`;
|
|
441
880
|
}
|
|
442
881
|
//# sourceMappingURL=enrichIntent.js.map
|