@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.
- package/browser/dist/833.browser.js +2 -0
- package/browser/dist/833.browser.js.LICENSE.txt +1 -0
- package/browser/dist/browser.js +1 -1
- package/browser/dist/browser.js.LICENSE.txt +0 -4
- package/dist/constants/chunks/contract-errors.generated.d.ts +15 -0
- package/dist/contract-errors.js +498 -0
- package/dist/contract-errors.mjs +461 -0
- package/dist/index.js +7110 -372
- package/dist/index.mjs +7350 -613
- package/dist/modules/diamond-hands-sdk.d.ts +12 -5
- package/dist/sign-guard.js +147 -2
- package/dist/sign-guard.mjs +139 -2
- package/dist/utils/address-conversion.utils.d.ts +15 -6
- package/dist/utils/agent-delegation.utils.d.ts +39 -0
- package/dist/utils/assert-provider-chain.d.ts +16 -0
- package/dist/utils/chunks/eip1559-broadcast.utils.d.ts +2 -2
- package/dist/utils/concurrency-limiter.utils.d.ts +59 -0
- package/dist/utils/contract-error-decoder.d.ts +47 -0
- package/dist/utils/lit-action-chain-name.d.ts +14 -0
- package/dist/utils/loan-helpers.utils.d.ts +115 -0
- package/dist/utils/quantum-revert.utils.d.ts +14 -2
- package/dist/utils/sign-guard/errors.d.ts +5 -1
- package/dist/utils/sign-guard/fee-ceiling.d.ts +16 -0
- package/dist/utils/sign-guard/index.d.ts +2 -1
- package/dist/utils/sign-guard/psm-exchange.d.ts +122 -0
- package/package.json +20 -6
|
@@ -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
|
-
*
|
|
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;
|
|
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
|
-
*
|
|
1006
|
-
* UCDToken.burn(from, amount) calls
|
|
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
|
|
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.
|
package/dist/sign-guard.js
CHANGED
|
@@ -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
|
-
|
|
70
|
-
|
|
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,
|
package/dist/sign-guard.mjs
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
// src/utils/sign-guard/errors.ts
|
|
2
2
|
var TxValidationError = class extends Error {
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
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
|
|
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
|
-
|
|
114
|
-
export
|
|
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;
|