@kamino-finance/klend-sdk
Version:
Typescript SDK for interacting with the Kamino Lending (klend) protocol
281 lines (236 loc) • 7.05 kB
text/typescript
import { PublicKey } from '@solana/web3.js';
import BN from 'bn.js';
export const NULL_PUBKEY = new PublicKey('nu11111111111111111111111111111111111111111');
export const COMPUTE_BUDGET_PROGRAM_ID = new PublicKey('ComputeBudget111111111111111111111111111111');
/**
* Helper function to check if a configured pubkey is null or default.
* @param pubkey
* @returns {boolean}
*/
export function isNotNullPubkey(pubkey: PublicKey): boolean {
return pubkey && !pubkey.equals(NULL_PUBKEY) && !pubkey.equals(PublicKey.default);
}
/**
* Custom set implementation for PublicKey
* Calling toString() on a PublicKey is pretty slow and pubkeys do not support `SameValueZero` comparison, so we use a custom set implementation where keys are compared with `PublicKey.equals(PublicKey)`
*/
export class PublicKeySet<T extends PublicKey> {
private items: T[] = [];
constructor(items: Array<T>) {
items.forEach((item) => this.add(item));
}
add(item: T): void {
if (!this.contains(item)) {
this.items.push(item);
}
}
remove(item: T): boolean {
const index = this.indexOf(item);
if (index == -1) {
return false;
}
this.items.splice(index, 1);
return true;
}
contains(item: T): boolean {
return this.indexOf(item) != -1;
}
private indexOf(item: T): number {
return this.items.findIndex((existingItem) => this.equals(existingItem, item));
}
private equals(item1: T, item2: T): boolean {
return item1.equals(item2);
}
isEmpty(): boolean {
return this.size() === 0;
}
size(): number {
return this.items.length;
}
toArray(): T[] {
return this.items.slice(); // Return a copy of the array to prevent external modifications
}
}
/**
* Custom map implementation for PublicKey, backed by a list for o(n) lookup speed
* Calling toString() on a PublicKey is pretty slow and pubkeys do not support `SameValueZero` comparison, so we use a custom map implementation where keys are compared with `PublicKey.equals(PublicKey)`
*/
export class PublicKeyMap<K extends PublicKey, V> {
private record: { key: K; value: V }[] = [];
set(key: K, value: V): void {
const index = this.findIndex(key);
if (index !== -1) {
// Update existing entry
this.record[index].value = value;
} else {
// Add new entry
this.record.push({ key, value });
}
}
get(key: K): V | undefined {
const index = this.findIndex(key);
return index !== -1 ? this.record[index].value : undefined;
}
has(key: K): boolean {
return this.findIndex(key) !== -1;
}
delete(key: K): void {
const index = this.findIndex(key);
if (index !== -1) {
this.record.splice(index, 1);
}
}
private findIndex(key: K): number {
return this.record.findIndex((entry) => entry.key.equals(key));
}
clear(): void {
this.record = [];
}
forEach(callback: (value: V, key: K) => void): void {
this.record.forEach((entry) => {
callback(entry.value, entry.key);
});
}
keys(): K[] {
return this.record.map((entry) => entry.key);
}
values(): V[] {
return this.record.map((entry) => entry.value);
}
entries(): [K, V][] {
return this.record.map((entry) => [entry.key, entry.value]);
}
isEmpty(): boolean {
return this.record.length === 0;
}
}
/**
* Custom map implementation for PublicKey, backed by a hashmap for o(1) lookup speed
* Calling toString() on a PublicKey is pretty slow and pubkeys do not support `SameValueZero` comparison, so we use a custom map implementation where keys are compared with `PublicKey.equals(PublicKey)`, and a hash is derived from the final 32 bits of the public key
*/
export class PubkeyHashMap<K extends PublicKey, V> implements Map<K, V> {
private buckets: Map<
number,
{
key: K;
value: V;
}[]
>;
size: number;
constructor(entries?: readonly (readonly [K, V])[] | null) {
this.buckets = new Map();
this.size = 0;
if (entries) {
for (const [key, value] of entries) {
this.set(key, value);
}
}
}
*[Symbol.iterator](): IterableIterator<[K, V]> {
for (const [, bucket] of this.buckets) {
for (const { key, value } of bucket) {
yield [key, value];
}
}
}
[Symbol.toStringTag]: string = 'PubkeyHashMap';
set(key: K, value: V): this {
const hash = new HashablePublicKey(key).hashCode();
const bucket = this.buckets.get(hash);
if (!bucket) {
this.buckets.set(hash, [{ key, value }]);
this.size++;
} else {
const entry = bucket.find((entry) => entry.key.equals(key));
if (entry) {
entry.value = value;
} else {
bucket.push({ key, value });
this.size++;
}
}
return this;
}
get(key: K): V | undefined {
const hash = new HashablePublicKey(key).hashCode();
const bucket = this.buckets.get(hash);
if (!bucket) {
return undefined;
}
const entry = bucket.find((entry) => entry.key.equals(key));
return entry ? entry.value : undefined;
}
has(key: K): boolean {
const hash = new HashablePublicKey(key).hashCode();
const bucket = this.buckets.get(hash);
if (!bucket) {
return false;
}
const entry = bucket.find((entry) => entry.key.equals(key));
return !!entry;
}
delete(key: K): boolean {
const hash = new HashablePublicKey(key).hashCode();
const bucket = this.buckets.get(hash);
if (!bucket) {
return false;
}
const index = bucket.findIndex((entry) => entry.key.equals(key));
if (index === -1) {
return false;
}
bucket.splice(index, 1);
if (bucket.length === 0) {
this.buckets.delete(hash);
}
this.size--;
return true;
}
clear(): void {
this.buckets = new Map();
this.size = 0;
}
isEmpty(): boolean {
return this.size === 0;
}
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void {
this.buckets.forEach((bucket) => {
bucket.forEach((entry) => {
callbackfn(entry.value, entry.key, this);
}, thisArg);
}, thisArg);
}
*keys(): IterableIterator<K> {
for (const [key] of this) {
yield key;
}
}
*values(): IterableIterator<V> {
for (const [, value] of this) {
yield value;
}
}
entries(): IterableIterator<[K, V]> {
return this[Symbol.iterator]();
}
}
export class HashablePublicKey extends PublicKey implements IEquality<HashablePublicKey> {
// We only use the last 32 bits of the public key for hashing
static MASK = new BN(1).shln(32).subn(1);
constructor(value: PublicKey | string | Buffer | Uint8Array | Array<number>) {
super(value);
}
hashCode(): number {
let hash = 13;
hash = hash * 7 + this.getBN().clone().iuand(HashablePublicKey.MASK).toNumber();
return hash;
}
private getBN(): BN {
//@ts-ignore
return this._bn;
}
}
interface IEquality<T extends IEquality<T>> {
equals(other: T): boolean;
hashCode(): number;
}