@gearbox-protocol/sdk 17.2.0 → 17.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/dist/cjs/onchain/accounts/intents/leverage-band.js +1 -1
  2. package/dist/cjs/onchain/accounts/intents/maxBorrow.js +15 -11
  3. package/dist/cjs/onchain/accounts/intents/testing/sdk-mock.js +5 -2
  4. package/dist/cjs/onchain/chain/chains.js +1 -2
  5. package/dist/cjs/onchain/index.js +2 -0
  6. package/dist/cjs/onchain/market/MarketSuite.js +11 -4
  7. package/dist/cjs/onchain/market/credit/CreditSuite.js +25 -138
  8. package/dist/cjs/onchain/market/credit/CreditSuiteStrategy.js +179 -0
  9. package/dist/cjs/onchain/market/credit/index.js +2 -0
  10. package/dist/cjs/onchain/market/index.js +2 -0
  11. package/dist/cjs/onchain/market/pool/PoolQuotaKeeperV310Contract.js +4 -1
  12. package/dist/cjs/onchain/opportunities/OpportunitiesService.js +7 -9
  13. package/dist/cjs/rewards/{multichain.js → RewardsService.js} +18 -30
  14. package/dist/cjs/rewards/errors.js +4 -4
  15. package/dist/cjs/rewards/helpers.js +88 -0
  16. package/dist/cjs/rewards/index.js +2 -2
  17. package/dist/cjs/rewards/merkl-api.js +14 -25
  18. package/dist/cjs/rewards/toMerklRewards.js +21 -77
  19. package/dist/cjs/rewards/toTurtleRewards.js +46 -62
  20. package/dist/cjs/rewards/turtle-api.js +17 -0
  21. package/dist/cjs/rewards/types.js +1 -0
  22. package/dist/cjs/sdk/prepare/PrepareApi.js +1 -1
  23. package/dist/esm/onchain/accounts/intents/leverage-band.js +1 -1
  24. package/dist/esm/onchain/accounts/intents/maxBorrow.js +15 -11
  25. package/dist/esm/onchain/accounts/intents/testing/sdk-mock.js +5 -2
  26. package/dist/esm/onchain/chain/chains.js +1 -2
  27. package/dist/esm/onchain/index.js +2 -1
  28. package/dist/esm/onchain/market/MarketSuite.js +11 -4
  29. package/dist/esm/onchain/market/credit/CreditSuite.js +27 -140
  30. package/dist/esm/onchain/market/credit/CreditSuiteStrategy.js +178 -0
  31. package/dist/esm/onchain/market/credit/index.js +2 -1
  32. package/dist/esm/onchain/market/index.js +2 -1
  33. package/dist/esm/onchain/market/pool/PoolQuotaKeeperV310Contract.js +4 -1
  34. package/dist/esm/onchain/opportunities/OpportunitiesService.js +7 -9
  35. package/dist/esm/rewards/{multichain.js → RewardsService.js} +19 -31
  36. package/dist/esm/rewards/errors.js +4 -4
  37. package/dist/esm/rewards/helpers.js +83 -0
  38. package/dist/esm/rewards/index.js +2 -2
  39. package/dist/esm/rewards/merkl-api.js +15 -25
  40. package/dist/esm/rewards/toMerklRewards.js +23 -78
  41. package/dist/esm/rewards/toTurtleRewards.js +46 -62
  42. package/dist/esm/rewards/turtle-api.js +17 -1
  43. package/dist/esm/rewards/types.js +1 -0
  44. package/dist/esm/sdk/prepare/PrepareApi.js +1 -1
  45. package/dist/types/model/errors/operation-errors.d.ts +6 -1
  46. package/dist/types/onchain/accounts/intents/maxBorrow.d.ts +12 -10
  47. package/dist/types/onchain/index.d.ts +2 -1
  48. package/dist/types/onchain/market/MarketSuite.d.ts +6 -0
  49. package/dist/types/onchain/market/credit/CreditSuite.d.ts +18 -52
  50. package/dist/types/onchain/market/credit/CreditSuiteStrategy.d.ts +78 -0
  51. package/dist/types/onchain/market/credit/index.d.ts +2 -1
  52. package/dist/types/onchain/market/credit/types.d.ts +3 -1
  53. package/dist/types/onchain/market/index.d.ts +2 -1
  54. package/dist/types/onchain/market/pool/PoolQuotaKeeperV310Contract.d.ts +2 -0
  55. package/dist/types/rewards/RewardsService.d.ts +29 -0
  56. package/dist/types/rewards/errors.d.ts +2 -2
  57. package/dist/types/rewards/helpers.d.ts +44 -0
  58. package/dist/types/rewards/index.d.ts +3 -3
  59. package/dist/types/rewards/merkl-api.d.ts +5 -10
  60. package/dist/types/rewards/toMerklRewards.d.ts +5 -48
  61. package/dist/types/rewards/toTurtleRewards.d.ts +3 -3
  62. package/dist/types/rewards/turtle-api.d.ts +5 -1
  63. package/dist/types/rewards/types.d.ts +41 -0
  64. package/package.json +1 -1
  65. package/dist/types/rewards/multichain.d.ts +0 -28
