@1sat/actions 0.0.190 → 0.0.192

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 +43 -76
  14. package/dist/opns/index.d.ts.map +1 -1
  15. package/dist/opns/index.js +420 -234
  16. package/dist/opns/index.js.map +1 -1
  17. package/dist/ordinals/index.d.ts +58 -44
  18. package/dist/ordinals/index.d.ts.map +1 -1
  19. package/dist/ordinals/index.js +217 -188
  20. package/dist/ordinals/index.js.map +1 -1
  21. package/dist/tokens/index.d.ts +3 -3
  22. package/dist/tokens/index.d.ts.map +1 -1
  23. package/dist/tokens/index.js +38 -24
  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,38 @@ 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
+ // ============================================================================
124
+ // internalizeOpns (ingress)
125
+ // ============================================================================
139
126
  export const internalizeOpns = {
140
127
  meta: {
141
128
  name: 'internalizeOpns',
142
- description: 'Internalize a foreign-created OpNS mint into the wallet with tracking tags',
129
+ description: 'Internalize a foreign-created OpNS mint (AtomicBEEF) into the OPNS basket',
143
130
  category: 'opns',
144
131
  inputSchema: {
145
132
  type: 'object',
146
133
  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
- },
134
+ tx: { type: 'array', items: { type: 'integer' } },
135
+ protocolID: { type: 'array' },
136
+ keyID: { type: 'string' },
137
+ counterparty: { type: 'string' },
164
138
  },
165
139
  required: ['tx', 'protocolID', 'keyID'],
166
140
  },
167
141
  },
