Apply parameters on plutus scripts
Many plutus scripts are compiled first without all its parameters fixed. Then we can "apply parameters" to them to get their final form before using the scripts.
On most offchain frameworks, there is a function to do this.
- lucid evo: https://github.com/Anastasia-Labs/lucid-evolution/blob/81c450f1773da6c6c283b959b027b93ccddcfd01/packages/uplc/src/node/uplc_tx.d.ts#L31
- mesh: https://github.com/MeshJS/mesh/blob/fdf5fd4b0ff71c71cd1b855cc6c7de88225a9919/packages/mesh-core/src/core.ts#L13
- elm-cardano: https://github.com/elm-cardano/elm-cardano/blob/97fa1f56da482ae7c6e580677f6807aee6db3fb9/src/Cardano/Uplc.elm#L142
It would be very useful if we had the capabilities to do that with pycardano. I’ve seen that opshin can do it, but opshin isn’t maintained up-to-date with the chain and doesn’t support PlutusV3 it seems.
In case useful, the next version of Aiken will include this PR, which enables usage of aiken blueprint apply --in some/plutus.json ... outside of an aiken project. This is pretty convenient to call it with subprocess.run() in python with just the path to a blueprint json file instead of needing to be inside an aiken project directory. https://github.com/aiken-lang/aiken/pull/1163
The Opshin repository contains a Plutus contract class that can load blueprints and apply parameters: https://github.com/OpShin/opshin/blob/56926395a799810bc3106d5fc3cba0f4c2a68790/opshin/builder.py#L146
It might make sense to merge this into pycardano if it is used more since it is independent of which language the contracts were written in.
I'm interested in this too, but not confident enough with Plutus to try mixing OpShin and PyCardano code yet. Is the best thing for now to use aiken blueprint apply via subprocess?
I would assume that yes, Aiken blueprint apply is the most stable tool currently
Here's a hacky first attempt in case it helps someone. My use case involves applying a string and then an output reference. I'm not sure how to do the hex encodings in general yet.
from pycardano import *
from dataclass import dataclass
from typing import List
import tempfile
import subprocess
@dataclass
class OutputReferenceHack(PlutusData):
CONSTR_ID = 0
transaction_id: bytes
index: int
def aiken_blueprint_apply_hex_params(plutus_json_path: str, hex_params: List[str]) -> dict:
"""
Apply a list of hex-encoded parameters to a Plutus blueprint using `aiken blueprint apply`.
:param plutus_json_path: Path to the initial plutus.json file
:param hex_params: List of parameters to apply (as hex-encoded strings)
:return: Fully parameterized blueprint as a dictionary
**Example**::
>>> desc = cbor2.dumps(b'my cool validator').hex()
>>> oref = OutputReferenceHack(utxo.input.transaction_id.to_cbor(), utxo.input.index).to_cbor().hex()
>>> blueprint = aiken_blueprint_apply_hex_params('./plutus.json', [desc, oref])
"""
with tempfile.NamedTemporaryFile(mode='w+', delete=False, suffix='.json') as temp_out:
temp_out_path = temp_out.name
current_blueprint_path = plutus_json_path
for hex_param in hex_params:
cmd = [
'aiken', 'blueprint', 'apply',
'--in', current_blueprint_path,
hex_param,
'--out', temp_out_path
]
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
err_msg = f"Blueprint application failed for parameter {hex_param}: {result.stderr}"
raise RuntimeError(err_msg)
current_blueprint_path = temp_out_path
with open(current_blueprint_path, 'r') as f:
final_blueprint = json.load(f)
return final_blueprint
It might make sense to merge this into pycardano if it is used more since it is independent of which language the contracts were written in.
Conceptually it makes sense, but it seems like we can't simply copy apply_parameter out of the box from opshin to pycardano because it would cause circular dependencies.