@1sat/actions 0.0.190 → 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 +69 -76
  14. package/dist/opns/index.d.ts.map +1 -1
  15. package/dist/opns/index.js +432 -234
  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,19 +1,23 @@
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 } from '@1sat/templates';
9
- import { OPNS_BASKET, OPNS_PUBLISHED_TAG, OPNS_PUSHDROP_TEMPLATE, OPNS_REGISTER_COUNTERPARTY, P1SAT_PROTOCOL, opnsRegisterKeyId, } from '@1sat/types';
10
- import { PushDrop, Transaction, Utils, } from '@bsv/sdk';
11
- import { buildTransferOrdinals, listOrdinal, transferOrdinals } from '../ordinals';
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
12
  import { executeTrackedAction, randomActionId, } from '../utils/createTrackedAction';
13
- import { resolveBeef } from '../utils/resolveBeef';
13
+ import { loadBasketOutputBeef } from '../utils/loadBasketOutput';
14
+ import { ordinalSeedTags } from '../utils/ordinalSeedTags';
14
15
  import { signOrdinalInput, unlockingScriptLengthForInstructions, } from '../utils/signOrdinalInput';
16
+ const OPNS_CONTENT_TYPE = 'application/op-ns';
15
17
  export { opnsRegisterKeyId } from '@1sat/types';
16
- /** Locate the name delivery output on an OpNS mint tx (nodes at i,i+1 → name at i+2). */
18
+ // ============================================================================
19
+ // Helpers
20
+ // ============================================================================
17
21
  function findMintNameDelivery(tx) {
18
22
  for (let i = 0; i + 2 < tx.outputs.length; i++) {
19
23
  const parent = OpNS.decode(tx.outputs[i].lockingScript);
@@ -27,79 +31,59 @@ function findMintNameDelivery(tx) {
27
31
  }
28
32
  return null;
29
33
  }
30
- // ============================================================================
31
- // Helpers
32
- // ============================================================================
33
- function sourceNameFromOrdinal(ordinal) {
34
- if (ordinal.customInstructions) {
35
- try {
36
- const name = JSON.parse(ordinal.customInstructions).name;
37
- if (typeof name === 'string' && name)
38
- return name;
39
- }
40
- catch { }
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;
43
+ }
44
+ catch {
45
+ return undefined;
41
46
  }
42
- return ordinal.tags?.find((t) => t.startsWith('name:'))?.slice(5);
43
47
  }
44
- async function signSingleOrdinalInput(ctx, ordinal) {
45
- if (!ordinal.customInstructions) {
46
- return { error: 'missing-custom-instructions' };
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);
47
60
  }
48
- return {
49
- sign: async (tx) => {
50
- const unlocking = await signOrdinalInput(ctx, tx, 0, ordinal.customInstructions);
51
- if (typeof unlocking !== 'string')
52
- throw new Error(unlocking.error);
53
- return { 0: { unlockingScript: unlocking } };
54
- },
55
- };
61
+ return tags;
56
62
  }
57
63
  // ============================================================================
58
- // Actions
64
+ // listOpns
59
65
  // ============================================================================
