Emurgo / cardano-serialization-lib

This is a library, written in Rust, for serialization & deserialization of data structures used in Cardano's Haskell implementation of Alonzo along with useful utility functions.
Other
234 stars 125 forks source link

Example how to interact with smart contract once token is locked #440

Open AdamMachera opened 2 years ago

AdamMachera commented 2 years ago

Hi guys,

I'm trying to migrate my marketplace from version 9.1.4 to version 10.1.0.

I was able to lock token in the smartcontract

export const offer = async (api: CardanoApi, policyId: string, assetName: string, askingPrice: string): Promise<string> => {
    const cardano = cardanoApiAdapter(api)
    const protocolParameters = await getProtocolProtocolParams()
    setupCoinSelector(protocolParameters)

    const txBuilder = await createTransactionBuilder(protocolParameters)
    const selfAddress = await cardano.getWalletAddress()
    const baseAddress = BaseAddress.from_address(selfAddress)!
    const pkh = toHex(baseAddress.payment_cred().to_keyhash()?.to_bytes() as Uint8Array);

    const transactionWitnessSet = TransactionWitnessSet.new();

    const offerDatum = createSalesOfferDatum(pkh, askingPrice, policyId, assetName);
    const offerDatumHash = hash_plutus_data(toPlutusData(offerDatum));
    //console.log(`offerDatumHash ${offerDatumHash} pkh: ${pkh} askingPrice: ${askingPrice} policyId: ${policyId} assetName: ${assetName}`);
    const contractOutput = TransactionOutput.new(
        getContractAddress(),
        getContractOutput(nftMoveFixedPrice, policyId, assetName)
    );
    contractOutput.set_data_hash(offerDatumHash);

    const utxos  = TransactionUnspentOutputs.new();
    const walletOutputs  = await cardano.getUtxos();
    walletOutputs.forEach(utxo => utxos.add(utxo))

    const aux_data: AuxiliaryData = await addOfferMetadata(txBuilder, selfAddress, askingPrice, assetName, policyId, "offer")

    txBuilder.add_output(contractOutput);
    txBuilder.add_inputs_from(utxos, 3)
    txBuilder.add_change_if_needed(selfAddress);
    const txBody = txBuilder.build();

    const transaction = Transaction.new(
        txBody,
        transactionWitnessSet,
        aux_data
    );

    logTxDetails(transaction)

    let signedtxVkeyWitnesses: TransactionWitnessSet
    try {
        signedtxVkeyWitnesses = await cardano.signTx(transaction, true);
    }
    catch (err) {
        throw handleSignError(err)
    }

    transactionWitnessSet.set_vkeys(signedtxVkeyWitnesses.vkeys()!);

    const signedTx = Transaction.new(
        txBody,
        transactionWitnessSet,
        transaction.auxiliary_data()
    );

    try {
        const txHash = await cardano.submitTx(signedTx);
        console.log(`txHash for sales offer: ${txHash}`);
        return txHash
    } catch (err) {
        appInsights.trackException({ exception: err as Error }, {
            sellerAddress: selfAddress.to_bech32(),
            sellerPkh: pkh,
            policyId: policyId,
            assetName: assetName,
            price: askingPrice,
            method: "offer"
        });
        throw err;
    }
}

However I'm struggling to figure out what I'm doing wrong when creating e.g. cancelation transaction (commented out code is some leftovers from the version 9.1.4):


