@anywalletkit/wallet-zcash 0.2.6 → 0.3.1

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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SpentOutput, Z as ZcashNetwork, a as ZcashTxDescription, b as ZcashClient, U as Utxo, c as ZcashTxInfo, d as ZcashConfig, e as ZcashSpend, f as ZcashTransferOptions, g as ZcashMaxSpendableResult, h as ZcashReceipt, i as ZcashTransaction } from './history-DQNeEwXE.cjs';
2
- export { B as BLOCKCHAIR_URL, j as BlockchairClient, k as BlockchairTxDashboard, C as CAIP2, H as HistoryUnavailableError, l as ZcashChainTx, m as ZcashClientConfig, n as ZcashHistoryCursor, o as ZcashHistoryOptions, p as ZcashHistoryPage, q as ZcashHistoryRaw, r as ZcashHistorySource, s as ZcashTxInputDescription, t as ZcashTxOutputDescription, u as ZcashWarning, v as blockchairTime, w as createZcashHistory, x as decodeHistoryCursor, y as encodeHistoryCursor, z as mapDashboardTx, A as zcashExplorerTxUrl } from './history-DQNeEwXE.cjs';
1
+ import { S as SpentOutput, Z as ZcashNetwork, a as ZcashTxDescription, b as ZcashClient, U as Utxo, c as ZcashTxInfo, d as ZcashConfig, e as ZcashSpend, f as ZcashTransferOptions, g as ZcashMaxSpendableResult, h as ZcashReceipt, i as ZcashTransaction } from './history-CiZ6aFMT.cjs';
2
+ export { B as BLOCKCHAIR_URL, j as BlockchairClient, k as BlockchairInput, l as BlockchairOutput, m as BlockchairTxDashboard, n as BlockchairTxRow, C as CAIP2, H as HistoryUnavailableError, o as ZcashChainTx, p as ZcashClientConfig, q as ZcashHistoryCursor, r as ZcashHistoryOptions, s as ZcashHistoryPage, t as ZcashHistoryRaw, u as ZcashHistorySource, v as ZcashTxInputDescription, w as ZcashTxOutputDescription, x as ZcashWarning, y as blockchairTime, z as createZcashHistory, A as decodeHistoryCursor, D as encodeHistoryCursor, E as mapDashboardTx, F as zcashExplorerTxUrl } from './history-CiZ6aFMT.cjs';
3
3
  import { Signer, IWalletAccountReadOnly, IWalletAccount, WriteOptions, TransferResult, WalletManager, FeeRates } from '@anywalletkit/core';
4
4
 
5
5
  declare const TX_VERSION_V5 = 2147483653;
@@ -83,7 +83,9 @@ type UnsafeTransactionCode =
83
83
  /** The recipient output is missing or carries a different amount. */
84
84
  | 'recipient-mismatch'
85
85
  /** A change output pays an address that is not ours. */
86
- | 'change-not-ours';
86
+ | 'change-not-ours'
87
+ /** Off mainnet without a configured `consensusBranchId`: the branch id cannot be known. */
88
+ | 'unknown-branch-id';
87
89
  /** A transaction failed a hard safety check and was not signed / broadcast. */