@@ -6,9 +6,21 @@ const require_rewards_toMerklRewards = require("./toMerklRewards.js");
6
6
  const require_rewards_toTurtleRewards = require("./toTurtleRewards.js");
7
7
  const require_rewards_turtle_api = require("./turtle-api.js");
8
8
  let viem = require("viem");
9
- //#region src/rewards/multichain.ts
10
- var RewardsFanOut = class extends require_onchain_base_MultichainConstruct.MultichainConstruct {
11
- async list({ wallet, chainIds, merklApiKey, turtleApiKey }) {
9
+ //#region src/rewards/RewardsService.ts
10
+ var RewardsService = class extends require_onchain_base_MultichainConstruct.MultichainConstruct {
11
+ #keys;
12
+ constructor(sdk, keys = {}) {
13
+ super(sdk);
14
+ this.#keys = keys;
15
+ }
16
+ /**
17
+ * Every claimable reward a wallet holds — Merkl campaigns and the Gearbox
18
+ * organisation's Turtle streams — across the chains the handle carries.
19
+ * A chain is `status: "error"` only when every source failed on it; a chain
20
+ * with nothing to claim is a `"success"` with no rewards.
21
+ **/
22
+ async list(wallet, chainIds) {
23
+ const { merklApiKey, turtleApiKey } = this.#keys;
12
24
  const user = (0, viem.getAddress)(wallet);
13
25
  const turtle = turtleApiKey ? require_rewards_turtle_api.fetchTurtleWalletRewards(user, turtleApiKey) : void 0;
14
26
  turtle?.catch(() => {});
@@ -16,13 +28,13 @@ var RewardsFanOut = class extends require_onchain_base_MultichainConstruct.Multi
16
28
  chainIds,
17
29
  label: "list rewards",
18
30
  block: "state",
19
- run: async (sdk, block) => {
31
+ run: async (sdk) => {
20
32
  const sources = [require_rewards_merkl_api.fetchMerklUserRewards({
21
33
  chainId: sdk.chainId,
22
34
  user,
23
35
  apiKey: merklApiKey
24
36
  }).then((response) => require_rewards_toMerklRewards.toMerklRewards(sdk, response))];
25
- if (turtle) sources.push(turtle.then(async (rewards) => require_rewards_toTurtleRewards.toTurtleRewards(sdk, rewards, await readClaimed(sdk, user, rewards, block.blockNumber))));
37
+ if (turtle) sources.push(turtle.then(async (rewards) => require_rewards_toTurtleRewards.toTurtleRewards(sdk, rewards, await require_rewards_turtle_api.readTurtleClaimed(sdk, user, rewards))));
26
38
  const settled = await Promise.allSettled(sources);
27
39
  const failed = settled.flatMap((r) => r.status === "rejected" ? [r.reason] : []);
28
40
  if (failed.length === settled.length) throw failed.length === 1 ? failed[0] : new AggregateError(failed, "no rewards source answered");
@@ -32,29 +44,5 @@ var RewardsFanOut = class extends require_onchain_base_MultichainConstruct.Multi
32
44
  });
33
45
  }
34
46
  };
35
- async function readClaimed(sdk, user, { proofs }, blockNumber) {
36
- const onChain = proofs.filter((p) => p.chainId === sdk.chainId);
37
- if (onChain.length === 0) return /* @__PURE__ */ new Map();
38
- const claimed = await sdk.client.multicall({
39
- contracts: onChain.map((p) => ({
40
- address: p.contractAddress,
41
- abi: require_rewards_turtle_api.turtleStreamAbi,
42
- functionName: "getClaimedRewards",
43
- args: [user]
44
- })),
45
- allowFailure: false,
46
- blockNumber
47
- });
48
- return new Map(onChain.map((p, i) => [p.streamId, claimed[i]]));
49
- }
50
- /**
51
- * Every claimable reward a wallet holds — Merkl campaigns and the Gearbox
52
- * organisation's Turtle streams — across the chains the handle carries.
53
- * A chain is `status: "error"` only when every source failed on it; a chain
54
- * with nothing to claim is a `"success"` with no rows.
55
- **/
56
- async function getRewardsMultichain({ sdk, ...props }) {
57
- return new RewardsFanOut(sdk).list(props);
58
- }
59
47
  //#endregion
60
- exports.getRewardsMultichain = getRewardsMultichain;
48
+ exports.RewardsService = RewardsService;
@@ -2,7 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let viem = require("viem");
3
3
  //#region src/rewards/errors.ts
4
4
  /**
5
- * Thrown when none of Merkl's domains answered for a chain.
5
+ * Thrown when Merkl did not answer for a chain.
6
6
  *
7
7
  * Everything a reader needs is in the message: this error travels to consumers
8
8
  * inside a chain's {@link ChainFailed} metadata, where it is typed `unknown`
@@ -11,10 +11,10 @@ let viem = require("viem");
11
11
  var MerklRequestFailedError = class extends viem.BaseError {
12
12
  name = "MerklRequestFailedError";
13
13
  chainId;
14
- constructor(chainId, path, attempts) {
14
+ constructor(chainId, path, cause) {
15
15
  super(`Merkl could not be reached for chain ${chainId}.`, {
16
- cause: attempts.find(([, c]) => c instanceof Error)?.[1],
17
- metaMessages: attempts.map(([domain, cause]) => `${domain}${path} — ${describe(cause)}`)
16
+ cause: cause instanceof Error ? cause : void 0,
17
+ metaMessages: [`${path} — ${describe(cause)}`]
18
18
  });
19
19
  this.chainId = chainId;
20
20
  }
@@ -0,0 +1,88 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_onchain_utils_AddressMap = require("../onchain/utils/AddressMap.js");
3
+ const require_onchain_utils_formatter = require("../onchain/utils/formatter.js");
4
+ require("../onchain/index.js");
5
+ let viem = require("viem");
6
+ //#region src/rewards/helpers.ts
7
+ /**
8
+ * The pool a source names, and its share token. A v3.1 pool is its own
9
+ * ERC-4626 share token, so its address is the only token a campaign can name;
10
+ * the `AddressMap` owns the casing a source writes it in.
11
+ */
12
+ function resolvePool(sdk) {
13
+ const pools = require_onchain_utils_AddressMap.AddressMap.fromMappedArray(sdk.marketRegister.pools.map(({ pool }) => pool.address), (address) => address);
14
+ return (address) => {
15
+ if (!address || !(0, viem.isAddress)(address, { strict: false })) return void 0;
16
+ const pool = pools.get(address);
17
+ const poolToken = pool && sdk.tokensMeta.getToken(pool);
18
+ return pool && poolToken ? {
19
+ pool,
20
+ poolToken
21
+ } : void 0;
22
+ };
23
+ }
24
+ /** Sources send amounts as free-form strings, which `toBigInt` throws on. */
25
+ function parseAmount(value) {
26
+ if (value === void 0) return void 0;
27
+ try {
28
+ return require_onchain_utils_formatter.toBigInt(value || 0);
29
+ } catch {
30
+ return;
31
+ }
32
+ }
33
+ /** What is left of `total` after `claimed`, when anything is. */
34
+ function remaining(total, claimed) {
35
+ if (total === void 0 || claimed === void 0) return void 0;
36
+ return total > claimed ? total - claimed : void 0;
37
+ }
38
+ /**
39
+ * An incentive token is rarely protocol collateral, so the registry usually
40
+ * has no entry for it — and both sources always name it.
41
+ */
42
+ function toRewardToken(sdk, address, named) {
43
+ return sdk.tokensMeta.getToken(address) ?? {
44
+ chainId: sdk.chainId,
45
+ address,
46
+ symbol: named.symbol,
47
+ name: named.name ?? named.symbol,
48
+ decimals: named.decimals || 18
49
+ };
50
+ }
51
+ /** A missing or unparsable price is `null`. */
52
+ function toPricedAmount(token, value, price) {
53
+ const usd = Number(price ?? NaN);
54
+ return {
55
+ token,
56
+ value,
57
+ valueUsd: Number.isFinite(usd) ? Number((0, viem.formatUnits)(value, token.decimals)) * usd : null
58
+ };
59
+ }
60
+ /**
61
+ * One reward per pool and incentive token, at the last part's price. Amounts
62
+ * are summed raw and priced once at the end — pricing each part and adding the results would round
63
+ * every one of them.
64
+ */
65
+ function mergeRewards(sdk, source, parts) {
66
+ const merged = /* @__PURE__ */ new Map();
67
+ for (const part of parts) {
68
+ const key = `${part.pool}_${part.token.address}`;
69
+ const seen = merged.get(key);
70
+ merged.set(key, seen ? {
71
+ ...part,
72
+ value: seen.value + part.value
73
+ } : part);
74
+ }
75
+ return [...merged.values()].map(({ pool, poolToken, token, value, price }) => ({
76
+ source,
77
+ chainId: sdk.chainId,
78
+ pool,
79
+ poolToken,
80
+ amount: toPricedAmount(token, value, price)
81
+ }));
82
+ }
83
+ //#endregion
84
+ exports.mergeRewards = mergeRewards;
85
+ exports.parseAmount = parseAmount;
86
+ exports.remaining = remaining;
87
+ exports.resolvePool = resolvePool;
88
+ exports.toRewardToken = toRewardToken;
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rewards_errors = require("./errors.js");
3
- const require_rewards_multichain = require("./multichain.js");
3
+ const require_rewards_RewardsService = require("./RewardsService.js");
4
4
  exports.MerklRequestFailedError = require_rewards_errors.MerklRequestFailedError;
5
+ exports.RewardsService = require_rewards_RewardsService.RewardsService;
5
6
  exports.TurtleRequestFailedError = require_rewards_errors.TurtleRequestFailedError;
6
- exports.getRewardsMultichain = require_rewards_multichain.getRewardsMultichain;
@@ -1,45 +1,34 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_rewards_errors = require("./errors.js");
3
3
  //#region src/rewards/merkl-api.ts
4
- /**
5
- * Merkl's own host and the Angle mirror, tried in this order.
6
- */
7
- const MERKL_DOMAINS = ["https://api.merkl.xyz", "https://api-merkl.angle.money"];
4
+ const MERKL_API_URL = "https://api.merkl.xyz";
8
5
  const MERKL_API_KEY_HEADER = "X-API-Key";
9
6
  /**
10
- * Per-attempt budget. Merkl has no timeout of its own, and a hung connection
11
- * would otherwise stall its leg of a fan-out for as long as the socket lives.
7
+ * Merkl has no timeout of its own, and a hung connection would otherwise stall
8
+ * its leg of a fan-out for as long as the socket lives.
12
9
  */
13
- const ATTEMPT_TIMEOUT = 1e4;
10
+ const TIMEOUT = 1e4;
14
11
  /**
15
12
  * The wallet's raw Merkl rewards on one chain.
16
13
  *
17
- * Rejects with {@link MerklRequestFailedError} when neither domain answers, so
18
- * a caller can tell an unreachable Merkl from a wallet with nothing to claim.
19
- * A non-2xx counts as no answer and moves to the next domain: it carries no
20
- * rewards either way, and treating it as success would report emptiness that
21
- * was never established.
14
+ * Rejects with {@link MerklRequestFailedError} when Merkl does not answer, so a
15
+ * caller can tell an unreachable Merkl from a wallet with nothing to claim. A
16
+ * non-2xx counts as no answer: it carries no rewards either way, and treating
17
+ * it as success would report emptiness that was never established.
22
18
  */
23
19
  async function fetchMerklUserRewards({ chainId, user, apiKey }) {
24
20
  const path = `/v4/users/${user}/rewards?chainId=${chainId}`;
25
- const headers = apiKey ? { [MERKL_API_KEY_HEADER]: apiKey } : void 0;
26
- const attempts = [];
27
- for (const domain of MERKL_DOMAINS) try {
28
- const response = await fetch(`${domain}${path}`, {
29
- headers,
30
- signal: AbortSignal.timeout(ATTEMPT_TIMEOUT)
21
+ try {
22
+ const response = await fetch(`${MERKL_API_URL}${path}`, {
23
+ headers: apiKey ? { [MERKL_API_KEY_HEADER]: apiKey } : void 0,
24
+ signal: AbortSignal.timeout(TIMEOUT)
31
25
  });
32
- if (!response.ok) {
33
- attempts.push([domain, /* @__PURE__ */ new Error(`answered ${response.status}`)]);
34
- continue;
35
- }
26
+ if (!response.ok) throw new Error(`answered ${response.status}`);
36
27
  return await response.json();
37
28
  } catch (error) {
38
- attempts.push([domain, error]);
29
+ throw new require_rewards_errors.MerklRequestFailedError(chainId, path, error);
39
30
  }
40
- throw new require_rewards_errors.MerklRequestFailedError(chainId, path, attempts);
41
31
  }
42
32
  //#endregion
43
33
  exports.MERKL_API_KEY_HEADER = MERKL_API_KEY_HEADER;
44
- exports.MERKL_DOMAINS = MERKL_DOMAINS;
45
34
  exports.fetchMerklUserRewards = fetchMerklUserRewards;
@@ -1,86 +1,30 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_onchain_utils_AddressMap = require("../onchain/utils/AddressMap.js");
3
- const require_onchain_utils_bigint_math = require("../onchain/utils/bigint-math.js");
4
- const require_onchain_utils_formatter = require("../onchain/utils/formatter.js");
5
- require("../onchain/index.js");
2
+ const require_rewards_helpers = require("./helpers.js");
6
3
  let viem = require("viem");
7
4
  //#region src/rewards/toMerklRewards.ts
8
5
  /**
9
- * Merkl's answer for one chain, turned into rows of the read model.
6
+ * Merkl's answer for one chain, one reward per pool and incentive token.
7
+ * A breakdown Merkl cannot be read from is skipped rather than thrown on: it
8
+ * must not sink the chain, which the caller would then be told is unreachable.
10
9
  */
11
10
  function toMerklRewards(sdk, response) {
12
- const poolByItsToken = require_onchain_utils_AddressMap.AddressMap.fromMappedArray(sdk.marketRegister.pools.map(({ pool }) => pool.address), (address) => address);
13
- const claimable = /* @__PURE__ */ new Map();
14
- for (const chainRewards of response) for (const reward of chainRewards.rewards) {
15
- if (!(0, viem.isAddress)(reward.token.address, { strict: false })) continue;
16
- const rewardTokenAddress = (0, viem.getAddress)(reward.token.address);
17
- for (const reason of reward.breakdowns) {
18
- const poolTokenAddress = (reason.reason || "").split("_").find((part) => part.startsWith("0x")) ?? "";
19
- if (!(0, viem.isAddress)(poolTokenAddress, { strict: false })) continue;
20
- const pool = poolByItsToken.get(poolTokenAddress);
21
- if (!pool) continue;
22
- const amounts = toAmounts(reason);
23
- if (!amounts) continue;
24
- const amount = require_onchain_utils_bigint_math.BigIntMath.max(amounts.total - amounts.claimed, 0n);
25
- if (amount === 0n) continue;
26
- const key = `${pool}_${rewardTokenAddress}`;
27
- const seen = claimable.get(key);
28
- if (seen) {
29
- seen.value += amount;
30
- continue;
31
- }
32
- const poolToken = sdk.tokensMeta.getToken(pool);
33
- if (!poolToken) continue;
34
- claimable.set(key, {
35
- chainId: sdk.chainId,
36
- pool,
37
- poolToken,
38
- token: toRewardToken(sdk, rewardTokenAddress, reward.token),
39
- value: amount,
40
- price: reward.token.price
41
- });
42
- }
43
- }
44
- return [...claimable.values()].map(toReward);
45
- }
46
- /**
47
- * `toBigInt` throws on anything `BigInt()` cannot parse, and Merkl's amounts
48
- * are free-form strings.
49
- */
50
- function toAmounts(reason) {
51
- try {
52
- return {
53
- total: require_onchain_utils_formatter.toBigInt(reason.amount || 0),
54
- claimed: require_onchain_utils_formatter.toBigInt(reason.claimed || 0)
55
- };
56
- } catch {
57
- return;
58
- }
59
- }
60
- function toReward({ price, token, value, ...rest }) {
61
- return {
62
- ...rest,
63
- source: "merkl",
64
- amount: {
65
- token,
66
- value,
67
- valueUsd: price === void 0 ? null : Number((0, viem.formatUnits)(value, token.decimals)) * price
68
- }
69
- };
70
- }
71
- /**
72
- * An incentive token is rarely protocol collateral, so the registry usually
73
- * has no entry for it — and both sources always name it.
74
- */
75
- function toRewardToken(sdk, address, named) {
76
- return sdk.tokensMeta.getToken(address) ?? {
77
- chainId: sdk.chainId,
78
- address,
79
- symbol: named.symbol,
80
- name: named.name ?? named.symbol,
81
- decimals: named.decimals || 18
82
- };
11
+ const poolOf = require_rewards_helpers.resolvePool(sdk);
12
+ const parts = response.flatMap(({ rewards }) => rewards).flatMap(({ token, breakdowns }) => {
13
+ if (!(0, viem.isAddress)(token.address, { strict: false })) return [];
14
+ const rewardToken = require_rewards_helpers.toRewardToken(sdk, (0, viem.getAddress)(token.address), token);
15
+ return breakdowns.flatMap(({ reason, amount, claimed }) => {
16
+ const pool = poolOf(reason?.split("_").find((p) => p.startsWith("0x")));
17
+ const value = require_rewards_helpers.remaining(require_rewards_helpers.parseAmount(amount), require_rewards_helpers.parseAmount(claimed));
18
+ if (!pool || !value) return [];
19
+ return [{
20
+ ...pool,
21
+ token: rewardToken,
22
+ value,
23
+ price: token.price
24
+ }];
25
+ });
26
+ });
27
+ return require_rewards_helpers.mergeRewards(sdk, "merkl", parts);
83
28
  }
84
29
  //#endregion
85
30
  exports.toMerklRewards = toMerklRewards;
86
- exports.toRewardToken = toRewardToken;
@@ -1,77 +1,61 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_onchain_utils_AddressMap = require("../onchain/utils/AddressMap.js");
3
- const require_onchain_utils_bigint_math = require("../onchain/utils/bigint-math.js");
4
- const require_onchain_utils_formatter = require("../onchain/utils/formatter.js");
5
- require("../onchain/index.js");
6
- const require_rewards_toMerklRewards = require("./toMerklRewards.js");
2
+ const require_rewards_helpers = require("./helpers.js");
7
3
  let viem = require("viem");
8
4
  //#region src/rewards/toTurtleRewards.ts
9
5
  /**
10
- * Rows for the streams targeting a pool on the SDK's chain, merged per pool
11
- * and incentive. A token row is what is committed and not yet claimed.
6
+ * The streams targeting a pool on the SDK's chain, merged per pool and
7
+ * incentive. A token reward is what is committed and not yet claimed.
12
8
  */
13
9
  function toTurtleRewards(sdk, { streams, proofs }, claimed) {
14
- const pools = require_onchain_utils_AddressMap.AddressMap.fromMappedArray(sdk.marketRegister.pools.map(({ pool }) => pool.address), (address) => address);
15
- const proofByStream = new Map(proofs.map((p) => [p.streamId, p]));
16
- const rows = /* @__PURE__ */ new Map();
17
- for (const { streamId, snapshots, stream } of streams) {
18
- const target = stream.customArgs.targetToken;
19
- if (!target || Number(target.chain.chainId) !== sdk.chainId) continue;
20
- if (!(0, viem.isAddress)(target.address, { strict: false })) continue;
21
- const pool = pools.get(target.address);
22
- const poolToken = pool && sdk.tokensMeta.getToken(pool);
23
- if (!pool || !poolToken) continue;
24
- const base = {
10
+ const poolOf = require_rewards_helpers.resolvePool(sdk);
11
+ const committed = new Map(proofs.map((p) => [p.streamId, p.amount]));
12
+ const targeted = streams.flatMap((stream) => {
13
+ const target = stream.stream.customArgs.targetToken;
14
+ if (Number(target?.chain.chainId) !== sdk.chainId) return [];
15
+ const pool = poolOf(target?.address);
16
+ return pool ? [{
17
+ stream,
18
+ ...pool
19
+ }] : [];
20
+ });
21
+ const tokens = targeted.flatMap(({ stream, pool, poolToken }) => {
22
+ const { point, rewardToken, lastSnapshot } = stream.stream;
23
+ if (point || !rewardToken || !(0, viem.isAddress)(rewardToken.address, { strict: false })) return [];
24
+ const value = require_rewards_helpers.remaining(require_rewards_helpers.parseAmount(committed.get(stream.streamId)), claimed.get(stream.streamId));
25
+ if (!value) return [];
26
+ return [{
27
+ pool,
28
+ poolToken,
29
+ token: require_rewards_helpers.toRewardToken(sdk, (0, viem.getAddress)(rewardToken.address), rewardToken),
30
+ value,
31
+ price: lastSnapshot?.rewardTokenPrice
32
+ }];
33
+ });
34
+ return [...require_rewards_helpers.mergeRewards(sdk, "turtle", tokens), ...toPoints(sdk, targeted)];
35
+ }
36
+ /** Points accrue per snapshot; the last one holds the running total. */
37
+ function toPoints(sdk, targeted) {
38
+ const merged = /* @__PURE__ */ new Map();
39
+ for (const { stream, pool, poolToken } of targeted) {
40
+ const { point } = stream.stream;
41
+ const raw = require_rewards_helpers.parseAmount(stream.snapshots.at(-1)?.rewardsAccumulated);
42
+ if (!point || !raw) continue;
43
+ const key = `${pool}_${point.id}`;
44
+ const value = (merged.get(key)?.points.value ?? 0) + Number((0, viem.formatUnits)(raw, point.decimals));
45
+ merged.set(key, {
25
46
  source: "turtle",
26
47
  chainId: sdk.chainId,
27
48
  pool,
28
- poolToken
29
- };
30
- const { point, rewardToken } = stream;
31
- if (point) {
32
- const value = parse(snapshots.at(-1)?.rewardsAccumulated);
33
- if (!value) continue;
34
- const key = `${pool}_${point.id}`;
35
- const seen = rows.get(key);
36
- rows.set(key, {
37
- ...base,
38
- points: {
39
- id: point.id,
40
- name: point.name,
41
- multiplier: null,
42
- value: (seen?.points.value ?? 0) + Number((0, viem.formatUnits)(value, point.decimals))
43
- }
44
- });
45
- continue;
46
- }
47
- if (!rewardToken || !(0, viem.isAddress)(rewardToken.address, { strict: false })) continue;
48
- const committed = parse(proofByStream.get(streamId)?.amount);
49
- const done = claimed.get(streamId);
50
- if (committed === void 0 || done === void 0) continue;
51
- const value = require_onchain_utils_bigint_math.BigIntMath.max(committed - done, 0n);
52
- if (value === 0n) continue;
53
- const token = require_rewards_toMerklRewards.toRewardToken(sdk, (0, viem.getAddress)(rewardToken.address), rewardToken);
54
- const key = `${pool}_${token.address}`;
55
- const total = (rows.get(key)?.amount.value ?? 0n) + value;
56
- const price = Number(stream.lastSnapshot?.rewardTokenPrice ?? NaN);
57
- rows.set(key, {
58
- ...base,
59
- amount: {
60
- token,
61
- value: total,
62
- valueUsd: Number.isFinite(price) ? Number((0, viem.formatUnits)(total, token.decimals)) * price : null
49
+ poolToken,
50
+ points: {
51
+ id: point.id,
52
+ name: point.name,
53
+ multiplier: null,
54
+ value
63
55
  }
64
56
  });
65
57
  }
66
- return [...rows.values()];
67
- }
68
- function parse(amount) {
69
- if (amount === void 0) return void 0;
70
- try {
71
- return require_onchain_utils_formatter.toBigInt(amount);
72
- } catch {
73
- return;
74
- }
58
+ return [...merged.values()];
75
59
  }
76
60
  //#endregion
77
61
  exports.toTurtleRewards = toTurtleRewards;
@@ -45,6 +45,23 @@ async function get(path, apiKey) {
45
45
  throw new require_rewards_errors.TurtleRequestFailedError(path, error);
46
46
  }
47
47
  }
48
+ /** What each of the wallet's streams on the chain has paid out, at the latest block. */
49
+ async function readTurtleClaimed(sdk, user, { proofs }) {
50
+ const onChain = proofs.filter((p) => p.chainId === sdk.chainId);
51
+ if (onChain.length === 0) return /* @__PURE__ */ new Map();
52
+ const claimed = await sdk.client.multicall({
53
+ contracts: onChain.map((p) => ({
54
+ address: p.contractAddress,
55
+ abi: turtleStreamAbi,
56
+ functionName: "getClaimedRewards",
57
+ args: [user]
58
+ })),
59
+ allowFailure: false,
60
+ blockTag: "latest"
61
+ });
62
+ return new Map(onChain.map((p, i) => [p.streamId, claimed[i]]));
63
+ }
48
64
  //#endregion
49
65
  exports.fetchTurtleWalletRewards = fetchTurtleWalletRewards;
66
+ exports.readTurtleClaimed = readTurtleClaimed;
50
67
  exports.turtleStreamAbi = turtleStreamAbi;
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
@@ -231,7 +231,7 @@ var PrepareApi = class extends require_onchain_base_MultichainConstruct.Multicha
231
231
  const sdk = await this.#chain(strategy.chainId);
232
232
  const at = stateBlock(sdk);
233
233
  Object.assign(state, marketOf(sdk, strategy.creditManager));
234
- const targetToken = params.targetToken ?? sdk.marketRegister.findCreditManager(strategy.creditManager).strategyTargetCollateral;
234
+ const targetToken = params.targetToken ?? sdk.marketRegister.findCreditManager(strategy.creditManager).strategy?.targetCollateral;
235
235
  if (!targetToken) return refused(require_model_errors_prepare_errors.noStrategyTargetCollateral(strategy.creditManager), state);
236
236
  const reused = await reusable(sdk, strategy, params.creditAccount);
237
237
  if (reused && "error" in reused) return refused(reused.error, state);
@@ -33,7 +33,7 @@ function calcLeverageBand({ sdk, creditManager, collateral, targetHF }) {
33
33
  const found = resolveCreditManager(sdk, creditManager);
34
34
  if (!found) return;
35
35
  const { suite, market } = found;
36
- const target = suite.strategyTargetCollateral;
36
+ const target = suite.strategy?.targetCollateral;
37
37
  if (!target) return;
38
38
  const ceiling = suite.creditManager.maxLeverage(target, targetHF);
39
39
  const underlying = market.pool.underlying;
@@ -19,17 +19,19 @@ import { collateralValuation } from "./collateral-valuation.js";
19
19
  * Collateral is valued the way the transaction will be judged — at safe
20
20
  * prices, under its liquidation threshold, capped by the quota the borrow
21
21
  * buys for it, all of which is {@link collateralValuation}'s business. The ceiling
22
- * is then held to what the market will actually lend: the pool's free
23
- * liquidity, the manager's own allowance and the facade's `maxDebt`, whichever
24
- * binds first.
22
+ * is then held to what the market will actually lend,
23
+ * {@link CreditSuite.maxBorrowAmount}: the pool's free liquidity, the
24
+ * manager's own allowance and the facade's `maxDebt`, whichever binds first.
25
25
  *
26
- * The facade's `minDebt` is deliberately not applied. It is a floor, and a
27
- * ceiling answered as `0n` because the collateral is too small for this market
28
- * would tell a form nothing about what it is holding — the number a user needs
29
- * to see is the one they are short of. Collateral that carries something
30
- * therefore answers with it, whether or not the market would lend that little;
31
- * a loan under the floor is refused by `borrow` itself, with `debtOutOfRange`
32
- * naming both ends.
26
+ * The facade's `minDebt` is not applied to the collateral's own ceiling. It
27
+ * is a floor, and a ceiling answered as `0n` because the collateral is too
28
+ * small for this market would tell a form nothing about what it is holding —
29
+ * the number a user needs to see is the one they are short of. Collateral
30
+ * that carries something therefore answers with it, whether or not the market
31
+ * would lend that little; a loan under the floor is refused by `borrow`
32
+ * itself, with `debtOutOfRange` naming both ends. A market whose own capacity
33
+ * is under `minDebt` is different: the answer is `0n`, because no loan of any
34
+ * size exists there.
33
35
  *
34
36
  * Nothing is fetched or simulated — the account does not exist yet and every
35
37
  * input is loaded market state, so a form can call this on each keystroke.
@@ -79,7 +81,9 @@ function maxBorrow(props) {
79
81
  const weighted = valuation.checkedUsd(holding) * valuation.lt(collateralToken);
80
82
  const backed = quotas.some((q) => eq(q.token, collateralToken)) ? BigIntMath.min(valuation.quotaValue(holding), weighted) : weighted;
81
83
  if (backed <= 0n) return 0n;
82
- const ceiling = BigIntMath.min(priceOracle.safeConvertFromUSD(underlying, backed / targetHF).value, suite.maxBorrowAmount().amount.value);
84
+ const lends = suite.maxBorrowAmount().amount.value;
85
+ if (lends < suite.creditFacade.minDebt) return 0n;
86
+ const ceiling = BigIntMath.min(priceOracle.safeConvertFromUSD(underlying, backed / targetHF).value, lends);
83
87
  const unwrapsPayout = !!rwaAsset && eq(borrowToken, rwaAsset);
84
88
  return eq(borrowToken, underlying) ? ceiling : unwrapsPayout ? toTargetDecimals(ceiling, underlying, borrowToken, sdk) : priceOracle.safeConvert(underlying, borrowToken, ceiling).value;
85
89
  }
@@ -1,6 +1,7 @@
1
1
  import "../../../constants/math.js";
2
2
  import "../../../constants/index.js";
3
3
  import { calcMaxLeverage } from "../../../market/math.js";
4
+ import { CreditSuiteStrategy } from "../../../market/credit/CreditSuiteStrategy.js";
4
5
  import { CreditSuite } from "../../../market/credit/CreditSuite.js";
5
6
  import { PositionsService } from "../../../positions/PositionsService.js";
6
7
  import { TestPriceOracle } from "../../../market/oracle/TestPriceOracle.mock.js";
@@ -189,8 +190,10 @@ function buildMockSdk(args) {
189
190
  const strategyName = strategyTargetCollateral ? `${tokenOf(strategyTargetCollateral).symbol} / ${underlyingToken.symbol}` : void 0;
190
191
  const creditManagerSuite = {
191
192
  name: "TestCreditManager",
192
- strategyName,
193
193
  underlyingToken,
194
+ get strategy() {
195
+ return strategyTargetCollateral ? new CreditSuiteStrategy(creditManagerSuite, strategyTargetCollateral) : void 0;
196
+ },
194
197
  accountTargetCollateral: () => strategyTargetCollateral ? tokenOf(strategyTargetCollateral) : null,
195
198
  accountStrategyName: () => strategyName ?? underlyingToken.symbol,
196
199
  liquidationFees: () => MOCK_LIQUIDATION_FEES,
@@ -219,7 +222,6 @@ function buildMockSdk(args) {
219
222
  market,
220
223
  isPaused: facadePaused || poolPaused,
221
224
  forbiddenTokens: [...forbidden],
222
- strategyTargetCollateral,
223
225
  isExpired: expirationDate > 0 && expirationDate < (args.timestamp ?? 0)
224
226
  };
225
227
  const routeCalls = (tokenIn, tokenOut) => {
@@ -356,6 +358,7 @@ function buildMockSdk(args) {
356
358
  }
357
359
  };
358
360
  Object.assign(sdk, { positions: new PositionsService(sdk) });
361
+ Object.assign(creditManagerSuite, { sdk });
359
362
  return sdk;
360
363
  }
361
364
  /** A full account payload carrying exactly what the slice builder reads back. */
@@ -96,8 +96,7 @@ const chains = {
96
96
  "0x89014edc549ffa5c5b6e859b1496731bd035c247": "0x31454faa1daa04cacf59a6bd37681da9160d092a",
97
97
  "0x3b7ab1f4fee570933b24b202de90ffda82f6cae0": "0x31454faa1daa04cacf59a6bd37681da9160d092a",
98
98
  "0x721798d8ccf31ae75c12db82fa72b3806759cbc9": "0x31454faa1daa04cacf59a6bd37681da9160d092a",
99
- "0xd7273d9594ac88f993eda9773041e621633acea0": "0x1a711a5bc48b5c1352c1882fa65dc14b5b9e829d",
100
- "0x34442ca47435e90b80d835aab9737166e76d9962": "0x403cc0d2694ec2639101f32b146b90d766461ce9"
99
+ "0xd7273d9594ac88f993eda9773041e621633acea0": "0x1a711a5bc48b5c1352c1882fa65dc14b5b9e829d"
101
100
  }),
102
101
  sunsetPools: new AddressSet([
103
102
  "0xF791Ecc5F2472637eac9DFe3f7894C0B32C32bDf",