@scallop-io/sui-scallop-sdk 3.0.2 → 4.1.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.
- package/dist/Logger-Cg2iFdpH.d.ts +8 -0
- package/dist/builder.cjs +22 -0
- package/dist/builder.d.cts +28 -0
- package/dist/builder.d.ts +28 -0
- package/dist/builder.js +4 -0
- package/dist/chunk-33AK5RWM.js +15 -0
- package/dist/chunk-6UQ4NWEU.js +3 -0
- package/dist/chunk-DIC6P5HB.js +11 -0
- package/dist/chunk-S34VOEZN.js +3 -0
- package/dist/chunk-VTGQACWU.js +6 -0
- package/dist/client.cjs +21 -0
- package/dist/client.d.cts +401 -0
- package/dist/client.d.ts +401 -0
- package/dist/client.js +5 -0
- package/dist/errors.cjs +10 -0
- package/dist/errors.d.cts +32 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.js +1 -0
- package/dist/index.cjs +51 -34
- package/dist/index.d.cts +130 -3145
- package/dist/index.d.ts +130 -3145
- package/dist/index.js +8 -12
- package/dist/logger.cjs +6 -0
- package/dist/logger.d.cts +7 -0
- package/dist/logger.d.ts +7 -0
- package/dist/logger.js +1 -0
- package/dist/query-DkitepDi.d.ts +3305 -0
- package/dist/query.cjs +19 -0
- package/dist/query.d.cts +9 -0
- package/dist/query.d.ts +9 -0
- package/dist/query.js +3 -0
- package/dist/types.cjs +2 -0
- package/dist/types.d.cts +90 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.js +1 -0
- package/package.json +98 -56
- package/src/constants/api.ts +2 -0
- package/src/constants/coinType.ts +2 -0
- package/src/constants/common.ts +0 -7
- package/src/constants/index.ts +2 -0
- package/src/constants/queryKeys.ts +14 -7
- package/src/constants/testAddress.ts +1 -0
- package/src/datasources/api.ts +34 -0
- package/src/datasources/indexer.ts +8 -0
- package/src/datasources/onchain.ts +121 -0
- package/src/entries/builder.ts +5 -0
- package/src/entries/client.ts +3 -0
- package/src/entries/errors.ts +2 -0
- package/src/entries/index.ts +17 -0
- package/src/entries/logger.ts +2 -0
- package/src/entries/query.ts +6 -0
- package/src/entries/types.ts +2 -0
- package/src/errors/ScallopConfigError.ts +8 -0
- package/src/errors/ScallopError.ts +30 -0
- package/src/errors/ScallopIndexerError.ts +8 -0
- package/src/errors/ScallopParseError.ts +8 -0
- package/src/errors/ScallopRpcError.ts +8 -0
- package/src/errors/ScallopTransactionBuildError.ts +8 -0
- package/src/errors/index.ts +6 -0
- package/src/logger/Logger.ts +6 -0
- package/src/logger/consoleLogger.ts +13 -0
- package/src/logger/index.ts +3 -0
- package/src/logger/noopLogger.ts +8 -0
- package/src/mappers/index.ts +1 -0
- package/src/mappers/moveTypeMapper.ts +48 -0
- package/src/models/index.ts +8 -10
- package/src/models/interface.ts +9 -9
- package/src/models/scallop.ts +9 -16
- package/src/models/scallopAddress/const.ts +416 -0
- package/src/models/scallopAddress/index.ts +188 -0
- package/src/models/scallopAddress/types.ts +212 -0
- package/src/models/{scallopBuilder.ts → scallopBuilder/index.ts} +65 -29
- package/src/models/scallopBuilder/types.ts +37 -0
- package/src/models/{scallopClient.ts → scallopClient/index.ts} +157 -598
- package/src/models/scallopClient/types.ts +25 -0
- package/src/models/scallopConstants/config/AddressConfigSource.ts +13 -0
- package/src/models/scallopConstants/config/ConfigValidator.ts +70 -0
- package/src/models/scallopConstants/config/PoolAddressConfigSource.ts +12 -0
- package/src/models/scallopConstants/config/ScallopConfig.ts +80 -0
- package/src/models/scallopConstants/config/ScallopConfigSnapshot.ts +46 -0
- package/src/models/scallopConstants/config/WhitelistConfigSource.ts +16 -0
- package/src/models/scallopConstants/config/index.ts +6 -0
- package/src/models/scallopConstants/const.ts +19 -0
- package/src/models/scallopConstants/constantsSource.ts +98 -0
- package/src/models/scallopConstants/deriveConstants.ts +150 -0
- package/src/models/scallopConstants/index.ts +257 -0
- package/src/models/scallopConstants/loadConstantsState.ts +135 -0
- package/src/models/scallopConstants/types.ts +77 -0
- package/src/models/scallopConstants/utils.ts +69 -0
- package/src/models/scallopQuery/index.ts +1279 -0
- package/src/models/scallopQuery/types.ts +9 -0
- package/src/models/scallopQuery/utils.ts +13 -0
- package/src/models/scallopUtils/index.ts +505 -0
- package/src/models/scallopUtils/types.ts +30 -0
- package/src/models/transactionExecutor.ts +122 -0
- package/src/repositories/addressApi/helpers.ts +14 -0
- package/src/repositories/addressApi/index.ts +24 -0
- package/src/repositories/addressApi/schema.ts +0 -0
- package/src/repositories/addressApi/types.ts +177 -0
- package/src/repositories/base.ts +38 -0
- package/src/repositories/borrowIncentive/bcs.ts +11 -0
- package/src/repositories/borrowIncentive/helpers.ts +396 -0
- package/src/repositories/borrowIncentive/index.ts +52 -0
- package/src/{types/query/borrowIncentive.ts → repositories/borrowIncentive/types.ts} +69 -23
- package/src/repositories/borrowIncentive/utils.ts +234 -0
- package/src/repositories/cache.ts +4 -0
- package/src/repositories/coinBalance/helpers.ts +394 -0
- package/src/repositories/coinBalance/index.ts +69 -0
- package/src/repositories/coinBalance/types.ts +33 -0
- package/src/repositories/flashloan/const.ts +3 -0
- package/src/repositories/flashloan/helpers.ts +107 -0
- package/src/repositories/flashloan/index.ts +34 -0
- package/src/repositories/flashloan/types.ts +16 -0
- package/src/repositories/isolatedAssets/bcs.ts +4 -0
- package/src/repositories/isolatedAssets/const.ts +2 -0
- package/src/repositories/isolatedAssets/helpers.ts +91 -0
- package/src/repositories/isolatedAssets/index.ts +42 -0
- package/src/repositories/isolatedAssets/types.ts +35 -0
- package/src/repositories/loyaltyProgram/bcs.ts +7 -0
- package/src/repositories/loyaltyProgram/helpers.ts +117 -0
- package/src/repositories/loyaltyProgram/index.ts +28 -0
- package/src/repositories/loyaltyProgram/schema.ts +18 -0
- package/src/repositories/loyaltyProgram/types.ts +37 -0
- package/src/repositories/market/const.ts +6 -0
- package/src/repositories/market/helpers.ts +740 -0
- package/src/repositories/market/index.ts +137 -0
- package/src/repositories/market/mapper.ts +30 -0
- package/src/repositories/market/types.ts +443 -0
- package/src/repositories/market/utils.ts +285 -0
- package/src/repositories/obligation/helpers.ts +228 -0
- package/src/repositories/obligation/index.ts +51 -0
- package/src/repositories/obligation/types.ts +71 -0
- package/src/repositories/obligation/utils.ts +57 -0
- package/src/repositories/poolAddresses/const.ts +5 -0
- package/src/repositories/poolAddresses/helpers.ts +422 -0
- package/src/repositories/poolAddresses/index.ts +56 -0
- package/src/repositories/poolAddresses/schema.ts +34 -0
- package/src/repositories/poolAddresses/types.ts +69 -0
- package/src/repositories/price/const.ts +1 -0
- package/src/repositories/price/helpers.ts +237 -0
- package/src/repositories/price/index.ts +91 -0
- package/src/repositories/price/schema.ts +24 -0
- package/src/repositories/price/types.ts +56 -0
- package/src/repositories/price/utils.ts +21 -0
- package/src/repositories/referral/bcs.ts +3 -0
- package/src/repositories/referral/helper.ts +24 -0
- package/src/repositories/referral/index.ts +31 -0
- package/src/repositories/referral/types.ts +33 -0
- package/src/repositories/spool/const.ts +1 -0
- package/src/repositories/spool/helpers.ts +471 -0
- package/src/repositories/spool/index.ts +135 -0
- package/src/{types/query/spool.ts → repositories/spool/types.ts} +67 -2
- package/src/repositories/spool/utils.ts +215 -0
- package/src/repositories/types.ts +39 -0
- package/src/repositories/utils.ts +137 -0
- package/src/repositories/veSca/bcs.ts +6 -0
- package/src/repositories/veSca/helpers.ts +341 -0
- package/src/repositories/veSca/index.ts +57 -0
- package/src/repositories/veSca/types.ts +79 -0
- package/src/repositories/veScaLoyaltyProgram/bcs.ts +3 -0
- package/src/repositories/veScaLoyaltyProgram/helpers.ts +135 -0
- package/src/repositories/veScaLoyaltyProgram/index.ts +28 -0
- package/src/repositories/veScaLoyaltyProgram/schema.ts +15 -0
- package/src/repositories/veScaLoyaltyProgram/types.ts +47 -0
- package/src/repositories/wiring/datasources.ts +42 -0
- package/src/repositories/wiring/metadata.ts +292 -0
- package/src/repositories/wiring/registry.ts +194 -0
- package/src/repositories/wiring/source.ts +63 -0
- package/src/repositories/xOracle/bcs.ts +7 -0
- package/src/repositories/xOracle/const.ts +1 -0
- package/src/repositories/xOracle/helpers.ts +304 -0
- package/src/repositories/xOracle/index.ts +45 -0
- package/src/repositories/xOracle/types.ts +96 -0
- package/src/repositories/xOracle/utils.ts +3 -0
- package/src/services/client/BorrowService.ts +229 -0
- package/src/services/client/CollateralService.ts +76 -0
- package/src/services/client/LendingService.ts +75 -0
- package/src/services/client/ReferralService.ts +71 -0
- package/src/services/client/SpoolService.ts +170 -0
- package/src/services/client/VeScaService.ts +142 -0
- package/src/services/client/types.ts +44 -0
- package/src/services/index.ts +8 -0
- package/src/services/query/portfolioCalculations.ts +1171 -0
- package/src/txBuilders/borrowIncentive/index.ts +80 -0
- package/src/txBuilders/borrowIncentive/moveCalls.ts +124 -0
- package/src/txBuilders/borrowIncentive/quick.ts +207 -0
- package/src/txBuilders/context.ts +62 -0
- package/src/txBuilders/core/index.ts +95 -0
- package/src/txBuilders/core/moveCalls.ts +241 -0
- package/src/txBuilders/core/quick.ts +306 -0
- package/src/{builders → txBuilders}/index.ts +35 -9
- package/src/txBuilders/loyaltyProgram/index.ts +78 -0
- package/src/txBuilders/loyaltyProgram/moveCalls.ts +44 -0
- package/src/txBuilders/loyaltyProgram/quick.ts +58 -0
- package/src/txBuilders/manifest.ts +160 -0
- package/src/txBuilders/modules.ts +70 -0
- package/src/{builders → txBuilders}/oracles/index.ts +3 -2
- package/src/{builders → txBuilders}/oracles/pyth.ts +4 -3
- package/src/txBuilders/referral/index.ts +72 -0
- package/src/txBuilders/referral/moveCalls.ts +93 -0
- package/src/txBuilders/referral/quick.ts +57 -0
- package/src/txBuilders/sCoin/index.ts +72 -0
- package/src/txBuilders/sCoin/moveCalls.ts +36 -0
- package/src/txBuilders/sCoin/quick.ts +34 -0
- package/src/txBuilders/spool/index.ts +82 -0
- package/src/txBuilders/spool/moveCalls.ts +79 -0
- package/src/{builders/spoolBuilder.ts → txBuilders/spool/quick.ts} +22 -162
- package/src/txBuilders/utils.ts +36 -0
- package/src/txBuilders/verify.ts +45 -0
- package/src/txBuilders/vesca/index.ts +75 -0
- package/src/txBuilders/vesca/moveCalls.ts +147 -0
- package/src/{builders/vescaBuilder.ts → txBuilders/vesca/quick.ts} +85 -274
- package/src/types/address.ts +4 -178
- package/src/types/builder/borrowIncentive.ts +4 -3
- package/src/types/builder/core.ts +6 -33
- package/src/types/builder/index.ts +5 -1
- package/src/types/builder/loyaltyProgram.ts +4 -3
- package/src/types/builder/modules.ts +49 -0
- package/src/types/builder/referral.ts +5 -4
- package/src/types/builder/sCoin.ts +20 -2
- package/src/types/builder/spool.ts +22 -2
- package/src/types/builder/vesca.ts +4 -12
- package/src/types/constant/index.ts +3 -1
- package/src/types/constant/queryKeys.ts +15 -8
- package/src/types/index.ts +5 -6
- package/src/types/internal/dto.ts +46 -0
- package/src/types/internal/index.ts +2 -0
- package/src/types/internal/move.ts +13 -0
- package/src/types/public/index.ts +21 -0
- package/src/types/query/core.ts +0 -317
- package/src/types/query/index.ts +3 -4
- package/src/types/query/portfolio.ts +2 -15
- package/src/types/repositories/borrowIncentive.ts +12 -0
- package/src/types/repositories/index.ts +9 -0
- package/src/types/repositories/loyaltyProgram.ts +2 -0
- package/src/types/repositories/market.ts +13 -0
- package/src/types/repositories/spool.ts +21 -0
- package/src/types/repositories/veSca.ts +5 -0
- package/src/types/repositories/veScaLoyaltyProgram.ts +2 -0
- package/src/types/sui.ts +0 -1
- package/src/utils/array.ts +7 -0
- package/src/utils/cache.ts +17 -0
- package/src/utils/object.ts +30 -12
- package/src/utils/query.ts +9 -190
- package/src/utils/vesca.ts +0 -8
- package/src/builders/borrowIncentiveBuilder.ts +0 -372
- package/src/builders/coreBuilder.ts +0 -636
- package/src/builders/loyaltyProgramBuilder.ts +0 -142
- package/src/builders/referralBuilder.ts +0 -192
- package/src/builders/sCoinBuilder.ts +0 -124
- package/src/index.ts +0 -13
- package/src/models/scallopAddress.ts +0 -841
- package/src/models/scallopAxios.ts +0 -185
- package/src/models/scallopConstants.ts +0 -380
- package/src/models/scallopIndexer.ts +0 -226
- package/src/models/scallopQuery.ts +0 -974
- package/src/models/scallopQueryClient.ts +0 -29
- package/src/models/scallopSuiKit.ts +0 -480
- package/src/models/scallopUtils.ts +0 -734
- package/src/queries/borrowIncentiveQuery.ts +0 -361
- package/src/queries/borrowLimitQuery.ts +0 -33
- package/src/queries/coreQuery.ts +0 -1173
- package/src/queries/flashloanFeeQuery.ts +0 -92
- package/src/queries/index.ts +0 -15
- package/src/queries/isolatedAssetQuery.ts +0 -103
- package/src/queries/loyaltyProgramQuery.ts +0 -178
- package/src/queries/ownerQuery.ts +0 -32
- package/src/queries/poolAddressesQuery.ts +0 -340
- package/src/queries/portfolioQuery.ts +0 -1149
- package/src/queries/priceQuery.ts +0 -37
- package/src/queries/referralQuery.ts +0 -42
- package/src/queries/sCoinQuery.ts +0 -195
- package/src/queries/spoolQuery.ts +0 -542
- package/src/queries/supplyLimitQuery.ts +0 -32
- package/src/queries/switchboardQuery.ts +0 -65
- package/src/queries/vescaQuery.ts +0 -312
- package/src/queries/xOracleQuery.ts +0 -149
- package/src/types/constant/common.ts +0 -55
- package/src/types/query/loyaltyProgram.ts +0 -12
- package/src/types/query/vesca.ts +0 -27
- package/src/utils/index.ts +0 -7
- package/src/utils/indexer.ts +0 -47
- /package/src/{models → datasources}/rateLimiter.ts +0 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { SuiClientTypes } from '@mysten/sui/client';
|
|
2
|
+
import {
|
|
3
|
+
SupportedOracle,
|
|
4
|
+
XOracleAssetOraclesContext,
|
|
5
|
+
XOracleOnDemandAggContext,
|
|
6
|
+
XOraclePriceUpdatePolicyContext,
|
|
7
|
+
XOracleSwitchboardRegistryContext,
|
|
8
|
+
XOracleUpdatePolicyRulesContext,
|
|
9
|
+
} from './types.js';
|
|
10
|
+
import { queryKeys } from 'src/constants/queryKeys.js';
|
|
11
|
+
import { bcs } from '@mysten/sui/bcs';
|
|
12
|
+
import { prepend0x } from './utils.js';
|
|
13
|
+
import { PricePolicyRulesVecSet } from './bcs.js';
|
|
14
|
+
import { getDynamicFieldOrNull, logError } from '../utils.js';
|
|
15
|
+
import { encodeDynamicFieldNameForV2 } from 'src/utils/dynamicField.js';
|
|
16
|
+
import { ScallopParseError, ScallopRpcError } from 'src/errors/index.js';
|
|
17
|
+
|
|
18
|
+
const SWITCHBOARD_REGISTRY_SCAN_THRESHOLD = 3;
|
|
19
|
+
|
|
20
|
+
const queryUpdatePolicyRules = async (
|
|
21
|
+
ctx: XOracleUpdatePolicyRulesContext,
|
|
22
|
+
vecSetId: string
|
|
23
|
+
): Promise<Record<string, SupportedOracle[]>> => {
|
|
24
|
+
const { onchain, fetchWithCache, metadata } = ctx;
|
|
25
|
+
const { addresses, parseCoinNameFromType } = metadata;
|
|
26
|
+
const limit = 50;
|
|
27
|
+
|
|
28
|
+
const ruleTypeNameToOracleType: Record<string, SupportedOracle> = {
|
|
29
|
+
[`${addresses.pyth.object}::rule::Rule`]: 'pyth',
|
|
30
|
+
[`${addresses.supra.object}::rule::Rule`]: 'supra',
|
|
31
|
+
[`${addresses.switchboard.object}::rule::Rule`]: 'switchboard',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let nextCursor = null;
|
|
35
|
+
let nextPage = true;
|
|
36
|
+
|
|
37
|
+
const results: Record<string, SupportedOracle[]> = {};
|
|
38
|
+
|
|
39
|
+
do {
|
|
40
|
+
const fetchOptions: SuiClientTypes.ListDynamicFieldsOptions = {
|
|
41
|
+
parentId: vecSetId,
|
|
42
|
+
cursor: nextCursor,
|
|
43
|
+
limit,
|
|
44
|
+
//@ts-ignore - Supported on grpc implementation
|
|
45
|
+
include: {
|
|
46
|
+
value: true,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const { dynamicFields, cursor, hasNextPage } = await fetchWithCache({
|
|
50
|
+
queryKey: queryKeys.rpc.getDynamicFields({
|
|
51
|
+
...fetchOptions,
|
|
52
|
+
node: onchain.url,
|
|
53
|
+
}),
|
|
54
|
+
queryFn: () => onchain.client.listDynamicFields(fetchOptions),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
nextCursor = cursor;
|
|
58
|
+
nextPage = hasNextPage;
|
|
59
|
+
|
|
60
|
+
// Process the dynamic fields
|
|
61
|
+
dynamicFields.forEach((field) => {
|
|
62
|
+
// Get coin type
|
|
63
|
+
const coinType = prepend0x(bcs.string().parse(field.name.bcs));
|
|
64
|
+
const coinName = parseCoinNameFromType(coinType);
|
|
65
|
+
// @ts-ignore - value is supported on grpc implementation
|
|
66
|
+
const parsedValue = PricePolicyRulesVecSet.parse(field.value.bcs);
|
|
67
|
+
|
|
68
|
+
if (!results[coinName]) {
|
|
69
|
+
results[coinName] = [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Iterate through parsedValue
|
|
73
|
+
parsedValue.contents.forEach(({ name }) => {
|
|
74
|
+
const oracle = ruleTypeNameToOracleType[prepend0x(name)];
|
|
75
|
+
if (oracle) {
|
|
76
|
+
results[coinName].push(oracle);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
} while (nextPage);
|
|
81
|
+
|
|
82
|
+
return results;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const getAssetOraclesFromOnChain = async (
|
|
86
|
+
ctx: XOracleAssetOraclesContext
|
|
87
|
+
) => {
|
|
88
|
+
const {
|
|
89
|
+
metadata: { addresses, whitelist },
|
|
90
|
+
} = ctx;
|
|
91
|
+
const [primary, secondary] = await Promise.all([
|
|
92
|
+
queryUpdatePolicyRules(
|
|
93
|
+
ctx,
|
|
94
|
+
addresses.oracles.primaryPriceUpdatePolicyVecsetId
|
|
95
|
+
),
|
|
96
|
+
queryUpdatePolicyRules(
|
|
97
|
+
ctx,
|
|
98
|
+
addresses.oracles.secondaryPriceUpdatePolicyVecsetId
|
|
99
|
+
),
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
return [...whitelist.lending.values()].reduce(
|
|
103
|
+
(acc, assetName) => {
|
|
104
|
+
acc[assetName] = {
|
|
105
|
+
primary: primary[assetName] ?? [],
|
|
106
|
+
secondary: secondary[assetName] ?? [],
|
|
107
|
+
};
|
|
108
|
+
return acc;
|
|
109
|
+
},
|
|
110
|
+
{} as Record<
|
|
111
|
+
string,
|
|
112
|
+
{ primary: SupportedOracle[]; secondary: SupportedOracle[] }
|
|
113
|
+
>
|
|
114
|
+
);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Primary/secondary price-update-policy dynamic fields.
|
|
119
|
+
*
|
|
120
|
+
* NOTE: the return shape changed from the legacy `getPriceUpdatePolicies`
|
|
121
|
+
* (which leaked `SuiObjectResponse`). This returns the new-gen client's
|
|
122
|
+
* dynamic-field result (or `null` when the policy rules key isn't present).
|
|
123
|
+
*/
|
|
124
|
+
export const getPriceUpdatePoliciesFromOnChain = async (
|
|
125
|
+
ctx: XOraclePriceUpdatePolicyContext
|
|
126
|
+
) => {
|
|
127
|
+
const { addresses } = ctx.metadata;
|
|
128
|
+
const keyType = `${addresses.xOracleObject}::price_update_policy::PriceUpdatePolicyRulesKey`;
|
|
129
|
+
const nameBcs = bcs
|
|
130
|
+
.struct('TypeName', { dummy_field: bcs.bool() })
|
|
131
|
+
.serialize({ dummy_field: false })
|
|
132
|
+
.toBytes();
|
|
133
|
+
|
|
134
|
+
const [primary, secondary] = await Promise.all([
|
|
135
|
+
getDynamicFieldOrNull(ctx, {
|
|
136
|
+
parentId: addresses.oracles.primaryPriceUpdatePolicyObject,
|
|
137
|
+
name: { type: keyType, bcs: nameBcs },
|
|
138
|
+
}),
|
|
139
|
+
getDynamicFieldOrNull(ctx, {
|
|
140
|
+
parentId: addresses.oracles.secondaryPriceUpdatePolicyObject,
|
|
141
|
+
name: { type: keyType, bcs: nameBcs },
|
|
142
|
+
}),
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
return { primary, secondary };
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Switchboard on-demand aggregator object ids for the given coins, aligned to
|
|
150
|
+
* the input order. Uses the address-API registration when present, else looks
|
|
151
|
+
* the coin up in the on-chain switchboard registry table.
|
|
152
|
+
*/
|
|
153
|
+
export const getOnDemandAggObjectIdsFromOnChain = async (
|
|
154
|
+
ctx: XOracleOnDemandAggContext,
|
|
155
|
+
coinNames: string[]
|
|
156
|
+
): Promise<string[]> => {
|
|
157
|
+
const { addresses, parseCoinType, getSwitchboardAggAddress } = ctx.metadata;
|
|
158
|
+
const registryTableId = addresses.oracles.switchboardRegistryTableId;
|
|
159
|
+
|
|
160
|
+
const missingAgg: Array<{ idx: number; coinName: string }> = [];
|
|
161
|
+
const registeredAggs: Array<string | null> = coinNames.map(
|
|
162
|
+
(coinName, idx) => {
|
|
163
|
+
const registered = getSwitchboardAggAddress(coinName);
|
|
164
|
+
if (registered) return registered;
|
|
165
|
+
missingAgg.push({ idx, coinName });
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
if (missingAgg.length === 0) return registeredAggs as string[];
|
|
171
|
+
|
|
172
|
+
if (missingAgg.length > SWITCHBOARD_REGISTRY_SCAN_THRESHOLD) {
|
|
173
|
+
const missingCoinTypes = new Map<
|
|
174
|
+
string,
|
|
175
|
+
{ idx: number; coinName: string }
|
|
176
|
+
>();
|
|
177
|
+
for (const missing of missingAgg) {
|
|
178
|
+
const coinType = parseCoinType(missing.coinName);
|
|
179
|
+
if (!coinType) {
|
|
180
|
+
throw logError(
|
|
181
|
+
ctx.logger,
|
|
182
|
+
new ScallopParseError(`Invalid coin name: ${missing.coinName}`, {
|
|
183
|
+
context: { coinName: missing.coinName },
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
missingCoinTypes.set(coinType.slice(2), missing);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const scannedAggs = await querySwitchboardRegistryAggs(
|
|
191
|
+
ctx,
|
|
192
|
+
registryTableId,
|
|
193
|
+
missingCoinTypes
|
|
194
|
+
);
|
|
195
|
+
for (const [coinTypeKey, { idx }] of missingCoinTypes) {
|
|
196
|
+
const agg = scannedAggs[coinTypeKey];
|
|
197
|
+
if (!agg) {
|
|
198
|
+
throw logError(
|
|
199
|
+
ctx.logger,
|
|
200
|
+
new ScallopRpcError(
|
|
201
|
+
`No on-demand aggregator found for 0x${coinTypeKey}`,
|
|
202
|
+
{ context: { coinTypeKey } }
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
registeredAggs[idx] = agg;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return registeredAggs as string[];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
await Promise.all(
|
|
213
|
+
missingAgg.map(async ({ idx, coinName }) => {
|
|
214
|
+
const coinType = parseCoinType(coinName);
|
|
215
|
+
if (!coinType) {
|
|
216
|
+
throw logError(
|
|
217
|
+
ctx.logger,
|
|
218
|
+
new ScallopParseError(`Invalid coin name: ${coinName}`, {
|
|
219
|
+
context: { coinName },
|
|
220
|
+
})
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
const result = await getDynamicFieldOrNull(ctx, {
|
|
224
|
+
parentId: registryTableId,
|
|
225
|
+
name: encodeDynamicFieldNameForV2({
|
|
226
|
+
type: '0x1::type_name::TypeName',
|
|
227
|
+
value: { name: coinType.slice(2) },
|
|
228
|
+
}),
|
|
229
|
+
});
|
|
230
|
+
if (!result) {
|
|
231
|
+
throw logError(
|
|
232
|
+
ctx.logger,
|
|
233
|
+
new ScallopRpcError(`No on-demand aggregator found for ${coinType}`, {
|
|
234
|
+
context: { coinType },
|
|
235
|
+
})
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
// The registry value is the aggregator object id (an address).
|
|
239
|
+
// UNVERIFIED: confirm the bcs shape against a live registry entry.
|
|
240
|
+
registeredAggs[idx] = bcs.Address.parse(result.dynamicField.value.bcs);
|
|
241
|
+
})
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
return registeredAggs as string[];
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const parseRegistryCoinTypeKey = (
|
|
248
|
+
field: SuiClientTypes.DynamicFieldEntry
|
|
249
|
+
): string | undefined => {
|
|
250
|
+
try {
|
|
251
|
+
return bcs.struct('TypeName', { name: bcs.string() }).parse(field.name.bcs)
|
|
252
|
+
.name;
|
|
253
|
+
} catch {
|
|
254
|
+
try {
|
|
255
|
+
return bcs.string().parse(field.name.bcs);
|
|
256
|
+
} catch {
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const querySwitchboardRegistryAggs = async (
|
|
263
|
+
ctx: XOracleSwitchboardRegistryContext,
|
|
264
|
+
registryTableId: string,
|
|
265
|
+
missingCoinTypes: ReadonlyMap<string, { idx: number; coinName: string }>
|
|
266
|
+
): Promise<Record<string, string>> => {
|
|
267
|
+
const { onchain, fetchWithCache } = ctx;
|
|
268
|
+
const result: Record<string, string> = {};
|
|
269
|
+
let cursor: string | null | undefined = null;
|
|
270
|
+
let hasNextPage = false;
|
|
271
|
+
|
|
272
|
+
do {
|
|
273
|
+
const options: SuiClientTypes.ListDynamicFieldsOptions = {
|
|
274
|
+
parentId: registryTableId,
|
|
275
|
+
cursor,
|
|
276
|
+
limit: 50,
|
|
277
|
+
// @ts-ignore - Supported on grpc implementation
|
|
278
|
+
include: { value: true },
|
|
279
|
+
};
|
|
280
|
+
const resp = await fetchWithCache({
|
|
281
|
+
queryKey: queryKeys.rpc.getDynamicFields({
|
|
282
|
+
...options,
|
|
283
|
+
node: onchain.url,
|
|
284
|
+
}),
|
|
285
|
+
queryFn: () => onchain.client.listDynamicFields(options),
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
for (const field of resp.dynamicFields) {
|
|
289
|
+
const coinTypeKey = parseRegistryCoinTypeKey(field);
|
|
290
|
+
if (!coinTypeKey || !missingCoinTypes.has(coinTypeKey)) continue;
|
|
291
|
+
// @ts-ignore - value is supported on grpc implementation
|
|
292
|
+
const valueBcs = field.value?.bcs;
|
|
293
|
+
if (valueBcs) {
|
|
294
|
+
result[coinTypeKey] = bcs.Address.parse(valueBcs);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
cursor = resp.cursor;
|
|
299
|
+
hasNextPage =
|
|
300
|
+
resp.hasNextPage && Object.keys(result).length < missingCoinTypes.size;
|
|
301
|
+
} while (hasNextPage);
|
|
302
|
+
|
|
303
|
+
return result;
|
|
304
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { BaseRepository } from '../base.js';
|
|
2
|
+
import { OnChainDataSource } from 'src/datasources/onchain.js';
|
|
3
|
+
import {
|
|
4
|
+
getAssetOraclesFromOnChain,
|
|
5
|
+
getOnDemandAggObjectIdsFromOnChain,
|
|
6
|
+
getPriceUpdatePoliciesFromOnChain,
|
|
7
|
+
} from './helpers.js';
|
|
8
|
+
import {
|
|
9
|
+
XOracleMetadata,
|
|
10
|
+
XOracleRepoParams,
|
|
11
|
+
XOracleRepoContext,
|
|
12
|
+
} from './types.js';
|
|
13
|
+
|
|
14
|
+
export class XOracleRepository extends BaseRepository<
|
|
15
|
+
XOracleRepoContext,
|
|
16
|
+
XOracleMetadata
|
|
17
|
+
> {
|
|
18
|
+
private readonly onchain: OnChainDataSource;
|
|
19
|
+
|
|
20
|
+
constructor({ onchain, ...params }: XOracleRepoParams) {
|
|
21
|
+
super(params);
|
|
22
|
+
this.onchain = onchain;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get context() {
|
|
26
|
+
return { ...this.baseContext, onchain: this.onchain };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Return supported primary and secondary oracles for supported assets
|
|
31
|
+
*/
|
|
32
|
+
getAssetOracles() {
|
|
33
|
+
return getAssetOraclesFromOnChain(this.context);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Primary/secondary price-update-policy dynamic fields. */
|
|
37
|
+
getPriceUpdatePolicies() {
|
|
38
|
+
return getPriceUpdatePoliciesFromOnChain(this.context);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Switchboard on-demand aggregator object ids for the given coins. */
|
|
42
|
+
getOnDemandAggObjectIds(coinNames: string[]) {
|
|
43
|
+
return getOnDemandAggObjectIdsFromOnChain(this.context, coinNames);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { BaseContext, BaseRepoParams } from '../types.js';
|
|
2
|
+
import { OnChainDataSource } from 'src/datasources/onchain.js';
|
|
3
|
+
import { SUPPORTED_ORACLES } from './const.js';
|
|
4
|
+
|
|
5
|
+
export type SupportedOracle = (typeof SUPPORTED_ORACLES)[number];
|
|
6
|
+
|
|
7
|
+
type SupportedOracleAddresses = Record<SupportedOracle, { object: string }>;
|
|
8
|
+
type XOracleAddresses = {
|
|
9
|
+
/** `core.packages.xOracle.object` — used to build the policy-rules key type. */
|
|
10
|
+
xOracleObject: string;
|
|
11
|
+
oracles: {
|
|
12
|
+
primaryPriceUpdatePolicyVecsetId: string;
|
|
13
|
+
secondaryPriceUpdatePolicyVecsetId: string;
|
|
14
|
+
primaryPriceUpdatePolicyObject: string;
|
|
15
|
+
secondaryPriceUpdatePolicyObject: string;
|
|
16
|
+
switchboardRegistryTableId: string;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type XOracleMetadata = {
|
|
21
|
+
addresses: SupportedOracleAddresses & XOracleAddresses;
|
|
22
|
+
whitelist: {
|
|
23
|
+
lending: ReadonlySet<string>;
|
|
24
|
+
};
|
|
25
|
+
parseCoinNameFromType: (type: string) => string;
|
|
26
|
+
parseCoinType: (coinName: string) => string;
|
|
27
|
+
/** Pre-registered switchboard aggregator id for a coin, if the address API
|
|
28
|
+
* carries one (`core.coins.<coin>.oracle.switchboard`). */
|
|
29
|
+
getSwitchboardAggAddress: (coinName: string) => string | undefined;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type XOracleRepoContext = BaseContext & {
|
|
33
|
+
onchain: OnChainDataSource;
|
|
34
|
+
metadata: XOracleMetadata;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type XOracleRepoParams = BaseRepoParams & {
|
|
38
|
+
onchain: OnChainDataSource;
|
|
39
|
+
metadata: XOracleMetadata;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Reads update-policy rule VecSets per coin: paginated on-chain dynamic-field
|
|
44
|
+
* scan + coin-name parsing. Needs the oracle rule package addresses.
|
|
45
|
+
*/
|
|
46
|
+
export type XOracleUpdatePolicyRulesContext = Pick<
|
|
47
|
+
XOracleRepoContext,
|
|
48
|
+
'onchain' | 'fetchWithCache' | 'logger'
|
|
49
|
+
> & {
|
|
50
|
+
metadata: Pick<XOracleMetadata, 'addresses' | 'parseCoinNameFromType'>;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Assembles primary/secondary asset oracles across the lending whitelist.
|
|
55
|
+
* Reads the rule VecSet ids + lending whitelist; delegates per-vecset scans to
|
|
56
|
+
* `queryUpdatePolicyRules` (hence the rule-scan slice on top).
|
|
57
|
+
*/
|
|
58
|
+
export type XOracleAssetOraclesContext = XOracleUpdatePolicyRulesContext & {
|
|
59
|
+
metadata: Pick<
|
|
60
|
+
XOracleMetadata,
|
|
61
|
+
'addresses' | 'parseCoinNameFromType' | 'whitelist'
|
|
62
|
+
>;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Reads the price-update-policy dynamic fields. Needs only the policy object
|
|
67
|
+
* ids from `addresses`; the dynamic-field reads go through
|
|
68
|
+
* `getDynamicFieldOrNull` (hence the `onchain`/`fetchWithCache` slice).
|
|
69
|
+
*/
|
|
70
|
+
export type XOraclePriceUpdatePolicyContext = Pick<
|
|
71
|
+
XOracleRepoContext,
|
|
72
|
+
'onchain' | 'fetchWithCache'
|
|
73
|
+
> & {
|
|
74
|
+
metadata: Pick<XOracleMetadata, 'addresses'>;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Resolves switchboard on-demand aggregator ids for coins. Needs the registry
|
|
79
|
+
* table id + coin-type/aggregator parse helpers, plus the on-chain reads for
|
|
80
|
+
* the registry scan / dynamic-field lookups.
|
|
81
|
+
*/
|
|
82
|
+
export type XOracleOnDemandAggContext = Pick<
|
|
83
|
+
XOracleRepoContext,
|
|
84
|
+
'onchain' | 'fetchWithCache' | 'logger'
|
|
85
|
+
> & {
|
|
86
|
+
metadata: Pick<
|
|
87
|
+
XOracleMetadata,
|
|
88
|
+
'addresses' | 'parseCoinType' | 'getSwitchboardAggAddress'
|
|
89
|
+
>;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** Paginated scan of the switchboard registry table. On-chain reads only. */
|
|
93
|
+
export type XOracleSwitchboardRegistryContext = Pick<
|
|
94
|
+
XOracleRepoContext,
|
|
95
|
+
'onchain' | 'fetchWithCache'
|
|
96
|
+
>;
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import type { TransactionResult } from '@mysten/sui/transactions';
|
|
2
|
+
import type { ClientServiceContext, ClientTxResult } from './types.js';
|
|
3
|
+
import { ScallopTransactionBuildError } from 'src/errors/index.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Application service for borrow-side lifecycle operations:
|
|
7
|
+
* - obligation creation
|
|
8
|
+
* - borrow / repay
|
|
9
|
+
* - supply + auto-stake convenience
|
|
10
|
+
* - borrow-incentive stake / unstake / claim
|
|
11
|
+
*
|
|
12
|
+
* Each method preserves the public ScallopClient signature one-to-one.
|
|
13
|
+
*/
|
|
14
|
+
export class BorrowService {
|
|
15
|
+
constructor(private readonly ctx: ClientServiceContext) {}
|
|
16
|
+
|
|
17
|
+
async openObligation<S extends boolean>(
|
|
18
|
+
sign: S = true as S
|
|
19
|
+
): Promise<ClientTxResult<S>> {
|
|
20
|
+
const txBlock = this.ctx.builder.createTxBlock();
|
|
21
|
+
txBlock.openObligationEntry();
|
|
22
|
+
if (sign) {
|
|
23
|
+
return (await this.ctx.executor.signAndSendTxn(
|
|
24
|
+
txBlock
|
|
25
|
+
)) as ClientTxResult<S>;
|
|
26
|
+
}
|
|
27
|
+
return txBlock.txBlock as ClientTxResult<S>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async borrow<S extends boolean>(
|
|
31
|
+
poolCoinName: string,
|
|
32
|
+
amount: number,
|
|
33
|
+
sign: S = true as S,
|
|
34
|
+
obligationId: string,
|
|
35
|
+
obligationKey: string,
|
|
36
|
+
walletAddress?: string,
|
|
37
|
+
isSponsoredTx?: boolean
|
|
38
|
+
): Promise<ClientTxResult<S>> {
|
|
39
|
+
const txBlock = this.ctx.builder.createTxBlock();
|
|
40
|
+
const sender = walletAddress ?? this.ctx.walletAddress;
|
|
41
|
+
txBlock.setSender(sender);
|
|
42
|
+
|
|
43
|
+
const availableStake =
|
|
44
|
+
this.ctx.constants.whitelist.lending.has(poolCoinName);
|
|
45
|
+
if (sign && availableStake) {
|
|
46
|
+
await txBlock.unstakeObligationQuick(obligationId, obligationKey);
|
|
47
|
+
}
|
|
48
|
+
const coin = await txBlock.borrowQuick(
|
|
49
|
+
amount,
|
|
50
|
+
poolCoinName,
|
|
51
|
+
obligationId,
|
|
52
|
+
obligationKey,
|
|
53
|
+
{ isSponsoredTx }
|
|
54
|
+
);
|
|
55
|
+
txBlock.transferObjects([coin], sender);
|
|
56
|
+
if (sign && availableStake) {
|
|
57
|
+
await txBlock.stakeObligationWithVeScaQuick(obligationId, obligationKey);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (sign) {
|
|
61
|
+
return (await this.ctx.executor.signAndSendTxn(
|
|
62
|
+
txBlock
|
|
63
|
+
)) as ClientTxResult<S>;
|
|
64
|
+
}
|
|
65
|
+
return txBlock.txBlock as ClientTxResult<S>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async repay<S extends boolean>(
|
|
69
|
+
poolCoinName: string,
|
|
70
|
+
amount: number,
|
|
71
|
+
sign: S = true as S,
|
|
72
|
+
obligationId: string,
|
|
73
|
+
obligationKey: string,
|
|
74
|
+
walletAddress?: string,
|
|
75
|
+
isSponsoredTx?: boolean
|
|
76
|
+
): Promise<ClientTxResult<S>> {
|
|
77
|
+
const txBlock = this.ctx.builder.createTxBlock();
|
|
78
|
+
const sender = walletAddress ?? this.ctx.walletAddress;
|
|
79
|
+
txBlock.setSender(sender);
|
|
80
|
+
|
|
81
|
+
const availableStake =
|
|
82
|
+
this.ctx.constants.whitelist.lending.has(poolCoinName);
|
|
83
|
+
if (sign && availableStake) {
|
|
84
|
+
await txBlock.unstakeObligationQuick(obligationId, obligationKey);
|
|
85
|
+
}
|
|
86
|
+
await txBlock.repayQuick(amount, poolCoinName, obligationId, isSponsoredTx);
|
|
87
|
+
if (sign && availableStake) {
|
|
88
|
+
await txBlock.stakeObligationWithVeScaQuick(obligationId, obligationKey);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (sign) {
|
|
92
|
+
return (await this.ctx.executor.signAndSendTxn(
|
|
93
|
+
txBlock
|
|
94
|
+
)) as ClientTxResult<S>;
|
|
95
|
+
}
|
|
96
|
+
return txBlock.txBlock as ClientTxResult<S>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async supplyAndStake<S extends boolean>(
|
|
100
|
+
stakeCoinName: string,
|
|
101
|
+
amount: number,
|
|
102
|
+
sign: S = true as S,
|
|
103
|
+
stakeAccountId?: string,
|
|
104
|
+
walletAddress?: string
|
|
105
|
+
): Promise<ClientTxResult<S>> {
|
|
106
|
+
const txBlock = this.ctx.builder.createTxBlock();
|
|
107
|
+
const sender = walletAddress ?? this.ctx.walletAddress;
|
|
108
|
+
txBlock.setSender(sender);
|
|
109
|
+
|
|
110
|
+
const stakeMarketCoinName =
|
|
111
|
+
this.ctx.utils.parseMarketCoinName<string>(stakeCoinName);
|
|
112
|
+
const stakeAccounts =
|
|
113
|
+
await this.ctx.query.getStakeAccounts(stakeMarketCoinName);
|
|
114
|
+
const targetStakeAccount = stakeAccountId ?? stakeAccounts[0]?.id;
|
|
115
|
+
|
|
116
|
+
const marketCoin = await txBlock.supplyQuick(amount, stakeCoinName, false);
|
|
117
|
+
if (targetStakeAccount) {
|
|
118
|
+
await txBlock.stakeQuick(
|
|
119
|
+
marketCoin,
|
|
120
|
+
stakeMarketCoinName,
|
|
121
|
+
targetStakeAccount
|
|
122
|
+
);
|
|
123
|
+
} else {
|
|
124
|
+
const account = txBlock.createStakeAccount(stakeMarketCoinName);
|
|
125
|
+
await txBlock.stakeQuick(marketCoin, stakeMarketCoinName, account);
|
|
126
|
+
txBlock.transferObjects([account], sender);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (sign) {
|
|
130
|
+
return (await this.ctx.executor.signAndSendTxn(
|
|
131
|
+
txBlock
|
|
132
|
+
)) as ClientTxResult<S>;
|
|
133
|
+
}
|
|
134
|
+
return txBlock.txBlock as ClientTxResult<S>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async stakeObligation<S extends boolean>(
|
|
138
|
+
obligationId: string,
|
|
139
|
+
obligationKeyId: string,
|
|
140
|
+
sign: S = true as S,
|
|
141
|
+
walletAddress?: string
|
|
142
|
+
): Promise<ClientTxResult<S>> {
|
|
143
|
+
const txBlock = this.ctx.builder.createTxBlock();
|
|
144
|
+
txBlock.setSender(walletAddress ?? this.ctx.walletAddress);
|
|
145
|
+
await txBlock.stakeObligationWithVeScaQuick(obligationId, obligationKeyId);
|
|
146
|
+
|
|
147
|
+
if (sign) {
|
|
148
|
+
return (await this.ctx.executor.signAndSendTxn(
|
|
149
|
+
txBlock
|
|
150
|
+
)) as ClientTxResult<S>;
|
|
151
|
+
}
|
|
152
|
+
return txBlock.txBlock as ClientTxResult<S>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async unstakeObligation<S extends boolean>(
|
|
156
|
+
obligationId: string,
|
|
157
|
+
obligationKeyId: string,
|
|
158
|
+
sign: S = true as S,
|
|
159
|
+
walletAddress?: string
|
|
160
|
+
): Promise<ClientTxResult<S>> {
|
|
161
|
+
const txBlock = this.ctx.builder.createTxBlock();
|
|
162
|
+
txBlock.setSender(walletAddress ?? this.ctx.walletAddress);
|
|
163
|
+
await txBlock.unstakeObligationQuick(obligationId, obligationKeyId);
|
|
164
|
+
|
|
165
|
+
if (sign) {
|
|
166
|
+
return (await this.ctx.executor.signAndSendTxn(
|
|
167
|
+
txBlock
|
|
168
|
+
)) as ClientTxResult<S>;
|
|
169
|
+
}
|
|
170
|
+
return txBlock.txBlock as ClientTxResult<S>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async claimBorrowIncentive<S extends boolean>(
|
|
174
|
+
obligationId: string,
|
|
175
|
+
obligationKeyId: string,
|
|
176
|
+
sign: S = true as S,
|
|
177
|
+
walletAddress?: string
|
|
178
|
+
): Promise<ClientTxResult<S>> {
|
|
179
|
+
const txBlock = this.ctx.builder.createTxBlock();
|
|
180
|
+
const sender = walletAddress ?? this.ctx.walletAddress;
|
|
181
|
+
txBlock.setSender(sender);
|
|
182
|
+
|
|
183
|
+
const rewardCoinsCollection: Record<string, TransactionResult[]> = {};
|
|
184
|
+
const obligationAccount =
|
|
185
|
+
await this.ctx.query.getObligationAccount(obligationId);
|
|
186
|
+
if (!obligationAccount) {
|
|
187
|
+
throw new ScallopTransactionBuildError('Obligation not found', {
|
|
188
|
+
context: { obligationId },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
const rewardCoinNames = Object.values(obligationAccount.borrowIncentives)
|
|
192
|
+
.filter((t): t is NonNullable<typeof t> => !!t)
|
|
193
|
+
.flatMap(({ rewards }) =>
|
|
194
|
+
rewards.filter(({ availableClaimAmount }) => availableClaimAmount > 0)
|
|
195
|
+
)
|
|
196
|
+
.flatMap(({ coinName }) => coinName);
|
|
197
|
+
|
|
198
|
+
for (const rewardCoinName of rewardCoinNames) {
|
|
199
|
+
const rewardCoin = await txBlock.claimBorrowIncentiveQuick(
|
|
200
|
+
rewardCoinName,
|
|
201
|
+
obligationId,
|
|
202
|
+
obligationKeyId
|
|
203
|
+
);
|
|
204
|
+
if (!rewardCoinsCollection[rewardCoinName]) {
|
|
205
|
+
rewardCoinsCollection[rewardCoinName] = [rewardCoin];
|
|
206
|
+
} else {
|
|
207
|
+
rewardCoinsCollection[rewardCoinName].push(rewardCoin);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
txBlock.transferObjects(
|
|
212
|
+
Object.values(rewardCoinsCollection).map((rewardCoins) => {
|
|
213
|
+
const mergeDest = rewardCoins[0];
|
|
214
|
+
if (rewardCoins.length > 1) {
|
|
215
|
+
txBlock.mergeCoins(mergeDest, rewardCoins.slice(1));
|
|
216
|
+
}
|
|
217
|
+
return mergeDest;
|
|
218
|
+
}),
|
|
219
|
+
sender
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
if (sign) {
|
|
223
|
+
return (await this.ctx.executor.signAndSendTxn(
|
|
224
|
+
txBlock
|
|
225
|
+
)) as ClientTxResult<S>;
|
|
226
|
+
}
|
|
227
|
+
return txBlock.txBlock as ClientTxResult<S>;
|
|
228
|
+
}
|
|
229
|
+
}
|