@bopen-io/color-permission-module 0.1.0 → 0.1.2
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/README.md +33 -3
- package/dist/index.d.mts +42 -8
- package/dist/index.mjs +189 -38
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -57,7 +57,36 @@ the manager's `getVersion` string.
|
|
|
57
57
|
`p color plan` labels, built with `ColorPlan.toLabels`, and
|
|
58
58
|
`options.randomizeOutputs: false` (every plan requires it: the plan's output
|
|
59
59
|
indices and the approved facts name the requested outputs). The module selects
|
|
60
|
-
admitted token inputs and adds token change in `createAction
|
|
60
|
+
admitted token inputs and adds token change in `createAction`, and supplies
|
|
61
|
+
the source transactions of the inputs it selects in `inputBEEF`, merged with
|
|
62
|
+
any `inputBEEF` the caller gave. Every output the module files (token
|
|
63
|
+
change, owned outputs, receipts, deploys and handoff splits) carries the tag
|
|
64
|
+
`p color <hex>`: the lowercase hex of the SHA-256 of the UTF-8 bytes of its
|
|
65
|
+
custody's derivation suffix. The tag is 72 characters
|
|
66
|
+
whatever the suffix, and storage keeps nothing of the suffix itself. The module reads the
|
|
67
|
+
transactions of the inputs it selects with one `listOutputs` per asset
|
|
68
|
+
basket, by their tags (`tagQueryMode: 'any'`, `limit` the number of
|
|
69
|
+
selected inputs), checks that the wallet returned exactly the selected
|
|
70
|
+
outputs, and keeps only each selected transaction with its proof or
|
|
71
|
+
unproven ancestors. An output without its suffix tag is not selected, and an app-named
|
|
72
|
+
input without it is refused, so outputs filed without the tag are not spent
|
|
73
|
+
through the module. Suffix tags are
|
|
74
|
+
left out of the `p color assets` view. A selected output whose transaction
|
|
75
|
+
the wallet does not return in full fails the call, naming the outpoint. The
|
|
76
|
+
module stages its record of each input it signs for (outpoint, script,
|
|
77
|
+
satoshis, asset and decrypted custody) when `createAction` funds the action,
|
|
78
|
+
and when the wallet's `createAction` comes back it binds the records of the
|
|
79
|
+
inputs of the returned unsigned transaction to that action's reference.
|
|
80
|
+
`signAction` signs only the inputs bound to its reference, without listing
|
|
81
|
+
the wallet's outputs; a wallet may hide an action's reserved inputs from its
|
|
82
|
+
listings (wallet-toolbox's `auto` action-batch mode does). Which outputs are
|
|
83
|
+
available is storage's to decide; the module also skips outputs staged or
|
|
84
|
+
being selected by its own calls. A reference's records are dropped when
|
|
85
|
+
`signAction` signs or refuses, and on `dispose()`. Two cases leave records
|
|
86
|
+
until `dispose()`: an action aborted before `signAction` leaves its bound
|
|
87
|
+
records unused, and a wallet `createAction` that fails after the module
|
|
88
|
+
funded it leaves its inputs staged, so the module does not select them again
|
|
89
|
+
until `dispose()`. In
|
|
61
90
|
`signAction` it checks the funded transaction against the plan, asks the user
|
|
62
91
|
to approve it (`color_send`: every asset leg, satoshi totals and fee, the
|
|
63
92
|
signatures it releases), signs the token inputs with any `sighashTypes`
|
|
@@ -119,8 +148,9 @@ The module fails explicitly, rather than falling back, on:
|
|
|
119
148
|
|
|
120
149
|
Explicit inputs are supported: this wallet's `p color` outputs are signed by the
|
|
121
150
|
module, and foreign token inputs count only once their asset's overlay admits
|
|
122
|
-
them.
|
|
123
|
-
|
|
151
|
+
them. The module supplies `inputBEEF` for the inputs it selects; the source
|
|
152
|
+
transactions of an input the app names are the app's to supply in `inputBEEF`,
|
|
153
|
+
as wallet-toolbox requires for explicit inputs. `inputPositions` and token `changePositions` are honored; change
|
|
124
154
|
positions follow the requested outputs.
|
|
125
155
|
|
|
126
156
|
The module does not validate BRC-176 lineage: proof transport is not part of
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AtomicBEEF, BasketInsertion, Beef, CreateActionArgs, CreateActionInput, CreateActionOutput, CreateSignatureArgs, InternalizeActionArgs, ListOutputsArgs, ListOutputsResult, LockingScript, LookupAnswer, STEAK, SignActionArgs, SignActionResult, Transaction, WalletCounterparty, WalletInterface, WalletOutput, WalletProtocol } from "@bsv/sdk";
|
|
1
|
+
import { AtomicBEEF, BEEF, BasketInsertion, Beef, CreateActionArgs, CreateActionInput, CreateActionOutput, CreateActionResult, CreateSignatureArgs, InternalizeActionArgs, ListOutputsArgs, ListOutputsResult, LockingScript, LookupAnswer, STEAK, SignActionArgs, SignActionResult, Transaction, WalletCounterparty, WalletInterface, WalletOutput, WalletProtocol } from "@bsv/sdk";
|
|
2
2
|
import { PermissionsModule, PermissionsModuleAction, PermissionsModuleNext, PermissionsModuleRequest } from "@bsv/wallet-toolbox-client";
|
|
3
3
|
//#region src/strictJson.d.ts
|
|
4
4
|
type JsonValue = null | boolean | number | string | JsonValue[] | JsonObject;
|
|
@@ -188,12 +188,16 @@ interface Candidate extends SourceOutput {
|
|
|
188
188
|
token: TokenOutput;
|
|
189
189
|
amount: bigint;
|
|
190
190
|
customInstructions: string;
|
|
191
|
+
/** The tags the wallet listed for the output. */
|
|
192
|
+
tags: readonly string[];
|
|
191
193
|
}
|
|
192
194
|
interface ManagedInput extends SourceOutput {
|
|
193
195
|
assetId: string;
|
|
194
196
|
role: TokenRole;
|
|
195
197
|
amount: bigint;
|
|
196
198
|
custody: Custody;
|
|
199
|
+
/** The source transaction as BEEF, when the module made it (a handoff split). */
|
|
200
|
+
sourceBEEF?: BEEF;
|
|
197
201
|
}
|
|
198
202
|
type InventoryState = 'available' | 'reserved';
|
|
199
203
|
/** One asset's token units and authority outputs on one side of a transaction. */
|
|
@@ -347,10 +351,14 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
347
351
|
readonly capabilities: Readonly<Record<string, string>>;
|
|
348
352
|
/** Outputs a send in progress has selected, so a concurrent send does not select them too. */
|
|
349
353
|
protected readonly selected: Set<string>;
|
|
354
|
+
/** The managed inputs of funded actions whose createAction has not come back, by outpoint. */
|
|
355
|
+
protected readonly staged: Map<string, ManagedInput>;
|
|
356
|
+
/** The managed inputs the module signs for, by the action reference they were bound to. */
|
|
357
|
+
protected readonly bound: Map<string, ManagedInput[]>;
|
|
350
358
|
protected readonly supportedAssets: Map<string, Promise<void>>;
|
|
351
359
|
protected identityKey?: Promise<string>;
|
|
352
360
|
constructor(options: ColorPermissionModuleOptions);
|
|
353
|
-
/** Clears in-memory selections and asset checks. */
|
|
361
|
+
/** Clears in-memory selections, staged and bound input records and asset checks. */
|
|
354
362
|
dispose(): void;
|
|
355
363
|
/** Protocol- and basket-routed calls: key and signature operations and the read views. */
|
|
356
364
|
handleRequest(req: PermissionsModuleRequest, next: PermissionsModuleNext): Promise<unknown>;
|
|
@@ -369,8 +377,11 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
369
377
|
* before it can be spent.
|
|
370
378
|
*/
|
|
371
379
|
protected fileDeploy(result: SignActionResult, action: PermissionsModuleAction, originator: string): Promise<void>;
|
|
372
|
-
/**
|
|
373
|
-
|
|
380
|
+
/**
|
|
381
|
+
* Verifies every `p color` receipt and tags it with its derivation suffix; the caller must
|
|
382
|
+
* already have submitted the transaction.
|
|
383
|
+
*/
|
|
384
|
+
protected internalizeAction(args: InternalizeActionArgs, originator: string): Promise<InternalizeActionArgs>;
|
|
374
385
|
/**
|
|
375
386
|
* Checks a received transaction: every input unlocks its source and every asset it involves is
|
|
376
387
|
* supported. Token amounts are not re-evaluated: the received output must be admitted and
|
|
@@ -380,9 +391,16 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
380
391
|
/** Checks each distinct asset in order, so the first unsupported one is the one reported. */
|
|
381
392
|
protected assertSupportedAssets(assetIds: Iterable<string>): Promise<void>;
|
|
382
393
|
/** One receipt: its custody, its output's asset, owner and basket, and overlay admission. */
|
|
383
|
-
protected verifyReceipt(tx: Transaction, outputIndex: number, remittance: BasketInsertion, originator: string): Promise<
|
|
394
|
+
protected verifyReceipt(tx: Transaction, outputIndex: number, remittance: BasketInsertion, originator: string): Promise<Custody>;
|
|
384
395
|
/** Checks a planned token action and adds its selected token inputs and per-asset change. */
|
|
385
396
|
protected createAction(args: CreateActionArgs, originator: string): Promise<CreateActionArgs>;
|
|
397
|
+
/** Stages a funded action's managed inputs until its createAction comes back. */
|
|
398
|
+
protected stage(inputs: readonly ManagedInput[]): void;
|
|
399
|
+
/**
|
|
400
|
+
* Moves the staged inputs the returned transaction spends to its action reference. A result
|
|
401
|
+
* without a signable transaction binds nothing; its named inputs are only unstaged.
|
|
402
|
+
*/
|
|
403
|
+
protected bindAction(result: CreateActionResult): void;
|
|
386
404
|
protected reserve(reserved: Set<string>, inputs: ReadonlyArray<{
|
|
387
405
|
outpoint: string;
|
|
388
406
|
}>): void;
|
|
@@ -461,6 +479,17 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
461
479
|
* does not report, as BTMS does when it selects, and returns the outputs it then admits.
|
|
462
480
|
*/
|
|
463
481
|
protected resubmitHeld(assetId: string, role: TokenRole, outputs: readonly SourceOutput[], originator: string): Promise<Set<string>>;
|
|
482
|
+
/**
|
|
483
|
+
* `callerBEEF` with the source transactions of the module's selected inputs merged in, each with
|
|
484
|
+
* its proof or unproven ancestors and nothing else. Inputs the caller named keep the BEEF the
|
|
485
|
+
* caller gave; the module adds none for them.
|
|
486
|
+
*/
|
|
487
|
+
protected inputBeef(callerBEEF: BEEF | undefined, inputs: readonly ManagedInput[], originator: string): Promise<BEEF | undefined>;
|
|
488
|
+
/**
|
|
489
|
+
* The selected inputs of one basket with their entire transactions, in one listing by their
|
|
490
|
+
* suffix tags. The listing must return exactly the selected outputs.
|
|
491
|
+
*/
|
|
492
|
+
protected sourceTransactions(basket: string, selected: readonly ManagedInput[], originator: string): Promise<Beef>;
|
|
464
493
|
/** One basket's token outputs of the asset, read from their locking scripts, excluding selected ones. */
|
|
465
494
|
protected candidates(assetId: string, role: TokenRole, originator: string): Promise<Candidate[]>;
|
|
466
495
|
/** A chosen candidate with its custody, or undefined when its custody record is unreadable. */
|
|
@@ -488,8 +517,13 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
488
517
|
* `p color` outputs.
|
|
489
518
|
*/
|
|
490
519
|
protected signAction(args: SignActionArgs, action: PermissionsModuleAction | undefined, originator: string): Promise<SignActionArgs>;
|
|
491
|
-
/**
|
|
492
|
-
protected
|
|
520
|
+
/** Rechecks, approves and signs the module's inputs of a funded action. */
|
|
521
|
+
protected signBound(args: SignActionArgs, funded: {
|
|
522
|
+
plan: ColorPlan;
|
|
523
|
+
createArgs: CreateActionArgs;
|
|
524
|
+
tx: Transaction;
|
|
525
|
+
tokens: TransactionTokens;
|
|
526
|
+
}, managed: ManagedInput[], originator: string): Promise<SignActionArgs>;
|
|
493
527
|
/**
|
|
494
528
|
* Submits a signed token transaction to the topic of every asset among its inputs and outputs,
|
|
495
529
|
* and a deploy to the discovery topic.
|
|
@@ -529,7 +563,7 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
529
563
|
/** The queried outputs the overlay reports admitted and unspent, matched to their source bytes. */
|
|
530
564
|
protected spendable(assetId: string, outputs: readonly SourceOutput[]): Promise<Set<string>>;
|
|
531
565
|
/** All spendable outputs of one basket, with locking scripts and custom instructions. */
|
|
532
|
-
protected listBasket(basket: string, originator: string, filter: Pick<ListOutputsArgs, 'tags' | 'tagQueryMode'>): Promise<WalletOutput[]>;
|
|
566
|
+
protected listBasket(basket: string, originator: string, filter: Pick<ListOutputsArgs, 'tags' | 'tagQueryMode' | 'includeTags'>): Promise<WalletOutput[]>;
|
|
533
567
|
protected sourceOutput(output: WalletOutput): SourceOutput;
|
|
534
568
|
protected preimage(tx: Transaction, inputIndex: number, source: SourceOutput, scope: number): number[];
|
|
535
569
|
/** Unlocking scripts for this module's inputs of `template`, keyed by input index. */
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Beef, LockingScript, PublicKey, Signature, Spend, Transaction, TransactionSignature, UnlockingScript } from "@bsv/sdk";
|
|
2
|
-
import { hash160, hash256 } from "@bsv/sdk/primitives/Hash";
|
|
2
|
+
import { hash160, hash256, sha256 } from "@bsv/sdk/primitives/Hash";
|
|
3
3
|
import Random from "@bsv/sdk/primitives/Random";
|
|
4
4
|
import { toArray, toBase64, toHex } from "@bsv/sdk/primitives/utils";
|
|
5
5
|
import { Bsv21Token, OneColor, StrictCborLink, isTokenShaped, tryDecodeStrictCbor } from "@bsv/templates";
|
|
@@ -140,6 +140,19 @@ function parseBalanceSelector(basket) {
|
|
|
140
140
|
}
|
|
141
141
|
/** The shared deployment discovery topic. */
|
|
142
142
|
const DISCOVERY_TOPIC = "tm_color";
|
|
143
|
+
const SUFFIX_TAG = /^p color [0-9a-f]{64}$/;
|
|
144
|
+
/**
|
|
145
|
+
* The tag filed on a managed output: `p color ` and the lowercase hex of the SHA-256 of the UTF-8
|
|
146
|
+
* bytes of its custody's derivation suffix. It has a fixed length whatever the suffix, and storage
|
|
147
|
+
* keeps nothing of the suffix itself.
|
|
148
|
+
*/
|
|
149
|
+
function derivationSuffixTag(derivationSuffix) {
|
|
150
|
+
return `${COLOR_BASKET_NAMESPACE} ${toHex(sha256(toArray(derivationSuffix, "utf8")))}`;
|
|
151
|
+
}
|
|
152
|
+
/** Whether a tag is a derivation suffix tag. */
|
|
153
|
+
function isDerivationSuffixTag(tag) {
|
|
154
|
+
return SUFFIX_TAG.test(tag);
|
|
155
|
+
}
|
|
143
156
|
function colorTopic(assetId) {
|
|
144
157
|
return `tm_color_${assetId}`;
|
|
145
158
|
}
|
|
@@ -727,6 +740,36 @@ function custodyOf(remittance) {
|
|
|
727
740
|
senderIdentityKey
|
|
728
741
|
};
|
|
729
742
|
}
|
|
743
|
+
/** Whether `beef` holds the transaction `txid` in full, not by txid alone. */
|
|
744
|
+
function holdsTransaction(beef, txid) {
|
|
745
|
+
const found = beef.findTxid(txid);
|
|
746
|
+
return found !== void 0 && !found.isTxidOnly;
|
|
747
|
+
}
|
|
748
|
+
/**
|
|
749
|
+
* Merges the source transaction of `outpoint` from `from` into `into` with only its own proof or
|
|
750
|
+
* unproven ancestors, so other transactions in `from` are left out.
|
|
751
|
+
*/
|
|
752
|
+
function mergeSource(into, from, outpoint) {
|
|
753
|
+
const { txid } = parseOutpoint(outpoint);
|
|
754
|
+
if (!holdsTransaction(from, txid)) throw new Error(`BRC-207: the wallet returned no source transaction for token input ${outpoint}`);
|
|
755
|
+
into.mergeBeef(from.toBinaryAtomic(txid));
|
|
756
|
+
}
|
|
757
|
+
/** The tag every output the module files carries: the module finds an output's transaction by it. */
|
|
758
|
+
function suffixTagOf(custody) {
|
|
759
|
+
return derivationSuffixTag(custody.derivationSuffix);
|
|
760
|
+
}
|
|
761
|
+
/** `tags` with the custody's suffix tag added. */
|
|
762
|
+
function withSuffixTag(tags, custody) {
|
|
763
|
+
return [.../* @__PURE__ */ new Set([...tags ?? [], suffixTagOf(custody)])];
|
|
764
|
+
}
|
|
765
|
+
/** Whether a listed output carries the suffix tag of its custody. */
|
|
766
|
+
function carriesSuffixTag(tags, custody) {
|
|
767
|
+
return tags?.includes(suffixTagOf(custody)) === true;
|
|
768
|
+
}
|
|
769
|
+
/** The BEEF a listOutputs result returns, empty when it returns none. */
|
|
770
|
+
function listingBeef(listing) {
|
|
771
|
+
return listing.BEEF === void 0 ? new Beef() : Beef.fromBinary(listing.BEEF);
|
|
772
|
+
}
|
|
730
773
|
function tokenInputs(inputs) {
|
|
731
774
|
return inputs.map((input) => ({
|
|
732
775
|
outpoint: input.outpoint,
|
|
@@ -1161,13 +1204,13 @@ function tryReadTokenOutput(lockingScript, outpoint) {
|
|
|
1161
1204
|
return;
|
|
1162
1205
|
}
|
|
1163
1206
|
}
|
|
1164
|
-
/** A record with its stored tags and the computed state tag in place of any stored one. */
|
|
1207
|
+
/** A record with its stored tags, less suffix tags, and the computed state tag in place of any stored one. */
|
|
1165
1208
|
function inventoryView(record) {
|
|
1166
1209
|
const state = inventoryState(record);
|
|
1167
1210
|
return {
|
|
1168
1211
|
record,
|
|
1169
1212
|
state,
|
|
1170
|
-
tags: [...(record.output.tags ?? []).filter((tag) => !tag.startsWith(STATE_TAG_PREFIX)), `${STATE_TAG_PREFIX}${state}`]
|
|
1213
|
+
tags: [...(record.output.tags ?? []).filter((tag) => !tag.startsWith(STATE_TAG_PREFIX) && !isDerivationSuffixTag(tag)), `${STATE_TAG_PREFIX}${state}`]
|
|
1171
1214
|
};
|
|
1172
1215
|
}
|
|
1173
1216
|
/** A BEEF holding only the transactions of the returned page. */
|
|
@@ -1199,6 +1242,10 @@ var ColorPermissionModule = class {
|
|
|
1199
1242
|
capabilities = { "brc207-color": "5" };
|
|
1200
1243
|
/** Outputs a send in progress has selected, so a concurrent send does not select them too. */
|
|
1201
1244
|
selected = /* @__PURE__ */ new Set();
|
|
1245
|
+
/** The managed inputs of funded actions whose createAction has not come back, by outpoint. */
|
|
1246
|
+
staged = /* @__PURE__ */ new Map();
|
|
1247
|
+
/** The managed inputs the module signs for, by the action reference they were bound to. */
|
|
1248
|
+
bound = /* @__PURE__ */ new Map();
|
|
1202
1249
|
supportedAssets = /* @__PURE__ */ new Map();
|
|
1203
1250
|
identityKey;
|
|
1204
1251
|
constructor(options) {
|
|
@@ -1209,9 +1256,11 @@ var ColorPermissionModule = class {
|
|
|
1209
1256
|
this.provider = options.provider;
|
|
1210
1257
|
this.requestTokenAccess = options.requestTokenAccess;
|
|
1211
1258
|
}
|
|
1212
|
-
/** Clears in-memory selections and asset checks. */
|
|
1259
|
+
/** Clears in-memory selections, staged and bound input records and asset checks. */
|
|
1213
1260
|
dispose() {
|
|
1214
1261
|
this.selected.clear();
|
|
1262
|
+
this.staged.clear();
|
|
1263
|
+
this.bound.clear();
|
|
1215
1264
|
this.supportedAssets.clear();
|
|
1216
1265
|
this.identityKey = void 0;
|
|
1217
1266
|
}
|
|
@@ -1244,11 +1293,7 @@ var ColorPermissionModule = class {
|
|
|
1244
1293
|
switch (req.method) {
|
|
1245
1294
|
case "createAction": return { args: await this.createAction(req.args, req.originator) };
|
|
1246
1295
|
case "signAction": return { args: await this.signAction(req.args, req.action, req.originator) };
|
|
1247
|
-
case "internalizeAction": {
|
|
1248
|
-
const args = req.args;
|
|
1249
|
-
await this.internalizeAction(args, req.originator);
|
|
1250
|
-
return { args };
|
|
1251
|
-
}
|
|
1296
|
+
case "internalizeAction": return { args: await this.internalizeAction(req.args, req.originator) };
|
|
1252
1297
|
case "listActions": {
|
|
1253
1298
|
const args = req.args;
|
|
1254
1299
|
await this.authorizeRead(req.originator, {
|
|
@@ -1261,6 +1306,7 @@ var ColorPermissionModule = class {
|
|
|
1261
1306
|
}
|
|
1262
1307
|
}
|
|
1263
1308
|
async onResponse(res, context) {
|
|
1309
|
+
if (context.method === "createAction") this.bindAction(res);
|
|
1264
1310
|
if (context.method === "signAction" && context.action !== void 0) {
|
|
1265
1311
|
await this.fileDeploy(res, context.action, context.originator);
|
|
1266
1312
|
if (!context.action.noSend) await this.submitSigned(res, context.action.transaction);
|
|
@@ -1280,8 +1326,8 @@ var ColorPermissionModule = class {
|
|
|
1280
1326
|
txid,
|
|
1281
1327
|
vout: 0
|
|
1282
1328
|
});
|
|
1283
|
-
const
|
|
1284
|
-
if (deploy?.deploy !== true ||
|
|
1329
|
+
const remittance = ColorPlan.fromLabels(action.createArgs?.labels)?.ownedOutputs.find((owned) => owned.outputIndex === 0 && owned.assetId === "ISSUE");
|
|
1330
|
+
if (deploy?.deploy !== true || remittance === void 0) return;
|
|
1285
1331
|
const customInstructions = action.createArgs?.outputs?.[0]?.customInstructions;
|
|
1286
1332
|
await this.wallet.internalizeAction({
|
|
1287
1333
|
tx: result.tx,
|
|
@@ -1291,12 +1337,16 @@ var ColorPermissionModule = class {
|
|
|
1291
1337
|
protocol: "basket insertion",
|
|
1292
1338
|
insertionRemittance: {
|
|
1293
1339
|
basket: assetBasket(deploy.assetId, deploy.role),
|
|
1294
|
-
...customInstructions === void 0 ? {} : { customInstructions }
|
|
1340
|
+
...customInstructions === void 0 ? {} : { customInstructions },
|
|
1341
|
+
tags: [suffixTagOf(remittance)]
|
|
1295
1342
|
}
|
|
1296
1343
|
}]
|
|
1297
1344
|
}, originator);
|
|
1298
1345
|
}
|
|
1299
|
-
/**
|
|
1346
|
+
/**
|
|
1347
|
+
* Verifies every `p color` receipt and tags it with its derivation suffix; the caller must
|
|
1348
|
+
* already have submitted the transaction.
|
|
1349
|
+
*/
|
|
1300
1350
|
async internalizeAction(args, originator) {
|
|
1301
1351
|
if ((args.labels ?? []).some((label) => label.startsWith(`p color `))) throw new Error("BRC-207: p color labels have no meaning in internalizeAction");
|
|
1302
1352
|
const tx = parsed("the received transaction", () => Transaction.fromAtomicBEEF(args.tx));
|
|
@@ -1307,11 +1357,23 @@ var ColorPermissionModule = class {
|
|
|
1307
1357
|
remittance
|
|
1308
1358
|
}] : [];
|
|
1309
1359
|
});
|
|
1310
|
-
if (receipts.length === 0) return;
|
|
1360
|
+
if (receipts.length === 0) return args;
|
|
1311
1361
|
await this.assertCompletedTransaction(tx);
|
|
1362
|
+
const tags = /* @__PURE__ */ new Map();
|
|
1312
1363
|
await eachInOrder(receipts, async ({ outputIndex, remittance }) => {
|
|
1313
|
-
await this.verifyReceipt(tx, outputIndex, remittance, originator);
|
|
1364
|
+
const custody = await this.verifyReceipt(tx, outputIndex, remittance, originator);
|
|
1365
|
+
tags.set(outputIndex, withSuffixTag(remittance.tags, custody));
|
|
1314
1366
|
});
|
|
1367
|
+
return {
|
|
1368
|
+
...args,
|
|
1369
|
+
outputs: args.outputs.map((output) => output.insertionRemittance === void 0 || !tags.has(output.outputIndex) ? output : {
|
|
1370
|
+
...output,
|
|
1371
|
+
insertionRemittance: {
|
|
1372
|
+
...output.insertionRemittance,
|
|
1373
|
+
tags: tags.get(output.outputIndex)
|
|
1374
|
+
}
|
|
1375
|
+
})
|
|
1376
|
+
};
|
|
1315
1377
|
}
|
|
1316
1378
|
/**
|
|
1317
1379
|
* Checks a received transaction: every input unlocks its source and every asset it involves is
|
|
@@ -1352,6 +1414,7 @@ var ColorPermissionModule = class {
|
|
|
1352
1414
|
lockingScript: source.lockingScript,
|
|
1353
1415
|
satoshis: source.satoshis
|
|
1354
1416
|
}])).has(outpoint)) throw new Error("BRC-207: the received output is not admitted and unspent in its asset overlay");
|
|
1417
|
+
return custody;
|
|
1355
1418
|
}
|
|
1356
1419
|
/** Checks a planned token action and adds its selected token inputs and per-asset change. */
|
|
1357
1420
|
async createAction(args, originator) {
|
|
@@ -1368,15 +1431,48 @@ var ColorPermissionModule = class {
|
|
|
1368
1431
|
this.reserve(reserved, explicit.managed);
|
|
1369
1432
|
const { inputs, changes } = await this.fund(reserved, plan, planned, explicit, handoff, originator);
|
|
1370
1433
|
sighashScopes(plan, new Set([...explicit.managed, ...inputs].map((input) => input.outpoint)));
|
|
1371
|
-
|
|
1434
|
+
const inputBEEF = await this.inputBeef(args.inputBEEF, inputs, originator);
|
|
1435
|
+
const funded = {
|
|
1372
1436
|
...args,
|
|
1373
1437
|
inputs: orderInputs(explicit.inputs, tokenInputs(inputs), plan.inputPositions),
|
|
1374
|
-
outputs: placeChange(outputs, changes, plan.changePositions)
|
|
1438
|
+
outputs: placeChange(outputs, changes, plan.changePositions),
|
|
1439
|
+
...inputBEEF === void 0 ? {} : { inputBEEF }
|
|
1375
1440
|
};
|
|
1441
|
+
this.stage([...explicit.managed, ...inputs]);
|
|
1442
|
+
return funded;
|
|
1376
1443
|
} finally {
|
|
1377
1444
|
for (const outpoint of reserved) this.selected.delete(outpoint);
|
|
1378
1445
|
}
|
|
1379
1446
|
}
|
|
1447
|
+
/** Stages a funded action's managed inputs until its createAction comes back. */
|
|
1448
|
+
stage(inputs) {
|
|
1449
|
+
for (const input of inputs) this.staged.set(input.outpoint, input);
|
|
1450
|
+
}
|
|
1451
|
+
/**
|
|
1452
|
+
* Moves the staged inputs the returned transaction spends to its action reference. A result
|
|
1453
|
+
* without a signable transaction binds nothing; its named inputs are only unstaged.
|
|
1454
|
+
*/
|
|
1455
|
+
bindAction(result) {
|
|
1456
|
+
const signable = result.signableTransaction;
|
|
1457
|
+
const atomic = signable?.tx ?? result.tx;
|
|
1458
|
+
if (atomic === void 0) return;
|
|
1459
|
+
let tx;
|
|
1460
|
+
try {
|
|
1461
|
+
tx = Transaction.fromAtomicBEEF(atomic);
|
|
1462
|
+
} catch {
|
|
1463
|
+
return;
|
|
1464
|
+
}
|
|
1465
|
+
for (const input of tx.inputs) {
|
|
1466
|
+
const outpoint = inputOutpoint(input);
|
|
1467
|
+
const staged = this.staged.get(outpoint);
|
|
1468
|
+
if (staged === void 0) continue;
|
|
1469
|
+
this.staged.delete(outpoint);
|
|
1470
|
+
if (signable === void 0) continue;
|
|
1471
|
+
const records = this.bound.get(signable.reference) ?? [];
|
|
1472
|
+
records.push(staged);
|
|
1473
|
+
this.bound.set(signable.reference, records);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1380
1476
|
reserve(reserved, inputs) {
|
|
1381
1477
|
for (const input of inputs) {
|
|
1382
1478
|
this.selected.add(input.outpoint);
|
|
@@ -1408,7 +1504,8 @@ var ColorPermissionModule = class {
|
|
|
1408
1504
|
basket: sdk.specOpBasketPrefixOutputs,
|
|
1409
1505
|
tags: [COLOR_BASKET_NAMESPACE],
|
|
1410
1506
|
include: "locking scripts",
|
|
1411
|
-
includeCustomInstructions: true
|
|
1507
|
+
includeCustomInstructions: true,
|
|
1508
|
+
includeTags: true
|
|
1412
1509
|
}, originator);
|
|
1413
1510
|
const own = new Map(listed.outputs.filter((output) => outpoints.has(output.outpoint)).map((output) => [output.outpoint, output]));
|
|
1414
1511
|
const beef = args.inputBEEF === void 0 ? void 0 : parsed("inputBEEF", () => Beef.fromBinary(args.inputBEEF));
|
|
@@ -1421,9 +1518,10 @@ var ColorPermissionModule = class {
|
|
|
1421
1518
|
}
|
|
1422
1519
|
/** Counts one of this wallet's `p color` outputs once it is managed, admitted and unspent. */
|
|
1423
1520
|
async ownInput(explicit, output, originator) {
|
|
1424
|
-
if (!output.spendable) throw new Error("BRC-207: an explicit p color input is reserved by another action");
|
|
1521
|
+
if (!output.spendable || this.staged.has(output.outpoint) || this.selected.has(output.outpoint)) throw new Error("BRC-207: an explicit p color input is reserved by another action");
|
|
1425
1522
|
const managed = await this.managedInput(output, originator);
|
|
1426
1523
|
if (managed === void 0) throw new Error("BRC-207: an explicit p color input is not a managed token output");
|
|
1524
|
+
if (!carriesSuffixTag(output.tags, managed.custody)) throw new Error(`BRC-207: token output ${managed.outpoint} was filed without its suffix tag and is not spendable through the module`);
|
|
1427
1525
|
await this.assertSupportedAsset(managed.assetId);
|
|
1428
1526
|
if (!(await this.spendable(managed.assetId, [managed])).has(managed.outpoint) && !(await this.resubmitHeld(managed.assetId, managed.role, [managed], originator)).has(managed.outpoint)) throw new Error("BRC-207: an explicit p color input is not admitted and unspent");
|
|
1429
1527
|
explicit.managed.push(managed);
|
|
@@ -1543,7 +1641,8 @@ var ColorPermissionModule = class {
|
|
|
1543
1641
|
return {
|
|
1544
1642
|
...output,
|
|
1545
1643
|
basket,
|
|
1546
|
-
customInstructions: custodyInstructions(custodyOf(remittance))
|
|
1644
|
+
customInstructions: custodyInstructions(custodyOf(remittance)),
|
|
1645
|
+
tags: withSuffixTag(output.tags, remittance)
|
|
1547
1646
|
};
|
|
1548
1647
|
}
|
|
1549
1648
|
/**
|
|
@@ -1696,10 +1795,52 @@ var ColorPermissionModule = class {
|
|
|
1696
1795
|
});
|
|
1697
1796
|
return await this.spendable(assetId, outputs);
|
|
1698
1797
|
}
|
|
1798
|
+
/**
|
|
1799
|
+
* `callerBEEF` with the source transactions of the module's selected inputs merged in, each with
|
|
1800
|
+
* its proof or unproven ancestors and nothing else. Inputs the caller named keep the BEEF the
|
|
1801
|
+
* caller gave; the module adds none for them.
|
|
1802
|
+
*/
|
|
1803
|
+
async inputBeef(callerBEEF, inputs, originator) {
|
|
1804
|
+
if (inputs.length === 0) return callerBEEF;
|
|
1805
|
+
const beef = callerBEEF === void 0 ? new Beef() : parsed("inputBEEF", () => Beef.fromBinary(callerBEEF));
|
|
1806
|
+
const baskets = /* @__PURE__ */ new Map();
|
|
1807
|
+
for (const input of inputs) {
|
|
1808
|
+
if (input.sourceBEEF !== void 0) {
|
|
1809
|
+
mergeSource(beef, Beef.fromBinary(input.sourceBEEF), input.outpoint);
|
|
1810
|
+
continue;
|
|
1811
|
+
}
|
|
1812
|
+
const basket = assetBasket(input.assetId, input.role);
|
|
1813
|
+
baskets.set(basket, [...baskets.get(basket) ?? [], input]);
|
|
1814
|
+
}
|
|
1815
|
+
await eachInOrder(baskets, async ([basket, selected]) => {
|
|
1816
|
+
const sources = await this.sourceTransactions(basket, selected, originator);
|
|
1817
|
+
for (const input of selected) mergeSource(beef, sources, input.outpoint);
|
|
1818
|
+
});
|
|
1819
|
+
return beef.toBinary();
|
|
1820
|
+
}
|
|
1821
|
+
/**
|
|
1822
|
+
* The selected inputs of one basket with their entire transactions, in one listing by their
|
|
1823
|
+
* suffix tags. The listing must return exactly the selected outputs.
|
|
1824
|
+
*/
|
|
1825
|
+
async sourceTransactions(basket, selected, originator) {
|
|
1826
|
+
const listing = await this.wallet.listOutputs({
|
|
1827
|
+
basket,
|
|
1828
|
+
tags: selected.map((input) => suffixTagOf(input.custody)),
|
|
1829
|
+
tagQueryMode: "any",
|
|
1830
|
+
include: "entire transactions",
|
|
1831
|
+
limit: selected.length
|
|
1832
|
+
}, originator);
|
|
1833
|
+
const wanted = new Set(selected.map((input) => input.outpoint));
|
|
1834
|
+
const returned = new Set(listing.outputs.map((output) => output.outpoint));
|
|
1835
|
+
const missing = [...wanted].filter((outpoint) => !returned.has(outpoint));
|
|
1836
|
+
const extra = [...returned].filter((outpoint) => !wanted.has(outpoint));
|
|
1837
|
+
if (missing.length > 0 || extra.length > 0 || listing.outputs.length !== wanted.size) throw new Error(`BRC-207: the wallet returned other outputs than the selected token inputs: missing ${missing.join(", ") || "none"}; extra ${extra.join(", ") || "none"}`);
|
|
1838
|
+
return listingBeef(listing);
|
|
1839
|
+
}
|
|
1699
1840
|
/** One basket's token outputs of the asset, read from their locking scripts, excluding selected ones. */
|
|
1700
1841
|
async candidates(assetId, role, originator) {
|
|
1701
|
-
return (await this.listBasket(assetBasket(assetId, role), originator, {})).flatMap((output) => {
|
|
1702
|
-
if (this.selected.has(output.outpoint) || output.customInstructions === void 0) return [];
|
|
1842
|
+
return (await this.listBasket(assetBasket(assetId, role), originator, { includeTags: true })).flatMap((output) => {
|
|
1843
|
+
if (output.spendable === false || this.selected.has(output.outpoint) || this.staged.has(output.outpoint) || output.customInstructions === void 0 || !(output.tags ?? []).some(isDerivationSuffixTag)) return [];
|
|
1703
1844
|
const source = this.sourceOutput(output);
|
|
1704
1845
|
const token = tryReadTokenOutput(source.lockingScript, parseOutpoint(output.outpoint));
|
|
1705
1846
|
if (token?.assetId !== assetId || token.role !== role) return [];
|
|
@@ -1707,20 +1848,23 @@ var ColorPermissionModule = class {
|
|
|
1707
1848
|
...source,
|
|
1708
1849
|
token,
|
|
1709
1850
|
amount: token.amount,
|
|
1710
|
-
customInstructions: output.customInstructions
|
|
1851
|
+
customInstructions: output.customInstructions,
|
|
1852
|
+
tags: output.tags ?? []
|
|
1711
1853
|
}];
|
|
1712
1854
|
});
|
|
1713
1855
|
}
|
|
1714
1856
|
/** A chosen candidate with its custody, or undefined when its custody record is unreadable. */
|
|
1715
1857
|
async readableInput(candidate, originator) {
|
|
1716
1858
|
try {
|
|
1717
|
-
const { outpoint, lockingScript, satoshis, token, customInstructions } = candidate;
|
|
1718
|
-
|
|
1859
|
+
const { outpoint, lockingScript, satoshis, token, customInstructions, tags } = candidate;
|
|
1860
|
+
const input = await this.custodyInput({
|
|
1719
1861
|
outpoint,
|
|
1720
1862
|
lockingScript,
|
|
1721
1863
|
satoshis,
|
|
1722
1864
|
token
|
|
1723
1865
|
}, customInstructions, originator);
|
|
1866
|
+
if (input === void 0 || !carriesSuffixTag(tags, input.custody)) return void 0;
|
|
1867
|
+
return input;
|
|
1724
1868
|
} catch {
|
|
1725
1869
|
return;
|
|
1726
1870
|
}
|
|
@@ -1782,6 +1926,7 @@ var ColorPermissionModule = class {
|
|
|
1782
1926
|
sighashTypes: []
|
|
1783
1927
|
}),
|
|
1784
1928
|
inputs: tokenInputs(selected),
|
|
1929
|
+
inputBEEF: await this.inputBeef(void 0, selected, originator),
|
|
1785
1930
|
outputs,
|
|
1786
1931
|
options: {
|
|
1787
1932
|
randomizeOutputs: false,
|
|
@@ -1810,7 +1955,8 @@ var ColorPermissionModule = class {
|
|
|
1810
1955
|
assetId,
|
|
1811
1956
|
role: "value",
|
|
1812
1957
|
amount,
|
|
1813
|
-
custody: exact.custody
|
|
1958
|
+
custody: exact.custody,
|
|
1959
|
+
sourceBEEF: signed.tx
|
|
1814
1960
|
};
|
|
1815
1961
|
}
|
|
1816
1962
|
/** A fresh wallet-owned value output in the asset's basket, keyed to this wallet. */
|
|
@@ -1829,6 +1975,7 @@ var ColorPermissionModule = class {
|
|
|
1829
1975
|
satoshis: TOKEN_CARRIER_SATOSHIS,
|
|
1830
1976
|
basket: assetBasket(assetId, "value"),
|
|
1831
1977
|
customInstructions: custodyInstructions(custody),
|
|
1978
|
+
tags: [suffixTagOf(custody)],
|
|
1832
1979
|
outputDescription: "BRC-207 token change"
|
|
1833
1980
|
}
|
|
1834
1981
|
};
|
|
@@ -1846,7 +1993,21 @@ var ColorPermissionModule = class {
|
|
|
1846
1993
|
const tx = action.transaction;
|
|
1847
1994
|
const tokens = transactionTokens(tx, true);
|
|
1848
1995
|
assertTransactionOperations(plan, tokens);
|
|
1849
|
-
const managed =
|
|
1996
|
+
const managed = this.bound.get(action.reference) ?? [];
|
|
1997
|
+
try {
|
|
1998
|
+
return await this.signBound(args, {
|
|
1999
|
+
plan,
|
|
2000
|
+
createArgs,
|
|
2001
|
+
tx,
|
|
2002
|
+
tokens
|
|
2003
|
+
}, managed, originator);
|
|
2004
|
+
} finally {
|
|
2005
|
+
this.bound.delete(action.reference);
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
/** Rechecks, approves and signs the module's inputs of a funded action. */
|
|
2009
|
+
async signBound(args, funded, managed, originator) {
|
|
2010
|
+
const { plan, createArgs, tx, tokens } = funded;
|
|
1850
2011
|
const scopes = sighashScopes(plan, new Set(managed.map((input) => input.outpoint)));
|
|
1851
2012
|
await eachInOrder(new Set(tokens.consumed.map((input) => input.assetId)), async (assetId) => {
|
|
1852
2013
|
const assetInputs = tokens.consumed.filter((input) => input.assetId === assetId);
|
|
@@ -1862,17 +2023,6 @@ var ColorPermissionModule = class {
|
|
|
1862
2023
|
}
|
|
1863
2024
|
};
|
|
1864
2025
|
}
|
|
1865
|
-
/** The transaction's inputs that spend this wallet's `p color` outputs, with their stored custody. */
|
|
1866
|
-
async actionInputs(tx, originator) {
|
|
1867
|
-
const outpoints = new Set(tx.inputs.map((input) => inputOutpoint(input)));
|
|
1868
|
-
const listed = await this.wallet.listOutputs({
|
|
1869
|
-
basket: sdk.specOpBasketPrefixOutputs,
|
|
1870
|
-
tags: [COLOR_BASKET_NAMESPACE],
|
|
1871
|
-
include: "locking scripts",
|
|
1872
|
-
includeCustomInstructions: true
|
|
1873
|
-
}, originator);
|
|
1874
|
-
return (await Promise.all(listed.outputs.filter((output) => outpoints.has(output.outpoint)).map(async (output) => await this.managedInput(output, originator)))).filter((input) => input !== void 0);
|
|
1875
|
-
}
|
|
1876
2026
|
/**
|
|
1877
2027
|
* Submits a signed token transaction to the topic of every asset among its inputs and outputs,
|
|
1878
2028
|
* and a deploy to the discovery topic.
|
|
@@ -2126,6 +2276,7 @@ var ColorPermissionModule = class {
|
|
|
2126
2276
|
basket,
|
|
2127
2277
|
tags: filter.tags,
|
|
2128
2278
|
tagQueryMode: filter.tagQueryMode,
|
|
2279
|
+
includeTags: filter.includeTags,
|
|
2129
2280
|
include: "locking scripts",
|
|
2130
2281
|
includeCustomInstructions: true,
|
|
2131
2282
|
limit: LIST_PAGE_LIMIT,
|