@orca-so/whirlpool-sdk
Version:
Whirlpool SDK for the Orca protocol.
316 lines (315 loc) • 14.1 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OrcaDAL = void 0;
const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
const spl_token_1 = require("@solana/spl-token");
const parse_1 = require("./parse");
const address_1 = require("../utils/address");
/**
* Data access layer for accounts used by OrcaWhirlpool and OrcaPosition.
* The types of accounts that are being used are defined by CachedAccount.
* Includes internal cache that can be refreshed by the client.
*/
class OrcaDAL {
constructor(whirlpoolsConfig, programId, connection) {
this._cache = {};
this._userTokens = [];
this.whirlpoolsConfig = (0, address_1.toPubKey)(whirlpoolsConfig);
this.programId = (0, address_1.toPubKey)(programId);
this.connection = connection;
}
/*** Public Methods ***/
/**
* Retrieve a cached whirlpool account. Fetch from rpc on cache miss.
*
* @param address whirlpool address
* @param refresh force cache refresh
* @returns whirlpool account
*/
getPool(address, refresh) {
return __awaiter(this, void 0, void 0, function* () {
return this.get((0, address_1.toPubKey)(address), parse_1.ParsableWhirlpool, refresh);
});
}
/**
* Retrieve a cached position account. Fetch from rpc on cache miss.
*
* @param address position address
* @param refresh force cache refresh
* @returns position account
*/
getPosition(address, refresh) {
return __awaiter(this, void 0, void 0, function* () {
return this.get((0, address_1.toPubKey)(address), parse_1.ParsablePosition, refresh);
});
}
/**
* Retrieve a cached tick array account. Fetch from rpc on cache miss.
*
* @param address tick array address
* @param refresh force cache refresh
* @returns tick array account
*/
getTickArray(address, refresh) {
return __awaiter(this, void 0, void 0, function* () {
return this.get((0, address_1.toPubKey)(address), parse_1.ParsableTickArray, refresh);
});
}
/**
* Retrieve a cached token info account. Fetch from rpc on cache miss.
*
* @param address token info address
* @param refresh force cache refresh
* @returns token info account
*/
getTokenInfo(address, refresh) {
return __awaiter(this, void 0, void 0, function* () {
return this.get((0, address_1.toPubKey)(address), parse_1.ParsableTokenInfo, refresh);
});
}
/**
* Retrieve a cached mint info account. Fetch from rpc on cache miss.
*
* @param address mint info address
* @param refresh force cache refresh
* @returns mint info account
*/
getMintInfo(address, refresh) {
return __awaiter(this, void 0, void 0, function* () {
return this.get((0, address_1.toPubKey)(address), parse_1.ParsableMintInfo, refresh);
});
}
/**
* Retrieve a cached whirlpool config account. Fetch from rpc on cache miss.
*
* @param address whirlpool config address
* @param refresh force cache refresh
* @returns whirlpool config account
*/
getConfig(address, refresh) {
return __awaiter(this, void 0, void 0, function* () {
return this.get((0, address_1.toPubKey)(address), parse_1.ParsableWhirlpoolsConfig, refresh);
});
}
/**
* Retrieve a list of cached whirlpool accounts. Fetch from rpc for cache misses.
*
* @param addresses whirlpool addresses
* @param refresh force cache refresh
* @returns whirlpool accounts
*/
listPools(addresses, refresh) {
return __awaiter(this, void 0, void 0, function* () {
return this.list((0, address_1.toPubKeys)(addresses), parse_1.ParsableWhirlpool, refresh);
});
}
/**
* Retrieve a list of cached position accounts. Fetch from rpc for cache misses.
*
* @param addresses position addresses
* @param refresh force cache refresh
* @returns position accounts
*/
listPositions(addresses, refresh) {
return __awaiter(this, void 0, void 0, function* () {
return this.list((0, address_1.toPubKeys)(addresses), parse_1.ParsablePosition, refresh);
});
}
/**
* Retrieve a list of cached tick array accounts. Fetch from rpc for cache misses.
*
* @param addresses tick array addresses
* @param refresh force cache refresh
* @returns tick array accounts
*/
listTickArrays(addresses, refresh) {
return __awaiter(this, void 0, void 0, function* () {
return this.list((0, address_1.toPubKeys)(addresses), parse_1.ParsableTickArray, refresh);
});
}
/**
* Retrieve a list of cached token info accounts. Fetch from rpc for cache misses.
*
* @param addresses token info addresses
* @param refresh force cache refresh
* @returns token info accounts
*/
listTokenInfos(addresses, refresh) {
return __awaiter(this, void 0, void 0, function* () {
return this.list((0, address_1.toPubKeys)(addresses), parse_1.ParsableTokenInfo, refresh);
});
}
/**
* Retrieve a list of cached mint info accounts. Fetch from rpc for cache misses.
*
* @param addresses mint info addresses
* @param refresh force cache refresh
* @returns mint info accounts
*/
listMintInfos(addresses, refresh) {
return __awaiter(this, void 0, void 0, function* () {
return this.list((0, address_1.toPubKeys)(addresses), parse_1.ParsableMintInfo, refresh);
});
}
/**
* Retrieve a list of tokens owned by the user.
*
* @param walletAddress user wallet address
* @param refresh foree cache refresh
* @returns user tokens
*/
listUserTokens(walletAddress, refresh) {
return __awaiter(this, void 0, void 0, function* () {
if (!this._userTokens || refresh) {
const filter = { programId: spl_token_1.TOKEN_PROGRAM_ID };
const { value } = yield this.connection.getParsedTokenAccountsByOwner((0, address_1.toPubKey)(walletAddress), filter);
const userTokens = value.map((accountInfo) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
return ({
address: accountInfo.pubkey,
amount: (_e = (_d = (_c = (_b = (_a = accountInfo.account) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.parsed) === null || _c === void 0 ? void 0 : _c.info) === null || _d === void 0 ? void 0 : _d.tokenAmount) === null || _e === void 0 ? void 0 : _e.amount,
decimals: (_k = (_j = (_h = (_g = (_f = accountInfo.account) === null || _f === void 0 ? void 0 : _f.data) === null || _g === void 0 ? void 0 : _g.parsed) === null || _h === void 0 ? void 0 : _h.info) === null || _j === void 0 ? void 0 : _j.tokenAmount) === null || _k === void 0 ? void 0 : _k.decimals,
mint: (_p = (_o = (_m = (_l = accountInfo.account) === null || _l === void 0 ? void 0 : _l.data) === null || _m === void 0 ? void 0 : _m.parsed) === null || _o === void 0 ? void 0 : _o.info) === null || _p === void 0 ? void 0 : _p.mint,
});
});
this._userTokens = userTokens;
return userTokens;
}
return this._userTokens;
});
}
/**
* Retrieve minimum balance for rent exemption of a Token Account;
*
* @param refresh force refresh of account rent exemption
* @returns minimum balance for rent exemption
*/
getAccountRentExempt(refresh = false) {
return __awaiter(this, void 0, void 0, function* () {
// This value should be relatively static or at least not break according to spec
// https://docs.solana.com/developing/programming-model/accounts#rent-exemption
if (!this._accountRentExempt || refresh) {
this._accountRentExempt = yield this.connection.getMinimumBalanceForRentExemption(spl_token_1.AccountLayout.span);
}
return this._accountRentExempt;
});
}
/**
* Update the cached value of all entities currently in the cache.
* Uses batched rpc request for network efficient fetch.
*/
refreshAll() {
return __awaiter(this, void 0, void 0, function* () {
const addresses = Object.keys(this._cache);
const data = yield this.bulkRequest(addresses);
for (const [idx, [key, cachedContent]] of Object.entries(this._cache).entries()) {
const entity = cachedContent.entity;
const value = entity.parse(data[idx]);
this._cache[key] = { entity, value };
}
});
}
/*** Private Methods ***/
/**
* Retrieve from cache or fetch from rpc, an account
*/
get(address, entity, refresh) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const key = address.toBase58();
const cachedValue = (_a = this._cache[key]) === null || _a === void 0 ? void 0 : _a.value;
if (cachedValue !== undefined && !refresh) {
return cachedValue;
}
const accountInfo = yield this.connection.getAccountInfo(address);
const accountData = accountInfo === null || accountInfo === void 0 ? void 0 : accountInfo.data;
const value = entity.parse(accountData);
this._cache[key] = { entity, value };
return value;
});
}
/**
* Retrieve from cache or fetch from rpc, a list of accounts
*/
list(addresses, entity, refresh) {
return __awaiter(this, void 0, void 0, function* () {
const keys = addresses.map((address) => address.toBase58());
const cachedValues = keys.map((key) => {
var _a;
return [
key,
refresh ? undefined : (_a = this._cache[key]) === null || _a === void 0 ? void 0 : _a.value,
];
});
/* Look for accounts not found in cache */
const undefinedAccounts = [];
cachedValues.forEach(([key, value], cacheIndex) => {
if (value === undefined) {
undefinedAccounts.push({ cacheIndex, key });
}
});
/* Fetch accounts not found in cache */
if (undefinedAccounts.length > 0) {
const data = yield this.bulkRequest(undefinedAccounts.map((account) => account.key));
undefinedAccounts.forEach(({ cacheIndex, key }, dataIndex) => {
var _a;
const value = entity.parse(data[dataIndex]);
(0, tiny_invariant_1.default)(((_a = cachedValues[cacheIndex]) === null || _a === void 0 ? void 0 : _a[1]) === undefined, "unexpected non-undefined value");
cachedValues[cacheIndex] = [key, value];
this._cache[key] = { entity, value };
});
}
const result = cachedValues
.map(([_, value]) => value)
.filter((value) => value !== undefined);
(0, tiny_invariant_1.default)(result.length === addresses.length, "not enough results fetched");
return result;
});
}
/**
* Make batch rpc request
*/
bulkRequest(addresses) {
return __awaiter(this, void 0, void 0, function* () {
const responses = [];
const chunk = 100; // getMultipleAccounts has limitation of 100 accounts per request
for (let i = 0; i < addresses.length; i += chunk) {
const addressesSubset = addresses.slice(i, i + chunk);
const res = this.connection._rpcRequest("getMultipleAccounts", [
addressesSubset,
{ commitment: this.connection.commitment },
]);
responses.push(res);
}
const combinedResult = [];
(yield Promise.all(responses)).forEach((res) => {
var _a;
(0, tiny_invariant_1.default)(!res.error, `bulkRequest result error: ${res.error}`);
(0, tiny_invariant_1.default)(!!((_a = res.result) === null || _a === void 0 ? void 0 : _a.value), "bulkRequest no value");
res.result.value.forEach((account) => {
if (!account || account.data[1] !== "base64") {
combinedResult.push(null);
}
else {
combinedResult.push(Buffer.from(account.data[0], account.data[1]));
}
});
});
(0, tiny_invariant_1.default)(combinedResult.length === addresses.length, "bulkRequest not enough results");
return combinedResult;
});
}
}
exports.OrcaDAL = OrcaDAL;