@renec-foundation/redex-sdk
Version:
Typescript SDK to interact with Orca's Whirlpool program.
329 lines (328 loc) • 14 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.AccountFetcher = void 0;
const common_sdk_1 = require("@orca-so/common-sdk");
const spl_token_1 = require("@solana/spl-token");
const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
const __1 = require("../..");
const parsing_1 = require("./parsing");
/**
* Data access layer to access Whirlpool related accounts
* Includes internal cache that can be refreshed by the client.
*
* @category Fetcher
*/
class AccountFetcher {
constructor(connection, cache) {
this._cache = {};
this.connection = connection;
this._cache = cache !== null && cache !== void 0 ? cache : {};
}
/*** Public Methods ***/
/**
* 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;
});
}
/**
* 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 = false) {
return __awaiter(this, void 0, void 0, function* () {
return this.get(common_sdk_1.AddressUtil.toPubKey(address), parsing_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 = false) {
return __awaiter(this, void 0, void 0, function* () {
return this.get(common_sdk_1.AddressUtil.toPubKey(address), parsing_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 = false) {
return __awaiter(this, void 0, void 0, function* () {
return this.get(common_sdk_1.AddressUtil.toPubKey(address), parsing_1.ParsableTickArray, refresh);
});
}
/**
* Retrieve a cached fee tier account. Fetch from rpc on cache miss.
*
* @param address fee tier address
* @param refresh force cache refresh
* @returns fee tier account
*/
getFeeTier(address, refresh = false) {
return __awaiter(this, void 0, void 0, function* () {
return this.get(common_sdk_1.AddressUtil.toPubKey(address), parsing_1.ParsableFeeTier, 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 = false) {
return __awaiter(this, void 0, void 0, function* () {
return this.get(common_sdk_1.AddressUtil.toPubKey(address), parsing_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 = false) {
return __awaiter(this, void 0, void 0, function* () {
return this.get(common_sdk_1.AddressUtil.toPubKey(address), parsing_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 = false) {
return __awaiter(this, void 0, void 0, function* () {
return this.get(common_sdk_1.AddressUtil.toPubKey(address), parsing_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(common_sdk_1.AddressUtil.toPubKeys(addresses), parsing_1.ParsableWhirlpool, refresh);
});
}
/**
* Retrieve a list of cached whirlpool addresses and accounts filtered by the given params using
* getProgramAccounts.
*
* @param params whirlpool filter params
* @returns tuple of whirlpool addresses and accounts
*/
listPoolsWithParams({ programId, configId, }) {
return __awaiter(this, void 0, void 0, function* () {
const filters = [
{ dataSize: __1.WHIRLPOOL_ACCOUNT_SIZE },
{
memcmp: __1.WHIRLPOOL_CODER.memcmp(__1.AccountName.Whirlpool, common_sdk_1.AddressUtil.toPubKey(configId).toBuffer()),
},
];
const accounts = yield this.connection.getProgramAccounts(common_sdk_1.AddressUtil.toPubKey(programId), {
filters,
});
const parsedAccounts = [];
accounts.forEach(({ pubkey, account }) => {
const parsedAccount = parsing_1.ParsableWhirlpool.parse(account.data);
(0, tiny_invariant_1.default)(!!parsedAccount, `could not parse whirlpool: ${pubkey.toBase58()}`);
parsedAccounts.push([pubkey, parsedAccount]);
this._cache[pubkey.toBase58()] = { entity: parsing_1.ParsableWhirlpool, value: parsedAccount };
});
return parsedAccounts;
});
}
/**
* 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(common_sdk_1.AddressUtil.toPubKeys(addresses), parsing_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(common_sdk_1.AddressUtil.toPubKeys(addresses), parsing_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(common_sdk_1.AddressUtil.toPubKeys(addresses), parsing_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(common_sdk_1.AddressUtil.toPubKeys(addresses), parsing_1.ParsableMintInfo, refresh);
});
}
/**
* 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.AccountFetcher = AccountFetcher;