@compass-labs/widgets 0.1.26 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +122 -329
- package/dist/index.d.ts +122 -329
- package/dist/index.js +2540 -2117
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2543 -2116
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.js +304 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +304 -0
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -73,6 +73,8 @@ function createCompassHandler(config) {
|
|
|
73
73
|
return await handleSwapPrepare(client, body);
|
|
74
74
|
case "swap/execute":
|
|
75
75
|
return await handleSwapExecute(client, body, config);
|
|
76
|
+
case "rebalance/preview":
|
|
77
|
+
return await handleRebalancePreview(client, body, config);
|
|
76
78
|
default:
|
|
77
79
|
return jsonResponse({ error: `Unknown POST route: ${route}` }, 404);
|
|
78
80
|
}
|
|
@@ -839,6 +841,308 @@ async function handlePositions(client, params) {
|
|
|
839
841
|
return jsonResponse({ error: "Failed to fetch positions" }, 500);
|
|
840
842
|
}
|
|
841
843
|
}
|
|
844
|
+
async function handleRebalancePreview(client, body, config) {
|
|
845
|
+
const { owner, chain = "base", targets, slippage = 0.5 } = body;
|
|
846
|
+
if (!owner) {
|
|
847
|
+
return jsonResponse({ error: "Missing owner parameter" }, 400);
|
|
848
|
+
}
|
|
849
|
+
if (!targets || targets.length === 0) {
|
|
850
|
+
return jsonResponse({ error: "Missing targets" }, 400);
|
|
851
|
+
}
|
|
852
|
+
for (const t of targets) {
|
|
853
|
+
if (t.targetPercent < 0 || t.targetPercent > 100) {
|
|
854
|
+
return jsonResponse({ error: `Invalid target percentage: ${t.targetPercent}%` }, 400);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
try {
|
|
858
|
+
const positionsResponse = await client.earn.earnPositions({
|
|
859
|
+
chain,
|
|
860
|
+
owner
|
|
861
|
+
});
|
|
862
|
+
const positionsRaw = JSON.parse(JSON.stringify(positionsResponse));
|
|
863
|
+
const balancesResponse = await client.earn.earnBalances({
|
|
864
|
+
chain,
|
|
865
|
+
owner
|
|
866
|
+
});
|
|
867
|
+
const balancesRaw = JSON.parse(JSON.stringify(balancesResponse));
|
|
868
|
+
const currentPositions = [];
|
|
869
|
+
for (const a of positionsRaw.aave || []) {
|
|
870
|
+
const balance = a.balance || "0";
|
|
871
|
+
if (parseFloat(balance) <= 0) continue;
|
|
872
|
+
const symbol = (a.reserveSymbol || a.reserve_symbol || "UNKNOWN").toUpperCase();
|
|
873
|
+
currentPositions.push({
|
|
874
|
+
venueType: "AAVE",
|
|
875
|
+
venueAddress: symbol,
|
|
876
|
+
token: symbol,
|
|
877
|
+
usdValue: parseFloat(a.usdValue || a.usd_value || balance),
|
|
878
|
+
balance
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
for (const v of positionsRaw.vaults || []) {
|
|
882
|
+
const balance = v.balance || "0";
|
|
883
|
+
if (parseFloat(balance) <= 0) continue;
|
|
884
|
+
const symbol = (v.underlyingSymbol || v.underlying_symbol || "TOKEN").toUpperCase();
|
|
885
|
+
currentPositions.push({
|
|
886
|
+
venueType: "VAULT",
|
|
887
|
+
venueAddress: v.vaultAddress || v.vault_address || "",
|
|
888
|
+
token: symbol,
|
|
889
|
+
usdValue: parseFloat(v.usdValue || v.usd_value || balance),
|
|
890
|
+
balance
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
for (const p of positionsRaw.pendlePt || positionsRaw.pendle_pt || []) {
|
|
894
|
+
const balance = p.ptBalance || p.pt_balance || p.balance || "0";
|
|
895
|
+
if (parseFloat(balance) <= 0) continue;
|
|
896
|
+
const symbol = (p.underlyingSymbol || p.underlying_symbol || "PT").toUpperCase();
|
|
897
|
+
currentPositions.push({
|
|
898
|
+
venueType: "PENDLE_PT",
|
|
899
|
+
venueAddress: p.marketAddress || p.market_address || "",
|
|
900
|
+
token: symbol,
|
|
901
|
+
usdValue: parseFloat(p.usdValue || p.usd_value || balance),
|
|
902
|
+
balance
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
let totalIdleUsd = 0;
|
|
906
|
+
for (const [, tokenData] of Object.entries(balancesRaw.balances || {})) {
|
|
907
|
+
const td = tokenData;
|
|
908
|
+
const usdVal = parseFloat(td.usd_value || td.usdValue || "0");
|
|
909
|
+
totalIdleUsd += usdVal;
|
|
910
|
+
}
|
|
911
|
+
const totalPositionUsd = currentPositions.reduce((sum, p) => sum + p.usdValue, 0);
|
|
912
|
+
const totalUsd = totalPositionUsd + totalIdleUsd;
|
|
913
|
+
if (totalUsd <= 0) {
|
|
914
|
+
return jsonResponse({ error: "No portfolio value found to rebalance" }, 400);
|
|
915
|
+
}
|
|
916
|
+
const bundleActions = [];
|
|
917
|
+
const actionsSummary = [];
|
|
918
|
+
const warnings = [];
|
|
919
|
+
const MIN_THRESHOLD_USD = 0.01;
|
|
920
|
+
const pendingDeposits = [];
|
|
921
|
+
for (const target of targets) {
|
|
922
|
+
const targetUsd = totalUsd * (target.targetPercent / 100);
|
|
923
|
+
const current = currentPositions.find(
|
|
924
|
+
(p) => p.venueType === target.venueType && p.venueAddress.toLowerCase() === target.venueAddress.toLowerCase()
|
|
925
|
+
);
|
|
926
|
+
const currentUsd = current?.usdValue || 0;
|
|
927
|
+
const deltaUsd = targetUsd - currentUsd;
|
|
928
|
+
if (Math.abs(deltaUsd) < MIN_THRESHOLD_USD) continue;
|
|
929
|
+
if (deltaUsd < 0 && current) {
|
|
930
|
+
const withdrawFraction = Math.abs(deltaUsd) / currentUsd;
|
|
931
|
+
const withdrawAmount = (parseFloat(current.balance) * withdrawFraction).toString();
|
|
932
|
+
let venue;
|
|
933
|
+
if (target.venueType === "VAULT") {
|
|
934
|
+
venue = { type: "VAULT", vaultAddress: target.venueAddress };
|
|
935
|
+
} else if (target.venueType === "AAVE") {
|
|
936
|
+
venue = { type: "AAVE", token: current.token };
|
|
937
|
+
} else if (target.venueType === "PENDLE_PT") {
|
|
938
|
+
venue = { type: "PENDLE_PT", marketAddress: target.venueAddress, maxSlippagePercent: slippage };
|
|
939
|
+
warnings.push(`Withdrawing from Pendle PT - check maturity implications`);
|
|
940
|
+
}
|
|
941
|
+
bundleActions.push({
|
|
942
|
+
body: {
|
|
943
|
+
actionType: "V2_MANAGE",
|
|
944
|
+
venue,
|
|
945
|
+
action: "WITHDRAW",
|
|
946
|
+
amount: withdrawAmount
|
|
947
|
+
}
|
|
948
|
+
});
|
|
949
|
+
actionsSummary.push({
|
|
950
|
+
type: "withdraw",
|
|
951
|
+
venue: target.venueAddress,
|
|
952
|
+
token: current.token,
|
|
953
|
+
amount: withdrawAmount,
|
|
954
|
+
usdValue: Math.abs(deltaUsd)
|
|
955
|
+
});
|
|
956
|
+
} else if (deltaUsd > 0) {
|
|
957
|
+
let venue;
|
|
958
|
+
const token = target.token || current?.token || "";
|
|
959
|
+
if (target.venueType === "VAULT") {
|
|
960
|
+
venue = { type: "VAULT", vaultAddress: target.venueAddress };
|
|
961
|
+
} else if (target.venueType === "AAVE") {
|
|
962
|
+
venue = { type: "AAVE", token };
|
|
963
|
+
} else if (target.venueType === "PENDLE_PT") {
|
|
964
|
+
venue = { type: "PENDLE_PT", marketAddress: target.venueAddress, token, maxSlippagePercent: slippage };
|
|
965
|
+
}
|
|
966
|
+
pendingDeposits.push({ venue, venueAddress: target.venueAddress, token, deltaUsd });
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
for (const current of currentPositions) {
|
|
970
|
+
const hasTarget = targets.some(
|
|
971
|
+
(t) => t.venueType === current.venueType && t.venueAddress.toLowerCase() === current.venueAddress.toLowerCase()
|
|
972
|
+
);
|
|
973
|
+
if (!hasTarget && current.usdValue >= MIN_THRESHOLD_USD) {
|
|
974
|
+
let venue;
|
|
975
|
+
if (current.venueType === "VAULT") {
|
|
976
|
+
venue = { type: "VAULT", vaultAddress: current.venueAddress };
|
|
977
|
+
} else if (current.venueType === "AAVE") {
|
|
978
|
+
venue = { type: "AAVE", token: current.token };
|
|
979
|
+
} else if (current.venueType === "PENDLE_PT") {
|
|
980
|
+
venue = { type: "PENDLE_PT", marketAddress: current.venueAddress, maxSlippagePercent: slippage };
|
|
981
|
+
}
|
|
982
|
+
bundleActions.unshift({
|
|
983
|
+
body: {
|
|
984
|
+
actionType: "V2_MANAGE",
|
|
985
|
+
venue,
|
|
986
|
+
action: "WITHDRAW",
|
|
987
|
+
amount: current.balance
|
|
988
|
+
}
|
|
989
|
+
});
|
|
990
|
+
actionsSummary.unshift({
|
|
991
|
+
type: "withdraw",
|
|
992
|
+
venue: current.venueAddress,
|
|
993
|
+
token: current.token,
|
|
994
|
+
amount: current.balance,
|
|
995
|
+
usdValue: current.usdValue
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
const availableByToken = {};
|
|
1000
|
+
for (const action of actionsSummary) {
|
|
1001
|
+
if (action.type === "withdraw") {
|
|
1002
|
+
const key = action.token.toUpperCase();
|
|
1003
|
+
if (!availableByToken[key]) availableByToken[key] = { usd: 0, tokenAmount: 0 };
|
|
1004
|
+
availableByToken[key].usd += action.usdValue;
|
|
1005
|
+
availableByToken[key].tokenAmount += parseFloat(action.amount);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
for (const [tokenSymbol, tokenData] of Object.entries(balancesRaw.balances || {})) {
|
|
1009
|
+
const td = tokenData;
|
|
1010
|
+
const usdVal = parseFloat(td.usd_value || td.usdValue || "0");
|
|
1011
|
+
const bal = parseFloat(td.balance || "0");
|
|
1012
|
+
if (usdVal > MIN_THRESHOLD_USD) {
|
|
1013
|
+
const key = tokenSymbol.toUpperCase();
|
|
1014
|
+
if (!availableByToken[key]) availableByToken[key] = { usd: 0, tokenAmount: 0 };
|
|
1015
|
+
availableByToken[key].usd += usdVal;
|
|
1016
|
+
availableByToken[key].tokenAmount += bal;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
const depositNeedsByToken = {};
|
|
1020
|
+
for (const dep of pendingDeposits) {
|
|
1021
|
+
const key = dep.token.toUpperCase();
|
|
1022
|
+
depositNeedsByToken[key] = (depositNeedsByToken[key] || 0) + dep.deltaUsd;
|
|
1023
|
+
}
|
|
1024
|
+
for (const [depositToken, neededUsd] of Object.entries(depositNeedsByToken)) {
|
|
1025
|
+
const availableUsd = availableByToken[depositToken]?.usd || 0;
|
|
1026
|
+
let shortfallUsd = neededUsd - availableUsd;
|
|
1027
|
+
if (shortfallUsd <= MIN_THRESHOLD_USD) continue;
|
|
1028
|
+
for (const [sourceToken, sourceData] of Object.entries(availableByToken)) {
|
|
1029
|
+
if (sourceToken === depositToken) continue;
|
|
1030
|
+
const sourceNeeded = depositNeedsByToken[sourceToken] || 0;
|
|
1031
|
+
const sourceExcess = sourceData.usd - sourceNeeded;
|
|
1032
|
+
if (sourceExcess <= MIN_THRESHOLD_USD) continue;
|
|
1033
|
+
const swapUsd = Math.min(shortfallUsd, sourceExcess);
|
|
1034
|
+
if (swapUsd < MIN_THRESHOLD_USD) continue;
|
|
1035
|
+
const tokenAmountIn = sourceData.usd > 0 ? swapUsd / sourceData.usd * sourceData.tokenAmount : swapUsd;
|
|
1036
|
+
bundleActions.push({
|
|
1037
|
+
body: {
|
|
1038
|
+
actionType: "V2_SWAP",
|
|
1039
|
+
tokenIn: sourceToken,
|
|
1040
|
+
tokenOut: depositToken,
|
|
1041
|
+
amountIn: tokenAmountIn.toString(),
|
|
1042
|
+
slippage
|
|
1043
|
+
}
|
|
1044
|
+
});
|
|
1045
|
+
actionsSummary.push({
|
|
1046
|
+
type: "swap",
|
|
1047
|
+
token: sourceToken,
|
|
1048
|
+
tokenOut: depositToken,
|
|
1049
|
+
amount: tokenAmountIn,
|
|
1050
|
+
usdValue: swapUsd
|
|
1051
|
+
});
|
|
1052
|
+
sourceData.usd -= swapUsd;
|
|
1053
|
+
sourceData.tokenAmount -= tokenAmountIn;
|
|
1054
|
+
const slippageFactor = 1 - slippage / 100;
|
|
1055
|
+
if (!availableByToken[depositToken]) availableByToken[depositToken] = { usd: 0, tokenAmount: 0 };
|
|
1056
|
+
const receivedUsd = swapUsd * slippageFactor;
|
|
1057
|
+
const existingData = availableByToken[depositToken];
|
|
1058
|
+
const impliedPrice = existingData.tokenAmount > 0 && existingData.usd > 0 ? existingData.usd / existingData.tokenAmount : 1;
|
|
1059
|
+
availableByToken[depositToken].usd += receivedUsd;
|
|
1060
|
+
availableByToken[depositToken].tokenAmount += receivedUsd / impliedPrice;
|
|
1061
|
+
shortfallUsd -= swapUsd;
|
|
1062
|
+
warnings.push(`Swap ${sourceToken} to ${depositToken} involves slippage risk`);
|
|
1063
|
+
if (shortfallUsd <= MIN_THRESHOLD_USD) break;
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
for (const dep of pendingDeposits) {
|
|
1067
|
+
const key = dep.token.toUpperCase();
|
|
1068
|
+
const available = availableByToken[key];
|
|
1069
|
+
const tokenPrice = available && available.tokenAmount > 0 && available.usd > 0 ? available.usd / available.tokenAmount : 1;
|
|
1070
|
+
const desiredTokens = dep.deltaUsd / tokenPrice;
|
|
1071
|
+
const maxAvailableTokens = available ? available.tokenAmount * 0.99 : 0;
|
|
1072
|
+
const depositTokenAmount = maxAvailableTokens > 0 && maxAvailableTokens < desiredTokens ? maxAvailableTokens : desiredTokens;
|
|
1073
|
+
bundleActions.push({
|
|
1074
|
+
body: {
|
|
1075
|
+
actionType: "V2_MANAGE",
|
|
1076
|
+
venue: dep.venue,
|
|
1077
|
+
action: "DEPOSIT",
|
|
1078
|
+
amount: depositTokenAmount.toString()
|
|
1079
|
+
}
|
|
1080
|
+
});
|
|
1081
|
+
const depositUsd = depositTokenAmount * tokenPrice;
|
|
1082
|
+
actionsSummary.push({
|
|
1083
|
+
type: "deposit",
|
|
1084
|
+
venue: dep.venueAddress,
|
|
1085
|
+
token: dep.token,
|
|
1086
|
+
amount: depositTokenAmount.toString(),
|
|
1087
|
+
usdValue: depositUsd
|
|
1088
|
+
});
|
|
1089
|
+
if (available) {
|
|
1090
|
+
available.usd -= depositUsd;
|
|
1091
|
+
available.tokenAmount -= depositTokenAmount;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
if (bundleActions.length === 0 && pendingDeposits.length === 0) {
|
|
1095
|
+
return jsonResponse({
|
|
1096
|
+
actions: [],
|
|
1097
|
+
actionsCount: 0,
|
|
1098
|
+
warnings: ["Portfolio is already at target allocation"]
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
bundleActions.sort((a, b) => {
|
|
1102
|
+
const getOrder = (action) => {
|
|
1103
|
+
if (action.body.action === "WITHDRAW") return 0;
|
|
1104
|
+
if (action.body.actionType === "V2_SWAP") return 1;
|
|
1105
|
+
if (action.body.action === "DEPOSIT") return 2;
|
|
1106
|
+
return 3;
|
|
1107
|
+
};
|
|
1108
|
+
return getOrder(a) - getOrder(b);
|
|
1109
|
+
});
|
|
1110
|
+
actionsSummary.sort((a, b) => {
|
|
1111
|
+
const order = { withdraw: 0, swap: 1, deposit: 2 };
|
|
1112
|
+
return (order[a.type] || 0) - (order[b.type] || 0);
|
|
1113
|
+
});
|
|
1114
|
+
if (actionsSummary.some((a) => a.type === "swap")) {
|
|
1115
|
+
warnings.push("Swap amounts are estimates - actual amounts may vary due to slippage");
|
|
1116
|
+
}
|
|
1117
|
+
const bundleResponse = await client.earn.earnBundle({
|
|
1118
|
+
owner,
|
|
1119
|
+
chain,
|
|
1120
|
+
gasSponsorship: true,
|
|
1121
|
+
actions: bundleActions
|
|
1122
|
+
});
|
|
1123
|
+
const eip712 = bundleResponse.eip712;
|
|
1124
|
+
if (!eip712) {
|
|
1125
|
+
return jsonResponse({ error: "No EIP-712 data returned from bundle API" }, 500);
|
|
1126
|
+
}
|
|
1127
|
+
const types = eip712.types;
|
|
1128
|
+
const normalizedTypes = {
|
|
1129
|
+
EIP712Domain: types.eip712Domain || types.EIP712Domain,
|
|
1130
|
+
SafeTx: types.safeTx || types.SafeTx
|
|
1131
|
+
};
|
|
1132
|
+
return jsonResponse({
|
|
1133
|
+
eip712,
|
|
1134
|
+
normalizedTypes,
|
|
1135
|
+
domain: eip712.domain,
|
|
1136
|
+
message: eip712.message,
|
|
1137
|
+
actions: actionsSummary,
|
|
1138
|
+
actionsCount: bundleActions.length,
|
|
1139
|
+
warnings
|
|
1140
|
+
});
|
|
1141
|
+
} catch (error) {
|
|
1142
|
+
const message = error instanceof Error ? error.message : "Failed to compute rebalance preview";
|
|
1143
|
+
return jsonResponse({ error: message }, 502);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
842
1146
|
|
|
843
1147
|
exports.createCompassHandler = createCompassHandler;
|
|
844
1148
|
//# sourceMappingURL=index.js.map
|