diff --git a/packages/mesh-contract/src/affirmation/index.ts b/packages/mesh-contract/src/affirmation/index.ts new file mode 100644 index 000000000..cbbc09dbb --- /dev/null +++ b/packages/mesh-contract/src/affirmation/index.ts @@ -0,0 +1 @@ +export * from './offchain'; diff --git a/packages/mesh-contract/src/affirmation/offchain.ts b/packages/mesh-contract/src/affirmation/offchain.ts new file mode 100644 index 000000000..c4f16a779 --- /dev/null +++ b/packages/mesh-contract/src/affirmation/offchain.ts @@ -0,0 +1,158 @@ +import { + applyCborEncoding, + deserializeDatum, + UTxO, + mConStr0, + resolveScriptHash, + serializePlutusScript, + serializeAddressObj,pubKeyAddress, + mConStr1 +} from '@meshsdk/core'; + +import { Address } from "@meshsdk/core-cst" + +import { MeshTxInitiator, MeshTxInitiatorInput } from "../common"; + +import blueprint from "./plutus.json"; + +export const MeshAffirmationBlueprint = blueprint; +type scriptInfo ={ + code: string, version: "V3", address: string, policyId: string +} +export class MeshAffirmationContract extends MeshTxInitiator { + constructor(inputs: MeshTxInitiatorInput) { + super(inputs); + this.languageVersion = 'V3'; + } + + // This just grabs the validator cbor and generates an address from it + getScript = (type: 'Mint' | 'Spend', beneficiaryKeyHash: string ,networkId: number ) => { + const scriptCbor=applyCborEncoding(blueprint?.validators[type=='Mint'?0:1]?.compiledCode || ''); + const policyId = resolveScriptHash(scriptCbor, "V3"); + let script:scriptInfo = {policyId: policyId, code: scriptCbor, version: "V3", ...serializePlutusScript( + {code: scriptCbor, version: "V3"}, + beneficiaryKeyHash, + networkId + )}; + return script; + }; + + affirm = async (beneficiary: any): Promise => { + const wallet = this.wallet; + if (!wallet) throw new Error('Wallet is needed'); + const { utxos, walletAddress, collateral } = + await this.getWalletInfoForTx(); + + const rewardAddress = (await wallet.getRewardAddresses())[0]; + + + if (!rewardAddress) throw new Error('Reward address is needed');; + const stakeAddrBech:string = walletAddress || ''; + const stakeAddr = Address.fromBech32(stakeAddrBech); + + const stakeAddrProps = stakeAddr.getProps(); + const stakeHash = stakeAddrProps.delegationPart?.hash || ""; + const stakeHashBin = Buffer.from(stakeHash,'hex'); + + + const mintingScript = this.getScript("Mint", beneficiary?.hash, await wallet.getNetworkId()); + if (!mintingScript.code) return ''; + const targetAddress = mintingScript.address; + + + const firstUtxo = utxos[0]; + if (firstUtxo === undefined) throw new Error("No UTXOs available"); + const remainingUtxos = utxos.slice(1); + + const redeemer =mConStr0([]); + + // {data: { alternative: 0, fields: [] }}; + const myDatum= { alternative: 0, fields: [stakeHash] }; + + await this.mesh + .txIn( + firstUtxo.input.txHash, + firstUtxo.input.outputIndex, + firstUtxo.output.amount, + firstUtxo.output.address, + ) + .mintPlutusScript(this.languageVersion) + .mint("1", mintingScript.policyId, stakeHashBin.toString('hex')) + .mintingScript(mintingScript.code) + .mintRedeemerValue(redeemer) + .txOut(targetAddress, [ + { unit: mintingScript.policyId + stakeHashBin.toString('hex'), quantity: "1" }, + ]) + .txOutInlineDatumValue(myDatum) + .changeAddress(walletAddress) + .txInCollateral( + collateral.input.txHash, + collateral.input.outputIndex, + collateral.output.amount, + collateral.output.address, + ) + .selectUtxosFrom(remainingUtxos,'keepRelevant','0') + .requiredSignerHash(stakeAddrProps.paymentPart?.hash || '') + .requiredSignerHash(stakeHash) + + .complete(); + + return this.mesh.txHex; + + }; + + revoke = async (scriptUtxo: UTxO): Promise => { + const wallet = this.wallet; + if (!wallet) throw new Error('Wallet is needed'); + + const { utxos, walletAddress, collateral } = + await this.getWalletInfoForTx(); + + const rewardAddress = (await wallet.getRewardAddresses())[0]; + + if (!rewardAddress) throw new Error('Reward address is needed');; + const stakeAddrBech:string = walletAddress || ''; + const stakeAddr = Address.fromBech32(stakeAddrBech); + const beneficiaryAddr = Address.fromBech32(scriptUtxo.output.address); + const beneficiary = beneficiaryAddr.getProps().delegationPart; + + const stakeAddrProps = stakeAddr.getProps(); + const stakeHash = stakeAddrProps.delegationPart?.hash || ""; + const stakeHashBin = Buffer.from(stakeHash,'hex'); + + const mintingScript = this.getScript("Mint", beneficiary?.hash || '', await wallet.getNetworkId()); + const spendingScript = this.getScript("Spend", beneficiary?.hash || '', await wallet.getNetworkId()); + if (!mintingScript.code) return ''; + + await this.mesh + .spendingPlutusScript(this.languageVersion) + .txIn( + scriptUtxo.input.txHash, + scriptUtxo.input.outputIndex, + scriptUtxo.output.amount, + scriptUtxo.output.address, + ) + .txInRedeemerValue(mConStr0([])) + .spendingReferenceTxInInlineDatumPresent() + .spendingReferenceTxInRedeemerValue("") + .txInScript(spendingScript.code) + .mintPlutusScript(this.languageVersion) + .mint("-1", mintingScript.policyId, stakeHashBin.toString('hex')) + .mintingScript(mintingScript.code) + .mintRedeemerValue(mConStr1([])) + .changeAddress(walletAddress) + .txInCollateral( + collateral.input.txHash, + collateral.input.outputIndex, + collateral.output.amount, + collateral.output.address, + ) + .selectUtxosFrom(utxos) + .requiredSignerHash(stakeAddrProps.paymentPart?.hash || '') + .requiredSignerHash(stakeHash) + .complete(); + return this.mesh.txHex; + + }; + +} \ No newline at end of file diff --git a/packages/mesh-contract/src/affirmation/plutus.json b/packages/mesh-contract/src/affirmation/plutus.json new file mode 100644 index 000000000..1d43a7026 --- /dev/null +++ b/packages/mesh-contract/src/affirmation/plutus.json @@ -0,0 +1,57 @@ +{ + "preamble": { + "title": "kieransimkin/affirmation-graph", + "description": "Aiken contracts for project 'affirmation-graph'", + "version": "1.0.0", + "plutusVersion": "v3", + "compiler": { + "name": "Aiken", + "version": "v1.1.4+79d0e45" + }, + "license": "Apache-2.0" + }, + "validators": [ + { + "title": "affirmation.v1.mint", + "redeemer": { + "title": "_redeemer", + "schema": { + "$ref": "#/definitions/Data" + } + }, + "compiledCode": "59060201010032323232323232322533300232323232323232323232323253233300f30070081323232323232325333019301c002132323253330193371e6eb8c0780100284c94ccc068c0300085288a99980d19b87480040085288a505333019300b00113232533301e3021002132533301c3370e9002180e9baa00113232533333302500213330150021533301e533301e3301400b375c602460406ea8008528899980f2514a0944526160010010010010011533333302400113330120011533301d533301d3301300a375c6022603e6ea8004528899980ea514a09445261616161616163021301e37540022c604060426042603a6ea800458c07c004c8cc004004024894ccc07800452f5c026464a66603a66ebcc044c07cdd51808980f9baa0023374a9001198109ba900e4bd7009981080119802002000899802002000981100118100008a99980ca99980c998078030010a5113330194a2941288a4c2c2c6eb4c074c078008dd7180e000980e0008b1bac301a0013007375660320046eb0c060c064c064c064c064004c060c060008dd6180b000980b180b18091baa00d375c602860226ea802454ccc03cc0040204c8c8c8c8c8c8c8c8c94ccc06cc0780084c8c8c94ccc06ccdc39bad302030210024800454ccc06cc04cc070dd50050991919299980f180b180f9baa0011323253330203375e602860446ea8c050c088dd5180098111baa0023374a9001198121ba90084bd7009925132325333333029007133301900713253330233371e0020122a666046602a6660066eacc010c094dd5180218129baa00500b0011533302353330233301900e00114a22666046945282511498585858dd7181398121baa0070010010010010011533333302800613330160061323232323253330263371e0080182a66604c66e212001002153330263371090001b8d0011533302630183330063756600e60506ea8c01cc0a0dd50040070020a99981329998131980e0088020a5113330264a2941288a4c2c2c2c2c2c6eb8c0a8c0ac008dd6981480098148011bae30270013023375400c2c2c2c2c2c44464a666048602c604a6ea8004520001375a6052604c6ea8004c94ccc090c058c094dd50008a60103d87a80001323300100137566054604e6ea8008894ccc0a4004530103d87a8000132323232533302a337220100042a66605466e3c0200084c038cc0b8dd4000a5eb80530103d87a8000133006006003375a60560066eb8c0a4008c0b4008c0ac004c8cc004004010894ccc0a00045300103d87a80001323232325333029337220100042a66605266e3c0200084c034cc0b4dd3000a5eb80530103d87a8000133006006003375660540066eb8c0a0008c0b0008c0a8004588c094c098004c08cc080dd50008b19198008008059129998110008a60103d87a80001323253330213375e602a60466ea80080444c014cc0940092f5c0266008008002604c00460480026e9520003020301d37540142c2c6eb8c07c004c07c008dd7180e8008b1bac301c0013009375660360046eb0c068c06cc06cc06cc06c004c068c068c068c068008dd6180c000980a1baa00f301630170023015001301137540122c6e1d20022323300100100222533301300114bd70099199911191980080080191299980c80088018991980d9ba73301b375200c66036603000266036603200297ae033003003301d002301b001375c60240026eacc04c004cc00c00cc05c008c0540048c0480048894ccc038c018c03cdd50018992999809000801099299999980b80080180180180189919299980a800802899299999980d00080300300309919299980c000804099299999980e800804804804804899299980d180e8018a8058051bae001301a001301a003375a00200c602e002602e0066eb8004c050004c040dd50018009119198008008019129998088008a50132533300f3371e6eb8c050008010528899801801800980a0009112999806180218069baa003132533301000100213253333330150010030030030031325333012301500315005004375c0026024002601c6ea800c004dc3a400060106ea8004c02cc030008c028004c028008c020004c010dd50008a4c26cacae6955ceaab9e5573eae815d0aba25749", + "hash": "780b8dcab8ec263281b810e26c4e5ef5e38a057c297c686661c75d4d" + }, + { + "title": "affirmation.v1.spend", + "datum": { + "title": "datum", + "schema": { + "$ref": "#/definitions/Data" + } + }, + "redeemer": { + "title": "_redeemer", + "schema": { + "$ref": "#/definitions/Data" + } + }, + "compiledCode": "59060201010032323232323232322533300232323232323232323232323253233300f30070081323232323232325333019301c002132323253330193371e6eb8c0780100284c94ccc068c0300085288a99980d19b87480040085288a505333019300b00113232533301e3021002132533301c3370e9002180e9baa00113232533333302500213330150021533301e533301e3301400b375c602460406ea8008528899980f2514a0944526160010010010010011533333302400113330120011533301d533301d3301300a375c6022603e6ea8004528899980ea514a09445261616161616163021301e37540022c604060426042603a6ea800458c07c004c8cc004004024894ccc07800452f5c026464a66603a66ebcc044c07cdd51808980f9baa0023374a9001198109ba900e4bd7009981080119802002000899802002000981100118100008a99980ca99980c998078030010a5113330194a2941288a4c2c2c6eb4c074c078008dd7180e000980e0008b1bac301a0013007375660320046eb0c060c064c064c064c064004c060c060008dd6180b000980b180b18091baa00d375c602860226ea802454ccc03cc0040204c8c8c8c8c8c8c8c8c94ccc06cc0780084c8c8c94ccc06ccdc39bad302030210024800454ccc06cc04cc070dd50050991919299980f180b180f9baa0011323253330203375e602860446ea8c050c088dd5180098111baa0023374a9001198121ba90084bd7009925132325333333029007133301900713253330233371e0020122a666046602a6660066eacc010c094dd5180218129baa00500b0011533302353330233301900e00114a22666046945282511498585858dd7181398121baa0070010010010010011533333302800613330160061323232323253330263371e0080182a66604c66e212001002153330263371090001b8d0011533302630183330063756600e60506ea8c01cc0a0dd50040070020a99981329998131980e0088020a5113330264a2941288a4c2c2c2c2c2c6eb8c0a8c0ac008dd6981480098148011bae30270013023375400c2c2c2c2c2c44464a666048602c604a6ea8004520001375a6052604c6ea8004c94ccc090c058c094dd50008a60103d87a80001323300100137566054604e6ea8008894ccc0a4004530103d87a8000132323232533302a337220100042a66605466e3c0200084c038cc0b8dd4000a5eb80530103d87a8000133006006003375a60560066eb8c0a4008c0b4008c0ac004c8cc004004010894ccc0a00045300103d87a80001323232325333029337220100042a66605266e3c0200084c034cc0b4dd3000a5eb80530103d87a8000133006006003375660540066eb8c0a0008c0b0008c0a8004588c094c098004c08cc080dd50008b19198008008059129998110008a60103d87a80001323253330213375e602a60466ea80080444c014cc0940092f5c0266008008002604c00460480026e9520003020301d37540142c2c6eb8c07c004c07c008dd7180e8008b1bac301c0013009375660360046eb0c068c06cc06cc06cc06c004c068c068c068c068008dd6180c000980a1baa00f301630170023015001301137540122c6e1d20022323300100100222533301300114bd70099199911191980080080191299980c80088018991980d9ba73301b375200c66036603000266036603200297ae033003003301d002301b001375c60240026eacc04c004cc00c00cc05c008c0540048c0480048894ccc038c018c03cdd50018992999809000801099299999980b80080180180180189919299980a800802899299999980d00080300300309919299980c000804099299999980e800804804804804899299980d180e8018a8058051bae001301a001301a003375a00200c602e002602e0066eb8004c050004c040dd50018009119198008008019129998088008a50132533300f3371e6eb8c050008010528899801801800980a0009112999806180218069baa003132533301000100213253333330150010030030030031325333012301500315005004375c0026024002601c6ea800c004dc3a400060106ea8004c02cc030008c028004c028008c020004c010dd50008a4c26cacae6955ceaab9e5573eae815d0aba25749", + "hash": "780b8dcab8ec263281b810e26c4e5ef5e38a057c297c686661c75d4d" + }, + { + "title": "affirmation.v1.else", + "redeemer": { + "schema": {} + }, + "compiledCode": "59060201010032323232323232322533300232323232323232323232323253233300f30070081323232323232325333019301c002132323253330193371e6eb8c0780100284c94ccc068c0300085288a99980d19b87480040085288a505333019300b00113232533301e3021002132533301c3370e9002180e9baa00113232533333302500213330150021533301e533301e3301400b375c602460406ea8008528899980f2514a0944526160010010010010011533333302400113330120011533301d533301d3301300a375c6022603e6ea8004528899980ea514a09445261616161616163021301e37540022c604060426042603a6ea800458c07c004c8cc004004024894ccc07800452f5c026464a66603a66ebcc044c07cdd51808980f9baa0023374a9001198109ba900e4bd7009981080119802002000899802002000981100118100008a99980ca99980c998078030010a5113330194a2941288a4c2c2c6eb4c074c078008dd7180e000980e0008b1bac301a0013007375660320046eb0c060c064c064c064c064004c060c060008dd6180b000980b180b18091baa00d375c602860226ea802454ccc03cc0040204c8c8c8c8c8c8c8c8c94ccc06cc0780084c8c8c94ccc06ccdc39bad302030210024800454ccc06cc04cc070dd50050991919299980f180b180f9baa0011323253330203375e602860446ea8c050c088dd5180098111baa0023374a9001198121ba90084bd7009925132325333333029007133301900713253330233371e0020122a666046602a6660066eacc010c094dd5180218129baa00500b0011533302353330233301900e00114a22666046945282511498585858dd7181398121baa0070010010010010011533333302800613330160061323232323253330263371e0080182a66604c66e212001002153330263371090001b8d0011533302630183330063756600e60506ea8c01cc0a0dd50040070020a99981329998131980e0088020a5113330264a2941288a4c2c2c2c2c2c6eb8c0a8c0ac008dd6981480098148011bae30270013023375400c2c2c2c2c2c44464a666048602c604a6ea8004520001375a6052604c6ea8004c94ccc090c058c094dd50008a60103d87a80001323300100137566054604e6ea8008894ccc0a4004530103d87a8000132323232533302a337220100042a66605466e3c0200084c038cc0b8dd4000a5eb80530103d87a8000133006006003375a60560066eb8c0a4008c0b4008c0ac004c8cc004004010894ccc0a00045300103d87a80001323232325333029337220100042a66605266e3c0200084c034cc0b4dd3000a5eb80530103d87a8000133006006003375660540066eb8c0a0008c0b0008c0a8004588c094c098004c08cc080dd50008b19198008008059129998110008a60103d87a80001323253330213375e602a60466ea80080444c014cc0940092f5c0266008008002604c00460480026e9520003020301d37540142c2c6eb8c07c004c07c008dd7180e8008b1bac301c0013009375660360046eb0c068c06cc06cc06cc06c004c068c068c068c068008dd6180c000980a1baa00f301630170023015001301137540122c6e1d20022323300100100222533301300114bd70099199911191980080080191299980c80088018991980d9ba73301b375200c66036603000266036603200297ae033003003301d002301b001375c60240026eacc04c004cc00c00cc05c008c0540048c0480048894ccc038c018c03cdd50018992999809000801099299999980b80080180180180189919299980a800802899299999980d00080300300309919299980c000804099299999980e800804804804804899299980d180e8018a8058051bae001301a001301a003375a00200c602e002602e0066eb8004c050004c040dd50018009119198008008019129998088008a50132533300f3371e6eb8c050008010528899801801800980a0009112999806180218069baa003132533301000100213253333330150010030030030031325333012301500315005004375c0026024002601c6ea800c004dc3a400060106ea8004c02cc030008c028004c028008c020004c010dd50008a4c26cacae6955ceaab9e5573eae815d0aba25749", + "hash": "780b8dcab8ec263281b810e26c4e5ef5e38a057c297c686661c75d4d" + } + ], + "definitions": { + "Data": { + "title": "Data", + "description": "Any Plutus data." + } + } +} \ No newline at end of file diff --git a/packages/mesh-contract/src/affirmation/readme.md b/packages/mesh-contract/src/affirmation/readme.md new file mode 100644 index 000000000..ab387e084 --- /dev/null +++ b/packages/mesh-contract/src/affirmation/readme.md @@ -0,0 +1,3 @@ +# Affirmation contract +## part of the [Affirmation Graph](https://github.com/kieransimkin/affirmation-graph) project by Kieran Simkin + diff --git a/packages/mesh-contract/src/index.ts b/packages/mesh-contract/src/index.ts index 35882adb4..bd0a56c1e 100644 --- a/packages/mesh-contract/src/index.ts +++ b/packages/mesh-contract/src/index.ts @@ -7,3 +7,4 @@ export * from "./payment-splitter"; export * from "./plutus-nft"; export * from "./swap"; export * from "./vesting"; +export * from "./affirmation"; \ No newline at end of file diff --git a/packages/mesh-wallet/src/embedded/index.ts b/packages/mesh-wallet/src/embedded/index.ts index 7fec8afb5..673fe33ce 100644 --- a/packages/mesh-wallet/src/embedded/index.ts +++ b/packages/mesh-wallet/src/embedded/index.ts @@ -281,16 +281,17 @@ export class EmbeddedWallet extends WalletStaticMethods { } } - signTx(unsignedTx: string, accountIndex = 0, keyIndex = 0): VkeyWitness { + signTx(unsignedTx: string, accountIndex = 0, keyIndex = 0, useStake=false): VkeyWitness { try { const txHash = deserializeTxHash(resolveTxHash(unsignedTx)); - const { paymentKey } = this.getAccount(accountIndex, keyIndex); + const { paymentKey, stakeKey } = this.getAccount(accountIndex, keyIndex); + const useKey = useStake ? stakeKey : paymentKey const vKeyWitness = new VkeyWitness( - Ed25519PublicKeyHex(paymentKey.toPublicKey().toBytes().toString("hex")), + Ed25519PublicKeyHex(useKey.toPublicKey().toBytes().toString("hex")), Ed25519SignatureHex( - paymentKey.sign(Buffer.from(txHash, "hex")).toString("hex"), + useKey.sign(Buffer.from(txHash, "hex")).toString("hex"), ), ); diff --git a/packages/mesh-wallet/src/mesh/index.ts b/packages/mesh-wallet/src/mesh/index.ts index a3d0b8ad1..3ab9a39f3 100644 --- a/packages/mesh-wallet/src/mesh/index.ts +++ b/packages/mesh-wallet/src/mesh/index.ts @@ -27,6 +27,7 @@ import { toAddress, toTxUnspentOutput, TransactionUnspentOutput, + VkeyWitness, } from "@meshsdk/core-cst"; import { Transaction } from "@meshsdk/transaction"; @@ -334,7 +335,7 @@ export class MeshWallet implements IInitiator, ISigner, ISubmitter { * @param partialSign - if the transaction is partially signed (default: false) * @returns a signed transaction in CBOR */ - signTx(unsignedTx: string, partialSign = false): string { + signTx(unsignedTx: string, partialSign = false, useStake=false): string { if (!this._wallet) { throw new Error( "[MeshWallet] Read only wallet does not support signing data.", @@ -350,14 +351,21 @@ export class MeshWallet implements IInitiator, ISigner, ISubmitter { throw new Error( "Signatures already exist in the transaction in a non partial sign call", ); - - const newSignatures = this._wallet.signTx( + const newSignatures:VkeyWitness[] = [ this._wallet.signTx( unsignedTx, this._accountIndex, this._keyIndex, - ); + )]; + if (useStake) { + newSignatures.push(this._wallet.signTx( + unsignedTx, + this._accountIndex, + this._keyIndex, + true + )); + } - let signedTx = EmbeddedWallet.addWitnessSets(unsignedTx, [newSignatures]); + let signedTx = EmbeddedWallet.addWitnessSets(unsignedTx, newSignatures); return signedTx; }