@exponent-labs/exponent-sdk 0.9.0 → 0.9.2
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/build/client/vaults/index.d.ts +2 -0
- package/build/client/vaults/index.js +2 -0
- package/build/client/vaults/index.js.map +1 -1
- package/build/client/vaults/types/index.d.ts +2 -0
- package/build/client/vaults/types/index.js +2 -0
- package/build/client/vaults/types/index.js.map +1 -1
- package/build/client/vaults/types/kaminoFarmEntry.d.ts +15 -0
- package/build/client/vaults/types/kaminoFarmEntry.js +17 -0
- package/build/client/vaults/types/kaminoFarmEntry.js.map +1 -0
- package/build/client/vaults/types/kaminoObligationEntry.d.ts +21 -4
- package/build/client/vaults/types/kaminoObligationEntry.js +2 -1
- package/build/client/vaults/types/kaminoObligationEntry.js.map +1 -1
- package/build/client/vaults/types/positionUpdate.d.ts +9 -0
- package/build/client/vaults/types/positionUpdate.js +23 -0
- package/build/client/vaults/types/positionUpdate.js.map +1 -1
- package/build/client/vaults/types/proposalAction.js +0 -3
- package/build/client/vaults/types/proposalAction.js.map +1 -1
- package/build/client/vaults/types/reserveFarmMapping.d.ts +19 -0
- package/build/client/vaults/types/reserveFarmMapping.js +18 -0
- package/build/client/vaults/types/reserveFarmMapping.js.map +1 -0
- package/build/client/vaults/types/strategyPosition.d.ts +5 -0
- package/build/client/vaults/types/strategyPosition.js +5 -0
- package/build/client/vaults/types/strategyPosition.js.map +1 -1
- package/build/exponentVaults/aumCalculator.d.ts +25 -4
- package/build/exponentVaults/aumCalculator.js +236 -15
- package/build/exponentVaults/aumCalculator.js.map +1 -1
- package/build/exponentVaults/fetcher.d.ts +52 -0
- package/build/exponentVaults/fetcher.js +199 -0
- package/build/exponentVaults/fetcher.js.map +1 -0
- package/build/exponentVaults/index.d.ts +10 -9
- package/build/exponentVaults/index.js +26 -8
- package/build/exponentVaults/index.js.map +1 -1
- package/build/exponentVaults/kamino-farms.d.ts +144 -0
- package/build/exponentVaults/kamino-farms.js +396 -0
- package/build/exponentVaults/kamino-farms.js.map +1 -0
- package/build/exponentVaults/loopscale/client.d.ts +240 -0
- package/build/exponentVaults/loopscale/client.js +590 -0
- package/build/exponentVaults/loopscale/client.js.map +1 -0
- package/build/exponentVaults/loopscale/client.test.d.ts +1 -0
- package/build/exponentVaults/loopscale/client.test.js +183 -0
- package/build/exponentVaults/loopscale/client.test.js.map +1 -0
- package/build/exponentVaults/loopscale/helpers.d.ts +29 -0
- package/build/exponentVaults/loopscale/helpers.js +119 -0
- package/build/exponentVaults/loopscale/helpers.js.map +1 -0
- package/build/exponentVaults/loopscale/index.d.ts +3 -0
- package/build/exponentVaults/loopscale/index.js +12 -0
- package/build/exponentVaults/loopscale/index.js.map +1 -0
- package/build/exponentVaults/loopscale/prepared-transactions.d.ts +13 -0
- package/build/exponentVaults/loopscale/prepared-transactions.js +271 -0
- package/build/exponentVaults/loopscale/prepared-transactions.js.map +1 -0
- package/build/exponentVaults/loopscale/prepared-transactions.test.d.ts +1 -0
- package/build/exponentVaults/loopscale/prepared-transactions.test.js +400 -0
- package/build/exponentVaults/loopscale/prepared-transactions.test.js.map +1 -0
- package/build/exponentVaults/loopscale/prepared-types.d.ts +62 -0
- package/build/exponentVaults/loopscale/prepared-types.js +3 -0
- package/build/exponentVaults/loopscale/prepared-types.js.map +1 -0
- package/build/exponentVaults/loopscale/response-plan.d.ts +69 -0
- package/build/exponentVaults/loopscale/response-plan.js +141 -0
- package/build/exponentVaults/loopscale/response-plan.js.map +1 -0
- package/build/exponentVaults/loopscale/response-plan.test.d.ts +1 -0
- package/build/exponentVaults/loopscale/response-plan.test.js +139 -0
- package/build/exponentVaults/loopscale/response-plan.test.js.map +1 -0
- package/build/exponentVaults/loopscale/send-plan.d.ts +75 -0
- package/build/exponentVaults/loopscale/send-plan.js +235 -0
- package/build/exponentVaults/loopscale/send-plan.js.map +1 -0
- package/build/exponentVaults/loopscale/types.d.ts +443 -0
- package/build/exponentVaults/loopscale/types.js +3 -0
- package/build/exponentVaults/loopscale/types.js.map +1 -0
- package/build/exponentVaults/loopscale-client.d.ts +113 -524
- package/build/exponentVaults/loopscale-client.js +296 -539
- package/build/exponentVaults/loopscale-client.js.map +1 -1
- package/build/exponentVaults/loopscale-client.test.d.ts +1 -0
- package/build/exponentVaults/loopscale-client.test.js +162 -0
- package/build/exponentVaults/loopscale-client.test.js.map +1 -0
- package/build/exponentVaults/loopscale-client.types.d.ts +425 -0
- package/build/exponentVaults/loopscale-client.types.js +3 -0
- package/build/exponentVaults/loopscale-client.types.js.map +1 -0
- package/build/exponentVaults/loopscale-execution.d.ts +125 -0
- package/build/exponentVaults/loopscale-execution.js +341 -0
- package/build/exponentVaults/loopscale-execution.js.map +1 -0
- package/build/exponentVaults/loopscale-execution.test.d.ts +1 -0
- package/build/exponentVaults/loopscale-execution.test.js +139 -0
- package/build/exponentVaults/loopscale-execution.test.js.map +1 -0
- package/build/exponentVaults/loopscale-vault.d.ts +115 -0
- package/build/exponentVaults/loopscale-vault.js +275 -0
- package/build/exponentVaults/loopscale-vault.js.map +1 -0
- package/build/exponentVaults/loopscale-vault.test.d.ts +1 -0
- package/build/exponentVaults/loopscale-vault.test.js +102 -0
- package/build/exponentVaults/loopscale-vault.test.js.map +1 -0
- package/build/exponentVaults/policyBuilders.d.ts +62 -0
- package/build/exponentVaults/policyBuilders.js +119 -2
- package/build/exponentVaults/policyBuilders.js.map +1 -1
- package/build/exponentVaults/pricePathResolver.d.ts +45 -0
- package/build/exponentVaults/pricePathResolver.js +198 -0
- package/build/exponentVaults/pricePathResolver.js.map +1 -0
- package/build/exponentVaults/pricePathResolver.test.d.ts +1 -0
- package/build/exponentVaults/pricePathResolver.test.js +369 -0
- package/build/exponentVaults/pricePathResolver.test.js.map +1 -0
- package/build/exponentVaults/syncTransaction.js +4 -1
- package/build/exponentVaults/syncTransaction.js.map +1 -1
- package/build/exponentVaults/titan-quote.js +170 -36
- package/build/exponentVaults/titan-quote.js.map +1 -1
- package/build/exponentVaults/vault-instruction-types.d.ts +363 -0
- package/build/exponentVaults/vault-instruction-types.js +128 -0
- package/build/exponentVaults/vault-instruction-types.js.map +1 -0
- package/build/exponentVaults/vault-interaction.d.ts +203 -343
- package/build/exponentVaults/vault-interaction.js +1894 -426
- package/build/exponentVaults/vault-interaction.js.map +1 -1
- package/build/exponentVaults/vault-interaction.kamino-vault.test.d.ts +1 -0
- package/build/exponentVaults/vault-interaction.kamino-vault.test.js +143 -0
- package/build/exponentVaults/vault-interaction.kamino-vault.test.js.map +1 -0
- package/build/exponentVaults/vault.d.ts +51 -2
- package/build/exponentVaults/vault.js +324 -48
- package/build/exponentVaults/vault.js.map +1 -1
- package/build/exponentVaults/vaultTransactionBuilder.d.ts +100 -134
- package/build/exponentVaults/vaultTransactionBuilder.js +383 -285
- package/build/exponentVaults/vaultTransactionBuilder.js.map +1 -1
- package/build/exponentVaults/vaultTransactionBuilder.test.d.ts +1 -0
- package/build/exponentVaults/vaultTransactionBuilder.test.js +297 -0
- package/build/exponentVaults/vaultTransactionBuilder.test.js.map +1 -0
- package/build/marketThree.d.ts +6 -2
- package/build/marketThree.js +10 -8
- package/build/marketThree.js.map +1 -1
- package/package.json +34 -32
- package/src/client/vaults/index.ts +2 -0
- package/src/client/vaults/types/index.ts +2 -0
- package/src/client/vaults/types/kaminoFarmEntry.ts +32 -0
- package/src/client/vaults/types/kaminoObligationEntry.ts +6 -3
- package/src/client/vaults/types/positionUpdate.ts +62 -0
- package/src/client/vaults/types/proposalAction.ts +0 -3
- package/src/client/vaults/types/reserveFarmMapping.ts +35 -0
- package/src/client/vaults/types/strategyPosition.ts +18 -1
- package/src/exponentVaults/aumCalculator.ts +353 -16
- package/src/exponentVaults/fetcher.ts +257 -0
- package/src/exponentVaults/index.ts +65 -40
- package/src/exponentVaults/kamino-farms.ts +538 -0
- package/src/exponentVaults/loopscale/client.ts +808 -0
- package/src/exponentVaults/loopscale/helpers.ts +172 -0
- package/src/exponentVaults/loopscale/index.ts +57 -0
- package/src/exponentVaults/loopscale/prepared-transactions.ts +435 -0
- package/src/exponentVaults/loopscale/prepared-types.ts +73 -0
- package/src/exponentVaults/loopscale/types.ts +466 -0
- package/src/exponentVaults/policyBuilders.ts +170 -0
- package/src/exponentVaults/pricePathResolver.test.ts +466 -0
- package/src/exponentVaults/pricePathResolver.ts +273 -0
- package/src/exponentVaults/syncTransaction.ts +6 -1
- package/src/exponentVaults/titan-quote.ts +231 -45
- package/src/exponentVaults/vault-instruction-types.ts +493 -0
- package/src/exponentVaults/vault-interaction.kamino-vault.test.ts +149 -0
- package/src/exponentVaults/vault-interaction.ts +2818 -799
- package/src/exponentVaults/vault.ts +474 -63
- package/src/exponentVaults/vaultTransactionBuilder.test.ts +349 -0
- package/src/exponentVaults/vaultTransactionBuilder.ts +581 -433
- package/src/marketThree.ts +14 -6
- package/src/exponentVaults/loopscale-client.ts +0 -1373
|
@@ -7,6 +7,8 @@ import { AccountConstraint, DataConstraint, InstructionConstraint, PolicyConfig,
|
|
|
7
7
|
// ============================================================================
|
|
8
8
|
|
|
9
9
|
export const KAMINO_LENDING_PROGRAM_ID = new PublicKey("KLend2g3cP87fffoy8q1mQqGKjrxjC8boSyAYavgmjD")
|
|
10
|
+
export const KAMINO_VAULT_PROGRAM_ID = new PublicKey("KvauGMspG5k6rtzrqqn7WNn3oZdyKqLKwK2XWQ8FLjd")
|
|
11
|
+
export const KAMINO_FARMS_PROGRAM_ID = new PublicKey("FarmsPZpWu9i7Kky8tPN37rs2TpmMrAZrC7S7vJa91Hr")
|
|
10
12
|
|
|
11
13
|
// ============================================================================
|
|
12
14
|
// Instruction Discriminators (first 8 bytes of sha256("global:<method_name>"))
|
|
@@ -33,6 +35,19 @@ export const KAMINO_DISCRIMINATORS = {
|
|
|
33
35
|
refreshObligation: Buffer.from([33, 132, 147, 228, 151, 192, 72, 89]),
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
export const KAMINO_VAULT_DISCRIMINATORS = {
|
|
39
|
+
deposit: Buffer.from([242, 35, 198, 137, 82, 225, 242, 182]),
|
|
40
|
+
withdraw: Buffer.from([183, 18, 70, 156, 148, 109, 161, 34]),
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const KAMINO_FARM_DISCRIMINATORS = {
|
|
44
|
+
initializeUser: Buffer.from([111, 17, 185, 250, 60, 122, 38, 254]),
|
|
45
|
+
stake: Buffer.from([206, 176, 202, 18, 200, 209, 179, 108]),
|
|
46
|
+
unstake: Buffer.from([90, 95, 107, 42, 205, 124, 50, 225]),
|
|
47
|
+
withdrawUnstakedDeposits: Buffer.from([36, 102, 187, 49, 220, 36, 132, 67]),
|
|
48
|
+
harvestReward: Buffer.from([68, 200, 228, 233, 184, 32, 226, 188]),
|
|
49
|
+
}
|
|
50
|
+
|
|
36
51
|
// ============================================================================
|
|
37
52
|
// Policy Builder Functions
|
|
38
53
|
// ============================================================================
|
|
@@ -672,6 +687,161 @@ export function createKaminoPolicy(params: {
|
|
|
672
687
|
})
|
|
673
688
|
}
|
|
674
689
|
|
|
690
|
+
export const KAMINO_VAULT_ACCOUNT_INDICES = {
|
|
691
|
+
deposit: {
|
|
692
|
+
vaultState: 1,
|
|
693
|
+
tokenMint: 3,
|
|
694
|
+
},
|
|
695
|
+
withdraw: {
|
|
696
|
+
vaultState: 1,
|
|
697
|
+
tokenMint: 6,
|
|
698
|
+
reserve: 15,
|
|
699
|
+
},
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
export type KaminoVaultPolicyAction = "deposit" | "withdraw"
|
|
703
|
+
|
|
704
|
+
export function createKaminoVaultPolicy(params: {
|
|
705
|
+
allowedVaults?: PublicKey[]
|
|
706
|
+
allowedDepositMints?: PublicKey[]
|
|
707
|
+
actions: KaminoVaultPolicyAction[]
|
|
708
|
+
spendingLimits?: SpendingLimit[]
|
|
709
|
+
threshold?: number
|
|
710
|
+
timeLock?: number
|
|
711
|
+
}): PolicyConfig {
|
|
712
|
+
const instructionsConstraints: InstructionConstraint[] = []
|
|
713
|
+
|
|
714
|
+
for (const action of params.actions) {
|
|
715
|
+
const accountConstraints: AccountConstraint[] = []
|
|
716
|
+
const accountIndices =
|
|
717
|
+
action === "deposit" ? KAMINO_VAULT_ACCOUNT_INDICES.deposit : KAMINO_VAULT_ACCOUNT_INDICES.withdraw
|
|
718
|
+
|
|
719
|
+
if (params.allowedVaults && params.allowedVaults.length > 0) {
|
|
720
|
+
accountConstraints.push(createAccountConstraint(accountIndices.vaultState, params.allowedVaults))
|
|
721
|
+
}
|
|
722
|
+
if (params.allowedDepositMints && params.allowedDepositMints.length > 0) {
|
|
723
|
+
accountConstraints.push(createAccountConstraint(accountIndices.tokenMint, params.allowedDepositMints))
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
instructionsConstraints.push(
|
|
727
|
+
createInstructionConstraint({
|
|
728
|
+
programId: KAMINO_VAULT_PROGRAM_ID,
|
|
729
|
+
accountConstraints,
|
|
730
|
+
dataConstraints: [
|
|
731
|
+
createDiscriminatorConstraint(
|
|
732
|
+
action === "deposit"
|
|
733
|
+
? KAMINO_VAULT_DISCRIMINATORS.deposit
|
|
734
|
+
: KAMINO_VAULT_DISCRIMINATORS.withdraw,
|
|
735
|
+
),
|
|
736
|
+
],
|
|
737
|
+
}),
|
|
738
|
+
)
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return createProgramInteractionPolicy({
|
|
742
|
+
instructionsConstraints,
|
|
743
|
+
spendingLimits: params.spendingLimits,
|
|
744
|
+
threshold: params.threshold,
|
|
745
|
+
timeLock: params.timeLock,
|
|
746
|
+
})
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
export const KAMINO_FARM_ACCOUNT_INDICES = {
|
|
750
|
+
initializeUser: {
|
|
751
|
+
farmState: 5,
|
|
752
|
+
},
|
|
753
|
+
stake: {
|
|
754
|
+
farmState: 2,
|
|
755
|
+
tokenMint: 5,
|
|
756
|
+
},
|
|
757
|
+
unstake: {
|
|
758
|
+
farmState: 2,
|
|
759
|
+
},
|
|
760
|
+
withdrawUnstakedDeposits: {
|
|
761
|
+
farmState: 2,
|
|
762
|
+
},
|
|
763
|
+
harvestReward: {
|
|
764
|
+
farmState: 2,
|
|
765
|
+
rewardMint: 4,
|
|
766
|
+
},
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
export type KaminoFarmPolicyAction =
|
|
770
|
+
| "initializeUser"
|
|
771
|
+
| "stake"
|
|
772
|
+
| "unstake"
|
|
773
|
+
| "withdrawUnstakedDeposits"
|
|
774
|
+
| "harvestReward"
|
|
775
|
+
|
|
776
|
+
export function createKaminoFarmPolicy(params: {
|
|
777
|
+
allowedFarmStates?: PublicKey[]
|
|
778
|
+
allowedUnderlyingMints?: PublicKey[]
|
|
779
|
+
allowedRewardMints?: PublicKey[]
|
|
780
|
+
actions: KaminoFarmPolicyAction[]
|
|
781
|
+
spendingLimits?: SpendingLimit[]
|
|
782
|
+
threshold?: number
|
|
783
|
+
timeLock?: number
|
|
784
|
+
}): PolicyConfig {
|
|
785
|
+
const instructionsConstraints: InstructionConstraint[] = []
|
|
786
|
+
|
|
787
|
+
for (const action of params.actions) {
|
|
788
|
+
const accountConstraints: AccountConstraint[] = []
|
|
789
|
+
|
|
790
|
+
const farmIndex =
|
|
791
|
+
action === "initializeUser"
|
|
792
|
+
? KAMINO_FARM_ACCOUNT_INDICES.initializeUser.farmState
|
|
793
|
+
: action === "stake"
|
|
794
|
+
? KAMINO_FARM_ACCOUNT_INDICES.stake.farmState
|
|
795
|
+
: action === "unstake"
|
|
796
|
+
? KAMINO_FARM_ACCOUNT_INDICES.unstake.farmState
|
|
797
|
+
: action === "withdrawUnstakedDeposits"
|
|
798
|
+
? KAMINO_FARM_ACCOUNT_INDICES.withdrawUnstakedDeposits.farmState
|
|
799
|
+
: KAMINO_FARM_ACCOUNT_INDICES.harvestReward.farmState
|
|
800
|
+
|
|
801
|
+
if (params.allowedFarmStates && params.allowedFarmStates.length > 0) {
|
|
802
|
+
accountConstraints.push(createAccountConstraint(farmIndex, params.allowedFarmStates))
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
if (action === "stake" && params.allowedUnderlyingMints && params.allowedUnderlyingMints.length > 0) {
|
|
806
|
+
accountConstraints.push(
|
|
807
|
+
createAccountConstraint(KAMINO_FARM_ACCOUNT_INDICES.stake.tokenMint, params.allowedUnderlyingMints),
|
|
808
|
+
)
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
if (action === "harvestReward" && params.allowedRewardMints && params.allowedRewardMints.length > 0) {
|
|
812
|
+
accountConstraints.push(
|
|
813
|
+
createAccountConstraint(KAMINO_FARM_ACCOUNT_INDICES.harvestReward.rewardMint, params.allowedRewardMints),
|
|
814
|
+
)
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const discriminator =
|
|
818
|
+
action === "initializeUser"
|
|
819
|
+
? KAMINO_FARM_DISCRIMINATORS.initializeUser
|
|
820
|
+
: action === "stake"
|
|
821
|
+
? KAMINO_FARM_DISCRIMINATORS.stake
|
|
822
|
+
: action === "unstake"
|
|
823
|
+
? KAMINO_FARM_DISCRIMINATORS.unstake
|
|
824
|
+
: action === "withdrawUnstakedDeposits"
|
|
825
|
+
? KAMINO_FARM_DISCRIMINATORS.withdrawUnstakedDeposits
|
|
826
|
+
: KAMINO_FARM_DISCRIMINATORS.harvestReward
|
|
827
|
+
|
|
828
|
+
instructionsConstraints.push(
|
|
829
|
+
createInstructionConstraint({
|
|
830
|
+
programId: KAMINO_FARMS_PROGRAM_ID,
|
|
831
|
+
accountConstraints,
|
|
832
|
+
dataConstraints: [createDiscriminatorConstraint(discriminator)],
|
|
833
|
+
}),
|
|
834
|
+
)
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
return createProgramInteractionPolicy({
|
|
838
|
+
instructionsConstraints,
|
|
839
|
+
spendingLimits: params.spendingLimits,
|
|
840
|
+
threshold: params.threshold,
|
|
841
|
+
timeLock: params.timeLock,
|
|
842
|
+
})
|
|
843
|
+
}
|
|
844
|
+
|
|
675
845
|
// ============================================================================
|
|
676
846
|
// Exponent Core Policy Builders (Strip/Merge)
|
|
677
847
|
// ============================================================================
|
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
import { describe, expect, it } from "@jest/globals"
|
|
2
|
+
import { PublicKey } from "@solana/web3.js"
|
|
3
|
+
import type { ExponentPrice, ExponentPrices } from "@exponent-labs/exponent-vaults-fetcher"
|
|
4
|
+
import {
|
|
5
|
+
extractPriceIds,
|
|
6
|
+
resolveExponentPricePath,
|
|
7
|
+
resolveBestKaminoQuotePath,
|
|
8
|
+
resolveKaminoReservePriceIdOrThrow,
|
|
9
|
+
resolvePriceIdFromMintToUnderlying,
|
|
10
|
+
resolvePriceIdFromMintToUnderlyingOrThrow,
|
|
11
|
+
getPriceInputMintFromPriceId,
|
|
12
|
+
} from "./pricePathResolver"
|
|
13
|
+
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Test helpers
|
|
16
|
+
// ============================================================================
|
|
17
|
+
|
|
18
|
+
/** Create a deterministic PublicKey from a seed byte. */
|
|
19
|
+
function pk(seed: number): PublicKey {
|
|
20
|
+
return new PublicKey(Uint8Array.from({ length: 32 }, () => seed))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Named mints for readability. */
|
|
24
|
+
const USDC = pk(1)
|
|
25
|
+
const SOL = pk(2)
|
|
26
|
+
const PT_SOL = pk(3)
|
|
27
|
+
const USDT = pk(6)
|
|
28
|
+
const BSOL = pk(8)
|
|
29
|
+
const PT_BSOL = pk(9)
|
|
30
|
+
|
|
31
|
+
function makePriceEntry(params: {
|
|
32
|
+
priceId: bigint
|
|
33
|
+
priceMint: PublicKey
|
|
34
|
+
underlyingMint: PublicKey
|
|
35
|
+
}): ExponentPrice {
|
|
36
|
+
return {
|
|
37
|
+
priceId: params.priceId,
|
|
38
|
+
priceMint: params.priceMint,
|
|
39
|
+
underlyingMint: params.underlyingMint,
|
|
40
|
+
price: [[0n, 0n, 0n, 0n]],
|
|
41
|
+
positionsAmount: 0n,
|
|
42
|
+
lastUpdatedAt: 0n,
|
|
43
|
+
lastUpdatedSlot: 0n,
|
|
44
|
+
priceType: 0,
|
|
45
|
+
impliedApyBps: null,
|
|
46
|
+
impliedApy: null,
|
|
47
|
+
priceInterfaceAccounts: PublicKey.default,
|
|
48
|
+
interfaceAccounts: [],
|
|
49
|
+
} as unknown as ExponentPrice
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function makePrices(entries: ExponentPrice[]): ExponentPrices {
|
|
53
|
+
return {
|
|
54
|
+
managers: [],
|
|
55
|
+
prices: entries,
|
|
56
|
+
} as unknown as ExponentPrices
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============================================================================
|
|
60
|
+
// resolveExponentPricePath — BFS core
|
|
61
|
+
// ============================================================================
|
|
62
|
+
|
|
63
|
+
describe("resolveExponentPricePath", () => {
|
|
64
|
+
it("resolves a direct 1-hop path (PT_SOL → SOL)", () => {
|
|
65
|
+
const prices = makePrices([
|
|
66
|
+
makePriceEntry({ priceId: 1n, priceMint: PT_SOL, underlyingMint: SOL }),
|
|
67
|
+
])
|
|
68
|
+
|
|
69
|
+
const path = resolveExponentPricePath(prices, PT_SOL, SOL)
|
|
70
|
+
|
|
71
|
+
expect(path).not.toBeNull()
|
|
72
|
+
expect(path!.sourceMint.equals(PT_SOL)).toBe(true)
|
|
73
|
+
expect(path!.targetMint.equals(SOL)).toBe(true)
|
|
74
|
+
expect(path!.edges).toHaveLength(1)
|
|
75
|
+
expect(path!.edges[0].priceId).toBe(1n)
|
|
76
|
+
expect(path!.mints).toHaveLength(2)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it("resolves a 2-hop path (PT_SOL → SOL → USDC)", () => {
|
|
80
|
+
const prices = makePrices([
|
|
81
|
+
makePriceEntry({ priceId: 1n, priceMint: PT_SOL, underlyingMint: SOL }),
|
|
82
|
+
makePriceEntry({ priceId: 2n, priceMint: SOL, underlyingMint: USDC }),
|
|
83
|
+
])
|
|
84
|
+
|
|
85
|
+
const path = resolveExponentPricePath(prices, PT_SOL, USDC)
|
|
86
|
+
|
|
87
|
+
expect(path).not.toBeNull()
|
|
88
|
+
expect(path!.edges).toHaveLength(2)
|
|
89
|
+
expect(path!.mints).toHaveLength(3)
|
|
90
|
+
expect(path!.mints[0].equals(PT_SOL)).toBe(true)
|
|
91
|
+
expect(path!.mints[1].equals(SOL)).toBe(true)
|
|
92
|
+
expect(path!.mints[2].equals(USDC)).toBe(true)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it("resolves a 3-hop path (PT_BSOL → BSOL → SOL → USDC)", () => {
|
|
96
|
+
const prices = makePrices([
|
|
97
|
+
makePriceEntry({ priceId: 1n, priceMint: PT_BSOL, underlyingMint: BSOL }),
|
|
98
|
+
makePriceEntry({ priceId: 2n, priceMint: BSOL, underlyingMint: SOL }),
|
|
99
|
+
makePriceEntry({ priceId: 3n, priceMint: SOL, underlyingMint: USDC }),
|
|
100
|
+
])
|
|
101
|
+
|
|
102
|
+
const path = resolveExponentPricePath(prices, PT_BSOL, USDC)
|
|
103
|
+
|
|
104
|
+
expect(path).not.toBeNull()
|
|
105
|
+
expect(path!.edges).toHaveLength(3)
|
|
106
|
+
expect(path!.mints[0].equals(PT_BSOL)).toBe(true)
|
|
107
|
+
expect(path!.mints[1].equals(BSOL)).toBe(true)
|
|
108
|
+
expect(path!.mints[2].equals(SOL)).toBe(true)
|
|
109
|
+
expect(path!.mints[3].equals(USDC)).toBe(true)
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
it("returns null when no path exists", () => {
|
|
113
|
+
const prices = makePrices([
|
|
114
|
+
makePriceEntry({ priceId: 1n, priceMint: PT_SOL, underlyingMint: SOL }),
|
|
115
|
+
// No edge from SOL to USDC — disconnected
|
|
116
|
+
])
|
|
117
|
+
|
|
118
|
+
const path = resolveExponentPricePath(prices, PT_SOL, USDC)
|
|
119
|
+
|
|
120
|
+
expect(path).toBeNull()
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it("returns null for empty price graph", () => {
|
|
124
|
+
const prices = makePrices([])
|
|
125
|
+
expect(resolveExponentPricePath(prices, PT_SOL, SOL)).toBeNull()
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it("finds the shortest path when multiple paths exist", () => {
|
|
129
|
+
const prices = makePrices([
|
|
130
|
+
// Direct: PT_SOL → USDC (1 hop)
|
|
131
|
+
makePriceEntry({ priceId: 10n, priceMint: PT_SOL, underlyingMint: USDC }),
|
|
132
|
+
// Indirect: PT_SOL → SOL → USDC (2 hops)
|
|
133
|
+
makePriceEntry({ priceId: 1n, priceMint: PT_SOL, underlyingMint: SOL }),
|
|
134
|
+
makePriceEntry({ priceId: 2n, priceMint: SOL, underlyingMint: USDC }),
|
|
135
|
+
])
|
|
136
|
+
|
|
137
|
+
const path = resolveExponentPricePath(prices, PT_SOL, USDC)
|
|
138
|
+
|
|
139
|
+
// BFS finds shortest first
|
|
140
|
+
expect(path).not.toBeNull()
|
|
141
|
+
expect(path!.edges).toHaveLength(1)
|
|
142
|
+
expect(path!.edges[0].priceId).toBe(10n)
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it("handles cycles in the graph without infinite loop", () => {
|
|
146
|
+
const prices = makePrices([
|
|
147
|
+
makePriceEntry({ priceId: 1n, priceMint: SOL, underlyingMint: USDC }),
|
|
148
|
+
makePriceEntry({ priceId: 2n, priceMint: USDC, underlyingMint: SOL }), // Cycle
|
|
149
|
+
makePriceEntry({ priceId: 3n, priceMint: PT_SOL, underlyingMint: SOL }),
|
|
150
|
+
])
|
|
151
|
+
|
|
152
|
+
const path = resolveExponentPricePath(prices, PT_SOL, USDC)
|
|
153
|
+
|
|
154
|
+
expect(path).not.toBeNull()
|
|
155
|
+
expect(path!.edges).toHaveLength(2)
|
|
156
|
+
})
|
|
157
|
+
})
|
|
158
|
+
|
|
159
|
+
// ============================================================================
|
|
160
|
+
// resolvePriceIdFromMintToUnderlying — PriceId construction
|
|
161
|
+
// ============================================================================
|
|
162
|
+
|
|
163
|
+
describe("resolvePriceIdFromMintToUnderlying", () => {
|
|
164
|
+
it("returns Simple PriceId for a 1-hop path", () => {
|
|
165
|
+
const prices = makePrices([
|
|
166
|
+
makePriceEntry({ priceId: 7n, priceMint: PT_SOL, underlyingMint: SOL }),
|
|
167
|
+
])
|
|
168
|
+
|
|
169
|
+
const resolved = resolvePriceIdFromMintToUnderlyingOrThrow({
|
|
170
|
+
prices,
|
|
171
|
+
sourceMint: PT_SOL,
|
|
172
|
+
targetMint: SOL,
|
|
173
|
+
label: "test",
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
expect(resolved.__kind).toBe("Simple")
|
|
177
|
+
expect(extractPriceIds(resolved)).toEqual([7n])
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it("returns Multiply PriceId for a 2-hop path in underlying-to-input order", () => {
|
|
181
|
+
// Path: PT_SOL → SOL → USDC
|
|
182
|
+
// Edges: [edge(12n, PT_SOL→SOL), edge(11n, SOL→USDC)]
|
|
183
|
+
// On-chain expects: multiply prices from underlying→input, so reversed: [11n, 12n]
|
|
184
|
+
const prices = makePrices([
|
|
185
|
+
makePriceEntry({ priceId: 11n, priceMint: SOL, underlyingMint: USDC }),
|
|
186
|
+
makePriceEntry({ priceId: 12n, priceMint: PT_SOL, underlyingMint: SOL }),
|
|
187
|
+
])
|
|
188
|
+
|
|
189
|
+
const resolved = resolvePriceIdFromMintToUnderlyingOrThrow({
|
|
190
|
+
prices,
|
|
191
|
+
sourceMint: PT_SOL,
|
|
192
|
+
targetMint: USDC,
|
|
193
|
+
label: "test",
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
expect(resolved.__kind).toBe("Multiply")
|
|
197
|
+
// On-chain validates: first entry's underlying_mint = vault underlying (USDC)
|
|
198
|
+
// Then chains: USDC ← SOL ← PT_SOL
|
|
199
|
+
expect(extractPriceIds(resolved)).toEqual([11n, 12n])
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it("returns Multiply PriceId for a 3-hop path", () => {
|
|
203
|
+
const prices = makePrices([
|
|
204
|
+
makePriceEntry({ priceId: 1n, priceMint: PT_BSOL, underlyingMint: BSOL }),
|
|
205
|
+
makePriceEntry({ priceId: 2n, priceMint: BSOL, underlyingMint: SOL }),
|
|
206
|
+
makePriceEntry({ priceId: 3n, priceMint: SOL, underlyingMint: USDC }),
|
|
207
|
+
])
|
|
208
|
+
|
|
209
|
+
const resolved = resolvePriceIdFromMintToUnderlyingOrThrow({
|
|
210
|
+
prices,
|
|
211
|
+
sourceMint: PT_BSOL,
|
|
212
|
+
targetMint: USDC,
|
|
213
|
+
label: "test",
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
expect(resolved.__kind).toBe("Multiply")
|
|
217
|
+
// Reversed edge order: [3n (SOL→USDC), 2n (BSOL→SOL), 1n (PT_BSOL→BSOL)]
|
|
218
|
+
expect(extractPriceIds(resolved)).toEqual([3n, 2n, 1n])
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
it("returns null when no path exists", () => {
|
|
222
|
+
const prices = makePrices([])
|
|
223
|
+
const resolved = resolvePriceIdFromMintToUnderlying(prices, PT_SOL, SOL)
|
|
224
|
+
expect(resolved).toBeNull()
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
it("throws with descriptive error when path is missing (OrThrow variant)", () => {
|
|
228
|
+
const prices = makePrices([])
|
|
229
|
+
|
|
230
|
+
expect(() =>
|
|
231
|
+
resolvePriceIdFromMintToUnderlyingOrThrow({
|
|
232
|
+
prices,
|
|
233
|
+
sourceMint: PT_SOL,
|
|
234
|
+
targetMint: SOL,
|
|
235
|
+
label: "test label",
|
|
236
|
+
})
|
|
237
|
+
).toThrow(`Missing Exponent price path for test label: ${PT_SOL.toBase58()} -> ${SOL.toBase58()}`)
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// ============================================================================
|
|
242
|
+
// resolveBestKaminoQuotePath — Kamino quote mint selection
|
|
243
|
+
// ============================================================================
|
|
244
|
+
|
|
245
|
+
describe("resolveBestKaminoQuotePath", () => {
|
|
246
|
+
it("always uses vault underlying as the quote mint", () => {
|
|
247
|
+
// USDC vault — quote should be USDC itself via the One price entry
|
|
248
|
+
const prices = makePrices([
|
|
249
|
+
makePriceEntry({ priceId: 1n, priceMint: USDC, underlyingMint: USDC }),
|
|
250
|
+
makePriceEntry({ priceId: 2n, priceMint: SOL, underlyingMint: USDC }),
|
|
251
|
+
])
|
|
252
|
+
|
|
253
|
+
const result = resolveBestKaminoQuotePath({
|
|
254
|
+
prices,
|
|
255
|
+
vaultUnderlyingMint: USDC,
|
|
256
|
+
})
|
|
257
|
+
|
|
258
|
+
expect(result.quoteInputMint.equals(USDC)).toBe(true)
|
|
259
|
+
expect(extractPriceIds(result.quotePriceId)).toEqual([1n])
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
it("works for a SOL vault with a SOL→SOL identity entry", () => {
|
|
263
|
+
const prices = makePrices([
|
|
264
|
+
makePriceEntry({ priceId: 3n, priceMint: SOL, underlyingMint: SOL }),
|
|
265
|
+
])
|
|
266
|
+
|
|
267
|
+
const result = resolveBestKaminoQuotePath({
|
|
268
|
+
prices,
|
|
269
|
+
vaultUnderlyingMint: SOL,
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
expect(result.quoteInputMint.equals(SOL)).toBe(true)
|
|
273
|
+
expect(extractPriceIds(result.quotePriceId)).toEqual([3n])
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it("throws when no identity price exists for vault underlying", () => {
|
|
277
|
+
const prices = makePrices([])
|
|
278
|
+
|
|
279
|
+
expect(() =>
|
|
280
|
+
resolveBestKaminoQuotePath({
|
|
281
|
+
prices,
|
|
282
|
+
vaultUnderlyingMint: USDC,
|
|
283
|
+
})
|
|
284
|
+
).toThrow("Missing Exponent price path for Kamino quote resolution")
|
|
285
|
+
})
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
// ============================================================================
|
|
289
|
+
// resolveKaminoReservePriceIdOrThrow — reserve-to-quote mapping
|
|
290
|
+
// ============================================================================
|
|
291
|
+
|
|
292
|
+
describe("resolveKaminoReservePriceIdOrThrow", () => {
|
|
293
|
+
it("returns identity (Simple 0) when reserve mint equals quote mint", () => {
|
|
294
|
+
const prices = makePrices([])
|
|
295
|
+
|
|
296
|
+
const result = resolveKaminoReservePriceIdOrThrow({
|
|
297
|
+
prices,
|
|
298
|
+
reserveMint: USDC,
|
|
299
|
+
quoteInputMint: USDC,
|
|
300
|
+
})
|
|
301
|
+
|
|
302
|
+
expect(result.__kind).toBe("Simple")
|
|
303
|
+
expect(extractPriceIds(result)).toEqual([0n])
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
it("resolves reserve → quote when they differ", () => {
|
|
307
|
+
const prices = makePrices([
|
|
308
|
+
makePriceEntry({ priceId: 5n, priceMint: SOL, underlyingMint: USDC }),
|
|
309
|
+
])
|
|
310
|
+
|
|
311
|
+
const result = resolveKaminoReservePriceIdOrThrow({
|
|
312
|
+
prices,
|
|
313
|
+
reserveMint: SOL,
|
|
314
|
+
quoteInputMint: USDC,
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
expect(extractPriceIds(result)).toEqual([5n])
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it("throws when reserve cannot reach quote", () => {
|
|
321
|
+
const prices = makePrices([])
|
|
322
|
+
|
|
323
|
+
expect(() =>
|
|
324
|
+
resolveKaminoReservePriceIdOrThrow({
|
|
325
|
+
prices,
|
|
326
|
+
reserveMint: SOL,
|
|
327
|
+
quoteInputMint: USDC,
|
|
328
|
+
})
|
|
329
|
+
).toThrow("Missing Exponent price path for Kamino reserve mapping")
|
|
330
|
+
})
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
// ============================================================================
|
|
334
|
+
// Kamino end-to-end: quote selection + reserve mapping
|
|
335
|
+
// ============================================================================
|
|
336
|
+
|
|
337
|
+
describe("Kamino end-to-end price resolution", () => {
|
|
338
|
+
it("resolves a SOL vault with SOL and USDC reserves", () => {
|
|
339
|
+
// SOL vault: underlying = SOL
|
|
340
|
+
// Reserves: SOL (passthrough), USDC (needs USDC→SOL price)
|
|
341
|
+
const prices = makePrices([
|
|
342
|
+
makePriceEntry({ priceId: 1n, priceMint: SOL, underlyingMint: SOL }),
|
|
343
|
+
makePriceEntry({ priceId: 2n, priceMint: USDC, underlyingMint: SOL }),
|
|
344
|
+
])
|
|
345
|
+
|
|
346
|
+
// Quote is always vault underlying
|
|
347
|
+
const quotePath = resolveBestKaminoQuotePath({
|
|
348
|
+
prices,
|
|
349
|
+
vaultUnderlyingMint: SOL,
|
|
350
|
+
})
|
|
351
|
+
expect(quotePath.quoteInputMint.equals(SOL)).toBe(true)
|
|
352
|
+
|
|
353
|
+
// SOL reserve → SOL quote: passthrough
|
|
354
|
+
const solReservePrice = resolveKaminoReservePriceIdOrThrow({
|
|
355
|
+
prices,
|
|
356
|
+
reserveMint: SOL,
|
|
357
|
+
quoteInputMint: quotePath.quoteInputMint,
|
|
358
|
+
})
|
|
359
|
+
expect(extractPriceIds(solReservePrice)).toEqual([0n])
|
|
360
|
+
|
|
361
|
+
// USDC reserve → SOL quote: needs the USDC→SOL price
|
|
362
|
+
const usdcReservePrice = resolveKaminoReservePriceIdOrThrow({
|
|
363
|
+
prices,
|
|
364
|
+
reserveMint: USDC,
|
|
365
|
+
quoteInputMint: quotePath.quoteInputMint,
|
|
366
|
+
})
|
|
367
|
+
expect(extractPriceIds(usdcReservePrice)).toEqual([2n])
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
it("resolves a USDC vault with BSOL needing a Multiply chain", () => {
|
|
371
|
+
// USDC vault: underlying = USDC
|
|
372
|
+
// BSOL reserve needs BSOL → SOL → USDC (two hops)
|
|
373
|
+
const prices = makePrices([
|
|
374
|
+
makePriceEntry({ priceId: 1n, priceMint: USDC, underlyingMint: USDC }),
|
|
375
|
+
makePriceEntry({ priceId: 2n, priceMint: SOL, underlyingMint: USDC }),
|
|
376
|
+
makePriceEntry({ priceId: 3n, priceMint: BSOL, underlyingMint: SOL }),
|
|
377
|
+
])
|
|
378
|
+
|
|
379
|
+
const quotePath = resolveBestKaminoQuotePath({
|
|
380
|
+
prices,
|
|
381
|
+
vaultUnderlyingMint: USDC,
|
|
382
|
+
})
|
|
383
|
+
expect(quotePath.quoteInputMint.equals(USDC)).toBe(true)
|
|
384
|
+
|
|
385
|
+
// BSOL → USDC requires chaining: Multiply [SOL→USDC, BSOL→SOL]
|
|
386
|
+
const bsolReservePrice = resolveKaminoReservePriceIdOrThrow({
|
|
387
|
+
prices,
|
|
388
|
+
reserveMint: BSOL,
|
|
389
|
+
quoteInputMint: quotePath.quoteInputMint,
|
|
390
|
+
})
|
|
391
|
+
expect(extractPriceIds(bsolReservePrice)).toEqual([2n, 3n])
|
|
392
|
+
|
|
393
|
+
// USDC → USDC: passthrough
|
|
394
|
+
const usdcReservePrice = resolveKaminoReservePriceIdOrThrow({
|
|
395
|
+
prices,
|
|
396
|
+
reserveMint: USDC,
|
|
397
|
+
quoteInputMint: quotePath.quoteInputMint,
|
|
398
|
+
})
|
|
399
|
+
expect(extractPriceIds(usdcReservePrice)).toEqual([0n])
|
|
400
|
+
})
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
// ============================================================================
|
|
404
|
+
// extractPriceIds — parsing
|
|
405
|
+
// ============================================================================
|
|
406
|
+
|
|
407
|
+
describe("extractPriceIds", () => {
|
|
408
|
+
it("extracts from Simple (__kind format)", () => {
|
|
409
|
+
expect(extractPriceIds({ __kind: "Simple", priceId: 42n })).toEqual([42n])
|
|
410
|
+
})
|
|
411
|
+
|
|
412
|
+
it("extracts from Multiply (__kind format)", () => {
|
|
413
|
+
expect(extractPriceIds({ __kind: "Multiply", priceIds: [1n, 2n, 3n] })).toEqual([1n, 2n, 3n])
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
it("extracts from Simple (nested format)", () => {
|
|
417
|
+
expect(extractPriceIds({ simple: { priceId: 42n } })).toEqual([42n])
|
|
418
|
+
})
|
|
419
|
+
|
|
420
|
+
it("extracts from Multiply (nested format)", () => {
|
|
421
|
+
expect(extractPriceIds({ multiply: { priceIds: [1n, 2n, 3n] } })).toEqual([1n, 2n, 3n])
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
it("handles number priceIds in nested format", () => {
|
|
425
|
+
expect(extractPriceIds({ simple: { priceId: 42 } })).toEqual([42n])
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
it("throws for invalid shapes", () => {
|
|
429
|
+
expect(() => extractPriceIds(null)).toThrow("Invalid PriceId")
|
|
430
|
+
expect(() => extractPriceIds({})).toThrow("Unsupported PriceId shape")
|
|
431
|
+
})
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
// ============================================================================
|
|
435
|
+
// getPriceInputMintFromPriceId — reverse lookup
|
|
436
|
+
// ============================================================================
|
|
437
|
+
|
|
438
|
+
describe("getPriceInputMintFromPriceId", () => {
|
|
439
|
+
it("returns the priceMint of the last price in a Simple PriceId", () => {
|
|
440
|
+
const prices = makePrices([
|
|
441
|
+
makePriceEntry({ priceId: 5n, priceMint: SOL, underlyingMint: USDC }),
|
|
442
|
+
])
|
|
443
|
+
|
|
444
|
+
const mint = getPriceInputMintFromPriceId(prices, { __kind: "Simple", priceId: 5n })
|
|
445
|
+
expect(mint.equals(SOL)).toBe(true)
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
it("returns the priceMint of the last price in a Multiply PriceId", () => {
|
|
449
|
+
const prices = makePrices([
|
|
450
|
+
makePriceEntry({ priceId: 1n, priceMint: SOL, underlyingMint: USDC }),
|
|
451
|
+
makePriceEntry({ priceId: 2n, priceMint: PT_SOL, underlyingMint: SOL }),
|
|
452
|
+
])
|
|
453
|
+
|
|
454
|
+
// Multiply [1n, 2n] — last entry is priceId 2n, which has priceMint PT_SOL
|
|
455
|
+
const mint = getPriceInputMintFromPriceId(prices, { __kind: "Multiply", priceIds: [1n, 2n] })
|
|
456
|
+
expect(mint.equals(PT_SOL)).toBe(true)
|
|
457
|
+
})
|
|
458
|
+
|
|
459
|
+
it("throws when price entry not found", () => {
|
|
460
|
+
const prices = makePrices([])
|
|
461
|
+
|
|
462
|
+
expect(() =>
|
|
463
|
+
getPriceInputMintFromPriceId(prices, { __kind: "Simple", priceId: 999n })
|
|
464
|
+
).toThrow("Price entry not found for id 999")
|
|
465
|
+
})
|
|
466
|
+
})
|