diff --git a/crates/cmds-bun/src/umbra/umbra_claim_utxo.ts b/crates/cmds-bun/src/umbra/umbra_claim_utxo.ts index a7cce06f..7a926eee 100644 --- a/crates/cmds-bun/src/umbra/umbra_claim_utxo.ts +++ b/crates/cmds-bun/src/umbra/umbra_claim_utxo.ts @@ -49,7 +49,10 @@ export default class UmbraClaimUtxo extends BaseCommand { console.log(` utxo data: ${safeJsonStringify(inputs.utxo_data).substring(0, 200)}...`); try { - const result = await (claimUtxo as any)(inputs.utxo_data); + const utxoBatch = Array.isArray(inputs.utxo_data) + ? inputs.utxo_data + : [inputs.utxo_data]; + const result = await (claimUtxo as any)(utxoBatch); console.log("UTXO claimed:", safeJsonStringify(result, 2)); diff --git a/crates/cmds-bun/src/umbra/umbra_common.ts b/crates/cmds-bun/src/umbra/umbra_common.ts index 43d31dab..3eadaaef 100644 --- a/crates/cmds-bun/src/umbra/umbra_common.ts +++ b/crates/cmds-bun/src/umbra/umbra_common.ts @@ -37,13 +37,13 @@ import { BaseCommand, Value, type Context } from "@space-operator/flow-lib-bun"; import bs58 from "bs58"; export const INDEXER_ENDPOINT_MAINNET = - "https://acqzie0a1h.execute-api.eu-central-1.amazonaws.com"; + "https://utxo-indexer.api.umbraprivacy.com"; export const INDEXER_ENDPOINT_DEVNET = "https://utxo-indexer.api-devnet.umbraprivacy.com"; // Backward-compatible alias; prefer the network-specific constants above. export const INDEXER_ENDPOINT = INDEXER_ENDPOINT_MAINNET; export const RELAYER_ENDPOINT_MAINNET = - "https://6yn4ndrv2i.execute-api.eu-central-1.amazonaws.com"; + "https://relayer.api.umbraprivacy.com"; // Devnet relayer inferred from the indexer URL pattern // (utxo-indexer.api-devnet.umbraprivacy.com → relayer.api-devnet.umbraprivacy.com). // If Umbra publishes a different devnet relayer domain, override via env or update here. @@ -165,9 +165,20 @@ export function getPrimarySignature(result: unknown): string { if (typeof result === "object" && result !== null) { const record = result as Record; + const batches = record.batches; + if (batches instanceof Map) { + const signatures: string[] = []; + for (const value of batches.values()) { + const signature = getPrimarySignature(value); + if (signature) signatures.push(signature); + } + if (signatures.length > 0) return signatures.join(","); + } + for ( const key of [ "signature", + "txSignature", "createUtxoSignature", "callbackSignature", "createProofAccountSignature", @@ -1405,7 +1416,10 @@ async function resolveCircuitAssets( ); } - const zkeyRel = entry.zkey ?? entry.url ?? entry; + const selectedEntry = typeof entry === "object" && entry !== null && !entry.zkey && !entry.url + ? entry.n1 ?? Object.values(entry)[0] + : entry; + const zkeyRel = selectedEntry.zkey ?? selectedEntry.url ?? selectedEntry; const wasmRel = (typeof zkeyRel === "string" ? zkeyRel : "").replace( ".zkey", ".wasm", diff --git a/crates/cmds-bun/src/umbra/umbra_fetch_utxos.ts b/crates/cmds-bun/src/umbra/umbra_fetch_utxos.ts index 88f2de8b..f682b0d2 100644 --- a/crates/cmds-bun/src/umbra/umbra_fetch_utxos.ts +++ b/crates/cmds-bun/src/umbra/umbra_fetch_utxos.ts @@ -56,21 +56,10 @@ export default class UmbraFetchUtxos extends BaseCommand { const myPublicSelfBurnable = publicSelfBurnable.filter(mineOnly); const myPublicReceived = publicReceived.filter(mineOnly); - // `utxos` feeds umbra_claim_utxo, which is wired to the SDK's - // getReceiverClaimableUtxoToEncryptedBalanceClaimerFunction. That claimer - // consumes the scanner's `received` category (encrypted receiver-claimable - // UTXOs). Public-tagged UTXOs have a different on-chain shape and make - // the claimer throw byte-size errors, so we expose them via separate - // outputs instead and keep `utxos` restricted to what Claim can process. - // - // SDK 4.0 note: as of @umbra-privacy/sdk@4.0.0, both - // getPublicBalanceToReceiverClaimableUtxoCreatorFunction AND - // getEncryptedBalanceToReceiverClaimableUtxoCreatorFunction round-trip - // through the indexer as `public-received` (not `received`). Until that - // upstream behavior changes, end-to-end Create→Fetch→Claim of - // receiver-claimable UTXOs on devnet will see the UTXO land in - // `publicReceived` and `utxos` stay empty. See docs/issues.md. - const claimableByReceiver = [...received]; + // Receiver-claimable UTXOs created from public balances currently index as + // `publicReceived`; include them so treasury claim flows see real inbound + // deposits instead of an empty `received` bucket. + const claimableByReceiver = [...received, ...myPublicReceived]; const allUtxos = [ ...selfBurnable, ...received, diff --git a/crates/cmds-bun/src/umbra/umbra_withdraw.ts b/crates/cmds-bun/src/umbra/umbra_withdraw.ts index 2199ac16..96408919 100644 --- a/crates/cmds-bun/src/umbra/umbra_withdraw.ts +++ b/crates/cmds-bun/src/umbra/umbra_withdraw.ts @@ -17,24 +17,57 @@ export default class UmbraWithdraw extends BaseCommand { ); const withdraw = getEncryptedBalanceToPublicBalanceDirectWithdrawerFunction({ client }); - const amount = BigInt(inputs.amount) as any; + const amount = BigInt(inputs.amount); + const chunkSize = inputs.chunk_size !== undefined + ? BigInt(inputs.chunk_size) + : 1_000_000n; const destination = (inputs.destination || client.signer.address) as any; console.log(`Withdrawing ${amount} tokens from encrypted balance...`); + console.log(` chunk_size: ${chunkSize}`); console.log(` destination: ${destination}`); console.log(` mint: ${inputs.mint}`); - const result = await withdraw(destination, inputs.mint as any, amount); + if (amount <= 0n) { + throw new Error("amount must be greater than zero"); + } + if (chunkSize <= 0n) { + throw new Error("chunk_size must be greater than zero"); + } - console.log("Withdrawal complete:", JSON.stringify(result, (_k: string, v: any) => typeof v === "bigint" ? v.toString() : v)); + let remaining = amount; + const results: any[] = []; + while (remaining > 0n) { + const nextAmount = remaining > chunkSize ? chunkSize : remaining; + console.log(` withdrawing chunk: ${nextAmount}`); + const result = await withdraw(destination, inputs.mint as any, nextAmount as any); + results.push({ amount: nextAmount, result }); + remaining -= nextAmount; + console.log("Withdrawal chunk complete:", JSON.stringify(result, (_k: string, v: any) => typeof v === "bigint" ? v.toString() : v)); + } - const signature = Array.isArray(result) - ? result.map(String).join(",") - : typeof result === "string" - ? result - : JSON.stringify(result, (_k: string, v: any) => typeof v === "bigint" ? v.toString() : v); + console.log("Withdrawal complete:", JSON.stringify(results, (_k: string, v: any) => typeof v === "bigint" ? v.toString() : v)); - return { signature }; + const signature = results + .map((entry) => { + const result = entry.result; + if (typeof result === "string") return result; + if (Array.isArray(result)) return result.map(String).join(","); + if (result && typeof result === "object") { + return result.callbackSignature || result.queueSignature || result.signature || JSON.stringify(result, (_k: string, v: any) => typeof v === "bigint" ? v.toString() : v); + } + return String(result); + }) + .filter(Boolean) + .join(","); + + return { + signature, + chunks: results.map((entry) => ({ + amount: entry.amount.toString(), + result: entry.result, + })), + }; } }