88
90
  declare class UnsafeTransactionError extends Error {
89
91
  readonly code: UnsafeTransactionCode;
@@ -163,11 +165,31 @@ declare class ZcashBlockbookClient implements ZcashClient {
163
165
 
164
166
  /**
165
167
  * One client, or a failover client over the list: each call walks the list in
166
- * order, `retries` extra rounds. A node's rejection of a broadcast (plain
167
- * Error) is final — only transport failures (NetworkError) move on.
168
+ * order, `retries` extra rounds. A node's refusal of a broadcast is final when
169
+ * no endpoint before it went silent; after a silent one the transaction may be
170
+ * out, so the result is "maybe sent" (round 3, M12). Transport failures move on.
168
171
  */
169
172
  declare function createClient(config: ZcashConfig): ZcashClient | undefined;
170
173
 
174
+ /**
175
+ * The transaction was handed to the network but no definitive answer came back (no response, a
176
+ * timeout, a 5xx, an unreadable body): it MAY be in the mempool. Do not spend the same coins again —
177
+ * watch `hash`, the txid computed before sending (round 3, M12).
178
+ */
179
+ declare class BroadcastUnknownError extends Error {
180
+ /** Always true: the transaction may be on its way. */
181
+ readonly maybeSent = true;
182
+ /** The transaction id (hex). */
183
+ readonly hash: string;
184
+ constructor(hash: string, cause: unknown);
185
+ }
186
+
187
+ /**
188
+ * An endpoint answered with a refusal (HTTP 4xx other than 408 with a reason, or an error body):
189
+ * nothing was taken. A thrown fetch, a timeout, a 5xx or a body that cannot be read may have been.
190
+ */
191
+ declare function isAnsweredRefusal(e: unknown): boolean;
192
+
171
193
  /** Hash of a signed message (`Zcash Signed Message:` prefix, double SHA-256). */
172
194
  declare function magicHash(message: string | Uint8Array): Uint8Array;
173
195
  /** Encode `r‖s` + recovery id as a base64 signature (compressed-key header 31 + recovery). */
@@ -366,7 +388,13 @@ declare class WalletAccountZcash extends WalletAccountReadOnlyZcash implements I
366
388
  /** Zcash message signature (zcashd `signmessage` encoding), base64. */
367
389
  sign(message: string | Uint8Array): Promise<string>;
368
390
  private ctx;
369
- /** Lock time + expiry + branch id from the tip (or the config override). */
391
+ /**
392
+ * Lock time, expiry and branch id for a transaction that lands in the next block (round 3, L7):
393
+ * expiry = tip + 1 + `expiryDelta`, the branch id in force at tip + 1 (mainnet table; off mainnet
394
+ * `consensusBranchId` must be configured — testnet and regtest activate upgrades at other heights).
395
+ * Without a tip nothing is signed when the expiry, anti-fee-sniping or the branch id need it: a
396
+ * transaction signed with expiry 0 never expires.
397
+ */
370
398
  private chainParams;
371
399
  /** Sign every input through the signer; the scriptSig is `<DER ‖ 0x01> <pubkey>`. */
372
400
  private signInputs;
@@ -386,6 +414,12 @@ declare class WalletAccountZcash extends WalletAccountReadOnlyZcash implements I
386
414
  sendTransaction(tx: ZcashTransaction, options?: WriteOptions): Promise<TransferResult>;
387
415
  /** Native ZEC only (`token` is rejected). Subject to `transferMaxFee`. */
388
416
  transfer(o: ZcashTransferOptions, writeOptions?: WriteOptions): Promise<TransferResult>;
417
+ /**
418
+ * Send once through the client. Only a node's refusal means nothing went out; "already in the
419
+ * mempool / block chain" means an earlier attempt delivered it; anything else may have landed and
420
+ * is a {@link BroadcastUnknownError} with the txid computed before sending (round 3, M12).
421
+ */
422
+ private broadcast;
389
423
  /** The same address as a {@link WalletAccountReadOnlyZcash}. */
390
424
  toReadOnlyAccount(): IWalletAccountReadOnly;
391
425
  dispose(): void;
@@ -416,4 +450,4 @@ declare class WalletManagerZcash extends WalletManager<WalletAccountZcash, Zcash
416
450
  getFeeRates(): Promise<FeeRates>;
417
451
  }
418
452
 
419
- export { type BlockbookTxDetails, type BlockbookVin, type BlockbookVout, CURRENT_BRANCH_ID, type CoinSelectOptions, type CoinSelection, DEFAULT_EXPIRY_DELTA, DEFAULT_MAX_FEE, DUST_LIMIT, type DecodedAddress, type DescribeContext, FINAL_SEQUENCE, GRACE_ACTIONS, MAINNET_UPGRADES, MARGINAL_FEE, MAX_SEQUENCE, MAX_UTXO_INPUTS, P2PKH_INPUT_SIZE, SIGHASH_ALL, type ScriptKind, type SpendPlan, SpentOutput, TX_VERSION_V5, TooManyInputsError, type TransparentTxV5, type TxInput, type TxOutput, type UnsafeTransactionCode, UnsafeTransactionError, Utxo, VERSION_GROUP_ID_V5, WalletAccountReadOnlyZcash, WalletAccountZcash, WalletManagerZcash, ZcashBlockbookClient, ZcashClient, ZcashConfig, ZcashMaxSpendableResult, ZcashNetwork, ZcashReceipt, ZcashSpend, ZcashTransaction, ZcashTransferOptions, ZcashTxDescription, ZcashTxInfo, addressFor, addressFromPublicKey, addressFromScript, addressPrefix, assertSafe, branchIdForHeight, coinType, conventionalFee, createClient, decodeAddress, WalletManagerZcash as default, describeTransaction, encodeAddress, encodeMessageSignature, estimateSize, hash160, headerDigest, logicalActions, magicHash, outputSize, outputsDigest, p2pkhScriptSig, parseP2pkhScriptSig, parseTx, prevoutsDigest, recoverMessagePublicKey, scriptFor, scriptKind, selectAll, selectCoins, sequenceDigest, serializeTx, sighashAll, signMessageWith, spendableUtxos, transparentDigest, txHashPersonalization, txid, txidDigest };
453
+ export { type BlockbookTxDetails, type BlockbookVin, type BlockbookVout, BroadcastUnknownError, type Built, CURRENT_BRANCH_ID, type CoinSelectOptions, type CoinSelection, DEFAULT_EXPIRY_DELTA, DEFAULT_MAX_FEE, DUST_LIMIT, type DecodedAddress, type DescribeContext, FINAL_SEQUENCE, GRACE_ACTIONS, MAINNET_UPGRADES, MARGINAL_FEE, MAX_SEQUENCE, MAX_UTXO_INPUTS, P2PKH_INPUT_SIZE, SIGHASH_ALL, type ScriptKind, type SpendPlan, SpentOutput, TX_VERSION_V5, TooManyInputsError, type TransparentTxV5, type TxInput, type TxOutput, type UnsafeTransactionCode, UnsafeTransactionError, Utxo, VERSION_GROUP_ID_V5, WalletAccountReadOnlyZcash, WalletAccountZcash, WalletManagerZcash, ZcashBlockbookClient, ZcashClient, ZcashConfig, ZcashMaxSpendableResult, ZcashNetwork, ZcashReceipt, ZcashSpend, ZcashTransaction, ZcashTransferOptions, ZcashTxDescription, ZcashTxInfo, addressFor, addressFromPublicKey, addressFromScript, addressPrefix, assertSafe, branchIdForHeight, coinType, conventionalFee, createClient, decodeAddress, WalletManagerZcash as default, describeTransaction, encodeAddress, encodeMessageSignature, estimateSize, hash160, headerDigest, isAnsweredRefusal, logicalActions, magicHash, outputSize, outputsDigest, p2pkhScriptSig, parseP2pkhScriptSig, parseTx, prevoutsDigest, recoverMessagePublicKey, scriptFor, scriptKind, selectAll, selectCoins, sequenceDigest, serializeTx, sighashAll, signMessageWith, spendableUtxos, transparentDigest, txHashPersonalization, txid, txidDigest };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SpentOutput, Z as ZcashNetwork, a as ZcashTxDescription, b as ZcashClient, U as Utxo, c as ZcashTxInfo, d as ZcashConfig, e as ZcashSpend, f as ZcashTransferOptions, g as ZcashMaxSpendableResult, h as ZcashReceipt, i as ZcashTransaction } from './history-DQNeEwXE.js';
2
- export { B as BLOCKCHAIR_URL, j as BlockchairClient, k as BlockchairTxDashboard, C as CAIP2, H as HistoryUnavailableError, l as ZcashChainTx, m as ZcashClientConfig, n as ZcashHistoryCursor, o as ZcashHistoryOptions, p as ZcashHistoryPage, q as ZcashHistoryRaw, r as ZcashHistorySource, s as ZcashTxInputDescription, t as ZcashTxOutputDescription, u as ZcashWarning, v as blockchairTime, w as createZcashHistory, x as decodeHistoryCursor, y as encodeHistoryCursor, z as mapDashboardTx, A as zcashExplorerTxUrl } from './history-DQNeEwXE.js';
1
+ import { S as SpentOutput, Z as ZcashNetwork, a as ZcashTxDescription, b as ZcashClient, U as Utxo, c as ZcashTxInfo, d as ZcashConfig, e as ZcashSpend, f as ZcashTransferOptions, g as ZcashMaxSpendableResult, h as ZcashReceipt, i as ZcashTransaction } from './history-CiZ6aFMT.js';
2
+ export { B as BLOCKCHAIR_URL, j as BlockchairClient, k as BlockchairInput, l as BlockchairOutput, m as BlockchairTxDashboard, n as BlockchairTxRow, C as CAIP2, H as HistoryUnavailableError, o as ZcashChainTx, p as ZcashClientConfig, q as ZcashHistoryCursor, r as ZcashHistoryOptions, s as ZcashHistoryPage, t as ZcashHistoryRaw, u as ZcashHistorySource, v as ZcashTxInputDescription, w as ZcashTxOutputDescription, x as ZcashWarning, y as blockchairTime, z as createZcashHistory, A as decodeHistoryCursor, D as encodeHistoryCursor, E as mapDashboardTx, F as zcashExplorerTxUrl } from './history-CiZ6aFMT.js';
3
3
  import { Signer, IWalletAccountReadOnly, IWalletAccount, WriteOptions, TransferResult, WalletManager, FeeRates } from '@anywalletkit/core';
