@gvnrdao/dh-sdk 0.0.339 → 0.0.340
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/browser.js +1 -1
- package/dist/index.js +925 -150
- package/dist/index.mjs +859 -84
- package/dist/modules/diamond-hands-sdk.d.ts +12 -5
- package/dist/sign-guard.js +141 -2
- package/dist/sign-guard.mjs +135 -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/concurrency-limiter.utils.d.ts +59 -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/sign-guard/errors.d.ts +5 -1
- package/dist/utils/sign-guard/index.d.ts +1 -0
- package/dist/utils/sign-guard/psm-exchange.d.ts +122 -0
- package/package.json +2 -3
|
@@ -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
|
@@ -42,17 +42,23 @@ __export(sign_guard_exports, {
|
|
|
42
42
|
authorizeWithServer: () => authorizeWithServer,
|
|
43
43
|
broadcastSignedTx: () => broadcastSignedTx,
|
|
44
44
|
buildAuthEnvelope: () => buildAuthEnvelope,
|
|
45
|
+
buildPsmApproveTx: () => buildPsmApproveTx,
|
|
46
|
+
buildPsmRedeemTx: () => buildPsmRedeemTx,
|
|
47
|
+
buildPsmSwapTx: () => buildPsmSwapTx,
|
|
45
48
|
buildValidationContext: () => buildValidationContext,
|
|
46
49
|
computeExtendFeeUpperBound: () => computeExtendFeeUpperBound,
|
|
47
50
|
decodeSignable: () => decodeSignable,
|
|
48
51
|
describeUnsignedTx: () => describeUnsignedTx,
|
|
49
52
|
encodeSignable: () => encodeSignable,
|
|
53
|
+
executePsmPlan: () => executePsmPlan,
|
|
50
54
|
fetchWithTimeout: () => fetchWithTimeout,
|
|
51
55
|
isAuthFresh: () => isAuthFresh,
|
|
52
56
|
isStrictCurrentQuantum: () => isStrictCurrentQuantum,
|
|
53
57
|
modeForChainId: () => modeForChainId,
|
|
54
58
|
nextQuantumTimestamp: () => nextQuantumTimestamp,
|
|
59
|
+
planPsmExchange: () => planPsmExchange,
|
|
55
60
|
postJson: () => postJson,
|
|
61
|
+
psmExchangeReadsFromProvider: () => psmExchangeReadsFromProvider,
|
|
56
62
|
randomNonceHex: () => randomNonceHex,
|
|
57
63
|
readTxFeeFields: () => readTxFeeFields,
|
|
58
64
|
signAndBroadcast: () => signAndBroadcast,
|
|
@@ -66,8 +72,12 @@ module.exports = __toCommonJS(sign_guard_exports);
|
|
|
66
72
|
|
|
67
73
|
// src/utils/sign-guard/errors.ts
|
|
68
74
|
var TxValidationError = class extends Error {
|
|
69
|
-
|
|
70
|
-
|
|
75
|
+
/**
|
|
76
|
+
* @param subject What is being refused. Defaults to the server-returned transaction the
|
|
77
|
+
* validator exists for; client-built flows (the PSM exchange) name themselves instead.
|
|
78
|
+
*/
|
|
79
|
+
constructor(msg, subject = "server-returned transaction") {
|
|
80
|
+
super(`Refusing to sign ${subject}: ${msg}`);
|
|
71
81
|
this.name = "TxValidationError";
|
|
72
82
|
}
|
|
73
83
|
};
|
|
@@ -962,6 +972,129 @@ async function computeExtendFeeUpperBound(provider, contracts, positionId, selec
|
|
|
962
972
|
const upperBoundFeeWei = ucdDebt * extensionFeeRateBps / 10000n;
|
|
963
973
|
return { ucdDebt, extensionFeeRateBps, upperBoundFeeWei };
|
|
964
974
|
}
|
|
975
|
+
|
|
976
|
+
// src/utils/sign-guard/psm-exchange.ts
|
|
977
|
+
var import_ethers6 = require("ethers");
|
|
978
|
+
function buildPsmApproveTx(p) {
|
|
979
|
+
return {
|
|
980
|
+
to: p.token,
|
|
981
|
+
data: encodeSignable("approve", [p.spender, p.amount]),
|
|
982
|
+
value: "0x0",
|
|
983
|
+
chainId: p.chainId
|
|
984
|
+
};
|
|
985
|
+
}
|
|
986
|
+
function buildPsmSwapTx(p) {
|
|
987
|
+
return {
|
|
988
|
+
to: p.psm,
|
|
989
|
+
data: encodeSignable("swap", [p.stablecoin, p.amountIn, p.minOut]),
|
|
990
|
+
value: "0x0",
|
|
991
|
+
chainId: p.chainId
|
|
992
|
+
};
|
|
993
|
+
}
|
|
994
|
+
function buildPsmRedeemTx(p) {
|
|
995
|
+
return {
|
|
996
|
+
to: p.psm,
|
|
997
|
+
data: encodeSignable("redeem", [p.stablecoin, p.ucdAmount, p.minOut]),
|
|
998
|
+
value: "0x0",
|
|
999
|
+
chainId: p.chainId
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
var PSM_READS_ABI = ["function supportedStablecoins(address) view returns (bool)"];
|
|
1003
|
+
var ERC20_READS_ABI = ["function allowance(address owner, address spender) view returns (uint256)"];
|
|
1004
|
+
function psmExchangeReadsFromProvider(provider, psmAddress) {
|
|
1005
|
+
const psm = new import_ethers6.ethers.Contract(psmAddress, PSM_READS_ABI, provider);
|
|
1006
|
+
return {
|
|
1007
|
+
isStablecoinSupported: async (stablecoin) => await psm.getFunction("supportedStablecoins")(stablecoin),
|
|
1008
|
+
allowance: async (token, owner, spender) => await new import_ethers6.ethers.Contract(token, ERC20_READS_ABI, provider).getFunction("allowance")(owner, spender)
|
|
1009
|
+
};
|
|
1010
|
+
}
|
|
1011
|
+
var refuse = (direction, msg) => new TxValidationError(msg, `PSM ${direction}`);
|
|
1012
|
+
function requireAddress(direction, label, value) {
|
|
1013
|
+
if (!value || !import_ethers6.ethers.isAddress(value)) {
|
|
1014
|
+
throw refuse(direction, `${label} address is missing or invalid (${String(value)})`);
|
|
1015
|
+
}
|
|
1016
|
+
return import_ethers6.ethers.getAddress(value);
|
|
1017
|
+
}
|
|
1018
|
+
async function planPsmExchange(params) {
|
|
1019
|
+
const { direction, amountIn, minOut, vctx, reads } = params;
|
|
1020
|
+
const chainId = vctx.chainId;
|
|
1021
|
+
if (amountIn <= 0n) {
|
|
1022
|
+
throw refuse(direction, `amount must be greater than zero (got ${amountIn})`);
|
|
1023
|
+
}
|
|
1024
|
+
if (minOut <= 0n) {
|
|
1025
|
+
throw refuse(direction, `minimum-out floor must be greater than zero (got ${minOut}) \u2014 a zero floor is refused on-chain`);
|
|
1026
|
+
}
|
|
1027
|
+
const owner = requireAddress(direction, "owner", params.owner);
|
|
1028
|
+
const psm = requireAddress(direction, "SimplePSMV2", params.addresses.psm);
|
|
1029
|
+
const stablecoin = requireAddress(direction, "stablecoin", params.addresses.stablecoin);
|
|
1030
|
+
const isSwap = direction === "swap";
|
|
1031
|
+
const token = isSwap ? stablecoin : requireAddress(direction, "UCDToken", params.addresses.ucdToken);
|
|
1032
|
+
const spender = isSwap ? psm : requireAddress(direction, "UCDController", params.addresses.ucdController);
|
|
1033
|
+
if (!await reads.isStablecoinSupported(stablecoin)) {
|
|
1034
|
+
throw refuse(direction, `stablecoin ${stablecoin} is not supported by the PSM at ${psm}`);
|
|
1035
|
+
}
|
|
1036
|
+
const approveExpected = (amount) => isSwap ? { kind: "stablecoin-approve", tokenAddress: token, spender, amountUnits: amount.toString() } : { kind: "ucd-approve-controller", ucdTokenAddress: token, spender, amountWei: amount.toString() };
|
|
1037
|
+
const allowanceBefore = await reads.allowance(token, owner, spender);
|
|
1038
|
+
const approvals = [];
|
|
1039
|
+
if (allowanceBefore < amountIn) {
|
|
1040
|
+
if (allowanceBefore > 0n) {
|
|
1041
|
+
approvals.push({
|
|
1042
|
+
step: "reset-approve",
|
|
1043
|
+
tx: buildPsmApproveTx({ token, spender, amount: 0n, chainId }),
|
|
1044
|
+
expected: approveExpected(0n)
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1047
|
+
approvals.push({
|
|
1048
|
+
step: "approve",
|
|
1049
|
+
tx: buildPsmApproveTx({ token, spender, amount: amountIn, chainId }),
|
|
1050
|
+
expected: approveExpected(amountIn)
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
const exec = isSwap ? {
|
|
1054
|
+
step: "swap",
|
|
1055
|
+
tx: buildPsmSwapTx({ psm, stablecoin, amountIn, minOut, chainId }),
|
|
1056
|
+
expected: { kind: "psm-swap", psmAddress: psm, stablecoin, amountIn: amountIn.toString(), minOut: minOut.toString() }
|
|
1057
|
+
} : {
|
|
1058
|
+
step: "redeem",
|
|
1059
|
+
tx: buildPsmRedeemTx({ psm, stablecoin, ucdAmount: amountIn, minOut, chainId }),
|
|
1060
|
+
expected: {
|
|
1061
|
+
kind: "psm-redeem",
|
|
1062
|
+
psmAddress: psm,
|
|
1063
|
+
stablecoin,
|
|
1064
|
+
amountUcdWei: amountIn.toString(),
|
|
1065
|
+
minOut: minOut.toString()
|
|
1066
|
+
}
|
|
1067
|
+
};
|
|
1068
|
+
for (const leg of [...approvals, exec]) {
|
|
1069
|
+
validateUnsignedTx({ ...leg.tx }, leg.expected, vctx);
|
|
1070
|
+
}
|
|
1071
|
+
return { direction, owner, token, spender, amountIn, minOut, allowanceBefore, approvals, exec };
|
|
1072
|
+
}
|
|
1073
|
+
async function executePsmPlan(plan, signer, vctx) {
|
|
1074
|
+
const approvalHashes = [];
|
|
1075
|
+
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.`;
|
|
1076
|
+
const sendLeg = async (leg) => {
|
|
1077
|
+
const request = { to: leg.tx.to, data: leg.tx.data, value: 0n, chainId: leg.tx.chainId };
|
|
1078
|
+
validateUnsignedTx({ ...request }, leg.expected, vctx);
|
|
1079
|
+
const sent = await signer.sendTransaction(request);
|
|
1080
|
+
const receipt = await sent.wait();
|
|
1081
|
+
if (!receipt)
|
|
1082
|
+
throw new Error(`PSM ${leg.step} tx ${sent.hash} returned no receipt`);
|
|
1083
|
+
if (receipt.status !== 1)
|
|
1084
|
+
throw new Error(`PSM ${leg.step} tx ${sent.hash} reverted`);
|
|
1085
|
+
return { hash: sent.hash, blockNumber: receipt.blockNumber };
|
|
1086
|
+
};
|
|
1087
|
+
for (const leg of plan.approvals) {
|
|
1088
|
+
const landed = await sendLeg(leg).catch((e) => {
|
|
1089
|
+
throw new Error(`PSM ${leg.step} failed: ${e instanceof Error ? e.message : String(e)}.${residual()}`);
|
|
1090
|
+
});
|
|
1091
|
+
approvalHashes.push(landed.hash);
|
|
1092
|
+
}
|
|
1093
|
+
const exec = await sendLeg(plan.exec).catch((e) => {
|
|
1094
|
+
throw new Error(`PSM ${plan.exec.step} failed: ${e instanceof Error ? e.message : String(e)}.${residual()}`);
|
|
1095
|
+
});
|
|
1096
|
+
return { ...exec, approvalHashes };
|
|
1097
|
+
}
|
|
965
1098
|
// Annotate the CommonJS export names for ESM import in node:
|
|
966
1099
|
0 && (module.exports = {
|
|
967
1100
|
AUTH_VALIDITY_WINDOWS,
|
|
@@ -986,17 +1119,23 @@ async function computeExtendFeeUpperBound(provider, contracts, positionId, selec
|
|
|
986
1119
|
authorizeWithServer,
|
|
987
1120
|
broadcastSignedTx,
|
|
988
1121
|
buildAuthEnvelope,
|
|
1122
|
+
buildPsmApproveTx,
|
|
1123
|
+
buildPsmRedeemTx,
|
|
1124
|
+
buildPsmSwapTx,
|
|
989
1125
|
buildValidationContext,
|
|
990
1126
|
computeExtendFeeUpperBound,
|
|
991
1127
|
decodeSignable,
|
|
992
1128
|
describeUnsignedTx,
|
|
993
1129
|
encodeSignable,
|
|
1130
|
+
executePsmPlan,
|
|
994
1131
|
fetchWithTimeout,
|
|
995
1132
|
isAuthFresh,
|
|
996
1133
|
isStrictCurrentQuantum,
|
|
997
1134
|
modeForChainId,
|
|
998
1135
|
nextQuantumTimestamp,
|
|
1136
|
+
planPsmExchange,
|
|
999
1137
|
postJson,
|
|
1138
|
+
psmExchangeReadsFromProvider,
|
|
1000
1139
|
randomNonceHex,
|
|
1001
1140
|
readTxFeeFields,
|
|
1002
1141
|
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
|
};
|
|
@@ -896,6 +900,129 @@ async function computeExtendFeeUpperBound(provider, contracts, positionId, selec
|
|
|
896
900
|
const upperBoundFeeWei = ucdDebt * extensionFeeRateBps / 10000n;
|
|
897
901
|
return { ucdDebt, extensionFeeRateBps, upperBoundFeeWei };
|
|
898
902
|
}
|
|
903
|
+
|
|
904
|
+
// src/utils/sign-guard/psm-exchange.ts
|
|
905
|
+
import { ethers as ethers6 } from "ethers";
|
|
906
|
+
function buildPsmApproveTx(p) {
|
|
907
|
+
return {
|
|
908
|
+
to: p.token,
|
|
909
|
+
data: encodeSignable("approve", [p.spender, p.amount]),
|
|
910
|
+
value: "0x0",
|
|
911
|
+
chainId: p.chainId
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
function buildPsmSwapTx(p) {
|
|
915
|
+
return {
|
|
916
|
+
to: p.psm,
|
|
917
|
+
data: encodeSignable("swap", [p.stablecoin, p.amountIn, p.minOut]),
|
|
918
|
+
value: "0x0",
|
|
919
|
+
chainId: p.chainId
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
function buildPsmRedeemTx(p) {
|
|
923
|
+
return {
|
|
924
|
+
to: p.psm,
|
|
925
|
+
data: encodeSignable("redeem", [p.stablecoin, p.ucdAmount, p.minOut]),
|
|
926
|
+
value: "0x0",
|
|
927
|
+
chainId: p.chainId
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
var PSM_READS_ABI = ["function supportedStablecoins(address) view returns (bool)"];
|
|
931
|
+
var ERC20_READS_ABI = ["function allowance(address owner, address spender) view returns (uint256)"];
|
|
932
|
+
function psmExchangeReadsFromProvider(provider, psmAddress) {
|
|
933
|
+
const psm = new ethers6.Contract(psmAddress, PSM_READS_ABI, provider);
|
|
934
|
+
return {
|
|
935
|
+
isStablecoinSupported: async (stablecoin) => await psm.getFunction("supportedStablecoins")(stablecoin),
|
|
936
|
+
allowance: async (token, owner, spender) => await new ethers6.Contract(token, ERC20_READS_ABI, provider).getFunction("allowance")(owner, spender)
|
|
937
|
+
};
|
|
938
|
+
}
|
|
939
|
+
var refuse = (direction, msg) => new TxValidationError(msg, `PSM ${direction}`);
|
|
940
|
+
function requireAddress(direction, label, value) {
|
|
941
|
+
if (!value || !ethers6.isAddress(value)) {
|
|
942
|
+
throw refuse(direction, `${label} address is missing or invalid (${String(value)})`);
|
|
943
|
+
}
|
|
944
|
+
return ethers6.getAddress(value);
|
|
945
|
+
}
|
|
946
|
+
async function planPsmExchange(params) {
|
|
947
|
+
const { direction, amountIn, minOut, vctx, reads } = params;
|
|
948
|
+
const chainId = vctx.chainId;
|
|
949
|
+
if (amountIn <= 0n) {
|
|
950
|
+
throw refuse(direction, `amount must be greater than zero (got ${amountIn})`);
|
|
951
|
+
}
|
|
952
|
+
if (minOut <= 0n) {
|
|
953
|
+
throw refuse(direction, `minimum-out floor must be greater than zero (got ${minOut}) \u2014 a zero floor is refused on-chain`);
|
|
954
|
+
}
|
|
955
|
+
const owner = requireAddress(direction, "owner", params.owner);
|
|
956
|
+
const psm = requireAddress(direction, "SimplePSMV2", params.addresses.psm);
|
|
957
|
+
const stablecoin = requireAddress(direction, "stablecoin", params.addresses.stablecoin);
|
|
958
|
+
const isSwap = direction === "swap";
|
|
959
|
+
const token = isSwap ? stablecoin : requireAddress(direction, "UCDToken", params.addresses.ucdToken);
|
|
960
|
+
const spender = isSwap ? psm : requireAddress(direction, "UCDController", params.addresses.ucdController);
|
|
961
|
+
if (!await reads.isStablecoinSupported(stablecoin)) {
|
|
962
|
+
throw refuse(direction, `stablecoin ${stablecoin} is not supported by the PSM at ${psm}`);
|
|
963
|
+
}
|
|
964
|
+
const approveExpected = (amount) => isSwap ? { kind: "stablecoin-approve", tokenAddress: token, spender, amountUnits: amount.toString() } : { kind: "ucd-approve-controller", ucdTokenAddress: token, spender, amountWei: amount.toString() };
|
|
965
|
+
const allowanceBefore = await reads.allowance(token, owner, spender);
|
|
966
|
+
const approvals = [];
|
|
967
|
+
if (allowanceBefore < amountIn) {
|
|
968
|
+
if (allowanceBefore > 0n) {
|
|
969
|
+
approvals.push({
|
|
970
|
+
step: "reset-approve",
|
|
971
|
+
tx: buildPsmApproveTx({ token, spender, amount: 0n, chainId }),
|
|
972
|
+
expected: approveExpected(0n)
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
approvals.push({
|
|
976
|
+
step: "approve",
|
|
977
|
+
tx: buildPsmApproveTx({ token, spender, amount: amountIn, chainId }),
|
|
978
|
+
expected: approveExpected(amountIn)
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
const exec = isSwap ? {
|
|
982
|
+
step: "swap",
|
|
983
|
+
tx: buildPsmSwapTx({ psm, stablecoin, amountIn, minOut, chainId }),
|
|
984
|
+
expected: { kind: "psm-swap", psmAddress: psm, stablecoin, amountIn: amountIn.toString(), minOut: minOut.toString() }
|
|
985
|
+
} : {
|
|
986
|
+
step: "redeem",
|
|
987
|
+
tx: buildPsmRedeemTx({ psm, stablecoin, ucdAmount: amountIn, minOut, chainId }),
|
|
988
|
+
expected: {
|
|
989
|
+
kind: "psm-redeem",
|
|
990
|
+
psmAddress: psm,
|
|
991
|
+
stablecoin,
|
|
992
|
+
amountUcdWei: amountIn.toString(),
|
|
993
|
+
minOut: minOut.toString()
|
|
994
|
+
}
|
|
995
|
+
};
|
|
996
|
+
for (const leg of [...approvals, exec]) {
|
|
997
|
+
validateUnsignedTx({ ...leg.tx }, leg.expected, vctx);
|
|
998
|
+
}
|
|
999
|
+
return { direction, owner, token, spender, amountIn, minOut, allowanceBefore, approvals, exec };
|
|
1000
|
+
}
|
|
1001
|
+
async function executePsmPlan(plan, signer, vctx) {
|
|
1002
|
+
const approvalHashes = [];
|
|
1003
|
+
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.`;
|
|
1004
|
+
const sendLeg = async (leg) => {
|
|
1005
|
+
const request = { to: leg.tx.to, data: leg.tx.data, value: 0n, chainId: leg.tx.chainId };
|
|
1006
|
+
validateUnsignedTx({ ...request }, leg.expected, vctx);
|
|
1007
|
+
const sent = await signer.sendTransaction(request);
|
|
1008
|
+
const receipt = await sent.wait();
|
|
1009
|
+
if (!receipt)
|
|
1010
|
+
throw new Error(`PSM ${leg.step} tx ${sent.hash} returned no receipt`);
|
|
1011
|
+
if (receipt.status !== 1)
|
|
1012
|
+
throw new Error(`PSM ${leg.step} tx ${sent.hash} reverted`);
|
|
1013
|
+
return { hash: sent.hash, blockNumber: receipt.blockNumber };
|
|
1014
|
+
};
|
|
1015
|
+
for (const leg of plan.approvals) {
|
|
1016
|
+
const landed = await sendLeg(leg).catch((e) => {
|
|
1017
|
+
throw new Error(`PSM ${leg.step} failed: ${e instanceof Error ? e.message : String(e)}.${residual()}`);
|
|
1018
|
+
});
|
|
1019
|
+
approvalHashes.push(landed.hash);
|
|
1020
|
+
}
|
|
1021
|
+
const exec = await sendLeg(plan.exec).catch((e) => {
|
|
1022
|
+
throw new Error(`PSM ${plan.exec.step} failed: ${e instanceof Error ? e.message : String(e)}.${residual()}`);
|
|
1023
|
+
});
|
|
1024
|
+
return { ...exec, approvalHashes };
|
|
1025
|
+
}
|
|
899
1026
|
export {
|
|
900
1027
|
AUTH_VALIDITY_WINDOWS,
|
|
901
1028
|
BROADCAST_PATH,
|
|
@@ -919,17 +1046,23 @@ export {
|
|
|
919
1046
|
authorizeWithServer,
|
|
920
1047
|
broadcastSignedTx,
|
|
921
1048
|
buildAuthEnvelope,
|
|
1049
|
+
buildPsmApproveTx,
|
|
1050
|
+
buildPsmRedeemTx,
|
|
1051
|
+
buildPsmSwapTx,
|
|
922
1052
|
buildValidationContext,
|
|
923
1053
|
computeExtendFeeUpperBound,
|
|
924
1054
|
decodeSignable,
|
|
925
1055
|
describeUnsignedTx,
|
|
926
1056
|
encodeSignable,
|
|
1057
|
+
executePsmPlan,
|
|
927
1058
|
fetchWithTimeout,
|
|
928
1059
|
isAuthFresh,
|
|
929
1060
|
isStrictCurrentQuantum,
|
|
930
1061
|
modeForChainId,
|
|
931
1062
|
nextQuantumTimestamp,
|
|
1063
|
+
planPsmExchange,
|
|
932
1064
|
postJson,
|
|
1065
|
+
psmExchangeReadsFromProvider,
|
|
933
1066
|
randomNonceHex,
|
|
934
1067
|
readTxFeeFields,
|
|
935
1068
|
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>;
|
|
@@ -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,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;
|