@subly_fi/pay 0.7.3 → 0.8.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.
@@ -1790,6 +1790,18 @@ async function assertWithdrawalPreview(input) {
1790
1790
  }
1791
1791
  }
1792
1792
 
1793
+ // ../../src/lib/canonical-json.ts
1794
+ import { createHash as createHash3 } from "node:crypto";
1795
+ function canonicalJson2(value) {
1796
+ return stableStringify(value);
1797
+ }
1798
+ function sha256HexOf(data) {
1799
+ return createHash3("sha256").update(data, "utf8").digest("hex");
1800
+ }
1801
+ function canonicalJsonHash(value) {
1802
+ return sha256HexOf(canonicalJson2(value));
1803
+ }
1804
+
1793
1805
  // ../../src/client/lookup-tables.ts
1794
1806
  import { fetchAllMaybeAddressLookupTable } from "@solana-program/address-lookup-table";
1795
1807
  import { address, getCompiledTransactionMessageDecoder as getCompiledTransactionMessageDecoder2 } from "@solana/kit";
@@ -1831,14 +1843,14 @@ async function fetchLookupTablesForTransaction(rpc, serializedTransaction) {
1831
1843
  }
1832
1844
 
1833
1845
  // ../../src/api/wallet-auth.ts
1834
- import { createHash as createHash3 } from "node:crypto";
1846
+ import { createHash as createHash4 } from "node:crypto";
1835
1847
  import bs587 from "bs58";
1836
1848
  import nacl3 from "tweetnacl";
1837
1849
  var WALLET_AUTH_WALLET_HEADER = "x-subly-wallet";
1838
1850
  var WALLET_AUTH_SIGNED_AT_HEADER = "x-subly-signed-at";
1839
1851
  var WALLET_AUTH_SIGNATURE_HEADER = "x-subly-signature";
1840
1852
  function sha256Hex(data) {
1841
- return createHash3("sha256").update(data, "utf8").digest("hex");
1853
+ return createHash4("sha256").update(data, "utf8").digest("hex");
1842
1854
  }
