@1sat/actions 0.0.189 → 0.0.191

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.
Files changed (41) hide show
  1. package/dist/index.d.ts +2 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/inscriptions/index.d.ts +2 -2
  6. package/dist/inscriptions/index.d.ts.map +1 -1
  7. package/dist/inscriptions/index.js +3 -6
  8. package/dist/inscriptions/index.js.map +1 -1
  9. package/dist/locks/index.d.ts +32 -3
  10. package/dist/locks/index.d.ts.map +1 -1
  11. package/dist/locks/index.js +76 -5
  12. package/dist/locks/index.js.map +1 -1
  13. package/dist/opns/index.d.ts +90 -65
  14. package/dist/opns/index.d.ts.map +1 -1
  15. package/dist/opns/index.js +517 -188
  16. package/dist/opns/index.js.map +1 -1
  17. package/dist/ordinals/index.d.ts +76 -42
  18. package/dist/ordinals/index.d.ts.map +1 -1
  19. package/dist/ordinals/index.js +219 -180
  20. package/dist/ordinals/index.js.map +1 -1
  21. package/dist/tokens/index.d.ts +5 -1
  22. package/dist/tokens/index.d.ts.map +1 -1
  23. package/dist/tokens/index.js +36 -18
  24. package/dist/tokens/index.js.map +1 -1
  25. package/dist/utils/createTrackedAction.js +1 -1
  26. package/dist/utils/internalizeBeef.d.ts.map +1 -1
  27. package/dist/utils/internalizeBeef.js +1 -25
  28. package/dist/utils/internalizeBeef.js.map +1 -1
  29. package/dist/utils/loadBasketOutput.d.ts +25 -0
  30. package/dist/utils/loadBasketOutput.d.ts.map +1 -0
  31. package/dist/utils/loadBasketOutput.js +50 -0
  32. package/dist/utils/loadBasketOutput.js.map +1 -0
  33. package/dist/utils/ordinalSeedTags.d.ts +8 -0
  34. package/dist/utils/ordinalSeedTags.d.ts.map +1 -0
  35. package/dist/utils/ordinalSeedTags.js +24 -0
  36. package/dist/utils/ordinalSeedTags.js.map +1 -0
  37. package/package.json +9 -9
  38. package/dist/utils/resolveBeef.d.ts +0 -20
  39. package/dist/utils/resolveBeef.d.ts.map +0 -1
  40. package/dist/utils/resolveBeef.js +0 -36
  41. package/dist/utils/resolveBeef.js.map +0 -1
@@ -1,136 +1,242 @@
1
1
  /**
2
2
  * OpNS Module
3
3
  *
4
- * Actions for managing OpNS names. Identity bind is a signed PushDrop on the
5
- * name UTXO (field0 = BRC-100 identity key). Moving the name spends that
6
- * script and re-locks under the normal ordinal formats (P2PKH / OrdLock).
4
+ * Wallet-owned names live in OPNS_BASKET with id: tags.
5
+ * Self-moves: id one loadBasketOutputBeef ordinalSeedTags + domain tags.
6
+ * Ingress (internalizeOpns / buyOpns) stamps full tags including id:.
7
7
  */
8
- import { OPNS_BASKET, OPNS_PUBLISHED_TAG, OPNS_PUSHDROP_TEMPLATE, OPNS_REGISTER_COUNTERPARTY, P1SAT_PROTOCOL, opnsRegisterKeyId, } from '@1sat/types';
9
- import { PushDrop, Utils, } from '@bsv/sdk';
10
- import { buildTransferOrdinals, listOrdinal, transferOrdinals } from '../ordinals';
11
- import { executeTrackedAction } from '../utils/createTrackedAction';
12
- import { resolveBeef } from '../utils/resolveBeef';
8
+ import { OpNS, OrdLock } from '@1sat/templates';
9
+ import { OPNS_BASKET, OPNS_PUBLISHED_TAG, OPNS_PUSHDROP_TEMPLATE, OPNS_REGISTER_COUNTERPARTY, P1SAT_PROTOCOL, buildInputAssetLabel, opnsRegisterKeyId, readAssetIdTag, } from '@1sat/types';
10
+ import { P2PKH, PublicKey, PushDrop, Transaction, Utils, } from '@bsv/sdk';
11
+ import { buildOrdLockScript, buyOrdinal, defaultPayAddress, deriveCancelAddressInternal, } from '../ordinals';
12
+ import { executeTrackedAction, randomActionId, } from '../utils/createTrackedAction';
13
+ import { loadBasketOutputBeef } from '../utils/loadBasketOutput';
14
+ import { ordinalSeedTags } from '../utils/ordinalSeedTags';
13
15
  import { signOrdinalInput, unlockingScriptLengthForInstructions, } from '../utils/signOrdinalInput';
