@bopen-io/color-permission-module 0.1.0 → 0.1.1
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 +19 -3
- package/dist/index.d.mts +29 -4
- package/dist/index.mjs +91 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -57,7 +57,21 @@ 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: it reads the asset basket with entire
|
|
63
|
+
transactions a page of 10 outputs at a time, stops at the page that returns
|
|
64
|
+
the last selected transaction, and keeps only each selected transaction with
|
|
65
|
+
its proof or unproven ancestors. A selected output whose transaction the
|
|
66
|
+
wallet does not return in full fails the call, naming the outpoint. The
|
|
67
|
+
module keeps its record of each input it signs for (outpoint, script,
|
|
68
|
+
satoshis, asset and decrypted custody) from `createAction` until that
|
|
69
|
+
action's `signAction`, and signs from those records without listing the
|
|
70
|
+
wallet's outputs; a wallet may hide an action's reserved inputs from its
|
|
71
|
+
listings (wallet-toolbox's `auto` action-batch mode does). The records are
|
|
72
|
+
dropped when `signAction` signs or refuses, and on `dispose()`; a record of an
|
|
73
|
+
action aborted before `signAction` stays until the output is selected again
|
|
74
|
+
or `dispose()`. In
|
|
61
75
|
`signAction` it checks the funded transaction against the plan, asks the user
|
|
62
76
|
to approve it (`color_send`: every asset leg, satoshi totals and fee, the
|
|
63
77
|
signatures it releases), signs the token inputs with any `sighashTypes`
|
|
@@ -119,8 +133,10 @@ The module fails explicitly, rather than falling back, on:
|
|
|
119
133
|
|
|
120
134
|
Explicit inputs are supported: this wallet's `p color` outputs are signed by the
|
|
121
135
|
module, and foreign token inputs count only once their asset's overlay admits
|
|
122
|
-
them.
|
|
123
|
-
|
|
136
|
+
them. The caller supplies the source transactions of the inputs it names in
|
|
137
|
+
`inputBEEF`, as wallet-toolbox requires for explicit inputs; the module adds
|
|
138
|
+
them only for the inputs it selects. An input missing from `inputBEEF` is
|
|
139
|
+
resolved by the wallet and checked in `signAction`. `inputPositions` and token `changePositions` are honored; change
|
|
124
140
|
positions follow the requested outputs.
|
|
125
141
|
|
|
126
142
|
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, 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;
|
|
@@ -194,6 +194,8 @@ interface ManagedInput extends SourceOutput {
|
|
|
194
194
|
role: TokenRole;
|
|
195
195
|
amount: bigint;
|
|
196
196
|
custody: Custody;
|
|
197
|
+
/** The source transaction as BEEF, when the module made it (a handoff split). */
|
|
198
|
+
sourceBEEF?: BEEF;
|
|
197
199
|
}
|
|
198
200
|
type InventoryState = 'available' | 'reserved';
|
|
199
201
|
/** One asset's token units and authority outputs on one side of a transaction. */
|
|
@@ -347,10 +349,15 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
347
349
|
readonly capabilities: Readonly<Record<string, string>>;
|
|
348
350
|
/** Outputs a send in progress has selected, so a concurrent send does not select them too. */
|
|
349
351
|
protected readonly selected: Set<string>;
|
|
352
|
+
/**
|
|
353
|
+
* The managed inputs of each funded action, by outpoint, kept from createAction until its
|
|
354
|
+
* signAction so the module signs from its own records instead of listing the wallet's outputs.
|
|
355
|
+
*/
|
|
356
|
+
protected readonly held: Map<string, ManagedInput>;
|
|
350
357
|
protected readonly supportedAssets: Map<string, Promise<void>>;
|
|
351
358
|
protected identityKey?: Promise<string>;
|
|
352
359
|
constructor(options: ColorPermissionModuleOptions);
|
|
353
|
-
/** Clears in-memory selections and asset checks. */
|
|
360
|
+
/** Clears in-memory selections, kept input records and asset checks. */
|
|
354
361
|
dispose(): void;
|
|
355
362
|
/** Protocol- and basket-routed calls: key and signature operations and the read views. */
|
|
356
363
|
handleRequest(req: PermissionsModuleRequest, next: PermissionsModuleNext): Promise<unknown>;
|
|
@@ -461,6 +468,17 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
461
468
|
* does not report, as BTMS does when it selects, and returns the outputs it then admits.
|
|
462
469
|
*/
|
|
463
470
|
protected resubmitHeld(assetId: string, role: TokenRole, outputs: readonly SourceOutput[], originator: string): Promise<Set<string>>;
|
|
471
|
+
/**
|
|
472
|
+
* `callerBEEF` with the source transactions of the module's selected inputs merged in, each with
|
|
473
|
+
* its proof or unproven ancestors and nothing else. Inputs the caller named keep the BEEF the
|
|
474
|
+
* caller gave; the module adds none for them.
|
|
475
|
+
*/
|
|
476
|
+
protected inputBeef(callerBEEF: BEEF | undefined, inputs: readonly ManagedInput[], originator: string): Promise<BEEF | undefined>;
|
|
477
|
+
/**
|
|
478
|
+
* Reads one basket with entire transactions, a page at a time, until every input's source
|
|
479
|
+
* transaction has been returned, so no more BEEF is read than the selection needs.
|
|
480
|
+
*/
|
|
481
|
+
protected sourceTransactions(basket: string, inputs: readonly ManagedInput[], originator: string): Promise<Beef>;
|
|
464
482
|
/** One basket's token outputs of the asset, read from their locking scripts, excluding selected ones. */
|
|
465
483
|
protected candidates(assetId: string, role: TokenRole, originator: string): Promise<Candidate[]>;
|
|
466
484
|
/** A chosen candidate with its custody, or undefined when its custody record is unreadable. */
|
|
@@ -488,8 +506,15 @@ export declare class ColorPermissionModule implements PermissionsModule {
|
|
|
488
506
|
* `p color` outputs.
|
|
489
507
|
*/
|
|
490
508
|
protected signAction(args: SignActionArgs, action: PermissionsModuleAction | undefined, originator: string): Promise<SignActionArgs>;
|
|
491
|
-
/** The
|
|
492
|
-
protected
|
|
509
|
+
/** The kept records of the module's inputs among the action's inputs. */
|
|
510
|
+
protected heldInputs(createArgs: CreateActionArgs): ManagedInput[];
|
|
511
|
+
/** Rechecks, approves and signs the module's inputs of a funded action. */
|
|
512
|
+
protected signHeld(args: SignActionArgs, funded: {
|
|
513
|
+
plan: ColorPlan;
|
|
514
|
+
createArgs: CreateActionArgs;
|
|
515
|
+
tx: Transaction;
|
|
516
|
+
tokens: TransactionTokens;
|
|
517
|
+
}, managed: ManagedInput[], originator: string): Promise<SignActionArgs>;
|
|
493
518
|
/**
|
|
494
519
|
* Submits a signed token transaction to the topic of every asset among its inputs and outputs,
|
|
495
520
|
* and a deploy to the discovery topic.
|
package/dist/index.mjs
CHANGED
|
@@ -698,6 +698,8 @@ const DEFAULT_SIGHASH = TransactionSignature.SIGHASH_ALL | TransactionSignature.
|
|
|
698
698
|
const MAX_ENVELOPE_BYTES = 16777216;
|
|
699
699
|
const MAX_RECEIPT_INSTRUCTION_BYTES = 1e3;
|
|
700
700
|
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;
|
|
701
703
|
const SUBMIT_ATTEMPTS = 3;
|
|
702
704
|
const NOSEND_EXPIRY_LABEL_PREFIX = "p nosend expiry ";
|
|
703
705
|
const STATE_TAG_PREFIX = "color_state_";
|
|
@@ -727,6 +729,20 @@ function custodyOf(remittance) {
|
|
|
727
729
|
senderIdentityKey
|
|
728
730
|
};
|
|
729
731
|
}
|
|
732
|
+
/** Whether `beef` holds the transaction `txid` in full, not by txid alone. */
|
|
733
|
+
function holdsTransaction(beef, txid) {
|
|
734
|
+
const found = beef.findTxid(txid);
|
|
735
|
+
return found !== void 0 && !found.isTxidOnly;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Merges the source transaction of `outpoint` from `from` into `into` with only its own proof or
|
|
739
|
+
* unproven ancestors, so other transactions in `from` are left out.
|
|
740
|
+
*/
|
|
741
|
+
function mergeSource(into, from, outpoint) {
|
|
742
|
+
const { txid } = parseOutpoint(outpoint);
|
|
743
|
+
if (!holdsTransaction(from, txid)) throw new Error(`BRC-207: the wallet returned no source transaction for token input ${outpoint}`);
|
|
744
|
+
into.mergeBeef(from.toBinaryAtomic(txid));
|
|
745
|
+
}
|
|
730
746
|
function tokenInputs(inputs) {
|
|
731
747
|
return inputs.map((input) => ({
|
|
732
748
|
outpoint: input.outpoint,
|
|
@@ -1199,6 +1215,11 @@ var ColorPermissionModule = class {
|
|
|
1199
1215
|
capabilities = { "brc207-color": "5" };
|
|
1200
1216
|
/** Outputs a send in progress has selected, so a concurrent send does not select them too. */
|
|
1201
1217
|
selected = /* @__PURE__ */ new Set();
|
|
1218
|
+
/**
|
|
1219
|
+
* The managed inputs of each funded action, by outpoint, kept from createAction until its
|
|
1220
|
+
* signAction so the module signs from its own records instead of listing the wallet's outputs.
|
|
1221
|
+
*/
|
|
1222
|
+
held = /* @__PURE__ */ new Map();
|
|
1202
1223
|
supportedAssets = /* @__PURE__ */ new Map();
|
|
1203
1224
|
identityKey;
|
|
1204
1225
|
constructor(options) {
|
|
@@ -1209,9 +1230,10 @@ var ColorPermissionModule = class {
|
|
|
1209
1230
|
this.provider = options.provider;
|
|
1210
1231
|
this.requestTokenAccess = options.requestTokenAccess;
|
|
1211
1232
|
}
|
|
1212
|
-
/** Clears in-memory selections and asset checks. */
|
|
1233
|
+
/** Clears in-memory selections, kept input records and asset checks. */
|
|
1213
1234
|
dispose() {
|
|
1214
1235
|
this.selected.clear();
|
|
1236
|
+
this.held.clear();
|
|
1215
1237
|
this.supportedAssets.clear();
|
|
1216
1238
|
this.identityKey = void 0;
|
|
1217
1239
|
}
|
|
@@ -1368,10 +1390,13 @@ var ColorPermissionModule = class {
|
|
|
1368
1390
|
this.reserve(reserved, explicit.managed);
|
|
1369
1391
|
const { inputs, changes } = await this.fund(reserved, plan, planned, explicit, handoff, originator);
|
|
1370
1392
|
sighashScopes(plan, new Set([...explicit.managed, ...inputs].map((input) => input.outpoint)));
|
|
1393
|
+
const inputBEEF = await this.inputBeef(args.inputBEEF, inputs, originator);
|
|
1394
|
+
for (const input of [...explicit.managed, ...inputs]) this.held.set(input.outpoint, input);
|
|
1371
1395
|
return {
|
|
1372
1396
|
...args,
|
|
1373
1397
|
inputs: orderInputs(explicit.inputs, tokenInputs(inputs), plan.inputPositions),
|
|
1374
|
-
outputs: placeChange(outputs, changes, plan.changePositions)
|
|
1398
|
+
outputs: placeChange(outputs, changes, plan.changePositions),
|
|
1399
|
+
...inputBEEF === void 0 ? {} : { inputBEEF }
|
|
1375
1400
|
};
|
|
1376
1401
|
} finally {
|
|
1377
1402
|
for (const outpoint of reserved) this.selected.delete(outpoint);
|
|
@@ -1696,6 +1721,45 @@ var ColorPermissionModule = class {
|
|
|
1696
1721
|
});
|
|
1697
1722
|
return await this.spendable(assetId, outputs);
|
|
1698
1723
|
}
|
|
1724
|
+
/**
|
|
1725
|
+
* `callerBEEF` with the source transactions of the module's selected inputs merged in, each with
|
|
1726
|
+
* its proof or unproven ancestors and nothing else. Inputs the caller named keep the BEEF the
|
|
1727
|
+
* caller gave; the module adds none for them.
|
|
1728
|
+
*/
|
|
1729
|
+
async inputBeef(callerBEEF, inputs, originator) {
|
|
1730
|
+
if (inputs.length === 0) return callerBEEF;
|
|
1731
|
+
const beef = callerBEEF === void 0 ? new Beef() : parsed("inputBEEF", () => Beef.fromBinary(callerBEEF));
|
|
1732
|
+
const baskets = /* @__PURE__ */ new Map();
|
|
1733
|
+
for (const input of inputs) if (input.sourceBEEF === void 0) {
|
|
1734
|
+
const basket = assetBasket(input.assetId, input.role);
|
|
1735
|
+
baskets.set(basket, [...baskets.get(basket) ?? [], input]);
|
|
1736
|
+
} else mergeSource(beef, Beef.fromBinary(input.sourceBEEF), input.outpoint);
|
|
1737
|
+
await eachInOrder(baskets, async ([basket, held]) => {
|
|
1738
|
+
const sources = await this.sourceTransactions(basket, held, originator);
|
|
1739
|
+
for (const input of held) mergeSource(beef, sources, input.outpoint);
|
|
1740
|
+
});
|
|
1741
|
+
return beef.toBinary();
|
|
1742
|
+
}
|
|
1743
|
+
/**
|
|
1744
|
+
* Reads one basket with entire transactions, a page at a time, until every input's source
|
|
1745
|
+
* transaction has been returned, so no more BEEF is read than the selection needs.
|
|
1746
|
+
*/
|
|
1747
|
+
async sourceTransactions(basket, inputs, originator) {
|
|
1748
|
+
const txids = new Set(inputs.map((input) => parseOutpoint(input.outpoint).txid));
|
|
1749
|
+
const beef = new Beef();
|
|
1750
|
+
for (let offset = 0; [...txids].some((txid) => !holdsTransaction(beef, txid));) {
|
|
1751
|
+
const page = await this.wallet.listOutputs({
|
|
1752
|
+
basket,
|
|
1753
|
+
include: "entire transactions",
|
|
1754
|
+
limit: INPUT_BEEF_PAGE_LIMIT,
|
|
1755
|
+
offset
|
|
1756
|
+
}, originator);
|
|
1757
|
+
if (page.BEEF !== void 0) beef.mergeBeef(page.BEEF);
|
|
1758
|
+
offset += page.outputs.length;
|
|
1759
|
+
if (page.outputs.length < INPUT_BEEF_PAGE_LIMIT) break;
|
|
1760
|
+
}
|
|
1761
|
+
return beef;
|
|
1762
|
+
}
|
|
1699
1763
|
/** One basket's token outputs of the asset, read from their locking scripts, excluding selected ones. */
|
|
1700
1764
|
async candidates(assetId, role, originator) {
|
|
1701
1765
|
return (await this.listBasket(assetBasket(assetId, role), originator, {})).flatMap((output) => {
|
|
@@ -1782,6 +1846,7 @@ var ColorPermissionModule = class {
|
|
|
1782
1846
|
sighashTypes: []
|
|
1783
1847
|
}),
|
|
1784
1848
|
inputs: tokenInputs(selected),
|
|
1849
|
+
inputBEEF: await this.inputBeef(void 0, selected, originator),
|
|
1785
1850
|
outputs,
|
|
1786
1851
|
options: {
|
|
1787
1852
|
randomizeOutputs: false,
|
|
@@ -1810,7 +1875,8 @@ var ColorPermissionModule = class {
|
|
|
1810
1875
|
assetId,
|
|
1811
1876
|
role: "value",
|
|
1812
1877
|
amount,
|
|
1813
|
-
custody: exact.custody
|
|
1878
|
+
custody: exact.custody,
|
|
1879
|
+
sourceBEEF: signed.tx
|
|
1814
1880
|
};
|
|
1815
1881
|
}
|
|
1816
1882
|
/** A fresh wallet-owned value output in the asset's basket, keyed to this wallet. */
|
|
@@ -1846,7 +1912,28 @@ var ColorPermissionModule = class {
|
|
|
1846
1912
|
const tx = action.transaction;
|
|
1847
1913
|
const tokens = transactionTokens(tx, true);
|
|
1848
1914
|
assertTransactionOperations(plan, tokens);
|
|
1849
|
-
const managed =
|
|
1915
|
+
const managed = this.heldInputs(createArgs);
|
|
1916
|
+
try {
|
|
1917
|
+
return await this.signHeld(args, {
|
|
1918
|
+
plan,
|
|
1919
|
+
createArgs,
|
|
1920
|
+
tx,
|
|
1921
|
+
tokens
|
|
1922
|
+
}, managed, originator);
|
|
1923
|
+
} finally {
|
|
1924
|
+
for (const input of managed) this.held.delete(input.outpoint);
|
|
1925
|
+
}
|
|
1926
|
+
}
|
|
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
|
+
/** Rechecks, approves and signs the module's inputs of a funded action. */
|
|
1935
|
+
async signHeld(args, funded, managed, originator) {
|
|
1936
|
+
const { plan, createArgs, tx, tokens } = funded;
|
|
1850
1937
|
const scopes = sighashScopes(plan, new Set(managed.map((input) => input.outpoint)));
|
|
1851
1938
|
await eachInOrder(new Set(tokens.consumed.map((input) => input.assetId)), async (assetId) => {
|
|
1852
1939
|
const assetInputs = tokens.consumed.filter((input) => input.assetId === assetId);
|
|
@@ -1862,17 +1949,6 @@ var ColorPermissionModule = class {
|
|
|
1862
1949
|
}
|
|
1863
1950
|
};
|
|
1864
1951
|
}
|
|
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
1952
|
/**
|
|
1877
1953
|
* Submits a signed token transaction to the topic of every asset among its inputs and outputs,
|
|
1878
1954
|
* and a deploy to the discovery topic.
|