4
4
 
5
5
  declare const TX_VERSION_V5 = 2147483653;
@@ -83,7 +83,9 @@ type UnsafeTransactionCode =
83
83
  /** The recipient output is missing or carries a different amount. */
84
84
  | 'recipient-mismatch'
85
85
  /** A change output pays an address that is not ours. */
86
- | 'change-not-ours';
86
+ | 'change-not-ours'
87
+ /** Off mainnet without a configured `consensusBranchId`: the branch id cannot be known. */
88
+ | 'unknown-branch-id';
87
89
  /** A transaction failed a hard safety check and was not signed / broadcast. */
88
90
  declare class UnsafeTransactionError extends Error {
89
91
  readonly code: UnsafeTransactionCode;
@@ -163,11 +165,31 @@ declare class ZcashBlockbookClient implements ZcashClient {
163
165
 
164
166
  /**
165
167
  * One client, or a failover client over the list: each call walks the list in
166
- * order, `retries` extra rounds. A node's rejection of a broadcast (plain
167
- * Error) is final — only transport failures (NetworkError) move on.
168
+ * order, `retries` extra rounds. A node's refusal of a broadcast is final when
169
+ * no endpoint before it went silent; after a silent one the transaction may be
170
+ * out, so the result is "maybe sent" (round 3, M12). Transport failures move on.
168
171
  */
169
172
  declare function createClient(config: ZcashConfig): ZcashClient | undefined;
170
173
 
174
+ /**
175
+ * The transaction was handed to the network but no definitive answer came back (no response, a
176
+ * timeout, a 5xx, an unreadable body): it MAY be in the mempool. Do not spend the same coins again —
177
+ * watch `hash`, the txid computed before sending (round 3, M12).
178
+ */
179
+ declare class BroadcastUnknownError extends Error {
180
+ /** Always true: the transaction may be on its way. */
181
+ readonly maybeSent = true;
182
+ /** The transaction id (hex). */
183
+ readonly hash: string;
184
+ constructor(hash: string, cause: unknown);
185
+ }
186
+
187
+ /**
188
+ * An endpoint answered with a refusal (HTTP 4xx other than 408 with a reason, or an error body):
189
+ * nothing was taken. A thrown fetch, a timeout, a 5xx or a body that cannot be read may have been.
190
+ */
191
+ declare function isAnsweredRefusal(e: unknown): boolean;
192
+
171
193
  /** Hash of a signed message (`Zcash Signed Message:` prefix, double SHA-256). */
172
194
  declare function magicHash(message: string | Uint8Array): Uint8Array;
173
195
  /** Encode `r‖s` + recovery id as a base64 signature (compressed-key header 31 + recovery). */
@@ -366,7 +388,13 @@ declare class WalletAccountZcash extends WalletAccountReadOnlyZcash implements I
366
388
  /** Zcash message signature (zcashd `signmessage` encoding), base64. */
367
389
  sign(message: string | Uint8Array): Promise<string>;
368
390
  private ctx;
369
- /** Lock time + expiry + branch id from the tip (or the config override). */
391
+ /**
392
+ * Lock time, expiry and branch id for a transaction that lands in the next block (round 3, L7):
393
+ * expiry = tip + 1 + `expiryDelta`, the branch id in force at tip + 1 (mainnet table; off mainnet
394
+ * `consensusBranchId` must be configured — testnet and regtest activate upgrades at other heights).
395
+ * Without a tip nothing is signed when the expiry, anti-fee-sniping or the branch id need it: a
396
+ * transaction signed with expiry 0 never expires.
397
+ */
370
398
  private chainParams;
371
399
  /** Sign every input through the signer; the scriptSig is `<DER ‖ 0x01> <pubkey>`. */
372
400
  private signInputs;
@@ -386,6 +414,12 @@ declare class WalletAccountZcash extends WalletAccountReadOnlyZcash implements I
386
414
  sendTransaction(tx: ZcashTransaction, options?: WriteOptions): Promise<TransferResult>;
387
415
  /** Native ZEC only (`token` is rejected). Subject to `transferMaxFee`. */
388
416
  transfer(o: ZcashTransferOptions, writeOptions?: WriteOptions): Promise<TransferResult>;
417
+ /**
418
+ * Send once through the client. Only a node's refusal means nothing went out; "already in the
419
+ * mempool / block chain" means an earlier attempt delivered it; anything else may have landed and
420
+ * is a {@link BroadcastUnknownError} with the txid computed before sending (round 3, M12).
421
+ */
422
+ private broadcast;
389
423
  /** The same address as a {@link WalletAccountReadOnlyZcash}. */
390
424
  toReadOnlyAccount(): IWalletAccountReadOnly;
391
425
  dispose(): void;
@@ -416,4 +450,4 @@ declare class WalletManagerZcash extends WalletManager<WalletAccountZcash, Zcash
416
450
  getFeeRates(): Promise<FeeRates>;
417
451
  }
418
452
 
419
- export { type BlockbookTxDetails, type BlockbookVin, type BlockbookVout, CURRENT_BRANCH_ID, type CoinSelectOptions, type CoinSelection, DEFAULT_EXPIRY_DELTA, DEFAULT_MAX_FEE, DUST_LIMIT, type DecodedAddress, type DescribeContext, FINAL_SEQUENCE, GRACE_ACTIONS, MAINNET_UPGRADES, MARGINAL_FEE, MAX_SEQUENCE, MAX_UTXO_INPUTS, P2PKH_INPUT_SIZE, SIGHASH_ALL, type ScriptKind, type SpendPlan, SpentOutput, TX_VERSION_V5, TooManyInputsError, type TransparentTxV5, type TxInput, type TxOutput, type UnsafeTransactionCode, UnsafeTransactionError, Utxo, VERSION_GROUP_ID_V5, WalletAccountReadOnlyZcash, WalletAccountZcash, WalletManagerZcash, ZcashBlockbookClient, ZcashClient, ZcashConfig, ZcashMaxSpendableResult, ZcashNetwork, ZcashReceipt, ZcashSpend, ZcashTransaction, ZcashTransferOptions, ZcashTxDescription, ZcashTxInfo, addressFor, addressFromPublicKey, addressFromScript, addressPrefix, assertSafe, branchIdForHeight, coinType, conventionalFee, createClient, decodeAddress, WalletManagerZcash as default, describeTransaction, encodeAddress, encodeMessageSignature, estimateSize, hash160, headerDigest, logicalActions, magicHash, outputSize, outputsDigest, p2pkhScriptSig, parseP2pkhScriptSig, parseTx, prevoutsDigest, recoverMessagePublicKey, scriptFor, scriptKind, selectAll, selectCoins, sequenceDigest, serializeTx, sighashAll, signMessageWith, spendableUtxos, transparentDigest, txHashPersonalization, txid, txidDigest };
453
+ export { type BlockbookTxDetails, type BlockbookVin, type BlockbookVout, BroadcastUnknownError, type Built, CURRENT_BRANCH_ID, type CoinSelectOptions, type CoinSelection, DEFAULT_EXPIRY_DELTA, DEFAULT_MAX_FEE, DUST_LIMIT, type DecodedAddress, type DescribeContext, FINAL_SEQUENCE, GRACE_ACTIONS, MAINNET_UPGRADES, MARGINAL_FEE, MAX_SEQUENCE, MAX_UTXO_INPUTS, P2PKH_INPUT_SIZE, SIGHASH_ALL, type ScriptKind, type SpendPlan, SpentOutput, TX_VERSION_V5, TooManyInputsError, type TransparentTxV5, type TxInput, type TxOutput, type UnsafeTransactionCode, UnsafeTransactionError, Utxo, VERSION_GROUP_ID_V5, WalletAccountReadOnlyZcash, WalletAccountZcash, WalletManagerZcash, ZcashBlockbookClient, ZcashClient, ZcashConfig, ZcashMaxSpendableResult, ZcashNetwork, ZcashReceipt, ZcashSpend, ZcashTransaction, ZcashTransferOptions, ZcashTxDescription, ZcashTxInfo, addressFor, addressFromPublicKey, addressFromScript, addressPrefix, assertSafe, branchIdForHeight, coinType, conventionalFee, createClient, decodeAddress, WalletManagerZcash as default, describeTransaction, encodeAddress, encodeMessageSignature, estimateSize, hash160, headerDigest, isAnsweredRefusal, logicalActions, magicHash, outputSize, outputsDigest, p2pkhScriptSig, parseP2pkhScriptSig, parseTx, prevoutsDigest, recoverMessagePublicKey, scriptFor, scriptKind, selectAll, selectCoins, sequenceDigest, serializeTx, sighashAll, signMessageWith, spendableUtxos, transparentDigest, txHashPersonalization, txid, txidDigest };
package/dist/index.js CHANGED
@@ -14,15 +14,19 @@ import {
14
14
  encodeCursor,
15
15
  fetchWithTimeout,
16
16
  hexToBytes,
17
+ isAlreadyKnown,
18
+ isAnsweredRefusal,
17
19
  isInt,
18
20
  isIntString,
19
21
  isRecord,
20
22
  mapDashboardTx,
23
+ refusal,
24
+ refusedStatus,
21
25
  reverseBytes,
22
26
  u32le,
23
27
  utf8ToBytes,
24
28
  zcashExplorerTxUrl
25
- } from "./chunk-6C7LNZNX.js";
29
+ } from "./chunk-JIJF3MGJ.js";
26
30
 
