@silvana-one/mina-utils 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +1 -0
- package/dist/node/config.d.ts +4 -0
- package/dist/node/config.js +5 -0
- package/dist/node/config.js.map +1 -0
- package/dist/node/index.cjs +1325 -0
- package/dist/node/index.d.ts +6 -0
- package/dist/node/index.js +7 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/networks.d.ts +32 -0
- package/dist/node/networks.js +81 -0
- package/dist/node/networks.js.map +1 -0
- package/dist/node/storage/index.d.ts +2 -0
- package/dist/node/storage/index.js +3 -0
- package/dist/node/storage/index.js.map +1 -0
- package/dist/node/storage/ipfs.d.ts +5 -0
- package/dist/node/storage/ipfs.js +16 -0
- package/dist/node/storage/ipfs.js.map +1 -0
- package/dist/node/storage/pinata.d.ts +6 -0
- package/dist/node/storage/pinata.js +40 -0
- package/dist/node/storage/pinata.js.map +1 -0
- package/dist/node/transactions/account.d.ts +4 -0
- package/dist/node/transactions/account.js +44 -0
- package/dist/node/transactions/account.js.map +1 -0
- package/dist/node/transactions/blockberry.d.ts +21 -0
- package/dist/node/transactions/blockberry.js +130 -0
- package/dist/node/transactions/blockberry.js.map +1 -0
- package/dist/node/transactions/chain.d.ts +1 -0
- package/dist/node/transactions/chain.js +2 -0
- package/dist/node/transactions/chain.js.map +1 -0
- package/dist/node/transactions/index.d.ts +8 -0
- package/dist/node/transactions/index.js +9 -0
- package/dist/node/transactions/index.js.map +1 -0
- package/dist/node/transactions/nonce.d.ts +17 -0
- package/dist/node/transactions/nonce.js +91 -0
- package/dist/node/transactions/nonce.js.map +1 -0
- package/dist/node/transactions/send.d.ts +31 -0
- package/dist/node/transactions/send.js +154 -0
- package/dist/node/transactions/send.js.map +1 -0
- package/dist/node/transactions/tiny-contract.d.ts +5 -0
- package/dist/node/transactions/tiny-contract.js +22 -0
- package/dist/node/transactions/tiny-contract.js.map +1 -0
- package/dist/node/transactions/transaction.d.ts +21 -0
- package/dist/node/transactions/transaction.js +222 -0
- package/dist/node/transactions/transaction.js.map +1 -0
- package/dist/node/transactions/txstatus.d.ts +8 -0
- package/dist/node/transactions/txstatus.js +16 -0
- package/dist/node/transactions/txstatus.js.map +1 -0
- package/dist/node/utils/base64-field.d.ts +5 -0
- package/dist/node/utils/base64-field.js +29 -0
- package/dist/node/utils/base64-field.js.map +1 -0
- package/dist/node/utils/base64.d.ts +6 -0
- package/dist/node/utils/base64.js +83 -0
- package/dist/node/utils/base64.js.map +1 -0
- package/dist/node/utils/fee.d.ts +6 -0
- package/dist/node/utils/fee.js +11 -0
- package/dist/node/utils/fee.js.map +1 -0
- package/dist/node/utils/fetch.d.ts +44 -0
- package/dist/node/utils/fetch.js +101 -0
- package/dist/node/utils/fetch.js.map +1 -0
- package/dist/node/utils/fields.d.ts +13 -0
- package/dist/node/utils/fields.js +39 -0
- package/dist/node/utils/fields.js.map +1 -0
- package/dist/node/utils/index.d.ts +8 -0
- package/dist/node/utils/index.js +9 -0
- package/dist/node/utils/index.js.map +1 -0
- package/dist/node/utils/indexed-map.d.ts +32 -0
- package/dist/node/utils/indexed-map.js +124 -0
- package/dist/node/utils/indexed-map.js.map +1 -0
- package/dist/node/utils/mina.d.ts +39 -0
- package/dist/node/utils/mina.js +123 -0
- package/dist/node/utils/mina.js.map +1 -0
- package/dist/node/utils/utils.d.ts +8 -0
- package/dist/node/utils/utils.js +61 -0
- package/dist/node/utils/utils.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/tsconfig.web.tsbuildinfo +1 -0
- package/dist/web/config.d.ts +4 -0
- package/dist/web/config.js +5 -0
- package/dist/web/config.js.map +1 -0
- package/dist/web/index.d.ts +6 -0
- package/dist/web/index.js +7 -0
- package/dist/web/index.js.map +1 -0
- package/dist/web/networks.d.ts +32 -0
- package/dist/web/networks.js +81 -0
- package/dist/web/networks.js.map +1 -0
- package/dist/web/storage/index.d.ts +2 -0
- package/dist/web/storage/index.js +3 -0
- package/dist/web/storage/index.js.map +1 -0
- package/dist/web/storage/ipfs.d.ts +5 -0
- package/dist/web/storage/ipfs.js +16 -0
- package/dist/web/storage/ipfs.js.map +1 -0
- package/dist/web/storage/pinata.d.ts +6 -0
- package/dist/web/storage/pinata.js +40 -0
- package/dist/web/storage/pinata.js.map +1 -0
- package/dist/web/transactions/account.d.ts +4 -0
- package/dist/web/transactions/account.js +44 -0
- package/dist/web/transactions/account.js.map +1 -0
- package/dist/web/transactions/blockberry.d.ts +21 -0
- package/dist/web/transactions/blockberry.js +130 -0
- package/dist/web/transactions/blockberry.js.map +1 -0
- package/dist/web/transactions/chain.d.ts +1 -0
- package/dist/web/transactions/chain.js +2 -0
- package/dist/web/transactions/chain.js.map +1 -0
- package/dist/web/transactions/index.d.ts +8 -0
- package/dist/web/transactions/index.js +9 -0
- package/dist/web/transactions/index.js.map +1 -0
- package/dist/web/transactions/nonce.d.ts +17 -0
- package/dist/web/transactions/nonce.js +91 -0
- package/dist/web/transactions/nonce.js.map +1 -0
- package/dist/web/transactions/send.d.ts +31 -0
- package/dist/web/transactions/send.js +154 -0
- package/dist/web/transactions/send.js.map +1 -0
- package/dist/web/transactions/tiny-contract.d.ts +5 -0
- package/dist/web/transactions/tiny-contract.js +22 -0
- package/dist/web/transactions/tiny-contract.js.map +1 -0
- package/dist/web/transactions/transaction.d.ts +21 -0
- package/dist/web/transactions/transaction.js +222 -0
- package/dist/web/transactions/transaction.js.map +1 -0
- package/dist/web/transactions/txstatus.d.ts +8 -0
- package/dist/web/transactions/txstatus.js +16 -0
- package/dist/web/transactions/txstatus.js.map +1 -0
- package/dist/web/utils/base64-field.d.ts +5 -0
- package/dist/web/utils/base64-field.js +29 -0
- package/dist/web/utils/base64-field.js.map +1 -0
- package/dist/web/utils/base64.d.ts +6 -0
- package/dist/web/utils/base64.js +83 -0
- package/dist/web/utils/base64.js.map +1 -0
- package/dist/web/utils/fee.d.ts +6 -0
- package/dist/web/utils/fee.js +11 -0
- package/dist/web/utils/fee.js.map +1 -0
- package/dist/web/utils/fetch.d.ts +44 -0
- package/dist/web/utils/fetch.js +101 -0
- package/dist/web/utils/fetch.js.map +1 -0
- package/dist/web/utils/fields.d.ts +13 -0
- package/dist/web/utils/fields.js +39 -0
- package/dist/web/utils/fields.js.map +1 -0
- package/dist/web/utils/index.d.ts +8 -0
- package/dist/web/utils/index.js +9 -0
- package/dist/web/utils/index.js.map +1 -0
- package/dist/web/utils/indexed-map.d.ts +32 -0
- package/dist/web/utils/indexed-map.js +124 -0
- package/dist/web/utils/indexed-map.js.map +1 -0
- package/dist/web/utils/mina.d.ts +39 -0
- package/dist/web/utils/mina.js +123 -0
- package/dist/web/utils/mina.js.map +1 -0
- package/dist/web/utils/utils.d.ts +8 -0
- package/dist/web/utils/utils.js +61 -0
- package/dist/web/utils/utils.js.map +1 -0
- package/package.json +62 -0
- package/src/config.ts +5 -0
- package/src/index.ts +6 -0
- package/src/networks.ts +130 -0
- package/src/storage/index.ts +2 -0
- package/src/storage/ipfs.ts +20 -0
- package/src/storage/pinata.ts +56 -0
- package/src/transactions/account.ts +57 -0
- package/src/transactions/blockberry.ts +198 -0
- package/src/transactions/chain.ts +1 -0
- package/src/transactions/index.ts +8 -0
- package/src/transactions/nonce.ts +121 -0
- package/src/transactions/send.ts +228 -0
- package/src/transactions/tiny-contract.ts +9 -0
- package/src/transactions/transaction.ts +319 -0
- package/src/transactions/txstatus.ts +26 -0
- package/src/utils/base64-field.ts +34 -0
- package/src/utils/base64.ts +87 -0
- package/src/utils/fee.ts +11 -0
- package/src/utils/fetch.ts +130 -0
- package/src/utils/fields.ts +39 -0
- package/src/utils/index.ts +8 -0
- package/src/utils/indexed-map.ts +170 -0
- package/src/utils/mina.ts +171 -0
- package/src/utils/utils.ts +79 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { checkZkappTransaction } from "o1js";
|
|
2
|
+
import { Mina } from "o1js";
|
|
3
|
+
import { blockchain } from "../networks.js";
|
|
4
|
+
import { sleep } from "../utils/utils.js";
|
|
5
|
+
import { fetchMinaAccount } from "../utils/fetch.js";
|
|
6
|
+
import { getCurrentNetwork } from "../utils/mina.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The function `sendTx` sends a transaction, checks account updates, and waits for
|
|
10
|
+
* confirmation on the blockchain.
|
|
11
|
+
* @param params The parameters object
|
|
12
|
+
* @param params.tx The transaction to send
|
|
13
|
+
* @param params.description A description of the transaction
|
|
14
|
+
* @param params.verbose Whether to log verbose information
|
|
15
|
+
* @param params.wait Whether to wait for the transaction to be included in a block
|
|
16
|
+
* @param params.chain The blockchain to send the transaction on
|
|
17
|
+
* @returns The `sendTx` function returns a `Mina.IncludedTransaction`, `Mina.PendingTransaction`,
|
|
18
|
+
* `Mina.RejectedTransaction`, or `undefined` if there was an error during the process.
|
|
19
|
+
*/
|
|
20
|
+
export async function sendTx(params: {
|
|
21
|
+
tx: Mina.Transaction<false, true> | Mina.Transaction<true, true>;
|
|
22
|
+
description?: string;
|
|
23
|
+
retry?: number;
|
|
24
|
+
verbose?: boolean;
|
|
25
|
+
wait?: boolean;
|
|
26
|
+
chain?: blockchain;
|
|
27
|
+
delay?: number;
|
|
28
|
+
}): Promise<
|
|
29
|
+
| Mina.IncludedTransaction
|
|
30
|
+
| Mina.PendingTransaction
|
|
31
|
+
| Mina.RejectedTransaction
|
|
32
|
+
| undefined
|
|
33
|
+
> {
|
|
34
|
+
const {
|
|
35
|
+
tx,
|
|
36
|
+
description = "",
|
|
37
|
+
verbose = true,
|
|
38
|
+
wait = true,
|
|
39
|
+
chain = getCurrentNetwork().network.chainId,
|
|
40
|
+
delay = chain === "zeko" || chain === "lightnet" ? 5000 : 60000,
|
|
41
|
+
retry = 30,
|
|
42
|
+
} = params;
|
|
43
|
+
// flatten accountUpdates
|
|
44
|
+
const accountUpdates = JSON.parse(tx.toJSON()).accountUpdates;
|
|
45
|
+
const auCount: {
|
|
46
|
+
publicKey: string;
|
|
47
|
+
tokenId: string | undefined;
|
|
48
|
+
count: number;
|
|
49
|
+
}[] = [];
|
|
50
|
+
let proofAuthorizationCount = 0;
|
|
51
|
+
// Calculate the number of account updates for each { publicKey, tokenId }
|
|
52
|
+
for (const au of accountUpdates) {
|
|
53
|
+
const { publicKey, tokenId, authorizationKind } = au.body;
|
|
54
|
+
if (au.authorization.proof) {
|
|
55
|
+
proofAuthorizationCount++;
|
|
56
|
+
if (authorizationKind.isProved === false)
|
|
57
|
+
console.error("Proof authorization exists but isProved is false");
|
|
58
|
+
} else if (authorizationKind.isProved === true)
|
|
59
|
+
console.error("isProved is true but no proof authorization");
|
|
60
|
+
const index = auCount.findIndex(
|
|
61
|
+
(item) => item.publicKey === publicKey && item.tokenId === tokenId
|
|
62
|
+
);
|
|
63
|
+
if (index === -1) auCount.push({ publicKey, tokenId, count: 1 });
|
|
64
|
+
else auCount[index].count++;
|
|
65
|
+
}
|
|
66
|
+
if (verbose)
|
|
67
|
+
console.log(
|
|
68
|
+
`Account updates for ${description ?? "tx"}: ${
|
|
69
|
+
auCount.length
|
|
70
|
+
}, proof authorizations: ${proofAuthorizationCount}`
|
|
71
|
+
);
|
|
72
|
+
for (const au of auCount) {
|
|
73
|
+
if (au.count > 1)
|
|
74
|
+
if (verbose)
|
|
75
|
+
console.log(
|
|
76
|
+
`DUPLICATE AU ${description ?? ""}: ${au.publicKey} ${
|
|
77
|
+
au.tokenId !== "wSHV2S4qX9jFsLjQo8r1BsMLH2ZRKsZx6EJd1sbozGPieEC4Jf"
|
|
78
|
+
? "tokenId: " + au.tokenId
|
|
79
|
+
: ""
|
|
80
|
+
} count: ${au.count}`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
let txSent;
|
|
85
|
+
let sent = false;
|
|
86
|
+
let attempt = 1;
|
|
87
|
+
while (!sent && attempt <= retry) {
|
|
88
|
+
txSent = await tx.safeSend();
|
|
89
|
+
if (txSent.status === "pending") {
|
|
90
|
+
sent = true;
|
|
91
|
+
if (verbose)
|
|
92
|
+
console.log(
|
|
93
|
+
`${description ?? ""} tx sent: hash: ${txSent.hash} status: ${
|
|
94
|
+
txSent.status
|
|
95
|
+
}`
|
|
96
|
+
);
|
|
97
|
+
} else if (chain !== "local") {
|
|
98
|
+
attempt++;
|
|
99
|
+
console.error(
|
|
100
|
+
`${description} tx NOT sent: hash: ${txSent?.hash} status: ${
|
|
101
|
+
txSent?.status
|
|
102
|
+
}, errors: ${String(txSent.errors ?? "")}`
|
|
103
|
+
);
|
|
104
|
+
if (verbose) console.log(`Retrying ${chain} tx, retry:`, attempt);
|
|
105
|
+
await sleep(10000 * attempt);
|
|
106
|
+
} else attempt = retry + 1; // local chain - do not retry
|
|
107
|
+
}
|
|
108
|
+
if (txSent === undefined) throw new Error("txSent is undefined");
|
|
109
|
+
if ((txSent.errors?.length ?? 0) > 0) {
|
|
110
|
+
console.error(
|
|
111
|
+
`${description ?? ""} tx error: hash: ${txSent.hash} status: ${
|
|
112
|
+
txSent.status
|
|
113
|
+
} errors: ${String(txSent.errors ?? "")}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (txSent.status === "pending" && wait !== false && chain !== "zeko") {
|
|
118
|
+
if (verbose) console.log(`Waiting for tx inclusion...`);
|
|
119
|
+
let txIncluded = await txSent.safeWait();
|
|
120
|
+
if (txIncluded.status !== "included") {
|
|
121
|
+
console.error(
|
|
122
|
+
`${description ?? ""} tx NOT included into block: hash: ${
|
|
123
|
+
txIncluded.hash
|
|
124
|
+
} status: ${txIncluded.status}, errors: ${String(
|
|
125
|
+
txIncluded.errors ?? ""
|
|
126
|
+
)}`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (chain !== "local") {
|
|
130
|
+
// we still wait for the tx to be included in the block by checking the nonce
|
|
131
|
+
// even in the case of tx NOT included
|
|
132
|
+
// because the tx might still be included in the block in the case of devnet instability
|
|
133
|
+
const { publicKey, nonce } = tx.transaction.feePayer.body;
|
|
134
|
+
const started = Date.now();
|
|
135
|
+
while (Date.now() - started < 1000 * 60 * 10) {
|
|
136
|
+
const newNonce = (
|
|
137
|
+
await fetchMinaAccount({
|
|
138
|
+
publicKey,
|
|
139
|
+
force: true,
|
|
140
|
+
})
|
|
141
|
+
).account?.nonce;
|
|
142
|
+
if (
|
|
143
|
+
newNonce &&
|
|
144
|
+
Number(newNonce.toBigint()) > Number(nonce.toBigint())
|
|
145
|
+
) {
|
|
146
|
+
let txIncluded = await txSent.safeWait();
|
|
147
|
+
if (txIncluded.status === "included") {
|
|
148
|
+
if (verbose)
|
|
149
|
+
console.log(
|
|
150
|
+
`${description ?? ""} tx included into block: hash: ${
|
|
151
|
+
txIncluded.hash
|
|
152
|
+
} status: ${txIncluded.status}`
|
|
153
|
+
);
|
|
154
|
+
if (delay > 0) await sleep(delay);
|
|
155
|
+
return txIncluded;
|
|
156
|
+
} else if (txIncluded.status === "rejected") {
|
|
157
|
+
// We never should see this error as the nonce is updated (except when tx failed due to a preconditions),
|
|
158
|
+
// so we retry to check if the tx is included
|
|
159
|
+
console.error(
|
|
160
|
+
`tx rejected: ${chain}: hash: ${txIncluded.hash} status: ${txIncluded.status} errors: ${txIncluded.errors}`
|
|
161
|
+
);
|
|
162
|
+
await sleep(30000);
|
|
163
|
+
txIncluded = await txSent.safeWait();
|
|
164
|
+
if (txIncluded.status === "included") {
|
|
165
|
+
if (verbose)
|
|
166
|
+
console.log(
|
|
167
|
+
`${description ?? ""} tx included into block: hash: ${
|
|
168
|
+
txIncluded.hash
|
|
169
|
+
} status: ${txIncluded.status}`
|
|
170
|
+
);
|
|
171
|
+
if (delay > 0) await sleep(delay);
|
|
172
|
+
return txIncluded;
|
|
173
|
+
}
|
|
174
|
+
console.error(
|
|
175
|
+
`tx failed: ${chain}: hash: ${txIncluded.hash} status: ${txIncluded.status} errors: ${txIncluded.errors}`
|
|
176
|
+
);
|
|
177
|
+
return txIncluded;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (verbose)
|
|
181
|
+
console.log(
|
|
182
|
+
`Waiting for ${chain} to update state for ${Math.floor(
|
|
183
|
+
(Date.now() - started) / 1000
|
|
184
|
+
)} sec...`
|
|
185
|
+
);
|
|
186
|
+
await sleep(30000);
|
|
187
|
+
}
|
|
188
|
+
// finally, if the tx is still not included, show an error
|
|
189
|
+
console.error(
|
|
190
|
+
`${chain} do not reflect nonce update for tx ${txIncluded.hash} with status ${txIncluded.status}`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
if (verbose)
|
|
194
|
+
console.log(
|
|
195
|
+
`${description ?? ""} tx included into block: hash: ${
|
|
196
|
+
txIncluded.hash
|
|
197
|
+
} status: ${txIncluded.status}`
|
|
198
|
+
);
|
|
199
|
+
return txIncluded;
|
|
200
|
+
} else return txSent;
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (chain !== "zeko") console.error("Error sending tx", error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export async function getTxStatusFast(params: {
|
|
207
|
+
hash: string;
|
|
208
|
+
chain?: blockchain;
|
|
209
|
+
}): Promise<{ success: boolean; result?: boolean; error?: string }> {
|
|
210
|
+
const { hash, chain = getCurrentNetwork().network.chainId } = params;
|
|
211
|
+
if (chain === "local" || chain === "zeko")
|
|
212
|
+
return { success: true, result: true };
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const txStatus = await checkZkappTransaction(hash);
|
|
216
|
+
return {
|
|
217
|
+
success: true,
|
|
218
|
+
result: txStatus?.success ?? false,
|
|
219
|
+
};
|
|
220
|
+
} catch (error: any) {
|
|
221
|
+
console.error(
|
|
222
|
+
"getTxStatusFast error while getting tx status - catch",
|
|
223
|
+
hash,
|
|
224
|
+
error
|
|
225
|
+
);
|
|
226
|
+
return { success: false, error: error?.message ?? "Cannot get tx status" };
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { Field, PublicKey, Transaction, Mina, UInt64 } from "o1js";
|
|
2
|
+
import { TransactionPayloads } from "@silvana-one/api";
|
|
3
|
+
import { fieldToBase64, fieldFromBase64 } from "../utils/base64-field.js";
|
|
4
|
+
|
|
5
|
+
export function createTransactionPayloads(
|
|
6
|
+
tx: Mina.Transaction<false, false> | Mina.Transaction<false, true>
|
|
7
|
+
): TransactionPayloads {
|
|
8
|
+
const transaction = tx.toJSON();
|
|
9
|
+
const txJSON = JSON.parse(transaction);
|
|
10
|
+
const signedData = JSON.stringify({ zkappCommand: txJSON });
|
|
11
|
+
const proverPayload = serializeTransaction(tx);
|
|
12
|
+
const fee = tx.transaction.feePayer.body.fee.toJSON();
|
|
13
|
+
const sender = tx.transaction.feePayer.body.publicKey.toBase58();
|
|
14
|
+
const nonce = Number(tx.transaction.feePayer.body.nonce.toBigint());
|
|
15
|
+
const memo = tx.transaction.memo;
|
|
16
|
+
const minaSignerPayload = {
|
|
17
|
+
zkappCommand: txJSON,
|
|
18
|
+
feePayer: {
|
|
19
|
+
feePayer: sender,
|
|
20
|
+
fee,
|
|
21
|
+
nonce,
|
|
22
|
+
memo,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const walletPayload = {
|
|
26
|
+
transaction,
|
|
27
|
+
nonce,
|
|
28
|
+
onlySign: true,
|
|
29
|
+
feePayer: {
|
|
30
|
+
fee,
|
|
31
|
+
memo,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
sender,
|
|
37
|
+
nonce,
|
|
38
|
+
memo,
|
|
39
|
+
fee,
|
|
40
|
+
walletPayload,
|
|
41
|
+
minaSignerPayload,
|
|
42
|
+
proverPayload,
|
|
43
|
+
signedData,
|
|
44
|
+
transaction,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface ForestSerialized {
|
|
49
|
+
length: number;
|
|
50
|
+
restoredItems?: number;
|
|
51
|
+
items: {
|
|
52
|
+
h?: string; // hash
|
|
53
|
+
p?: string; // previousHash
|
|
54
|
+
i?: number; // id
|
|
55
|
+
c?: string; // callData
|
|
56
|
+
}[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function transactionParams(
|
|
60
|
+
params:
|
|
61
|
+
| {
|
|
62
|
+
proverPayload: string;
|
|
63
|
+
signedData: string;
|
|
64
|
+
}
|
|
65
|
+
| TransactionPayloads
|
|
66
|
+
): {
|
|
67
|
+
fee: UInt64;
|
|
68
|
+
sender: PublicKey;
|
|
69
|
+
nonce: number;
|
|
70
|
+
memo: string;
|
|
71
|
+
} {
|
|
72
|
+
const { proverPayload, signedData } = params;
|
|
73
|
+
const signedJson = JSON.parse(signedData);
|
|
74
|
+
const { sender, tx } = JSON.parse(proverPayload);
|
|
75
|
+
const transaction = Mina.Transaction.fromJSON(JSON.parse(tx));
|
|
76
|
+
const memo = transaction.transaction.memo;
|
|
77
|
+
return {
|
|
78
|
+
fee: UInt64.from(signedJson.zkappCommand.feePayer.body.fee),
|
|
79
|
+
sender: PublicKey.fromBase58(sender),
|
|
80
|
+
nonce: Number(signedJson.zkappCommand.feePayer.body.nonce),
|
|
81
|
+
memo,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function parseTransactionPayloads(
|
|
86
|
+
params:
|
|
87
|
+
| {
|
|
88
|
+
proverPayload: string;
|
|
89
|
+
signedData: string;
|
|
90
|
+
txNew: Mina.Transaction<false, false> | Mina.Transaction<false, true>;
|
|
91
|
+
}
|
|
92
|
+
| {
|
|
93
|
+
payloads: TransactionPayloads;
|
|
94
|
+
txNew: Mina.Transaction<false, false> | Mina.Transaction<false, true>;
|
|
95
|
+
}
|
|
96
|
+
): Transaction<false, true> {
|
|
97
|
+
const { txNew } = params;
|
|
98
|
+
const proverPayload =
|
|
99
|
+
"payloads" in params ? params.payloads.proverPayload : params.proverPayload;
|
|
100
|
+
const signedData =
|
|
101
|
+
"payloads" in params ? params.payloads.signedData : params.signedData;
|
|
102
|
+
const signedJson = JSON.parse(signedData);
|
|
103
|
+
const { tx, blindingValues, length, forestJSONs } = JSON.parse(proverPayload);
|
|
104
|
+
const transaction = Mina.Transaction.fromJSON(JSON.parse(tx));
|
|
105
|
+
const forests: ForestSerialized[] = forestJSONs.map(
|
|
106
|
+
(f: string) => JSON.parse(f) as ForestSerialized
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (length !== txNew.transaction.accountUpdates.length) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`New Transaction length mismatch: ${length} !== ${txNew.transaction.accountUpdates.length}`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
if (length !== transaction.transaction.accountUpdates.length) {
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Serialized Transaction length mismatch: ${length} !== ${transaction.transaction.accountUpdates.length}`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
for (let i = 0; i < length; i++) {
|
|
120
|
+
transaction.transaction.accountUpdates[i].lazyAuthorization =
|
|
121
|
+
txNew.transaction.accountUpdates[i].lazyAuthorization;
|
|
122
|
+
if (blindingValues[i] !== "") {
|
|
123
|
+
if (
|
|
124
|
+
transaction.transaction.accountUpdates[i].lazyAuthorization ===
|
|
125
|
+
undefined ||
|
|
126
|
+
(transaction.transaction.accountUpdates[i].lazyAuthorization as any)
|
|
127
|
+
.blindingValue === undefined
|
|
128
|
+
) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Lazy authorization blinding value is undefined for item ${i}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
(
|
|
134
|
+
transaction.transaction.accountUpdates[i].lazyAuthorization as any
|
|
135
|
+
).blindingValue = Field.fromJSON(blindingValues[i]);
|
|
136
|
+
}
|
|
137
|
+
if (forests[i].length > 0) {
|
|
138
|
+
if (
|
|
139
|
+
transaction.transaction.accountUpdates[i].lazyAuthorization ===
|
|
140
|
+
undefined ||
|
|
141
|
+
(transaction.transaction.accountUpdates[i].lazyAuthorization as any)
|
|
142
|
+
.args === undefined
|
|
143
|
+
) {
|
|
144
|
+
throw new Error(`Lazy authorization args is undefined for item ${i}`);
|
|
145
|
+
}
|
|
146
|
+
deserializeLazyAuthorization(
|
|
147
|
+
(transaction.transaction.accountUpdates[i].lazyAuthorization as any)
|
|
148
|
+
.args,
|
|
149
|
+
forests[i]
|
|
150
|
+
);
|
|
151
|
+
if (forests[i].restoredItems !== forests[i].length) {
|
|
152
|
+
throw new Error(`Forest ${i} not fully restored`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
transaction.transaction.feePayer.authorization =
|
|
157
|
+
signedJson.zkappCommand.feePayer.authorization;
|
|
158
|
+
transaction.transaction.feePayer.body.fee = UInt64.from(
|
|
159
|
+
signedJson.zkappCommand.feePayer.body.fee
|
|
160
|
+
);
|
|
161
|
+
for (let i = 0; i < length; i++) {
|
|
162
|
+
const signature =
|
|
163
|
+
signedJson.zkappCommand.accountUpdates[i].authorization.signature;
|
|
164
|
+
if (signature !== undefined && signature !== null) {
|
|
165
|
+
transaction.transaction.accountUpdates[i].authorization.signature =
|
|
166
|
+
signedJson.zkappCommand.accountUpdates[i].authorization.signature;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return transaction;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function serializeTransaction(
|
|
173
|
+
tx: Mina.Transaction<false, false> | Mina.Transaction<false, true>
|
|
174
|
+
): string {
|
|
175
|
+
const length = tx.transaction.accountUpdates.length;
|
|
176
|
+
let i;
|
|
177
|
+
const blindingValues = [];
|
|
178
|
+
const forests: ForestSerialized[] = [];
|
|
179
|
+
for (i = 0; i < length; i++) {
|
|
180
|
+
const la = tx.transaction.accountUpdates[i].lazyAuthorization;
|
|
181
|
+
if (
|
|
182
|
+
la !== undefined &&
|
|
183
|
+
(la as any).blindingValue !== undefined &&
|
|
184
|
+
la.kind === "lazy-proof"
|
|
185
|
+
)
|
|
186
|
+
blindingValues.push(la.blindingValue.toJSON());
|
|
187
|
+
else blindingValues.push("");
|
|
188
|
+
const forest: ForestSerialized = { length: 0, items: [] };
|
|
189
|
+
serializeLazyAuthorization(
|
|
190
|
+
(tx.transaction.accountUpdates[i].lazyAuthorization as any)?.args,
|
|
191
|
+
forest
|
|
192
|
+
);
|
|
193
|
+
forests.push(forest);
|
|
194
|
+
}
|
|
195
|
+
const serializedTransaction = JSON.stringify(
|
|
196
|
+
{
|
|
197
|
+
tx: tx.toJSON(),
|
|
198
|
+
blindingValues,
|
|
199
|
+
forestJSONs: forests.map((f) => JSON.stringify(f)),
|
|
200
|
+
length,
|
|
201
|
+
fee: tx.transaction.feePayer.body.fee.toJSON(),
|
|
202
|
+
sender: tx.transaction.feePayer.body.publicKey.toBase58(),
|
|
203
|
+
nonce: tx.transaction.feePayer.body.nonce.toBigint().toString(),
|
|
204
|
+
},
|
|
205
|
+
null,
|
|
206
|
+
2
|
|
207
|
+
);
|
|
208
|
+
return serializedTransaction;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function serializeLazyAuthorization(
|
|
212
|
+
lazyAuthorization: any,
|
|
213
|
+
serialized: ForestSerialized
|
|
214
|
+
): void {
|
|
215
|
+
if (lazyAuthorization?.hash !== undefined && lazyAuthorization.hash.toJSON) {
|
|
216
|
+
serialized.items.push({
|
|
217
|
+
h: fieldToBase64(lazyAuthorization.hash),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
if (
|
|
222
|
+
lazyAuthorization?.previousHash !== undefined &&
|
|
223
|
+
lazyAuthorization.previousHash.toJSON
|
|
224
|
+
) {
|
|
225
|
+
serialized.items.push({
|
|
226
|
+
p: fieldToBase64(lazyAuthorization.previousHash),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
if (
|
|
230
|
+
lazyAuthorization?.callData !== undefined &&
|
|
231
|
+
lazyAuthorization.callData.toJSON
|
|
232
|
+
) {
|
|
233
|
+
serialized.items.push({
|
|
234
|
+
c: fieldToBase64(lazyAuthorization.callData),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (lazyAuthorization?.id !== undefined) {
|
|
239
|
+
serialized.items.push({
|
|
240
|
+
i: lazyAuthorization.id,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (Array.isArray(lazyAuthorization)) {
|
|
245
|
+
for (const item of lazyAuthorization) {
|
|
246
|
+
serializeLazyAuthorization(item, serialized);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (typeof lazyAuthorization === "object") {
|
|
250
|
+
for (const key in lazyAuthorization) {
|
|
251
|
+
serializeLazyAuthorization(lazyAuthorization[key], serialized);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
serialized.length = serialized.items.length;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function deserializeLazyAuthorization(
|
|
258
|
+
lazyAuthorization: any,
|
|
259
|
+
serialized: ForestSerialized
|
|
260
|
+
): void {
|
|
261
|
+
if (serialized.restoredItems === undefined) serialized.restoredItems = 0;
|
|
262
|
+
if (lazyAuthorization?.hash !== undefined && lazyAuthorization.hash.toJSON) {
|
|
263
|
+
if (serialized.restoredItems >= serialized.length)
|
|
264
|
+
throw new Error("Restored more items than expected");
|
|
265
|
+
const hash = serialized.items[serialized.restoredItems].h;
|
|
266
|
+
if (hash === undefined)
|
|
267
|
+
throw new Error(`Hash is undefined for item ${serialized.restoredItems}`);
|
|
268
|
+
lazyAuthorization.hash = fieldFromBase64(hash);
|
|
269
|
+
|
|
270
|
+
serialized.restoredItems++;
|
|
271
|
+
}
|
|
272
|
+
if (
|
|
273
|
+
lazyAuthorization?.previousHash !== undefined &&
|
|
274
|
+
lazyAuthorization.previousHash.toJSON
|
|
275
|
+
) {
|
|
276
|
+
if (serialized.restoredItems >= serialized.length)
|
|
277
|
+
throw new Error("Restored more items than expected");
|
|
278
|
+
const previousHash = serialized.items[serialized.restoredItems].p;
|
|
279
|
+
if (previousHash === undefined)
|
|
280
|
+
throw new Error(
|
|
281
|
+
`Previous hash is undefined for item ${serialized.restoredItems}`
|
|
282
|
+
);
|
|
283
|
+
lazyAuthorization.previousHash = fieldFromBase64(previousHash);
|
|
284
|
+
serialized.restoredItems++;
|
|
285
|
+
}
|
|
286
|
+
if (
|
|
287
|
+
lazyAuthorization?.callData !== undefined &&
|
|
288
|
+
lazyAuthorization.callData.toJSON
|
|
289
|
+
) {
|
|
290
|
+
if (serialized.restoredItems >= serialized.length)
|
|
291
|
+
throw new Error("Restored more items than expected");
|
|
292
|
+
const callData = serialized.items[serialized.restoredItems].c;
|
|
293
|
+
if (callData === undefined)
|
|
294
|
+
throw new Error(
|
|
295
|
+
`Call data is undefined for item ${serialized.restoredItems}`
|
|
296
|
+
);
|
|
297
|
+
lazyAuthorization.callData = fieldFromBase64(callData);
|
|
298
|
+
serialized.restoredItems++;
|
|
299
|
+
}
|
|
300
|
+
if (lazyAuthorization?.id !== undefined) {
|
|
301
|
+
if (serialized.restoredItems >= serialized.length)
|
|
302
|
+
throw new Error("Restored more items than expected");
|
|
303
|
+
const id = serialized.items[serialized.restoredItems].i;
|
|
304
|
+
if (id === undefined)
|
|
305
|
+
throw new Error(`Id is undefined for item ${serialized.restoredItems}`);
|
|
306
|
+
lazyAuthorization.id = id;
|
|
307
|
+
serialized.restoredItems++;
|
|
308
|
+
}
|
|
309
|
+
if (Array.isArray(lazyAuthorization)) {
|
|
310
|
+
for (const item of lazyAuthorization) {
|
|
311
|
+
deserializeLazyAuthorization(item, serialized);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (typeof lazyAuthorization === "object") {
|
|
315
|
+
for (const key in lazyAuthorization) {
|
|
316
|
+
deserializeLazyAuthorization(lazyAuthorization[key], serialized);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BlockBerryChain } from "./chain.js";
|
|
2
|
+
import { getZkAppTxFromBlockBerry } from "./blockberry.js";
|
|
3
|
+
const TIMEOUT = 1000 * 60 * 21;
|
|
4
|
+
|
|
5
|
+
export async function txStatusBlockberry(params: {
|
|
6
|
+
hash: string;
|
|
7
|
+
time: number;
|
|
8
|
+
chain: BlockBerryChain;
|
|
9
|
+
blockBerryApiKey: string;
|
|
10
|
+
timeout?: number;
|
|
11
|
+
}): Promise<string> {
|
|
12
|
+
const { hash, chain, time, blockBerryApiKey } = params;
|
|
13
|
+
|
|
14
|
+
const tx = await getZkAppTxFromBlockBerry({ hash, chain, blockBerryApiKey });
|
|
15
|
+
if (tx?.txStatus) return tx?.txStatus;
|
|
16
|
+
if (Date.now() - time > (params.timeout ?? TIMEOUT)) {
|
|
17
|
+
console.error(
|
|
18
|
+
"txStatus: Timeout while checking tx with blockberry",
|
|
19
|
+
chain,
|
|
20
|
+
hash
|
|
21
|
+
);
|
|
22
|
+
return "replaced";
|
|
23
|
+
} else {
|
|
24
|
+
return "pending";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Field } from "o1js";
|
|
2
|
+
import { toBase, fromBase } from "./base64.js";
|
|
3
|
+
|
|
4
|
+
// URL friendly base64 encoding
|
|
5
|
+
const TABLE =
|
|
6
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
7
|
+
|
|
8
|
+
export function fieldToBase56(field: Field): string {
|
|
9
|
+
const digits = toBase(field.toBigInt(), 56n);
|
|
10
|
+
//console.log("digits:", digits);
|
|
11
|
+
const str = digits.map((x) => TABLE[Number(x)]).join("");
|
|
12
|
+
//console.log("str:", str);
|
|
13
|
+
return str;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function fieldFromBase56(str: string): Field {
|
|
17
|
+
const base56Digits = str.split("").map((x) => BigInt(TABLE.indexOf(x)));
|
|
18
|
+
const x = fromBase(base56Digits, 56n);
|
|
19
|
+
return Field(x);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function fieldToBase64(field: Field): string {
|
|
23
|
+
const digits = toBase(field.toBigInt(), 64n);
|
|
24
|
+
//console.log("digits:", digits);
|
|
25
|
+
const str = digits.map((x) => TABLE[Number(x)]).join("");
|
|
26
|
+
//console.log("str:", str);
|
|
27
|
+
return str;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function fieldFromBase64(str: string): Field {
|
|
31
|
+
const base64Digits = str.split("").map((x) => BigInt(TABLE.indexOf(x)));
|
|
32
|
+
const x = fromBase(base64Digits, 64n);
|
|
33
|
+
return Field(x);
|
|
34
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// URL friendly base64 encoding
|
|
2
|
+
const TABLE =
|
|
3
|
+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
4
|
+
|
|
5
|
+
export function bigintToBase56(value: bigint): string {
|
|
6
|
+
const digits = toBase(value, 56n);
|
|
7
|
+
//console.log("digits:", digits);
|
|
8
|
+
const str = digits.map((x) => TABLE[Number(x)]).join("");
|
|
9
|
+
//console.log("str:", str);
|
|
10
|
+
return str;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function bigintFromBase56(str: string): bigint {
|
|
14
|
+
const base56Digits = str.split("").map((x) => BigInt(TABLE.indexOf(x)));
|
|
15
|
+
const x = fromBase(base56Digits, 56n);
|
|
16
|
+
return x;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function bigintToBase64(value: bigint): string {
|
|
20
|
+
const digits = toBase(value, 64n);
|
|
21
|
+
//console.log("digits:", digits);
|
|
22
|
+
const str = digits.map((x) => TABLE[Number(x)]).join("");
|
|
23
|
+
//console.log("str:", str);
|
|
24
|
+
return str;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function bigintFromBase64(str: string): bigint {
|
|
28
|
+
const base64Digits = str.split("").map((x) => BigInt(TABLE.indexOf(x)));
|
|
29
|
+
const x = fromBase(base64Digits, 64n);
|
|
30
|
+
return x;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function fromBase(digits: bigint[], base: bigint) {
|
|
34
|
+
if (base <= 0n) throw Error("fromBase: base must be positive");
|
|
35
|
+
// compute powers base, base^2, base^4, ..., base^(2^k)
|
|
36
|
+
// with largest k s.t. n = 2^k < digits.length
|
|
37
|
+
let basePowers = [];
|
|
38
|
+
for (let power = base, n = 1; n < digits.length; power **= 2n, n *= 2) {
|
|
39
|
+
basePowers.push(power);
|
|
40
|
+
}
|
|
41
|
+
let k = basePowers.length;
|
|
42
|
+
// pad digits array with zeros s.t. digits.length === 2^k
|
|
43
|
+
digits = digits.concat(Array(2 ** k - digits.length).fill(0n));
|
|
44
|
+
// accumulate [x0, x1, x2, x3, ...] -> [x0 + base*x1, x2 + base*x3, ...] -> [x0 + base*x1 + base^2*(x2 + base*x3), ...] -> ...
|
|
45
|
+
// until we end up with a single element
|
|
46
|
+
for (let i = 0; i < k; i++) {
|
|
47
|
+
let newDigits = Array(digits.length >> 1);
|
|
48
|
+
let basePower = basePowers[i];
|
|
49
|
+
for (let j = 0; j < newDigits.length; j++) {
|
|
50
|
+
newDigits[j] = digits[2 * j] + basePower * digits[2 * j + 1];
|
|
51
|
+
}
|
|
52
|
+
digits = newDigits;
|
|
53
|
+
}
|
|
54
|
+
console.assert(digits.length === 1);
|
|
55
|
+
let [digit] = digits;
|
|
56
|
+
return digit;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function toBase(x: bigint, base: bigint) {
|
|
60
|
+
if (base <= 0n) throw Error("toBase: base must be positive");
|
|
61
|
+
// compute powers base, base^2, base^4, ..., base^(2^k)
|
|
62
|
+
// with largest k s.t. base^(2^k) < x
|
|
63
|
+
let basePowers = [];
|
|
64
|
+
for (let power = base; power <= x; power **= 2n) {
|
|
65
|
+
basePowers.push(power);
|
|
66
|
+
}
|
|
67
|
+
let digits = [x]; // single digit w.r.t base^(2^(k+1))
|
|
68
|
+
// successively split digits w.r.t. base^(2^j) into digits w.r.t. base^(2^(j-1))
|
|
69
|
+
// until we arrive at digits w.r.t. base
|
|
70
|
+
let k = basePowers.length;
|
|
71
|
+
for (let i = 0; i < k; i++) {
|
|
72
|
+
let newDigits = Array(2 * digits.length);
|
|
73
|
+
let basePower = basePowers[k - 1 - i];
|
|
74
|
+
for (let j = 0; j < digits.length; j++) {
|
|
75
|
+
let x = digits[j];
|
|
76
|
+
let high = x / basePower;
|
|
77
|
+
newDigits[2 * j + 1] = high;
|
|
78
|
+
newDigits[2 * j] = x - high * basePower;
|
|
79
|
+
}
|
|
80
|
+
digits = newDigits;
|
|
81
|
+
}
|
|
82
|
+
// pop "leading" zero digits
|
|
83
|
+
while (digits[digits.length - 1] === 0n) {
|
|
84
|
+
digits.pop();
|
|
85
|
+
}
|
|
86
|
+
return digits;
|
|
87
|
+
}
|