60
- /**
61
- * List OpNS names in the wallet. Mirrors listOutputs (fixed OPNS basket).
62
- * Metadata-only by default; pass include: 'entire transactions' for batch BEEF.
63
- */
64
- export const getOpnsNames = {
66
+ export const listOpns = {
65
67
  meta: {
66
- name: 'getOpnsNames',
68
+ name: 'listOpns',
67
69
  description: 'List OpNS names from the wallet (metadata by default; optional BEEF)',
68
70
  category: 'opns',
69
71
  inputSchema: {
70
72
  type: 'object',
71
73
  properties: {
72
- tags: {
73
- type: 'array',
74
- description: 'Filter tags (listOutputs tags)',
75
- items: { type: 'string' },
76
- },
77
- tagQueryMode: {
78
- type: 'string',
79
- enum: ['all', 'any'],
80
- description: 'How tags combine (default any when filtering)',
81
- },
82
- names: {
83
- type: 'array',
84
- description: 'Appended as name:${n} tags',
85
- items: { type: 'string' },
86
- },
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' } },
87
78
  include: {
88
79
  type: 'string',
89
80
  enum: ['locking scripts', 'entire transactions'],
90
- description: 'Omit for metadata-only (no BEEF)',
91
81
  },
92
82
  includeCustomInstructions: { type: 'boolean' },
93
83
  includeTags: { type: 'boolean' },
94
84
  includeLabels: { type: 'boolean' },
95
- limit: {
96
- type: 'integer',
97
- description: 'Max names to return (default: 100)',
98
- },
99
- offset: {
100
- type: 'integer',
101
- description: 'Offset for pagination (default: 0)',
102
- },
85
+ limit: { type: 'integer' },
86
+ offset: { type: 'integer' },
103
87
  },
104
88
  },
105
89
  },
@@ -109,6 +93,10 @@ export const getOpnsNames = {
109
93
  if (n)
110
94
  tags.push(`name:${n}`);
111
95
  }
96
+ for (const id of input.ids ?? []) {
97
+ if (id)
98
+ tags.push(id.startsWith('id:') ? id : `id:${id}`);
99
+ }
112
100
  const filtering = tags.length > 0;
113
101
  const result = await ctx.wallet.listOutputs({
114
102
  basket: OPNS_BASKET,
@@ -132,51 +120,40 @@ export const getOpnsNames = {
132
120
  };
133
121
  },
134
122
  };
135
- /**
136
- * Internalize an OpNS mint delivery into the wallet model (basket, name tag, id:).
137
- * Caller supplies BEEF + the spend derivation used for the receive lock.
138
- */
123
+ /** @deprecated Use listOpns */
124
+ export const getOpnsNames = listOpns;
125
+ // ============================================================================
126
+ // internalizeOpns (ingress)
127
+ // ============================================================================
139
128
  export const internalizeOpns = {
140
129
  meta: {
141
130
  name: 'internalizeOpns',
142
- description: 'Internalize a foreign-created OpNS mint into the wallet with tracking tags',
131
+ description: 'Internalize a foreign-created OpNS mint (AtomicBEEF) into the OPNS basket',
143
132
  category: 'opns',
144
133
  inputSchema: {
145
134
  type: 'object',
146
135
  properties: {
147
- tx: {
148
- type: 'array',
149
- description: 'Mint AtomicBEEF (BRC-95) bytes',
150
- items: { type: 'integer' },
151
- },
152
- protocolID: {
153
- type: 'array',
154
- description: 'Wallet protocol for spend derivation',
155
- },
156
- keyID: {
157
- type: 'string',
158
- description: 'Key ID for spend derivation',
159
- },
160
- counterparty: {
161
- type: 'string',
162
- description: 'Counterparty for spend derivation (default self)',
163
- },
136
+ tx: { type: 'array', items: { type: 'integer' } },
137
+ protocolID: { type: 'array' },
138
+ keyID: { type: 'string' },
139
+ counterparty: { type: 'string' },
164
140
  },
165
141
  required: ['tx', 'protocolID', 'keyID'],
166
142
  },
167
143
  },
168
- async execute(_ctx, input) {
144
+ async execute(ctx, input) {
169
145
  try {
170
146
  const parsed = Transaction.fromAtomicBEEF(input.tx);
171
147
  const txid = parsed.id('hex');
172
148
  const delivery = findMintNameDelivery(parsed);
173
- if (!delivery) {
149
+ if (!delivery)
174
150
  return { error: 'not-an-opns-mint' };
175
- }
176
151
  const { vout, name } = delivery;
152
+ const outpoint = `${txid}.${vout}`;
177
153
  const actionId = randomActionId();
154
+ const idTag = `id:${actionId}_${vout}`;
178
155
  const counterparty = input.counterparty ?? 'self';
179
- await _ctx.wallet.internalizeAction({
156
+ await ctx.wallet.internalizeAction({
180
157
  tx: input.tx,
181
158
  outputs: [
182
159
  {
@@ -184,7 +161,13 @@ export const internalizeOpns = {
184
161
  protocol: 'basket insertion',
185
162
  insertionRemittance: {
186
163
  basket: OPNS_BASKET,
187
- tags: ['opns', `name:${name}`, `id:${actionId}_${vout}`],
164
+ tags: [
165
+ 'opns',
166
+ `type:${OPNS_CONTENT_TYPE}`,
167
+ `origin:${outpoint}`,
168
+ `name:${name}`,
169
+ idTag,
170
+ ],
188
171
  customInstructions: JSON.stringify({
189
172
  protocolID: input.protocolID,
190
173
  keyID: input.keyID,
@@ -198,8 +181,9 @@ export const internalizeOpns = {
198
181
  });
199
182
  return {
200
183
  txid,
201
- outpoint: `${txid}.${vout}`,
184
+ outpoint,
202
185
  name,
186
+ id: `${actionId}_${vout}`,
203
187
  };
204
188
  }
205
189
  catch (err) {
@@ -209,58 +193,50 @@ export const internalizeOpns = {
209
193
  }
210
194
  },
211
195
  };
212
- /**
213
- * Bind the wallet identity key to an OpNS name via signed PushDrop.
214
- *
215
- * Lock: PushDrop under [0,'p 1sat'] / opns:{inputOutpoint} / anyone, forSelf.
216
- * fields[0] = identity pubkey bytes; field-sig included (same derivation).
217
- */
218
- export const opnsRegister = {
196
+ // ============================================================================
197
+ // register / deregister
198
+ // ============================================================================
199
+ export const registerOpns = {
219
200
  meta: {
220
- name: 'opnsRegister',
201
+ name: 'registerOpns',
221
202
  description: 'Bind BRC-100 identity key to an OpNS name via signed PushDrop',
222
203
  category: 'opns',
223
204
  inputSchema: {
224
205
  type: 'object',
225
206
  properties: {
226
- ordinal: {
227
- type: 'object',
228
- description: 'WalletOutput of the OpNS ordinal from listOutputs',
229
- },
230
- inputBEEF: {
231
- type: 'array',
232
- description: "BEEF from listOutputs with include: 'entire transactions'",
233
- },
207
+ id: { type: 'string', description: 'OPNS basket tracking id' },
234
208
  },
235
- required: ['ordinal'],
209
+ required: ['id'],
236
210
  },
237
211
  },
238
212
  async execute(ctx, input) {
239
213
  try {
240
- const { ordinal } = input;
241
- 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) {
242
219
  return { error: 'missing-custom-instructions' };
243
220
  }
244
- const inputBEEF = input.inputBEEF ??
245
- (await resolveBeef(ctx.wallet, OPNS_BASKET, ordinal));
246
221
  const { publicKey: identityPubKey } = await ctx.wallet.getPublicKey({
247
222
  identityKey: true,
248
223
  });
249
- const keyID = opnsRegisterKeyId(ordinal.outpoint);
224
+ const keyID = opnsRegisterKeyId(output.outpoint);
250
225
  const lockingScript = await new PushDrop(ctx.wallet).lock([Utils.toArray(identityPubKey, 'hex')], P1SAT_PROTOCOL, keyID, OPNS_REGISTER_COUNTERPARTY, true, true);
251
- const tags = [
252
- ...(ordinal.tags ?? []).filter((t) => t !== OPNS_PUBLISHED_TAG && !t.startsWith('ordlock')),
253
- OPNS_PUBLISHED_TAG,
254
- ];
255
- const name = sourceNameFromOrdinal(ordinal);
256
- 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, {
257
230
  description: 'Register OpNS identity bind',
258
- inputBEEF,
231
+ inputBEEF: beef,
232
+ ...(inputId && {
233
+ labels: [buildInputAssetLabel(OPNS_BASKET, inputId)],
234
+ }),
259
235
  inputs: [
260
236
  {
261
- outpoint: ordinal.outpoint,
237
+ outpoint: output.outpoint,
262
238
  inputDescription: 'OpNS name to register',
263
- unlockingScriptLength: unlockingScriptLengthForInstructions(ordinal.customInstructions),
239
+ unlockingScriptLength: unlockingScriptLengthForInstructions(output.customInstructions),
264
240
  },
265
241
  ],
266
242
  outputs: [
@@ -280,186 +256,408 @@ export const opnsRegister = {
280
256
  },
281
257
  ],
282
258
  options: { randomizeOutputs: false },
283
- }, input.fundingProvider, inputBEEF, async (tx) => {
284
- 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);
285
261
  if (typeof unlocking !== 'string')
286
262
  throw new Error(unlocking.error);
287
263
  return { 0: { unlockingScript: unlocking } };
288
264
  });
289
- return result;
290
265
  }
291
266
  catch (error) {
292
- console.error('[opnsRegister]', error);
293
- if (ctx.debug && ctx.log) {
294
- ctx.log({
295
- timestamp: new Date().toISOString(),
296
- action: 'opnsRegister',
297
- input: { outpoint: input.ordinal.outpoint },
298
- error: error instanceof Error ? error.message : 'unknown-error',
299
- });
300
- }
267
+ console.error('[registerOpns]', error);
301
268
  return {
302
269
  error: error instanceof Error ? error.message : 'unknown-error',
303
270
  };
304
271
  }
305
272
  },
306
273
  };
307
- /**
308
- * Remove an identity bind by self-transferring to plain P2PKH.
309
- * Spending the PushDrop clears the on-chain bind.
310
- */
311
- export const opnsDeregister = {
274
+ /** @deprecated Use registerOpns */
275
+ export const opnsRegister = registerOpns;
276
+ export const deregisterOpns = {
312
277
  meta: {
313
- name: 'opnsDeregister',
278
+ name: 'deregisterOpns',
314
279
  description: 'Remove identity bind from an OpNS name (self-transfer to P2PKH)',
315
280
  category: 'opns',
316
281
  inputSchema: {
317
282
  type: 'object',
318
283
  properties: {
319
- ordinal: {
320
- type: 'object',
321
- description: 'WalletOutput of the OpNS ordinal from listOutputs',
322
- },
323
- inputBEEF: {
324
- type: 'array',
325
- description: "BEEF from listOutputs with include: 'entire transactions'",
326
- },
284
+ id: { type: 'string' },
327
285
  },
328
- required: ['ordinal'],
286
+ required: ['id'],
329
287
  },
330
288
  },
331
289
  async execute(ctx, input) {
332
290
  try {
333
- const { ordinal } = input;
334
- const inputBEEF = input.inputBEEF ?? (await resolveBeef(ctx.wallet, OPNS_BASKET, ordinal));
335
- const params = await buildTransferOrdinals(ctx, {
336
- 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: [
337
316
  {
338
- ordinal,
339
- counterparty: 'self',
340
- extraTags: [],
317
+ outpoint,
318
+ inputDescription: 'OpNS name to deregister',
319
+ unlockingScriptLength: unlockingScriptLengthForInstructions(output.customInstructions),
341
320
  },
342
321
  ],
343
- 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 } };
344
342
  });
345
- if ('error' in params) {
346
- return params;
347
- }
348
- // Drop published tag on the plain self-transfer output
349
- if (params.outputs?.[0]?.tags) {
350
- 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' };
351
385
  }
352
- const signer = await signSingleOrdinalInput(ctx, ordinal);
353
- if ('error' in signer)
354
- 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);
355
393
  return await executeTrackedAction(ctx.wallet, {
356
- ...params,
357
- 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
+ ],
358
420
  options: { randomizeOutputs: false },
359
- }, 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
+ });
360
427
  }
361
428
  catch (error) {
362
- console.error('[opnsDeregister]', error);
363
- if (ctx.debug && ctx.log) {
364
- ctx.log({
365
- timestamp: new Date().toISOString(),
366
- action: 'opnsDeregister',
367
- input: { outpoint: input.ordinal.outpoint },
368
- 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,
369
474
  });
475
+ recipientAddress = PublicKey.fromString(publicKey).toAddress();
476
+ }
477
+ else {
478
+ recipientAddress = input.address;
370
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);
371
527
  return {
372
528
  error: error instanceof Error ? error.message : 'unknown-error',
373
529
  };
374
530
  }
375
531
  },
376
532
  };
377
- /**
378
- * List an OpNS name for sale.
379
- * Spends the current lock (PushDrop or P2PKH); OrdLock output has no identity bind.
380
- */
381
- export const opnsList = {
533
+ /** @deprecated Use sendOpns */
534
+ export const opnsTransfer = sendOpns;
535
+ export const cancelOpnsListing = {
382
536
  meta: {
383
- name: 'opnsList',
384
- description: 'List an OpNS name for sale',
537
+ name: 'cancelOpnsListing',
538
+ description: 'Cancel an OpNS name listing back into the OPNS basket',
385
539
  category: 'opns',
386
540
  inputSchema: {
387
541
  type: 'object',
388
542
  properties: {
389
- ordinal: {
390
- type: 'object',
391
- description: 'WalletOutput of the OpNS ordinal from listOutputs',
392
- },
393
- inputBEEF: {
394
- type: 'array',
395
- description: "BEEF from listOutputs with include: 'entire transactions'",
396
- },
397
- price: { type: 'integer', description: 'Price in satoshis' },
398
- payAddress: {
399
- type: 'string',
400
- description: 'Address to receive payment on purchase',
401
- },
543
+ id: { type: 'string' },
402
544
  },
403
- required: ['ordinal', 'price', 'payAddress'],
545
+ required: ['id'],
404
546
  },
405
547
  },
406
548
  async execute(ctx, input) {
407
- 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
+ }
408
604
  },
409
605
  };
606
+ /** @deprecated Use cancelOpnsListing */
607
+ export const opnsCancelListing = cancelOpnsListing;
410
608
  /**
411
- * Transfer an OpNS name to a new owner.
412
- * 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.
413
610
  */
414
- export const opnsTransfer = {
611
+ export const buyOpns = {
415
612
  meta: {
416
- name: 'opnsTransfer',
417
- description: 'Transfer an OpNS name to a new owner',
613
+ name: 'buyOpns',
614
+ description: 'Purchase an OpNS name listing into the OPNS basket',
418
615
  category: 'opns',
616
+ requiresServices: true,
419
617
  inputSchema: {
420
618
  type: 'object',
421
619
  properties: {
422
- ordinal: {
423
- type: 'object',
424
- description: 'WalletOutput of the OpNS ordinal from listOutputs',
425
- },
426
- counterparty: {
427
- type: 'string',
428
- description: 'Recipient identity public key (hex)',
429
- },
430
- address: {
431
- type: 'string',
432
- description: 'Recipient P2PKH address',
433
- },
434
- inputBEEF: {
435
- type: 'array',
436
- description: "BEEF from listOutputs with include: 'entire transactions'",
437
- },
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' },
438
626
  },
439
- required: ['ordinal'],
627
+ required: ['outpoint'],
440
628
  },
441
629
  },
442
630
  async execute(ctx, input) {
443
- return transferOrdinals.execute(ctx, {
444
- transfers: [
445
- {
446
- ordinal: input.ordinal,
447
- counterparty: input.counterparty,
448
- address: input.address,
449
- },
450
- ],
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,
451
641
  inputBEEF: input.inputBEEF,
642
+ marketplaceAddress: input.marketplaceAddress,
643
+ marketplaceRate: input.marketplaceRate,
644
+ name,
645
+ origin,
646
+ contentType: OPNS_CONTENT_TYPE,
452
647
  fundingProvider: input.fundingProvider,
648
+ basket: OPNS_BASKET,
649
+ tags,
453
650
  });
454
651
  },
455
652
  };
456
- /** All OpNS actions for registry (paid mine lives on 1sat.name / orchestrator). */
457
653
  export const opnsActions = [
458
- getOpnsNames,
654
+ listOpns,
459
655
  internalizeOpns,
460
- opnsRegister,
461
- opnsDeregister,
462
- opnsList,
463
- opnsTransfer,
656
+ registerOpns,
657
+ deregisterOpns,
658
+ sellOpns,
659
+ sendOpns,
660
+ cancelOpnsListing,
661
+ buyOpns,
464
662
  ];
465
663
  //# sourceMappingURL=index.js.map