@glamsystems/glam-cli 0.1.34 → 0.1.35

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.
Files changed (2) hide show
  1. package/main.js +259 -35
  2. package/package.json +2 -2
package/main.js CHANGED
@@ -275,12 +275,15 @@ class StateModel extends StateIdlModel {
275
275
  // @ts-ignore
276
276
  const value = Object.values(param.value)[0].val;
277
277
  // Ledger is a mint param but we store it on the state model
278
- if (Object.keys(stateAccount.accountType)[0] === "fund") {
279
- if (name === "ledger") {
280
- stateModel["ledger"] = value;
281
- }
278
+ if (name === "ledger") {
279
+ stateModel["ledger"] = value;
280
+ }
281
+ else if (name === "redemptionNotifyAndSettle") {
282
+ mintIdlModel["notifyAndSettle"] = value;
283
+ }
284
+ else {
285
+ mintIdlModel[name] = value;
282
286
  }
283
- mintIdlModel[name] = value;
284
287
  });
285
288
  if (openfundsMetadataAccount) {
286
289
  const mintOpenfundsFields = {};
@@ -763,7 +766,7 @@ const blockhash_1 = __webpack_require__(20);
763
766
  const glamPDAs_1 = __webpack_require__(21);
764
767
  const DEFAULT_PRIORITY_FEE = 10000; // microLamports
765
768
  const LOOKUP_TABLES = [
766
- new web3_js_1.PublicKey("284iwGtA9X9aLy3KsyV8uT2pXLARhYbiSi5SiM2g47M2"), // kamino
769
+ new web3_js_1.PublicKey("284iwGtA9X9aLy3KsyV8uT2pXLARhYbiSi5SiM2g47M2"), // kamino lending
767
770
  new web3_js_1.PublicKey("D9cnvzswDikQDf53k4HpQ3KJ9y1Fv3HGGDFYMXnK5T6c"), // drift
768
771
  new web3_js_1.PublicKey("EiWSskK5HXnBTptiS5DH6gpAJRVNQ3cAhTKBGaiaysAb"), // drift
769
772
  ];
@@ -1353,6 +1356,7 @@ exports.BaseClient = BaseClient;
1353
1356
 
1354
1357
  Object.defineProperty(exports, "__esModule", ({ value: true }));
1355
1358
  exports.setsAreEqual = exports.getSimulationResult = exports.parseMeteoraPosition = exports.fetchMeteoraPositions = exports.getStakeAccountsWithStates = exports.findStakeAccounts = void 0;
1359
+ exports.getProgramAccountsV2 = getProgramAccountsV2;
1356
1360
  exports.getTokenAccountsByOwner = getTokenAccountsByOwner;
1357
1361
  exports.getSolAndTokenBalances = getSolAndTokenBalances;
1358
1362
  exports.parseProgramLogs = parseProgramLogs;
@@ -1362,6 +1366,78 @@ const dlmm_1 = __webpack_require__(15);
1362
1366
  const anchor_1 = __webpack_require__(4);
1363
1367
  const glamExports_1 = __webpack_require__(3);
1364
1368
  const spl_token_1 = __webpack_require__(8);
1369
+ // Example response from Helius:
1370
+ // {
1371
+ // "jsonrpc": "2.0",
1372
+ // "id": "1",
1373
+ // "result": {
1374
+ // "accounts": [
1375
+ // {
1376
+ // "pubkey": "CxELquR1gPP8wHe33gZ4QxqGB3sZ9RSwsJ2KshVewkFY",
1377
+ // "account": {
1378
+ // "lamports": 15298080,
1379
+ // "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
1380
+ // "data": [
1381
+ // "2R9jLfiAQ9bgdcw6h8s44439",
1382
+ // "base64"
1383
+ // ],
1384
+ // "executable": false,
1385
+ // "rentEpoch": 28,
1386
+ // "space": 165
1387
+ // }
1388
+ // }
1389
+ // ],
1390
+ // "paginationKey": "8WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
1391
+ // "totalResults": 25000
1392
+ // }
1393
+ // }
1394
+ async function getProgramAccountsV2(programId, limit = 100, filters) {
1395
+ const heliusApiKey = process.env.NEXT_PUBLIC_HELIUS_API_KEY || process.env.HELIUS_API_KEY;
1396
+ let allAccounts = [];
1397
+ let paginationKey = null;
1398
+ let encoding = "base64";
1399
+ do {
1400
+ const response = await fetch(`https://mainnet.helius-rpc.com/?api-key=${heliusApiKey}`, {
1401
+ method: "POST",
1402
+ headers: { "Content-Type": "application/json" },
1403
+ body: JSON.stringify({
1404
+ jsonrpc: "2.0",
1405
+ id: "1",
1406
+ method: "getProgramAccountsV2",
1407
+ params: [
1408
+ programId.toBase58(),
1409
+ {
1410
+ encoding,
1411
+ filters,
1412
+ limit,
1413
+ ...(paginationKey && { paginationKey }),
1414
+ },
1415
+ ],
1416
+ }),
1417
+ });
1418
+ const data = await response.json();
1419
+ data.result.accounts.forEach(({ pubkey, account }) => {
1420
+ const [acountData, encoding] = account.data;
1421
+ let decodedData;
1422
+ if (encoding === "base64") {
1423
+ decodedData = Buffer.from(acountData, "base64");
1424
+ }
1425
+ if (!decodedData) {
1426
+ throw new Error("Failed to decode base64 account data");
1427
+ }
1428
+ allAccounts.push({
1429
+ pubkey: new web3_js_1.PublicKey(pubkey),
1430
+ account: {
1431
+ ...account,
1432
+ owner: new web3_js_1.PublicKey(account.owner),
1433
+ data: decodedData,
1434
+ },
1435
+ });
1436
+ });
1437
+ paginationKey = data.result.paginationKey;
1438
+ } while (paginationKey);
1439
+ return allAccounts;
1440
+ }
1365
1441
  /**
1366
1442
  * Fetches all the token accounts owned by the specified pubkey.
1367
1443
  */
@@ -5823,7 +5899,6 @@ class MintClient {
5823
5899
  });
