@gvnrdao/dh-sdk 0.0.339 → 0.0.341

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.
@@ -984,11 +984,12 @@ export declare class DiamondHandsSDK {
984
984
  getPSMAvailableReserves(stablecoinAddress: string): Promise<bigint>;
985
985
  /**
986
986
  * Execute a PSM stablecoin → UCD swap.
987
- * Handles stablecoin approval to the PSM contract if the current allowance is insufficient.
987
+ * Approves EXACTLY `amountWei` to the PSM when the current allowance is short (resetting a
988
+ * partial allowance to zero first); every leg is validated by the sign-guard before it is sent.
988
989
  *
989
990
  * @param params.stablecoinAddress - ERC-20 address of the stablecoin to swap in
990
991
  * @param params.amountWei - Stablecoin amount in native decimals (bigint)
991
- * @param params.minUcdOutWei - Minimum UCD to receive; reverts if below this (1% slippage guard)
992
+ * @param params.minUcdOutWei - Minimum UCD to receive; must be > 0 (reverts on-chain if below)
992
993
  * @param params.signer - Connected signer for the approval and swap transactions
993
994
  */
994
995
  psmSwap(params: {
@@ -1002,12 +1003,13 @@ export declare class DiamondHandsSDK {
1002
1003
  }>;
1003
1004
  /**
1004
1005
  * Execute a PSM UCD → stablecoin redeem.
1005
- * Handles UCD approval to UCDController (not PSM) to satisfy the M-4 burn allowance guard:
1006
- * UCDToken.burn(from, amount) calls _spendAllowance(from, msg.sender=ucdController, amount).
1006
+ * Approves EXACTLY `ucdAmountWei` of UCD to the UCDController (not the PSM) to satisfy the
1007
+ * M-4 burn allowance guard: UCDToken.burn(from, amount) calls
1008
+ * _spendAllowance(from, msg.sender=ucdController, amount).
1007
1009
  *
1008
1010
  * @param params.stablecoinAddress - ERC-20 address of the stablecoin to receive
1009
1011
  * @param params.ucdAmountWei - UCD amount to redeem (18 decimals, bigint)
1010
- * @param params.minStablecoinOutWei - Minimum stablecoin to receive (slippage guard)
1012
+ * @param params.minStablecoinOutWei - Minimum stablecoin to receive; must be > 0
1011
1013
  * @param params.signer - Connected signer
1012
1014
  */
1013
1015
  psmRedeem(params: {
@@ -1019,6 +1021,11 @@ export declare class DiamondHandsSDK {
1019
1021
  hash: string;
1020
1022
  blockNumber: number;
1021
1023
  }>;
1024
+ /**
1025
+ * Shared PSM path: plan + validate every leg against the chain the SIGNER is on, then send.
1026
+ * The signer's chain must match the SDK's configured chain — the addresses came from it.
1027
+ */
1028
+ private runPsmExchange;
1022
1029
  /**
1023
1030
  * Wait for the subgraph to index up to (and including) the given block number.
1024
1031
  * Call after on-chain actions (createLoan, mintUCD, etc.) before querying the subgraph.
@@ -32,6 +32,8 @@ __export(sign_guard_exports, {
32
32
  OP_ENVELOPE_FIELD_ORDER: () => OP_ENVELOPE_FIELD_ORDER,
33
33
  OP_ENVELOPE_LAYOUT: () => OP_ENVELOPE_LAYOUT,
34
34
  OP_ENVELOPE_TYPES: () => OP_ENVELOPE_TYPES,
35
+ PRIORITY_FEE_CAP_WEI: () => PRIORITY_FEE_CAP_WEI,
36
+ PRIORITY_FEE_FLOOR_WEI: () => PRIORITY_FEE_FLOOR_WEI,
35
37
  QUANTUM_SECONDS: () => QUANTUM_SECONDS,
36
38
  REQUEST_TIMEOUT_MS: () => REQUEST_TIMEOUT_MS,
37
39
  SIGNABLE_FUNCTIONS: () => SIGNABLE_FUNCTIONS,
@@ -42,17 +44,23 @@ __export(sign_guard_exports, {
42
44
  authorizeWithServer: () => authorizeWithServer,
43
45
  broadcastSignedTx: () => broadcastSignedTx,
44
46
  buildAuthEnvelope: () => buildAuthEnvelope,
47
+ buildPsmApproveTx: () => buildPsmApproveTx,
48
+ buildPsmRedeemTx: () => buildPsmRedeemTx,
49
+ buildPsmSwapTx: () => buildPsmSwapTx,
45
50
  buildValidationContext: () => buildValidationContext,
46
51
  computeExtendFeeUpperBound: () => computeExtendFeeUpperBound,
47
52
  decodeSignable: () => decodeSignable,
48
53
  describeUnsignedTx: () => describeUnsignedTx,
49
54
  encodeSignable: () => encodeSignable,
55
+ executePsmPlan: () => executePsmPlan,
50
56
  fetchWithTimeout: () => fetchWithTimeout,
51
57
  isAuthFresh: () => isAuthFresh,
52
58
  isStrictCurrentQuantum: () => isStrictCurrentQuantum,
53
59
  modeForChainId: () => modeForChainId,
54
60
  nextQuantumTimestamp: () => nextQuantumTimestamp,
61
+ planPsmExchange: () => planPsmExchange,
55
62
  postJson: () => postJson,
63
+ psmExchangeReadsFromProvider: () => psmExchangeReadsFromProvider,
56
64
  randomNonceHex: () => randomNonceHex,
57
65
  readTxFeeFields: () => readTxFeeFields,
58
66
  signAndBroadcast: () => signAndBroadcast,
@@ -66,8 +74,12 @@ module.exports = __toCommonJS(sign_guard_exports);
66
74
 
67
75
  // src/utils/sign-guard/errors.ts
68
76
  var TxValidationError = class extends Error {
69
- constructor(msg) {
70
- super(`Refusing to sign server-returned transaction: ${msg}`);
77
+ /**
78
+ * @param subject What is being refused. Defaults to the server-returned transaction the
79
+ * validator exists for; client-built flows (the PSM exchange) name themselves instead.
80
+ */
81
+ constructor(msg, subject = "server-returned transaction") {
82
+ super(`Refusing to sign ${subject}: ${msg}`);
71
83
  this.name = "TxValidationError";
72
84
  }
73
85
  };
@@ -150,6 +162,8 @@ var import_ethers2 = require("ethers");
150
162
  var MAX_GAS_LIMIT = 5000000n;
151
163
  var MAX_FEE_PER_GAS_WEI = 2000000000000n;
152
164
  var DEFAULT_MAX_TX_FEE_WEI = 250000000000000000n;
165
+ var PRIORITY_FEE_FLOOR_WEI = 100000000n;
166
+ var PRIORITY_FEE_CAP_WEI = 3000000000n;
153
167
  var DEFAULT_FEE_CEILING = {
154
168
  maxGasLimit: MAX_GAS_LIMIT,
155
169
  maxFeePerGasWei: MAX_FEE_PER_GAS_WEI,
@@ -962,6 +976,129 @@ async function computeExtendFeeUpperBound(provider, contracts, positionId, selec
962
976
  const upperBoundFeeWei = ucdDebt * extensionFeeRateBps / 10000n;
963
977
  return { ucdDebt, extensionFeeRateBps, upperBoundFeeWei };
964
978
  }
979
+
980
+ // src/utils/sign-guard/psm-exchange.ts
981
+ var import_ethers6 = require("ethers");
982
+ function buildPsmApproveTx(p) {
983
+ return {
984
+ to: p.token,
985
+ data: encodeSignable("approve", [p.spender, p.amount]),
986
+ value: "0x0",
987
+ chainId: p.chainId
988
+ };
989
+ }
990
+ function buildPsmSwapTx(p) {
991
+ return {
992
+ to: p.psm,
993
+ data: encodeSignable("swap", [p.stablecoin, p.amountIn, p.minOut]),
994
+ value: "0x0",
995
+ chainId: p.chainId
996
+ };
997
+ }
998
+ function buildPsmRedeemTx(p) {
999
+ return {
1000
+ to: p.psm,
1001
+ data: encodeSignable("redeem", [p.stablecoin, p.ucdAmount, p.minOut]),
1002
+ value: "0x0",
1003
+ chainId: p.chainId
1004
+ };
1005
+ }
1006
+ var PSM_READS_ABI = ["function supportedStablecoins(address) view returns (bool)"];
1007
+ var ERC20_READS_ABI = ["function allowance(address owner, address spender) view returns (uint256)"];
1008
+ function psmExchangeReadsFromProvider(provider, psmAddress) {
1009
+ const psm = new import_ethers6.ethers.Contract(psmAddress, PSM_READS_ABI, provider);
1010
+ return {
1011
+ isStablecoinSupported: async (stablecoin) => await psm.getFunction("supportedStablecoins")(stablecoin),
1012
+ allowance: async (token, owner, spender) => await new import_ethers6.ethers.Contract(token, ERC20_READS_ABI, provider).getFunction("allowance")(owner, spender)
1013
+ };
1014
+ }
1015
+ var refuse = (direction, msg) => new TxValidationError(msg, `PSM ${direction}`);
1016
+ function requireAddress(direction, label, value) {
1017
+ if (!value || !import_ethers6.ethers.isAddress(value)) {
1018
+ throw refuse(direction, `${label} address is missing or invalid (${String(value)})`);
1019
+ }
1020
+ return import_ethers6.ethers.getAddress(value);
1021
+ }
1022
+ async function planPsmExchange(params) {
1023
+ const { direction, amountIn, minOut, vctx, reads } = params;
1024
+ const chainId = vctx.chainId;
1025
+ if (amountIn <= 0n) {
1026
+ throw refuse(direction, `amount must be greater than zero (got ${amountIn})`);
1027
+ }
1028
+ if (minOut <= 0n) {
1029
+ throw refuse(direction, `minimum-out floor must be greater than zero (got ${minOut}) \u2014 a zero floor is refused on-chain`);
1030
+ }
1031
+ const owner = requireAddress(direction, "owner", params.owner);
1032
+ const psm = requireAddress(direction, "SimplePSMV2", params.addresses.psm);
1033
+ const stablecoin = requireAddress(direction, "stablecoin", params.addresses.stablecoin);
1034
+ const isSwap = direction === "swap";
1035
+ const token = isSwap ? stablecoin : requireAddress(direction, "UCDToken", params.addresses.ucdToken);
1036
+ const spender = isSwap ? psm : requireAddress(direction, "UCDController", params.addresses.ucdController);
1037
+ if (!await reads.isStablecoinSupported(stablecoin)) {
1038
+ throw refuse(direction, `stablecoin ${stablecoin} is not supported by the PSM at ${psm}`);
1039
+ }
1040
+ const approveExpected = (amount) => isSwap ? { kind: "stablecoin-approve", tokenAddress: token, spender, amountUnits: amount.toString() } : { kind: "ucd-approve-controller", ucdTokenAddress: token, spender, amountWei: amount.toString() };
1041
+ const allowanceBefore = await reads.allowance(token, owner, spender);
1042
+ const approvals = [];
1043
+ if (allowanceBefore < amountIn) {
1044
+ if (allowanceBefore > 0n) {
1045
+ approvals.push({
1046
+ step: "reset-approve",
1047
+ tx: buildPsmApproveTx({ token, spender, amount: 0n, chainId }),
1048
+ expected: approveExpected(0n)
1049
+ });
1050
+ }
1051
+ approvals.push({
1052
+ step: "approve",
1053
+ tx: buildPsmApproveTx({ token, spender, amount: amountIn, chainId }),
1054
+ expected: approveExpected(amountIn)
1055
+ });
1056
+ }
1057
+ const exec = isSwap ? {
1058
+ step: "swap",
1059
+ tx: buildPsmSwapTx({ psm, stablecoin, amountIn, minOut, chainId }),
1060
+ expected: { kind: "psm-swap", psmAddress: psm, stablecoin, amountIn: amountIn.toString(), minOut: minOut.toString() }
1061
+ } : {
1062
+ step: "redeem",
1063
+ tx: buildPsmRedeemTx({ psm, stablecoin, ucdAmount: amountIn, minOut, chainId }),
1064
+ expected: {
1065
+ kind: "psm-redeem",
1066
+ psmAddress: psm,
1067
+ stablecoin,
1068
+ amountUcdWei: amountIn.toString(),
1069
+ minOut: minOut.toString()
1070
+ }
1071
+ };
1072
+ for (const leg of [...approvals, exec]) {
1073
+ validateUnsignedTx({ ...leg.tx }, leg.expected, vctx);
1074
+ }
1075
+ return { direction, owner, token, spender, amountIn, minOut, allowanceBefore, approvals, exec };
1076
+ }
1077
+ async function executePsmPlan(plan, signer, vctx) {
1078
+ const approvalHashes = [];
1079
+ const residual = () => approvalHashes.length === 0 ? "" : ` An approval already landed (tx ${approvalHashes[approvalHashes.length - 1]}): an allowance of exactly ${plan.amountIn} to ${plan.spender} stands. It authorizes only that amount; the next exchange uses or replaces it.`;
1080
+ const sendLeg = async (leg) => {
1081
+ const request = { to: leg.tx.to, data: leg.tx.data, value: 0n, chainId: leg.tx.chainId };
1082
+ validateUnsignedTx({ ...request }, leg.expected, vctx);
1083
+ const sent = await signer.sendTransaction(request);
1084
+ const receipt = await sent.wait();
1085
+ if (!receipt)
1086
+ throw new Error(`PSM ${leg.step} tx ${sent.hash} returned no receipt`);
1087
+ if (receipt.status !== 1)
1088
+ throw new Error(`PSM ${leg.step} tx ${sent.hash} reverted`);
1089
+ return { hash: sent.hash, blockNumber: receipt.blockNumber };
1090
+ };
1091
+ for (const leg of plan.approvals) {
1092
+ const landed = await sendLeg(leg).catch((e) => {
1093
+ throw new Error(`PSM ${leg.step} failed: ${e instanceof Error ? e.message : String(e)}.${residual()}`);
1094
+ });
1095
+ approvalHashes.push(landed.hash);
1096
+ }
1097
+ const exec = await sendLeg(plan.exec).catch((e) => {
1098
+ throw new Error(`PSM ${plan.exec.step} failed: ${e instanceof Error ? e.message : String(e)}.${residual()}`);
1099
+ });
1100
+ return { ...exec, approvalHashes };
1101
+ }
965
1102
  // Annotate the CommonJS export names for ESM import in node:
966
1103
  0 && (module.exports = {
967
1104
  AUTH_VALIDITY_WINDOWS,
@@ -976,6 +1113,8 @@ async function computeExtendFeeUpperBound(provider, contracts, positionId, selec
976
1113
  OP_ENVELOPE_FIELD_ORDER,
977
1114
  OP_ENVELOPE_LAYOUT,
978
1115
  OP_ENVELOPE_TYPES,
1116
+ PRIORITY_FEE_CAP_WEI,
1117
+ PRIORITY_FEE_FLOOR_WEI,
979
1118
  QUANTUM_SECONDS,
980
1119
  REQUEST_TIMEOUT_MS,
981
1120
  SIGNABLE_FUNCTIONS,
@@ -986,17 +1125,23 @@ async function computeExtendFeeUpperBound(provider, contracts, positionId, selec
986
1125
  authorizeWithServer,
987
1126
  broadcastSignedTx,
988
1127
  buildAuthEnvelope,
1128
+ buildPsmApproveTx,
1129
+ buildPsmRedeemTx,
1130
+ buildPsmSwapTx,
989
1131
  buildValidationContext,
990
1132
  computeExtendFeeUpperBound,
991
1133
  decodeSignable,
992
1134
  describeUnsignedTx,
993
1135
  encodeSignable,
1136
+ executePsmPlan,
994
1137
  fetchWithTimeout,
995
1138
  isAuthFresh,
996
1139
  isStrictCurrentQuantum,
997
1140
  modeForChainId,
998
1141
  nextQuantumTimestamp,
1142
+ planPsmExchange,
999
1143
  postJson,
1144
+ psmExchangeReadsFromProvider,
1000
1145
  randomNonceHex,
1001
1146
  readTxFeeFields,
1002
1147
  signAndBroadcast,
@@ -1,7 +1,11 @@
1
1
  // src/utils/sign-guard/errors.ts
2
2
  var TxValidationError = class extends Error {
3
- constructor(msg) {
4
- super(`Refusing to sign server-returned transaction: ${msg}`);
3
+ /**
4
+ * @param subject What is being refused. Defaults to the server-returned transaction the
5
+ * validator exists for; client-built flows (the PSM exchange) name themselves instead.
6
+ */
7
+ constructor(msg, subject = "server-returned transaction") {
8
+ super(`Refusing to sign ${subject}: ${msg}`);
5
9
  this.name = "TxValidationError";
6
10
  }
7
11
  };
@@ -84,6 +88,8 @@ import { ethers as ethers2 } from "ethers";
84
88
  var MAX_GAS_LIMIT = 5000000n;
85
89
  var MAX_FEE_PER_GAS_WEI = 2000000000000n;
86
90
  var DEFAULT_MAX_TX_FEE_WEI = 250000000000000000n;
91
+ var PRIORITY_FEE_FLOOR_WEI = 100000000n;
92
+ var PRIORITY_FEE_CAP_WEI = 3000000000n;
87
93
  var DEFAULT_FEE_CEILING = {
88
94
  maxGasLimit: MAX_GAS_LIMIT,
89
95
  maxFeePerGasWei: MAX_FEE_PER_GAS_WEI,
@@ -896,6 +902,129 @@ async function computeExtendFeeUpperBound(provider, contracts, positionId, selec
896
902
  const upperBoundFeeWei = ucdDebt * extensionFeeRateBps / 10000n;
897
903
  return { ucdDebt, extensionFeeRateBps, upperBoundFeeWei };
898
904
  }
905
+
906
+ // src/utils/sign-guard/psm-exchange.ts
907
+ import { ethers as ethers6 } from "ethers";
908
+ function buildPsmApproveTx(p) {
909
+ return {
910
+ to: p.token,
911
+ data: encodeSignable("approve", [p.spender, p.amount]),
912
+ value: "0x0",
913
+ chainId: p.chainId
914
+ };
915
+ }
916
+ function buildPsmSwapTx(p) {
917
+ return {
918
+ to: p.psm,
919
+ data: encodeSignable("swap", [p.stablecoin, p.amountIn, p.minOut]),
920
+ value: "0x0",
921
+ chainId: p.chainId
922
+ };
923
+ }
924
+ function buildPsmRedeemTx(p) {
925
+ return {
926
+ to: p.psm,
927
+ data: encodeSignable("redeem", [p.stablecoin, p.ucdAmount, p.minOut]),
928
+ value: "0x0",
929
+ chainId: p.chainId
930
+ };
931
+ }
932
+ var PSM_READS_ABI = ["function supportedStablecoins(address) view returns (bool)"];
933
+ var ERC20_READS_ABI = ["function allowance(address owner, address spender) view returns (uint256)"];
934
+ function psmExchangeReadsFromProvider(provider, psmAddress) {
935
+ const psm = new ethers6.Contract(psmAddress, PSM_READS_ABI, provider);
936
+ return {
937
+ isStablecoinSupported: async (stablecoin) => await psm.getFunction("supportedStablecoins")(stablecoin),
938
+ allowance: async (token, owner, spender) => await new ethers6.Contract(token, ERC20_READS_ABI, provider).getFunction("allowance")(owner, spender)
939
+ };
940
+ }
941
+ var refuse = (direction, msg) => new TxValidationError(msg, `PSM ${direction}`);
942
+ function requireAddress(direction, label, value) {
943
+ if (!value || !ethers6.isAddress(value)) {
944
+ throw refuse(direction, `${label} address is missing or invalid (${String(value)})`);
945
+ }
946
+ return ethers6.getAddress(value);
947
+ }
948
+ async function planPsmExchange(params) {
949
+ const { direction, amountIn, minOut, vctx, reads } = params;
950
+ const chainId = vctx.chainId;
951
+ if (amountIn <= 0n) {
952
+ throw refuse(direction, `amount must be greater than zero (got ${amountIn})`);
953
+ }
954
+ if (minOut <= 0n) {
955
+ throw refuse(direction, `minimum-out floor must be greater than zero (got ${minOut}) \u2014 a zero floor is refused on-chain`);
956
+ }
957
+ const owner = requireAddress(direction, "owner", params.owner);
958
+ const psm = requireAddress(direction, "SimplePSMV2", params.addresses.psm);
959
+ const stablecoin = requireAddress(direction, "stablecoin", params.addresses.stablecoin);
960
+ const isSwap = direction === "swap";
961
+ const token = isSwap ? stablecoin : requireAddress(direction, "UCDToken", params.addresses.ucdToken);
962
+ const spender = isSwap ? psm : requireAddress(direction, "UCDController", params.addresses.ucdController);
963
+ if (!await reads.isStablecoinSupported(stablecoin)) {
964
+ throw refuse(direction, `stablecoin ${stablecoin} is not supported by the PSM at ${psm}`);
965
+ }
966
+ const approveExpected = (amount) => isSwap ? { kind: "stablecoin-approve", tokenAddress: token, spender, amountUnits: amount.toString() } : { kind: "ucd-approve-controller", ucdTokenAddress: token, spender, amountWei: amount.toString() };
967
+ const allowanceBefore = await reads.allowance(token, owner, spender);
968
+ const approvals = [];
969
+ if (allowanceBefore < amountIn) {
970
+ if (allowanceBefore > 0n) {
971
+ approvals.push({
972
+ step: "reset-approve",
973
+ tx: buildPsmApproveTx({ token, spender, amount: 0n, chainId }),
974
+ expected: approveExpected(0n)
975
+ });
976
+ }
977
+ approvals.push({
978
+ step: "approve",
979
+ tx: buildPsmApproveTx({ token, spender, amount: amountIn, chainId }),
980
+ expected: approveExpected(amountIn)
981
+ });
982
+ }
983
+ const exec = isSwap ? {
984
+ step: "swap",
985
+ tx: buildPsmSwapTx({ psm, stablecoin, amountIn, minOut, chainId }),
986
+ expected: { kind: "psm-swap", psmAddress: psm, stablecoin, amountIn: amountIn.toString(), minOut: minOut.toString() }
987
+ } : {
988
+ step: "redeem",
989
+ tx: buildPsmRedeemTx({ psm, stablecoin, ucdAmount: amountIn, minOut, chainId }),
990
+ expected: {
991
+ kind: "psm-redeem",
992
+ psmAddress: psm,
993
+ stablecoin,
994
+ amountUcdWei: amountIn.toString(),
995
+ minOut: minOut.toString()
996
+ }
997
+ };
998
+ for (const leg of [...approvals, exec]) {
999
+ validateUnsignedTx({ ...leg.tx }, leg.expected, vctx);
1000
+ }
1001
+ return { direction, owner, token, spender, amountIn, minOut, allowanceBefore, approvals, exec };
1002
+ }
1003
+ async function executePsmPlan(plan, signer, vctx) {
1004
+ const approvalHashes = [];
1005
+ const residual = () => approvalHashes.length === 0 ? "" : ` An approval already landed (tx ${approvalHashes[approvalHashes.length - 1]}): an allowance of exactly ${plan.amountIn} to ${plan.spender} stands. It authorizes only that amount; the next exchange uses or replaces it.`;
1006
+ const sendLeg = async (leg) => {
1007
+ const request = { to: leg.tx.to, data: leg.tx.data, value: 0n, chainId: leg.tx.chainId };
1008
+ validateUnsignedTx({ ...request }, leg.expected, vctx);
1009
+ const sent = await signer.sendTransaction(request);
1010
+ const receipt = await sent.wait();
1011
+ if (!receipt)
1012
+ throw new Error(`PSM ${leg.step} tx ${sent.hash} returned no receipt`);
1013
+ if (receipt.status !== 1)
1014
+ throw new Error(`PSM ${leg.step} tx ${sent.hash} reverted`);
1015
+ return { hash: sent.hash, blockNumber: receipt.blockNumber };
1016
+ };
1017
+ for (const leg of plan.approvals) {
1018
+ const landed = await sendLeg(leg).catch((e) => {
1019
+ throw new Error(`PSM ${leg.step} failed: ${e instanceof Error ? e.message : String(e)}.${residual()}`);
1020
+ });
1021
+ approvalHashes.push(landed.hash);
1022
+ }
1023
+ const exec = await sendLeg(plan.exec).catch((e) => {
1024
+ throw new Error(`PSM ${plan.exec.step} failed: ${e instanceof Error ? e.message : String(e)}.${residual()}`);
1025
+ });
1026
+ return { ...exec, approvalHashes };
1027
+ }
899
1028
  export {
900
1029
  AUTH_VALIDITY_WINDOWS,
901
1030
  BROADCAST_PATH,
@@ -909,6 +1038,8 @@ export {
909
1038
  OP_ENVELOPE_FIELD_ORDER,
910
1039
  OP_ENVELOPE_LAYOUT,
911
1040
  OP_ENVELOPE_TYPES,
1041
+ PRIORITY_FEE_CAP_WEI,
1042
+ PRIORITY_FEE_FLOOR_WEI,
912
1043
  QUANTUM_SECONDS,
913
1044
  REQUEST_TIMEOUT_MS,
914
1045
  SIGNABLE_FUNCTIONS,
@@ -919,17 +1050,23 @@ export {
919
1050
  authorizeWithServer,
920
1051
  broadcastSignedTx,
921
1052
  buildAuthEnvelope,
1053
+ buildPsmApproveTx,
1054
+ buildPsmRedeemTx,
1055
+ buildPsmSwapTx,
922
1056
  buildValidationContext,
923
1057
  computeExtendFeeUpperBound,
924
1058
  decodeSignable,
925
1059
  describeUnsignedTx,
926
1060
  encodeSignable,
1061
+ executePsmPlan,
927
1062
  fetchWithTimeout,
928
1063
  isAuthFresh,
929
1064
  isStrictCurrentQuantum,
930
1065
  modeForChainId,
931
1066
  nextQuantumTimestamp,
1067
+ planPsmExchange,
932
1068
  postJson,
1069
+ psmExchangeReadsFromProvider,
933
1070
  randomNonceHex,
934
1071
  readTxFeeFields,
935
1072
  signAndBroadcast,
@@ -22,14 +22,22 @@ export declare function safeGetBitcoinAddressesFromPkp(pkpId: string, maxRetries
22
22
  testnet: string;
23
23
  regtest: string;
24
24
  }>;
25
+ type BitcoinAddressNetwork = 'mainnet' | 'testnet' | 'regtest';
25
26
  /**
26
- * Safely validate and format a Bitcoin address
27
- * @param address Bitcoin address to validate
28
- * @param network Network type for validation
29
- * @returns Validated address
30
- * @throws Clear error message if validation fails
27
+ * Validate a Bitcoin address for the network the caller is operating on.
28
+ *
29
+ * Runs on the BTC withdrawal path before any Lit Action, authorization or signature, so
30
+ * it verifies the CHECKSUM (base58check / bech32 / bech32m, via `BitcoinUtils`) and then
31
+ * the network. BTC sent to a mistyped or wrong-network address is unrecoverable; the
32
+ * previous implementation checked the prefix only, so a one-character typo passed.
33
+ *
34
+ * `network` is required. It used to default to 'regtest', which would have validated a
35
+ * mainnet caller's address against the wrong chain without saying so.
36
+ *
37
+ * @returns the address unchanged
38
+ * @throws if the address is malformed, fails its checksum, or is not on `network`
31
39
  */
32
- export declare function safeValidateBitcoinAddress(address: string, network?: 'mainnet' | 'testnet' | 'regtest'): string;
40
+ export declare function safeValidateBitcoinAddress(address: string, network: BitcoinAddressNetwork): string;
33
41
  /**
34
42
  * Canonicalize a Bitcoin address so that the SAME string is used on both the
35
43
  * approve path (registry.addAddress) and the withdraw path. The on-chain
@@ -49,3 +57,4 @@ export declare function normalizeBitcoinAddress(address: string): string;
49
57
  * @throws Clear error message if validation fails
50
58
  */
51
59
  export declare function safeValidatePositionId(positionId: string): string;
60
+ export {};
@@ -18,3 +18,42 @@ export declare function mintAgentPkp(params: {
18
18
  authHeader?: () => Promise<Record<string, string>> | Record<string, string>;
19
19
  timeoutMs?: number;
20
20
  }): Promise<string>;
21
+ /** `AgentStatus` in `IAgentDelegateRegistryBase.sol`. */
22
+ export declare const AGENT_STATUS: {
23
+ readonly None: 0;
24
+ readonly Active: 1;
25
+ readonly Revoked: 2;
26
+ };
27
+ /** How the enable-path binds the borrower's agent. `reuse` needs no mint and no tx. */
28
+ export type AgentBindingPlan = {
29
+ kind: "reuse";
30
+ agent: string;
31
+ } | {
32
+ kind: "register";
33
+ } | {
34
+ kind: "rotate";
35
+ };
36
+ /**
37
+ * Decide which registry call binds a borrower's agent — BEFORE a PKP is minted.
38
+ *
39
+ * The registry does not change `status` on expiry: an expired agent is still
40
+ * `status == Active`, only `validUntil` has passed. So `isAgentActive()` is false, yet
41
+ * `registerAgent` reverts `AgentIsActiveUseRotate()` — rotation is the renewal path, and it
42
+ * needs a NEW agent address (AR-1 burns an address on first bind). Treating "not active" as
43
+ * "register" minted a billed PKP and then reverted, on every attempt, once an agent expired.
44
+ *
45
+ * `isAgentActive` also folds in the registry's pause. An UNEXPIRED `Active` record that reads
46
+ * inactive means paused: both `registerAgent` and `rotateAgent` are `whenNotPaused`, so this
47
+ * throws rather than let the caller mint a PKP for a transaction that cannot land.
48
+ *
49
+ * @param nowSeconds chain time (latest block timestamp), not the local clock
50
+ */
51
+ export declare function planAgentBinding(params: {
52
+ isActive: boolean;
53
+ record: {
54
+ agent: string;
55
+ validUntil: bigint | number;
56
+ status: bigint | number;
57
+ };
58
+ nowSeconds: number;
59
+ }): AgentBindingPlan;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * An explicit `chainId` is a claim until the provider confirms it.
3
+ *
4
+ * The SDK resolves its contract addresses, validators and subgraph from `chainId`. If the
5
+ * provider (the wallet) is actually on another chain, every address it is about to call or
6
+ * sign for belongs to the wrong network. So the claim is checked once, at initialisation.
7
+ *
8
+ * Returns the mismatch as a message (for the caller's `Result`), or `null` when the provider
9
+ * agrees. A provider that cannot report its network is NOT waved through: its error
10
+ * propagates unchanged, so the caller sees the real cause and no SDK exists to sign with.
11
+ */
12
+ export declare function describeProviderChainMismatch(provider: {
13
+ getNetwork(): Promise<{
14
+ chainId: bigint | number;
15
+ }>;
16
+ }, chainId: number): Promise<string | null>;
@@ -110,8 +110,8 @@ export declare const LIQUIDATION_REVEAL_GAS_CEILING: bigint;
110
110
  * fee — far more than the tip), the cap stops a spiking feeHistory from
111
111
  * overpaying. At ~751k gas the floor costs ≈ $0.18 (ETH $2,458).
112
112
  */
113
- export declare const PRIORITY_FEE_FLOOR_WEI: bigint;
114
- export declare const PRIORITY_FEE_CAP_WEI: bigint;
113
+ import { PRIORITY_FEE_FLOOR_WEI, PRIORITY_FEE_CAP_WEI } from "../sign-guard/fee-ceiling";
114
+ export { PRIORITY_FEE_FLOOR_WEI, PRIORITY_FEE_CAP_WEI };
115
115
  /** Blocks of eth_feeHistory sampled; the median across them absorbs a single spike block. */
116
116
  export declare const FEE_HISTORY_BLOCK_COUNT = 20;
117
117
  /**
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Concurrency Limiter Utility
3
+ * Limits the number of concurrent operations to prevent overwhelming external services
4
+ */
5
+ /**
6
+ * Concurrency limiter configuration
7
+ */
8
+ export interface ConcurrencyLimiterConfig {
9
+ concurrency?: number;
10
+ timeout?: number;
11
+ debug?: boolean;
12
+ }
13
+ /**
14
+ * Concurrency limiter for controlling parallel operations
15
+ */
16
+ export declare class ConcurrencyLimiter {
17
+ private concurrency;
18
+ private timeout;
19
+ private debug;
20
+ private running;
21
+ private queue;
22
+ constructor(config?: ConcurrencyLimiterConfig);
23
+ /**
24
+ * Execute a function with concurrency limiting
25
+ */
26
+ execute<T>(fn: () => Promise<T>): Promise<T>;
27
+ /**
28
+ * Process the queue of pending operations
29
+ */
30
+ private processQueue;
31
+ /**
32
+ * Run function with timeout
33
+ */
34
+ private runWithTimeout;
35
+ /**
36
+ * Get current status
37
+ */
38
+ getStatus(): {
39
+ running: number;
40
+ queued: number;
41
+ concurrency: number;
42
+ };
43
+ /**
44
+ * Wait for all operations to complete
45
+ */
46
+ waitForAll(): Promise<void>;
47
+ /**
48
+ * Clear queue and reject pending operations
49
+ */
50
+ clear(): void;
51
+ }
52
+ /**
53
+ * Create a concurrency limiter with default settings
54
+ */
55
+ export declare function createConcurrencyLimiter(config?: ConcurrencyLimiterConfig): ConcurrencyLimiter;
56
+ /**
57
+ * Execute multiple functions with concurrency limiting
58
+ */
59
+ export declare function executeWithConcurrencyLimit<T>(functions: Array<() => Promise<T>>, config?: ConcurrencyLimiterConfig): Promise<T[]>;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Contract error decoder — a revert's bytes turned into the error the CONTRACT declares.
3
+ *
4
+ * Every name and selector here comes from the committed typechain ABIs, never from a
5
+ * hand-written table. Two hand-written tables used to live in this file and they had
6
+ * drifted: `0x137f3b70 -> InDeadZone` is a selector no contract can ever emit (the real
7
+ * error is `DeadZoneViolation()`, `0xbe4b82c1`; `isInDeadZone` is a view function), so a
8
+ * borrower hitting the dead zone would have been told about an error that does not exist.
9
+ * An ABI-derived table cannot drift: a renamed or deleted error disappears with it.
10
+ *
11
+ * Selector collisions across contracts are possible in principle (4 bytes). The table
12
+ * records EVERY contract a selector belongs to and `contract` names them all, rather than
13
+ * silently picking one.
14
+ */
15
+ import { type ErrorFragment } from "ethers";
16
+ /** Decoded error information. */
17
+ export interface DecodedError {
18
+ name: string;
19
+ selector: string;
20
+ args?: any[];
21
+ /** The contract(s) declaring this error, or `"Unknown"`. */
22
+ contract: string;
23
+ message: string;
24
+ }
25
+ export interface ContractErrorEntry {
26
+ /** `Name(type,type)` exactly as declared. */
27
+ signature: string;
28
+ fragment: ErrorFragment;
29
+ /** Every contract in the set declaring this selector. */
30
+ contracts: string[];
31
+ }
32
+ /** Selector -> declared error, built once from the generated fragments. */
33
+ export declare function contractErrorTable(): Map<string, ContractErrorEntry>;
34
+ /** The declared error for a selector, or null when no contract in the set declares it. */
35
+ export declare function lookupContractError(selector: string): ContractErrorEntry | null;
36
+ /**
37
+ * Decode a contract revert.
38
+ *
39
+ * Returns the declared error with its ARGUMENTS wherever the payload carries them — the
40
+ * figures are the point (`SlippageExceeded(got, minimum)`, `InsufficientReserves(available,
41
+ * required)`). Selector-only payloads still resolve to the right name with empty args.
42
+ * Standard `Error(string)` and `Panic(uint256)` reverts decode too. Returns null only when
43
+ * there is nothing to decode at all.
44
+ */
45
+ export declare function decodeContractError(error: any): DecodedError | null;
46
+ /** Format a decoded error for logging and user-facing messages. */
47
+ export declare function formatDecodedError(decoded: DecodedError): string;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Chain names the Lit Actions understand, by EVM chainId.
3
+ *
4
+ * The name selects the action's policy — address pinning, allowed RPC hosts, whether a dev
5
+ * Bitcoin provider is honoured — so it is looked up, never guessed. Sepolia uses regtest Bitcoin
6
+ * (the private dh-btc-faucet), the same convention as lit-ops-server.
7
+ */
8
+ export declare const LIT_ACTION_CHAIN_NAMES: Readonly<Record<number, string>>;
9
+ /**
10
+ * The Lit Action chain name for `chainId`. Throws on an unsupported chain rather than falling
11
+ * back: defaulting to "sepolia" ran Hardhat under Sepolia policy, and signed for any unknown
12
+ * chain under Sepolia's name.
13
+ */
14
+ export declare function litActionChainNameForChainId(chainId: number | bigint): string;