27
31
  // src/address.ts
28
32
  import { sha256 } from "@noble/hashes/sha2.js";
@@ -340,9 +344,26 @@ function describeTransaction(tx, spent, ctx) {
340
344
  const inputs = tx.inputs.map((i, k) => ({ txid: i.txid, vout: i.vout, value: spent[k].value, address: addressFromScript(spent[k].script, ctx.network) }));
341
345
  const outputs = [];
342
346
  const warnings = /* @__PURE__ */ new Set();
347
+ const scriptOf = (address) => {
348
+ try {
349
+ return bytesToHex(decodeAddress(address, ctx.network).script);
350
+ } catch {
351
+ return void 0;
352
+ }
353
+ };
354
+ const ownScript = scriptOf(ctx.ownAddress);
355
+ const intendedScript = ctx.intended ? scriptOf(ctx.intended.to) : void 0;
356
+ let recipientSeen = false;
343
357
  for (const o of tx.outputs) {
344
358
  const address = addressFromScript(o.script, ctx.network);
345
- const role = address !== void 0 && address === ctx.ownAddress ? "change" : ctx.intended === void 0 ? "recipient" : address !== void 0 && address === ctx.intended.to && o.value === ctx.intended.value ? "recipient" : "unknown";
359
+ const script = bytesToHex(o.script);
360
+ const mine = script === ownScript;
361
+ let role;
362
+ if (ctx.intended === void 0) role = mine ? "change" : "recipient";
363
+ else if (!recipientSeen && script === intendedScript && o.value === ctx.intended.value) {
364
+ role = "recipient";
365
+ recipientSeen = true;
366
+ } else role = mine ? "change" : "unknown";
346
367
  if (scriptKind(o.script) !== "unknown" && o.value < DUST_LIMIT) warnings.add("dust-output");
347
368
  outputs.push({ address, value: o.value, role });
348
369
  }
@@ -408,7 +429,11 @@ var ZcashBlockbookClient = class _ZcashBlockbookClient {
408
429
  async get(path, check) {
409
430
  const url = `${this.base}/api${path}`;
410
431
  const res = await fetchWithTimeout(this.fetchFn, url, { headers: this.headers }, this.timeoutMs, this.base);
411
- if (!res.ok) throw new NetworkError(`Blockbook request failed: ${res.status} ${res.statusText} (${url})`, { provider: this.base });
432
+ if (!res.ok) {
433
+ const reason = refusedStatus(res.status) ? (await res.text().catch(() => "")).slice(0, 200) : "";
434
+ const err = new NetworkError(`Blockbook request failed: ${res.status} ${res.statusText} (${url})${reason ? `: ${reason}` : ""}`, { provider: this.base });
435
+ throw refusedStatus(res.status) ? refusal(err) : err;
436
+ }
412
437
  let body;
413
438
  try {
414
439
  body = await res.json();
@@ -458,7 +483,7 @@ var ZcashBlockbookClient = class _ZcashBlockbookClient {
458
483
  }
459
484
  async broadcast(rawTx) {
460
485
  const data = await this.get(`/v2/sendtx/${rawTx}`, (v) => isRecord(v));
461
- if (data.error) throw new Error(`Broadcast rejected: ${typeof data.error === "string" ? data.error : data.error.message ?? "broadcast failed"}`);
486
+ if (data.error) throw refusal(new Error(`Broadcast rejected: ${typeof data.error === "string" ? data.error : data.error.message ?? "broadcast failed"}`));
462
487
  if (!data.result) throw new NetworkError("Blockbook returned no txid", { provider: this.base });
463
488
  return data.result;
464
489
  }
@@ -494,12 +519,19 @@ function createClient(config) {
494
519
  const attempts = list.length + (config.retries ?? 3);
495
520
  const call = (method) => async (...args) => {
496
521
  let last;
522
+ let silent = false;
497
523
  for (let i = 0; i < attempts; i++) {
498
524
  const c = list[i % list.length];
499
525
  try {
500
526
  return await c[method].apply(c, args);
501
527
  } catch (e) {
502
- if (method === "broadcast" && !(e instanceof NetworkError2)) throw e;
528
+ if (method === "broadcast") {
529
+ if (isAnsweredRefusal(e)) {
530
+ if (!silent) throw e;
531
+ throw new NetworkError2("A Zcash client refused the broadcast after another never answered: it may be out", { cause: e });
532
+ }
533
+ silent = true;
534
+ }
503
535
  last = e;
504
536
  }
505
537
  }
@@ -510,6 +542,19 @@ function createClient(config) {
510
542
  return out;
511
543
  }
512
544
 
545
+ // src/errors.ts
546
+ var BroadcastUnknownError = class extends Error {
547
+ /** Always true: the transaction may be on its way. */
548
+ maybeSent = true;
549
+ /** The transaction id (hex). */
550
+ hash;
551
+ constructor(hash, cause) {
552
+ super(`Broadcast outcome unknown for ${hash}: ${cause instanceof Error ? cause.message.slice(0, 200) : String(cause).slice(0, 200)}`, { cause });
553
+ this.name = "BroadcastUnknownError";
554
+ this.hash = hash;
555
+ }
556
+ };
557
+
513
558
  // src/message.ts
514
559
  import { sha256 as sha2562 } from "@noble/hashes/sha2.js";
515
560
  import { secp256k1 } from "@noble/curves/secp256k1.js";
@@ -563,7 +608,7 @@ function recoverMessagePublicKey(message, signature) {
563
608
  import { LocalSigner as LocalSigner2, SignerError as SignerError2, WalletManager, isSeedSigner } from "@anywalletkit/core";
564
609
 
565
610
  // src/account.ts
566
- import { InsufficientFundsError as InsufficientFundsError3, LocalSigner, SignerError, feeLimit, requireCurve } from "@anywalletkit/core";
611
+ import { InsufficientFundsError as InsufficientFundsError3, LocalSigner, NetworkError as NetworkError4, SignerError, feeLimit, requireCurve } from "@anywalletkit/core";
567
612
  import { secp256k1 as secp256k12 } from "@noble/curves/secp256k1.js";
568
613
 
569
614
  // src/read-only-account.ts
@@ -719,13 +764,27 @@ var WalletAccountZcash = class _WalletAccountZcash extends WalletAccountReadOnly
719
764
  ctx(intended) {
720
765
  return { network: this._network, ownAddress: this.address, intended, maxFee: this.maxFee };
721
766
  }
722
- /** Lock time + expiry + branch id from the tip (or the config override). */
767
+ /**
768
+ * Lock time, expiry and branch id for a transaction that lands in the next block (round 3, L7):
769
+ * expiry = tip + 1 + `expiryDelta`, the branch id in force at tip + 1 (mainnet table; off mainnet
770
+ * `consensusBranchId` must be configured — testnet and regtest activate upgrades at other heights).
771
+ * Without a tip nothing is signed when the expiry, anti-fee-sniping or the branch id need it: a
772
+ * transaction signed with expiry 0 never expires.
773
+ */
723
774
  async chainParams() {
724
- const tip = await this.tipHeight();
725
775
  const delta = this._config.expiryDelta ?? DEFAULT_EXPIRY_DELTA;
726
- const consensusBranchId = this._config.consensusBranchId ?? branchIdForHeight(tip);
727
- const lockTime = tip !== void 0 && this._config.antiFeeSniping !== false ? tip : 0;
728
- const expiryHeight = tip !== void 0 && delta > 0 ? tip + delta : 0;
776
+ const configured = this._config.consensusBranchId;
777
+ if (configured === void 0 && this._network !== "mainnet") {
778
+ throw new UnsafeTransactionError("unknown-branch-id", `On ${this._network} the consensus branch id depends on that network's upgrade heights; set consensusBranchId in the config.`);
779
+ }
780
+ const antiSnipe = this._config.antiFeeSniping !== false;
781
+ const needTip = delta > 0 || antiSnipe || configured === void 0;
782
+ const tip = needTip ? await this.tipHeight() : void 0;
783
+ if (needTip && tip === void 0) throw new NetworkError4("The Zcash chain tip is unknown, so the expiry height and the branch id cannot be set; nothing was signed.");
784
+ const next = tip === void 0 ? void 0 : tip + 1;
785
+ const consensusBranchId = configured ?? branchIdForHeight(next);
786
+ const lockTime = tip !== void 0 && antiSnipe ? tip : 0;
787
+ const expiryHeight = next !== void 0 && delta > 0 ? next + delta : 0;
729
788
  return { lockTime, expiryHeight, consensusBranchId };
730
789
  }
731
790
  /** Sign every input through the signer; the scriptSig is `<DER ‖ 0x01> <pubkey>`. */
@@ -815,8 +874,7 @@ var WalletAccountZcash = class _WalletAccountZcash extends WalletAccountReadOnly
815
874
  fee = built.fee;
816
875
  }
817
876
  this.checkMax(fee, feeLimit(this._config.transactionMaxFee, options), "transaction");
818
- const id = await client.broadcast(rawHex);
819
- return { hash: id || hash, fee };
877
+ return { hash: await this.broadcast(client, rawHex, hash), fee };
820
878
  }
821
879
  /** Native ZEC only (`token` is rejected). Subject to `transferMaxFee`. */
822
880
  async transfer(o, writeOptions = {}) {
@@ -825,8 +883,21 @@ var WalletAccountZcash = class _WalletAccountZcash extends WalletAccountReadOnly
825
883
  const client = this.requireClient("transfer");
826
884
  const built = await this.signSpend({ to: o.recipient, value: o.amount });
827
885
  this.checkMax(built.fee, feeLimit(this._config.transferMaxFee, writeOptions), "transfer");
828
- const id = await client.broadcast(built.hex);
829
- return { hash: id || built.txid, fee: built.fee };
886
+ return { hash: await this.broadcast(client, built.hex, built.txid), fee: built.fee };
887
+ }
888
+ /**
889
+ * Send once through the client. Only a node's refusal means nothing went out; "already in the
890
+ * mempool / block chain" means an earlier attempt delivered it; anything else may have landed and
891
+ * is a {@link BroadcastUnknownError} with the txid computed before sending (round 3, M12).
892
+ */
893
+ async broadcast(client, rawHex, txid2) {
894
+ try {
895
+ return await client.broadcast(rawHex) || txid2;
896
+ } catch (e) {
897
+ if (isAlreadyKnown(e)) return txid2;
898
+ if (isAnsweredRefusal(e)) throw e;
899
+ throw new BroadcastUnknownError(txid2, e);
900
+ }
830
901
  }
831
902
  /** The same address as a {@link WalletAccountReadOnlyZcash}. */
832
903
  toReadOnlyAccount() {
@@ -890,6 +961,7 @@ var WalletManagerZcash = class extends WalletManager {
890
961
  export {
891
962
  BLOCKCHAIR_URL,
892
963
  BlockchairClient,
964
+ BroadcastUnknownError,
893
965
  CAIP2,
894
966
  CURRENT_BRANCH_ID,
895
967
  DEFAULT_EXPIRY_DELTA,
@@ -933,6 +1005,7 @@ export {
933
1005
  estimateSize,
934
1006
  hash160,
935
1007
  headerDigest,
1008
+ isAnsweredRefusal,
936
1009
  logicalActions,
937
1010
  magicHash,
938
1011
  mapDashboardTx,