@1sat/wallet-toolbox 0.0.26 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/constants.d.ts +2 -0
- package/dist/api/constants.js +3 -0
- package/dist/api/index.d.ts +4 -4
- package/dist/api/index.js +4 -4
- package/dist/api/inscriptions/index.js +1 -0
- package/dist/api/ordinals/index.d.ts +22 -14
- package/dist/api/ordinals/index.js +208 -106
- package/dist/api/sweep/index.d.ts +9 -2
- package/dist/api/sweep/index.js +177 -4
- package/dist/api/sweep/types.d.ts +23 -0
- package/dist/api/tokens/index.js +9 -5
- package/dist/sync/SyncManager.js +2 -1
- package/package.json +1 -1
package/dist/api/constants.d.ts
CHANGED
|
@@ -17,6 +17,8 @@ export declare const LOCK_PREFIX = "20d37f4de0d1c735b4d51a5572df0f3d9104d1d9e99d
|
|
|
17
17
|
export declare const LOCK_SUFFIX = "88ac7e7601207f75a9011488";
|
|
18
18
|
export declare const ONESAT_PROTOCOL: [0 | 1 | 2, string];
|
|
19
19
|
export declare const MESSAGE_SIGNING_PROTOCOL: [0 | 1 | 2, string];
|
|
20
|
+
export declare const BSV21_PROTOCOL: [0 | 1 | 2, string];
|
|
21
|
+
export declare const BSV21_FEE_SATS = 1000;
|
|
20
22
|
export declare const MAX_INSCRIPTION_BYTES = 100000;
|
|
21
23
|
export declare const MIN_UNLOCK_SATS = 1500;
|
|
22
24
|
export declare const EXCHANGE_RATE_CACHE_TTL: number;
|
package/dist/api/constants.js
CHANGED
|
@@ -24,6 +24,9 @@ export const LOCK_SUFFIX = "88ac7e7601207f75a9011488";
|
|
|
24
24
|
// Protocol IDs
|
|
25
25
|
export const ONESAT_PROTOCOL = [1, "onesat"];
|
|
26
26
|
export const MESSAGE_SIGNING_PROTOCOL = [1, "message signing"];
|
|
27
|
+
export const BSV21_PROTOCOL = [1, "bsv21"];
|
|
28
|
+
// Fee constants
|
|
29
|
+
export const BSV21_FEE_SATS = 1000;
|
|
27
30
|
// Constants
|
|
28
31
|
export const MAX_INSCRIPTION_BYTES = 100_000;
|
|
29
32
|
export const MIN_UNLOCK_SATS = 1500;
|
package/dist/api/index.d.ts
CHANGED
|
@@ -6,17 +6,17 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* ```typescript
|
|
9
|
-
* import { createContext,
|
|
9
|
+
* import { createContext, transferOrdinals, skillRegistry } from '@1sat/wallet-toolbox/api';
|
|
10
10
|
*
|
|
11
11
|
* // Create context with a BRC-100 compatible wallet
|
|
12
12
|
* const ctx = createContext(wallet, { services, chain: 'main' });
|
|
13
13
|
*
|
|
14
14
|
* // Execute skills directly
|
|
15
|
-
* const result = await
|
|
15
|
+
* const result = await transferOrdinals.execute(ctx, { transfers: [...], inputBEEF: [...] });
|
|
16
16
|
*
|
|
17
17
|
* // Or via registry (useful for AI agents)
|
|
18
|
-
* const skill = skillRegistry.get('
|
|
19
|
-
* const result = await skill.execute(ctx, {
|
|
18
|
+
* const skill = skillRegistry.get('transferOrdinals');
|
|
19
|
+
* const result = await skill.execute(ctx, { transfers: [...], inputBEEF: [...] });
|
|
20
20
|
*
|
|
21
21
|
* // Get MCP-compatible tool list
|
|
22
22
|
* const tools = skillRegistry.toMcpTools();
|
package/dist/api/index.js
CHANGED
|
@@ -6,17 +6,17 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Usage:
|
|
8
8
|
* ```typescript
|
|
9
|
-
* import { createContext,
|
|
9
|
+
* import { createContext, transferOrdinals, skillRegistry } from '@1sat/wallet-toolbox/api';
|
|
10
10
|
*
|
|
11
11
|
* // Create context with a BRC-100 compatible wallet
|
|
12
12
|
* const ctx = createContext(wallet, { services, chain: 'main' });
|
|
13
13
|
*
|
|
14
14
|
* // Execute skills directly
|
|
15
|
-
* const result = await
|
|
15
|
+
* const result = await transferOrdinals.execute(ctx, { transfers: [...], inputBEEF: [...] });
|
|
16
16
|
*
|
|
17
17
|
* // Or via registry (useful for AI agents)
|
|
18
|
-
* const skill = skillRegistry.get('
|
|
19
|
-
* const result = await skill.execute(ctx, {
|
|
18
|
+
* const skill = skillRegistry.get('transferOrdinals');
|
|
19
|
+
* const result = await skill.execute(ctx, { transfers: [...], inputBEEF: [...] });
|
|
20
20
|
*
|
|
21
21
|
* // Get MCP-compatible tool list
|
|
22
22
|
* const tools = skillRegistry.toMcpTools();
|
|
@@ -7,19 +7,25 @@
|
|
|
7
7
|
import { type WalletOutput, type CreateActionArgs } from "@bsv/sdk";
|
|
8
8
|
import type { Skill, OneSatContext } from "../skills/types";
|
|
9
9
|
type PubKeyHex = string;
|
|
10
|
-
export interface
|
|
11
|
-
/**
|
|
12
|
-
|
|
10
|
+
export interface TransferItem {
|
|
11
|
+
/** The ordinal output to transfer (from listOutputs) */
|
|
12
|
+
ordinal: WalletOutput;
|
|
13
13
|
/** Recipient's identity public key (preferred) */
|
|
14
14
|
counterparty?: PubKeyHex;
|
|
15
|
-
/**
|
|
15
|
+
/** Raw P2PKH address */
|
|
16
16
|
address?: string;
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
}
|
|
18
|
+
export interface TransferOrdinalsRequest {
|
|
19
|
+
/** Ordinals to transfer with their destinations */
|
|
20
|
+
transfers: TransferItem[];
|
|
21
|
+
/** BEEF data from listOutputs (include: 'entire transactions') */
|
|
22
|
+
inputBEEF: number[];
|
|
19
23
|
}
|
|
20
24
|
export interface ListOrdinalRequest {
|
|
21
|
-
/**
|
|
22
|
-
|
|
25
|
+
/** The ordinal output to list (from listOutputs) */
|
|
26
|
+
ordinal: WalletOutput;
|
|
27
|
+
/** BEEF data from listOutputs (include: 'entire transactions') */
|
|
28
|
+
inputBEEF: number[];
|
|
23
29
|
/** Price in satoshis */
|
|
24
30
|
price: number;
|
|
25
31
|
/** Address that receives payment on purchase (BRC-29 receive address) */
|
|
@@ -45,10 +51,10 @@ export interface OrdinalOperationResponse {
|
|
|
45
51
|
error?: string;
|
|
46
52
|
}
|
|
47
53
|
/**
|
|
48
|
-
* Build CreateActionArgs for transferring
|
|
54
|
+
* Build CreateActionArgs for transferring one or more ordinals.
|
|
49
55
|
* Does NOT execute - returns params for createAction.
|
|
50
56
|
*/
|
|
51
|
-
export declare function
|
|
57
|
+
export declare function buildTransferOrdinals(ctx: OneSatContext, request: TransferOrdinalsRequest): Promise<CreateActionArgs | {
|
|
52
58
|
error: string;
|
|
53
59
|
}>;
|
|
54
60
|
/**
|
|
@@ -81,15 +87,17 @@ export declare const deriveCancelAddress: Skill<DeriveCancelAddressInput, string
|
|
|
81
87
|
/**
|
|
82
88
|
* Transfer an ordinal to a new owner.
|
|
83
89
|
*/
|
|
84
|
-
export declare const
|
|
90
|
+
export declare const transferOrdinals: Skill<TransferOrdinalsRequest, OrdinalOperationResponse>;
|
|
85
91
|
/**
|
|
86
92
|
* List an ordinal for sale on the global orderbook.
|
|
87
93
|
*/
|
|
88
94
|
export declare const listOrdinal: Skill<ListOrdinalRequest, OrdinalOperationResponse>;
|
|
89
95
|
/** Input for cancelListing skill */
|
|
90
96
|
export interface CancelListingInput {
|
|
91
|
-
/**
|
|
92
|
-
|
|
97
|
+
/** The listing output to cancel (from listOutputs, must include lockingScript) */
|
|
98
|
+
listing: WalletOutput;
|
|
99
|
+
/** BEEF data from listOutputs (include: 'entire transactions') */
|
|
100
|
+
inputBEEF: number[];
|
|
93
101
|
}
|
|
94
102
|
/**
|
|
95
103
|
* Cancel an ordinal listing.
|
|
@@ -100,5 +108,5 @@ export declare const cancelListing: Skill<CancelListingInput, OrdinalOperationRe
|
|
|
100
108
|
*/
|
|
101
109
|
export declare const purchaseOrdinal: Skill<PurchaseOrdinalRequest, OrdinalOperationResponse>;
|
|
102
110
|
/** All ordinals skills for registry */
|
|
103
|
-
export declare const ordinalsSkills: (Skill<ListOrdinalsInput, WalletOutput[]> | Skill<DeriveCancelAddressInput, string> | Skill<
|
|
111
|
+
export declare const ordinalsSkills: (Skill<ListOrdinalsInput, WalletOutput[]> | Skill<DeriveCancelAddressInput, string> | Skill<TransferOrdinalsRequest, OrdinalOperationResponse> | Skill<ListOrdinalRequest, OrdinalOperationResponse> | Skill<CancelListingInput, OrdinalOperationResponse> | Skill<PurchaseOrdinalRequest, OrdinalOperationResponse>)[];
|
|
104
112
|
export {};
|
|
@@ -8,6 +8,69 @@ import { BigNumber, Hash, LockingScript, OP, P2PKH, PublicKey, Script, Transacti
|
|
|
8
8
|
import { OrdLock } from "@bopen-io/templates";
|
|
9
9
|
import { ORDINALS_BASKET, ORDLOCK_PREFIX, ORDLOCK_SUFFIX, ONESAT_PROTOCOL } from "../constants";
|
|
10
10
|
// ============================================================================
|
|
11
|
+
// Helpers
|
|
12
|
+
// ============================================================================
|
|
13
|
+
function extractName(customInstructions) {
|
|
14
|
+
if (!customInstructions)
|
|
15
|
+
return undefined;
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(customInstructions);
|
|
18
|
+
return parsed.name;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Sign a P2PKH input using the wallet's key derivation.
|
|
26
|
+
* Returns the unlocking script hex for the input.
|
|
27
|
+
*/
|
|
28
|
+
async function signP2PKHInput(ctx, tx, inputIndex, protocolID, keyID) {
|
|
29
|
+
const txInput = tx.inputs[inputIndex];
|
|
30
|
+
const sourceLockingScript = txInput.sourceTransaction?.outputs[txInput.sourceOutputIndex]?.lockingScript;
|
|
31
|
+
if (!sourceLockingScript) {
|
|
32
|
+
return { error: `missing-source-locking-script-for-input-${inputIndex}` };
|
|
33
|
+
}
|
|
34
|
+
const sourceTXID = txInput.sourceTXID ?? txInput.sourceTransaction?.id("hex");
|
|
35
|
+
if (!sourceTXID) {
|
|
36
|
+
return { error: `missing-source-txid-for-input-${inputIndex}` };
|
|
37
|
+
}
|
|
38
|
+
const preimage = TransactionSignature.format({
|
|
39
|
+
sourceTXID,
|
|
40
|
+
sourceOutputIndex: txInput.sourceOutputIndex,
|
|
41
|
+
sourceSatoshis: 1,
|
|
42
|
+
transactionVersion: tx.version,
|
|
43
|
+
otherInputs: tx.inputs.filter((_, idx) => idx !== inputIndex).map((inp) => ({
|
|
44
|
+
sourceTXID: inp.sourceTXID ?? inp.sourceTransaction?.id("hex") ?? "",
|
|
45
|
+
sourceOutputIndex: inp.sourceOutputIndex,
|
|
46
|
+
sequence: inp.sequence ?? 0xffffffff,
|
|
47
|
+
})),
|
|
48
|
+
inputIndex,
|
|
49
|
+
outputs: tx.outputs,
|
|
50
|
+
inputSequence: txInput.sequence ?? 0xffffffff,
|
|
51
|
+
subscript: sourceLockingScript,
|
|
52
|
+
lockTime: tx.lockTime,
|
|
53
|
+
scope: TransactionSignature.SIGHASH_ALL | TransactionSignature.SIGHASH_FORKID,
|
|
54
|
+
});
|
|
55
|
+
const sighash = Hash.sha256(Hash.sha256(preimage));
|
|
56
|
+
const { signature } = await ctx.wallet.createSignature({
|
|
57
|
+
protocolID,
|
|
58
|
+
keyID,
|
|
59
|
+
counterparty: "self",
|
|
60
|
+
hashToDirectlySign: Array.from(sighash),
|
|
61
|
+
});
|
|
62
|
+
const { publicKey } = await ctx.wallet.getPublicKey({
|
|
63
|
+
protocolID,
|
|
64
|
+
keyID,
|
|
65
|
+
forSelf: true,
|
|
66
|
+
});
|
|
67
|
+
const sigWithHashtype = [...signature, TransactionSignature.SIGHASH_ALL | TransactionSignature.SIGHASH_FORKID];
|
|
68
|
+
return new UnlockingScript()
|
|
69
|
+
.writeBin(sigWithHashtype)
|
|
70
|
+
.writeBin(Utils.toArray(publicKey, "hex"))
|
|
71
|
+
.toHex();
|
|
72
|
+
}
|
|
73
|
+
// ============================================================================
|
|
11
74
|
// Internal helpers
|
|
12
75
|
// ============================================================================
|
|
13
76
|
async function deriveCancelAddressInternal(ctx, outpoint) {
|
|
@@ -81,64 +144,61 @@ async function buildPurchaseUnlockingScript(tx, inputIndex, sourceSatoshis, lock
|
|
|
81
144
|
// Builder functions (utilities for advanced use)
|
|
82
145
|
// ============================================================================
|
|
83
146
|
/**
|
|
84
|
-
* Build CreateActionArgs for transferring
|
|
147
|
+
* Build CreateActionArgs for transferring one or more ordinals.
|
|
85
148
|
* Does NOT execute - returns params for createAction.
|
|
86
149
|
*/
|
|
87
|
-
export async function
|
|
88
|
-
const {
|
|
89
|
-
if (!
|
|
90
|
-
return { error: "
|
|
91
|
-
}
|
|
92
|
-
let recipientAddress;
|
|
93
|
-
if (counterparty) {
|
|
94
|
-
const { publicKey } = await ctx.wallet.getPublicKey({
|
|
95
|
-
protocolID: ONESAT_PROTOCOL,
|
|
96
|
-
keyID: outpoint,
|
|
97
|
-
counterparty,
|
|
98
|
-
forSelf: false,
|
|
99
|
-
});
|
|
100
|
-
recipientAddress = PublicKey.fromString(publicKey).toAddress();
|
|
101
|
-
}
|
|
102
|
-
else if (paymail) {
|
|
103
|
-
return { error: "paymail-not-yet-implemented" };
|
|
150
|
+
export async function buildTransferOrdinals(ctx, request) {
|
|
151
|
+
const { transfers, inputBEEF } = request;
|
|
152
|
+
if (!transfers.length) {
|
|
153
|
+
return { error: "no-transfers" };
|
|
104
154
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
155
|
+
const inputs = [];
|
|
156
|
+
const outputs = [];
|
|
157
|
+
for (const { ordinal, counterparty, address } of transfers) {
|
|
158
|
+
if (!counterparty && !address) {
|
|
159
|
+
return { error: "must-provide-counterparty-or-address" };
|
|
160
|
+
}
|
|
161
|
+
const outpoint = ordinal.outpoint;
|
|
162
|
+
let recipientAddress;
|
|
163
|
+
if (counterparty) {
|
|
164
|
+
const { publicKey } = await ctx.wallet.getPublicKey({
|
|
165
|
+
protocolID: ONESAT_PROTOCOL,
|
|
166
|
+
keyID: outpoint,
|
|
167
|
+
counterparty,
|
|
168
|
+
forSelf: false,
|
|
169
|
+
});
|
|
170
|
+
recipientAddress = PublicKey.fromString(publicKey).toAddress();
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
recipientAddress = address;
|
|
174
|
+
}
|
|
175
|
+
// Preserve important tags from source output
|
|
176
|
+
const tags = [];
|
|
177
|
+
for (const tag of ordinal.tags ?? []) {
|
|
178
|
+
if (tag.startsWith("type:") || tag.startsWith("origin:") || tag.startsWith("name:")) {
|
|
179
|
+
tags.push(tag);
|
|
180
|
+
}
|
|
124
181
|
}
|
|
182
|
+
const sourceName = extractName(ordinal.customInstructions);
|
|
183
|
+
inputs.push({ outpoint, inputDescription: "Ordinal to transfer", unlockingScriptLength: 108 });
|
|
184
|
+
outputs.push({
|
|
185
|
+
lockingScript: new P2PKH().lock(recipientAddress).toHex(),
|
|
186
|
+
satoshis: 1,
|
|
187
|
+
outputDescription: "Ordinal transfer",
|
|
188
|
+
basket: ORDINALS_BASKET,
|
|
189
|
+
tags,
|
|
190
|
+
customInstructions: JSON.stringify({
|
|
191
|
+
protocolID: ONESAT_PROTOCOL,
|
|
192
|
+
keyID: outpoint,
|
|
193
|
+
...(sourceName && { name: sourceName }),
|
|
194
|
+
}),
|
|
195
|
+
});
|
|
125
196
|
}
|
|
126
197
|
return {
|
|
127
|
-
description: "Transfer ordinal"
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
lockingScript: new P2PKH().lock(recipientAddress).toHex(),
|
|
132
|
-
satoshis: 1,
|
|
133
|
-
outputDescription: "Ordinal transfer",
|
|
134
|
-
basket: ORDINALS_BASKET,
|
|
135
|
-
tags,
|
|
136
|
-
customInstructions: JSON.stringify({
|
|
137
|
-
protocolID: ONESAT_PROTOCOL,
|
|
138
|
-
keyID: outpoint,
|
|
139
|
-
}),
|
|
140
|
-
},
|
|
141
|
-
],
|
|
198
|
+
description: transfers.length === 1 ? "Transfer ordinal" : `Transfer ${transfers.length} ordinals`,
|
|
199
|
+
inputBEEF,
|
|
200
|
+
inputs,
|
|
201
|
+
outputs,
|
|
142
202
|
};
|
|
143
203
|
}
|
|
144
204
|
/**
|
|
@@ -146,25 +206,17 @@ export async function buildTransferOrdinal(ctx, request) {
|
|
|
146
206
|
* Does NOT execute - returns params for createAction.
|
|
147
207
|
*/
|
|
148
208
|
export async function buildListOrdinal(ctx, request) {
|
|
149
|
-
const {
|
|
209
|
+
const { ordinal, inputBEEF, price, payAddress } = request;
|
|
150
210
|
if (!payAddress)
|
|
151
211
|
return { error: "missing-pay-address" };
|
|
152
212
|
if (price <= 0)
|
|
153
213
|
return { error: "invalid-price" };
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
limit: 10000,
|
|
159
|
-
});
|
|
160
|
-
const sourceOutput = result.outputs.find((o) => o.outpoint === outpoint);
|
|
161
|
-
if (!sourceOutput) {
|
|
162
|
-
return { error: "ordinal-not-found" };
|
|
163
|
-
}
|
|
164
|
-
const typeTag = sourceOutput.tags?.find((t) => t.startsWith("type:"));
|
|
165
|
-
const originTag = sourceOutput.tags?.find((t) => t.startsWith("origin:"));
|
|
166
|
-
const nameTag = sourceOutput.tags?.find((t) => t.startsWith("name:"));
|
|
214
|
+
const outpoint = ordinal.outpoint;
|
|
215
|
+
const typeTag = ordinal.tags?.find((t) => t.startsWith("type:"));
|
|
216
|
+
const originTag = ordinal.tags?.find((t) => t.startsWith("origin:"));
|
|
217
|
+
const nameTag = ordinal.tags?.find((t) => t.startsWith("name:"));
|
|
167
218
|
const originOutpoint = originTag ? originTag.slice(7) : outpoint;
|
|
219
|
+
const sourceName = extractName(ordinal.customInstructions);
|
|
168
220
|
const cancelAddress = await deriveCancelAddressInternal(ctx, outpoint);
|
|
169
221
|
const lockingScript = buildOrdLockScript(cancelAddress, payAddress, price);
|
|
170
222
|
const tags = ["ordlock", `origin:${originOutpoint}`, `price:${price}`];
|
|
@@ -174,7 +226,8 @@ export async function buildListOrdinal(ctx, request) {
|
|
|
174
226
|
tags.push(nameTag);
|
|
175
227
|
return {
|
|
176
228
|
description: `List ordinal for ${price} sats`,
|
|
177
|
-
|
|
229
|
+
inputBEEF,
|
|
230
|
+
inputs: [{ outpoint, inputDescription: "Ordinal to list", unlockingScriptLength: 108 }],
|
|
178
231
|
outputs: [
|
|
179
232
|
{
|
|
180
233
|
lockingScript: lockingScript.toHex(),
|
|
@@ -185,6 +238,7 @@ export async function buildListOrdinal(ctx, request) {
|
|
|
185
238
|
customInstructions: JSON.stringify({
|
|
186
239
|
protocolID: ONESAT_PROTOCOL,
|
|
187
240
|
keyID: outpoint,
|
|
241
|
+
...(sourceName && { name: sourceName }),
|
|
188
242
|
}),
|
|
189
243
|
},
|
|
190
244
|
],
|
|
@@ -240,36 +294,69 @@ export const deriveCancelAddress = {
|
|
|
240
294
|
/**
|
|
241
295
|
* Transfer an ordinal to a new owner.
|
|
242
296
|
*/
|
|
243
|
-
export const
|
|
297
|
+
export const transferOrdinals = {
|
|
244
298
|
meta: {
|
|
245
|
-
name: "
|
|
246
|
-
description: "Transfer
|
|
299
|
+
name: "transferOrdinals",
|
|
300
|
+
description: "Transfer one or more ordinals to new owners",
|
|
247
301
|
category: "ordinals",
|
|
248
302
|
inputSchema: {
|
|
249
303
|
type: "object",
|
|
250
304
|
properties: {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
305
|
+
transfers: {
|
|
306
|
+
type: "array",
|
|
307
|
+
description: "Ordinals to transfer with destinations",
|
|
308
|
+
items: {
|
|
309
|
+
type: "object",
|
|
310
|
+
properties: {
|
|
311
|
+
ordinal: { type: "object", description: "WalletOutput from listOutputs" },
|
|
312
|
+
counterparty: { type: "string", description: "Recipient identity public key (hex)" },
|
|
313
|
+
address: { type: "string", description: "Recipient P2PKH address" },
|
|
314
|
+
},
|
|
315
|
+
required: ["ordinal"],
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
inputBEEF: { type: "array", description: "BEEF from listOutputs with include: 'entire transactions'" },
|
|
255
319
|
},
|
|
256
|
-
required: ["
|
|
320
|
+
required: ["transfers", "inputBEEF"],
|
|
257
321
|
},
|
|
258
322
|
},
|
|
259
323
|
async execute(ctx, input) {
|
|
260
324
|
try {
|
|
261
|
-
const params = await
|
|
325
|
+
const params = await buildTransferOrdinals(ctx, input);
|
|
262
326
|
if ("error" in params) {
|
|
263
327
|
return params;
|
|
264
328
|
}
|
|
265
|
-
const
|
|
329
|
+
const createResult = await ctx.wallet.createAction({
|
|
266
330
|
...params,
|
|
267
|
-
options: { randomizeOutputs: false },
|
|
331
|
+
options: { signAndProcess: false, randomizeOutputs: false },
|
|
332
|
+
});
|
|
333
|
+
if (!createResult.signableTransaction) {
|
|
334
|
+
return { error: "no-signable-transaction" };
|
|
335
|
+
}
|
|
336
|
+
const tx = Transaction.fromBEEF(createResult.signableTransaction.tx);
|
|
337
|
+
const spends = {};
|
|
338
|
+
for (let i = 0; i < input.transfers.length; i++) {
|
|
339
|
+
const { ordinal } = input.transfers[i];
|
|
340
|
+
if (!ordinal.customInstructions) {
|
|
341
|
+
return { error: `missing-custom-instructions-for-${ordinal.outpoint}` };
|
|
342
|
+
}
|
|
343
|
+
const { protocolID, keyID } = JSON.parse(ordinal.customInstructions);
|
|
344
|
+
const unlocking = await signP2PKHInput(ctx, tx, i, protocolID, keyID);
|
|
345
|
+
if (typeof unlocking !== "string")
|
|
346
|
+
return unlocking;
|
|
347
|
+
spends[i] = { unlockingScript: unlocking };
|
|
348
|
+
}
|
|
349
|
+
const signResult = await ctx.wallet.signAction({
|
|
350
|
+
reference: createResult.signableTransaction.reference,
|
|
351
|
+
spends,
|
|
268
352
|
});
|
|
269
|
-
if (
|
|
270
|
-
return { error:
|
|
353
|
+
if ("error" in signResult) {
|
|
354
|
+
return { error: String(signResult.error) };
|
|
271
355
|
}
|
|
272
|
-
return {
|
|
356
|
+
return {
|
|
357
|
+
txid: signResult.txid,
|
|
358
|
+
rawtx: signResult.tx ? Utils.toHex(signResult.tx) : undefined,
|
|
359
|
+
};
|
|
273
360
|
}
|
|
274
361
|
catch (error) {
|
|
275
362
|
return { error: error instanceof Error ? error.message : "unknown-error" };
|
|
@@ -287,11 +374,12 @@ export const listOrdinal = {
|
|
|
287
374
|
inputSchema: {
|
|
288
375
|
type: "object",
|
|
289
376
|
properties: {
|
|
290
|
-
|
|
377
|
+
ordinal: { type: "object", description: "WalletOutput from listOutputs" },
|
|
378
|
+
inputBEEF: { type: "array", description: "BEEF from listOutputs with include: 'entire transactions'" },
|
|
291
379
|
price: { type: "integer", description: "Price in satoshis" },
|
|
292
380
|
payAddress: { type: "string", description: "Address to receive payment on purchase" },
|
|
293
381
|
},
|
|
294
|
-
required: ["
|
|
382
|
+
required: ["ordinal", "inputBEEF", "price", "payAddress"],
|
|
295
383
|
},
|
|
296
384
|
},
|
|
297
385
|
async execute(ctx, input) {
|
|
@@ -300,14 +388,32 @@ export const listOrdinal = {
|
|
|
300
388
|
if ("error" in params) {
|
|
301
389
|
return params;
|
|
302
390
|
}
|
|
303
|
-
const
|
|
391
|
+
const createResult = await ctx.wallet.createAction({
|
|
304
392
|
...params,
|
|
305
|
-
options: { randomizeOutputs: false },
|
|
393
|
+
options: { signAndProcess: false, randomizeOutputs: false },
|
|
306
394
|
});
|
|
307
|
-
if (!
|
|
308
|
-
return { error: "no-
|
|
395
|
+
if (!createResult.signableTransaction) {
|
|
396
|
+
return { error: "no-signable-transaction" };
|
|
397
|
+
}
|
|
398
|
+
if (!input.ordinal.customInstructions) {
|
|
399
|
+
return { error: "missing-custom-instructions" };
|
|
309
400
|
}
|
|
310
|
-
|
|
401
|
+
const { protocolID, keyID } = JSON.parse(input.ordinal.customInstructions);
|
|
402
|
+
const tx = Transaction.fromBEEF(createResult.signableTransaction.tx);
|
|
403
|
+
const unlocking = await signP2PKHInput(ctx, tx, 0, protocolID, keyID);
|
|
404
|
+
if (typeof unlocking !== "string")
|
|
405
|
+
return unlocking;
|
|
406
|
+
const signResult = await ctx.wallet.signAction({
|
|
407
|
+
reference: createResult.signableTransaction.reference,
|
|
408
|
+
spends: { 0: { unlockingScript: unlocking } },
|
|
409
|
+
});
|
|
410
|
+
if ("error" in signResult) {
|
|
411
|
+
return { error: String(signResult.error) };
|
|
412
|
+
}
|
|
413
|
+
return {
|
|
414
|
+
txid: signResult.txid,
|
|
415
|
+
rawtx: signResult.tx ? Utils.toHex(signResult.tx) : undefined,
|
|
416
|
+
};
|
|
311
417
|
}
|
|
312
418
|
catch (error) {
|
|
313
419
|
return { error: error instanceof Error ? error.message : "unknown-error" };
|
|
@@ -325,29 +431,20 @@ export const cancelListing = {
|
|
|
325
431
|
inputSchema: {
|
|
326
432
|
type: "object",
|
|
327
433
|
properties: {
|
|
328
|
-
|
|
434
|
+
listing: { type: "object", description: "WalletOutput of the listing (must include lockingScript)" },
|
|
435
|
+
inputBEEF: { type: "array", description: "BEEF from listOutputs with include: 'entire transactions'" },
|
|
329
436
|
},
|
|
330
|
-
required: ["
|
|
437
|
+
required: ["listing", "inputBEEF"],
|
|
331
438
|
},
|
|
332
439
|
},
|
|
333
440
|
async execute(ctx, input) {
|
|
334
441
|
try {
|
|
335
|
-
const {
|
|
336
|
-
const
|
|
337
|
-
basket: ORDINALS_BASKET,
|
|
338
|
-
includeTags: true,
|
|
339
|
-
includeCustomInstructions: true,
|
|
340
|
-
include: "locking scripts",
|
|
341
|
-
limit: 10000,
|
|
342
|
-
});
|
|
343
|
-
const listing = result.outputs.find((o) => o.outpoint === outpoint);
|
|
344
|
-
if (!listing) {
|
|
345
|
-
return { error: "listing-not-found" };
|
|
346
|
-
}
|
|
442
|
+
const { listing, inputBEEF } = input;
|
|
443
|
+
const outpoint = listing.outpoint;
|
|
347
444
|
if (!listing.customInstructions) {
|
|
348
445
|
return { error: "missing-custom-instructions" };
|
|
349
446
|
}
|
|
350
|
-
const { protocolID, keyID } = JSON.parse(listing.customInstructions);
|
|
447
|
+
const { protocolID, keyID, name: listingName } = JSON.parse(listing.customInstructions);
|
|
351
448
|
const typeTag = listing.tags?.find((t) => t.startsWith("type:"));
|
|
352
449
|
const originTag = listing.tags?.find((t) => t.startsWith("origin:"));
|
|
353
450
|
const nameTag = listing.tags?.find((t) => t.startsWith("name:"));
|
|
@@ -361,6 +458,7 @@ export const cancelListing = {
|
|
|
361
458
|
tags.push(nameTag);
|
|
362
459
|
const createResult = await ctx.wallet.createAction({
|
|
363
460
|
description: "Cancel ordinal listing",
|
|
461
|
+
inputBEEF,
|
|
364
462
|
inputs: [
|
|
365
463
|
{
|
|
366
464
|
outpoint,
|
|
@@ -375,7 +473,7 @@ export const cancelListing = {
|
|
|
375
473
|
outputDescription: "Cancelled listing",
|
|
376
474
|
basket: ORDINALS_BASKET,
|
|
377
475
|
tags,
|
|
378
|
-
customInstructions: JSON.stringify({ protocolID, keyID }),
|
|
476
|
+
customInstructions: JSON.stringify({ protocolID, keyID, ...(listingName && { name: listingName }) }),
|
|
379
477
|
},
|
|
380
478
|
],
|
|
381
479
|
options: { signAndProcess: false, randomizeOutputs: false },
|
|
@@ -388,7 +486,10 @@ export const cancelListing = {
|
|
|
388
486
|
}
|
|
389
487
|
const tx = Transaction.fromBEEF(createResult.signableTransaction.tx);
|
|
390
488
|
const txInput = tx.inputs[0];
|
|
391
|
-
const lockingScript =
|
|
489
|
+
const lockingScript = txInput.sourceTransaction?.outputs[txInput.sourceOutputIndex]?.lockingScript;
|
|
490
|
+
if (!lockingScript) {
|
|
491
|
+
return { error: "missing-locking-script" };
|
|
492
|
+
}
|
|
392
493
|
const sourceTXID = txInput.sourceTXID ?? txInput.sourceTransaction?.id("hex");
|
|
393
494
|
if (!sourceTXID) {
|
|
394
495
|
return { error: "missing-source-txid" };
|
|
@@ -523,6 +624,7 @@ export const purchaseOrdinal = {
|
|
|
523
624
|
customInstructions: JSON.stringify({
|
|
524
625
|
protocolID: ONESAT_PROTOCOL,
|
|
525
626
|
keyID: outpoint,
|
|
627
|
+
...(name && { name: name.slice(0, 64) }),
|
|
526
628
|
}),
|
|
527
629
|
});
|
|
528
630
|
const payoutReader = new Utils.Reader(ordLockData.payout);
|
|
@@ -592,7 +694,7 @@ export const purchaseOrdinal = {
|
|
|
592
694
|
export const ordinalsSkills = [
|
|
593
695
|
listOrdinals,
|
|
594
696
|
deriveCancelAddress,
|
|
595
|
-
|
|
697
|
+
transferOrdinals,
|
|
596
698
|
listOrdinal,
|
|
597
699
|
cancelListing,
|
|
598
700
|
purchaseOrdinal,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { OneSatContext, Skill } from "../skills/types";
|
|
7
7
|
import type { IndexedOutput } from "../../services/types";
|
|
8
|
-
import type { SweepBsvRequest, SweepBsvResponse, SweepInput, SweepOrdinalsRequest, SweepOrdinalsResponse } from "./types";
|
|
8
|
+
import type { SweepBsvRequest, SweepBsvResponse, SweepInput, SweepOrdinalsRequest, SweepOrdinalsResponse, SweepBsv21Request, SweepBsv21Response } from "./types";
|
|
9
9
|
export * from "./types";
|
|
10
10
|
/**
|
|
11
11
|
* Prepare sweep inputs from IndexedOutput objects by fetching locking scripts.
|
|
@@ -27,4 +27,11 @@ export declare const sweepBsv: Skill<SweepBsvRequest, SweepBsvResponse>;
|
|
|
27
27
|
* transferred to a derived address using the wallet's key derivation.
|
|
28
28
|
*/
|
|
29
29
|
export declare const sweepOrdinals: Skill<SweepOrdinalsRequest, SweepOrdinalsResponse>;
|
|
30
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Sweep BSV-21 tokens from external inputs into the destination wallet.
|
|
32
|
+
*
|
|
33
|
+
* Consolidates all token inputs into a single output. All inputs must be
|
|
34
|
+
* for the same tokenId. Creates a fee output to the overlay fund address.
|
|
35
|
+
*/
|
|
36
|
+
export declare const sweepBsv21: Skill<SweepBsv21Request, SweepBsv21Response>;
|
|
37
|
+
export declare const sweepSkills: (Skill<SweepBsvRequest, SweepBsvResponse> | Skill<SweepOrdinalsRequest, SweepOrdinalsResponse> | Skill<SweepBsv21Request, SweepBsv21Response>)[];
|
package/dist/api/sweep/index.js
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* Functions for sweeping assets from external wallets into a BRC-100 wallet.
|
|
5
5
|
*/
|
|
6
6
|
import { P2PKH, PrivateKey, PublicKey, Transaction, } from "@bsv/sdk";
|
|
7
|
-
import {
|
|
7
|
+
import { BSV21 } from "@bopen-io/templates";
|
|
8
|
+
import { ONESAT_PROTOCOL, BSV21_PROTOCOL, BSV21_FEE_SATS, BSV21_BASKET } from "../constants";
|
|
9
|
+
import { deriveFundAddress } from "../../indexers";
|
|
8
10
|
export * from "./types";
|
|
9
11
|
/**
|
|
10
12
|
* Prepare sweep inputs from IndexedOutput objects by fetching locking scripts.
|
|
@@ -308,8 +310,6 @@ export const sweepOrdinals = {
|
|
|
308
310
|
tags.push(`type:${input.contentType}`);
|
|
309
311
|
if (input.origin)
|
|
310
312
|
tags.push(`origin:${input.origin}`);
|
|
311
|
-
if (input.name)
|
|
312
|
-
tags.push(`name:${input.name}`);
|
|
313
313
|
outputs.push({
|
|
314
314
|
lockingScript: lockingScript.toHex(),
|
|
315
315
|
satoshis: 1,
|
|
@@ -319,6 +319,7 @@ export const sweepOrdinals = {
|
|
|
319
319
|
customInstructions: JSON.stringify({
|
|
320
320
|
protocolID: ONESAT_PROTOCOL,
|
|
321
321
|
keyID: input.outpoint,
|
|
322
|
+
...(input.name && { name: input.name.slice(0, 64) }),
|
|
322
323
|
}),
|
|
323
324
|
});
|
|
324
325
|
}
|
|
@@ -384,5 +385,177 @@ export const sweepOrdinals = {
|
|
|
384
385
|
}
|
|
385
386
|
},
|
|
386
387
|
};
|
|
388
|
+
/**
|
|
389
|
+
* Sweep BSV-21 tokens from external inputs into the destination wallet.
|
|
390
|
+
*
|
|
391
|
+
* Consolidates all token inputs into a single output. All inputs must be
|
|
392
|
+
* for the same tokenId. Creates a fee output to the overlay fund address.
|
|
393
|
+
*/
|
|
394
|
+
export const sweepBsv21 = {
|
|
395
|
+
meta: {
|
|
396
|
+
name: "sweepBsv21",
|
|
397
|
+
description: "Sweep BSV-21 tokens from external wallet (via WIF) into the connected wallet",
|
|
398
|
+
category: "sweep",
|
|
399
|
+
requiresServices: true,
|
|
400
|
+
inputSchema: {
|
|
401
|
+
type: "object",
|
|
402
|
+
properties: {
|
|
403
|
+
inputs: {
|
|
404
|
+
type: "array",
|
|
405
|
+
description: "Token UTXOs to sweep (must all be same tokenId)",
|
|
406
|
+
items: {
|
|
407
|
+
type: "object",
|
|
408
|
+
properties: {
|
|
409
|
+
outpoint: { type: "string", description: "Outpoint (txid_vout)" },
|
|
410
|
+
satoshis: { type: "integer", description: "Satoshis (should be 1)" },
|
|
411
|
+
lockingScript: { type: "string", description: "Locking script hex" },
|
|
412
|
+
tokenId: { type: "string", description: "Token ID (txid_vout format)" },
|
|
413
|
+
amount: { type: "string", description: "Token amount as string" },
|
|
414
|
+
},
|
|
415
|
+
required: ["outpoint", "satoshis", "lockingScript", "tokenId", "amount"],
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
wif: {
|
|
419
|
+
type: "string",
|
|
420
|
+
description: "WIF private key controlling the inputs",
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
required: ["inputs", "wif"],
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
async execute(ctx, request) {
|
|
427
|
+
if (!ctx.services) {
|
|
428
|
+
return { error: "services-required" };
|
|
429
|
+
}
|
|
430
|
+
try {
|
|
431
|
+
const { inputs, wif } = request;
|
|
432
|
+
if (!inputs || inputs.length === 0) {
|
|
433
|
+
return { error: "no-inputs" };
|
|
434
|
+
}
|
|
435
|
+
// Validate all inputs have the same tokenId
|
|
436
|
+
const tokenId = inputs[0].tokenId;
|
|
437
|
+
if (!inputs.every((i) => i.tokenId === tokenId)) {
|
|
438
|
+
return { error: "mixed-token-ids" };
|
|
439
|
+
}
|
|
440
|
+
// Parse WIF
|
|
441
|
+
const privateKey = PrivateKey.fromWif(wif);
|
|
442
|
+
// Sum all input amounts
|
|
443
|
+
const totalAmount = inputs.reduce((sum, i) => sum + BigInt(i.amount), 0n);
|
|
444
|
+
if (totalAmount <= 0n) {
|
|
445
|
+
return { error: "no-token-amount" };
|
|
446
|
+
}
|
|
447
|
+
// Fetch BEEF for all input transactions and merge them
|
|
448
|
+
const txids = [...new Set(inputs.map((i) => i.outpoint.split("_")[0]))];
|
|
449
|
+
console.log(`[sweepBsv21] Fetching BEEF for ${txids.length} transactions`);
|
|
450
|
+
const firstBeef = await ctx.services.getBeefForTxid(txids[0]);
|
|
451
|
+
for (let i = 1; i < txids.length; i++) {
|
|
452
|
+
const additionalBeef = await ctx.services.getBeefForTxid(txids[i]);
|
|
453
|
+
firstBeef.mergeBeef(additionalBeef);
|
|
454
|
+
}
|
|
455
|
+
console.log(`[sweepBsv21] Merged BEEF valid=${firstBeef.isValid()}, txs=${firstBeef.txs.length}`);
|
|
456
|
+
// Build input descriptors
|
|
457
|
+
const inputDescriptors = inputs.map((input) => {
|
|
458
|
+
const [txid, voutStr] = input.outpoint.split("_");
|
|
459
|
+
return {
|
|
460
|
+
outpoint: `${txid}.${voutStr}`,
|
|
461
|
+
inputDescription: `Token input ${input.outpoint}`,
|
|
462
|
+
unlockingScriptLength: 108,
|
|
463
|
+
sequenceNumber: 0xffffffff,
|
|
464
|
+
};
|
|
465
|
+
});
|
|
466
|
+
// Build outputs
|
|
467
|
+
const outputs = [];
|
|
468
|
+
// 1. Token output (1 sat) - derive key for this token
|
|
469
|
+
const keyID = `${tokenId}-${Date.now()}`;
|
|
470
|
+
const pubKeyResult = await ctx.wallet.getPublicKey({
|
|
471
|
+
protocolID: BSV21_PROTOCOL,
|
|
472
|
+
keyID,
|
|
473
|
+
forSelf: true,
|
|
474
|
+
});
|
|
475
|
+
if (!pubKeyResult.publicKey) {
|
|
476
|
+
return { error: "failed-to-derive-key" };
|
|
477
|
+
}
|
|
478
|
+
const derivedAddress = PublicKey.fromString(pubKeyResult.publicKey).toAddress();
|
|
479
|
+
const p2pkh = new P2PKH();
|
|
480
|
+
const destinationLockingScript = p2pkh.lock(derivedAddress);
|
|
481
|
+
const transferScript = BSV21.transfer(tokenId, totalAmount).lock(destinationLockingScript);
|
|
482
|
+
outputs.push({
|
|
483
|
+
lockingScript: transferScript.toHex(),
|
|
484
|
+
satoshis: 1,
|
|
485
|
+
outputDescription: `Sweep ${totalAmount} tokens`,
|
|
486
|
+
basket: BSV21_BASKET,
|
|
487
|
+
tags: [`id:${tokenId}`, `amt:${totalAmount}`],
|
|
488
|
+
customInstructions: JSON.stringify({
|
|
489
|
+
protocolID: BSV21_PROTOCOL,
|
|
490
|
+
keyID,
|
|
491
|
+
}),
|
|
492
|
+
});
|
|
493
|
+
// 2. Fee output (1000 sats) to overlay fund address
|
|
494
|
+
const fundAddress = deriveFundAddress(tokenId);
|
|
495
|
+
outputs.push({
|
|
496
|
+
lockingScript: p2pkh.lock(fundAddress).toHex(),
|
|
497
|
+
satoshis: BSV21_FEE_SATS,
|
|
498
|
+
outputDescription: "Overlay processing fee",
|
|
499
|
+
});
|
|
500
|
+
const beefData = firstBeef.toBinary();
|
|
501
|
+
// Create action to get signable transaction
|
|
502
|
+
const createResult = await ctx.wallet.createAction({
|
|
503
|
+
description: `Sweep ${inputs.length} token UTXO${inputs.length !== 1 ? "s" : ""}`,
|
|
504
|
+
inputBEEF: beefData,
|
|
505
|
+
inputs: inputDescriptors,
|
|
506
|
+
outputs,
|
|
507
|
+
options: { signAndProcess: false, randomizeOutputs: false },
|
|
508
|
+
});
|
|
509
|
+
if ("error" in createResult && createResult.error) {
|
|
510
|
+
return { error: String(createResult.error) };
|
|
511
|
+
}
|
|
512
|
+
if (!createResult.signableTransaction) {
|
|
513
|
+
return { error: "no-signable-transaction" };
|
|
514
|
+
}
|
|
515
|
+
// Sign each input with our external key
|
|
516
|
+
const tx = Transaction.fromBEEF(createResult.signableTransaction.tx);
|
|
517
|
+
// Build a set of outpoints we control
|
|
518
|
+
const ourOutpoints = new Set(inputs.map((input) => {
|
|
519
|
+
const [txid, vout] = input.outpoint.split("_");
|
|
520
|
+
return `${txid}.${vout}`;
|
|
521
|
+
}));
|
|
522
|
+
// Set up P2PKH unlocker on each input we control
|
|
523
|
+
for (let i = 0; i < tx.inputs.length; i++) {
|
|
524
|
+
const txInput = tx.inputs[i];
|
|
525
|
+
const inputOutpoint = `${txInput.sourceTXID}.${txInput.sourceOutputIndex}`;
|
|
526
|
+
if (ourOutpoints.has(inputOutpoint)) {
|
|
527
|
+
txInput.unlockingScriptTemplate = p2pkh.unlock(privateKey, "all", true);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
await tx.sign();
|
|
531
|
+
// Extract unlocking scripts for signAction
|
|
532
|
+
const spends = {};
|
|
533
|
+
for (let i = 0; i < tx.inputs.length; i++) {
|
|
534
|
+
const txInput = tx.inputs[i];
|
|
535
|
+
const inputOutpoint = `${txInput.sourceTXID}.${txInput.sourceOutputIndex}`;
|
|
536
|
+
if (ourOutpoints.has(inputOutpoint)) {
|
|
537
|
+
spends[i] = { unlockingScript: txInput.unlockingScript?.toHex() ?? "" };
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
// Complete the action with our signatures
|
|
541
|
+
const signResult = await ctx.wallet.signAction({
|
|
542
|
+
reference: createResult.signableTransaction.reference,
|
|
543
|
+
spends,
|
|
544
|
+
});
|
|
545
|
+
if ("error" in signResult) {
|
|
546
|
+
return { error: String(signResult.error) };
|
|
547
|
+
}
|
|
548
|
+
return {
|
|
549
|
+
txid: signResult.txid,
|
|
550
|
+
beef: signResult.tx ? Array.from(signResult.tx) : undefined,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
catch (error) {
|
|
554
|
+
return {
|
|
555
|
+
error: error instanceof Error ? error.message : "unknown-error",
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
};
|
|
387
560
|
// Export skills array for registry
|
|
388
|
-
export const sweepSkills = [sweepBsv, sweepOrdinals];
|
|
561
|
+
export const sweepSkills = [sweepBsv, sweepOrdinals, sweepBsv21];
|
|
@@ -53,3 +53,26 @@ export interface SweepOrdinalsResponse {
|
|
|
53
53
|
/** Error message if failed */
|
|
54
54
|
error?: string;
|
|
55
55
|
}
|
|
56
|
+
/** Input for BSV-21 token sweep operations */
|
|
57
|
+
export interface SweepBsv21Input extends SweepInput {
|
|
58
|
+
/** Token ID (txid_vout format) */
|
|
59
|
+
tokenId: string;
|
|
60
|
+
/** Token amount as string (bigint serialization) */
|
|
61
|
+
amount: string;
|
|
62
|
+
}
|
|
63
|
+
/** Request to sweep BSV-21 tokens */
|
|
64
|
+
export interface SweepBsv21Request {
|
|
65
|
+
/** Token UTXOs to sweep (must all be same tokenId) */
|
|
66
|
+
inputs: SweepBsv21Input[];
|
|
67
|
+
/** WIF private key controlling the inputs */
|
|
68
|
+
wif: string;
|
|
69
|
+
}
|
|
70
|
+
/** Response from BSV-21 token sweep operation */
|
|
71
|
+
export interface SweepBsv21Response {
|
|
72
|
+
/** Transaction ID if successful */
|
|
73
|
+
txid?: string;
|
|
74
|
+
/** BEEF (transaction with validity proof) */
|
|
75
|
+
beef?: number[];
|
|
76
|
+
/** Error message if failed */
|
|
77
|
+
error?: string;
|
|
78
|
+
}
|
package/dist/api/tokens/index.js
CHANGED
|
@@ -5,11 +5,8 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { BigNumber, LockingScript, OP, P2PKH, PublicKey, Transaction, TransactionSignature, UnlockingScript, Utils, } from "@bsv/sdk";
|
|
7
7
|
import { BSV21, OrdLock } from "@bopen-io/templates";
|
|
8
|
-
import { BSV21_BASKET } from "../constants";
|
|
9
|
-
|
|
10
|
-
// Constants
|
|
11
|
-
// ============================================================================
|
|
12
|
-
const BSV21_PROTOCOL = [1, "bsv21"];
|
|
8
|
+
import { BSV21_BASKET, BSV21_PROTOCOL, BSV21_FEE_SATS } from "../constants";
|
|
9
|
+
import { deriveFundAddress } from "../../indexers";
|
|
13
10
|
// ============================================================================
|
|
14
11
|
// Internal helpers
|
|
15
12
|
// ============================================================================
|
|
@@ -256,6 +253,13 @@ export const sendBsv21 = {
|
|
|
256
253
|
satoshis: 1,
|
|
257
254
|
outputDescription: `Send ${amount} tokens`,
|
|
258
255
|
});
|
|
256
|
+
// Fee output to overlay fund address
|
|
257
|
+
const fundAddress = deriveFundAddress(tokenId);
|
|
258
|
+
outputs.push({
|
|
259
|
+
lockingScript: p2pkh.lock(fundAddress).toHex(),
|
|
260
|
+
satoshis: BSV21_FEE_SATS,
|
|
261
|
+
outputDescription: "Overlay processing fee",
|
|
262
|
+
});
|
|
259
263
|
const change = totalIn - amount;
|
|
260
264
|
if (change > 0n) {
|
|
261
265
|
const changeKeyID = `${tokenId}-${Date.now()}`;
|
package/dist/sync/SyncManager.js
CHANGED
|
@@ -352,17 +352,18 @@ export class SyncProcessor {
|
|
|
352
352
|
// These outputs need custom unlock scripts when spent
|
|
353
353
|
const basket = txo.basket || "custom";
|
|
354
354
|
const tags = this.collectTags(txo);
|
|
355
|
+
const nameTag = tags.find((t) => t.startsWith("name:"));
|
|
355
356
|
return {
|
|
356
357
|
outputIndex: vout,
|
|
357
358
|
protocol: "basket insertion",
|
|
358
359
|
insertionRemittance: {
|
|
359
360
|
basket,
|
|
360
361
|
tags,
|
|
361
|
-
// Store derivation info for future signing
|
|
362
362
|
customInstructions: JSON.stringify({
|
|
363
363
|
derivationPrefix: derivation.derivationPrefix,
|
|
364
364
|
derivationSuffix: derivation.derivationSuffix,
|
|
365
365
|
senderIdentityKey: derivation.senderIdentityKey,
|
|
366
|
+
...(nameTag && { name: nameTag.slice(5).slice(0, 64) }),
|
|
366
367
|
}),
|
|
367
368
|
},
|
|
368
369
|
};
|