export const cancel = async (api: CardanoApi, policyId: string, assetName: string): Promise<string> => {
    const cardano = cardanoApiAdapter(api)

    const protocolParameters = await getProtocolProtocolParams()
    setupCoinSelector(protocolParameters)

    console.log('cancel start1')
    const txBuilder = await createTransactionBuilder(protocolParameters)
    const unspentOutput = await getUnspentTransactionUtxoForNft(getContractAddress(), policyId, assetName);
    const transactionWitnessSet = TransactionWitnessSet.new();
    const selfAddress = Address.from_bech32(unspentOutput.metadata?.addr.join("") as string)
    const baseAddress = BaseAddress.from_address(selfAddress)!
    const pkh = toHex(baseAddress.payment_cred().to_keyhash()?.to_bytes() as Uint8Array);

    var scriptLockedValue = await getContractOutput(nftMoveFixedPrice, policyId, assetName)
    const outputs: TransactionOutput[] = [
        TransactionOutput.new(
            selfAddress,
            scriptLockedValue
        )
    ];

    const aux_data: AuxiliaryData = await addOfferMetadata(txBuilder, selfAddress, unspentOutput.metadata?.price as string, assetName, policyId, "cancel")
    const scriptInputIndex = unspentOutput.scriptUtxo.input().index();
    const utxos  = TransactionUnspentOutputs.new();
    const walletOutputs  = await cardano.getUtxos();
    walletOutputs.forEach(utxo => utxos.add(utxo))
    utxos.add(unspentOutput.scriptUtxo)
    // txBuilder.add_input(getContractAddress(), unspentOutput.scriptUtxo.input(), scriptLockedValue)
    txBuilder.add_output(outputs[0]);
    txBuilder.add_inputs_from(utxos, 3)
    txBuilder.add_change_if_needed(selfAddress);
    txBuilder.set_auxiliary_data(aux_data)

    const requiredSigners = Ed25519KeyHashes.new();
    requiredSigners.add(baseAddress.payment_cred().to_keyhash() as Ed25519KeyHash);
    // txBuilder.set_required_signers(requiredSigners);

    const salesOfferDatum = createSalesOfferDatum(pkh, unspentOutput.metadata?.price as string, policyId, assetName);
    const datum = toPlutusData(salesOfferDatum);
    const datumList = PlutusList.new();
    datumList.add(datum);

    const redeemers = Redeemers.new();
    redeemers.add(createRedeemer(scriptInputIndex, RedeemerType.Close));
    // txBuilder.set_plutus_scripts(getContractScript());
    // txBuilder.set_plutus_data(datumList);
    // txBuilder.set_redeemers(redeemers);

    transactionWitnessSet.set_plutus_scripts(getContractScript());
    transactionWitnessSet.set_plutus_data(datumList);
    transactionWitnessSet.set_redeemers(redeemers);

    const plutusWitness: PlutusWitness = PlutusWitness.new(getContractScriptV2(), datum, createRedeemer(scriptInputIndex, RedeemerType.Close))
    const witnesses = PlutusWitnesses.new()
    witnesses.add(plutusWitness)
    txBuilder.add_required_plutus_input_scripts(witnesses)

    console.log('cancel start4')
    const collateralUnspentTransactions = await getCollateralUnspentTransactionOutput(api);
    const collateralInputs = TransactionInputs.new();
    collateralUnspentTransactions.forEach(c => collateralInputs.add(c.input()));
    // txBuilder.set_collateral(collateralInputs);
    // txBuilder.add_change_if_needed(selfAddress);
    // txBuilder.set_fee(BigNum.from_str("1200000"))

    console.log('cancel start5')
    const transaction = txBuilder.build_tx();
    transaction.body().set_required_signers(requiredSigners)
    transaction.body().set_collateral(collateralInputs)
    // txBody.set_required_signers(requiredSigners);
    // txBody.set_collateral(collateralInputs);
    // const transaction = Transaction.new(
    //     txBody,
    //     transactionWitnessSet,
    //     aux_data
    // );

    let signedtxVkeyWitnesses: TransactionWitnessSet
    try {
        signedtxVkeyWitnesses = await cardano.signTx(transaction, true);
    }
    catch (err) {
        console.log(err)
        throw handleSignError(err)
    }

    transactionWitnessSet.set_vkeys(signedtxVkeyWitnesses.vkeys()!);
    const signedTx = Transaction.new(
        transaction.body(),
        transactionWitnessSet,
        transaction.auxiliary_data()
    );

    logTxDetails(signedTx)

    try
    {
        let txHash = await cardano.submitTx(signedTx);
        console.log(`cancel txhash: ${txHash}`)
        return txHash
    } catch (err) {
        console.log(err)
        appInsights.trackException({ exception: err as Error }, {
            sellerAddress: selfAddress.to_bech32(),
            sellerPkh: pkh,
            policyId: policyId,
            assetName: assetName,
            price: unspentOutput.metadata?.price as string,
            method: "cancel"
        });
        throw err;
    }
}

It would be nice if you can provide example of e.g. locking some funds with datum in SC and how later spent from SC using datum and redeemer.

Currently I'm getting "Not enought ADA leftover to include non-ADA assets in a change address" so probably I'm passing some wrong inputs but I'm passing utxos from wallet + adding input and its hash from SC on which NFT is laying.

jinglescode commented 1 year ago

@AdamMachera, a guide on how to lock and unlock token from smart contract, datum and redeemer.