@easy1staking/cip113-sdk-ts 0.3.1 → 0.4.0
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 +20 -0
- package/blueprints/standard/v0.3.0/UPSTREAM_PIN.json +30 -0
- package/blueprints/standard/v0.5.0-alpha.2/UPSTREAM_PIN.json +33 -0
- package/blueprints/standard/v0.5.0-alpha.2/plutus.json +1010 -0
- package/blueprints/substandards/dummy/v0.1.0/UPSTREAM_PIN.json +29 -0
- package/blueprints/substandards/dummy/v0.2.0/UPSTREAM_PIN.json +36 -0
- package/blueprints/substandards/dummy/v0.2.0/plutus.json +84 -0
- package/blueprints/substandards/freeze-and-seize/v0.1.0/UPSTREAM_PIN.json +35 -0
- package/dist/core/cip171.d.ts +149 -0
- package/dist/core/cip171.d.ts.map +1 -0
- package/dist/core/cip171.js +284 -0
- package/dist/core/cip171.js.map +1 -0
- package/dist/core/evo-utils.d.ts +125 -17
- package/dist/core/evo-utils.d.ts.map +1 -1
- package/dist/core/evo-utils.js +149 -21
- package/dist/core/evo-utils.js.map +1 -1
- package/dist/core/ledger-order.d.ts +109 -0
- package/dist/core/ledger-order.d.ts.map +1 -0
- package/dist/core/ledger-order.js +185 -0
- package/dist/core/ledger-order.js.map +1 -0
- package/dist/core/registry.d.ts +16 -3
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +18 -9
- package/dist/core/registry.js.map +1 -1
- package/dist/index.d.ts +26 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -16
- package/dist/index.js.map +1 -1
- package/dist/standard/blueprint.d.ts +48 -1
- package/dist/standard/blueprint.d.ts.map +1 -1
- package/dist/standard/blueprint.js +71 -5
- package/dist/standard/blueprint.js.map +1 -1
- package/dist/standard/scripts.d.ts +76 -27
- package/dist/standard/scripts.d.ts.map +1 -1
- package/dist/standard/scripts.js +177 -49
- package/dist/standard/scripts.js.map +1 -1
- package/dist/substandards/dummy/index.d.ts.map +1 -1
- package/dist/substandards/dummy/index.js +443 -26
- package/dist/substandards/dummy/index.js.map +1 -1
- package/dist/substandards/freeze-and-seize/index.d.ts.map +1 -1
- package/dist/substandards/freeze-and-seize/index.js +283 -41
- package/dist/substandards/freeze-and-seize/index.js.map +1 -1
- package/dist/substandards/interface.d.ts +93 -1
- package/dist/substandards/interface.d.ts.map +1 -1
- package/dist/types.d.ts +87 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -1
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { Address as EvoAddress, Assets, Data, Transaction, TransactionHash as EvoTransactionHash, TransactionInput as EvoTransactionInput, } from "@evolution-sdk/evolution";
|
|
9
9
|
import { sortTxInputs, findRefInputIndex, findRegistryNode, findCoveringNode, utxoToTxInput, } from "../../core/registry.js";
|
|
10
|
-
import { buildEvoScript, scriptAddress, rewardAddress, baseAddress, stakingCredentialHash, paymentCredentialHash, stringToHex, MAX_NEXT, voidData, registryNodeDatum, blacklistNodeDatum,
|
|
10
|
+
import { buildEvoScript, scriptAddress, rewardAddress, baseAddress, stakingCredentialHash, paymentCredentialHash, stringToHex, MAX_NEXT, voidData, registryNodeDatum, decodeRegistryNode, blacklistNodeDatum, mintingProofOutputIndex, mintingProofRefInput, registryInsertRedeemer, blacklistInitRedeemer, blacklistAddRedeemer, blacklistRemoveRedeemer, extractConstrBytesField, getInlineDatum, utxoUnitQty, utxoLovelace, utxoTxHash, utxoOutputIndex, outputAssets, mintAssetsFromMap, REGISTRY_NODE_MIN_ADA, Credential, KeyHash, InlineDatum, labeledAssetName, buildCIP68FTDatum, } from "../../core/evo-utils.js";
|
|
11
|
+
import { baseSpendRedeemer, transferRedeemer, thirdPartyRedeemer, withdrawalIndexOf, } from "../../core/ledger-order.js";
|
|
11
12
|
import { createFESScripts } from "./scripts.js";
|
|
12
13
|
// ---------------------------------------------------------------------------
|
|
13
14
|
// Helpers
|
|
@@ -16,6 +17,20 @@ import { createFESScripts } from "./scripts.js";
|
|
|
16
17
|
* Build the transaction and extract unsigned CBOR + txHash.
|
|
17
18
|
* Works with both ReadOnlyClient (TransactionResultBase) and SigningClient (SignBuilder).
|
|
18
19
|
*/
|
|
20
|
+
/**
|
|
21
|
+
* The evaluator this plugin builds with, set by `init()`.
|
|
22
|
+
*
|
|
23
|
+
* Module-scoped rather than threaded through nine call sites. Without it every
|
|
24
|
+
* script failure in this substandard surfaces as Kupmios's bare
|
|
25
|
+
* "evaluateTx failed", naming neither the failing validator nor the reason —
|
|
26
|
+
* which is the difference between a diagnosis and a guess, and it cost a cycle
|
|
27
|
+
* here exactly as it did in `dummy` before the same fix.
|
|
28
|
+
*
|
|
29
|
+
* ⚠ Shared across instances of this plugin in one process. Acceptable because
|
|
30
|
+
* it only affects DIAGNOSTICS, never the transaction built; if that ever stops
|
|
31
|
+
* being true, thread it through instead.
|
|
32
|
+
*/
|
|
33
|
+
let sharedEvaluator;
|
|
19
34
|
async function buildAndSerialize(
|
|
20
35
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
21
36
|
builder, changeAddress, availableUtxos, passAdditionalUtxos = false) {
|
|
@@ -23,6 +38,9 @@ builder, changeAddress, availableUtxos, passAdditionalUtxos = false) {
|
|
|
23
38
|
const buildOpts = {
|
|
24
39
|
changeAddress: EvoAddress.fromBech32(changeAddress),
|
|
25
40
|
};
|
|
41
|
+
if (sharedEvaluator) {
|
|
42
|
+
buildOpts.evaluator = sharedEvaluator;
|
|
43
|
+
}
|
|
26
44
|
if (availableUtxos) {
|
|
27
45
|
buildOpts.availableUtxos = availableUtxos;
|
|
28
46
|
}
|
|
@@ -42,6 +60,35 @@ builder, changeAddress, availableUtxos, passAdditionalUtxos = false) {
|
|
|
42
60
|
}
|
|
43
61
|
return { cbor, txHash, chainAvailable, _signBuilder: result };
|
|
44
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Wallet UTxOs that are SAFE TO SPEND.
|
|
65
|
+
*
|
|
66
|
+
* A deployment's reference scripts live in outputs somewhere on chain, and if
|
|
67
|
+
* that somewhere is the operator's own wallet — which is exactly what the
|
|
68
|
+
* devnet harness does — then `getUtxos(wallet)` hands them back alongside
|
|
69
|
+
* ordinary funds. Spending one DESTROYS protocol infrastructure: the
|
|
70
|
+
* `script_ref` is not carried into the change output, so the reference input
|
|
71
|
+
* every later operation depends on simply ceases to exist.
|
|
72
|
+
*
|
|
73
|
+
* MEASURED, not theorised: `seize` consumed the deployment's `third_party`
|
|
74
|
+
* reference-script UTxO this way, which is why it appeared to work at all —
|
|
75
|
+
* a spent input's reference script counts as supplied (CIP-33) — and why the
|
|
76
|
+
* next run failed with 3011.
|
|
77
|
+
*/
|
|
78
|
+
function spendableWalletUtxos(walletUtxos, deployment) {
|
|
79
|
+
const reserved = new Set([
|
|
80
|
+
deployment.programmableBaseRefInput,
|
|
81
|
+
deployment.transferRefInput,
|
|
82
|
+
deployment.thirdPartyRefInput,
|
|
83
|
+
deployment.unfrackingRefInput,
|
|
84
|
+
]
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.map((r) => `${r.txHash}#${r.outputIndex}`));
|
|
87
|
+
return walletUtxos.filter((u) => {
|
|
88
|
+
const i = utxoToTxInput(u);
|
|
89
|
+
return !reserved.has(`${i.txHash}#${i.outputIndex}`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
45
92
|
function selectUtxosForAmount(utxos, unit, requiredAmount) {
|
|
46
93
|
const selected = [];
|
|
47
94
|
let total = 0n;
|
|
@@ -62,7 +109,9 @@ function selectUtxosForAmount(utxos, unit, requiredAmount) {
|
|
|
62
109
|
/** Find protocol params UTxO by searching for the protocol params NFT */
|
|
63
110
|
async function findProtocolParamsUtxo(client, networkId, deployment) {
|
|
64
111
|
const ppUnit = deployment.protocolParams.policyId + stringToHex("ProtocolParams");
|
|
65
|
-
const addr = EvoAddress.fromBech32(
|
|
112
|
+
const addr = EvoAddress.fromBech32(
|
|
113
|
+
// 0.5.x: params NFT lives at coordination_spend, not always_fail.
|
|
114
|
+
scriptAddress(networkId, deployment.coordination.scriptHash));
|
|
66
115
|
const utxos = await client.getUtxosWithUnit(addr, ppUnit);
|
|
67
116
|
if (utxos.length > 0)
|
|
68
117
|
return utxos[0];
|
|
@@ -114,7 +163,27 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
114
163
|
version: "0.1.0",
|
|
115
164
|
blueprint: config.blueprint,
|
|
116
165
|
init(context) {
|
|
166
|
+
// ---------------------------------------------------------------------
|
|
167
|
+
// MIGRATION STATE (W-E). Read this before assuming an operation works.
|
|
168
|
+
// ---------------------------------------------------------------------
|
|
169
|
+
//
|
|
170
|
+
// The blanket refusal that D-14 installed is GONE: `register`, `mint`,
|
|
171
|
+
// `transfer`, `initCompliance`, `freeze` and `unfreeze` are migrated to
|
|
172
|
+
// CIP-113 0.5.0-alpha.2 and this plugin initialises.
|
|
173
|
+
//
|
|
174
|
+
// `seize` and `burn` are migrated too (W-E S-5): both take the
|
|
175
|
+
// THIRD-PARTY route, which #110 moved off the deleted `ThirdPartyAct` and
|
|
176
|
+
// onto the standalone `third_party` validator's withdraw-0 with a
|
|
177
|
+
// ThirdPartyRedeemer, plus BaseSpendRedeemer(SpendViaThirdParty) on every
|
|
178
|
+
// programmable input.
|
|
179
|
+
//
|
|
180
|
+
// ⚠ The seize/burn paths still need re-validating against upstream #79
|
|
181
|
+
// (UTxO contamination), #80 (issuance delegation scope) and #115 (datum
|
|
182
|
+
// hashes and reference scripts banned on programmable outputs). Those are
|
|
183
|
+
// SEMANTIC changes with no signature to catch them, so a green devnet run
|
|
184
|
+
// is necessary and not sufficient.
|
|
117
185
|
ctx = context;
|
|
186
|
+
sharedEvaluator = ctx.evaluator;
|
|
118
187
|
networkId = ctx.client.chain.id;
|
|
119
188
|
const { adminPkh, assetName, blacklistNodePolicyId, blacklistInitTxInput } = config.deployment;
|
|
120
189
|
const fes = createFESScripts(config.blueprint);
|
|
@@ -140,7 +209,12 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
140
209
|
const { feePayerAddress, assetName, quantity, recipientAddress } = params;
|
|
141
210
|
const recipient = recipientAddress || feePayerAddress;
|
|
142
211
|
const chainedUtxos = (params.chainedUtxos ?? []);
|
|
143
|
-
|
|
212
|
+
// Already hex: `assetName` is raw asset-name HEX at every API boundary in
|
|
213
|
+
// this SDK. This previously ran stringToHex over it, double-encoding any
|
|
214
|
+
// caller who followed the convention — and silently producing a DIFFERENT
|
|
215
|
+
// token, under a policy derived from a different issuer_admin, so nothing
|
|
216
|
+
// failed; the tokens simply were not where anyone looked for them.
|
|
217
|
+
const assetNameHex = assetName;
|
|
144
218
|
const hasCIP68 = !!params.cip68Metadata;
|
|
145
219
|
const client = ctx.client;
|
|
146
220
|
// When CIP-68 is enabled, prefix asset names with CIP-67 labels
|
|
@@ -162,26 +236,41 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
162
236
|
const protocolParamsUtxo = await findProtocolParamsUtxo(client, networkId, ctx.deployment);
|
|
163
237
|
const issuanceCborHexUtxo = await findIssuanceCborHexUtxo(client, networkId, ctx.deployment);
|
|
164
238
|
// 3. Build datums
|
|
165
|
-
|
|
166
|
-
|
|
239
|
+
// UNREACHABLE — this plugin refuses at init (T-D09). Retained, and made to
|
|
240
|
+
// typecheck against the 7-field layout, so the freeze-and-seize epic gets a
|
|
241
|
+
// readable diff instead of a rewrite from memory.
|
|
242
|
+
//
|
|
243
|
+
// The index-based reads that used to be here (2/3/4) were silently wrong
|
|
244
|
+
// under the 7-field layout. Replaced with decodeRegistryNode, which is
|
|
245
|
+
// positional-safe and fails loudly on a short record.
|
|
246
|
+
if (!coveringDatum) {
|
|
247
|
+
throw new Error("register: the covering registry node has no inline datum. The directory " +
|
|
248
|
+
"cannot be traversed without it — a node with no datum is malformed, not empty.");
|
|
249
|
+
}
|
|
250
|
+
const covering = decodeRegistryNode(coveringDatum);
|
|
167
251
|
const updatedCoveringDatum = registryNodeDatum({
|
|
252
|
+
...covering,
|
|
168
253
|
key: coveringKey,
|
|
169
254
|
next: scripts.tokenPolicyId,
|
|
170
|
-
transferLogicScript: coveringTransferCred,
|
|
171
|
-
thirdPartyTransferLogicScript: coveringThirdPartyCred,
|
|
172
|
-
globalStateCs: extractConstrBytesField(coveringDatum, 4) ?? "",
|
|
173
255
|
});
|
|
174
256
|
const newRegistryNodeDatum = registryNodeDatum({
|
|
175
257
|
key: scripts.tokenPolicyId,
|
|
176
258
|
next: coveringNext,
|
|
259
|
+
mintingLogicScript: { type: "script", hash: scripts.issuerAdmin.hash },
|
|
177
260
|
transferLogicScript: { type: "script", hash: scripts.transfer.hash },
|
|
178
261
|
thirdPartyTransferLogicScript: { type: "script", hash: scripts.issuerAdmin.hash },
|
|
262
|
+
// ⚠ UNASSIGNED, NOT DECIDED. Upstream added an unfracking delegate per
|
|
263
|
+
// registry node; freeze-and-seize has no unfracking concept and the
|
|
264
|
+
// choice belongs to its own epic. Pointing it at issuerAdmin keeps the
|
|
265
|
+
// shape valid; it is NOT a design decision and is never emitted, because
|
|
266
|
+
// init() throws before any of this runs.
|
|
267
|
+
unfrackingLogicScript: { type: "script", hash: scripts.issuerAdmin.hash },
|
|
179
268
|
globalStateCs: "",
|
|
180
269
|
});
|
|
181
270
|
// 4. Build redeemers — registry output index shifts when CIP-68 adds an extra output
|
|
182
271
|
const registryOutputIndex = hasCIP68 ? 3 : 2;
|
|
183
|
-
const issuanceRedeemer =
|
|
184
|
-
const registryMintRedeemer = registryInsertRedeemer(scripts.issuanceMint.hash, scripts.issuerAdmin.hash);
|
|
272
|
+
const issuanceRedeemer = mintingProofOutputIndex(registryOutputIndex);
|
|
273
|
+
const registryMintRedeemer = registryInsertRedeemer(scripts.issuanceMint.hash, { type: "script", hash: scripts.issuerAdmin.hash });
|
|
185
274
|
const tokenDatum = voidData();
|
|
186
275
|
// 5. Determine if chaining from initCompliance
|
|
187
276
|
const useChaining = chainedUtxos.length > 0;
|
|
@@ -233,10 +322,19 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
233
322
|
assets: outputAssets(utxoLovelace(coveringNodeUtxo), coveringNodeTokenMap),
|
|
234
323
|
datum: new InlineDatum.InlineDatum({ data: updatedCoveringDatum }),
|
|
235
324
|
});
|
|
236
|
-
// New registry node (output 2 or 3)
|
|
325
|
+
// New registry node (output 2 or 3).
|
|
326
|
+
//
|
|
327
|
+
// 3 ADA, not 2: the RegistryNode datum is SEVEN fields in 0.5.x and
|
|
328
|
+
// min-UTxO scales with serialised output size. MEASURED at 2,038,630 for
|
|
329
|
+
// a node of this shape — the inherited 2,000,000 was sized for the
|
|
330
|
+
// five-field datum, and the ledger reports the shortfall as
|
|
331
|
+
// "insufficient Ada" with a number, never as "your datum grew".
|
|
332
|
+
// (The covering-node output above needs no change: it carries the
|
|
333
|
+
// covering UTxO's OWN lovelace forward, so it inherits whatever the
|
|
334
|
+
// bootstrap funded the origin with.)
|
|
237
335
|
tx = tx.payToAddress({
|
|
238
336
|
address: EvoAddress.fromBech32(registrySpendAddr),
|
|
239
|
-
assets: outputAssets(
|
|
337
|
+
assets: outputAssets(REGISTRY_NODE_MIN_ADA, new Map([[registryNftUnit, 1n]])),
|
|
240
338
|
datum: new InlineDatum.InlineDatum({ data: newRegistryNodeDatum }),
|
|
241
339
|
});
|
|
242
340
|
// Reference inputs
|
|
@@ -287,7 +385,7 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
287
385
|
const sortedRefInputs = sortTxInputs([regRef]);
|
|
288
386
|
const registryRefIdx = findRefInputIndex(sortedRefInputs, regRef);
|
|
289
387
|
// 3. Build redeemers
|
|
290
|
-
const issuanceRedeemer =
|
|
388
|
+
const issuanceRedeemer = mintingProofRefInput(registryRefIdx);
|
|
291
389
|
const tokenDatum = voidData();
|
|
292
390
|
// 4. Build PLB address
|
|
293
391
|
const plbHash = ctx.standardScripts.programmableLogicBase.hash;
|
|
@@ -297,7 +395,8 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
297
395
|
// 6. Build transaction
|
|
298
396
|
const tokenAssets = mintAssetsFromMap(new Map([[unit, quantity]]));
|
|
299
397
|
let tx = client.newTx();
|
|
300
|
-
|
|
398
|
+
const spendableUtxos = spendableWalletUtxos(walletUtxos, ctx.deployment);
|
|
399
|
+
tx = tx.collectFrom({ inputs: spendableUtxos.slice(0, 2) });
|
|
301
400
|
tx = tx.withdraw({
|
|
302
401
|
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(scripts.issuerAdmin.hash, "hex"))),
|
|
303
402
|
amount: 0n,
|
|
@@ -313,7 +412,7 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
313
412
|
tx = tx.attachScript({ script: buildEvoScript(scripts.issuerAdmin.compiledCode) });
|
|
314
413
|
tx = tx.attachScript({ script: buildEvoScript(scripts.issuanceMint.compiledCode) });
|
|
315
414
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(config.deployment.adminPkh) });
|
|
316
|
-
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress,
|
|
415
|
+
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress, spendableUtxos);
|
|
317
416
|
return { cbor, txHash, tokenPolicyId, _signBuilder };
|
|
318
417
|
},
|
|
319
418
|
// ====================================================================
|
|
@@ -349,8 +448,33 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
349
448
|
const sortedRefInputs = sortTxInputs(allRefInputRefs);
|
|
350
449
|
const registryIdx = findRefInputIndex(sortedRefInputs, utxoToTxInput(registryUtxo));
|
|
351
450
|
// 4. Build redeemers
|
|
352
|
-
const issuanceRedeemer =
|
|
353
|
-
const
|
|
451
|
+
const issuanceRedeemer = mintingProofRefInput(registryIdx);
|
|
452
|
+
const paramsIdx = findRefInputIndex(sortedRefInputs, utxoToTxInput(protocolParamsUtxo));
|
|
453
|
+
// 0.5.x third-party route. `ThirdPartyAct` was DELETED by #110: the
|
|
454
|
+
// administrative path is now the standalone `third_party` validator, and
|
|
455
|
+
// programmable_logic_base dispatches to it via SpendViaThirdParty — so
|
|
456
|
+
// this transaction never loads the `transfer` reference script at all.
|
|
457
|
+
//
|
|
458
|
+
// The withdrawal set is BOTH scripts: the framework delegate, and the
|
|
459
|
+
// issuer authority the registry node names in
|
|
460
|
+
// `third_party_transfer_logic_script`. `third_party` requires the latter
|
|
461
|
+
// by name — it is the only thing standing between a holder's tokens and
|
|
462
|
+
// anyone who wants them, and for FES it is a real issuer-admin check
|
|
463
|
+
// rather than dummy's unconditional one.
|
|
464
|
+
const thirdPartyKey = {
|
|
465
|
+
hash: ctx.standardScripts.thirdParty.hash,
|
|
466
|
+
isScript: true,
|
|
467
|
+
};
|
|
468
|
+
const issuerAuthorityKey = { hash: scripts.issuerAdmin.hash, isScript: true };
|
|
469
|
+
const thirdPartyWdrlIdx = withdrawalIndexOf([thirdPartyKey, issuerAuthorityKey], thirdPartyKey);
|
|
470
|
+
// outputs_start_idx = 0: `third_party` PAIRS each programmable input with
|
|
471
|
+
// the NEXT output (same address, datum and reference script, lovelace
|
|
472
|
+
// ratcheting up) and reads the seized amount as the DELTA. Destination
|
|
473
|
+
// outputs must therefore sit AMONG THE LEADING ones it skips.
|
|
474
|
+
// See docs/api-reference.md — getting this backwards fails with an EMPTY
|
|
475
|
+
// TRACE LIST, because it is a structural expect and not a traced check.
|
|
476
|
+
const plgRedeemer = thirdPartyRedeemer(paramsIdx, registryIdx, 0);
|
|
477
|
+
const plbSpendRedeemer = baseSpendRedeemer("THIRD_PARTY", paramsIdx, thirdPartyWdrlIdx);
|
|
354
478
|
const tokenDatum = voidData();
|
|
355
479
|
// 5. Compute remaining assets (remove burned token's policy)
|
|
356
480
|
const remainingTokens = new Map();
|
|
@@ -370,31 +494,37 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
370
494
|
const walletUtxos = await client.getUtxos(EvoAddress.fromBech32(feePayerAddress));
|
|
371
495
|
// 8. Build transaction
|
|
372
496
|
let tx = client.newTx();
|
|
373
|
-
|
|
374
|
-
tx = tx.collectFrom({ inputs:
|
|
497
|
+
const spendableUtxos = spendableWalletUtxos(walletUtxos, ctx.deployment);
|
|
498
|
+
tx = tx.collectFrom({ inputs: spendableUtxos.slice(0, 2) });
|
|
499
|
+
tx = tx.collectFrom({ inputs: [utxoToBurn], redeemer: plbSpendRedeemer });
|
|
375
500
|
tx = tx.withdraw({
|
|
376
501
|
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(scripts.issuerAdmin.hash, "hex"))),
|
|
377
502
|
amount: 0n,
|
|
378
503
|
redeemer: voidData(),
|
|
379
504
|
});
|
|
380
505
|
tx = tx.withdraw({
|
|
381
|
-
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.
|
|
506
|
+
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.thirdParty.hash, "hex"))),
|
|
382
507
|
amount: 0n,
|
|
383
508
|
redeemer: plgRedeemer,
|
|
384
509
|
});
|
|
385
510
|
tx = tx.payToAddress({
|
|
386
511
|
address: utxoToBurn.address,
|
|
387
512
|
assets: outputAssets(utxoLovelace(utxoToBurn), remainingTokens.size > 0 ? remainingTokens : undefined),
|
|
388
|
-
|
|
513
|
+
// The PAIRED continuation: `third_party` requires it to preserve the
|
|
514
|
+
// input's ADDRESS, DATUM and REFERENCE SCRIPT exactly. Carry the input's
|
|
515
|
+
// own datum rather than a fresh void one — they happen to be equal today
|
|
516
|
+
// because every programmable output here is void-datumed, and that is a
|
|
517
|
+
// coincidence rather than a guarantee.
|
|
518
|
+
datum: new InlineDatum.InlineDatum({ data: getInlineDatum(utxoToBurn) ?? tokenDatum }),
|
|
389
519
|
});
|
|
390
520
|
tx = tx.mintAssets({ assets: burnAssets, redeemer: issuanceRedeemer });
|
|
391
521
|
tx = tx.readFrom({ referenceInputs: [protocolParamsUtxo, registryUtxo] });
|
|
392
522
|
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.programmableLogicBase.compiledCode) });
|
|
393
|
-
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.
|
|
523
|
+
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.thirdParty.compiledCode) });
|
|
394
524
|
tx = tx.attachScript({ script: buildEvoScript(scripts.issuerAdmin.compiledCode) });
|
|
395
525
|
tx = tx.attachScript({ script: buildEvoScript(scripts.issuanceMint.compiledCode) });
|
|
396
526
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(config.deployment.adminPkh) });
|
|
397
|
-
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress,
|
|
527
|
+
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress, spendableUtxos);
|
|
398
528
|
return { cbor, txHash, _signBuilder };
|
|
399
529
|
},
|
|
400
530
|
// ====================================================================
|
|
@@ -451,18 +581,56 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
451
581
|
const sortedRefInputs = sortTxInputs(allRefInputRefs);
|
|
452
582
|
const proofIndices = selected.map(() => findRefInputIndex(sortedRefInputs, utxoToTxInput(proofUtxos[0])));
|
|
453
583
|
const registryIdx = findRefInputIndex(sortedRefInputs, utxoToTxInput(registryUtxo));
|
|
584
|
+
const paramsIdx = findRefInputIndex(sortedRefInputs, utxoToTxInput(protocolParamsUtxo));
|
|
454
585
|
// 8. Get sender's wallet UTxOs
|
|
455
586
|
const senderWalletUtxos = await client.getUtxos(EvoAddress.fromBech32(senderAddress));
|
|
456
|
-
// 9. Build redeemers
|
|
587
|
+
// 9. Build redeemers — 0.5.x shapes.
|
|
588
|
+
//
|
|
589
|
+
// The withdrawal set must be COMPLETE and in LEDGER order: script
|
|
590
|
+
// credentials before key credentials, bytewise within each. This
|
|
591
|
+
// transaction carries two, both scripts — the framework's `transfer`
|
|
592
|
+
// delegate and this substandard's own transfer logic.
|
|
593
|
+
const coreTransferKey = {
|
|
594
|
+
hash: ctx.standardScripts.transfer.hash,
|
|
595
|
+
isScript: true,
|
|
596
|
+
};
|
|
597
|
+
const fesLogicKey = { hash: scripts.transfer.hash, isScript: true };
|
|
598
|
+
const transferWdrlIdx = withdrawalIndexOf([coreTransferKey, fesLogicKey], coreTransferKey);
|
|
599
|
+
if (process.env.DUMP_TX_STRUCTURE) {
|
|
600
|
+
// Computed indices, printed beside the artefact's own order so the two
|
|
601
|
+
// can be COMPARED rather than reasoned about. A structural expect in
|
|
602
|
+
// programmable_logic_base fails with an empty trace list, so this is the
|
|
603
|
+
// only way to see which index is wrong.
|
|
604
|
+
// eslint-disable-next-line no-console
|
|
605
|
+
console.error("=== FES transfer COMPUTED ===\n" +
|
|
606
|
+
` refs (sorted): ${sortedRefInputs.map((r) => `${r.txHash.slice(0, 8)}#${r.outputIndex}`).join(" ")}\n` +
|
|
607
|
+
` paramsIdx=${paramsIdx} -> ${sortedRefInputs[paramsIdx]?.txHash?.slice(0, 8)}#${sortedRefInputs[paramsIdx]?.outputIndex}\n` +
|
|
608
|
+
` registryIdx=${registryIdx}\n` +
|
|
609
|
+
` coreTransfer=${ctx.standardScripts.transfer.hash}\n` +
|
|
610
|
+
` fesLogic =${scripts.transfer.hash}\n` +
|
|
611
|
+
` transferWdrlIdx=${transferWdrlIdx}\n` +
|
|
612
|
+
"=== END COMPUTED ===");
|
|
613
|
+
}
|
|
457
614
|
const fesTransferRedeemer = Data.list(proofIndices.map((idx) => Data.constr(0n, [Data.int(BigInt(idx))])));
|
|
458
|
-
|
|
459
|
-
|
|
615
|
+
// TransferRedeemer { params_idx, proofs } — params_idx was PREPENDED by
|
|
616
|
+
// #109; an old TransferAct encoder's bytes are not compatible.
|
|
617
|
+
const plgRedeemer = transferRedeemer(paramsIdx, [
|
|
618
|
+
{ type: "exists", nodeIdx: registryIdx },
|
|
619
|
+
]);
|
|
620
|
+
// programmable_logic_base no longer takes an untyped redeemer: it
|
|
621
|
+
// dispatches on the constructor and witnesses where its delegate's
|
|
622
|
+
// withdrawal sits.
|
|
623
|
+
const spendRdmr = baseSpendRedeemer("TRANSFER", paramsIdx, transferWdrlIdx);
|
|
460
624
|
const tokenDatum = voidData();
|
|
461
625
|
// 10. Build transaction
|
|
462
626
|
let tx = client.newTx();
|
|
463
627
|
tx = tx.collectFrom({ inputs: selected, redeemer: spendRdmr });
|
|
628
|
+
// The TRANSFER delegate — not third_party. programmable_logic_base
|
|
629
|
+
// resolves the withdrawal at `wdrl_idx` and requires it to equal the
|
|
630
|
+
// credential the params datum names for THIS dispatch arm, so withdrawing
|
|
631
|
+
// the wrong delegate fails the spend with an empty trace list.
|
|
464
632
|
tx = tx.withdraw({
|
|
465
|
-
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.
|
|
633
|
+
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.transfer.hash, "hex"))),
|
|
466
634
|
amount: 0n,
|
|
467
635
|
redeemer: plgRedeemer,
|
|
468
636
|
});
|
|
@@ -485,7 +653,7 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
485
653
|
});
|
|
486
654
|
tx = tx.readFrom({ referenceInputs: [...proofUtxos, protocolParamsUtxo, registryUtxo] });
|
|
487
655
|
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.programmableLogicBase.compiledCode) });
|
|
488
|
-
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.
|
|
656
|
+
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.transfer.compiledCode) });
|
|
489
657
|
tx = tx.attachScript({ script: buildEvoScript(scripts.transfer.compiledCode) });
|
|
490
658
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(senderStakingHash) });
|
|
491
659
|
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, senderAddress, senderWalletUtxos);
|
|
@@ -592,7 +760,8 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
592
760
|
const nftAssets = mintAssetsFromMap(new Map([[nftUnit, 1n]]));
|
|
593
761
|
const walletUtxos = await client.getUtxos(EvoAddress.fromBech32(feePayerAddress));
|
|
594
762
|
let tx = client.newTx();
|
|
595
|
-
|
|
763
|
+
const spendableUtxos = spendableWalletUtxos(walletUtxos, ctx.deployment);
|
|
764
|
+
tx = tx.collectFrom({ inputs: spendableUtxos.slice(0, 2) });
|
|
596
765
|
tx = tx.collectFrom({ inputs: [coveringNode], redeemer: voidData() });
|
|
597
766
|
tx = tx.mintAssets({ assets: nftAssets, redeemer: blacklistAddRedeemer(targetStakingHash) });
|
|
598
767
|
// Output 0: updated covering node
|
|
@@ -615,7 +784,7 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
615
784
|
tx = tx.attachScript({ script: buildEvoScript(scripts.blacklistMint.compiledCode) });
|
|
616
785
|
const managerPkh = paymentCredentialHash(feePayerAddress);
|
|
617
786
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(managerPkh) });
|
|
618
|
-
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress,
|
|
787
|
+
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress, spendableUtxos);
|
|
619
788
|
return { cbor, txHash, _signBuilder };
|
|
620
789
|
},
|
|
621
790
|
// ====================================================================
|
|
@@ -648,7 +817,8 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
648
817
|
const burnAssets = mintAssetsFromMap(new Map([[nftUnit, -1n]]));
|
|
649
818
|
const walletUtxos = await client.getUtxos(EvoAddress.fromBech32(feePayerAddress));
|
|
650
819
|
let tx = client.newTx();
|
|
651
|
-
|
|
820
|
+
const spendableUtxos = spendableWalletUtxos(walletUtxos, ctx.deployment);
|
|
821
|
+
tx = tx.collectFrom({ inputs: spendableUtxos.slice(0, 2) });
|
|
652
822
|
tx = tx.collectFrom({ inputs: [nodeToRemove], redeemer: voidData() });
|
|
653
823
|
tx = tx.collectFrom({ inputs: [precedingNode], redeemer: voidData() });
|
|
654
824
|
tx = tx.mintAssets({ assets: burnAssets, redeemer: blacklistRemoveRedeemer(targetStakingHash) });
|
|
@@ -666,7 +836,7 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
666
836
|
tx = tx.attachScript({ script: buildEvoScript(scripts.blacklistMint.compiledCode) });
|
|
667
837
|
const managerPkh = paymentCredentialHash(feePayerAddress);
|
|
668
838
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(managerPkh) });
|
|
669
|
-
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress,
|
|
839
|
+
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress, spendableUtxos);
|
|
670
840
|
return { cbor, txHash, _signBuilder };
|
|
671
841
|
},
|
|
672
842
|
// ====================================================================
|
|
@@ -709,11 +879,64 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
709
879
|
if (!registryUtxo)
|
|
710
880
|
throw new Error(`Registry node not found for ${tokenPolicyId}`);
|
|
711
881
|
// 3. Sort reference inputs
|
|
712
|
-
|
|
882
|
+
// `third_party`'s script is supplied EXPLICITLY, as a reference input.
|
|
883
|
+
//
|
|
884
|
+
// ⚠ It used to arrive BY ACCIDENT: the bootstrap publishes reference
|
|
885
|
+
// scripts to the fee payer's own address, so `getUtxos(wallet)` returns
|
|
886
|
+
// them and `slice(0, 2)` sometimes SPENT one. A spent input's reference
|
|
887
|
+
// script counts as supplied (CIP-33), so the transaction validated —
|
|
888
|
+
// and CONSUMED the deployment's reference-script UTxO in the process.
|
|
889
|
+
// When the draw went the other way the script was absent entirely and
|
|
890
|
+
// the ledger rejected with 3011 (missing script witness). One root
|
|
891
|
+
// cause, two opposite symptoms; the earlier 3104 was its third face,
|
|
892
|
+
// an explicit attach duplicating what the spent input already supplied.
|
|
893
|
+
const thirdPartyRefUtxos = await client.getUtxosByOutRef([
|
|
894
|
+
new EvoTransactionInput.TransactionInput({
|
|
895
|
+
transactionId: EvoTransactionHash.fromHex(ctx.deployment.thirdPartyRefInput.txHash),
|
|
896
|
+
index: BigInt(ctx.deployment.thirdPartyRefInput.outputIndex),
|
|
897
|
+
}),
|
|
898
|
+
]);
|
|
899
|
+
if (thirdPartyRefUtxos.length === 0) {
|
|
900
|
+
throw new Error(`third_party reference script not found on-chain at ` +
|
|
901
|
+
`${ctx.deployment.thirdPartyRefInput.txHash}#${ctx.deployment.thirdPartyRefInput.outputIndex}. ` +
|
|
902
|
+
`Deployment reference scripts are load-bearing: if an earlier transaction SPENT this ` +
|
|
903
|
+
`output, the deployment is broken and must be re-published.`);
|
|
904
|
+
}
|
|
905
|
+
const thirdPartyRefUtxo = thirdPartyRefUtxos[0];
|
|
906
|
+
const allRefInputRefs = [
|
|
907
|
+
utxoToTxInput(protocolParamsUtxo),
|
|
908
|
+
utxoToTxInput(registryUtxo),
|
|
909
|
+
utxoToTxInput(thirdPartyRefUtxo),
|
|
910
|
+
];
|
|
713
911
|
const sortedRefInputs = sortTxInputs(allRefInputRefs);
|
|
714
912
|
const registryIdx = findRefInputIndex(sortedRefInputs, utxoToTxInput(registryUtxo));
|
|
715
913
|
// 4. Build redeemers
|
|
716
|
-
const
|
|
914
|
+
const paramsIdx = findRefInputIndex(sortedRefInputs, utxoToTxInput(protocolParamsUtxo));
|
|
915
|
+
// 0.5.x third-party route. `ThirdPartyAct` was DELETED by #110: the
|
|
916
|
+
// administrative path is now the standalone `third_party` validator, and
|
|
917
|
+
// programmable_logic_base dispatches to it via SpendViaThirdParty — so
|
|
918
|
+
// this transaction never loads the `transfer` reference script at all.
|
|
919
|
+
//
|
|
920
|
+
// The withdrawal set is BOTH scripts: the framework delegate, and the
|
|
921
|
+
// issuer authority the registry node names in
|
|
922
|
+
// `third_party_transfer_logic_script`. `third_party` requires the latter
|
|
923
|
+
// by name — it is the only thing standing between a holder's tokens and
|
|
924
|
+
// anyone who wants them, and for FES it is a real issuer-admin check
|
|
925
|
+
// rather than dummy's unconditional one.
|
|
926
|
+
const thirdPartyKey = {
|
|
927
|
+
hash: ctx.standardScripts.thirdParty.hash,
|
|
928
|
+
isScript: true,
|
|
929
|
+
};
|
|
930
|
+
const issuerAuthorityKey = { hash: scripts.issuerAdmin.hash, isScript: true };
|
|
931
|
+
const thirdPartyWdrlIdx = withdrawalIndexOf([thirdPartyKey, issuerAuthorityKey], thirdPartyKey);
|
|
932
|
+
// outputs_start_idx = 1: `third_party` PAIRS each programmable input with
|
|
933
|
+
// the NEXT output (same address, datum and reference script, lovelace
|
|
934
|
+
// ratcheting up) and reads the seized amount as the DELTA. Destination
|
|
935
|
+
// outputs must therefore sit AMONG THE LEADING ones it skips.
|
|
936
|
+
// See docs/api-reference.md — getting this backwards fails with an EMPTY
|
|
937
|
+
// TRACE LIST, because it is a structural expect and not a traced check.
|
|
938
|
+
const plgRedeemer = thirdPartyRedeemer(paramsIdx, registryIdx, 1);
|
|
939
|
+
const plbSpendRedeemer = baseSpendRedeemer("THIRD_PARTY", paramsIdx, thirdPartyWdrlIdx);
|
|
717
940
|
const tokenDatum = voidData();
|
|
718
941
|
// 5. Build recipient PLB address
|
|
719
942
|
const recipientPlbAddr = baseAddress(networkId, plbHash, destinationAddress);
|
|
@@ -729,17 +952,19 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
729
952
|
}
|
|
730
953
|
// 7. Get wallet UTxOs
|
|
731
954
|
const walletUtxos = await client.getUtxos(EvoAddress.fromBech32(feePayerAddress));
|
|
955
|
+
// NEVER spend a deployment reference-script UTxO — see spendableWalletUtxos.
|
|
956
|
+
const spendableUtxos = spendableWalletUtxos(walletUtxos, ctx.deployment);
|
|
732
957
|
// 8. Build transaction
|
|
733
958
|
let tx = client.newTx();
|
|
734
|
-
tx = tx.collectFrom({ inputs:
|
|
735
|
-
tx = tx.collectFrom({ inputs: [utxoToSeize], redeemer:
|
|
959
|
+
tx = tx.collectFrom({ inputs: spendableUtxos.slice(0, 2) });
|
|
960
|
+
tx = tx.collectFrom({ inputs: [utxoToSeize], redeemer: plbSpendRedeemer });
|
|
736
961
|
tx = tx.withdraw({
|
|
737
962
|
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(scripts.issuerAdmin.hash, "hex"))),
|
|
738
963
|
amount: 0n,
|
|
739
964
|
redeemer: voidData(),
|
|
740
965
|
});
|
|
741
966
|
tx = tx.withdraw({
|
|
742
|
-
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.
|
|
967
|
+
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.thirdParty.hash, "hex"))),
|
|
743
968
|
amount: 0n,
|
|
744
969
|
redeemer: plgRedeemer,
|
|
745
970
|
});
|
|
@@ -753,14 +978,31 @@ export function freezeAndSeizeSubstandard(config) {
|
|
|
753
978
|
tx = tx.payToAddress({
|
|
754
979
|
address: utxoToSeize.address,
|
|
755
980
|
assets: outputAssets(utxoLovelace(utxoToSeize), remainingTokens.size > 0 ? remainingTokens : undefined),
|
|
756
|
-
|
|
981
|
+
// The PAIRED continuation: `third_party` requires it to preserve the
|
|
982
|
+
// input's ADDRESS, DATUM and REFERENCE SCRIPT exactly. Carry the input's
|
|
983
|
+
// own datum rather than a fresh void one — they happen to be equal today
|
|
984
|
+
// because every programmable output here is void-datumed, and that is a
|
|
985
|
+
// coincidence rather than a guarantee.
|
|
986
|
+
datum: new InlineDatum.InlineDatum({ data: getInlineDatum(utxoToSeize) ?? tokenDatum }),
|
|
757
987
|
});
|
|
758
|
-
tx = tx.readFrom({ referenceInputs: [protocolParamsUtxo, registryUtxo] });
|
|
988
|
+
tx = tx.readFrom({ referenceInputs: [protocolParamsUtxo, registryUtxo, thirdPartyRefUtxo] });
|
|
989
|
+
if (process.env.DUMP_TX_STRUCTURE) {
|
|
990
|
+
// eslint-disable-next-line no-console
|
|
991
|
+
console.error("=== SEIZE attached scripts ===\n" +
|
|
992
|
+
` programmableLogicBase=${ctx.standardScripts.programmableLogicBase.hash}\n` +
|
|
993
|
+
` thirdParty =${ctx.standardScripts.thirdParty.hash}\n` +
|
|
994
|
+
` issuerAdmin =${scripts.issuerAdmin.hash}\n` +
|
|
995
|
+
"=== END SEIZE SCRIPTS ===");
|
|
996
|
+
}
|
|
759
997
|
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.programmableLogicBase.compiledCode) });
|
|
760
|
-
|
|
998
|
+
// ⚠ `third_party` is NOT attached here. Its withdrawal already carries the
|
|
999
|
+
// script witness, and attaching it again makes the duplicate extraneous —
|
|
1000
|
+
// the ledger rejects the whole transaction with code 3104. MEASURED: the
|
|
1001
|
+
// hash it names is third_party's, while the withdrawal for that same
|
|
1002
|
+
// credential is present and correct.
|
|
761
1003
|
tx = tx.attachScript({ script: buildEvoScript(scripts.issuerAdmin.compiledCode) });
|
|
762
1004
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(config.deployment.adminPkh) });
|
|
763
|
-
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress,
|
|
1005
|
+
const { cbor, txHash, _signBuilder } = await buildAndSerialize(tx, feePayerAddress, spendableUtxos);
|
|
764
1006
|
return { cbor, txHash, _signBuilder };
|
|
765
1007
|
},
|
|
766
1008
|
};
|