@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
283 lines (261 loc) • 8.2 kB
text/typescript
import { Address, TransactionSigner } from '@solana/kit';
import { ManagerConnectionPool } from './ManagerConnectionPool';
import { PROGRAM_ID as KLEND_PROGRAM_ID } from '../../@codegen/klend/programId';
import { PROGRAM_ID as KVAULT_PROGRAM_ID } from '../../@codegen/kvault/programId';
import { PROGRAM_ID as FARMS_PROGRAM_ID } from '@kamino-finance/farms-sdk/dist/@codegen/farms/programId';
import { STAGING_PROGRAM_ID as KLEND_STAGING_PROGRAM_ID } from '../../utils/constants';
import { noopSigner, parseKeypairFile } from './keypair';
import { Chain } from './rpc';
import { KaminoMarket } from '../../classes';
import { FARMS_GLOBAL_CONFIG_MAINNET, FARMS_GLOBAL_CONFIG_DEVNET } from '../../classes/farm_utils';
import {
KVAULT_STAGING_PROGRAM_ID,
KVAULT_DEVNET_PROGRAM_ID,
FARMS_STAGING_PROGRAM_ID,
FARMS_DEVNET_PROGRAM_ID,
} from '../utils/consts';
import { VaultState } from '../../@codegen/kvault/accounts';
export type Cluster = 'localnet' | 'devnet' | 'mainnet-beta';
export type SendTxMode = 'execute' | 'simulate' | 'multisig' | 'print';
export type SignerConfig = {
multisigSigner?: TransactionSigner;
admin?: TransactionSigner;
};
export type ProgramConfig = {
staging?: boolean;
devnet?: boolean;
klendProgramId?: Address;
kvaultProgramId?: Address;
farmsProgramId?: Address;
farmsGlobalConfig?: Address;
};
interface GetSignerProps {
market?: KaminoMarket;
useLendingMarketOwnerCached?: boolean;
vaultState?: VaultState;
useVaultPendingAdmin?: boolean;
useVaultAllocationAdmin?: boolean;
}
export class ManagerEnv {
c: ManagerConnectionPool;
cluster: Cluster;
signerConfig: SignerConfig;
klendProgramId: Address;
kvaultProgramId: Address;
farmsProgramId: Address;
farmsGlobalConfig: Address;
constructor(
c: ManagerConnectionPool,
cluster: Cluster,
signerConfig: SignerConfig,
klendProgramId: Address,
kvaultProgramId: Address,
farmsProgramId: Address,
farmsGlobalConfig: Address
) {
this.c = c;
this.cluster = cluster;
this.signerConfig = signerConfig;
this.klendProgramId = klendProgramId;
this.kvaultProgramId = kvaultProgramId;
this.farmsProgramId = farmsProgramId;
this.farmsGlobalConfig = farmsGlobalConfig;
}
async getSigner({
market,
useLendingMarketOwnerCached,
vaultState,
useVaultPendingAdmin,
useVaultAllocationAdmin,
}: GetSignerProps = {}): Promise<TransactionSigner> {
function matchesAdmin(config: SignerConfig, a: Address): boolean {
return config.admin !== undefined && config.admin.address === a;
}
if (vaultState) {
if (useVaultPendingAdmin) {
return matchesAdmin(this.signerConfig, vaultState.pendingAdmin)
? this.signerConfig.admin!
: noopSigner(vaultState.pendingAdmin);
} else if (useVaultAllocationAdmin) {
return matchesAdmin(this.signerConfig, vaultState.allocationAdmin)
? this.signerConfig.admin!
: noopSigner(vaultState.allocationAdmin);
} else {
return matchesAdmin(this.signerConfig, vaultState.vaultAdminAuthority)
? this.signerConfig.admin!
: noopSigner(vaultState.vaultAdminAuthority);
}
} else if (market) {
if (useLendingMarketOwnerCached) {
return matchesAdmin(this.signerConfig, market.state.lendingMarketOwnerCached)
? this.signerConfig.admin!
: noopSigner(market.state.lendingMarketOwnerCached);
} else {
return matchesAdmin(this.signerConfig, market.state.lendingMarketOwner)
? this.signerConfig.admin!
: noopSigner(market.state.lendingMarketOwner);
}
} else if (this.signerConfig.admin) {
return this.signerConfig.admin;
} else if (this.signerConfig.multisigSigner) {
return this.signerConfig.multisigSigner;
}
throw new Error(`No signer in config ${JSON.stringify(this.signerConfig)}`);
}
}
function resolveProgramId(
explicit: Address | undefined,
ids: { staging: Address; devnet?: Address; mainnet: Address },
staging: boolean,
devnet: boolean
): Address {
if (explicit) return explicit;
if (staging) return ids.staging;
if (devnet && ids.devnet) return ids.devnet;
return ids.mainnet;
}
function defaultProgramConfig(programConfig: ProgramConfig): Required<ProgramConfig> {
const staging = programConfig.staging ?? false;
const devnet = programConfig.devnet ?? false;
return {
staging,
devnet,
klendProgramId: resolveProgramId(
programConfig.klendProgramId,
{
staging: KLEND_STAGING_PROGRAM_ID,
mainnet: KLEND_PROGRAM_ID,
devnet: KLEND_PROGRAM_ID,
},
staging,
devnet
),
kvaultProgramId: resolveProgramId(
programConfig.kvaultProgramId,
{
staging: KVAULT_STAGING_PROGRAM_ID,
devnet: KVAULT_DEVNET_PROGRAM_ID,
mainnet: KVAULT_PROGRAM_ID,
},
staging,
devnet
),
farmsProgramId: resolveProgramId(
programConfig.farmsProgramId,
{
staging: FARMS_STAGING_PROGRAM_ID,
devnet: FARMS_DEVNET_PROGRAM_ID,
mainnet: FARMS_PROGRAM_ID,
},
staging,
devnet
),
farmsGlobalConfig: resolveProgramId(
programConfig.farmsGlobalConfig,
{
staging: FARMS_GLOBAL_CONFIG_MAINNET,
devnet: FARMS_GLOBAL_CONFIG_DEVNET,
mainnet: FARMS_GLOBAL_CONFIG_MAINNET,
},
staging,
devnet
),
};
}
export async function initEnv(
staging: boolean = false,
multisig: Address | undefined = undefined,
adminKeypairPath: string | undefined = undefined,
rpcUrl: string | undefined = undefined,
devnet: boolean = false
): Promise<ManagerEnv> {
if (staging && devnet) {
throw new Error('Cannot use both --staging and --devnet at the same time');
}
const config = defaultProgramConfig({
staging,
devnet,
});
let resolvedUrl: string;
if (rpcUrl) {
resolvedUrl = rpcUrl;
} else if (devnet && process.env.RPC_DEVNET) {
resolvedUrl = process.env.RPC_DEVNET;
} else if (process.env.RPC) {
resolvedUrl = process.env.RPC;
} else {
throw 'Must specify an RPC URL';
}
const resolvedAdminPath = adminKeypairPath ?? (devnet ? process.env.ADMIN_DEVNET : process.env.ADMIN);
let resolvedAdmin: TransactionSigner | undefined = undefined;
if (resolvedAdminPath) {
resolvedAdmin = await parseKeypairFile(resolvedAdminPath);
}
const rpcChain = parseRpcChain(resolvedUrl, devnet);
const c = new ManagerConnectionPool(rpcChain);
const multisigSigner = multisig ? noopSigner(multisig) : undefined;
const env: ManagerEnv = new ManagerEnv(
c,
rpcChain.name,
{
admin: resolvedAdmin,
multisigSigner,
},
config.klendProgramId,
config.kvaultProgramId,
config.farmsProgramId,
config.farmsGlobalConfig
);
console.log('\nSettings ⚙️');
console.log(`Multisig: ${multisig}`);
console.log(`Multisig signer: ${multisigSigner?.address}`);
console.log(`Admin: ${resolvedAdmin?.address}`);
console.log(`Rpc: ${resolvedUrl}`);
console.log(`klendProgramId: ${env.klendProgramId}`);
console.log(`kvaultProgramId: ${env.kvaultProgramId}`);
return env;
}
function parseRpcChain(rpcUrl: string, devnet: boolean = false): Chain {
let chain: Chain;
if (rpcUrl === 'localnet') {
chain = {
name: 'localnet',
endpoint: {
url: 'http://127.0.0.1:8899',
name: 'localnet',
},
wsEndpoint: {
name: 'localnet',
url: 'ws://127.0.0.1:8900',
},
multicastEndpoints: [],
};
} else if (devnet) {
chain = {
name: 'devnet',
endpoint: {
url: rpcUrl,
name: 'devnet',
},
wsEndpoint: {
url: rpcUrl.replace('https:', 'wss:'),
name: 'devnet-ws',
},
multicastEndpoints: [],
};
} else {
chain = {
name: 'mainnet-beta',
endpoint: {
url: rpcUrl,
name: 'mainnet-beta',
},
wsEndpoint: {
url: rpcUrl.replace('https:', 'wss:') + '/whirligig',
name: 'mainnet-beta-ws',
},
multicastEndpoints: [],
};
}
return chain;
}