168
- async execute(_ctx, input) {
142
+ async execute(ctx, input) {
169
143
  try {
170
144
  const parsed = Transaction.fromAtomicBEEF(input.tx);
171
145
  const txid = parsed.id('hex');
172
146
  const delivery = findMintNameDelivery(parsed);
173
- if (!delivery) {
147
+ if (!delivery)
174
148
  return { error: 'not-an-opns-mint' };
175
- }
176
149
  const { vout, name } = delivery;
150
+ const outpoint = `${txid}.${vout}`;
177
151
  const actionId = randomActionId();
152
+ const idTag = `id:${actionId}_${vout}`;
178
153
  const counterparty = input.counterparty ?? 'self';
179
- await _ctx.wallet.internalizeAction({
154
+ await ctx.wallet.internalizeAction({
180
155
  tx: input.tx,
181
156
  outputs: [
182
157
  {
@@ -184,7 +159,13 @@ export const internalizeOpns = {
184
159
  protocol: 'basket insertion',
185
160
  insertionRemittance: {
186
161
  basket: OPNS_BASKET,
187
- tags: ['opns', `name:${name}`, `id:${actionId}_${vout}`],
162
+ tags: [
163
+ 'opns',
164
+ `type:${OPNS_CONTENT_TYPE}`,
165
+ `origin:${outpoint}`,
166
+ `name:${name}`,
167
+ idTag,
168
+ ],
188
169
  customInstructions: JSON.stringify({
189
170
  protocolID: input.protocolID,
190
171
  keyID: input.keyID,
@@ -198,8 +179,9 @@ export const internalizeOpns = {
198
179
  });
199
180
  return {
200
181
  txid,
201
- outpoint: `${txid}.${vout}`,
182
+ outpoint,
202
183
  name,
184
+ id: `${actionId}_${vout}`,
203
185
  };
204
186
  }
205
187
  catch (err) {
@@ -209,58 +191,50 @@ export const internalizeOpns = {
209
191
  }
210
192
  },
211
193
  };
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 = {
194
+ // ============================================================================
195
+ // register / deregister
196
+ // ============================================================================
197
+ export const registerOpns = {
219
198
  meta: {
220
- name: 'opnsRegister',
199
+ name: 'registerOpns',
221
200
  description: 'Bind BRC-100 identity key to an OpNS name via signed PushDrop',
222
201
  category: 'opns',
223
202
  inputSchema: {
224
203
  type: 'object',
225
204
  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
- },
205
+ id: { type: 'string', description: 'OPNS basket tracking id' },
234
206
  },
235
- required: ['ordinal'],
207
+ required: ['id'],
236
208
  },
237
209
  },
238
210
  async execute(ctx, input) {
239
211
  try {
240
- const { ordinal } = input;
241
- if (!ordinal.customInstructions) {
212
+ const loaded = await loadOpnsSpend(ctx, input);
213
+ if ('error' in loaded)
214
+ return loaded;
215
+ const { output, beef } = loaded;
216
+ if (!output.customInstructions) {
242
217
  return { error: 'missing-custom-instructions' };
243
218
  }
244
- const inputBEEF = input.inputBEEF ??
245
- (await resolveBeef(ctx.wallet, OPNS_BASKET, ordinal));
246
219
  const { publicKey: identityPubKey } = await ctx.wallet.getPublicKey({
247
220
  identityKey: true,
248
221
  });
249
- const keyID = opnsRegisterKeyId(ordinal.outpoint);
222
+ const keyID = opnsRegisterKeyId(output.outpoint);
250
223
  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, {
224
+ const name = nameFromOutput(output);
225
+ const tags = opnsFileTags(output, [OPNS_PUBLISHED_TAG]);
226
+ const inputId = readAssetIdTag(output.tags);
227
+ return await executeTrackedAction(ctx.wallet, {
257
228
  description: 'Register OpNS identity bind',
258
- inputBEEF,
229
+ inputBEEF: beef,
230
+ ...(inputId && {
231
+ labels: [buildInputAssetLabel(OPNS_BASKET, inputId)],
232
+ }),
259
233
  inputs: [
260
234
  {
261
- outpoint: ordinal.outpoint,
235
+ outpoint: output.outpoint,
262
236
  inputDescription: 'OpNS name to register',
263
- unlockingScriptLength: unlockingScriptLengthForInstructions(ordinal.customInstructions),
237
+ unlockingScriptLength: unlockingScriptLengthForInstructions(output.customInstructions),
264
238
  },
265
239
  ],
266
240
  outputs: [
@@ -280,186 +254,398 @@ export const opnsRegister = {
280
254
  },
281
255
  ],
282
256
  options: { randomizeOutputs: false },
283
- }, input.fundingProvider, inputBEEF, async (tx) => {
284
- const unlocking = await signOrdinalInput(ctx, tx, 0, ordinal.customInstructions);
257
+ }, input.fundingProvider, beef, async (tx) => {
258
+ const unlocking = await signOrdinalInput(ctx, tx, 0, output.customInstructions);
285
259
  if (typeof unlocking !== 'string')
286
260
  throw new Error(unlocking.error);
287
261
  return { 0: { unlockingScript: unlocking } };
288
262
  });
289
- return result;
290
263
  }
291
264
  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
- }
265
+ console.error('[registerOpns]', error);
301
266
  return {
302
267
  error: error instanceof Error ? error.message : 'unknown-error',
303
268
  };
304
269
  }
305
270
  },
306
271
  };
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 = {
272
+ export const deregisterOpns = {
312
273
  meta: {
313
- name: 'opnsDeregister',
274
+ name: 'deregisterOpns',
314
275
  description: 'Remove identity bind from an OpNS name (self-transfer to P2PKH)',
315
276
  category: 'opns',
316
277
  inputSchema: {
317
278
  type: 'object',
318
279
  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
- },
280
+ id: { type: 'string' },
327
281
  },
328
- required: ['ordinal'],
282
+ required: ['id'],
329
283
  },
330
284
  },
331
285
  async execute(ctx, input) {
332
286
  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: [
287
+ const loaded = await loadOpnsSpend(ctx, input);
288
+ if ('error' in loaded)
289
+ return loaded;
290
+ const { output, beef } = loaded;
291
+ if (!output.customInstructions) {
292
+ return { error: 'missing-custom-instructions' };
293
+ }
294
+ const outpoint = output.outpoint;
295
+ const { publicKey } = await ctx.wallet.getPublicKey({
296
+ protocolID: P1SAT_PROTOCOL,
297
+ keyID: outpoint,
298
+ counterparty: 'self',
299
+ forSelf: true,
300
+ });
301
+ const address = PublicKey.fromString(publicKey).toAddress();
302
+ const tags = opnsFileTags(output);
303
+ const name = nameFromOutput(output);
304
+ const inputId = readAssetIdTag(output.tags);
305
+ return await executeTrackedAction(ctx.wallet, {
306
+ description: 'Deregister OpNS identity bind',
307
+ inputBEEF: beef,
308
+ ...(inputId && {
309
+ labels: [buildInputAssetLabel(OPNS_BASKET, inputId)],
310
+ }),
311
+ inputs: [
337
312
  {
338
- ordinal,
339
- counterparty: 'self',
340
- extraTags: [],
313
+ outpoint,
314
+ inputDescription: 'OpNS name to deregister',
315
+ unlockingScriptLength: unlockingScriptLengthForInstructions(output.customInstructions),
341
316
  },
342
317
  ],
343
- inputBEEF,
318
+ outputs: [
319
+ {
320
+ lockingScript: new P2PKH().lock(address).toHex(),
321
+ satoshis: 1,
322
+ outputDescription: 'OpNS name (unbound)',
323
+ basket: OPNS_BASKET,
324
+ tags,
325
+ customInstructions: JSON.stringify({
326
+ protocolID: P1SAT_PROTOCOL,
327
+ keyID: outpoint,
328
+ ...(name && { name }),
329
+ }),
330
+ },
331
+ ],
332
+ options: { randomizeOutputs: false },
333
+ }, input.fundingProvider, beef, async (tx) => {
334
+ const unlocking = await signOrdinalInput(ctx, tx, 0, output.customInstructions);
335
+ if (typeof unlocking !== 'string')
336
+ throw new Error(unlocking.error);
337
+ return { 0: { unlockingScript: unlocking } };
344
338
  });
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);
339
+ }
340
+ catch (error) {
341
+ console.error('[deregisterOpns]', error);
342
+ return {
343
+ error: error instanceof Error ? error.message : 'unknown-error',
344
+ };
345
+ }
346
+ },
347
+ };
348
+ // ============================================================================
349
+ // sell / send / cancel
350
+ // ============================================================================
351
+ export const sellOpns = {
352
+ meta: {
353
+ name: 'sellOpns',
354
+ description: 'List an OpNS name for sale',
355
+ category: 'opns',
356
+ inputSchema: {
357
+ type: 'object',
358
+ properties: {
359
+ id: { type: 'string' },
360
+ price: { type: 'integer' },
361
+ payAddress: {
362
+ type: 'string',
363
+ description: 'Payment address (default: P1SAT keyID 1sat 0)',
364
+ },
365
+ },
366
+ required: ['id', 'price'],
367
+ },
368
+ },
369
+ async execute(ctx, input) {
370
+ try {
371
+ if (input.price <= 0)
372
+ return { error: 'invalid-price' };
373
+ const loaded = await loadOpnsSpend(ctx, input);
374
+ if ('error' in loaded)
375
+ return loaded;
376
+ const { output, beef } = loaded;
377
+ if (!output.customInstructions) {
378
+ return { error: 'missing-custom-instructions' };
351
379
  }
352
- const signer = await signSingleOrdinalInput(ctx, ordinal);
353
- if ('error' in signer)
354
- return signer;
380
+ const outpoint = output.outpoint;
381
+ const payAddress = input.payAddress ?? (await defaultPayAddress(ctx));
382
+ const cancelAddress = await deriveCancelAddressInternal(ctx, outpoint);
383
+ const lockingScript = buildOrdLockScript(cancelAddress, payAddress, input.price).toHex();
384
+ const tags = opnsFileTags(output, ['ordlock', `price:${input.price}`]);
385
+ const name = nameFromOutput(output);
386
+ const inputId = readAssetIdTag(output.tags);
355
387
  return await executeTrackedAction(ctx.wallet, {
356
- ...params,
357
- description: 'Deregister OpNS identity bind',
388
+ description: `List OpNS for ${input.price} sats`,
389
+ inputBEEF: beef,
390
+ ...(inputId && {
391
+ labels: [buildInputAssetLabel(OPNS_BASKET, inputId)],
392
+ }),
393
+ inputs: [
394
+ {
395
+ outpoint,
396
+ inputDescription: 'OpNS name to list',
397
+ unlockingScriptLength: unlockingScriptLengthForInstructions(output.customInstructions),
398
+ },
399
+ ],
400
+ outputs: [
401
+ {
402
+ lockingScript,
403
+ satoshis: 1,
404
+ outputDescription: `List OpNS for ${input.price} sats`,
405
+ basket: OPNS_BASKET,
406
+ tags,
407
+ customInstructions: JSON.stringify({
408
+ protocolID: P1SAT_PROTOCOL,
409
+ keyID: outpoint,
410
+ ...(name && { name }),
411
+ }),
412
+ },
413
+ ],
358
414
  options: { randomizeOutputs: false },
359
- }, input.fundingProvider, inputBEEF, signer.sign);
415
+ }, input.fundingProvider, beef, async (tx) => {
416
+ const unlocking = await signOrdinalInput(ctx, tx, 0, output.customInstructions);
417
+ if (typeof unlocking !== 'string')
418
+ throw new Error(unlocking.error);
419
+ return { 0: { unlockingScript: unlocking } };
420
+ });
360
421
  }
361
422
  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',
423
+ console.error('[sellOpns]', error);
424
+ return {
425
+ error: error instanceof Error ? error.message : 'unknown-error',
426
+ };
427
+ }
428
+ },
429
+ };
430
+ export const sendOpns = {
431
+ meta: {
432
+ name: 'sendOpns',
433
+ description: 'Transfer an OpNS name to a new owner',
434
+ category: 'opns',
435
+ inputSchema: {
436
+ type: 'object',
437
+ properties: {
438
+ id: { type: 'string' },
439
+ counterparty: { type: 'string' },
440
+ address: { type: 'string' },
441
+ },
442
+ required: ['id'],
443
+ },
444
+ },
445
+ async execute(ctx, input) {
446
+ try {
447
+ if (!input.counterparty && !input.address) {
448
+ return { error: 'must-provide-counterparty-or-address' };
449
+ }
450
+ const loaded = await loadOpnsSpend(ctx, input);
451
+ if ('error' in loaded)
452
+ return loaded;
453
+ const { output, beef } = loaded;
454
+ if (!output.customInstructions) {
455
+ return { error: 'missing-custom-instructions' };
456
+ }
457
+ const outpoint = output.outpoint;
458
+ const isSelf = input.counterparty === 'self';
459
+ let recipientAddress;
460
+ if (input.counterparty) {
461
+ const { publicKey } = await ctx.wallet.getPublicKey({
462
+ protocolID: P1SAT_PROTOCOL,
463
+ keyID: outpoint,
464
+ counterparty: input.counterparty,
465
+ forSelf: isSelf,
369
466
  });
467
+ recipientAddress = PublicKey.fromString(publicKey).toAddress();
468
+ }
469
+ else {
470
+ recipientAddress = input.address;
370
471
  }
472
+ const inputId = readAssetIdTag(output.tags);
473
+ const name = nameFromOutput(output);
474
+ const tags = opnsFileTags(output);
475
+ return await executeTrackedAction(ctx.wallet, {
476
+ description: 'Transfer OpNS name',
477
+ inputBEEF: beef,
478
+ ...(inputId && {
479
+ labels: [buildInputAssetLabel(OPNS_BASKET, inputId)],
480
+ }),
481
+ inputs: [
482
+ {
483
+ outpoint,
484
+ inputDescription: 'OpNS name to transfer',
485
+ unlockingScriptLength: unlockingScriptLengthForInstructions(output.customInstructions),
486
+ },
487
+ ],
488
+ outputs: [
489
+ isSelf
490
+ ? {
491
+ lockingScript: new P2PKH().lock(recipientAddress).toHex(),
492
+ satoshis: 1,
493
+ outputDescription: 'OpNS self-transfer',
494
+ basket: OPNS_BASKET,
495
+ tags,
496
+ customInstructions: JSON.stringify({
497
+ protocolID: P1SAT_PROTOCOL,
498
+ keyID: outpoint,
499
+ ...(name && { name }),
500
+ }),
501
+ }
502
+ : {
503
+ lockingScript: new P2PKH().lock(recipientAddress).toHex(),
504
+ satoshis: 1,
505
+ outputDescription: 'OpNS transfer',
506
+ tags: [],
507
+ },
508
+ ],
509
+ options: { randomizeOutputs: false },
510
+ }, input.fundingProvider, beef, async (tx) => {
511
+ const unlocking = await signOrdinalInput(ctx, tx, 0, output.customInstructions);
512
+ if (typeof unlocking !== 'string')
513
+ throw new Error(unlocking.error);
514
+ return { 0: { unlockingScript: unlocking } };
515
+ });
516
+ }
517
+ catch (error) {
518
+ console.error('[sendOpns]', error);
371
519
  return {
372
520
  error: error instanceof Error ? error.message : 'unknown-error',
373
521
  };
374
522
  }
375
523
  },
376
524
  };
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 = {
525
+ export const cancelOpnsListing = {
382
526
  meta: {
383
- name: 'opnsList',
384
- description: 'List an OpNS name for sale',
527
+ name: 'cancelOpnsListing',
528
+ description: 'Cancel an OpNS name listing back into the OPNS basket',
385
529
  category: 'opns',
386
530
  inputSchema: {
387
531
  type: 'object',
388
532
  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
- },
533
+ id: { type: 'string' },
402
534
  },
403
- required: ['ordinal', 'price', 'payAddress'],
535
+ required: ['id'],
404
536
  },
405
537
  },
406
538
  async execute(ctx, input) {
407
- return listOrdinal.execute(ctx, input);
539
+ try {
540
+ const loaded = await loadOpnsSpend(ctx, input);
541
+ if ('error' in loaded)
542
+ return loaded;
543
+ const { output: listing, beef: inputBEEF } = loaded;
544
+ const outpoint = listing.outpoint;
545
+ if (!listing.customInstructions) {
546
+ return { error: 'missing-custom-instructions' };
547
+ }
548
+ const { protocolID: signProtocolID, keyID: signKeyID, counterparty: signCounterparty, } = JSON.parse(listing.customInstructions);
549
+ const newKeyID = outpoint;
550
+ const cancelAddress = await deriveCancelAddressInternal(ctx, newKeyID);
551
+ const tags = opnsFileTags(listing);
552
+ const name = nameFromOutput(listing);
553
+ const inputId = readAssetIdTag(listing.tags);
554
+ const cancelUnlock = OrdLock.cancelWithWallet(ctx.wallet, signProtocolID, signKeyID, signCounterparty);
555
+ return await executeTrackedAction(ctx.wallet, {
556
+ description: 'Cancel OpNS listing',
557
+ inputBEEF,
558
+ ...(inputId && {
559
+ labels: [buildInputAssetLabel(OPNS_BASKET, inputId)],
560
+ }),
561
+ inputs: [
562
+ {
563
+ outpoint,
564
+ inputDescription: 'Listed OpNS name',
565
+ unlockingScriptLength: 108,
566
+ },
567
+ ],
568
+ outputs: [
569
+ {
570
+ lockingScript: new P2PKH().lock(cancelAddress).toHex(),
571
+ satoshis: 1,
572
+ outputDescription: 'Cancelled OpNS listing',
573
+ basket: OPNS_BASKET,
574
+ tags,
575
+ customInstructions: JSON.stringify({
576
+ protocolID: P1SAT_PROTOCOL,
577
+ keyID: newKeyID,
578
+ ...(name && { name }),
579
+ }),
580
+ },
581
+ ],
582
+ options: { randomizeOutputs: false },
583
+ }, input.fundingProvider, inputBEEF, async (tx) => {
584
+ const unlockingScript = await cancelUnlock.sign(tx, 0);
585
+ return { 0: { unlockingScript: unlockingScript.toHex() } };
586
+ });
587
+ }
588
+ catch (error) {
589
+ console.error('[cancelOpnsListing]', error);
590
+ return {
591
+ error: error instanceof Error ? error.message : 'unknown-error',
592
+ };
593
+ }
408
594
  },
409
595
  };
410
596
  /**
411
- * Transfer an OpNS name to a new owner.
412
- * Spends the current lock; recipient gets plain P2PKH (bind does not carry forward).
597
+ * Buy an OpNS listing and file into the OPNS basket with full tags.
413
598
  */
414
- export const opnsTransfer = {
599
+ export const buyOpns = {
415
600
  meta: {
416
- name: 'opnsTransfer',
417
- description: 'Transfer an OpNS name to a new owner',
601
+ name: 'buyOpns',
602
+ description: 'Purchase an OpNS name listing into the OPNS basket',
418
603
  category: 'opns',
604
+ requiresServices: true,
419
605
  inputSchema: {
420
606
  type: 'object',
421
607
  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
- },
608
+ outpoint: { type: 'string' },
609
+ inputBEEF: { type: 'array', items: { type: 'integer' } },
610
+ name: { type: 'string' },
611
+ origin: { type: 'string' },
612
+ marketplaceAddress: { type: 'string' },
613
+ marketplaceRate: { type: 'number' },
438
614
  },
439
- required: ['ordinal'],
615
+ required: ['outpoint'],
440
616
  },
441
617
  },
442
618
  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
- ],
619
+ const name = input.name?.slice(0, 64);
620
+ const origin = input.origin ?? input.outpoint;
621
+ const tags = [
622
+ 'opns',
623
+ `type:${OPNS_CONTENT_TYPE}`,
624
+ `origin:${origin}`,
625
+ ...(name ? [`name:${name}`] : []),
626
+ ];
627
+ return buyOrdinal.execute(ctx, {
628
+ outpoint: input.outpoint,
451
629
  inputBEEF: input.inputBEEF,
630
+ marketplaceAddress: input.marketplaceAddress,
631
+ marketplaceRate: input.marketplaceRate,
632
+ name,
633
+ origin,
634
+ contentType: OPNS_CONTENT_TYPE,
452
635
  fundingProvider: input.fundingProvider,
636
+ basket: OPNS_BASKET,
637
+ tags,
453
638
  });
454
639
  },
455
640
  };
456
- /** All OpNS actions for registry (paid mine lives on 1sat.name / orchestrator). */
457
641
  export const opnsActions = [
458
- getOpnsNames,
642
+ listOpns,
459
643
  internalizeOpns,
460
- opnsRegister,
461
- opnsDeregister,
462
- opnsList,
463
- opnsTransfer,
644
+ registerOpns,
645
+ deregisterOpns,
646
+ sellOpns,
647
+ sendOpns,
648
+ cancelOpnsListing,
649
+ buyOpns,
464
650
  ];
465
651
  //# sourceMappingURL=index.js.map