5824
5900
  }
5825
5901
  async update(mintModel, txOptions = {}) {
5826
- // @ts-ignore
5827
5902
  const tx = await this.base.program.methods
5828
5903
  .updateMint(0, new models_1.MintIdlModel(mintModel))
5829
5904
  .accounts({
@@ -5856,6 +5931,19 @@ class MintClient {
5856
5931
  const vTx = await this.base.intoVersionedTransaction(tx, txOptions);
5857
5932
  return await this.base.sendAndConfirm(vTx);
5858
5933
  }
5934
+ async setPermissionlessFulfill(enabled, txOptions = {}) {
5935
+ const stateModel = await this.base.fetchStateModel();
5936
+ const notifyAndSettle = stateModel.mints?.[0]?.notifyAndSettle;
5937
+ if (!notifyAndSettle) {
5938
+ throw new Error("Mint does not have notifyAndSettle configured.");
5939
+ }
5940
+ return await this.update({
5941
+ notifyAndSettle: {
5942
+ ...notifyAndSettle,
5943
+ permissionlessFulfillment: enabled,
5944
+ },
5945
+ }, txOptions);
5946
+ }
5859
5947
  async closeMintIx() {
5860
5948
  return await this.base.program.methods
5861
5949
  .closeMint(0)
@@ -6087,6 +6175,7 @@ const borsh = tslib_1.__importStar(__webpack_require__(25));
6087
6175
  const spl_token_1 = __webpack_require__(8);
6088
6176
  const constants_1 = __webpack_require__(10);
6089
6177
  const kaminoLayouts_1 = __webpack_require__(36);
6178
+ const helpers_1 = __webpack_require__(14);
6090
6179
  const DEFAULT_OBLIGATION_ARGS = { tag: 0, id: 0 };
6091
6180
  const EVENT_AUTHORITY = new web3_js_1.PublicKey("24tHwQyJJ9akVXxnvkekGfAoeUJXXS7mE6kQNioNySsK");
6092
6181
  class KaminoLendingClient {
@@ -6907,19 +6996,30 @@ class KaminoFarmClient {
6907
6996
  this.farmVaultsAuthority = (farm) => web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("authority"), farm.toBuffer()], constants_1.KAMINO_FARM_PROGRAM)[0];
6908
6997
  this.rewardsTreasuryVault = (globalConfig, mint) => web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("tvault"), globalConfig.toBuffer(), mint.toBuffer()], constants_1.KAMINO_FARM_PROGRAM)[0];
6909
6998
  }
6910
- async findAndParseFarmStates(owner) {
6911
- const accounts = await this.base.provider.connection.getProgramAccounts(constants_1.KAMINO_FARM_PROGRAM, {
6912
- filters: [
6913
- { dataSize: 920 },
6914
- { memcmp: { offset: 48, bytes: owner.toBase58() } },
6915
- ],
6916
- });
6917
- return accounts.map((account) => {
6918
- const data = account.account.data;
6919
- const farmState = new web3_js_1.PublicKey(data.subarray(16, 48));
6999
+ async findAndParseUserStates(owner) {
7000
+ const accounts = await (0, helpers_1.getProgramAccountsV2)(constants_1.KAMINO_FARM_PROGRAM, 10, [
7001
+ { dataSize: 920 },
7002
+ { memcmp: { offset: 48, bytes: owner.toBase58() } },
7003
+ ]);
7004
+ return accounts.map(({ pubkey, account }) => {
7005
+ // farmState: [16, 48]
7006
+ // owner: [48, 80]
7007
+ // isFarmDelegated + padding: [80, 88]
7008
+ // rewardsTallyScaled: [88, 248]
7009
+ // unclaimedRewards[0..10]: [248, 328]
7010
+ const farmState = new web3_js_1.PublicKey(account.data.subarray(16, 48));
7011
+ const rewardsOffset = 248;
7012
+ const numRewards = 10;
7013
+ const rewardSize = 8;
7014
+ const rewardsData = account.data.subarray(rewardsOffset, rewardsOffset + numRewards * rewardSize);
7015
+ const unclaimedRewards = Array.from({ length: numRewards }, (_, i) => {
7016
+ const rewardData = rewardsData.subarray(i * rewardSize, (i + 1) * rewardSize);
7017
+ return new anchor_1.BN(rewardData, "le");
7018
+ });
6920
7019
  return {
6921
- userFarmState: account.pubkey,
7020
+ userState: pubkey,
6922
7021
  farmState,
7022
+ unclaimedRewards,
6923
7023
  };
6924
7024
  });
6925
7025
  }
@@ -6974,22 +7074,25 @@ class KaminoFarmClient {
6974
7074
  }
6975
7075
  async harvestTx(txOptions = {}) {
6976
7076
  const glamSigner = txOptions.signer || this.base.getSigner();
6977
- const vault = this.base.vaultPda;
6978
- const farmStates = await this.findAndParseFarmStates(vault);
7077
+ const farmStates = await this.findAndParseUserStates(this.base.vaultPda);
6979
7078
  const parsedFarms = await this.fetchAndParseFarms(farmStates.map((f) => f.farmState));
6980
7079
  const tx = new web3_js_1.Transaction();
6981
- for (const { userFarmState, farmState } of farmStates) {
7080
+ console.log("Building transaction to harvest the following rewards:");
7081
+ for (const { userState, farmState, unclaimedRewards } of farmStates) {
6982
7082
  const { globalConfig, rewards } = parsedFarms.get(farmState.toBase58());
6983
7083
  for (const { index, mint, tokenProgram, rewardsVault } of rewards) {
6984
- console.log("Reward token:", mint.toBase58());
7084
+ if (unclaimedRewards[index].eq(new anchor_1.BN(0))) {
7085
+ continue;
7086
+ }
7087
+ console.log(`userState: ${userState}, farmState: ${farmState}, unclaimedReward: ${unclaimedRewards[index]}, token: ${mint}`);
6985
7088
  const vaultAta = this.base.getVaultAta(mint, tokenProgram);
6986
- const createAtaIx = (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(glamSigner, vaultAta, vault, mint, tokenProgram);
7089
+ const createAtaIx = (0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(glamSigner, vaultAta, this.base.vaultPda, mint, tokenProgram);
6987
7090
  const harvestIx = await this.base.program.methods
6988
7091
  .kaminoFarmHarvestReward(new anchor_1.BN(index))
6989
7092
  .accounts({
6990
7093
  glamState: this.base.statePda,
6991
7094
  glamSigner,
6992
- userState: userFarmState,
7095
+ userState,
6993
7096
  farmState,
6994
7097
  globalConfig,
6995
7098
  rewardMint: mint,
@@ -7004,6 +7107,9 @@ class KaminoFarmClient {
7004
7107
  tx.add(createAtaIx, harvestIx);
7005
7108
  }
7006
7109
  }
7110
+ if (tx.instructions.length === 0) {
7111
+ throw new Error("No rewards to harvest");
7112
+ }
7007
7113
  const vTx = await this.base.intoVersionedTransaction(tx, txOptions);
7008
7114
  return vTx;
7009
7115
  }
@@ -7504,16 +7610,48 @@ class InvestClient {
7504
7610
  constructor(base) {
7505
7611
  this.base = base;
7506
7612
  }
7613
+ /**
7614
+ * Subscribe to a tokenized vault
7615
+ *
7616
+ * @param asset Deposit asset
7617
+ * @param amount
7618
+ * @param mintId
7619
+ * @param queued by default false, set to true to subscribe in queued mode
7620
+ * @param txOptions
7621
+ * @returns
7622
+ */
7507
7623
  async subscribe(asset, amount, mintId = 0, queued = false, txOptions = {}) {
7508
7624
  const tx = await (queued
7509
7625
  ? this.queuedSubscribeTx(asset, amount, mintId, txOptions)
7510
7626
  : this.subscribeTx(asset, amount, mintId, txOptions));
7511
7627
  return await this.base.sendAndConfirm(tx);
7512
7628
  }
7629
+ /**
7630
+ * Request to redeem share tokens of a tokenized vault in queued mode
7631
+ *
7632
+ * @param amount
7633
+ * @param mintId
7634
+ * @param txOptions
7635
+ * @returns
7636
+ */
7513
7637
  async queuedRedeem(amount, mintId = 0, txOptions = {}) {
7514
7638
  const tx = await this.queuedRedeemTx(amount, mintId, txOptions);
7515
7639
  return await this.base.sendAndConfirm(tx);
7516
7640
  }
7641
+ /**
7642
+ * Redeem share tokens of a tokenized vault instantly. Preconditions:
7643
+ * 1. The vault must allow permissionless fulfillment
7644
+ * 2. The vault must have sufficient liquidity
7645
+ *
7646
+ * @param amount
7647
+ * @param mintId
7648
+ * @param txOptions
7649
+ * @returns
7650
+ */
7651
+ async instantRedeem(amount, mintId = 0, txOptions = {}) {
7652
+ const tx = await this.instantRedeemTx(amount, mintId, txOptions);
7653
+ return await this.base.sendAndConfirm(tx);
7654
+ }
7517
7655
  async fulfill(mintId = 0, txOptions = {}) {
7518
7656
  const tx = await this.fulfillTx(mintId, txOptions);
7519
7657
  return await this.base.sendAndConfirm(tx);
@@ -7563,7 +7701,6 @@ class InvestClient {
7563
7701
  signerPolicy = (0, glamPDAs_1.getAccountPolicyPda)(this.base.getMintAta(signer));
7564
7702
  console.log(`signerPolicy: ${signerPolicy} for signer ${signer} and token account ${mintTo}`);
7565
7703
  }
7566
- // @ts-ignore
7567
7704
  const tx = await this.base.program.methods
7568
7705
  .subscribe(0, amount)
7569
7706
  .accounts({
@@ -7617,6 +7754,67 @@ class InvestClient {
7617
7754
  .transaction();
7618
7755
  return await this.base.intoVersionedTransaction(tx, txOptions);
7619
7756
  }
7757
+ async instantRedeemTx(amount, mintId = 0, txOptions = {}) {
7758
+ if (mintId !== 0) {
7759
+ throw new Error("mintId must be 0");
7760
+ }
7761
+ // Instant redemption flow is realized by enqueueing a redemption, fulfilling it, and then claiming the tokens in a single transaction.
7762
+ const preInstructions = txOptions.preInstructions || [];
7763
+ const signer = txOptions.signer || this.base.getSigner();
7764
+ const glamMint = this.base.mintPda;
7765
+ const stateModel = await this.base.fetchStateModel();
7766
+ const baseAsset = stateModel.baseAsset;
7767
+ const signerAta = this.base.getAta(baseAsset, signer);
7768
+ const fulfillIx = await this.base.program.methods
7769
+ .fulfill(mintId)
7770
+ .accounts({
7771
+ glamState: this.base.statePda,
7772
+ glamMint,
7773
+ signer,
7774
+ asset: baseAsset,
7775
+ depositTokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
7776
+ })
7777
+ .instruction();
7778
+ preInstructions.push((0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(signer, signerAta, signer, baseAsset));
7779
+ const claimIx = await this.base.program.methods
7780
+ .claim(0)
7781
+ .accounts({
7782
+ glamState: this.base.statePda,
7783
+ signer,
7784
+ tokenMint: baseAsset,
7785
+ claimTokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
7786
+ })
7787
+ .instruction();
7788
+ const remainingAccounts = [];
7789
+ if (await this.base.isLockupEnabled()) {
7790
+ const extraMetasAccount = this.base.extraMetasPda;
7791
+ const signerPolicy = (0, glamPDAs_1.getAccountPolicyPda)(this.base.getMintAta(signer));
7792
+ const escrow = this.base.escrowPda;
7793
+ const escrowPolicy = (0, glamPDAs_1.getAccountPolicyPda)(this.base.getMintAta(escrow));
7794
+ remainingAccounts.push(...[
7795
+ extraMetasAccount,
7796
+ signerPolicy,
7797
+ escrowPolicy,
7798
+ constants_1.TRANSFER_HOOK_PROGRAM,
7799
+ ]);
7800
+ }
7801
+ const tx = await this.base.program.methods
7802
+ .queuedRedeem(0, amount)
7803
+ .accounts({
7804
+ glamState: this.base.statePda,
7805
+ glamMint,
7806
+ signer,
7807
+ })
7808
+ .remainingAccounts(remainingAccounts.map((pubkey) => ({
7809
+ pubkey,
7810
+ isSigner: false,
7811
+ isWritable: false,
7812
+ })))
7813
+ .preInstructions(preInstructions)
7814
+ .postInstructions([fulfillIx, claimIx])
7815
+ .transaction();
7816
+ return await this.base.intoVersionedTransaction(tx, txOptions);
7817
+ }
7620
7818
  async queuedRedeemTx(amount, mintId = 0, txOptions = {}) {
7621
7819
  if (mintId !== 0) {
7622
7820
  throw new Error("mintId must be 0");
@@ -8521,6 +8719,7 @@ class CliConfig {
8521
8719
  }
8522
8720
  process.env.ANCHOR_PROVIDER_URL = cliConfig.json_rpc_url;
8523
8721
  process.env.ANCHOR_WALLET = cliConfig.keypair_path;
8722
+ process.env.HELIUS_API_KEY = cliConfig.priority_fee?.helius_api_key;
8524
8723
  return cliConfig;
8525
8724
  }
8526
8725
  catch (err) {
@@ -10083,17 +10282,28 @@ function installInvestCommands(tokenized, glamClient, cliConfig, txOptions) {
10083
10282
  });
10084
10283
  tokenized
10085
10284
  .command("redeem <amount>")
10285
+ .option("-i, --instant", "Redeem share tokens instantly", false)
10286
+ .option("-y, --yes", "Skip confirmation prompt", false)
10086
10287
  .description("Request to redeem share tokens")
10087
- .option("-y, --yes", "Skip confirmation prompt")
10088
- .action(async (amount, options) => {
10288
+ .action(async (amount, { instant, yes }) => {
10089
10289
  const amountBN = new anchor_1.BN(parseFloat(amount) * web3_js_1.LAMPORTS_PER_SOL);
10090
- options?.yes ||
10091
- (await (0, utils_1.confirmOperation)(`Confirm queued redemption of ${amount} shares?`));
10290
+ yes ||
10291
+ (await (0, utils_1.confirmOperation)(`Confirm ${instant ? "instant" : "queued"} redemption of ${amount} shares?`));
10092
10292
  try {
10093
- const txSig = await glamClient.invest.queuedRedeem(amountBN, 0, {
10094
- ...txOptions,
10095
- });
10096
- console.log(`${glamClient.signer} requested to redeem:`, txSig);
10293
+ let txSig;
10294
+ if (instant) {
10295
+ const preInstructions = await glamClient.price.priceVaultIxs(glam_sdk_1.PriceDenom.SOL);
10296
+ const lookupTables = glamClient.price.lookupTables;
10297
+ txSig = await glamClient.invest.instantRedeem(amountBN, 0, {
10298
+ ...txOptions,
10299
+ preInstructions,
10300
+ lookupTables,
10301
+ });
10302
+ }
10303
+ else {
10304
+ txSig = await glamClient.invest.queuedRedeem(amountBN, 0, txOptions);
10305
+ }
10306
+ console.log(`${glamClient.signer} ${instant ? "instantly" : "queued"} redeemed:`, txSig);
10097
10307
  }
10098
10308
  catch (e) {
10099
10309
  console.error((0, utils_1.parseTxError)(e));
@@ -10132,6 +10342,20 @@ function installInvestCommands(tokenized, glamClient, cliConfig, txOptions) {
10132
10342
  throw e;
10133
10343
  }
10134
10344
  });
10345
+ tokenized
10346
+ .command("set-permissionless-fulfill")
10347
+ .argument("<enabled>", "Enable or disable permissionless fulfillment", (v) => v === "true" || v === "1", false)
10348
+ .description("Enable or disable permissionless fulfillment")
10349
+ .action(async (enabled) => {
10350
+ try {
10351
+ const txSig = await glamClient.mint.setPermissionlessFulfill(enabled);
10352
+ console.log(`Permissionless fulfillment set to ${enabled}:`, txSig);
10353
+ }
10354
+ catch (e) {
10355
+ console.error((0, utils_1.parseTxError)(e));
10356
+ process.exit(1);
10357
+ }
10358
+ });
10135
10359
  }
10136
10360
 
10137
10361
 
@@ -10150,7 +10374,7 @@ function installAltCommands(alt, glamClient, cliConfig, txOptions = {}) {
10150
10374
  .description("Create address lookup table (ALT) for the active GLAM")
10151
10375
  .action(async () => {
10152
10376
  try {
10153
- const response = await fetch(`https://rest2.glam.systems/v0/lut/vault/create?state=${glamClient.statePda}&payer=${glamClient.getSigner()}`);
10377
+ const response = await fetch(`https://api.glam.systems/v0/lut/vault/create?state=${glamClient.statePda}&payer=${glamClient.getSigner()}`);
10154
10378
  const data = await response.json();
10155
10379
  const table = data.tables[0];
10156
10380
  const b64Txs = data.tx;
@@ -10642,7 +10866,7 @@ program
10642
10866
  .hook("preSubcommand", async (thisCommand, actionCommand) => {
10643
10867
  await (0, idl_1.idlCheck)(glamClient);
10644
10868
  })
10645
- .version("0.1.34");
10869
+ .version("0.1.35");
10646
10870
  program
10647
10871
  .command("env")
10648
10872
  .description("Display current environment setup")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glamsystems/glam-cli",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "description": "CLI for interacting with the GLAM Protocol",
5
5
  "main": "./main.js",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "node": ">=20.18.0"
22
22
  },
23
23
  "dependencies": {
24
- "@glamsystems/glam-sdk": "0.1.34",
24
+ "@glamsystems/glam-sdk": "0.1.35",
25
25
  "commander": "^11.1.0",
26
26
  "inquirer": "^8.2.6",
27
27
  "@switchboard-xyz/common": "^3.0.0"