@veilo/sdk-core 0.3.3 → 0.5.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 +15 -0
- package/README.md +289 -1262
- package/dist/cjs/accounts/admin.d.ts +84 -0
- package/dist/cjs/accounts/admin.js +208 -0
- package/dist/cjs/accounts/errors.d.ts +6 -0
- package/dist/cjs/accounts/errors.js +98 -0
- package/dist/cjs/accounts/index.d.ts +4 -0
- package/dist/cjs/accounts/index.js +20 -0
- package/dist/cjs/accounts/pdas.d.ts +23 -0
- package/dist/cjs/accounts/pdas.js +44 -0
- package/dist/cjs/accounts/queries.d.ts +41 -0
- package/dist/cjs/accounts/queries.js +95 -0
- package/dist/cjs/client.d.ts +2 -407
- package/dist/cjs/client.js +17 -912
- package/dist/cjs/cloak/client.d.ts +28 -0
- package/dist/cjs/cloak/client.js +68 -0
- package/dist/cjs/cloak/errors.d.ts +13 -0
- package/dist/cjs/cloak/errors.js +25 -0
- package/dist/cjs/cloak/helpers.d.ts +6 -0
- package/dist/cjs/cloak/helpers.js +35 -0
- package/dist/cjs/cloak/index.d.ts +6 -0
- package/dist/cjs/cloak/index.js +24 -0
- package/dist/cjs/cloak/polling.d.ts +7 -0
- package/dist/cjs/cloak/polling.js +66 -0
- package/dist/cjs/cloak/transport.d.ts +15 -0
- package/dist/cjs/cloak/transport.js +119 -0
- package/dist/cjs/cloak/types.d.ts +188 -0
- package/dist/cjs/cloak/types.js +5 -0
- package/dist/cjs/cloak.d.ts +1 -0
- package/dist/cjs/cloak.js +18 -0
- package/dist/cjs/compactNote.d.ts +128 -0
- package/dist/cjs/compactNote.js +191 -0
- package/dist/cjs/events.d.ts +1 -1
- package/dist/cjs/events.js +2 -2
- package/dist/cjs/idl/privacy_pool.d.ts +5 -0
- package/dist/cjs/idl/privacy_pool.js +15218 -0
- package/dist/cjs/index.d.ts +17 -13
- package/dist/cjs/index.js +43 -30
- package/dist/cjs/merkle.d.ts +13 -0
- package/dist/cjs/merkle.js +31 -8
- package/dist/cjs/notes/encryption.d.ts +41 -0
- package/dist/cjs/notes/encryption.js +75 -0
- package/dist/cjs/notes/index.d.ts +5 -0
- package/dist/cjs/notes/index.js +21 -0
- package/dist/cjs/notes/mailbox.d.ts +70 -0
- package/dist/cjs/notes/mailbox.js +164 -0
- package/dist/cjs/notes/model.d.ts +91 -0
- package/dist/cjs/notes/model.js +109 -0
- package/dist/cjs/notes/nullifier.d.ts +27 -0
- package/dist/cjs/notes/nullifier.js +45 -0
- package/dist/cjs/notes/recovery.d.ts +10 -0
- package/dist/cjs/notes/recovery.js +31 -0
- package/dist/cjs/program.d.ts +11 -0
- package/dist/cjs/program.js +26 -3
- package/dist/cjs/proof.d.ts +1 -183
- package/dist/cjs/proof.js +16 -290
- package/dist/cjs/proofs/encoding.d.ts +17 -0
- package/dist/cjs/proofs/encoding.js +72 -0
- package/dist/cjs/proofs/formatting.d.ts +6 -0
- package/dist/cjs/proofs/formatting.js +34 -0
- package/dist/cjs/proofs/index.d.ts +5 -0
- package/dist/cjs/proofs/index.js +21 -0
- package/dist/cjs/proofs/swap.d.ts +87 -0
- package/dist/cjs/proofs/swap.js +144 -0
- package/dist/cjs/proofs/transaction.d.ts +28 -0
- package/dist/cjs/proofs/transaction.js +110 -0
- package/dist/cjs/proofs/types.d.ts +70 -0
- package/dist/cjs/proofs/types.js +2 -0
- package/dist/cjs/prover.d.ts +4 -1
- package/dist/cjs/prover.js +12 -2
- package/dist/cjs/random.d.ts +16 -0
- package/dist/cjs/random.js +28 -0
- package/dist/cjs/relayer/client.d.ts +61 -0
- package/dist/cjs/relayer/client.js +151 -0
- package/dist/cjs/relayer/crypto.d.ts +5 -0
- package/dist/cjs/relayer/crypto.js +26 -0
- package/dist/cjs/relayer/encoding.d.ts +2 -0
- package/dist/cjs/relayer/encoding.js +23 -0
- package/dist/cjs/relayer/errors.d.ts +7 -0
- package/dist/cjs/relayer/errors.js +17 -0
- package/dist/cjs/relayer/index.d.ts +3 -0
- package/dist/cjs/relayer/index.js +19 -0
- package/dist/cjs/relayer/transport.d.ts +17 -0
- package/dist/cjs/relayer/transport.js +63 -0
- package/dist/cjs/relayer/types.d.ts +276 -0
- package/dist/cjs/relayer/types.js +5 -0
- package/dist/cjs/relayer.d.ts +1 -295
- package/dist/cjs/relayer.js +15 -243
- package/dist/cjs/shield/alt.d.ts +87 -0
- package/dist/cjs/shield/alt.js +194 -0
- package/dist/cjs/shield/computeBudget.d.ts +61 -0
- package/dist/cjs/shield/computeBudget.js +64 -0
- package/dist/cjs/shield/errors.d.ts +58 -0
- package/dist/cjs/shield/errors.js +121 -0
- package/dist/cjs/shield/finalize.d.ts +45 -0
- package/dist/cjs/shield/finalize.js +119 -0
- package/dist/cjs/shield/index.d.ts +35 -0
- package/dist/cjs/shield/index.js +68 -0
- package/dist/cjs/shield/ix.d.ts +54 -0
- package/dist/cjs/shield/ix.js +119 -0
- package/dist/cjs/shield/owner.d.ts +36 -0
- package/dist/cjs/shield/owner.js +127 -0
- package/dist/cjs/shield/ports.d.ts +43 -0
- package/dist/cjs/shield/ports.js +153 -0
- package/dist/cjs/shield/preflight.d.ts +30 -0
- package/dist/cjs/shield/preflight.js +154 -0
- package/dist/cjs/shield/shield.d.ts +68 -0
- package/dist/cjs/shield/shield.js +500 -0
- package/dist/cjs/shield/types.d.ts +202 -0
- package/dist/cjs/shield/types.js +2 -0
- package/dist/cjs/transactions/deposit.d.ts +94 -0
- package/dist/cjs/transactions/deposit.js +234 -0
- package/dist/cjs/transactions/index.d.ts +7 -0
- package/dist/cjs/transactions/index.js +26 -0
- package/dist/cjs/transactions/swap.d.ts +71 -0
- package/dist/cjs/transactions/swap.js +184 -0
- package/dist/cjs/transactions/transact.d.ts +34 -0
- package/dist/cjs/transactions/transact.js +146 -0
- package/dist/cjs/transactions/transfer.d.ts +51 -0
- package/dist/cjs/transactions/transfer.js +106 -0
- package/dist/cjs/transactions/withdraw.d.ts +52 -0
- package/dist/cjs/transactions/withdraw.js +104 -0
- package/dist/cjs/utxo.d.ts +1 -215
- package/dist/cjs/utxo.js +15 -391
- package/dist/esm/accounts/admin.d.ts +84 -0
- package/dist/esm/accounts/admin.js +165 -0
- package/dist/esm/accounts/errors.d.ts +6 -0
- package/dist/esm/accounts/errors.js +95 -0
- package/dist/esm/accounts/index.d.ts +4 -0
- package/dist/esm/accounts/index.js +4 -0
- package/dist/esm/accounts/pdas.d.ts +23 -0
- package/dist/esm/accounts/pdas.js +38 -0
- package/dist/esm/accounts/queries.d.ts +41 -0
- package/dist/esm/accounts/queries.js +88 -0
- package/dist/esm/client.d.ts +2 -0
- package/dist/esm/client.js +3 -887
- package/dist/esm/cloak/client.d.ts +28 -0
- package/dist/esm/cloak/client.js +64 -0
- package/dist/esm/cloak/errors.d.ts +13 -0
- package/dist/esm/cloak/errors.js +21 -0
- package/dist/esm/cloak/helpers.d.ts +6 -0
- package/dist/esm/cloak/helpers.js +31 -0
- package/dist/esm/cloak/index.d.ts +6 -0
- package/dist/esm/cloak/index.js +6 -0
- package/dist/esm/cloak/polling.d.ts +7 -0
- package/dist/esm/cloak/polling.js +63 -0
- package/dist/esm/cloak/transport.d.ts +15 -0
- package/dist/esm/cloak/transport.js +115 -0
- package/dist/esm/cloak/types.d.ts +188 -0
- package/dist/esm/cloak/types.js +4 -0
- package/dist/esm/cloak.d.ts +1 -0
- package/dist/esm/cloak.js +2 -0
- package/dist/esm/compactNote.d.ts +128 -0
- package/dist/esm/compactNote.js +179 -0
- package/dist/esm/config.d.ts +82 -0
- package/dist/esm/events.d.ts +77 -0
- package/dist/esm/events.js +1 -1
- package/dist/esm/idl/privacy_pool.d.ts +5 -0
- package/dist/esm/idl/privacy_pool.js +15216 -0
- package/dist/esm/index.d.ts +17 -0
- package/dist/esm/index.js +20 -11
- package/dist/esm/merkle.d.ts +77 -0
- package/dist/esm/merkle.js +23 -1
- package/dist/esm/notes/encryption.d.ts +41 -0
- package/dist/esm/notes/encryption.js +67 -0
- package/dist/esm/notes/index.d.ts +5 -0
- package/dist/esm/notes/index.js +5 -0
- package/dist/esm/notes/mailbox.d.ts +70 -0
- package/dist/esm/notes/mailbox.js +154 -0
- package/dist/esm/notes/model.d.ts +91 -0
- package/dist/esm/notes/model.js +99 -0
- package/dist/esm/notes/nullifier.d.ts +27 -0
- package/dist/esm/notes/nullifier.js +40 -0
- package/dist/esm/notes/recovery.d.ts +10 -0
- package/dist/esm/notes/recovery.js +28 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/poseidon.d.ts +29 -0
- package/dist/esm/program.d.ts +37 -0
- package/dist/esm/program.js +23 -1
- package/dist/esm/proof.d.ts +1 -0
- package/dist/esm/proof.js +2 -281
- package/dist/esm/proofs/encoding.d.ts +17 -0
- package/dist/esm/proofs/encoding.js +68 -0
- package/dist/esm/proofs/formatting.d.ts +6 -0
- package/dist/esm/proofs/formatting.js +31 -0
- package/dist/esm/proofs/index.d.ts +5 -0
- package/dist/esm/proofs/index.js +5 -0
- package/dist/esm/proofs/swap.d.ts +87 -0
- package/dist/esm/proofs/swap.js +138 -0
- package/dist/esm/proofs/transaction.d.ts +28 -0
- package/dist/esm/proofs/transaction.js +105 -0
- package/dist/esm/proofs/types.d.ts +70 -0
- package/dist/esm/proofs/types.js +1 -0
- package/dist/esm/prover.d.ts +57 -0
- package/dist/esm/prover.js +10 -1
- package/dist/esm/random.d.ts +16 -0
- package/dist/esm/random.js +21 -0
- package/dist/esm/relayer/client.d.ts +61 -0
- package/dist/esm/relayer/client.js +144 -0
- package/dist/esm/relayer/crypto.d.ts +5 -0
- package/dist/esm/relayer/crypto.js +20 -0
- package/dist/esm/relayer/encoding.d.ts +2 -0
- package/dist/esm/relayer/encoding.js +19 -0
- package/dist/esm/relayer/errors.d.ts +7 -0
- package/dist/esm/relayer/errors.js +13 -0
- package/dist/esm/relayer/index.d.ts +3 -0
- package/dist/esm/relayer/index.js +3 -0
- package/dist/esm/relayer/transport.d.ts +17 -0
- package/dist/esm/relayer/transport.js +59 -0
- package/dist/esm/relayer/types.d.ts +276 -0
- package/dist/esm/relayer/types.js +4 -0
- package/dist/esm/relayer.d.ts +1 -0
- package/dist/esm/relayer.js +2 -238
- package/dist/esm/retry.d.ts +32 -0
- package/dist/esm/shield/alt.d.ts +87 -0
- package/dist/esm/shield/alt.js +186 -0
- package/dist/esm/shield/computeBudget.d.ts +61 -0
- package/dist/esm/shield/computeBudget.js +61 -0
- package/dist/esm/shield/errors.d.ts +58 -0
- package/dist/esm/shield/errors.js +115 -0
- package/dist/esm/shield/finalize.d.ts +45 -0
- package/dist/esm/shield/finalize.js +83 -0
- package/dist/esm/shield/index.d.ts +35 -0
- package/dist/esm/shield/index.js +32 -0
- package/dist/esm/shield/ix.d.ts +54 -0
- package/dist/esm/shield/ix.js +82 -0
- package/dist/esm/shield/owner.d.ts +36 -0
- package/dist/esm/shield/owner.js +123 -0
- package/dist/esm/shield/ports.d.ts +43 -0
- package/dist/esm/shield/ports.js +147 -0
- package/dist/esm/shield/preflight.d.ts +30 -0
- package/dist/esm/shield/preflight.js +151 -0
- package/dist/esm/shield/shield.d.ts +68 -0
- package/dist/esm/shield/shield.js +493 -0
- package/dist/esm/shield/types.d.ts +202 -0
- package/dist/esm/shield/types.js +1 -0
- package/dist/esm/transactions/deposit.d.ts +94 -0
- package/dist/esm/transactions/deposit.js +198 -0
- package/dist/esm/transactions/index.d.ts +7 -0
- package/dist/esm/transactions/index.js +7 -0
- package/dist/esm/transactions/swap.d.ts +71 -0
- package/dist/esm/transactions/swap.js +144 -0
- package/dist/esm/transactions/transact.d.ts +34 -0
- package/dist/esm/transactions/transact.js +110 -0
- package/dist/esm/transactions/transfer.d.ts +51 -0
- package/dist/esm/transactions/transfer.js +103 -0
- package/dist/esm/transactions/withdraw.d.ts +52 -0
- package/dist/esm/transactions/withdraw.js +101 -0
- package/dist/esm/utxo.d.ts +1 -0
- package/dist/esm/utxo.js +2 -372
- package/package.json +111 -15
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SHARED_ALT_MINTS = exports.SHARED_DEPOSIT_ALT = void 0;
|
|
4
|
+
exports.sharedDepositAlt = sharedDepositAlt;
|
|
5
|
+
exports.explicitAlt = explicitAlt;
|
|
6
|
+
exports.requiredAltKeys = requiredAltKeys;
|
|
7
|
+
exports.expectedDepositAltKeys = expectedDepositAltKeys;
|
|
8
|
+
exports.auditDepositAlt = auditDepositAlt;
|
|
9
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
10
|
+
const spl_token_1 = require("@solana/spl-token");
|
|
11
|
+
const pdas_js_1 = require("../accounts/pdas.js");
|
|
12
|
+
const program_js_1 = require("../program.js");
|
|
13
|
+
const config_js_1 = require("../config.js");
|
|
14
|
+
const errors_js_1 = require("./errors.js");
|
|
15
|
+
/**
|
|
16
|
+
* Address lookup table handling for the shield path.
|
|
17
|
+
*
|
|
18
|
+
* The ALT is not an optimisation here — it is load-bearing. A `transact` carries
|
|
19
|
+
* 847 bytes of instruction data across 16 accounts, which is ~1500 bytes as a
|
|
20
|
+
* legacy transaction and therefore unlandable. Compressing the constant pool
|
|
21
|
+
* accounts to 1-byte indices is what brings it to ~1163 and leaves room for
|
|
22
|
+
* Phantom's Lighthouse guard instructions.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* The shared, team-owned deposit table.
|
|
26
|
+
*
|
|
27
|
+
* Deliberately shared rather than per-wallet: creating a per-wallet table costs
|
|
28
|
+
* the signer an extra signature and unrecoverable rent, which is unacceptable to
|
|
29
|
+
* ask of an external wallet shielding once, and impossible to ask of a
|
|
30
|
+
* third-party platform's managed wallet.
|
|
31
|
+
*/
|
|
32
|
+
exports.SHARED_DEPOSIT_ALT = new web3_js_1.PublicKey("3x2JxCgAPmv4U4EJonFBntm1q4W3xzpnB6aT1tCvxftk");
|
|
33
|
+
/** Mints the shared table is provisioned for. */
|
|
34
|
+
exports.SHARED_ALT_MINTS = [
|
|
35
|
+
{ symbol: "SOL", mint: config_js_1.NATIVE_SOL_MINT },
|
|
36
|
+
{ symbol: "USDC", mint: new web3_js_1.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") },
|
|
37
|
+
{ symbol: "USDT", mint: new web3_js_1.PublicKey("Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB") },
|
|
38
|
+
{ symbol: "USD1", mint: new web3_js_1.PublicKey("USD1ttGY1N17NEEHLmELoaybftRBUSErhqYiQzvEmuB") },
|
|
39
|
+
{ symbol: "JUP", mint: new web3_js_1.PublicKey("JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN") },
|
|
40
|
+
{ symbol: "ARX", mint: new web3_js_1.PublicKey("ARXwZkNAtzPfdcoqQiduJn8EPv9fKiDfGn2KyggyDrFs") },
|
|
41
|
+
];
|
|
42
|
+
const ALT_CACHE_TTL_MS = 10 * 60000;
|
|
43
|
+
/**
|
|
44
|
+
* Load the shared table, and verify it actually covers this shield.
|
|
45
|
+
*
|
|
46
|
+
* The verification is the important part. Coverage is an *operational* property,
|
|
47
|
+
* not a code property: the table is populated by a script, and if a mint or a
|
|
48
|
+
* tree is missing, every one of its accounts falls back to a 32-byte static key
|
|
49
|
+
* and the transaction silently grows past 1232. Failing here with a list of the
|
|
50
|
+
* missing addresses is far better than failing at `signTransaction` with
|
|
51
|
+
* "transaction too large" and no indication why.
|
|
52
|
+
*/
|
|
53
|
+
function sharedDepositAlt(address = exports.SHARED_DEPOSIT_ALT, opts = {}) {
|
|
54
|
+
return {
|
|
55
|
+
async resolve({ connection, mint, outputTreeId, programId }) {
|
|
56
|
+
const key = `alt:${address.toBase58()}`;
|
|
57
|
+
let account = await opts.cache?.get(key);
|
|
58
|
+
if (!account) {
|
|
59
|
+
// "finalized" so every RPC the signer's wallet might simulate against
|
|
60
|
+
// has already seen the table. A table read at "processed" can be
|
|
61
|
+
// invisible to the wallet's own simulator.
|
|
62
|
+
const res = await connection.getAddressLookupTable(address, {
|
|
63
|
+
commitment: "finalized",
|
|
64
|
+
});
|
|
65
|
+
if (!res.value) {
|
|
66
|
+
throw new errors_js_1.ShieldError("ALT_NOT_FOUND", `Deposit address lookup table ${address.toBase58()} was not found at ` +
|
|
67
|
+
`finalized commitment. Without it a shield cannot fit in 1232 bytes.`, { context: { address: address.toBase58() } });
|
|
68
|
+
}
|
|
69
|
+
account = res.value;
|
|
70
|
+
await opts.cache?.set(key, account, ALT_CACHE_TTL_MS);
|
|
71
|
+
}
|
|
72
|
+
if (opts.verifyCoverage !== false) {
|
|
73
|
+
const missing = missingCoverage(account, {
|
|
74
|
+
mint,
|
|
75
|
+
outputTreeId,
|
|
76
|
+
programId,
|
|
77
|
+
});
|
|
78
|
+
if (missing.length > 0) {
|
|
79
|
+
throw new errors_js_1.ShieldError("ALT_INCOMPLETE", `The deposit lookup table does not cover this shield: ${missing.length} ` +
|
|
80
|
+
`required account(s) are absent, so the transaction would exceed 1232 ` +
|
|
81
|
+
`bytes. Extend the table before shielding this mint/tree.`, {
|
|
82
|
+
context: {
|
|
83
|
+
alt: address.toBase58(),
|
|
84
|
+
mint: mint.toBase58(),
|
|
85
|
+
outputTreeId,
|
|
86
|
+
missing: missing.map((m) => `${m.label} (${m.address.toBase58()})`),
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return account;
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
/** Use a table the caller already loaded. Skips both the RPC call and the coverage check. */
|
|
96
|
+
function explicitAlt(account) {
|
|
97
|
+
return { async resolve() { return account; } };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Accounts that must be in the table for a shield of `mint` into `outputTreeId`
|
|
101
|
+
* to fit.
|
|
102
|
+
*
|
|
103
|
+
* The signer, the two nullifier markers, and the user's token account are
|
|
104
|
+
* deliberately excluded: markers are unique per transaction and the others are
|
|
105
|
+
* per-user, so none can live in a shared table. They are the static keys the
|
|
106
|
+
* size budget accounts for.
|
|
107
|
+
*/
|
|
108
|
+
function requiredAltKeys(params) {
|
|
109
|
+
const programId = params.programId ?? program_js_1.PRIVACY_POOL_PROGRAM_ID;
|
|
110
|
+
const { config, vault, nullifiers } = (0, pdas_js_1.getPoolPdas)(programId, params.mint);
|
|
111
|
+
const keys = [
|
|
112
|
+
{ label: "programId", address: programId },
|
|
113
|
+
{ label: "computeBudgetProgram", address: web3_js_1.ComputeBudgetProgram.programId },
|
|
114
|
+
{ label: "systemProgram", address: web3_js_1.SystemProgram.programId },
|
|
115
|
+
{ label: "globalConfig", address: (0, pdas_js_1.getGlobalConfigPda)(programId) },
|
|
116
|
+
{ label: "config", address: config },
|
|
117
|
+
{ label: "vault", address: vault },
|
|
118
|
+
{ label: "nullifiers", address: nullifiers },
|
|
119
|
+
{ label: "inputTree(0)", address: (0, pdas_js_1.getNoteTreePda)(programId, params.mint, 0) },
|
|
120
|
+
];
|
|
121
|
+
if (params.outputTreeId !== 0) {
|
|
122
|
+
keys.push({
|
|
123
|
+
label: `outputTree(${params.outputTreeId})`,
|
|
124
|
+
address: (0, pdas_js_1.getNoteTreePda)(programId, params.mint, params.outputTreeId),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (!params.mint.equals(config_js_1.NATIVE_SOL_MINT)) {
|
|
128
|
+
keys.push({ label: "tokenProgram", address: spl_token_1.TOKEN_PROGRAM_ID }, {
|
|
129
|
+
label: "vaultAta",
|
|
130
|
+
address: (0, spl_token_1.getAssociatedTokenAddressSync)(params.mint, vault, true),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
return keys;
|
|
134
|
+
}
|
|
135
|
+
function missingCoverage(account, params) {
|
|
136
|
+
const present = new Set(account.state.addresses.map((a) => a.toBase58()));
|
|
137
|
+
return requiredAltKeys(params).filter((k) => !present.has(k.address.toBase58()));
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Everything the shared table should contain across every supported mint and
|
|
141
|
+
* every tree up to `maxTreeId`.
|
|
142
|
+
*
|
|
143
|
+
* Exported so this can be asserted in CI. The table currently only covers tree 0
|
|
144
|
+
* per mint, which means the size budget quietly loses 32 bytes of headroom the
|
|
145
|
+
* day tree 0 fills and `getBestTreeForDeposit` starts returning tree 1. That is
|
|
146
|
+
* a scheduled failure, and this function is how it gets caught before a user
|
|
147
|
+
* finds it.
|
|
148
|
+
*/
|
|
149
|
+
async function expectedDepositAltKeys(params) {
|
|
150
|
+
const programId = params.programId ?? program_js_1.PRIVACY_POOL_PROGRAM_ID;
|
|
151
|
+
const mints = params.mints ?? exports.SHARED_ALT_MINTS.map((m) => m.mint);
|
|
152
|
+
const maxTreeId = params.maxTreeId ?? 4;
|
|
153
|
+
const out = [
|
|
154
|
+
{ label: "programId", address: programId },
|
|
155
|
+
{ label: "computeBudgetProgram", address: web3_js_1.ComputeBudgetProgram.programId },
|
|
156
|
+
{ label: "systemProgram", address: web3_js_1.SystemProgram.programId },
|
|
157
|
+
{ label: "tokenProgram", address: spl_token_1.TOKEN_PROGRAM_ID },
|
|
158
|
+
{ label: "globalConfig", address: (0, pdas_js_1.getGlobalConfigPda)(programId) },
|
|
159
|
+
];
|
|
160
|
+
for (const mint of mints) {
|
|
161
|
+
const label = mint.equals(config_js_1.NATIVE_SOL_MINT) ? "SOL" : mint.toBase58().slice(0, 4);
|
|
162
|
+
const { config, vault, nullifiers } = (0, pdas_js_1.getPoolPdas)(programId, mint);
|
|
163
|
+
out.push({ label: `${label}.config`, address: config }, { label: `${label}.vault`, address: vault }, { label: `${label}.nullifiers`, address: nullifiers });
|
|
164
|
+
for (let t = 0; t <= maxTreeId; t++) {
|
|
165
|
+
out.push({
|
|
166
|
+
label: `${label}.tree(${t})`,
|
|
167
|
+
address: (0, pdas_js_1.getNoteTreePda)(programId, mint, t),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (!mint.equals(config_js_1.NATIVE_SOL_MINT)) {
|
|
171
|
+
out.push({
|
|
172
|
+
label: `${label}.vaultAta`,
|
|
173
|
+
address: await (0, spl_token_1.getAssociatedTokenAddress)(mint, vault, true),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return out;
|
|
178
|
+
}
|
|
179
|
+
/** Diff the on-chain table against {@link expectedDepositAltKeys}. */
|
|
180
|
+
async function auditDepositAlt(connection, params = {}) {
|
|
181
|
+
const address = params.address ?? exports.SHARED_DEPOSIT_ALT;
|
|
182
|
+
const res = await connection.getAddressLookupTable(address, {
|
|
183
|
+
commitment: "finalized",
|
|
184
|
+
});
|
|
185
|
+
if (!res.value) {
|
|
186
|
+
throw new errors_js_1.ShieldError("ALT_NOT_FOUND", `Deposit ALT ${address.toBase58()} not found`, { context: { address: address.toBase58() } });
|
|
187
|
+
}
|
|
188
|
+
const present = new Set(res.value.state.addresses.map((a) => a.toBase58()));
|
|
189
|
+
const expected = await expectedDepositAltKeys(params);
|
|
190
|
+
const expectedSet = new Set(expected.map((e) => e.address.toBase58()));
|
|
191
|
+
const missing = expected.filter((e) => !present.has(e.address.toBase58()));
|
|
192
|
+
const extra = [...present].filter((p) => !expectedSet.has(p));
|
|
193
|
+
return { ok: missing.length === 0, present: present.size, missing, extra };
|
|
194
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compute budget and transaction size constants for the shield path.
|
|
3
|
+
*
|
|
4
|
+
* These numbers are empirical and load-bearing. They are constants rather than
|
|
5
|
+
* inline literals so a future program change that invalidates them has one
|
|
6
|
+
* place to be fixed and one place to be read.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Compute unit limit reserved for a shield.
|
|
10
|
+
*
|
|
11
|
+
* Measured usage for a deposit is ~377k CU; 700k leaves roughly 2× headroom
|
|
12
|
+
* while staying low enough that Phantom's Lighthouse guard still fits its own
|
|
13
|
+
* instructions alongside ours.
|
|
14
|
+
*
|
|
15
|
+
* Do NOT raise this to 1.4M (what the stale `client.ts` `deposit()` reserves).
|
|
16
|
+
* An oversized CU reservation is one of the signals that makes Phantom render
|
|
17
|
+
* the transaction with a malicious-dApp warning.
|
|
18
|
+
*
|
|
19
|
+
* Ported from veilo-dapp/features/transactions/handleDeposit.ts:673-681.
|
|
20
|
+
*/
|
|
21
|
+
export declare const SHIELD_COMPUTE_UNIT_LIMIT = 700000;
|
|
22
|
+
/** Typical measured usage. If real usage approaches the limit above, revisit both. */
|
|
23
|
+
export declare const SHIELD_COMPUTE_UNITS_MEASURED_TYPICAL = 377000;
|
|
24
|
+
/** Default priority fee. Set to 0 to omit the instruction entirely (saves ~40 bytes). */
|
|
25
|
+
export declare const SHIELD_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS = 50000;
|
|
26
|
+
/** Solana's hard cap on a serialized transaction, signatures included. */
|
|
27
|
+
export declare const MAX_TRANSACTION_BYTES = 1232;
|
|
28
|
+
/** One ed25519 signature: 64 bytes plus the compact-array length prefix. */
|
|
29
|
+
export declare const SIGNATURE_BYTES = 65;
|
|
30
|
+
/**
|
|
31
|
+
* `NullifierMarker::LEN` (privacy-program .../lib.rs:415). Two markers are
|
|
32
|
+
* created per transact and paid for by the signer — see the note on sponsorship
|
|
33
|
+
* in the module docs of `shield.ts`.
|
|
34
|
+
*/
|
|
35
|
+
export declare const NULLIFIER_MARKER_ACCOUNT_BYTES = 10;
|
|
36
|
+
/** Markers created per `transact` — one per circuit input. */
|
|
37
|
+
export declare const NULLIFIER_MARKERS_PER_TRANSACT = 2;
|
|
38
|
+
/** Default validity window for the on-chain `deadline` argument. */
|
|
39
|
+
export declare const DEFAULT_DEADLINE_SECONDS = 3600;
|
|
40
|
+
/**
|
|
41
|
+
* Serialized size of the `transact` instruction data, for reference:
|
|
42
|
+
*
|
|
43
|
+
* 8 discriminator
|
|
44
|
+
* 32 root
|
|
45
|
+
* 2 input_tree_id
|
|
46
|
+
* 2 output_tree_id
|
|
47
|
+
* 8 public_amount
|
|
48
|
+
* 32 ext_data_hash
|
|
49
|
+
* 32 mint_address
|
|
50
|
+
* 128 2 nullifiers + 2 commitments
|
|
51
|
+
* 8 deadline
|
|
52
|
+
* 112 ExtData (recipient, relayer, fee, refund, claimant)
|
|
53
|
+
* 256 Groth16 proof (a:64, b:128, c:64)
|
|
54
|
+
* 227 Option<NoteCiphers> (1 tag + 226)
|
|
55
|
+
* ---
|
|
56
|
+
* 847
|
|
57
|
+
*
|
|
58
|
+
* With 16 accounts this cannot fit in a legacy transaction (~1500 bytes), which
|
|
59
|
+
* is why the shield path is v0-with-ALT and not optional.
|
|
60
|
+
*/
|
|
61
|
+
export declare const TRANSACT_INSTRUCTION_DATA_BYTES = 847;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Compute budget and transaction size constants for the shield path.
|
|
4
|
+
*
|
|
5
|
+
* These numbers are empirical and load-bearing. They are constants rather than
|
|
6
|
+
* inline literals so a future program change that invalidates them has one
|
|
7
|
+
* place to be fixed and one place to be read.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.TRANSACT_INSTRUCTION_DATA_BYTES = exports.DEFAULT_DEADLINE_SECONDS = exports.NULLIFIER_MARKERS_PER_TRANSACT = exports.NULLIFIER_MARKER_ACCOUNT_BYTES = exports.SIGNATURE_BYTES = exports.MAX_TRANSACTION_BYTES = exports.SHIELD_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS = exports.SHIELD_COMPUTE_UNITS_MEASURED_TYPICAL = exports.SHIELD_COMPUTE_UNIT_LIMIT = void 0;
|
|
11
|
+
/**
|
|
12
|
+
* Compute unit limit reserved for a shield.
|
|
13
|
+
*
|
|
14
|
+
* Measured usage for a deposit is ~377k CU; 700k leaves roughly 2× headroom
|
|
15
|
+
* while staying low enough that Phantom's Lighthouse guard still fits its own
|
|
16
|
+
* instructions alongside ours.
|
|
17
|
+
*
|
|
18
|
+
* Do NOT raise this to 1.4M (what the stale `client.ts` `deposit()` reserves).
|
|
19
|
+
* An oversized CU reservation is one of the signals that makes Phantom render
|
|
20
|
+
* the transaction with a malicious-dApp warning.
|
|
21
|
+
*
|
|
22
|
+
* Ported from veilo-dapp/features/transactions/handleDeposit.ts:673-681.
|
|
23
|
+
*/
|
|
24
|
+
exports.SHIELD_COMPUTE_UNIT_LIMIT = 700000;
|
|
25
|
+
/** Typical measured usage. If real usage approaches the limit above, revisit both. */
|
|
26
|
+
exports.SHIELD_COMPUTE_UNITS_MEASURED_TYPICAL = 377000;
|
|
27
|
+
/** Default priority fee. Set to 0 to omit the instruction entirely (saves ~40 bytes). */
|
|
28
|
+
exports.SHIELD_COMPUTE_UNIT_PRICE_MICRO_LAMPORTS = 50000;
|
|
29
|
+
/** Solana's hard cap on a serialized transaction, signatures included. */
|
|
30
|
+
exports.MAX_TRANSACTION_BYTES = 1232;
|
|
31
|
+
/** One ed25519 signature: 64 bytes plus the compact-array length prefix. */
|
|
32
|
+
exports.SIGNATURE_BYTES = 65;
|
|
33
|
+
/**
|
|
34
|
+
* `NullifierMarker::LEN` (privacy-program .../lib.rs:415). Two markers are
|
|
35
|
+
* created per transact and paid for by the signer — see the note on sponsorship
|
|
36
|
+
* in the module docs of `shield.ts`.
|
|
37
|
+
*/
|
|
38
|
+
exports.NULLIFIER_MARKER_ACCOUNT_BYTES = 10;
|
|
39
|
+
/** Markers created per `transact` — one per circuit input. */
|
|
40
|
+
exports.NULLIFIER_MARKERS_PER_TRANSACT = 2;
|
|
41
|
+
/** Default validity window for the on-chain `deadline` argument. */
|
|
42
|
+
exports.DEFAULT_DEADLINE_SECONDS = 3600;
|
|
43
|
+
/**
|
|
44
|
+
* Serialized size of the `transact` instruction data, for reference:
|
|
45
|
+
*
|
|
46
|
+
* 8 discriminator
|
|
47
|
+
* 32 root
|
|
48
|
+
* 2 input_tree_id
|
|
49
|
+
* 2 output_tree_id
|
|
50
|
+
* 8 public_amount
|
|
51
|
+
* 32 ext_data_hash
|
|
52
|
+
* 32 mint_address
|
|
53
|
+
* 128 2 nullifiers + 2 commitments
|
|
54
|
+
* 8 deadline
|
|
55
|
+
* 112 ExtData (recipient, relayer, fee, refund, claimant)
|
|
56
|
+
* 256 Groth16 proof (a:64, b:128, c:64)
|
|
57
|
+
* 227 Option<NoteCiphers> (1 tag + 226)
|
|
58
|
+
* ---
|
|
59
|
+
* 847
|
|
60
|
+
*
|
|
61
|
+
* With 16 accounts this cannot fit in a legacy transaction (~1500 bytes), which
|
|
62
|
+
* is why the shield path is v0-with-ALT and not optional.
|
|
63
|
+
*/
|
|
64
|
+
exports.TRANSACT_INSTRUCTION_DATA_BYTES = 847;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { TransactionInstruction } from "@solana/web3.js";
|
|
2
|
+
/**
|
|
3
|
+
* Error taxonomy for the shield path.
|
|
4
|
+
*
|
|
5
|
+
* `parseOnChainError` already turns program codes into readable sentences. What
|
|
6
|
+
* it does not give you is a *decision*: can this be retried, and how? That is
|
|
7
|
+
* what integrators actually need, because the three retry classes have wildly
|
|
8
|
+
* different costs:
|
|
9
|
+
*
|
|
10
|
+
* - `rebuild` — reuse the proof, new blockhash. Milliseconds.
|
|
11
|
+
* - `reshield` — the proof is stale, prove again. 10–30 seconds.
|
|
12
|
+
* - `none` — retrying cannot help; something must change first.
|
|
13
|
+
*/
|
|
14
|
+
export type ShieldRetryClass = "rebuild" | "reshield" | "none";
|
|
15
|
+
export type ShieldErrorCode = "POOL_NOT_FOUND" | "POOL_PAUSED" | "AMOUNT_BELOW_MINIMUM" | "AMOUNT_ABOVE_MAXIMUM" | "TREE_FULL" | "INSUFFICIENT_SOL" | "INSUFFICIENT_TOKEN_BALANCE" | "TOKEN_ACCOUNT_MISSING" | "TOKEN_ACCOUNT_WRONG_MINT" | "TOKEN_ACCOUNT_WRONG_OWNER" | "TOKEN_ACCOUNT_DELEGATED" | "TOKEN_ACCOUNT_FROZEN" | "VAULT_ATA_MISSING" | "ALT_NOT_FOUND" | "ALT_INCOMPLETE" | "TX_TOO_LARGE" | "OWNER_INVARIANT_UNACKNOWLEDGED" | "UNRESOLVED_ACCOUNT" | "BLOCKHASH_EXPIRED" | "DEADLINE_EXPIRED" | "ROOT_STALE" | "NULLIFIER_COLLISION" | "COMPUTE_BUDGET_EXCEEDED" | "PROOF_REJECTED" | "UNKNOWN";
|
|
16
|
+
export type ShieldErrorRemedy = {
|
|
17
|
+
/** Human-readable description of what would fix this. */
|
|
18
|
+
description: string;
|
|
19
|
+
/** Instructions that, once executed, clear the condition. */
|
|
20
|
+
instructions: TransactionInstruction[];
|
|
21
|
+
};
|
|
22
|
+
export declare class ShieldError extends Error {
|
|
23
|
+
readonly code: ShieldErrorCode;
|
|
24
|
+
/** Program error code (6000–6060) when this was derived from a chain error. */
|
|
25
|
+
readonly programCode?: number;
|
|
26
|
+
/** Whether and how a retry can succeed. */
|
|
27
|
+
readonly retryable: ShieldRetryClass;
|
|
28
|
+
/** Machine-readable detail: which account, what value, what was expected. */
|
|
29
|
+
readonly context?: Record<string, unknown>;
|
|
30
|
+
/** Present when the condition is fixable by executing instructions first. */
|
|
31
|
+
readonly remedy?: ShieldErrorRemedy;
|
|
32
|
+
readonly cause?: unknown;
|
|
33
|
+
constructor(code: ShieldErrorCode, message: string, opts?: {
|
|
34
|
+
programCode?: number;
|
|
35
|
+
retryable?: ShieldRetryClass;
|
|
36
|
+
context?: Record<string, unknown>;
|
|
37
|
+
remedy?: ShieldErrorRemedy;
|
|
38
|
+
cause?: unknown;
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/** Extract a privacy_pool program error code from an arbitrary thrown value. */
|
|
42
|
+
export declare function extractProgramCode(error: any): number | null;
|
|
43
|
+
/**
|
|
44
|
+
* Map a submission failure onto the taxonomy.
|
|
45
|
+
*
|
|
46
|
+
* Two mappings here are not obvious and are worth stating plainly:
|
|
47
|
+
*
|
|
48
|
+
* **Nullifier collision means "already landed", not "already spent."** The
|
|
49
|
+
* marker PDAs are `init`, not `init_if_needed`, so a second submission of the
|
|
50
|
+
* same shield fails at account resolution. For a deposit the nullifiers derive
|
|
51
|
+
* from freshly generated random keys, so a genuine collision is impossible —
|
|
52
|
+
* in practice this only ever means the first attempt succeeded. Telling a user
|
|
53
|
+
* their funds were "already spent" here would be alarming and wrong.
|
|
54
|
+
*
|
|
55
|
+
* **`UnknownRoot` is recoverable but not by `rebuild()`.** The proof commits to
|
|
56
|
+
* the root, so a stale root needs a fresh proof, not a fresh blockhash.
|
|
57
|
+
*/
|
|
58
|
+
export declare function mapShieldError(error: unknown): ShieldError;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ShieldError = void 0;
|
|
4
|
+
exports.extractProgramCode = extractProgramCode;
|
|
5
|
+
exports.mapShieldError = mapShieldError;
|
|
6
|
+
const errors_js_1 = require("../accounts/errors.js");
|
|
7
|
+
class ShieldError extends Error {
|
|
8
|
+
constructor(code, message, opts = {}) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "ShieldError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
this.programCode = opts.programCode;
|
|
13
|
+
this.retryable = opts.retryable ?? "none";
|
|
14
|
+
this.context = opts.context;
|
|
15
|
+
this.remedy = opts.remedy;
|
|
16
|
+
this.cause = opts.cause;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.ShieldError = ShieldError;
|
|
20
|
+
/** Extract a privacy_pool program error code from an arbitrary thrown value. */
|
|
21
|
+
function extractProgramCode(error) {
|
|
22
|
+
const raw = (error?.message ?? error?.toString() ?? "").toLowerCase();
|
|
23
|
+
const hex = raw.match(/custom program error:\s*0x([0-9a-f]+)/);
|
|
24
|
+
if (hex)
|
|
25
|
+
return parseInt(hex[1], 16);
|
|
26
|
+
const num = raw.match(/error number:\s*(\d+)/);
|
|
27
|
+
if (num)
|
|
28
|
+
return parseInt(num[1], 10);
|
|
29
|
+
if (typeof error?.error?.errorCode?.number === "number") {
|
|
30
|
+
return error.error.errorCode.number;
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Map a submission failure onto the taxonomy.
|
|
36
|
+
*
|
|
37
|
+
* Two mappings here are not obvious and are worth stating plainly:
|
|
38
|
+
*
|
|
39
|
+
* **Nullifier collision means "already landed", not "already spent."** The
|
|
40
|
+
* marker PDAs are `init`, not `init_if_needed`, so a second submission of the
|
|
41
|
+
* same shield fails at account resolution. For a deposit the nullifiers derive
|
|
42
|
+
* from freshly generated random keys, so a genuine collision is impossible —
|
|
43
|
+
* in practice this only ever means the first attempt succeeded. Telling a user
|
|
44
|
+
* their funds were "already spent" here would be alarming and wrong.
|
|
45
|
+
*
|
|
46
|
+
* **`UnknownRoot` is recoverable but not by `rebuild()`.** The proof commits to
|
|
47
|
+
* the root, so a stale root needs a fresh proof, not a fresh blockhash.
|
|
48
|
+
*/
|
|
49
|
+
function mapShieldError(error) {
|
|
50
|
+
if (error instanceof ShieldError)
|
|
51
|
+
return error;
|
|
52
|
+
const err = error;
|
|
53
|
+
const raw = (err?.message ?? String(err ?? "")).toLowerCase();
|
|
54
|
+
const programCode = extractProgramCode(err);
|
|
55
|
+
const readable = (0, errors_js_1.parseOnChainError)(err);
|
|
56
|
+
if (programCode !== null) {
|
|
57
|
+
switch (programCode) {
|
|
58
|
+
case 6000:
|
|
59
|
+
return new ShieldError("POOL_PAUSED", readable, { programCode, cause: err });
|
|
60
|
+
case 6007:
|
|
61
|
+
return new ShieldError("ROOT_STALE", "The Merkle root this shield was proven against is no longer in the on-chain " +
|
|
62
|
+
"history. Build a new shield — the proof cannot be reused.", { programCode, retryable: "reshield", cause: err });
|
|
63
|
+
case 6012:
|
|
64
|
+
return new ShieldError("PROOF_REJECTED", readable, { programCode, cause: err });
|
|
65
|
+
case 6013:
|
|
66
|
+
return new ShieldError("TREE_FULL", readable, {
|
|
67
|
+
programCode,
|
|
68
|
+
retryable: "reshield",
|
|
69
|
+
cause: err,
|
|
70
|
+
});
|
|
71
|
+
case 6017:
|
|
72
|
+
return new ShieldError("TOKEN_ACCOUNT_WRONG_MINT", readable, {
|
|
73
|
+
programCode,
|
|
74
|
+
cause: err,
|
|
75
|
+
});
|
|
76
|
+
case 6029:
|
|
77
|
+
return new ShieldError("TOKEN_ACCOUNT_DELEGATED", "The source token account has an active delegate. Revoke it, then shield again.", { programCode, cause: err });
|
|
78
|
+
case 6033:
|
|
79
|
+
return new ShieldError("TOKEN_ACCOUNT_WRONG_OWNER", "The source token account is not owned by the signing wallet.", { programCode, cause: err });
|
|
80
|
+
case 6035:
|
|
81
|
+
return new ShieldError("AMOUNT_BELOW_MINIMUM", readable, { programCode, cause: err });
|
|
82
|
+
case 6036:
|
|
83
|
+
return new ShieldError("AMOUNT_ABOVE_MAXIMUM", readable, { programCode, cause: err });
|
|
84
|
+
case 6050:
|
|
85
|
+
return new ShieldError("VAULT_ATA_MISSING", readable, { programCode, cause: err });
|
|
86
|
+
case 6005:
|
|
87
|
+
case 6046:
|
|
88
|
+
case 6052:
|
|
89
|
+
return new ShieldError("NULLIFIER_COLLISION", ALREADY_SHIELDED_MESSAGE, {
|
|
90
|
+
programCode,
|
|
91
|
+
cause: err,
|
|
92
|
+
});
|
|
93
|
+
case 6060:
|
|
94
|
+
return new ShieldError("DEADLINE_EXPIRED", "The shield's deadline passed before it landed. Rebuild it — the proof is still valid.", { programCode, retryable: "rebuild", cause: err });
|
|
95
|
+
default:
|
|
96
|
+
return new ShieldError("UNKNOWN", readable, { programCode, cause: err });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Non-program failures.
|
|
100
|
+
if (raw.includes("block height exceeded") ||
|
|
101
|
+
raw.includes("blockhash not found") ||
|
|
102
|
+
err?.name === "TransactionExpiredBlockheightExceededError") {
|
|
103
|
+
return new ShieldError("BLOCKHASH_EXPIRED", "The blockhash expired before the shield landed. Rebuild it — the proof is still valid.", { retryable: "rebuild", cause: err });
|
|
104
|
+
}
|
|
105
|
+
if (raw.includes("already in use")) {
|
|
106
|
+
return new ShieldError("NULLIFIER_COLLISION", ALREADY_SHIELDED_MESSAGE, { cause: err });
|
|
107
|
+
}
|
|
108
|
+
if (raw.includes("exceeded cus") ||
|
|
109
|
+
raw.includes("computational budget exceeded") ||
|
|
110
|
+
raw.includes("exceeded compute")) {
|
|
111
|
+
return new ShieldError("COMPUTE_BUDGET_EXCEEDED", `The shield exceeded its compute budget. Real usage has grown past the reserved ` +
|
|
112
|
+
`limit — SHIELD_COMPUTE_UNIT_LIMIT needs revisiting.`, { cause: err });
|
|
113
|
+
}
|
|
114
|
+
if (raw.includes("too large") || raw.includes("transaction too large")) {
|
|
115
|
+
return new ShieldError("TX_TOO_LARGE", readable, { cause: err });
|
|
116
|
+
}
|
|
117
|
+
return new ShieldError("UNKNOWN", readable, { cause: err });
|
|
118
|
+
}
|
|
119
|
+
const ALREADY_SHIELDED_MESSAGE = "This shield has already been submitted — check the transaction signature before " +
|
|
120
|
+
"retrying. (The nullifier markers already exist on-chain, which for a deposit means " +
|
|
121
|
+
"the original transaction landed; it does not mean the funds were spent.)";
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Connection, PublicKey } from "@solana/web3.js";
|
|
2
|
+
import type { ShieldNote, ShieldOwner } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Read the authoritative leaf index for a landed shield, and produce the
|
|
5
|
+
* mailbox blob for optional relayer delivery.
|
|
6
|
+
*
|
|
7
|
+
* ## Why this is a separate step
|
|
8
|
+
*
|
|
9
|
+
* Every existing deposit implementation records `tree.nextIndex` read *before*
|
|
10
|
+
* submission as the note's leaf index. Under concurrent deposits into the same
|
|
11
|
+
* tree that value is wrong, and it is wrong permanently — the stored index is
|
|
12
|
+
* what a later withdrawal builds its Merkle path from, so the note becomes
|
|
13
|
+
* unspendable through the normal path.
|
|
14
|
+
*
|
|
15
|
+
* That race is rare for a single wallet and constant for an SDK: a platform
|
|
16
|
+
* batching shields for many users will hit it routinely. So `shield()` reports
|
|
17
|
+
* only a `predictedLeafIndex`, and the real one is read here from the
|
|
18
|
+
* transaction's own `CommitmentEvent` after it confirms.
|
|
19
|
+
*
|
|
20
|
+
* ## What is optional
|
|
21
|
+
*
|
|
22
|
+
* Everything, strictly. The note is recoverable from chain data alone via the
|
|
23
|
+
* on-chain compact cipher, so a caller that skips finalization has not lost the
|
|
24
|
+
* funds — only the fast path to finding them. Persist nothing that depends on a
|
|
25
|
+
* leaf index without calling this first.
|
|
26
|
+
*/
|
|
27
|
+
export declare function finalizeShield(params: {
|
|
28
|
+
connection: Connection;
|
|
29
|
+
signature: string;
|
|
30
|
+
note: ShieldNote;
|
|
31
|
+
owner: ShieldOwner;
|
|
32
|
+
programId?: PublicKey;
|
|
33
|
+
/** Defaults to "confirmed". Use "finalized" if the index will be persisted. */
|
|
34
|
+
commitment?: "confirmed" | "finalized";
|
|
35
|
+
}): Promise<{
|
|
36
|
+
leafIndex: number;
|
|
37
|
+
newRoot: Uint8Array;
|
|
38
|
+
treeId: number;
|
|
39
|
+
slot: number;
|
|
40
|
+
/** Encrypted note blob, ready for POST /notes/save if the caller wants mailbox delivery. */
|
|
41
|
+
blob: {
|
|
42
|
+
ephemeralPublicKey: Uint8Array;
|
|
43
|
+
encryptedBlob: string;
|
|
44
|
+
};
|
|
45
|
+
}>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.finalizeShield = finalizeShield;
|
|
37
|
+
const anchor = __importStar(require("@coral-xyz/anchor"));
|
|
38
|
+
const program_js_1 = require("../program.js");
|
|
39
|
+
const mailbox_js_1 = require("../notes/mailbox.js");
|
|
40
|
+
const poseidon_js_1 = require("../poseidon.js");
|
|
41
|
+
const errors_js_1 = require("./errors.js");
|
|
42
|
+
/**
|
|
43
|
+
* Read the authoritative leaf index for a landed shield, and produce the
|
|
44
|
+
* mailbox blob for optional relayer delivery.
|
|
45
|
+
*
|
|
46
|
+
* ## Why this is a separate step
|
|
47
|
+
*
|
|
48
|
+
* Every existing deposit implementation records `tree.nextIndex` read *before*
|
|
49
|
+
* submission as the note's leaf index. Under concurrent deposits into the same
|
|
50
|
+
* tree that value is wrong, and it is wrong permanently — the stored index is
|
|
51
|
+
* what a later withdrawal builds its Merkle path from, so the note becomes
|
|
52
|
+
* unspendable through the normal path.
|
|
53
|
+
*
|
|
54
|
+
* That race is rare for a single wallet and constant for an SDK: a platform
|
|
55
|
+
* batching shields for many users will hit it routinely. So `shield()` reports
|
|
56
|
+
* only a `predictedLeafIndex`, and the real one is read here from the
|
|
57
|
+
* transaction's own `CommitmentEvent` after it confirms.
|
|
58
|
+
*
|
|
59
|
+
* ## What is optional
|
|
60
|
+
*
|
|
61
|
+
* Everything, strictly. The note is recoverable from chain data alone via the
|
|
62
|
+
* on-chain compact cipher, so a caller that skips finalization has not lost the
|
|
63
|
+
* funds — only the fast path to finding them. Persist nothing that depends on a
|
|
64
|
+
* leaf index without calling this first.
|
|
65
|
+
*/
|
|
66
|
+
async function finalizeShield(params) {
|
|
67
|
+
const { connection, signature, note, owner, programId = program_js_1.PRIVACY_POOL_PROGRAM_ID, commitment = "confirmed", } = params;
|
|
68
|
+
const tx = await connection.getTransaction(signature, {
|
|
69
|
+
commitment: commitment === "finalized" ? "finalized" : "confirmed",
|
|
70
|
+
maxSupportedTransactionVersion: 0,
|
|
71
|
+
});
|
|
72
|
+
if (!tx) {
|
|
73
|
+
throw new errors_js_1.ShieldError("UNKNOWN", `Transaction ${signature} not found at ${commitment} commitment. ` +
|
|
74
|
+
`If it was just submitted, wait for confirmation and retry.`, { context: { signature } });
|
|
75
|
+
}
|
|
76
|
+
if (tx.meta?.err) {
|
|
77
|
+
throw new errors_js_1.ShieldError("UNKNOWN", `Transaction ${signature} failed on-chain; there is no note to finalize.`, { context: { signature, err: tx.meta.err } });
|
|
78
|
+
}
|
|
79
|
+
const parser = new anchor.EventParser(programId, new anchor.BorshCoder(program_js_1.PRIVACY_POOL_IDL));
|
|
80
|
+
const wantCommitment = Buffer.from(note.commitment).toString("hex");
|
|
81
|
+
let match = null;
|
|
82
|
+
for (const event of parser.parseLogs(tx.meta?.logMessages ?? [])) {
|
|
83
|
+
if (event.name !== "CommitmentEvent" && event.name !== "commitmentEvent")
|
|
84
|
+
continue;
|
|
85
|
+
const data = event.data;
|
|
86
|
+
const emitted = Buffer.from(data.commitment).toString("hex");
|
|
87
|
+
if (emitted === wantCommitment) {
|
|
88
|
+
match = data;
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (!match) {
|
|
93
|
+
throw new errors_js_1.ShieldError("UNKNOWN", `Transaction ${signature} landed but emitted no CommitmentEvent for this note's ` +
|
|
94
|
+
`commitment. The note may belong to a different transaction.`, { context: { signature, commitment: wantCommitment } });
|
|
95
|
+
}
|
|
96
|
+
const leafIndex = Number(match.leafIndex ?? match.leaf_index);
|
|
97
|
+
const treeId = Number(match.treeId ?? match.tree_id ?? note.treeId);
|
|
98
|
+
const newRoot = new Uint8Array(match.newRoot ?? match.new_root);
|
|
99
|
+
// Mailbox copy. Sending this to the relayer with a `recipientWalletPublicKey`
|
|
100
|
+
// buys instant delivery and a push notification, at the cost of writing the
|
|
101
|
+
// sender→recipient association into the relayer's database AND removing the
|
|
102
|
+
// note from the chain-recovery feed. For shield-to-someone-else, chain-only
|
|
103
|
+
// discovery is usually the better default — see the plan's Phase 3 note.
|
|
104
|
+
const blob = (0, mailbox_js_1.encryptBlindMailboxNote)(owner.noteViewingKey.toBytes(), {
|
|
105
|
+
blinding: (0, poseidon_js_1.bigIntToBytesBE)(note.blinding),
|
|
106
|
+
leafIndex,
|
|
107
|
+
commitment: note.commitment,
|
|
108
|
+
amount: note.amount,
|
|
109
|
+
mintAddress: note.mint.toBase58(),
|
|
110
|
+
treeId,
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
leafIndex,
|
|
114
|
+
newRoot,
|
|
115
|
+
treeId,
|
|
116
|
+
slot: tx.slot,
|
|
117
|
+
blob,
|
|
118
|
+
};
|
|
119
|
+
}
|