UNPKG

o1js

Version:

TypeScript framework for zk-SNARKs and zkApps

237 lines 8.41 kB
import { ZkappCommand, AccountUpdate, AccountUpdateLayout, addMissingSignatures, TokenId, addMissingProofs, } from './account-update.js'; import { PublicKey } from '../../provable/crypto/signature.js'; import { UInt32, UInt64 } from '../../provable/int.js'; import { currentTransaction } from './transaction-context.js'; import { Provable } from '../../provable/provable.js'; import { assertPreconditionInvariants } from './precondition.js'; import { activeInstance } from './mina-instance.js'; import * as Fetch from './fetch.js'; import { sendZkappQuery } from './graphql.js'; import { assertPromise } from '../../util/assert.js'; import { getTotalTimeRequired } from './transaction-validation.js'; export { Transaction, createTransaction, toTransactionPromise, toPendingTransactionPromise, sendTransaction, newTransaction, getAccount, transaction, createRejectedTransaction, createIncludedTransaction, }; var Transaction; (function (Transaction) { function fromJSON(json) { let transaction = ZkappCommand.fromJSON(json); return newTransaction(transaction, activeInstance.proofsEnabled); } Transaction.fromJSON = fromJSON; })(Transaction || (Transaction = {})); function toTransactionPromise(getPromise) { const pending = getPromise().then(); return Object.assign(pending, { sign(...args) { return toTransactionPromise(() => pending.then((v) => v.sign(...args))); }, send() { return toPendingTransactionPromise(() => pending.then((v) => v.send())); }, prove() { return toTransactionPromise(() => pending.then((v) => v.prove())); }, proofs() { return pending.then((v) => v.proofs); }, }); } function toPendingTransactionPromise(getPromise) { const pending = getPromise().then(); return Object.assign(pending, { wait(...args) { return pending.then((v) => v.wait(...args)); }, }); } async function createTransaction(feePayer, f, numberOfRuns, { fetchMode = 'cached', isFinalRunOutsideCircuit = true, proofsEnabled = true } = {}) { if (currentTransaction.has()) { throw new Error('Cannot start new transaction within another transaction'); } let feePayerSpec; if (feePayer === undefined) { feePayerSpec = {}; } else if (feePayer instanceof PublicKey) { feePayerSpec = { sender: feePayer }; } else { feePayerSpec = feePayer; } let { sender, fee, memo = '', nonce } = feePayerSpec; let transactionId = currentTransaction.enter({ sender, layout: new AccountUpdateLayout(), fetchMode, isFinalRunOutsideCircuit, numberOfRuns, }); // run circuit try { if (fetchMode === 'test') { await Provable.runUnchecked(async () => { await assertPromise(f()); Provable.asProver(() => { let tx = currentTransaction.get(); tx.layout.toConstantInPlace(); }); }); } else { await assertPromise(f()); } } catch (err) { currentTransaction.leave(transactionId); throw err; } let accountUpdates = currentTransaction.get().layout.toFlatList({ mutate: true }); try { // check that on-chain values weren't used without setting a precondition for (let accountUpdate of accountUpdates) { assertPreconditionInvariants(accountUpdate); } } catch (err) { currentTransaction.leave(transactionId); throw err; } let feePayerAccountUpdate; if (sender !== undefined) { // if senderKey is provided, fetch account to get nonce and mark to be signed let nonce_; let senderAccount = getAccount(sender, TokenId.default); if (nonce === undefined) { nonce_ = senderAccount.nonce; } else { nonce_ = UInt32.from(nonce); senderAccount.nonce = nonce_; Fetch.addCachedAccount(senderAccount); } feePayerAccountUpdate = AccountUpdate.defaultFeePayer(sender, nonce_); if (fee !== undefined) { feePayerAccountUpdate.body.fee = fee instanceof UInt64 ? fee : UInt64.from(String(fee)); } } else { // otherwise use a dummy fee payer that has to be filled in later feePayerAccountUpdate = AccountUpdate.dummyFeePayer(); } let transaction = { accountUpdates, feePayer: feePayerAccountUpdate, memo, }; currentTransaction.leave(transactionId); return newTransaction(transaction, proofsEnabled); } function newTransaction(transaction, proofsEnabled) { let self = { transaction, sign(privateKeys) { self.transaction = addMissingSignatures(self.transaction, privateKeys); return self; }, prove() { return toTransactionPromise(async () => { let { zkappCommand, proofs } = await addMissingProofs(self.transaction, { proofsEnabled, }); self.transaction = zkappCommand; return Object.assign(self, { proofs, }); }); }, toJSON() { let json = ZkappCommand.toJSON(self.transaction); return JSON.stringify(json); }, toPretty() { return ZkappCommand.toPretty(self.transaction); }, toGraphqlQuery() { return sendZkappQuery(self.toJSON()); }, send() { return toPendingTransactionPromise(async () => { const pendingTransaction = await sendTransaction(self); if (pendingTransaction.errors.length > 0) { throw Error(`Transaction failed with errors:\n- ${pendingTransaction.errors.join('\n- ')}`); } return pendingTransaction; }); }, async safeSend() { const pendingTransaction = await sendTransaction(self); if (pendingTransaction.errors.length > 0) { return createRejectedTransaction(pendingTransaction, pendingTransaction.errors); } return pendingTransaction; }, setFeePerSnarkCost(newFeePerSnarkCost) { let { totalTimeRequired } = getTotalTimeRequired(transaction.accountUpdates); return this.setFee(new UInt64(Math.round(totalTimeRequired * newFeePerSnarkCost))); }, setFee(newFee) { return toTransactionPromise(async () => { self = self; self.transaction.accountUpdates.forEach((au) => { if (au.body.useFullCommitment.toBoolean()) { au.authorization.signature = undefined; au.lazyAuthorization = { kind: 'lazy-signature' }; } }); self.transaction.feePayer.body.fee = newFee; self.transaction.feePayer.lazyAuthorization = { kind: 'lazy-signature' }; return self; }); }, }; return self; } function transaction(senderOrF, fOrUndefined) { let sender; let f; if (fOrUndefined !== undefined) { sender = senderOrF; f = fOrUndefined; } else { sender = undefined; f = senderOrF; } return activeInstance.transaction(sender, f); } // TODO: should we instead constrain to `Transaction<true, true>`? async function sendTransaction(txn) { return await activeInstance.sendTransaction(txn); } /** * @return The account data associated to the given public key. */ function getAccount(publicKey, tokenId) { return activeInstance.getAccount(publicKey, tokenId); } function createRejectedTransaction({ transaction, data, toJSON, toPretty, hash }, errors) { return { status: 'rejected', errors, transaction, toJSON, toPretty, hash, data, }; } function createIncludedTransaction({ transaction, data, toJSON, toPretty, hash, }) { return { status: 'included', transaction, toJSON, toPretty, hash, data, }; } //# sourceMappingURL=transaction.js.map