@bopen-io/color-permission-module 0.1.1 → 0.1.3
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 +31 -17
- package/dist/index.d.mts +33 -21
- package/dist/index.mjs +156 -70
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -59,19 +59,34 @@ the manager's `getVersion` string.
|
|
|
59
59
|
indices and the approved facts name the requested outputs). The module selects
|
|
60
60
|
admitted token inputs and adds token change in `createAction`, and supplies
|
|
61
61
|
the source transactions of the inputs it selects in `inputBEEF`, merged with
|
|
62
|
-
any `inputBEEF` the caller gave
|
|
63
|
-
|
|
64
|
-
the
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
|
75
90
|
`signAction` it checks the funded transaction against the plan, asks the user
|
|
76
91
|
to approve it (`color_send`: every asset leg, satoshi totals and fee, the
|
|
77
92
|
signatures it releases), signs the token inputs with any `sighashTypes`
|
|
@@ -133,10 +148,9 @@ The module fails explicitly, rather than falling back, on:
|
|
|
133
148
|
|
|
134
149
|
Explicit inputs are supported: this wallet's `p color` outputs are signed by the
|
|
135
150
|
module, and foreign token inputs count only once their asset's overlay admits
|
|
136
|
-
them. The
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
resolved by the wallet and checked in `signAction`. `inputPositions` and token `changePositions` are honored; change
|
|
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
|
|
140
154
|
positions follow the requested outputs.
|
|
141
155
|
|
|
142
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, BEEF, 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 suffix tag the output carries; the module lists its transaction by it. */
|
|
200
|
+
suffixTag: string;
|
|
197
201
|
/** The source transaction as BEEF, when the module made it (a handoff split). */
|
|
198
202
|
sourceBEEF?: BEEF;
|
|
199
203
|
}
|
|
@@ -349,15 +353,14 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
349
353
|
readonly capabilities: Readonly<Record<string, string>>;
|
|
350
354
|
/** Outputs a send in progress has selected, so a concurrent send does not select them too. */
|
|
351
355
|
protected readonly selected: Set<string>;
|
|
352
|
-
/**
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
protected readonly held: Map<string, ManagedInput>;
|
|
356
|
+
/** The managed inputs of funded actions whose createAction has not come back, by outpoint. */
|
|
357
|
+
protected readonly staged: Map<string, ManagedInput>;
|
|
358
|
+
/** The managed inputs the module signs for, by the action reference they were bound to. */
|
|
359
|
+
protected readonly bound: Map<string, ManagedInput[]>;
|
|
357
360
|
protected readonly supportedAssets: Map<string, Promise<void>>;
|
|
358
361
|
protected identityKey?: Promise<string>;
|
|
359
362
|
constructor(options: ColorPermissionModuleOptions);
|
|
360
|
-
/** Clears in-memory selections,
|
|
363
|
+
/** Clears in-memory selections, staged and bound input records and asset checks. */
|
|
361
364
|
dispose(): void;
|
|
362
365
|
/** Protocol- and basket-routed calls: key and signature operations and the read views. */
|
|
363
366
|
handleRequest(req: PermissionsModuleRequest, next: PermissionsModuleNext): Promise<unknown>;
|
|
@@ -376,8 +379,11 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
376
379
|
* before it can be spent.
|
|
377
380
|
*/
|
|
378
381
|
protected fileDeploy(result: SignActionResult, action: PermissionsModuleAction, originator: string): Promise<void>;
|
|
379
|
-
/**
|
|
380
|
-
|
|
382
|
+
/**
|
|
383
|
+
* Verifies every `p color` receipt and tags it with its derivation suffix; the caller must
|
|
384
|
+
* already have submitted the transaction.
|
|
385
|
+
*/
|
|
386
|
+
protected internalizeAction(args: InternalizeActionArgs, originator: string): Promise<InternalizeActionArgs>;
|
|
381
387
|
/**
|
|
382
388
|
* Checks a received transaction: every input unlocks its source and every asset it involves is
|
|
383
389
|
* supported. Token amounts are not re-evaluated: the received output must be admitted and
|
|
@@ -387,9 +393,16 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
387
393
|
/** Checks each distinct asset in order, so the first unsupported one is the one reported. */
|
|
388
394
|
protected assertSupportedAssets(assetIds: Iterable<string>): Promise<void>;
|
|
389
395
|
/** One receipt: its custody, its output's asset, owner and basket, and overlay admission. */
|
|
390
|
-
protected verifyReceipt(tx: Transaction, outputIndex: number, remittance: BasketInsertion, originator: string): Promise<
|
|
396
|
+
protected verifyReceipt(tx: Transaction, outputIndex: number, remittance: BasketInsertion, originator: string): Promise<Custody>;
|
|
391
397
|
/** Checks a planned token action and adds its selected token inputs and per-asset change. */
|
|
392
398
|
protected createAction(args: CreateActionArgs, originator: string): Promise<CreateActionArgs>;
|
|
399
|
+
/** Stages a funded action's managed inputs until its createAction comes back. */
|
|
400
|
+
protected stage(inputs: readonly ManagedInput[]): void;
|
|
401
|
+
/**
|
|
402
|
+
* Moves the staged inputs the returned transaction spends to its action reference. A result
|
|
403
|
+
* without a signable transaction binds nothing; its named inputs are only unstaged.
|
|
404
|
+
*/
|
|
405
|
+
protected bindAction(result: CreateActionResult): void;
|
|
393
406
|
protected reserve(reserved: Set<string>, inputs: ReadonlyArray<{
|
|
394
407
|
outpoint: string;
|
|
395
408
|
}>): void;
|
|
@@ -417,7 +430,8 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
417
430
|
protected jsonDestination(plan: ColorPlan, index: number, output: CreateActionOutput, token: TokenOutput, planned: PlannedOutputs): void;
|
|
418
431
|
/**
|
|
419
432
|
* The action's deploy, which BRC-162 requires at output 0. An owned deploy keeps its `ISSUE`
|
|
420
|
-
* key; its asset basket is assigned once the transaction
|
|
433
|
+
* key and is tagged like every owned output; its asset basket is assigned once the transaction
|
|
434
|
+
* ID is final.
|
|
421
435
|
*/
|
|
422
436
|
protected deployOutput(args: CreateActionArgs, output: CreateActionOutput, token: TokenOutput, remittance: ColorRemittance | undefined, planned: PlannedOutputs, originator: string): Promise<CreateActionOutput>;
|
|
423
437
|
/** A token output of the action that this wallet keeps, placed in its asset's basket. */
|
|
@@ -475,20 +489,20 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
475
489
|
*/
|
|
476
490
|
protected inputBeef(callerBEEF: BEEF | undefined, inputs: readonly ManagedInput[], originator: string): Promise<BEEF | undefined>;
|
|
477
491
|
/**
|
|
478
|
-
*
|
|
479
|
-
*
|
|
492
|
+
* The selected inputs of one basket with their entire transactions, in one listing by their
|
|
493
|
+
* suffix tags. The listing must return exactly the selected outputs.
|
|
480
494
|
*/
|
|
481
|
-
protected sourceTransactions(basket: string,
|
|
495
|
+
protected sourceTransactions(basket: string, selected: readonly ManagedInput[], originator: string): Promise<Beef>;
|
|
482
496
|
/** One basket's token outputs of the asset, read from their locking scripts, excluding selected ones. */
|
|
483
497
|
protected candidates(assetId: string, role: TokenRole, originator: string): Promise<Candidate[]>;
|
|
484
498
|
/** A chosen candidate with its custody, or undefined when its custody record is unreadable. */
|
|
485
499
|
protected readableInput(candidate: Candidate, originator: string): Promise<ManagedInput | undefined>;
|
|
486
|
-
/** A `p color` basket output as a managed token input, or undefined when it is not one. */
|
|
487
|
-
protected managedInput(output: WalletOutput, originator: string): Promise<ManagedInput | undefined>;
|
|
500
|
+
/** A `p color` basket output as a managed token input, before its tag, or undefined when it is not one. */
|
|
501
|
+
protected managedInput(output: WalletOutput, originator: string): Promise<Omit<ManagedInput, 'suffixTag'> | undefined>;
|
|
488
502
|
/** Decrypts and parses an output's custody tuple; undefined when it names another asset. */
|
|
489
503
|
protected custodyInput(output: SourceOutput & {
|
|
490
504
|
token: TokenOutput;
|
|
491
|
-
}, customInstructions: string, originator: string): Promise<ManagedInput | undefined>;
|
|
505
|
+
}, customInstructions: string, originator: string): Promise<Omit<ManagedInput, 'suffixTag'> | undefined>;
|
|
492
506
|
/**
|
|
493
507
|
* Before a handoff, splits the selected inputs into an output of exactly `amount` and token
|
|
494
508
|
* change, broadcast and submitted. The split pays this wallet and, like BRC-177's revocation
|
|
@@ -506,10 +520,8 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
506
520
|
* `p color` outputs.
|
|
507
521
|
*/
|
|
508
522
|
protected signAction(args: SignActionArgs, action: PermissionsModuleAction | undefined, originator: string): Promise<SignActionArgs>;
|
|
509
|
-
/** The kept records of the module's inputs among the action's inputs. */
|
|
510
|
-
protected heldInputs(createArgs: CreateActionArgs): ManagedInput[];
|
|
511
523
|
/** Rechecks, approves and signs the module's inputs of a funded action. */
|
|
512
|
-
protected
|
|
524
|
+
protected signBound(args: SignActionArgs, funded: {
|
|
513
525
|
plan: ColorPlan;
|
|
514
526
|
createArgs: CreateActionArgs;
|
|
515
527
|
tx: Transaction;
|
|
@@ -554,7 +566,7 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
554
566
|
/** The queried outputs the overlay reports admitted and unspent, matched to their source bytes. */
|
|
555
567
|
protected spendable(assetId: string, outputs: readonly SourceOutput[]): Promise<Set<string>>;
|
|
556
568
|
/** All spendable outputs of one basket, with locking scripts and custom instructions. */
|
|
557
|
-
protected listBasket(basket: string, originator: string, filter: Pick<ListOutputsArgs, 'tags' | 'tagQueryMode'>): Promise<WalletOutput[]>;
|
|
569
|
+
protected listBasket(basket: string, originator: string, filter: Pick<ListOutputsArgs, 'tags' | 'tagQueryMode' | 'includeTags'>): Promise<WalletOutput[]>;
|
|
558
570
|
protected sourceOutput(output: WalletOutput): SourceOutput;
|
|
559
571
|
protected preimage(tx: Transaction, inputIndex: number, source: SourceOutput, scope: number): number[];
|
|
560
572
|
/** 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
|
}
|
|
@@ -698,8 +711,6 @@ const DEFAULT_SIGHASH = TransactionSignature.SIGHASH_ALL | TransactionSignature.
|
|
|
698
711
|
const MAX_ENVELOPE_BYTES = 16777216;
|
|
699
712
|
const MAX_RECEIPT_INSTRUCTION_BYTES = 1e3;
|
|
700
713
|
const LIST_PAGE_LIMIT = 1e4;
|
|
701
|
-
/** Outputs per page when reading selected inputs' source transactions, which carry BEEF. */
|
|
702
|
-
const INPUT_BEEF_PAGE_LIMIT = 10;
|
|
703
714
|
const SUBMIT_ATTEMPTS = 3;
|
|
704
715
|
const NOSEND_EXPIRY_LABEL_PREFIX = "p nosend expiry ";
|
|
705
716
|
const STATE_TAG_PREFIX = "color_state_";
|
|
@@ -743,6 +754,22 @@ function mergeSource(into, from, outpoint) {
|
|
|
743
754
|
if (!holdsTransaction(from, txid)) throw new Error(`BRC-207: the wallet returned no source transaction for token input ${outpoint}`);
|
|
744
755
|
into.mergeBeef(from.toBinaryAtomic(txid));
|
|
745
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
|
+
/** The suffix tag among a listed output's tags, by which its transaction is listed. */
|
|
766
|
+
function suffixTagIn(tags) {
|
|
767
|
+
return tags?.find(isDerivationSuffixTag);
|
|
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
|
+
}
|
|
746
773
|
function tokenInputs(inputs) {
|
|
747
774
|
return inputs.map((input) => ({
|
|
748
775
|
outpoint: input.outpoint,
|
|
@@ -1177,13 +1204,13 @@ function tryReadTokenOutput(lockingScript, outpoint) {
|
|
|
1177
1204
|
return;
|
|
1178
1205
|
}
|
|
1179
1206
|
}
|
|
1180
|
-
/** 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. */
|
|
1181
1208
|
function inventoryView(record) {
|
|
1182
1209
|
const state = inventoryState(record);
|
|
1183
1210
|
return {
|
|
1184
1211
|
record,
|
|
1185
1212
|
state,
|
|
1186
|
-
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}`]
|
|
1187
1214
|
};
|
|
1188
1215
|
}
|
|
1189
1216
|
/** A BEEF holding only the transactions of the returned page. */
|
|
@@ -1215,11 +1242,10 @@ var ColorPermissionModule = class {
|
|
|
1215
1242
|
capabilities = { "brc207-color": "5" };
|
|
1216
1243
|
/** Outputs a send in progress has selected, so a concurrent send does not select them too. */
|
|
1217
1244
|
selected = /* @__PURE__ */ new Set();
|
|
1218
|
-
/**
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
*/
|
|
1222
|
-
held = /* @__PURE__ */ new Map();
|
|
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();
|
|
1223
1249
|
supportedAssets = /* @__PURE__ */ new Map();
|
|
1224
1250
|
identityKey;
|
|
1225
1251
|
constructor(options) {
|
|
@@ -1230,10 +1256,11 @@ var ColorPermissionModule = class {
|
|
|
1230
1256
|
this.provider = options.provider;
|
|
1231
1257
|
this.requestTokenAccess = options.requestTokenAccess;
|
|
1232
1258
|
}
|
|
1233
|
-
/** Clears in-memory selections,
|
|
1259
|
+
/** Clears in-memory selections, staged and bound input records and asset checks. */
|
|
1234
1260
|
dispose() {
|
|
1235
1261
|
this.selected.clear();
|
|
1236
|
-
this.
|
|
1262
|
+
this.staged.clear();
|
|
1263
|
+
this.bound.clear();
|
|
1237
1264
|
this.supportedAssets.clear();
|
|
1238
1265
|
this.identityKey = void 0;
|
|
1239
1266
|
}
|
|
@@ -1266,11 +1293,7 @@ var ColorPermissionModule = class {
|
|
|
1266
1293
|
switch (req.method) {
|
|
1267
1294
|
case "createAction": return { args: await this.createAction(req.args, req.originator) };
|
|
1268
1295
|
case "signAction": return { args: await this.signAction(req.args, req.action, req.originator) };
|
|
1269
|
-
case "internalizeAction": {
|
|
1270
|
-
const args = req.args;
|
|
1271
|
-
await this.internalizeAction(args, req.originator);
|
|
1272
|
-
return { args };
|
|
1273
|
-
}
|
|
1296
|
+
case "internalizeAction": return { args: await this.internalizeAction(req.args, req.originator) };
|
|
1274
1297
|
case "listActions": {
|
|
1275
1298
|
const args = req.args;
|
|
1276
1299
|
await this.authorizeRead(req.originator, {
|
|
@@ -1283,6 +1306,7 @@ var ColorPermissionModule = class {
|
|
|
1283
1306
|
}
|
|
1284
1307
|
}
|
|
1285
1308
|
async onResponse(res, context) {
|
|
1309
|
+
if (context.method === "createAction") this.bindAction(res);
|
|
1286
1310
|
if (context.method === "signAction" && context.action !== void 0) {
|
|
1287
1311
|
await this.fileDeploy(res, context.action, context.originator);
|
|
1288
1312
|
if (!context.action.noSend) await this.submitSigned(res, context.action.transaction);
|
|
@@ -1302,8 +1326,8 @@ var ColorPermissionModule = class {
|
|
|
1302
1326
|
txid,
|
|
1303
1327
|
vout: 0
|
|
1304
1328
|
});
|
|
1305
|
-
const
|
|
1306
|
-
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;
|
|
1307
1331
|
const customInstructions = action.createArgs?.outputs?.[0]?.customInstructions;
|
|
1308
1332
|
await this.wallet.internalizeAction({
|
|
1309
1333
|
tx: result.tx,
|
|
@@ -1318,7 +1342,10 @@ var ColorPermissionModule = class {
|
|
|
1318
1342
|
}]
|
|
1319
1343
|
}, originator);
|
|
1320
1344
|
}
|
|
1321
|
-
/**
|
|
1345
|
+
/**
|
|
1346
|
+
* Verifies every `p color` receipt and tags it with its derivation suffix; the caller must
|
|
1347
|
+
* already have submitted the transaction.
|
|
1348
|
+
*/
|
|
1322
1349
|
async internalizeAction(args, originator) {
|
|
1323
1350
|
if ((args.labels ?? []).some((label) => label.startsWith(`p color `))) throw new Error("BRC-207: p color labels have no meaning in internalizeAction");
|
|
1324
1351
|
const tx = parsed("the received transaction", () => Transaction.fromAtomicBEEF(args.tx));
|
|
@@ -1329,11 +1356,23 @@ var ColorPermissionModule = class {
|
|
|
1329
1356
|
remittance
|
|
1330
1357
|
}] : [];
|
|
1331
1358
|
});
|
|
1332
|
-
if (receipts.length === 0) return;
|
|
1359
|
+
if (receipts.length === 0) return args;
|
|
1333
1360
|
await this.assertCompletedTransaction(tx);
|
|
1361
|
+
const tags = /* @__PURE__ */ new Map();
|
|
1334
1362
|
await eachInOrder(receipts, async ({ outputIndex, remittance }) => {
|
|
1335
|
-
await this.verifyReceipt(tx, outputIndex, remittance, originator);
|
|
1363
|
+
const custody = await this.verifyReceipt(tx, outputIndex, remittance, originator);
|
|
1364
|
+
tags.set(outputIndex, withSuffixTag(remittance.tags, custody));
|
|
1336
1365
|
});
|
|
1366
|
+
return {
|
|
1367
|
+
...args,
|
|
1368
|
+
outputs: args.outputs.map((output) => output.insertionRemittance === void 0 || !tags.has(output.outputIndex) ? output : {
|
|
1369
|
+
...output,
|
|
1370
|
+
insertionRemittance: {
|
|
1371
|
+
...output.insertionRemittance,
|
|
1372
|
+
tags: tags.get(output.outputIndex)
|
|
1373
|
+
}
|
|
1374
|
+
})
|
|
1375
|
+
};
|
|
1337
1376
|
}
|
|
1338
1377
|
/**
|
|
1339
1378
|
* Checks a received transaction: every input unlocks its source and every asset it involves is
|
|
@@ -1374,6 +1413,7 @@ var ColorPermissionModule = class {
|
|
|
1374
1413
|
lockingScript: source.lockingScript,
|
|
1375
1414
|
satoshis: source.satoshis
|
|
1376
1415
|
}])).has(outpoint)) throw new Error("BRC-207: the received output is not admitted and unspent in its asset overlay");
|
|
1416
|
+
return custody;
|
|
1377
1417
|
}
|
|
1378
1418
|
/** Checks a planned token action and adds its selected token inputs and per-asset change. */
|
|
1379
1419
|
async createAction(args, originator) {
|
|
@@ -1391,17 +1431,47 @@ var ColorPermissionModule = class {
|
|
|
1391
1431
|
const { inputs, changes } = await this.fund(reserved, plan, planned, explicit, handoff, originator);
|
|
1392
1432
|
sighashScopes(plan, new Set([...explicit.managed, ...inputs].map((input) => input.outpoint)));
|
|
1393
1433
|
const inputBEEF = await this.inputBeef(args.inputBEEF, inputs, originator);
|
|
1394
|
-
|
|
1395
|
-
return {
|
|
1434
|
+
const funded = {
|
|
1396
1435
|
...args,
|
|
1397
1436
|
inputs: orderInputs(explicit.inputs, tokenInputs(inputs), plan.inputPositions),
|
|
1398
1437
|
outputs: placeChange(outputs, changes, plan.changePositions),
|
|
1399
1438
|
...inputBEEF === void 0 ? {} : { inputBEEF }
|
|
1400
1439
|
};
|
|
1440
|
+
this.stage([...explicit.managed, ...inputs]);
|
|
1441
|
+
return funded;
|
|
1401
1442
|
} finally {
|
|
1402
1443
|
for (const outpoint of reserved) this.selected.delete(outpoint);
|
|
1403
1444
|
}
|
|
1404
1445
|
}
|
|
1446
|
+
/** Stages a funded action's managed inputs until its createAction comes back. */
|
|
1447
|
+
stage(inputs) {
|
|
1448
|
+
for (const input of inputs) this.staged.set(input.outpoint, input);
|
|
1449
|
+
}
|
|
1450
|
+
/**
|
|
1451
|
+
* Moves the staged inputs the returned transaction spends to its action reference. A result
|
|
1452
|
+
* without a signable transaction binds nothing; its named inputs are only unstaged.
|
|
1453
|
+
*/
|
|
1454
|
+
bindAction(result) {
|
|
1455
|
+
const signable = result.signableTransaction;
|
|
1456
|
+
const atomic = signable?.tx ?? result.tx;
|
|
1457
|
+
if (atomic === void 0) return;
|
|
1458
|
+
let tx;
|
|
1459
|
+
try {
|
|
1460
|
+
tx = Transaction.fromAtomicBEEF(atomic);
|
|
1461
|
+
} catch {
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
for (const input of tx.inputs) {
|
|
1465
|
+
const outpoint = inputOutpoint(input);
|
|
1466
|
+
const staged = this.staged.get(outpoint);
|
|
1467
|
+
if (staged === void 0) continue;
|
|
1468
|
+
this.staged.delete(outpoint);
|
|
1469
|
+
if (signable === void 0) continue;
|
|
1470
|
+
const records = this.bound.get(signable.reference) ?? [];
|
|
1471
|
+
records.push(staged);
|
|
1472
|
+
this.bound.set(signable.reference, records);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1405
1475
|
reserve(reserved, inputs) {
|
|
1406
1476
|
for (const input of inputs) {
|
|
1407
1477
|
this.selected.add(input.outpoint);
|
|
@@ -1433,7 +1503,8 @@ var ColorPermissionModule = class {
|
|
|
1433
1503
|
basket: sdk.specOpBasketPrefixOutputs,
|
|
1434
1504
|
tags: [COLOR_BASKET_NAMESPACE],
|
|
1435
1505
|
include: "locking scripts",
|
|
1436
|
-
includeCustomInstructions: true
|
|
1506
|
+
includeCustomInstructions: true,
|
|
1507
|
+
includeTags: true
|
|
1437
1508
|
}, originator);
|
|
1438
1509
|
const own = new Map(listed.outputs.filter((output) => outpoints.has(output.outpoint)).map((output) => [output.outpoint, output]));
|
|
1439
1510
|
const beef = args.inputBEEF === void 0 ? void 0 : parsed("inputBEEF", () => Beef.fromBinary(args.inputBEEF));
|
|
@@ -1446,9 +1517,15 @@ var ColorPermissionModule = class {
|
|
|
1446
1517
|
}
|
|
1447
1518
|
/** Counts one of this wallet's `p color` outputs once it is managed, admitted and unspent. */
|
|
1448
1519
|
async ownInput(explicit, output, originator) {
|
|
1449
|
-
if (!output.spendable) throw new Error("BRC-207: an explicit p color input is reserved by another action");
|
|
1450
|
-
const
|
|
1451
|
-
if (
|
|
1520
|
+
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");
|
|
1521
|
+
const untagged = await this.managedInput(output, originator);
|
|
1522
|
+
if (untagged === void 0) throw new Error("BRC-207: an explicit p color input is not a managed token output");
|
|
1523
|
+
const suffixTag = suffixTagIn(output.tags);
|
|
1524
|
+
if (suffixTag === void 0) throw new Error(`BRC-207: token output ${untagged.outpoint} was filed without its suffix tag and is not spendable through the module`);
|
|
1525
|
+
const managed = {
|
|
1526
|
+
...untagged,
|
|
1527
|
+
suffixTag
|
|
1528
|
+
};
|
|
1452
1529
|
await this.assertSupportedAsset(managed.assetId);
|
|
1453
1530
|
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");
|
|
1454
1531
|
explicit.managed.push(managed);
|
|
@@ -1537,7 +1614,8 @@ var ColorPermissionModule = class {
|
|
|
1537
1614
|
}
|
|
1538
1615
|
/**
|
|
1539
1616
|
* The action's deploy, which BRC-162 requires at output 0. An owned deploy keeps its `ISSUE`
|
|
1540
|
-
* key; its asset basket is assigned once the transaction
|
|
1617
|
+
* key and is tagged like every owned output; its asset basket is assigned once the transaction
|
|
1618
|
+
* ID is final.
|
|
1541
1619
|
*/
|
|
1542
1620
|
async deployOutput(args, output, token, remittance, planned, originator) {
|
|
1543
1621
|
if (output.basket !== void 0) throw new Error("BRC-207: a deploy output's basket is assigned once its transaction is signed");
|
|
@@ -1556,7 +1634,8 @@ var ColorPermissionModule = class {
|
|
|
1556
1634
|
await this.assertOwned(token, remittance, remittance.senderIdentityKey, originator);
|
|
1557
1635
|
return {
|
|
1558
1636
|
...output,
|
|
1559
|
-
customInstructions: await encryptMetadata(this.wallet, custodyInstructions(custodyOf(remittance)), originator)
|
|
1637
|
+
customInstructions: await encryptMetadata(this.wallet, custodyInstructions(custodyOf(remittance)), originator),
|
|
1638
|
+
tags: withSuffixTag(output.tags, remittance)
|
|
1560
1639
|
};
|
|
1561
1640
|
}
|
|
1562
1641
|
/** A token output of the action that this wallet keeps, placed in its asset's basket. */
|
|
@@ -1568,7 +1647,8 @@ var ColorPermissionModule = class {
|
|
|
1568
1647
|
return {
|
|
1569
1648
|
...output,
|
|
1570
1649
|
basket,
|
|
1571
|
-
customInstructions: custodyInstructions(custodyOf(remittance))
|
|
1650
|
+
customInstructions: custodyInstructions(custodyOf(remittance)),
|
|
1651
|
+
tags: withSuffixTag(output.tags, remittance)
|
|
1572
1652
|
};
|
|
1573
1653
|
}
|
|
1574
1654
|
/**
|
|
@@ -1730,40 +1810,43 @@ var ColorPermissionModule = class {
|
|
|
1730
1810
|
if (inputs.length === 0) return callerBEEF;
|
|
1731
1811
|
const beef = callerBEEF === void 0 ? new Beef() : parsed("inputBEEF", () => Beef.fromBinary(callerBEEF));
|
|
1732
1812
|
const baskets = /* @__PURE__ */ new Map();
|
|
1733
|
-
for (const input of inputs)
|
|
1813
|
+
for (const input of inputs) {
|
|
1814
|
+
if (input.sourceBEEF !== void 0) {
|
|
1815
|
+
mergeSource(beef, Beef.fromBinary(input.sourceBEEF), input.outpoint);
|
|
1816
|
+
continue;
|
|
1817
|
+
}
|
|
1734
1818
|
const basket = assetBasket(input.assetId, input.role);
|
|
1735
1819
|
baskets.set(basket, [...baskets.get(basket) ?? [], input]);
|
|
1736
|
-
}
|
|
1737
|
-
await eachInOrder(baskets, async ([basket,
|
|
1738
|
-
const sources = await this.sourceTransactions(basket,
|
|
1739
|
-
for (const input of
|
|
1820
|
+
}
|
|
1821
|
+
await eachInOrder(baskets, async ([basket, selected]) => {
|
|
1822
|
+
const sources = await this.sourceTransactions(basket, selected, originator);
|
|
1823
|
+
for (const input of selected) mergeSource(beef, sources, input.outpoint);
|
|
1740
1824
|
});
|
|
1741
1825
|
return beef.toBinary();
|
|
1742
1826
|
}
|
|
1743
1827
|
/**
|
|
1744
|
-
*
|
|
1745
|
-
*
|
|
1828
|
+
* The selected inputs of one basket with their entire transactions, in one listing by their
|
|
1829
|
+
* suffix tags. The listing must return exactly the selected outputs.
|
|
1746
1830
|
*/
|
|
1747
|
-
async sourceTransactions(basket,
|
|
1748
|
-
const
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
return beef;
|
|
1831
|
+
async sourceTransactions(basket, selected, originator) {
|
|
1832
|
+
const listing = await this.wallet.listOutputs({
|
|
1833
|
+
basket,
|
|
1834
|
+
tags: selected.map((input) => input.suffixTag),
|
|
1835
|
+
tagQueryMode: "any",
|
|
1836
|
+
include: "entire transactions",
|
|
1837
|
+
limit: selected.length
|
|
1838
|
+
}, originator);
|
|
1839
|
+
const wanted = new Set(selected.map((input) => input.outpoint));
|
|
1840
|
+
const returned = new Set(listing.outputs.map((output) => output.outpoint));
|
|
1841
|
+
const missing = [...wanted].filter((outpoint) => !returned.has(outpoint));
|
|
1842
|
+
const extra = [...returned].filter((outpoint) => !wanted.has(outpoint));
|
|
1843
|
+
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"}`);
|
|
1844
|
+
return listingBeef(listing);
|
|
1762
1845
|
}
|
|
1763
1846
|
/** One basket's token outputs of the asset, read from their locking scripts, excluding selected ones. */
|
|
1764
1847
|
async candidates(assetId, role, originator) {
|
|
1765
|
-
return (await this.listBasket(assetBasket(assetId, role), originator, {})).flatMap((output) => {
|
|
1766
|
-
if (this.selected.has(output.outpoint) || output.customInstructions === void 0) return [];
|
|
1848
|
+
return (await this.listBasket(assetBasket(assetId, role), originator, { includeTags: true })).flatMap((output) => {
|
|
1849
|
+
if (output.spendable === false || this.selected.has(output.outpoint) || this.staged.has(output.outpoint) || output.customInstructions === void 0 || !(output.tags ?? []).some(isDerivationSuffixTag)) return [];
|
|
1767
1850
|
const source = this.sourceOutput(output);
|
|
1768
1851
|
const token = tryReadTokenOutput(source.lockingScript, parseOutpoint(output.outpoint));
|
|
1769
1852
|
if (token?.assetId !== assetId || token.role !== role) return [];
|
|
@@ -1771,25 +1854,32 @@ var ColorPermissionModule = class {
|
|
|
1771
1854
|
...source,
|
|
1772
1855
|
token,
|
|
1773
1856
|
amount: token.amount,
|
|
1774
|
-
customInstructions: output.customInstructions
|
|
1857
|
+
customInstructions: output.customInstructions,
|
|
1858
|
+
tags: output.tags ?? []
|
|
1775
1859
|
}];
|
|
1776
1860
|
});
|
|
1777
1861
|
}
|
|
1778
1862
|
/** A chosen candidate with its custody, or undefined when its custody record is unreadable. */
|
|
1779
1863
|
async readableInput(candidate, originator) {
|
|
1780
1864
|
try {
|
|
1781
|
-
const { outpoint, lockingScript, satoshis, token, customInstructions } = candidate;
|
|
1782
|
-
|
|
1865
|
+
const { outpoint, lockingScript, satoshis, token, customInstructions, tags } = candidate;
|
|
1866
|
+
const input = await this.custodyInput({
|
|
1783
1867
|
outpoint,
|
|
1784
1868
|
lockingScript,
|
|
1785
1869
|
satoshis,
|
|
1786
1870
|
token
|
|
1787
1871
|
}, customInstructions, originator);
|
|
1872
|
+
const suffixTag = suffixTagIn(tags);
|
|
1873
|
+
if (input === void 0 || suffixTag === void 0) return void 0;
|
|
1874
|
+
return {
|
|
1875
|
+
...input,
|
|
1876
|
+
suffixTag
|
|
1877
|
+
};
|
|
1788
1878
|
} catch {
|
|
1789
1879
|
return;
|
|
1790
1880
|
}
|
|
1791
1881
|
}
|
|
1792
|
-
/** A `p color` basket output as a managed token input, or undefined when it is not one. */
|
|
1882
|
+
/** A `p color` basket output as a managed token input, before its tag, or undefined when it is not one. */
|
|
1793
1883
|
async managedInput(output, originator) {
|
|
1794
1884
|
const source = this.sourceOutput(output);
|
|
1795
1885
|
const token = readTokenOutput(source.lockingScript, parseOutpoint(output.outpoint));
|
|
@@ -1876,6 +1966,7 @@ var ColorPermissionModule = class {
|
|
|
1876
1966
|
role: "value",
|
|
1877
1967
|
amount,
|
|
1878
1968
|
custody: exact.custody,
|
|
1969
|
+
suffixTag: suffixTagOf(exact.custody),
|
|
1879
1970
|
sourceBEEF: signed.tx
|
|
1880
1971
|
};
|
|
1881
1972
|
}
|
|
@@ -1895,6 +1986,7 @@ var ColorPermissionModule = class {
|
|
|
1895
1986
|
satoshis: TOKEN_CARRIER_SATOSHIS,
|
|
1896
1987
|
basket: assetBasket(assetId, "value"),
|
|
1897
1988
|
customInstructions: custodyInstructions(custody),
|
|
1989
|
+
tags: [suffixTagOf(custody)],
|
|
1898
1990
|
outputDescription: "BRC-207 token change"
|
|
1899
1991
|
}
|
|
1900
1992
|
};
|
|
@@ -1912,27 +2004,20 @@ var ColorPermissionModule = class {
|
|
|
1912
2004
|
const tx = action.transaction;
|
|
1913
2005
|
const tokens = transactionTokens(tx, true);
|
|
1914
2006
|
assertTransactionOperations(plan, tokens);
|
|
1915
|
-
const managed = this.
|
|
2007
|
+
const managed = this.bound.get(action.reference) ?? [];
|
|
1916
2008
|
try {
|
|
1917
|
-
return await this.
|
|
2009
|
+
return await this.signBound(args, {
|
|
1918
2010
|
plan,
|
|
1919
2011
|
createArgs,
|
|
1920
2012
|
tx,
|
|
1921
2013
|
tokens
|
|
1922
2014
|
}, managed, originator);
|
|
1923
2015
|
} finally {
|
|
1924
|
-
|
|
2016
|
+
this.bound.delete(action.reference);
|
|
1925
2017
|
}
|
|
1926
2018
|
}
|
|
1927
|
-
/** The kept records of the module's inputs among the action's inputs. */
|
|
1928
|
-
heldInputs(createArgs) {
|
|
1929
|
-
return (createArgs.inputs ?? []).flatMap((input) => {
|
|
1930
|
-
const managed = this.held.get(input.outpoint);
|
|
1931
|
-
return managed === void 0 ? [] : [managed];
|
|
1932
|
-
});
|
|
1933
|
-
}
|
|
1934
2019
|
/** Rechecks, approves and signs the module's inputs of a funded action. */
|
|
1935
|
-
async
|
|
2020
|
+
async signBound(args, funded, managed, originator) {
|
|
1936
2021
|
const { plan, createArgs, tx, tokens } = funded;
|
|
1937
2022
|
const scopes = sighashScopes(plan, new Set(managed.map((input) => input.outpoint)));
|
|
1938
2023
|
await eachInOrder(new Set(tokens.consumed.map((input) => input.assetId)), async (assetId) => {
|
|
@@ -2202,6 +2287,7 @@ var ColorPermissionModule = class {
|
|
|
2202
2287
|
basket,
|
|
2203
2288
|
tags: filter.tags,
|
|
2204
2289
|
tagQueryMode: filter.tagQueryMode,
|
|
2290
|
+
includeTags: filter.includeTags,
|
|
2205
2291
|
include: "locking scripts",
|
|
2206
2292
|
includeCustomInstructions: true,
|
|
2207
2293
|
limit: LIST_PAGE_LIMIT,
|