16
+ const OPNS_CONTENT_TYPE = 'application/op-ns';
14
17
  export { opnsRegisterKeyId } from '@1sat/types';
15
18
  // ============================================================================
16
19
  // Helpers
17
20
  // ============================================================================
18
- function sourceNameFromOrdinal(ordinal) {
19
- if (ordinal.customInstructions) {
20
- try {
21
- const name = JSON.parse(ordinal.customInstructions).name;
22
- if (typeof name === 'string' && name)
23
- return name;
24
- }
25
- catch { }
21
+ function findMintNameDelivery(tx) {
22
+ for (let i = 0; i + 2 < tx.outputs.length; i++) {
23
+ const parent = OpNS.decode(tx.outputs[i].lockingScript);
24
+ const child = OpNS.decode(tx.outputs[i + 1].lockingScript);
25
+ if (!parent || !child)
26
+ continue;
27
+ const name = child.domain.trim().slice(0, 64);
28
+ if (!name)
29
+ continue;
30
+ return { vout: i + 2, name };
26
31
  }
27
- return ordinal.tags?.find((t) => t.startsWith('name:'))?.slice(5);
32
+ return null;
28
33
  }
29
- async function signSingleOrdinalInput(ctx, ordinal) {
30
- if (!ordinal.customInstructions) {
31
- return { error: 'missing-custom-instructions' };
34
+ function nameFromOutput(out) {
35
+ const fromTag = out.tags?.find((t) => t.startsWith('name:'))?.slice(5);
36
+ if (fromTag)
37
+ return fromTag.slice(0, 64);
38
+ if (!out.customInstructions)
39
+ return undefined;
40
+ try {
41
+ const n = JSON.parse(out.customInstructions).name;
42
+ return typeof n === 'string' && n ? n.slice(0, 64) : undefined;
32
43
  }
33
- return {
34
- sign: async (tx) => {
35
- const unlocking = await signOrdinalInput(ctx, tx, 0, ordinal.customInstructions);
36
- if (typeof unlocking !== 'string')
37
- throw new Error(unlocking.error);
38
- return { 0: { unlockingScript: unlocking } };
39
- },
40
- };
44
+ catch {
45
+ return undefined;
46
+ }
47
+ }
48
+ /** Load OPNS row + BEEF from wallet storage by id. */
49
+ async function loadOpnsSpend(ctx, input) {
50
+ return loadBasketOutputBeef(ctx.wallet, OPNS_BASKET, input.id);
51
+ }
52
+ /** Ordinal seed + opns domain tag; optional extras (ordlock, published, …). */
53
+ function opnsFileTags(output, extra = []) {
54
+ const tags = ordinalSeedTags(output);
55
+ if (!tags.includes('opns'))
56
+ tags.unshift('opns');
57
+ for (const e of extra) {
58
+ if (e && !tags.includes(e))
59
+ tags.push(e);
60
+ }
61
+ return tags;
41
62
  }
42
63
  // ============================================================================
43
- // Actions
64
+ // listOpns
44
65
  // ============================================================================
45
- /**
46
- * Get OpNS names from the wallet with BEEF for spending.
47
- */
48
- export const getOpnsNames = {
66
+ export const listOpns = {
49
67
  meta: {
50
- name: 'getOpnsNames',
51
- description: 'Get OpNS names from the wallet with BEEF for spending',
68
+ name: 'listOpns',
69
+ description: 'List OpNS names from the wallet (metadata by default; optional BEEF)',
52
70
  category: 'opns',
53
71
  inputSchema: {
54
72
  type: 'object',
55
73
  properties: {
56
- limit: {
57
- type: 'integer',
58
- description: 'Max names to return (default: 100)',
59
- },
60
- offset: {
61
- type: 'integer',
62
- description: 'Offset for pagination (default: 0)',
74
+ tags: { type: 'array', items: { type: 'string' } },
75
+ tagQueryMode: { type: 'string', enum: ['all', 'any'] },
76
+ names: { type: 'array', items: { type: 'string' } },
77
+ ids: { type: 'array', items: { type: 'string' } },
78
+ include: {
79
+ type: 'string',
80
+ enum: ['locking scripts', 'entire transactions'],
63
81
  },
82
+ includeCustomInstructions: { type: 'boolean' },
83
+ includeTags: { type: 'boolean' },
84
+ includeLabels: { type: 'boolean' },
85
+ limit: { type: 'integer' },
86
+ offset: { type: 'integer' },
64
87
  },
65
88
  },
66
89
  },
67
90
  async execute(ctx, input) {
91
+ const tags = [...(input.tags ?? [])];
92
+ for (const n of input.names ?? []) {
93
+ if (n)
94
+ tags.push(`name:${n}`);
95
+ }
96
+ for (const id of input.ids ?? []) {
97
+ if (id)
98
+ tags.push(id.startsWith('id:') ? id : `id:${id}`);
99
+ }
100
+ const filtering = tags.length > 0;
68
101
  const result = await ctx.wallet.listOutputs({
69
102
  basket: OPNS_BASKET,
70
- includeTags: true,
71
- includeCustomInstructions: true,
72
- include: 'entire transactions',
103
+ ...(filtering && {
104
+ tags,
105
+ tagQueryMode: input.tagQueryMode ?? 'any',
106
+ }),
107
+ ...(input.include && { include: input.include }),
108
+ includeCustomInstructions: input.includeCustomInstructions ?? true,
109
+ includeTags: input.includeTags ?? true,
110
+ ...(input.includeLabels != null && {
111
+ includeLabels: input.includeLabels,
112
+ }),
73
113
  limit: input.limit ?? 100,
74
114
  offset: input.offset ?? 0,
75
115
  });
76
116
  return {
77
117
  outputs: result.outputs,
78
118
  BEEF: result.BEEF,
119
+ totalOutputs: result.totalOutputs,
79
120
  };
80
121
  },
81
122
  };
82
- /**
83
- * Bind the wallet identity key to an OpNS name via signed PushDrop.
84
- *
85
- * Lock: PushDrop under [0,'p 1sat'] / opns:{inputOutpoint} / anyone, forSelf.
86
- * fields[0] = identity pubkey bytes; field-sig included (same derivation).
87
- */
88
- export const opnsRegister = {
123
+ /** @deprecated Use listOpns */
124
+ export const getOpnsNames = listOpns;
125
+ // ============================================================================
126
+ // internalizeOpns (ingress)
127
+ // ============================================================================
128
+ export const internalizeOpns = {
129
+ meta: {
130
+ name: 'internalizeOpns',
131
+ description: 'Internalize a foreign-created OpNS mint (AtomicBEEF) into the OPNS basket',
132
+ category: 'opns',
133
+ inputSchema: {
134
+ type: 'object',
135
+ properties: {
136
+ tx: { type: 'array', items: { type: 'integer' } },
137
+ protocolID: { type: 'array' },
138
+ keyID: { type: 'string' },
139
+ counterparty: { type: 'string' },
140
+ },
141
+ required: ['tx', 'protocolID', 'keyID'],
142
+ },
143
+ },
144
+ async execute(ctx, input) {
145
+ try {
146
+ const parsed = Transaction.fromAtomicBEEF(input.tx);
147
+ const txid = parsed.id('hex');
148
+ const delivery = findMintNameDelivery(parsed);
149
+ if (!delivery)
150
+ return { error: 'not-an-opns-mint' };
151
+ const { vout, name } = delivery;
152
+ const outpoint = `${txid}.${vout}`;
153
+ const actionId = randomActionId();
154
+ const idTag = `id:${actionId}_${vout}`;
155
+ const counterparty = input.counterparty ?? 'self';
156
+ await ctx.wallet.internalizeAction({
157
+ tx: input.tx,
158
+ outputs: [
159
+ {
160
+ outputIndex: vout,
161
+ protocol: 'basket insertion',
162
+ insertionRemittance: {
163
+ basket: OPNS_BASKET,
164
+ tags: [
165
+ 'opns',
166
+ `type:${OPNS_CONTENT_TYPE}`,
167
+ `origin:${outpoint}`,
168
+ `name:${name}`,
169
+ idTag,
170
+ ],
171
+ customInstructions: JSON.stringify({
172
+ protocolID: input.protocolID,
173
+ keyID: input.keyID,
174
+ counterparty,
175
+ name,
176
+ }),
177
+ },
178
+ },
179
+ ],
180
+ description: `opns name ${name}`.slice(0, 50),
181
+ });
182
+ return {
183
+ txid,
184
+ outpoint,
185
+ name,
186
+ id: `${actionId}_${vout}`,
187
+ };
188
+ }
189
+ catch (err) {
190
+ return {
191
+ error: err instanceof Error ? err.message : String(err),
192
+ };
193
+ }
194
+ },
195
+ };
196
+ // ============================================================================
197
+ // register / deregister
198
+ // ============================================================================
199
+ export const registerOpns = {
89
200
  meta: {
90
- name: 'opnsRegister',
201
+ name: 'registerOpns',
91
202
  description: 'Bind BRC-100 identity key to an OpNS name via signed PushDrop',
92
203
  category: 'opns',
93
204
  inputSchema: {
94
205
  type: 'object',
95
206
  properties: {
96
- ordinal: {
97
- type: 'object',
98
- description: 'WalletOutput of the OpNS ordinal from listOutputs',
99
- },
100
- inputBEEF: {
101
- type: 'array',
102
- description: "BEEF from listOutputs with include: 'entire transactions'",
103
- },
207
+ id: { type: 'string', description: 'OPNS basket tracking id' },
104
208
  },
105
- required: ['ordinal'],
209
+ required: ['id'],
106
210
  },
107
211
  },
108
212
  async execute(ctx, input) {
109
213
  try {
110
- const { ordinal } = input;
111
- if (!ordinal.customInstructions) {
214
+ const loaded = await loadOpnsSpend(ctx, input);
215
+ if ('error' in loaded)
216
+ return loaded;
217
+ const { output, beef } = loaded;
218
+ if (!output.customInstructions) {
112
219
  return { error: 'missing-custom-instructions' };
113
220
  }
114
- const inputBEEF = input.inputBEEF ??
115
- (await resolveBeef(ctx.wallet, OPNS_BASKET, ordinal));
116
221
  const { publicKey: identityPubKey } = await ctx.wallet.getPublicKey({
117
222
  identityKey: true,
118
223
  });
119
- const keyID = opnsRegisterKeyId(ordinal.outpoint);
224
+ const keyID = opnsRegisterKeyId(output.outpoint);
120
225
  const lockingScript = await new PushDrop(ctx.wallet).lock([Utils.toArray(identityPubKey, 'hex')], P1SAT_PROTOCOL, keyID, OPNS_REGISTER_COUNTERPARTY, true, true);
121
- const tags = [
122
- ...(ordinal.tags ?? []).filter((t) => t !== OPNS_PUBLISHED_TAG && !t.startsWith('ordlock')),
123
- OPNS_PUBLISHED_TAG,
124
- ];
125
- const name = sourceNameFromOrdinal(ordinal);
126
- const result = await executeTrackedAction(ctx.wallet, {
226
+ const name = nameFromOutput(output);
227
+ const tags = opnsFileTags(output, [OPNS_PUBLISHED_TAG]);
228
+ const inputId = readAssetIdTag(output.tags);
229
+ return await executeTrackedAction(ctx.wallet, {
127
230
  description: 'Register OpNS identity bind',
128
- inputBEEF,
231
+ inputBEEF: beef,
232
+ ...(inputId && {
233
+ labels: [buildInputAssetLabel(OPNS_BASKET, inputId)],
234
+ }),
129
235
  inputs: [
130
236
  {
131
- outpoint: ordinal.outpoint,
237
+ outpoint: output.outpoint,
132
238
  inputDescription: 'OpNS name to register',
133
- unlockingScriptLength: unlockingScriptLengthForInstructions(ordinal.customInstructions),
239
+ unlockingScriptLength: unlockingScriptLengthForInstructions(output.customInstructions),
134
240
  },
135
241
  ],
136
242
  outputs: [
@@ -150,185 +256,408 @@ export const opnsRegister = {
150
256
  },
151
257
  ],
152
258
  options: { randomizeOutputs: false },
153
- }, input.fundingProvider, inputBEEF, async (tx) => {
154
- const unlocking = await signOrdinalInput(ctx, tx, 0, ordinal.customInstructions);
259
+ }, input.fundingProvider, beef, async (tx) => {
260
+ const unlocking = await signOrdinalInput(ctx, tx, 0, output.customInstructions);
155
261
  if (typeof unlocking !== 'string')
156
262
  throw new Error(unlocking.error);
157
263
  return { 0: { unlockingScript: unlocking } };
158
264
  });
159
- return result;
160
265
  }
161
266
  catch (error) {
162
- console.error('[opnsRegister]', error);
163
- if (ctx.debug && ctx.log) {
164
- ctx.log({
165
- timestamp: new Date().toISOString(),
166
- action: 'opnsRegister',
167
- input: { outpoint: input.ordinal.outpoint },
168
- error: error instanceof Error ? error.message : 'unknown-error',
169
- });
170
- }
267
+ console.error('[registerOpns]', error);
171
268
  return {
172
269
  error: error instanceof Error ? error.message : 'unknown-error',
173
270
  };
174
271
  }
175
272
  },
176
273
  };
177
- /**
178
- * Remove an identity bind by self-transferring to plain P2PKH.
179
- * Spending the PushDrop clears the on-chain bind.
180
- */
181
- export const opnsDeregister = {
274
+ /** @deprecated Use registerOpns */
275
+ export const opnsRegister = registerOpns;
276
+ export const deregisterOpns = {
182
277
  meta: {
183
- name: 'opnsDeregister',
278
+ name: 'deregisterOpns',
184
279
  description: 'Remove identity bind from an OpNS name (self-transfer to P2PKH)',
185
280
  category: 'opns',
186
281
  inputSchema: {
187
282
  type: 'object',
188
283
  properties: {
189
- ordinal: {
190
- type: 'object',
191
- description: 'WalletOutput of the OpNS ordinal from listOutputs',
192
- },
193
- inputBEEF: {
194
- type: 'array',
195
- description: "BEEF from listOutputs with include: 'entire transactions'",
196
- },
284
+ id: { type: 'string' },
197
285
  },
198
- required: ['ordinal'],
286
+ required: ['id'],
199
287
  },
200
288
  },
201
289
  async execute(ctx, input) {
202
290
  try {
203
- const { ordinal } = input;
204
- const inputBEEF = input.inputBEEF ?? (await resolveBeef(ctx.wallet, OPNS_BASKET, ordinal));
205
- const params = await buildTransferOrdinals(ctx, {
206
- transfers: [
291
+ const loaded = await loadOpnsSpend(ctx, input);
292
+ if ('error' in loaded)
293
+ return loaded;
294
+ const { output, beef } = loaded;
295
+ if (!output.customInstructions) {
296
+ return { error: 'missing-custom-instructions' };
297
+ }
298
+ const outpoint = output.outpoint;
299
+ const { publicKey } = await ctx.wallet.getPublicKey({
300
+ protocolID: P1SAT_PROTOCOL,
301
+ keyID: outpoint,
302
+ counterparty: 'self',
303
+ forSelf: true,
304
+ });
305
+ const address = PublicKey.fromString(publicKey).toAddress();
306
+ const tags = opnsFileTags(output);
307
+ const name = nameFromOutput(output);
308
+ const inputId = readAssetIdTag(output.tags);
309
+ return await executeTrackedAction(ctx.wallet, {
310
+ description: 'Deregister OpNS identity bind',
311
+ inputBEEF: beef,
312
+ ...(inputId && {
313
+ labels: [buildInputAssetLabel(OPNS_BASKET, inputId)],
314
+ }),
315
+ inputs: [
207
316
  {
208
- ordinal,
209
- counterparty: 'self',
210
- extraTags: [],
317
+ outpoint,
318
+ inputDescription: 'OpNS name to deregister',
319
+ unlockingScriptLength: unlockingScriptLengthForInstructions(output.customInstructions),
211
320
  },
212
321
  ],
213
- inputBEEF,
322
+ outputs: [
323
+ {
324
+ lockingScript: new P2PKH().lock(address).toHex(),
325
+ satoshis: 1,
326
+ outputDescription: 'OpNS name (unbound)',
327
+ basket: OPNS_BASKET,
328
+ tags,
329
+ customInstructions: JSON.stringify({
330
+ protocolID: P1SAT_PROTOCOL,
331
+ keyID: outpoint,
332
+ ...(name && { name }),
333
+ }),
334
+ },
335
+ ],
336
+ options: { randomizeOutputs: false },
337
+ }, input.fundingProvider, beef, async (tx) => {
338
+ const unlocking = await signOrdinalInput(ctx, tx, 0, output.customInstructions);
339
+ if (typeof unlocking !== 'string')
340
+ throw new Error(unlocking.error);
341
+ return { 0: { unlockingScript: unlocking } };
214
342
  });
215
- if ('error' in params) {
216
- return params;
217
- }
218
- // Drop published tag on the plain self-transfer output
219
- if (params.outputs?.[0]?.tags) {
220
- params.outputs[0].tags = params.outputs[0].tags.filter((t) => t !== OPNS_PUBLISHED_TAG);
343
+ }
344
+ catch (error) {
345
+ console.error('[deregisterOpns]', error);
346
+ return {
347
+ error: error instanceof Error ? error.message : 'unknown-error',
348
+ };
349
+ }
350
+ },
351
+ };
352
+ /** @deprecated Use deregisterOpns */
353
+ export const opnsDeregister = deregisterOpns;
354
+ // ============================================================================
355
+ // sell / send / cancel
356
+ // ============================================================================
357
+ export const sellOpns = {
358
+ meta: {
359
+ name: 'sellOpns',
360
+ description: 'List an OpNS name for sale',
361
+ category: 'opns',
362
+ inputSchema: {
363
+ type: 'object',
364
+ properties: {
365
+ id: { type: 'string' },
366
+ price: { type: 'integer' },
367
+ payAddress: {
368
+ type: 'string',
369
+ description: 'Payment address (default: P1SAT keyID 1sat 0)',
370
+ },
371
+ },
372
+ required: ['id', 'price'],
373
+ },
374
+ },
375
+ async execute(ctx, input) {
376
+ try {
377
+ if (input.price <= 0)
378
+ return { error: 'invalid-price' };
379
+ const loaded = await loadOpnsSpend(ctx, input);
380
+ if ('error' in loaded)
381
+ return loaded;
382
+ const { output, beef } = loaded;
383
+ if (!output.customInstructions) {
384
+ return { error: 'missing-custom-instructions' };
221
385
  }
222
- const signer = await signSingleOrdinalInput(ctx, ordinal);
223
- if ('error' in signer)
224
- return signer;
386
+ const outpoint = output.outpoint;
387
+ const payAddress = input.payAddress ?? (await defaultPayAddress(ctx));
388
+ const cancelAddress = await deriveCancelAddressInternal(ctx, outpoint);
389
+ const lockingScript = buildOrdLockScript(cancelAddress, payAddress, input.price).toHex();
390
+ const tags = opnsFileTags(output, ['ordlock', `price:${input.price}`]);
391
+ const name = nameFromOutput(output);
392
+ const inputId = readAssetIdTag(output.tags);
225
393
  return await executeTrackedAction(ctx.wallet, {
226
- ...params,
227
- description: 'Deregister OpNS identity bind',
394
+ description: `List OpNS for ${input.price} sats`,
395
+ inputBEEF: beef,
396
+ ...(inputId && {
397
+ labels: [buildInputAssetLabel(OPNS_BASKET, inputId)],
398
+ }),
399
+ inputs: [
400
+ {
401
+ outpoint,
402
+ inputDescription: 'OpNS name to list',
403
+ unlockingScriptLength: unlockingScriptLengthForInstructions(output.customInstructions),
404
+ },
405
+ ],
406
+ outputs: [
407
+ {
408
+ lockingScript,
409
+ satoshis: 1,
410
+ outputDescription: `List OpNS for ${input.price} sats`,
411
+ basket: OPNS_BASKET,
412
+ tags,
413
+ customInstructions: JSON.stringify({
414
+ protocolID: P1SAT_PROTOCOL,
415
+ keyID: outpoint,
416
+ ...(name && { name }),
417
+ }),
418
+ },
419
+ ],
228
420
  options: { randomizeOutputs: false },
229
- }, input.fundingProvider, inputBEEF, signer.sign);
421
+ }, input.fundingProvider, beef, async (tx) => {
422
+ const unlocking = await signOrdinalInput(ctx, tx, 0, output.customInstructions);
423
+ if (typeof unlocking !== 'string')
424
+ throw new Error(unlocking.error);
425
+ return { 0: { unlockingScript: unlocking } };
426
+ });
230
427
  }
231
428
  catch (error) {
232
- console.error('[opnsDeregister]', error);
233
- if (ctx.debug && ctx.log) {
234
- ctx.log({
235
- timestamp: new Date().toISOString(),
236
- action: 'opnsDeregister',
237
- input: { outpoint: input.ordinal.outpoint },
238
- error: error instanceof Error ? error.message : 'unknown-error',
429
+ console.error('[sellOpns]', error);
430
+ return {
431
+ error: error instanceof Error ? error.message : 'unknown-error',
432
+ };
433
+ }
434
+ },
435
+ };
436
+ /** @deprecated Use sellOpns */
437
+ export const opnsList = sellOpns;
438
+ export const sendOpns = {
439
+ meta: {
440
+ name: 'sendOpns',
441
+ description: 'Transfer an OpNS name to a new owner',
442
+ category: 'opns',
443
+ inputSchema: {
444
+ type: 'object',
445
+ properties: {
446
+ id: { type: 'string' },
447
+ counterparty: { type: 'string' },
448
+ address: { type: 'string' },
449
+ },
450
+ required: ['id'],
451
+ },
452
+ },
453
+ async execute(ctx, input) {
454
+ try {
455
+ if (!input.counterparty && !input.address) {
456
+ return { error: 'must-provide-counterparty-or-address' };
457
+ }
458
+ const loaded = await loadOpnsSpend(ctx, input);
459
+ if ('error' in loaded)
460
+ return loaded;
461
+ const { output, beef } = loaded;
462
+ if (!output.customInstructions) {
463
+ return { error: 'missing-custom-instructions' };
464
+ }
465
+ const outpoint = output.outpoint;
466
+ const isSelf = input.counterparty === 'self';
467
+ let recipientAddress;
468
+ if (input.counterparty) {
469
+ const { publicKey } = await ctx.wallet.getPublicKey({
470
+ protocolID: P1SAT_PROTOCOL,
471
+ keyID: outpoint,
472
+ counterparty: input.counterparty,
473
+ forSelf: isSelf,
239
474
  });
475
+ recipientAddress = PublicKey.fromString(publicKey).toAddress();
476
+ }
477
+ else {
478
+ recipientAddress = input.address;
240
479
  }
480
+ const inputId = readAssetIdTag(output.tags);
481
+ const name = nameFromOutput(output);
482
+ const tags = opnsFileTags(output);
483
+ return await executeTrackedAction(ctx.wallet, {
484
+ description: 'Transfer OpNS name',
485
+ inputBEEF: beef,
486
+ ...(inputId && {
487
+ labels: [buildInputAssetLabel(OPNS_BASKET, inputId)],
488
+ }),
489
+ inputs: [
490
+ {
491
+ outpoint,
492
+ inputDescription: 'OpNS name to transfer',
493
+ unlockingScriptLength: unlockingScriptLengthForInstructions(output.customInstructions),
494
+ },
495
+ ],
496
+ outputs: [
497
+ isSelf
498
+ ? {
499
+ lockingScript: new P2PKH().lock(recipientAddress).toHex(),
500
+ satoshis: 1,
501
+ outputDescription: 'OpNS self-transfer',
502
+ basket: OPNS_BASKET,
503
+ tags,
504
+ customInstructions: JSON.stringify({
505
+ protocolID: P1SAT_PROTOCOL,
506
+ keyID: outpoint,
507
+ ...(name && { name }),
508
+ }),
509
+ }
510
+ : {
511
+ lockingScript: new P2PKH().lock(recipientAddress).toHex(),
512
+ satoshis: 1,
513
+ outputDescription: 'OpNS transfer',
514
+ tags: [],
515
+ },
516
+ ],
517
+ options: { randomizeOutputs: false },
518
+ }, input.fundingProvider, beef, async (tx) => {
519
+ const unlocking = await signOrdinalInput(ctx, tx, 0, output.customInstructions);
520
+ if (typeof unlocking !== 'string')
521
+ throw new Error(unlocking.error);
522
+ return { 0: { unlockingScript: unlocking } };
523
+ });
524
+ }
525
+ catch (error) {
526
+ console.error('[sendOpns]', error);
241
527
  return {
242
528
  error: error instanceof Error ? error.message : 'unknown-error',
243
529
  };
244
530
  }
245
531
  },
246
532
  };
247
- /**
248
- * List an OpNS name for sale.
249
- * Spends the current lock (PushDrop or P2PKH); OrdLock output has no identity bind.
250
- */
251
- export const opnsList = {
533
+ /** @deprecated Use sendOpns */
534
+ export const opnsTransfer = sendOpns;
535
+ export const cancelOpnsListing = {
252
536
  meta: {
253
- name: 'opnsList',
254
- description: 'List an OpNS name for sale',
537
+ name: 'cancelOpnsListing',
538
+ description: 'Cancel an OpNS name listing back into the OPNS basket',
255
539
  category: 'opns',
256
540
  inputSchema: {
257
541
  type: 'object',
258
542
  properties: {
259
- ordinal: {
260
- type: 'object',
261
- description: 'WalletOutput of the OpNS ordinal from listOutputs',
262
- },
263
- inputBEEF: {
264
- type: 'array',
265
- description: "BEEF from listOutputs with include: 'entire transactions'",
266
- },
267
- price: { type: 'integer', description: 'Price in satoshis' },
268
- payAddress: {
269
- type: 'string',
270
- description: 'Address to receive payment on purchase',
271
- },
543
+ id: { type: 'string' },
272
544
  },
273
- required: ['ordinal', 'price', 'payAddress'],
545
+ required: ['id'],
274
546
  },
275
547
  },
276
548
  async execute(ctx, input) {
277
- return listOrdinal.execute(ctx, input);
549
+ try {
550
+ const loaded = await loadOpnsSpend(ctx, input);
551
+ if ('error' in loaded)
552
+ return loaded;
553
+ const { output: listing, beef: inputBEEF } = loaded;
554
+ const outpoint = listing.outpoint;
555
+ if (!listing.customInstructions) {
556
+ return { error: 'missing-custom-instructions' };
557
+ }
558
+ const { protocolID: signProtocolID, keyID: signKeyID, counterparty: signCounterparty, } = JSON.parse(listing.customInstructions);
559
+ const newKeyID = outpoint;
560
+ const cancelAddress = await deriveCancelAddressInternal(ctx, newKeyID);
561
+ const tags = opnsFileTags(listing);
562
+ const name = nameFromOutput(listing);
563
+ const inputId = readAssetIdTag(listing.tags);
564
+ const cancelUnlock = OrdLock.cancelWithWallet(ctx.wallet, signProtocolID, signKeyID, signCounterparty);
565
+ return await executeTrackedAction(ctx.wallet, {
566
+ description: 'Cancel OpNS listing',
567
+ inputBEEF,
568
+ ...(inputId && {
569
+ labels: [buildInputAssetLabel(OPNS_BASKET, inputId)],
570
+ }),
571
+ inputs: [
572
+ {
573
+ outpoint,
574
+ inputDescription: 'Listed OpNS name',
575
+ unlockingScriptLength: 108,
576
+ },
577
+ ],
578
+ outputs: [
579
+ {
580
+ lockingScript: new P2PKH().lock(cancelAddress).toHex(),
581
+ satoshis: 1,
582
+ outputDescription: 'Cancelled OpNS listing',
583
+ basket: OPNS_BASKET,
584
+ tags,
585
+ customInstructions: JSON.stringify({
586
+ protocolID: P1SAT_PROTOCOL,
587
+ keyID: newKeyID,
588
+ ...(name && { name }),
589
+ }),
590
+ },
591
+ ],
592
+ options: { randomizeOutputs: false },
593
+ }, input.fundingProvider, inputBEEF, async (tx) => {
594
+ const unlockingScript = await cancelUnlock.sign(tx, 0);
595
+ return { 0: { unlockingScript: unlockingScript.toHex() } };
596
+ });
597
+ }
598
+ catch (error) {
599
+ console.error('[cancelOpnsListing]', error);
600
+ return {
601
+ error: error instanceof Error ? error.message : 'unknown-error',
602
+ };
603
+ }
278
604
  },
279
605
  };
606
+ /** @deprecated Use cancelOpnsListing */
607
+ export const opnsCancelListing = cancelOpnsListing;
280
608
  /**
281
- * Transfer an OpNS name to a new owner.
282
- * Spends the current lock; recipient gets plain P2PKH (bind does not carry forward).
609
+ * Buy an OpNS listing and file into the OPNS basket with full tags.
283
610
  */
284
- export const opnsTransfer = {
611
+ export const buyOpns = {
285
612
  meta: {
286
- name: 'opnsTransfer',
287
- description: 'Transfer an OpNS name to a new owner',
613
+ name: 'buyOpns',
614
+ description: 'Purchase an OpNS name listing into the OPNS basket',
288
615
  category: 'opns',
616
+ requiresServices: true,
289
617
  inputSchema: {
290
618
  type: 'object',
291
619
  properties: {
292
- ordinal: {
293
- type: 'object',
294
- description: 'WalletOutput of the OpNS ordinal from listOutputs',
295
- },
296
- counterparty: {
297
- type: 'string',
298
- description: 'Recipient identity public key (hex)',
299
- },
300
- address: {
301
- type: 'string',
302
- description: 'Recipient P2PKH address',
303
- },
304
- inputBEEF: {
305
- type: 'array',
306
- description: "BEEF from listOutputs with include: 'entire transactions'",
307
- },
620
+ outpoint: { type: 'string' },
621
+ inputBEEF: { type: 'array', items: { type: 'integer' } },
622
+ name: { type: 'string' },
623
+ origin: { type: 'string' },
624
+ marketplaceAddress: { type: 'string' },
625
+ marketplaceRate: { type: 'number' },
308
626
  },
309
- required: ['ordinal'],
627
+ required: ['outpoint'],
310
628
  },
311
629
  },
312
630
  async execute(ctx, input) {
313
- return transferOrdinals.execute(ctx, {
314
- transfers: [
315
- {
316
- ordinal: input.ordinal,
317
- counterparty: input.counterparty,
318
- address: input.address,
319
- },
320
- ],
631
+ const name = input.name?.slice(0, 64);
632
+ const origin = input.origin ?? input.outpoint;
633
+ const tags = [
634
+ 'opns',
635
+ `type:${OPNS_CONTENT_TYPE}`,
636
+ `origin:${origin}`,
637
+ ...(name ? [`name:${name}`] : []),
638
+ ];
639
+ return buyOrdinal.execute(ctx, {
640
+ outpoint: input.outpoint,
321
641
  inputBEEF: input.inputBEEF,
642
+ marketplaceAddress: input.marketplaceAddress,
643
+ marketplaceRate: input.marketplaceRate,
644
+ name,
645
+ origin,
646
+ contentType: OPNS_CONTENT_TYPE,
322
647
  fundingProvider: input.fundingProvider,
648
+ basket: OPNS_BASKET,
649
+ tags,
323
650
  });
324
651
  },
325
652
  };
326
- /** All OpNS actions for registry (paid mine lives on 1sat.name / orchestrator). */
327
653
  export const opnsActions = [
328
- getOpnsNames,
329
- opnsRegister,
330
- opnsDeregister,
331
- opnsList,
332
- opnsTransfer,
654
+ listOpns,
655
+ internalizeOpns,
656
+ registerOpns,
657
+ deregisterOpns,
658
+ sellOpns,
659
+ sendOpns,
660
+ cancelOpnsListing,
661
+ buyOpns,
333
662
  ];
334
663
  //# sourceMappingURL=index.js.map