1843
1855
  function walletAuthMessage(params) {
1844
1856
  return new TextEncoder().encode(
@@ -1882,6 +1894,11 @@ var VaultFlowClientError = class extends Error {
1882
1894
  code;
1883
1895
  errorDetails;
1884
1896
  };
1897
+ function vaultOperationKind(intentId) {
1898
+ if (/^dep_[0-9a-f]{32}$/.test(intentId)) return "deposit";
1899
+ if (/^wdr_[0-9a-f]{32}$/.test(intentId)) return "withdrawal";
1900
+ throw new VaultFlowClientError("read", "intentId must be the original dep_ or wdr_ ID followed by 32 lowercase hexadecimal characters");
1901
+ }
1885
1902
  var VaultFlowClient = class {
1886
1903
  vault;
1887
1904
  rpc;
@@ -1984,9 +2001,36 @@ var VaultFlowClient = class {
1984
2001
  ...input.approvalId === void 0 ? {} : { approvalId: input.approvalId }
1985
2002
  }
1986
2003
  );
2004
+ this.assertPreparedWithdrawal(prepared, input);
2005
+ await input.onPrepared?.(prepared);
2006
+ return this.submitPreparedWithdrawal(prepared, input);
2007
+ }
2008
+ /** Reconcile or submit the original intent; never prepare a replacement. */
2009
+ async resumeWithdrawal(prepared, input) {
2010
+ this.assertPreparedWithdrawal(prepared, input);
2011
+ const current = await this.getJson(
2012
+ `/v1/withdrawals/${encodeURIComponent(prepared.withdrawalId)}`
2013
+ );
2014
+ if (current.withdrawalId !== prepared.withdrawalId || current.wallet !== this.signer.walletAddress || current.vault !== this.vault.address || current.requestedWithdrawRawUsdc !== input.amountRawUsdc.toString() || current.purpose !== (input.purpose ?? "normal") || canonicalJsonHash(current.paymentBinding ?? null) !== canonicalJsonHash(input.payment ?? null) || current.serializedTransaction !== prepared.serializedTransaction || current.preparedMessageHash !== prepared.signingIntent.preparedMessageHash || current.destinationUsdcAta !== prepared.destinationUsdcAta) {
2015
+ throw new VaultFlowClientError("read", "Saved withdrawal differs from the original operation; refusing to resume");
2016
+ }
2017
+ if (current.status === "prepared") {
2018
+ return this.submitPreparedWithdrawal(prepared, input);
2019
+ }
2020
+ if (!["submitted", "confirmed", "failed", "failed_not_submitted", "expired", "quarantined"].includes(current.status)) {
2021
+ throw new VaultFlowClientError("read", "Relayer returned an unknown withdrawal status");
2022
+ }
2023
+ return this.withdrawalOutcome(prepared, current);
2024
+ }
2025
+ assertPreparedWithdrawal(prepared, input) {
1987
2026
  if (prepared.signingIntent?.wallet !== this.signer.walletAddress || prepared.signingIntent.vault !== this.vault.address || prepared.requestedWithdrawRawUsdc !== input.amountRawUsdc.toString() || prepared.purpose !== (input.purpose ?? "normal") || input.purpose === "yield_realize" && prepared.signingIntent.allowFullExit) {
1988
2027
  throw new VaultFlowClientError("prepare", "Prepared withdrawal differs from the requested operation");
1989
2028
  }
2029
+ if (typeof prepared.withdrawalId !== "string" || prepared.withdrawalId.length === 0) {
2030
+ throw new VaultFlowClientError("prepare", "Prepared withdrawal has no withdrawal ID");
2031
+ }
2032
+ }
2033
+ async submitPreparedWithdrawal(prepared, input) {
1990
2034
  await assertWithdrawalPreview({
1991
2035
  rpc: this.rpc,
1992
2036
  serializedTransaction: prepared.serializedTransaction,
@@ -2000,6 +2044,7 @@ var VaultFlowClient = class {
2000
2044
  serializedTransaction: prepared.serializedTransaction,
2001
2045
  lookupTables: await this.lookupTablesFor(prepared.serializedTransaction)
2002
2046
  });
2047
+ input.onBeforeSubmit?.();
2003
2048
  let outcome = await this.postJson("submit", "/v1/withdrawals/submit", {
2004
2049
  withdrawalId: prepared.withdrawalId,
2005
2050
  serializedTransaction: signed.serializedTransaction,
@@ -2011,6 +2056,9 @@ var VaultFlowClient = class {
2011
2056
  outcome
2012
2057
  );
2013
2058
  }
2059
+ return this.withdrawalOutcome(prepared, outcome);
2060
+ }
2061
+ withdrawalOutcome(prepared, outcome) {
2014
2062
  return {
2015
2063
  withdrawalId: prepared.withdrawalId,
2016
2064
  status: outcome.status,
@@ -2021,6 +2069,40 @@ var VaultFlowClient = class {
2021
2069
  errorCode: outcome.errorCode ?? null
2022
2070
  };
2023
2071
  }
2072
+ /** Authenticated read/reconciliation only: never prepare, sign or submit a transaction. */
2073
+ async getOperationStatus(intentId) {
2074
+ const kind = vaultOperationKind(intentId);
2075
+ const raw = await this.getJson(`/v1/${kind === "deposit" ? "deposits" : "withdrawals"}/${intentId}?resubmit=false`);
2076
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
2077
+ throw new VaultFlowClientError("read", "Relayer returned an invalid operation status");
2078
+ }
2079
+ const record = raw;
2080
+ if (record[kind === "deposit" ? "depositId" : "withdrawalId"] !== intentId || record.wallet !== this.signer.walletAddress || record.vault !== this.vault.address) {
2081
+ throw new VaultFlowClientError("read", "Operation does not match the requested ID, current wallet or selected vault; use the original wallet, vault and relayer");
2082
+ }
2083
+ const requested = record[kind === "deposit" ? "amountRawUsdc" : "requestedWithdrawRawUsdc"];
2084
+ const actual = record[kind === "deposit" ? "actualDepositRawUsdc" : "actualWithdrawRawUsdc"];
2085
+ if (typeof record.status !== "string" || !["prepared", "submitted", "confirmed", "failed", "expired", "failed_not_submitted"].includes(record.status) || typeof requested !== "string" || !/^\d+$/.test(requested) || actual !== null && (typeof actual !== "string" || !/^\d+$/.test(actual)) || record.txSignature !== null && (typeof record.txSignature !== "string" || record.txSignature.length === 0) || record.errorCode !== null && typeof record.errorCode !== "string" || record.status === "confirmed" && (actual === null || record.txSignature === null)) {
2086
+ throw new VaultFlowClientError("read", "Relayer returned incomplete or invalid operation status fields");
2087
+ }
2088
+ const status = record.status;
2089
+ const nextAction = status === "confirmed" ? "done" : status === "submitted" || status === "prepared" ? "check_again" : "reconcile_with_operator";
2090
+ const message = status === "confirmed" ? `The original ${kind} is confirmed.` : status === "submitted" ? "The original transaction is still confirming. Check this same intent ID again; do not repeat the deposit or withdrawal." : status === "prepared" ? "The original intent is prepared. This status check does not submit it. Check the same ID again or ask the operator to reconcile it before starting another operation." : "The original operation ended without a confirmed result. Reconcile its intent ID and transaction with the operator before starting another operation.";
2091
+ return {
2092
+ intentId,
2093
+ kind,
2094
+ wallet: this.signer.walletAddress,
2095
+ vault: this.vault.address,
2096
+ status,
2097
+ requestedAmountRawUsdc: requested,
2098
+ actualAmountRawUsdc: actual,
2099
+ txSignature: record.txSignature,
2100
+ errorCode: record.errorCode,
2101
+ stillConfirming: status === "submitted",
2102
+ nextAction,
2103
+ message
2104
+ };
2105
+ }
2024
2106
  /**
2025
2107
  * Reads the yield budget. Syncs the relayer's ledger from chain first (so
2026
2108
  * yield accrued since the last sync shows up); the sync is best-effort and