o1js
Version:
TypeScript framework for zk-SNARKs and zkApps
591 lines (560 loc) • 13.7 kB
text/typescript
import { Types } from '../../../bindings/mina-transaction/v1/types.js';
import { UInt32 } from '../../provable/int.js';
import { TokenId, type ZkappCommand } from './account-update.js';
import type { ActionStatesStringified } from './fetch.js';
export {
accountQuery,
currentSlotQuery,
genesisConstantsQuery,
getActionsQuery,
getEventsQuery,
lastBlockQuery,
lastBlockQueryFailureCheck,
removeJsonQuotes,
sendZkappQuery,
transactionStatusQuery,
type ActionQueryResponse,
type ActionsQueryInputs,
type CurrentSlotResponse,
type DepthOptions,
type EpochData,
type EventActionFilterOptions,
type EventQueryResponse,
type EventsQueryInputs,
type FailureReasonResponse,
type FetchedAccount,
type FetchedAccountResponse,
type FetchedAction,
type FetchedBlock,
type GenesisConstantsResponse,
type LastBlockQueryFailureCheckResponse,
type LastBlockQueryResponse,
type SendZkAppResponse,
type TransactionDepthInfo,
type TransactionStatus,
type TransactionStatusQueryResponse,
};
// removes the quotes on JSON keys
function removeJsonQuotes(json: string) {
let cleaned = JSON.stringify(JSON.parse(json), null, 2);
return cleaned.replace(/\"(\S+)\"\s*:/gm, '$1:');
}
type ActionsQueryInputs = {
/** Public key of the ZkApp to which actions have been emitted */
publicKey: string;
actionStates?: ActionStatesStringified;
tokenId?: string;
/** Block number to query from */
from?: number;
/** Block number to query to */
to?: number;
};
type EventsQueryInputs = {
/** Public key of the ZkApp to which events have been emitted */
publicKey: string;
tokenId?: string;
/** Block number to query from */
from?: number;
/** Block number to query to */
to?: number;
};
type AuthRequired = Types.Json.AuthRequired;
// TODO auto-generate this type and the query
type FetchedAccount = {
publicKey: string;
token: string;
nonce: string;
balance: { total: string };
tokenSymbol: string | null;
receiptChainHash: string | null;
timing: {
initialMinimumBalance: string | null;
cliffTime: string | null;
cliffAmount: string | null;
vestingPeriod: string | null;
vestingIncrement: string | null;
};
permissions: {
editState: AuthRequired;
access: AuthRequired;
send: AuthRequired;
receive: AuthRequired;
setDelegate: AuthRequired;
setPermissions: AuthRequired;
setVerificationKey: {
auth: AuthRequired;
txnVersion: string;
};
setZkappUri: AuthRequired;
editActionState: AuthRequired;
setTokenSymbol: AuthRequired;
incrementNonce: AuthRequired;
setVotingFor: AuthRequired;
setTiming: AuthRequired;
} | null;
delegateAccount: { publicKey: string } | null;
votingFor: string | null;
zkappState: string[] | null;
verificationKey: { verificationKey: string; hash: string } | null;
actionState: string[] | null;
provedState: boolean | null;
zkappUri: string | null;
};
type FetchedAccountResponse = {
account: FetchedAccount;
};
type EpochData = {
ledger: {
hash: string;
totalCurrency: string;
};
seed: string;
startCheckpoint: string;
lockCheckpoint: string;
epochLength: string;
};
type LastBlockQueryResponse = {
bestChain: {
protocolState: {
blockchainState: {
snarkedLedgerHash: string;
stagedLedgerHash: string;
date: string;
utcDate: string;
stagedLedgerProofEmitted: boolean;
};
previousStateHash: string;
consensusState: {
blockHeight: string;
slotSinceGenesis: string;
slot: string;
nextEpochData: EpochData;
stakingEpochData: EpochData;
epochCount: string;
minWindowDensity: string;
totalCurrency: string;
epoch: string;
};
};
}[];
};
type FailureReasonResponse = {
failures: string[];
index: number;
}[];
type LastBlockQueryFailureCheckResponse = {
bestChain: {
transactions: {
zkappCommands: {
hash: string;
failureReason: FailureReasonResponse;
}[];
};
stateHash: string;
protocolState: {
consensusState: {
blockHeight: string;
epoch: string;
slotSinceGenesis: string;
};
previousStateHash: string;
};
}[];
};
type FetchedBlock = {
protocolState: {
blockchainState: {
snarkedLedgerHash: string; // hash-like encoding
stagedLedgerHash: string; // hash-like encoding
date: string; // String(Date.now())
utcDate: string; // String(Date.now())
stagedLedgerProofEmitted: boolean; // bool
};
previousStateHash: string; // hash-like encoding
consensusState: {
blockHeight: string; // String(number)
slotSinceGenesis: string; // String(number)
slot: string; // String(number)
nextEpochData: {
ledger: {
hash: string; // hash-like encoding
totalCurrency: string; // String(number)
};
seed: string; // hash-like encoding
startCheckpoint: string; // hash-like encoding
lockCheckpoint: string; // hash-like encoding
epochLength: string; // String(number)
};
stakingEpochData: {
ledger: {
hash: string; // hash-like encoding
totalCurrency: string; // String(number)
};
seed: string; // hash-like encoding
startCheckpoint: string; // hash-like encoding
lockCheckpoint: string; // hash-like encoding
epochLength: string; // String(number)
};
epochCount: string; // String(number)
minWindowDensity: string; // String(number)
totalCurrency: string; // String(number)
epoch: string; // String(number)
};
};
};
type GenesisConstantsResponse = {
genesisConstants: {
genesisTimestamp: string;
coinbase: string;
accountCreationFee: string;
};
daemonStatus: {
consensusConfiguration: {
epochDuration: string;
k: string;
slotDuration: string;
slotsPerEpoch: string;
};
};
};
type CurrentSlotResponse = {
bestChain: Array<{
protocolState: {
consensusState: {
slot: number;
slotSinceGenesis: number;
};
};
}>;
};
/**
* INCLUDED: A transaction that is on the longest chain
*
* PENDING: A transaction either in the transition frontier or in transaction pool but is not on the longest chain
*
* UNKNOWN: The transaction has either been snarked, reached finality through consensus or has been dropped
*
*/
type TransactionStatus = 'INCLUDED' | 'PENDING' | 'UNKNOWN';
type TransactionStatusQueryResponse = {
transactionStatus: TransactionStatus;
};
/**
* Information about a transaction's depth (confirmation count) in the blockchain.
* Depth represents how many blocks have been built on top of the block containing the transaction.
*
* @see https://docs.minaprotocol.com/mina-protocol/lifecycle-of-a-payment
*/
type TransactionDepthInfo = {
/** Number of blocks built on top of the block containing the transaction (0 = just included) */
depth: number;
/** Block height at which the transaction was included */
inclusionBlockHeight: number;
/** Current block height of the chain */
currentBlockHeight: number;
/** Whether the transaction has reached finality (depth >= finalityThreshold) */
isFinalized: boolean;
/** The finality threshold used for this calculation */
finalityThreshold: number;
};
/**
* Options for querying transaction depth.
*/
type DepthOptions = {
/** Number of blocks to search for the transaction (default: 20) */
blockLength?: number;
/**
* Number of blocks required for finality (default: 15).
* Default of 15 blocks provides 99.9% confidence per Mina documentation.
* @see https://docs.minaprotocol.com/mina-protocol/lifecycle-of-a-payment
*/
finalityThreshold?: number;
};
type SendZkAppResponse = {
sendZkapp: {
zkapp: {
hash: string;
id: string;
zkappCommand: ZkappCommand;
failureReasons: FailureReasonResponse;
};
};
};
type EventQueryResponse = {
events: {
blockInfo: {
distanceFromMaxBlockHeight: number;
globalSlotSinceGenesis: number;
height: number;
stateHash: string;
parentHash: string;
chainStatus: string;
};
eventData: {
transactionInfo: {
hash: string;
memo: string;
status: string;
};
data: string[];
}[];
}[];
};
type FetchedAction = {
blockInfo: {
distanceFromMaxBlockHeight: number;
};
actionState: {
actionStateOne: string;
actionStateTwo: string;
};
actionData: {
accountUpdateId: string;
data: string[];
transactionInfo?: {
sequenceNumber: number;
zkappAccountUpdateIds: number[];
};
}[];
};
type ActionQueryResponse = {
actions: FetchedAction[];
};
type EventActionFilterOptions = {
to?: UInt32;
from?: UInt32;
};
const transactionStatusQuery = (txId: string) => `query {
transactionStatus(zkappTransaction:"${txId}")
}`;
const getEventsQuery = (inputs: EventsQueryInputs) => {
inputs.tokenId ??= TokenId.toBase58(TokenId.default);
const { publicKey, tokenId, to, from } = inputs;
let input = `address: "${publicKey}", tokenId: "${tokenId}"`;
if (to !== undefined) {
input += `, to: ${to.toString()}`;
}
if (from !== undefined) {
input += `, from: ${from.toString()}`;
}
return `{
events(input: { ${input} }) {
blockInfo {
distanceFromMaxBlockHeight
height
globalSlotSinceGenesis
stateHash
parentHash
chainStatus
}
eventData {
transactionInfo {
hash
memo
status
}
data
}
}
}`;
};
const getActionsQuery = (inputs: ActionsQueryInputs) => {
inputs.tokenId ??= TokenId.toBase58(TokenId.default);
const { publicKey, tokenId, actionStates, from, to } = inputs;
const { fromActionState, endActionState } = actionStates ?? {};
let input = `address: "${publicKey}", tokenId: "${tokenId}"`;
if (fromActionState !== undefined) {
input += `, fromActionState: "${fromActionState}"`;
}
if (endActionState !== undefined) {
input += `, endActionState: "${endActionState}"`;
}
if (to !== undefined) {
input += `, to: ${to}`;
}
if (from !== undefined) {
input += `, from: ${from}`;
}
return `{
actions(input: { ${input} }) {
blockInfo {
distanceFromMaxBlockHeight
}
actionState {
actionStateOne
actionStateTwo
}
actionData {
accountUpdateId
data
transactionInfo {
sequenceNumber
zkappAccountUpdateIds
}
}
}
}`;
};
const genesisConstantsQuery = `{
genesisConstants {
genesisTimestamp
coinbase
accountCreationFee
}
daemonStatus {
consensusConfiguration {
epochDuration
k
slotDuration
slotsPerEpoch
}
}
}`;
const lastBlockQuery = `{
bestChain(maxLength: 1) {
protocolState {
blockchainState {
snarkedLedgerHash
stagedLedgerHash
date
utcDate
stagedLedgerProofEmitted
}
previousStateHash
consensusState {
blockHeight
slotSinceGenesis
slot
nextEpochData {
ledger {hash totalCurrency}
seed
startCheckpoint
lockCheckpoint
epochLength
}
stakingEpochData {
ledger {hash totalCurrency}
seed
startCheckpoint
lockCheckpoint
epochLength
}
epochCount
minWindowDensity
totalCurrency
epoch
}
}
}
}`;
const lastBlockQueryFailureCheck = (length: number) => `{
bestChain(maxLength: ${length}) {
transactions {
zkappCommands {
hash
failureReason {
failures
index
}
}
}
stateHash
protocolState {
consensusState {
blockHeight
epoch
slotSinceGenesis
}
previousStateHash
}
}
}`;
// TODO: Decide an appropriate response structure.
function sendZkappQuery(json: string) {
return `mutation {
sendZkapp(input: {
zkappCommand: ${removeJsonQuotes(json)}
}) {
zkapp {
hash
id
failureReason {
failures
index
}
zkappCommand {
memo
feePayer {
body {
publicKey
}
}
accountUpdates {
body {
publicKey
useFullCommitment
incrementNonce
}
}
}
}
}
}
`;
}
const accountQuery = (publicKey: string, tokenId: string) => `{
account(publicKey: "${publicKey}", token: "${tokenId}") {
publicKey
token
nonce
balance { total }
tokenSymbol
receiptChainHash
timing {
initialMinimumBalance
cliffTime
cliffAmount
vestingPeriod
vestingIncrement
}
permissions {
editState
access
send
receive
setDelegate
setPermissions
setVerificationKey {
auth
txnVersion
}
setZkappUri
editActionState
setTokenSymbol
incrementNonce
setVotingFor
setTiming
}
delegateAccount { publicKey }
votingFor
zkappState
verificationKey {
verificationKey
hash
}
actionState
provedState
zkappUri
}
}
`;
const currentSlotQuery = `{
bestChain(maxLength: 1) {
protocolState {
consensusState {
slot
slotSinceGenesis
}
}
}
}`;