@easy1staking/cip113-sdk-ts 0.7.0 → 0.9.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 +72 -2
- package/blueprints/standard/v0.5.0-alpha.3/UPSTREAM_PIN.json +35 -0
- package/blueprints/standard/v0.5.0-alpha.3/plutus.json +1126 -0
- package/blueprints/standard/v0.5.0-alpha.4/UPSTREAM_PIN.json +36 -0
- package/blueprints/standard/v0.5.0-alpha.4/plutus.json +1434 -0
- package/dist/core/evo-utils.d.ts +357 -39
- package/dist/core/evo-utils.d.ts.map +1 -1
- package/dist/core/evo-utils.js +513 -27
- package/dist/core/evo-utils.js.map +1 -1
- package/dist/core/ledger-order.d.ts +204 -18
- package/dist/core/ledger-order.d.ts.map +1 -1
- package/dist/core/ledger-order.js +385 -28
- package/dist/core/ledger-order.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -3
- package/dist/index.js.map +1 -1
- package/dist/standard/blueprint.d.ts +128 -29
- package/dist/standard/blueprint.d.ts.map +1 -1
- package/dist/standard/blueprint.js +244 -47
- package/dist/standard/blueprint.js.map +1 -1
- package/dist/standard/scripts.d.ts +143 -75
- package/dist/standard/scripts.d.ts.map +1 -1
- package/dist/standard/scripts.js +211 -116
- package/dist/standard/scripts.js.map +1 -1
- package/dist/substandards/dummy/index.d.ts +19 -0
- package/dist/substandards/dummy/index.d.ts.map +1 -1
- package/dist/substandards/dummy/index.js +322 -73
- 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 +455 -117
- package/dist/substandards/freeze-and-seize/index.js.map +1 -1
- package/dist/types.d.ts +131 -48
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -1
|
@@ -7,17 +7,63 @@
|
|
|
7
7
|
*
|
|
8
8
|
* No compliance features, no blacklist.
|
|
9
9
|
* Uses Evolution SDK directly — no adapter abstraction.
|
|
10
|
+
*
|
|
11
|
+
* ⛔ alpha.4 SPLIT ISSUANCE (#129), AND IT CHANGED WHAT `register` AND `mint`
|
|
12
|
+
* MUST CARRY. `issuance_mint` is frozen — its applied hash IS the policy id —
|
|
13
|
+
* so its redeemer is now `IssuanceRedeemer { params_idx }` and nothing else.
|
|
14
|
+
* Everything replaceable moved behind `issuance_logic_cred` in the
|
|
15
|
+
* protocol-params datum, which means EVERY mint and EVERY burn now carries a
|
|
16
|
+
* SECOND protocol withdrawal: `issuance_logic`'s withdraw-0, whose redeemer is
|
|
17
|
+
* the per-policy `MintingRegistryProof` map.
|
|
18
|
+
*
|
|
19
|
+
* ⚠ Omitting that withdrawal is SILENT. `issuance_mint` calls `covered_by`,
|
|
20
|
+
* which scans `tx.redeemers` for a `Withdraw(issuance_logic_cred)` purpose;
|
|
21
|
+
* with the withdrawal absent there is no such redeemer, the scan returns False,
|
|
22
|
+
* and the mint fails naming neither a withdrawal, nor a policy, nor an index.
|
|
23
|
+
* That is why `register` and `mint` route every index through `issuancePlan`
|
|
24
|
+
* and cross-check `plan.withdrawals.length` against their own `withdraw()`
|
|
25
|
+
* call count: a comment cannot prove a call site exists, a count can.
|
|
26
|
+
*
|
|
27
|
+
* `transfer` and `thirdPartyTransfer` neither mint nor burn, so neither needs
|
|
28
|
+
* `issuance_logic` and both still use `plbWithdrawalPlan`.
|
|
10
29
|
*/
|
|
11
30
|
import { CIP171_METADATA_LABEL, buildCip171Metadatum } from "../../core/cip171.js";
|
|
12
|
-
import { Address as EvoAddress, Assets as EvoAssets, Bytes, Data, Transaction, } from "@evolution-sdk/evolution";
|
|
31
|
+
import { Address as EvoAddress, Assets as EvoAssets, Bytes, Data, Transaction, TransactionHash as EvoTransactionHash, TransactionInput as EvoTransactionInput, } from "@evolution-sdk/evolution";
|
|
13
32
|
import { getValidatorCode } from "../../standard/blueprint.js";
|
|
14
33
|
import { findRegistryNode, findCoveringNode, utxoToTxInput, } from "../../core/registry.js";
|
|
15
|
-
import { baseSpendRedeemer, transferRedeemer, thirdPartyRedeemer, referenceInputIndexOf,
|
|
16
|
-
import { buildEvoScript, computeScriptHash, scriptAddress, baseAddress, stakingCredentialHash, stringToHex, voidData, registryNodeDatum, decodeRegistryNode, registryInsertRedeemer,
|
|
34
|
+
import { baseSpendRedeemer, transferRedeemer, thirdPartyRedeemer, referenceInputIndexOf, plbWithdrawalPlan, issuancePlan, programmableLogicGlobalRedeemer, compareTxInputs, } from "../../core/ledger-order.js";
|
|
35
|
+
import { buildEvoScript, computeScriptHash, scriptAddress, baseAddress, stakingCredentialHash, stringToHex, voidData, registryNodeDatum, decodeRegistryNode, registryInsertRedeemer, mintAssetsFromMap, REGISTRY_NODE_MIN_ADA, getInlineDatum, assertProtocolParamsIssuanceLogic, utxoUnitQty, outputAssets, minUtxoAtLeast, Credential, KeyHash, InlineDatum, } from "../../core/evo-utils.js";
|
|
17
36
|
const DUMMY_VALIDATORS = {
|
|
18
37
|
ISSUE: "transfer.issue.withdraw",
|
|
19
38
|
TRANSFER: "transfer.transfer.withdraw",
|
|
20
39
|
};
|
|
40
|
+
/**
|
|
41
|
+
* The flat amount this plugin used to hardcode, kept as a FLOOR rather than an
|
|
42
|
+
* answer — see `minUtxoAtLeast`. All five token outputs share one shape (a
|
|
43
|
+
* programmable-logic-base address, one token, a `voidData()` datum), so the
|
|
44
|
+
* DATUM is fixed and they look like a constant is safe. It is not: min-UTxO
|
|
45
|
+
* scales with SERIALISED OUTPUT SIZE, and two of the three things that scale it
|
|
46
|
+
* come from the caller — the ASSET NAME (raw hex at every boundary here, 32
|
|
47
|
+
* bytes when CIP-67-labelled) and the QUANTITY (a CBOR integer that widens with
|
|
48
|
+
* magnitude).
|
|
49
|
+
*
|
|
50
|
+
* MEASURED against preview's live `coinsPerUtxoByte` of 4310 (2026-09-07):
|
|
51
|
+
*
|
|
52
|
+
* live "DUM"+suffix (6B), qty 1000 1,202,490 OK, 97,510 headroom
|
|
53
|
+
* live name (6B), qty 2^63-1 1,228,350 OK, 71,650 headroom
|
|
54
|
+
* CIP-67 labelled 12B, qty 1000 1,215,420 OK, 84,580 headroom
|
|
55
|
+
* max CIP-67 name (32B), qty 1000 1,318,860 SHORT by 18,860
|
|
56
|
+
* max name (32B), qty 2^63-1 1,344,720 SHORT by 44,720
|
|
57
|
+
*
|
|
58
|
+
* ⚠ IDENTICAL figures to `freeze-and-seize`, and that is the point: same output
|
|
59
|
+
* shape means this was ONE defect appearing in two places, not two defects. It
|
|
60
|
+
* was found in FES first and only then looked for here — the second sighting is
|
|
61
|
+
* what made it a pattern rather than a one-off.
|
|
62
|
+
*
|
|
63
|
+
* The floor keeps the change MONOTONE: ordinary names emit exactly what they
|
|
64
|
+
* always did, and only the cases that were genuinely short move.
|
|
65
|
+
*/
|
|
66
|
+
const TOKEN_OUTPUT_FLOOR = 1300000n;
|
|
21
67
|
export function dummySubstandard(config) {
|
|
22
68
|
let ctx;
|
|
23
69
|
let issueScript;
|
|
@@ -87,14 +133,16 @@ export function dummySubstandard(config) {
|
|
|
87
133
|
*/
|
|
88
134
|
async function findParamsUtxo() {
|
|
89
135
|
const unit = ctx.deployment.protocolParams.policyId + stringToHex("ProtocolParams");
|
|
90
|
-
const addr = EvoAddress.fromBech32(scriptAddress(networkId, ctx.deployment.
|
|
136
|
+
const addr = EvoAddress.fromBech32(scriptAddress(networkId, ctx.deployment.protocolParams.policyId));
|
|
91
137
|
const utxos = await ctx.client.getUtxosWithUnit(addr, unit);
|
|
92
138
|
if (utxos.length === 0) {
|
|
93
139
|
throw new Error(`Protocol params UTxO not found (unit ${unit}) at the coordination address. ` +
|
|
94
140
|
`The params NFT is one-shot: zero results means it is locked elsewhere, not that ` +
|
|
95
141
|
`the protocol is un-deployed.`);
|
|
96
142
|
}
|
|
97
|
-
|
|
143
|
+
const utxo = utxos[0];
|
|
144
|
+
assertProtocolParamsIssuanceLogic(utxo, ctx.deployment.issuanceLogic.scriptHash);
|
|
145
|
+
return utxo;
|
|
98
146
|
}
|
|
99
147
|
/**
|
|
100
148
|
* The IssuanceCborHex UTxO.
|
|
@@ -115,6 +163,29 @@ export function dummySubstandard(config) {
|
|
|
115
163
|
}
|
|
116
164
|
return utxos[0];
|
|
117
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* The `issuance_logic` reference-script UTxO.
|
|
168
|
+
*
|
|
169
|
+
* ⛔ Its withdraw-0 rides on EVERY mint and burn, which makes this the single
|
|
170
|
+
* most load-bearing reference-script output in the deployment. If an earlier
|
|
171
|
+
* transaction spent it, the deployment is broken and the script must be
|
|
172
|
+
* re-published; attaching the body here would hide that infrastructure loss.
|
|
173
|
+
*/
|
|
174
|
+
async function findIssuanceLogicRefUtxo() {
|
|
175
|
+
const { txHash, outputIndex } = ctx.deployment.issuanceLogicRefInput;
|
|
176
|
+
const utxos = await ctx.client.getUtxosByOutRef([
|
|
177
|
+
new EvoTransactionInput.TransactionInput({
|
|
178
|
+
transactionId: EvoTransactionHash.fromHex(txHash),
|
|
179
|
+
index: BigInt(outputIndex),
|
|
180
|
+
}),
|
|
181
|
+
]);
|
|
182
|
+
if (utxos.length === 0) {
|
|
183
|
+
throw new Error(`issuance_logic reference script not found on-chain at ${txHash}#${outputIndex}. ` +
|
|
184
|
+
`Deployment reference scripts are load-bearing: if an earlier transaction SPENT this ` +
|
|
185
|
+
`output, the deployment is broken and must be re-published.`);
|
|
186
|
+
}
|
|
187
|
+
return utxos[0];
|
|
188
|
+
}
|
|
118
189
|
/** Build the transaction and shape it into an UnsignedTx. */
|
|
119
190
|
async function finish(tx, changeAddress, extra = {}) {
|
|
120
191
|
// ⛔ NEVER LET COIN SELECTION REACH A REFERENCE-SCRIPT UTxO.
|
|
@@ -170,22 +241,33 @@ export function dummySubstandard(config) {
|
|
|
170
241
|
* node (the one whose key < ours and whose next > ours), spending it to
|
|
171
242
|
* repoint its `next` at us, and minting a new node NFT for our own entry.
|
|
172
243
|
*
|
|
173
|
-
* Mint and registration are one transaction because
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
244
|
+
* Mint and registration are one transaction because the registry proof can
|
|
245
|
+
* name the new node as an OUTPUT of this very transaction (OutputIndex), so
|
|
246
|
+
* the token can be minted before its node exists anywhere else. In alpha.4
|
|
247
|
+
* that proof rides inside `issuance_logic`'s withdraw-0 map, keyed by policy
|
|
248
|
+
* id; `issuance_mint`'s own redeemer is only `IssuanceRedeemer { params_idx }`.
|
|
249
|
+
* Register IS a first mint, so it needs that withdrawal, the params reference
|
|
250
|
+
* input and its map entry exactly as `mint` does.
|
|
177
251
|
*/
|
|
178
252
|
async register(params) {
|
|
179
253
|
const { feePayerAddress, assetName, quantity } = params;
|
|
180
254
|
const recipient = params.recipientAddress ?? feePayerAddress;
|
|
181
255
|
const client = ctx.client;
|
|
256
|
+
// min-UTxO is sized from live protocol parameters, not guessed — the asset
|
|
257
|
+
// name and the quantity are both caller-supplied and both widen the output.
|
|
258
|
+
const coinsPerUtxoByte = (await client.getProtocolParameters()).coinsPerUtxoByte;
|
|
182
259
|
// The token policy IS issuance_mint parameterised by our minting logic.
|
|
183
260
|
const issuanceMint = ctx.standardScripts.buildIssuanceMint(issueScript.hash);
|
|
184
261
|
const tokenPolicyId = issuanceMint.hash;
|
|
185
262
|
const unit = tokenPolicyId + assetName;
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
263
|
+
const registryPolicyId = ctx.standardScripts.registry.hash;
|
|
264
|
+
// ⚑ ONE HASH, TWO ROLES. registry_mint and registry_spend merged (#117), so
|
|
265
|
+
// the node NFT policy id and the node address's payment credential are the
|
|
266
|
+
// same value — the minting policy naming itself. The old names
|
|
267
|
+
// (registrySpendAddr / registryMintPolicyId) asserted a distinction that no
|
|
268
|
+
// longer exists and would tell a reader to look for two hashes.
|
|
269
|
+
const registryAddr = scriptAddress(networkId, ctx.standardScripts.registry.hash);
|
|
270
|
+
const registryUtxos = await client.getUtxos(EvoAddress.fromBech32(registryAddr));
|
|
189
271
|
const covering = findCoveringNode(registryUtxos, tokenPolicyId);
|
|
190
272
|
if (!covering) {
|
|
191
273
|
throw new Error(`No covering registry node found for policy ${tokenPolicyId}. The registry origin ` +
|
|
@@ -220,14 +302,34 @@ export function dummySubstandard(config) {
|
|
|
220
302
|
globalStateCs: "",
|
|
221
303
|
});
|
|
222
304
|
const updatedCoveringDatum = registryNodeDatum({ ...coveringNode, next: tokenPolicyId });
|
|
223
|
-
const registryNftUnit =
|
|
224
|
-
const coveringNftUnit =
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
305
|
+
const registryNftUnit = registryPolicyId + tokenPolicyId;
|
|
306
|
+
const coveringNftUnit = registryPolicyId + coveringNode.key;
|
|
307
|
+
const paramsUtxo = await findParamsUtxo();
|
|
308
|
+
const issuanceCborUtxo = await findIssuanceCborUtxo();
|
|
309
|
+
const issuanceLogicRefUtxo = await findIssuanceLogicRefUtxo();
|
|
310
|
+
// ⛔ ONE ARRAY, TWO USES, DECLARED ONCE. `params_idx` and every other
|
|
311
|
+
// reference-input index the plan computes are positions in THIS set, and
|
|
312
|
+
// `readFrom` below is what actually puts that set in the transaction. Two
|
|
313
|
+
// separately-built lists are two chances to disagree, and a disagreement
|
|
314
|
+
// here is a valid integer pointing at the wrong UTxO.
|
|
315
|
+
const refUtxos = [paramsUtxo, issuanceCborUtxo, issuanceLogicRefUtxo];
|
|
316
|
+
// Output order is the contract: `issuance_logic`'s map names our registry
|
|
317
|
+
// node by OUTPUT INDEX, so moving these outputs silently changes which
|
|
318
|
+
// output the validator inspects. Balancing change is appended after the
|
|
319
|
+
// explicit outputs — established by the literals this replaces having
|
|
320
|
+
// validated on chain, not by reasoning about Evolution.
|
|
321
|
+
const outputTags = ["user-token", "new-node", "covering-node"];
|
|
322
|
+
// ⛔ EVERY INDEX IN THIS TRANSACTION, COMPUTED IN ONE PLACE OVER THE
|
|
323
|
+
// COMPLETE SETS. No `plgHash`: register spends the covering registry node
|
|
324
|
+
// and wallet funds, never a `programmable_logic_base` input.
|
|
325
|
+
const plan = issuancePlan({
|
|
326
|
+
issuanceLogicHash: ctx.deployment.issuanceLogic.scriptHash,
|
|
327
|
+
otherWithdrawals: [{ hash: issueScript.hash, isScript: true }],
|
|
328
|
+
referenceInputs: refUtxos.map(utxoToTxInput),
|
|
329
|
+
paramsRefInput: utxoToTxInput(paramsUtxo),
|
|
330
|
+
outputs: outputTags,
|
|
331
|
+
issued: [{ policyId: tokenPolicyId, proof: { kind: "output", tag: "new-node" } }],
|
|
332
|
+
});
|
|
231
333
|
let tx = client.newTx();
|
|
232
334
|
// CIP-171 provenance, carried by the transaction that parameterises the
|
|
233
335
|
// scripts it describes. Optional: absent, this behaves exactly as before.
|
|
@@ -238,24 +340,43 @@ export function dummySubstandard(config) {
|
|
|
238
340
|
});
|
|
239
341
|
}
|
|
240
342
|
tx = tx.collectFrom({ inputs: [covering], redeemer: voidData() });
|
|
241
|
-
// The minting-logic
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
343
|
+
// The minting-logic and protocol issuance-logic withdrawals. The latter's
|
|
344
|
+
// script witness comes from `issuanceLogicRefUtxo` in `readFrom` below.
|
|
345
|
+
const withdrawalSpecs = [
|
|
346
|
+
{ hash: issueScript.hash, redeemer: Data.int(100n) },
|
|
347
|
+
{
|
|
348
|
+
hash: ctx.deployment.issuanceLogic.scriptHash,
|
|
349
|
+
redeemer: plan.issuanceLogicRedeemer,
|
|
350
|
+
},
|
|
351
|
+
];
|
|
352
|
+
if (withdrawalSpecs.length !== plan.withdrawals.length) {
|
|
353
|
+
throw new Error(`register: issuance plan declares ${plan.withdrawals.length} withdrawals but ` +
|
|
354
|
+
`${withdrawalSpecs.length} are emitted`);
|
|
355
|
+
}
|
|
356
|
+
for (const withdrawal of withdrawalSpecs) {
|
|
357
|
+
tx = tx.withdraw({
|
|
358
|
+
stakeCredential: Credential.makeScriptHash(hexToBytes(withdrawal.hash)),
|
|
359
|
+
amount: 0n,
|
|
360
|
+
redeemer: withdrawal.redeemer,
|
|
361
|
+
});
|
|
362
|
+
}
|
|
247
363
|
tx = tx.mintAssets({
|
|
248
364
|
assets: mintAssetsFromMap(new Map([[unit, quantity]])),
|
|
249
|
-
redeemer:
|
|
365
|
+
redeemer: plan.issuanceRedeemer,
|
|
250
366
|
});
|
|
251
367
|
tx = tx.mintAssets({
|
|
252
368
|
assets: mintAssetsFromMap(new Map([[registryNftUnit, 1n]])),
|
|
253
369
|
redeemer: registryInsertRedeemer(tokenPolicyId, { type: "script", hash: issueScript.hash }),
|
|
254
370
|
});
|
|
371
|
+
// outputTags[0] — "user-token": the minted supply at the recipient's PLB.
|
|
255
372
|
tx = tx.payToAddress({
|
|
256
373
|
address: EvoAddress.fromBech32(recipientPlbAddr),
|
|
257
|
-
assets: outputAssets(
|
|
258
|
-
|
|
374
|
+
assets: outputAssets(minUtxoAtLeast(TOKEN_OUTPUT_FLOOR, {
|
|
375
|
+
address: recipientPlbAddr,
|
|
376
|
+
assets: outputAssets(0n, new Map([[unit, quantity]])),
|
|
377
|
+
datum: voidData(),
|
|
378
|
+
coinsPerUtxoByte,
|
|
379
|
+
}), new Map([[unit, quantity]])), datum: new InlineDatum.InlineDatum({ data: voidData() }),
|
|
259
380
|
});
|
|
260
381
|
// ⚠ min-UTxO, not a round number pulled from the old code.
|
|
261
382
|
//
|
|
@@ -267,24 +388,62 @@ export function dummySubstandard(config) {
|
|
|
267
388
|
// Deliberately generous rather than exact: min-UTxO scales with
|
|
268
389
|
// serialised size and with a protocol parameter that can rise.
|
|
269
390
|
const REGISTRY_NODE_ADA = REGISTRY_NODE_MIN_ADA;
|
|
391
|
+
// outputTags[1] — "new-node": our registry node, named by the proof.
|
|
270
392
|
tx = tx.payToAddress({
|
|
271
|
-
address: EvoAddress.fromBech32(
|
|
393
|
+
address: EvoAddress.fromBech32(registryAddr),
|
|
272
394
|
assets: outputAssets(REGISTRY_NODE_ADA, new Map([[registryNftUnit, 1n]])),
|
|
273
395
|
datum: new InlineDatum.InlineDatum({ data: newNodeDatum }),
|
|
274
396
|
});
|
|
397
|
+
// outputTags[2] — "covering-node": the predecessor with its new link.
|
|
275
398
|
tx = tx.payToAddress({
|
|
276
|
-
address: EvoAddress.fromBech32(
|
|
399
|
+
address: EvoAddress.fromBech32(registryAddr),
|
|
277
400
|
assets: outputAssets(REGISTRY_NODE_ADA, new Map([[coveringNftUnit, 1n]])),
|
|
278
401
|
datum: new InlineDatum.InlineDatum({ data: updatedCoveringDatum }),
|
|
279
402
|
});
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
403
|
+
// ⛔ THE PROTOCOL-PARAMS REFERENCE INPUT STAYS — and as of S-11 that is a
|
|
404
|
+
// MEASURED decision, not a cautious one.
|
|
405
|
+
//
|
|
406
|
+
// alpha.3's registry no longer reads it (#117). `issuance_mint` does, to
|
|
407
|
+
// pull the LIVE delegate credentials from its datum, and that locate is
|
|
408
|
+
// DELIBERATELY NON-FAILING: absent params UTxO means "no delegation",
|
|
409
|
+
// falling back to local `no_escape` custody. So removing it raises no
|
|
410
|
+
// error — it silently selects a different custody path.
|
|
411
|
+
//
|
|
412
|
+
// MEASURED ON DEVNET (S-11): removing it from BOTH substandards' register
|
|
413
|
+
// paths, the full lifecycle still passes — dummy 2/2, FES 3/3. The two
|
|
414
|
+
// custody branches do converge here, as the reasoning predicted.
|
|
415
|
+
//
|
|
416
|
+
// ⇒ AND IT STAYS ANYWAY, for a reason the measurement itself supplies:
|
|
417
|
+
// the tests pass EITHER WAY. That is precisely what makes dropping it
|
|
418
|
+
// unsafe to keep. Nothing in this suite would notice if `issuance_mint`'s
|
|
419
|
+
// delegation semantics changed and the absent input started to matter —
|
|
420
|
+
// the guard against that is the input being there, not a test. The saving
|
|
421
|
+
// is one reference input on a once-per-token transaction; the exposure is
|
|
422
|
+
// a silent custody change nobody would see.
|
|
423
|
+
// alpha.4 makes it mandatory rather than an optional custody hint because
|
|
424
|
+
// `with_protocol_params_fields` opens with `list.expect_at`.
|
|
425
|
+
tx = tx.readFrom({ referenceInputs: refUtxos });
|
|
283
426
|
tx = tx.attachScript({ script: buildEvoScript(issuanceMint.compiledCode) });
|
|
284
|
-
|
|
285
|
-
|
|
427
|
+
// ⚑ ONE attach, not two. registry_mint and registry_spend merged into a
|
|
428
|
+
// single validator (#117): this transaction both MINTS a node NFT and
|
|
429
|
+
// SPENDS the covering node, which in alpha.2 needed two scripts. It is
|
|
430
|
+
// now one script serving both purposes — attaching it twice would put a
|
|
431
|
+
// duplicate witness in the transaction.
|
|
432
|
+
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.registry.compiledCode) });
|
|
286
433
|
tx = tx.attachScript({ script: buildEvoScript(issueScript.compiledCode) });
|
|
287
|
-
|
|
434
|
+
// Register's withdrawal set is asserted on the decoded transaction at
|
|
435
|
+
// test/devnet/dummy-lifecycle.test.ts:142-155. It stays there as readable
|
|
436
|
+
// documentation of the invariant rather than as a guard, because the live
|
|
437
|
+
// evaluator is upstream of it.
|
|
438
|
+
return finish(tx, feePayerAddress, {
|
|
439
|
+
tokenPolicyId,
|
|
440
|
+
unit,
|
|
441
|
+
outputIndices: {
|
|
442
|
+
OUT_TOKEN: plan.outputIndexOf("user-token"),
|
|
443
|
+
OUT_NEW_NODE: plan.outputIndexOf("new-node"),
|
|
444
|
+
OUT_COVERING: plan.outputIndexOf("covering-node"),
|
|
445
|
+
},
|
|
446
|
+
});
|
|
288
447
|
},
|
|
289
448
|
/**
|
|
290
449
|
* Mint more of an already-registered dummy token.
|
|
@@ -297,40 +456,77 @@ export function dummySubstandard(config) {
|
|
|
297
456
|
const { feePayerAddress, tokenPolicyId, assetName, quantity } = params;
|
|
298
457
|
const recipient = params.recipientAddress ?? feePayerAddress;
|
|
299
458
|
const client = ctx.client;
|
|
459
|
+
// min-UTxO is sized from live protocol parameters, not guessed — the asset
|
|
460
|
+
// name and the quantity are both caller-supplied and both widen the output.
|
|
461
|
+
const coinsPerUtxoByte = (await client.getProtocolParameters()).coinsPerUtxoByte;
|
|
300
462
|
const unit = tokenPolicyId + assetName;
|
|
301
463
|
const issuanceMint = ctx.standardScripts.buildIssuanceMint(issueScript.hash);
|
|
302
464
|
if (issuanceMint.hash !== tokenPolicyId) {
|
|
303
465
|
throw new Error(`Policy ${tokenPolicyId} is not a dummy token: issuance_mint parameterised by ` +
|
|
304
466
|
`dummy's issue logic hashes to ${issuanceMint.hash}.`);
|
|
305
467
|
}
|
|
306
|
-
const
|
|
307
|
-
const registryUtxos = await client.getUtxos(EvoAddress.fromBech32(
|
|
468
|
+
const registryAddr = scriptAddress(networkId, ctx.standardScripts.registry.hash);
|
|
469
|
+
const registryUtxos = await client.getUtxos(EvoAddress.fromBech32(registryAddr));
|
|
308
470
|
const node = findRegistryNode(registryUtxos, tokenPolicyId);
|
|
309
471
|
if (!node)
|
|
310
472
|
throw new Error(`Registry node not found for policy ${tokenPolicyId}`);
|
|
311
473
|
const paramsUtxo = await findParamsUtxo();
|
|
312
|
-
const
|
|
313
|
-
|
|
474
|
+
const issuanceLogicRefUtxo = await findIssuanceLogicRefUtxo();
|
|
475
|
+
// ⛔ ONE ARRAY, TWO USES, DECLARED ONCE. The plan computes `params_idx`
|
|
476
|
+
// and the registry-node proof over this set; `readFrom` supplies this same
|
|
477
|
+
// set. A second list could disagree while remaining perfectly well typed.
|
|
478
|
+
const refUtxos = [paramsUtxo, node, issuanceLogicRefUtxo];
|
|
479
|
+
const plan = issuancePlan({
|
|
480
|
+
issuanceLogicHash: ctx.deployment.issuanceLogic.scriptHash,
|
|
481
|
+
otherWithdrawals: [{ hash: issueScript.hash, isScript: true }],
|
|
482
|
+
referenceInputs: refUtxos.map(utxoToTxInput),
|
|
483
|
+
paramsRefInput: utxoToTxInput(paramsUtxo),
|
|
484
|
+
issued: [
|
|
485
|
+
{
|
|
486
|
+
policyId: tokenPolicyId,
|
|
487
|
+
proof: { kind: "reference-input", input: utxoToTxInput(node) },
|
|
488
|
+
},
|
|
489
|
+
],
|
|
490
|
+
});
|
|
314
491
|
const plbHash = ctx.standardScripts.programmableLogicBase.hash;
|
|
315
492
|
const recipientPlbAddr = baseAddress(networkId, plbHash, recipient);
|
|
316
493
|
let tx = client.newTx();
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
494
|
+
const withdrawalSpecs = [
|
|
495
|
+
{ hash: issueScript.hash, redeemer: Data.int(100n) },
|
|
496
|
+
{
|
|
497
|
+
hash: ctx.deployment.issuanceLogic.scriptHash,
|
|
498
|
+
redeemer: plan.issuanceLogicRedeemer,
|
|
499
|
+
},
|
|
500
|
+
];
|
|
501
|
+
if (withdrawalSpecs.length !== plan.withdrawals.length) {
|
|
502
|
+
throw new Error(`mint: issuance plan declares ${plan.withdrawals.length} withdrawals but ` +
|
|
503
|
+
`${withdrawalSpecs.length} are emitted`);
|
|
504
|
+
}
|
|
505
|
+
for (const withdrawal of withdrawalSpecs) {
|
|
506
|
+
tx = tx.withdraw({
|
|
507
|
+
stakeCredential: Credential.makeScriptHash(hexToBytes(withdrawal.hash)),
|
|
508
|
+
amount: 0n,
|
|
509
|
+
redeemer: withdrawal.redeemer,
|
|
510
|
+
});
|
|
511
|
+
}
|
|
322
512
|
tx = tx.mintAssets({
|
|
323
513
|
assets: mintAssetsFromMap(new Map([[unit, quantity]])),
|
|
324
|
-
redeemer:
|
|
514
|
+
redeemer: plan.issuanceRedeemer,
|
|
325
515
|
});
|
|
326
516
|
tx = tx.payToAddress({
|
|
327
517
|
address: EvoAddress.fromBech32(recipientPlbAddr),
|
|
328
|
-
assets: outputAssets(
|
|
329
|
-
|
|
518
|
+
assets: outputAssets(minUtxoAtLeast(TOKEN_OUTPUT_FLOOR, {
|
|
519
|
+
address: recipientPlbAddr,
|
|
520
|
+
assets: outputAssets(0n, new Map([[unit, quantity]])),
|
|
521
|
+
datum: voidData(),
|
|
522
|
+
coinsPerUtxoByte,
|
|
523
|
+
}), new Map([[unit, quantity]])), datum: new InlineDatum.InlineDatum({ data: voidData() }),
|
|
330
524
|
});
|
|
331
|
-
tx = tx.readFrom({ referenceInputs:
|
|
525
|
+
tx = tx.readFrom({ referenceInputs: refUtxos });
|
|
332
526
|
tx = tx.attachScript({ script: buildEvoScript(issuanceMint.compiledCode) });
|
|
333
527
|
tx = tx.attachScript({ script: buildEvoScript(issueScript.compiledCode) });
|
|
528
|
+
// The executable count check above prevents the plan and emitted
|
|
529
|
+
// transaction silently diverging before evaluation.
|
|
334
530
|
return finish(tx, feePayerAddress, { tokenPolicyId, unit });
|
|
335
531
|
},
|
|
336
532
|
async burn(_params) {
|
|
@@ -364,6 +560,9 @@ export function dummySubstandard(config) {
|
|
|
364
560
|
const { holderAddress, recipientAddress, tokenPolicyId, assetName, quantity, feePayerAddress } = params;
|
|
365
561
|
const unit = tokenPolicyId + assetName;
|
|
366
562
|
const client = ctx.client;
|
|
563
|
+
// min-UTxO is sized from live protocol parameters, not guessed — the asset
|
|
564
|
+
// name and the quantity are both caller-supplied and both widen the output.
|
|
565
|
+
const coinsPerUtxoByte = (await client.getProtocolParameters()).coinsPerUtxoByte;
|
|
367
566
|
const plbHash = ctx.standardScripts.programmableLogicBase.hash;
|
|
368
567
|
const holderPlbAddr = baseAddress(networkId, plbHash, holderAddress);
|
|
369
568
|
const recipientPlbAddr = baseAddress(networkId, plbHash, recipientAddress);
|
|
@@ -374,8 +573,8 @@ export function dummySubstandard(config) {
|
|
|
374
573
|
}
|
|
375
574
|
const { selected, totalTokenAmount } = selectUtxosForAmount(tokenUtxos, unit, quantity);
|
|
376
575
|
const returningAmount = totalTokenAmount - quantity;
|
|
377
|
-
const
|
|
378
|
-
const registryUtxos = await client.getUtxos(EvoAddress.fromBech32(
|
|
576
|
+
const registryAddr = scriptAddress(networkId, ctx.standardScripts.registry.hash);
|
|
577
|
+
const registryUtxos = await client.getUtxos(EvoAddress.fromBech32(registryAddr));
|
|
379
578
|
const registryUtxo = findRegistryNode(registryUtxos, tokenPolicyId);
|
|
380
579
|
if (!registryUtxo)
|
|
381
580
|
throw new Error(`Registry node not found for policy ${tokenPolicyId}`);
|
|
@@ -392,8 +591,12 @@ export function dummySubstandard(config) {
|
|
|
392
591
|
isScript: true,
|
|
393
592
|
};
|
|
394
593
|
const issuerAuthorityKey = { hash: transferScript.hash, isScript: true };
|
|
395
|
-
|
|
396
|
-
const
|
|
594
|
+
// alpha.3: the DISPATCHER withdraws too, and PLB's wdrl_idx points at IT.
|
|
595
|
+
const plan = plbWithdrawalPlan({
|
|
596
|
+
plgHash: ctx.standardScripts.programmableLogicGlobal.hash,
|
|
597
|
+
others: [thirdPartyKey, issuerAuthorityKey],
|
|
598
|
+
});
|
|
599
|
+
const allWithdrawals = plan.all;
|
|
397
600
|
// ---- Output layout is the contract, and it is NOT the transfer layout --
|
|
398
601
|
//
|
|
399
602
|
// `third_party` PAIRS each programmable input with an output, in ledger
|
|
@@ -413,14 +616,20 @@ export function dummySubstandard(config) {
|
|
|
413
616
|
// Getting this backwards — destination last, continuations first — encodes
|
|
414
617
|
// and submits happily and fails at evaluation with an empty trace list.
|
|
415
618
|
const OUTPUTS_START_IDX = 1;
|
|
416
|
-
|
|
619
|
+
// wdrl_idx targets the dispatcher; the ACT moves to the dispatcher's own
|
|
620
|
+
// redeemer. In alpha.2 both lived on this one redeemer.
|
|
621
|
+
const spendRdmr = baseSpendRedeemer(paramsIdx, plan.plgIdx);
|
|
417
622
|
let tx = client.newTx();
|
|
418
623
|
tx = tx.collectFrom({ inputs: selected, redeemer: spendRdmr });
|
|
419
624
|
// Destination first.
|
|
420
625
|
tx = tx.payToAddress({
|
|
421
626
|
address: EvoAddress.fromBech32(recipientPlbAddr),
|
|
422
|
-
assets: outputAssets(
|
|
423
|
-
|
|
627
|
+
assets: outputAssets(minUtxoAtLeast(TOKEN_OUTPUT_FLOOR, {
|
|
628
|
+
address: recipientPlbAddr,
|
|
629
|
+
assets: outputAssets(0n, new Map([[unit, quantity]])),
|
|
630
|
+
datum: voidData(),
|
|
631
|
+
coinsPerUtxoByte,
|
|
632
|
+
}), new Map([[unit, quantity]])), datum: new InlineDatum.InlineDatum({ data: voidData() }),
|
|
424
633
|
});
|
|
425
634
|
// Then one continuation per input, in LEDGER ORDER — the validator walks
|
|
426
635
|
// inputs and outputs in lockstep, and the ledger sorts inputs by
|
|
@@ -448,10 +657,18 @@ export function dummySubstandard(config) {
|
|
|
448
657
|
if (stillToSeize > 0n) {
|
|
449
658
|
throw new Error(`Selected UTxOs hold ${quantity - stillToSeize} of ${quantity} ${unit} — selection is short`);
|
|
450
659
|
}
|
|
660
|
+
// The dispatcher's own withdraw-0 — new in alpha.3, and required on every
|
|
661
|
+
// programmable transaction. Its redeemer carries the act that used to be
|
|
662
|
+
// BaseSpendRedeemer's constructor.
|
|
663
|
+
tx = tx.withdraw({
|
|
664
|
+
stakeCredential: Credential.makeScriptHash(hexToBytes(ctx.standardScripts.programmableLogicGlobal.hash)),
|
|
665
|
+
amount: 0n,
|
|
666
|
+
redeemer: programmableLogicGlobalRedeemer("THIRD_PARTY"),
|
|
667
|
+
});
|
|
451
668
|
tx = tx.withdraw({
|
|
452
669
|
stakeCredential: Credential.makeScriptHash(hexToBytes(ctx.standardScripts.thirdParty.hash)),
|
|
453
670
|
amount: 0n,
|
|
454
|
-
redeemer: thirdPartyRedeemer(
|
|
671
|
+
redeemer: thirdPartyRedeemer(registryIdx, OUTPUTS_START_IDX),
|
|
455
672
|
});
|
|
456
673
|
tx = tx.withdraw({
|
|
457
674
|
stakeCredential: Credential.makeScriptHash(hexToBytes(transferScript.hash)),
|
|
@@ -460,6 +677,12 @@ export function dummySubstandard(config) {
|
|
|
460
677
|
});
|
|
461
678
|
tx = tx.readFrom({ referenceInputs: refUtxos });
|
|
462
679
|
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.thirdParty.compiledCode) });
|
|
680
|
+
// ⛔ THE DISPATCHER'S OWN SCRIPT WITNESS. A withdraw-0 needs the script in
|
|
681
|
+
// full, not just a redeemer — alpha.3 added the dispatcher's withdrawal to
|
|
682
|
+
// every programmable transaction, and S-6 wired the withdrawal and the
|
|
683
|
+
// redeemer but not this. The ledger says "An associated script witness is
|
|
684
|
+
// missing" on purpose=withdraw, which names the shape but not the script.
|
|
685
|
+
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.programmableLogicGlobal.compiledCode) });
|
|
463
686
|
tx = tx.attachScript({ script: buildEvoScript(transferScript.compiledCode) });
|
|
464
687
|
tx = tx.attachScript({
|
|
465
688
|
script: buildEvoScript(ctx.standardScripts.programmableLogicBase.compiledCode),
|
|
@@ -472,6 +695,9 @@ export function dummySubstandard(config) {
|
|
|
472
695
|
const { senderAddress, recipientAddress, tokenPolicyId, assetName, quantity } = params;
|
|
473
696
|
const unit = tokenPolicyId + assetName;
|
|
474
697
|
const client = ctx.client;
|
|
698
|
+
// min-UTxO is sized from live protocol parameters, not guessed — the asset
|
|
699
|
+
// name and the quantity are both caller-supplied and both widen the output.
|
|
700
|
+
const coinsPerUtxoByte = (await client.getProtocolParameters()).coinsPerUtxoByte;
|
|
475
701
|
const plbHash = ctx.standardScripts.programmableLogicBase.hash;
|
|
476
702
|
// 1. Build PLB addresses
|
|
477
703
|
const senderPlbAddr = baseAddress(networkId, plbHash, senderAddress);
|
|
@@ -486,8 +712,8 @@ export function dummySubstandard(config) {
|
|
|
486
712
|
const { selected, totalTokenAmount } = selectUtxosForAmount(tokenUtxos, unit, quantity);
|
|
487
713
|
const returningAmount = totalTokenAmount - quantity;
|
|
488
714
|
// 4. Find registry node reference input
|
|
489
|
-
const
|
|
490
|
-
const registryUtxos = await client.getUtxos(EvoAddress.fromBech32(
|
|
715
|
+
const registryAddr = scriptAddress(networkId, ctx.standardScripts.registry.hash);
|
|
716
|
+
const registryUtxos = await client.getUtxos(EvoAddress.fromBech32(registryAddr));
|
|
491
717
|
const registryUtxo = findRegistryNode(registryUtxos, tokenPolicyId);
|
|
492
718
|
if (!registryUtxo) {
|
|
493
719
|
throw new Error(`Registry node not found for policy ${tokenPolicyId}`);
|
|
@@ -510,18 +736,21 @@ export function dummySubstandard(config) {
|
|
|
510
736
|
hash: ctx.standardScripts.transfer.hash,
|
|
511
737
|
isScript: true,
|
|
512
738
|
};
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
const
|
|
739
|
+
// alpha.3: the DISPATCHER withdraws too, and PLB's wdrl_idx points at IT.
|
|
740
|
+
const plan = plbWithdrawalPlan({
|
|
741
|
+
plgHash: ctx.standardScripts.programmableLogicGlobal.hash,
|
|
742
|
+
others: [{ hash: transferScript.hash, isScript: true }, coreTransferKey],
|
|
743
|
+
});
|
|
744
|
+
const allWithdrawals = plan.all;
|
|
745
|
+
// params_idx is gone from the delegate redeemer — delegates no longer
|
|
746
|
+
// read the params datum at all.
|
|
747
|
+
const coreTransferRedeemer = transferRedeemer([
|
|
519
748
|
{ type: "exists", nodeIdx: registryIdx },
|
|
520
749
|
]);
|
|
521
750
|
const dummyTransferRedeemer = Data.int(200n);
|
|
522
|
-
// programmable_logic_base
|
|
523
|
-
//
|
|
524
|
-
const spendRdmr = baseSpendRedeemer(
|
|
751
|
+
// programmable_logic_base witnesses where the DISPATCHER's withdrawal
|
|
752
|
+
// sits; the dispatch choice itself is the dispatcher's own redeemer.
|
|
753
|
+
const spendRdmr = baseSpendRedeemer(paramsIdx, plan.plgIdx);
|
|
525
754
|
const tokenDatum = voidData();
|
|
526
755
|
// 8. Get sender's staking credential
|
|
527
756
|
const senderStakingHash = stakingCredentialHash(senderAddress);
|
|
@@ -536,6 +765,12 @@ export function dummySubstandard(config) {
|
|
|
536
765
|
amount: 0n,
|
|
537
766
|
redeemer: dummyTransferRedeemer,
|
|
538
767
|
});
|
|
768
|
+
// The dispatcher's own withdraw-0 — new in alpha.3.
|
|
769
|
+
tx = tx.withdraw({
|
|
770
|
+
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.programmableLogicGlobal.hash, "hex"))),
|
|
771
|
+
amount: 0n,
|
|
772
|
+
redeemer: programmableLogicGlobalRedeemer("TRANSFER"),
|
|
773
|
+
});
|
|
539
774
|
tx = tx.withdraw({
|
|
540
775
|
stakeCredential: Credential.makeScriptHash(new Uint8Array(Buffer.from(ctx.standardScripts.transfer.hash, "hex"))),
|
|
541
776
|
amount: 0n,
|
|
@@ -544,17 +779,31 @@ export function dummySubstandard(config) {
|
|
|
544
779
|
if (returningAmount > 0n) {
|
|
545
780
|
tx = tx.payToAddress({
|
|
546
781
|
address: EvoAddress.fromBech32(senderPlbAddr),
|
|
547
|
-
assets: outputAssets(
|
|
548
|
-
|
|
782
|
+
assets: outputAssets(minUtxoAtLeast(TOKEN_OUTPUT_FLOOR, {
|
|
783
|
+
address: senderPlbAddr,
|
|
784
|
+
assets: outputAssets(0n, new Map([[unit, returningAmount]])),
|
|
785
|
+
datum: voidData(),
|
|
786
|
+
coinsPerUtxoByte,
|
|
787
|
+
}), new Map([[unit, returningAmount]])), datum: new InlineDatum.InlineDatum({ data: tokenDatum }),
|
|
549
788
|
});
|
|
550
789
|
}
|
|
551
790
|
tx = tx.payToAddress({
|
|
552
791
|
address: EvoAddress.fromBech32(recipientPlbAddr),
|
|
553
|
-
assets: outputAssets(
|
|
554
|
-
|
|
792
|
+
assets: outputAssets(minUtxoAtLeast(TOKEN_OUTPUT_FLOOR, {
|
|
793
|
+
address: recipientPlbAddr,
|
|
794
|
+
assets: outputAssets(0n, new Map([[unit, quantity]])),
|
|
795
|
+
datum: voidData(),
|
|
796
|
+
coinsPerUtxoByte,
|
|
797
|
+
}), new Map([[unit, quantity]])), datum: new InlineDatum.InlineDatum({ data: tokenDatum }),
|
|
555
798
|
});
|
|
556
799
|
tx = tx.readFrom({ referenceInputs: [protocolParamsUtxo, registryUtxo] });
|
|
557
800
|
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.transfer.compiledCode) });
|
|
801
|
+
// ⛔ THE DISPATCHER'S OWN SCRIPT WITNESS. A withdraw-0 needs the script in
|
|
802
|
+
// full, not just a redeemer — alpha.3 added the dispatcher's withdrawal to
|
|
803
|
+
// every programmable transaction, and S-6 wired the withdrawal and the
|
|
804
|
+
// redeemer but not this. The ledger says "An associated script witness is
|
|
805
|
+
// missing" on purpose=withdraw, which names the shape but not the script.
|
|
806
|
+
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.programmableLogicGlobal.compiledCode) });
|
|
558
807
|
tx = tx.attachScript({ script: buildEvoScript(transferScript.compiledCode) });
|
|
559
808
|
tx = tx.attachScript({ script: buildEvoScript(ctx.standardScripts.programmableLogicBase.compiledCode) });
|
|
560
809
|
tx = tx.addSigner({ keyHash: KeyHash.fromHex(senderStakingHash) });
|