@glamsystems/glam-sdk 0.1.34 → 0.1.36

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.
package/index.cjs.js CHANGED
@@ -14783,12 +14783,13 @@ class StateModel extends StateIdlModel {
14783
14783
  // @ts-ignore
14784
14784
  const value = Object.values(param.value)[0].val;
14785
14785
  // Ledger is a mint param but we store it on the state model
14786
- if (Object.keys(stateAccount.accountType)[0] === "fund") {
14787
- if (name === "ledger") {
14788
- stateModel["ledger"] = value;
14789
- }
14786
+ if (name === "ledger") {
14787
+ stateModel["ledger"] = value;
14788
+ } else if (name === "redemptionNotifyAndSettle") {
14789
+ mintIdlModel["notifyAndSettle"] = value;
14790
+ } else {
14791
+ mintIdlModel[name] = value;
14790
14792
  }
14791
- mintIdlModel[name] = value;
14792
14793
  });
14793
14794
  if (openfundsMetadataAccount) {
14794
14795
  const mintOpenfundsFields = {};
@@ -15028,6 +15029,82 @@ var ClusterNetwork = /*#__PURE__*/ function(ClusterNetwork) {
15028
15029
  return ClusterNetwork;
15029
15030
  }({});
15030
15031
 
15032
+ // Example response from Helius:
15033
+ // {
15034
+ // "jsonrpc": "2.0",
15035
+ // "id": "1",
15036
+ // "result": {
15037
+ // "accounts": [
15038
+ // {
15039
+ // "pubkey": "CxELquR1gPP8wHe33gZ4QxqGB3sZ9RSwsJ2KshVewkFY",
15040
+ // "account": {
15041
+ // "lamports": 15298080,
15042
+ // "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
15043
+ // "data": [
15044
+ // "2R9jLfiAQ9bgdcw6h8s44439",
15045
+ // "base64"
15046
+ // ],
15047
+ // "executable": false,
15048
+ // "rentEpoch": 28,
15049
+ // "space": 165
15050
+ // }
15051
+ // }
15052
+ // ],
15053
+ // "paginationKey": "8WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
15054
+ // "totalResults": 25000
15055
+ // }
15056
+ // }
15057
+ async function getProgramAccountsV2(programId, limit = 100, filters) {
15058
+ const heliusApiKey = process.env.NEXT_PUBLIC_HELIUS_API_KEY || process.env.HELIUS_API_KEY;
15059
+ let allAccounts = [];
15060
+ let paginationKey = null;
15061
+ let encoding = "base64";
15062
+ do {
15063
+ const response = await fetch(`https://mainnet.helius-rpc.com/?api-key=${heliusApiKey}`, {
15064
+ method: "POST",
15065
+ headers: {
15066
+ "Content-Type": "application/json"
15067
+ },
15068
+ body: JSON.stringify({
15069
+ jsonrpc: "2.0",
15070
+ id: "1",
15071
+ method: "getProgramAccountsV2",
15072
+ params: [
15073
+ programId.toBase58(),
15074
+ {
15075
+ encoding,
15076
+ filters,
15077
+ limit,
15078
+ ...paginationKey && {
15079
+ paginationKey
15080
+ }
15081
+ }
15082
+ ]
15083
+ })
15084
+ });
15085
+ const data = await response.json();
15086
+ data.result.accounts.forEach(({ pubkey, account })=>{
15087
+ const [acountData, encoding] = account.data;
15088
+ let decodedData;
15089
+ if (encoding === "base64") {
15090
+ decodedData = Buffer.from(acountData, "base64");
15091
+ }
15092
+ if (!decodedData) {
15093
+ throw new Error("Failed to decode base64 account data");
15094
+ }
15095
+ allAccounts.push({
15096
+ pubkey: new web3_js.PublicKey(pubkey),
15097
+ account: {
15098
+ ...account,
15099
+ owner: new web3_js.PublicKey(account.owner),
15100
+ data: decodedData
15101
+ }
15102
+ });
15103
+ });
15104
+ paginationKey = data.result.paginationKey;
15105
+ }while (paginationKey)
15106
+ return allAccounts;
15107
+ }
15031
15108
  /**
15032
15109
  * Fetches all the token accounts owned by the specified pubkey.
15033
15110
  */ async function getTokenAccountsByOwner(connection, owner) {
@@ -19428,7 +19505,8 @@ class StakingClient {
19428
19505
  const { programId: stakePoolProgram, poolMint, withdrawAuthority, feeAccount, tokenProgramId: tokenProgram, validatorList, reserveStake } = await this.getStakePoolAccountData(stakePool);
19429
19506
  const poolTokensFrom = this.base.getVaultAta(poolMint, tokenProgram);
19430
19507
  // The reserve stake account should NOT be used for withdrawals unless we have no other options.
19431
- const validatorStakeCandidates = (await getStakeAccountsWithStates(this.base.provider.connection, withdrawAuthority)).filter((s)=>!s.address.equals(reserveStake));
19508
+ // And only active validator stake accounts should be used.
19509
+ const validatorStakeCandidates = (await getStakeAccountsWithStates(this.base.provider.connection, withdrawAuthority)).filter((s)=>!s.address.equals(reserveStake) && s.state === "active");
19432
19510
  const validatorStakeAccount = validatorStakeCandidates.length === 0 ? reserveStake : validatorStakeCandidates[0].address;
19433
19511
  const [stakeAccount, createStakeAccountIx] = await this.createStakeAccount(glamSigner);
19434
19512
  const postInstructions = deactivate ? [
@@ -19857,7 +19935,6 @@ class MintClient {
19857
19935
  });
19858
19936
  }
19859
19937
  async update(mintModel, txOptions = {}) {
19860
- // @ts-ignore
19861
19938
  const tx = await this.base.program.methods.updateMint(0, new MintIdlModel(mintModel)).accounts({
19862
19939
  glamState: this.base.statePda,
19863
19940
  glamMint: this.base.mintPda
@@ -19881,6 +19958,19 @@ class MintClient {
19881
19958
  const vTx = await this.base.intoVersionedTransaction(tx, txOptions);
19882
19959
  return await this.base.sendAndConfirm(vTx);
19883
19960
  }
19961
+ async setPermissionlessFulfill(enabled, txOptions = {}) {
19962
+ const stateModel = await this.base.fetchStateModel();
19963
+ const notifyAndSettle = stateModel.mints?.[0]?.notifyAndSettle;
19964
+ if (!notifyAndSettle) {
19965
+ throw new Error("Mint does not have notifyAndSettle configured.");
19966
+ }
19967
+ return await this.update({
19968
+ notifyAndSettle: {
19969
+ ...notifyAndSettle,
19970
+ permissionlessFulfillment: enabled
19971
+ }
19972
+ }, txOptions);
19973
+ }
19884
19974
  async closeMintIx() {
19885
19975
  return await this.base.program.methods.closeMint(0).accounts({
19886
19976
  glamState: this.base.statePda,
@@ -21090,26 +21180,39 @@ class KaminoLendingClient {
21090
21180
  }
21091
21181
  }
21092
21182
  class KaminoFarmClient {
21093
- async findAndParseFarmStates(owner) {
21094
- const accounts = await this.base.provider.connection.getProgramAccounts(KAMINO_FARM_PROGRAM, {
21095
- filters: [
21096
- {
21097
- dataSize: 920
21098
- },
21099
- {
21100
- memcmp: {
21101
- offset: 48,
21102
- bytes: owner.toBase58()
21103
- }
21183
+ async findAndParseUserStates(owner) {
21184
+ const accounts = await getProgramAccountsV2(KAMINO_FARM_PROGRAM, 10, [
21185
+ {
21186
+ dataSize: 920
21187
+ },
21188
+ {
21189
+ memcmp: {
21190
+ offset: 48,
21191
+ bytes: owner.toBase58()
21104
21192
  }
21105
- ]
21106
- });
21107
- return accounts.map((account)=>{
21108
- const data = account.account.data;
21109
- const farmState = new web3_js.PublicKey(data.subarray(16, 48));
21193
+ }
21194
+ ]);
21195
+ return accounts.map(({ pubkey, account })=>{
21196
+ // farmState: [16, 48]
21197
+ // owner: [48, 80]
21198
+ // isFarmDelegated + padding: [80, 88]
21199
+ // rewardsTallyScaled: [88, 248]
21200
+ // unclaimedRewards[0..10]: [248, 328]
21201
+ const farmState = new web3_js.PublicKey(account.data.subarray(16, 48));
21202
+ const rewardsOffset = 248;
21203
+ const numRewards = 10;
21204
+ const rewardSize = 8;
21205
+ const rewardsData = account.data.subarray(rewardsOffset, rewardsOffset + numRewards * rewardSize);
21206
+ const unclaimedRewards = Array.from({
21207
+ length: numRewards
21208
+ }, (_, i)=>{
21209
+ const rewardData = rewardsData.subarray(i * rewardSize, (i + 1) * rewardSize);
21210
+ return new anchor.BN(rewardData, "le");
21211
+ });
21110
21212
  return {
21111
- userFarmState: account.pubkey,
21112
- farmState
21213
+ userState: pubkey,
21214
+ farmState,
21215
+ unclaimedRewards
21113
21216
  };
21114
21217
  });
21115
21218
  }
@@ -21169,20 +21272,23 @@ class KaminoFarmClient {
21169
21272
  }
21170
21273
  async harvestTx(txOptions = {}) {
21171
21274
  const glamSigner = txOptions.signer || this.base.getSigner();
21172
- const vault = this.base.vaultPda;
21173
- const farmStates = await this.findAndParseFarmStates(vault);
21275
+ const farmStates = await this.findAndParseUserStates(this.base.vaultPda);
21174
21276
  const parsedFarms = await this.fetchAndParseFarms(farmStates.map((f)=>f.farmState));
21175
21277
  const tx = new web3_js.Transaction();
21176
- for (const { userFarmState, farmState } of farmStates){
21278
+ console.log("Building transaction to harvest the following rewards:");
21279
+ for (const { userState, farmState, unclaimedRewards } of farmStates){
21177
21280
  const { globalConfig, rewards } = parsedFarms.get(farmState.toBase58());
21178
21281
  for (const { index, mint, tokenProgram, rewardsVault } of rewards){
21179
- console.log("Reward token:", mint.toBase58());
21282
+ if (unclaimedRewards[index].eq(new anchor.BN(0))) {
21283
+ continue;
21284
+ }
21285
+ console.log(`userState: ${userState}, farmState: ${farmState}, unclaimedReward: ${unclaimedRewards[index]}, token: ${mint}`);
21180
21286
  const vaultAta = this.base.getVaultAta(mint, tokenProgram);
21181
- const createAtaIx = splToken.createAssociatedTokenAccountIdempotentInstruction(glamSigner, vaultAta, vault, mint, tokenProgram);
21287
+ const createAtaIx = splToken.createAssociatedTokenAccountIdempotentInstruction(glamSigner, vaultAta, this.base.vaultPda, mint, tokenProgram);
21182
21288
  const harvestIx = await this.base.program.methods.kaminoFarmHarvestReward(new anchor.BN(index)).accounts({
21183
21289
  glamState: this.base.statePda,
21184
21290
  glamSigner,
21185
- userState: userFarmState,
21291
+ userState,
21186
21292
  farmState,
21187
21293
  globalConfig,
21188
21294
  rewardMint: mint,
@@ -21196,6 +21302,9 @@ class KaminoFarmClient {
21196
21302
  tx.add(createAtaIx, harvestIx);
21197
21303
  }
21198
21304
  }
21305
+ if (tx.instructions.length === 0) {
21306
+ throw new Error("No rewards to harvest");
21307
+ }
21199
21308
  const vTx = await this.base.intoVersionedTransaction(tx, txOptions);
21200
21309
  return vTx;
21201
21310
  }
@@ -21605,14 +21714,43 @@ class MeteoraDlmmClient {
21605
21714
  }
21606
21715
 
21607
21716
  class InvestClient {
21608
- async subscribe(asset, amount, mintId = 0, queued = false, txOptions = {}) {
21717
+ /**
21718
+ * Subscribe to a tokenized vault
21719
+ *
21720
+ * @param asset Deposit asset
21721
+ * @param amount
21722
+ * @param mintId
21723
+ * @param queued by default false, set to true to subscribe in queued mode
21724
+ * @param txOptions
21725
+ * @returns
21726
+ */ async subscribe(asset, amount, mintId = 0, queued = false, txOptions = {}) {
21609
21727
  const tx = await (queued ? this.queuedSubscribeTx(asset, amount, mintId, txOptions) : this.subscribeTx(asset, amount, mintId, txOptions));
21610
21728
  return await this.base.sendAndConfirm(tx);
21611
21729
  }
21612
- async queuedRedeem(amount, mintId = 0, txOptions = {}) {
21730
+ /**
21731
+ * Request to redeem share tokens of a tokenized vault in queued mode
21732
+ *
21733
+ * @param amount
21734
+ * @param mintId
21735
+ * @param txOptions
21736
+ * @returns
21737
+ */ async queuedRedeem(amount, mintId = 0, txOptions = {}) {
21613
21738
  const tx = await this.queuedRedeemTx(amount, mintId, txOptions);
21614
21739
  return await this.base.sendAndConfirm(tx);
21615
21740
  }
21741
+ /**
21742
+ * Redeem share tokens of a tokenized vault instantly. Preconditions:
21743
+ * 1. The vault must allow permissionless fulfillment
21744
+ * 2. The vault must have sufficient liquidity
21745
+ *
21746
+ * @param amount
21747
+ * @param mintId
21748
+ * @param txOptions
21749
+ * @returns
21750
+ */ async instantRedeem(amount, mintId = 0, txOptions = {}) {
21751
+ const tx = await this.instantRedeemTx(amount, mintId, txOptions);
21752
+ return await this.base.sendAndConfirm(tx);
21753
+ }
21616
21754
  async fulfill(mintId = 0, txOptions = {}) {
21617
21755
  const tx = await this.fulfillTx(mintId, txOptions);
21618
21756
  return await this.base.sendAndConfirm(tx);
@@ -21660,7 +21798,6 @@ class InvestClient {
21660
21798
  signerPolicy = getAccountPolicyPda(this.base.getMintAta(signer));
21661
21799
  console.log(`signerPolicy: ${signerPolicy} for signer ${signer} and token account ${mintTo}`);
21662
21800
  }
21663
- // @ts-ignore
21664
21801
  const tx = await this.base.program.methods.subscribe(0, amount).accounts({
21665
21802
  glamState: this.base.statePda,
21666
21803
  glamMint,
@@ -21702,6 +21839,58 @@ class InvestClient {
21702
21839
  }).preInstructions(preInstructions).postInstructions(postInstructions).transaction();
21703
21840
  return await this.base.intoVersionedTransaction(tx, txOptions);
21704
21841
  }
21842
+ async instantRedeemTx(amount, mintId = 0, txOptions = {}) {
21843
+ if (mintId !== 0) {
21844
+ throw new Error("mintId must be 0");
21845
+ }
21846
+ // Instant redemption flow is realized by enqueueing a redemption, fulfilling it, and then claiming the tokens in a single transaction.
21847
+ const preInstructions = txOptions.preInstructions || [];
21848
+ const signer = txOptions.signer || this.base.getSigner();
21849
+ const glamMint = this.base.mintPda;
21850
+ const stateModel = await this.base.fetchStateModel();
21851
+ const baseAsset = stateModel.baseAsset;
21852
+ const signerAta = this.base.getAta(baseAsset, signer);
21853
+ const fulfillIx = await this.base.program.methods.fulfill(mintId).accounts({
21854
+ glamState: this.base.statePda,
21855
+ glamMint,
21856
+ signer,
21857
+ asset: baseAsset,
21858
+ depositTokenProgram: splToken.TOKEN_PROGRAM_ID
21859
+ }).instruction();
21860
+ preInstructions.push(splToken.createAssociatedTokenAccountIdempotentInstruction(signer, signerAta, signer, baseAsset));
21861
+ const claimIx = await this.base.program.methods.claim(0).accounts({
21862
+ glamState: this.base.statePda,
21863
+ signer,
21864
+ tokenMint: baseAsset,
21865
+ claimTokenProgram: splToken.TOKEN_PROGRAM_ID
21866
+ }).instruction();
21867
+ const remainingAccounts = [];
21868
+ if (await this.base.isLockupEnabled()) {
21869
+ const extraMetasAccount = this.base.extraMetasPda;
21870
+ const signerPolicy = getAccountPolicyPda(this.base.getMintAta(signer));
21871
+ const escrow = this.base.escrowPda;
21872
+ const escrowPolicy = getAccountPolicyPda(this.base.getMintAta(escrow));
21873
+ remainingAccounts.push(...[
21874
+ extraMetasAccount,
21875
+ signerPolicy,
21876
+ escrowPolicy,
21877
+ TRANSFER_HOOK_PROGRAM
21878
+ ]);
21879
+ }
21880
+ const tx = await this.base.program.methods.queuedRedeem(0, amount).accounts({
21881
+ glamState: this.base.statePda,
21882
+ glamMint,
21883
+ signer
21884
+ }).remainingAccounts(remainingAccounts.map((pubkey)=>({
21885
+ pubkey,
21886
+ isSigner: false,
21887
+ isWritable: false
21888
+ }))).preInstructions(preInstructions).postInstructions([
21889
+ fulfillIx,
21890
+ claimIx
21891
+ ]).transaction();
21892
+ return await this.base.intoVersionedTransaction(tx, txOptions);
21893
+ }
21705
21894
  async queuedRedeemTx(amount, mintId = 0, txOptions = {}) {
21706
21895
  if (mintId !== 0) {
21707
21896
  throw new Error("mintId must be 0");
@@ -22649,6 +22838,7 @@ exports.getMintPda = getMintPda;
22649
22838
  exports.getOpenfundsPda = getOpenfundsPda;
22650
22839
  exports.getOrderParams = getOrderParams;
22651
22840
  exports.getPriorityFeeEstimate = getPriorityFeeEstimate;
22841
+ exports.getProgramAccountsV2 = getProgramAccountsV2;
22652
22842
  exports.getSimulationResult = getSimulationResult;
22653
22843
  exports.getSolAndTokenBalances = getSolAndTokenBalances;
22654
22844
  exports.getStakeAccountsWithStates = getStakeAccountsWithStates;
package/index.esm.js CHANGED
@@ -14763,12 +14763,13 @@ class StateModel extends StateIdlModel {
14763
14763
  // @ts-ignore
14764
14764
  const value = Object.values(param.value)[0].val;
14765
14765
  // Ledger is a mint param but we store it on the state model
14766
- if (Object.keys(stateAccount.accountType)[0] === "fund") {
14767
- if (name === "ledger") {
14768
- stateModel["ledger"] = value;
14769
- }
14766
+ if (name === "ledger") {
14767
+ stateModel["ledger"] = value;
14768
+ } else if (name === "redemptionNotifyAndSettle") {
14769
+ mintIdlModel["notifyAndSettle"] = value;
14770
+ } else {
14771
+ mintIdlModel[name] = value;
14770
14772
  }
14771
- mintIdlModel[name] = value;
14772
14773
  });
14773
14774
  if (openfundsMetadataAccount) {
14774
14775
  const mintOpenfundsFields = {};
@@ -15008,6 +15009,82 @@ var ClusterNetwork = /*#__PURE__*/ function(ClusterNetwork) {
15008
15009
  return ClusterNetwork;
15009
15010
  }({});
15010
15011
 
15012
+ // Example response from Helius:
15013
+ // {
15014
+ // "jsonrpc": "2.0",
15015
+ // "id": "1",
15016
+ // "result": {
15017
+ // "accounts": [
15018
+ // {
15019
+ // "pubkey": "CxELquR1gPP8wHe33gZ4QxqGB3sZ9RSwsJ2KshVewkFY",
15020
+ // "account": {
15021
+ // "lamports": 15298080,
15022
+ // "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
15023
+ // "data": [
15024
+ // "2R9jLfiAQ9bgdcw6h8s44439",
15025
+ // "base64"
15026
+ // ],
15027
+ // "executable": false,
15028
+ // "rentEpoch": 28,
15029
+ // "space": 165
15030
+ // }
15031
+ // }
15032
+ // ],
15033
+ // "paginationKey": "8WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
15034
+ // "totalResults": 25000
15035
+ // }
15036
+ // }
15037
+ async function getProgramAccountsV2(programId, limit = 100, filters) {
15038
+ const heliusApiKey = process.env.NEXT_PUBLIC_HELIUS_API_KEY || process.env.HELIUS_API_KEY;
15039
+ let allAccounts = [];
15040
+ let paginationKey = null;
15041
+ let encoding = "base64";
15042
+ do {
15043
+ const response = await fetch(`https://mainnet.helius-rpc.com/?api-key=${heliusApiKey}`, {
15044
+ method: "POST",
15045
+ headers: {
15046
+ "Content-Type": "application/json"
15047
+ },
15048
+ body: JSON.stringify({
15049
+ jsonrpc: "2.0",
15050
+ id: "1",
15051
+ method: "getProgramAccountsV2",
15052
+ params: [
15053
+ programId.toBase58(),
15054
+ {
15055
+ encoding,
15056
+ filters,
15057
+ limit,
15058
+ ...paginationKey && {
15059
+ paginationKey
15060
+ }
15061
+ }
15062
+ ]
15063
+ })
15064
+ });
15065
+ const data = await response.json();
15066
+ data.result.accounts.forEach(({ pubkey, account })=>{
15067
+ const [acountData, encoding] = account.data;
15068
+ let decodedData;
15069
+ if (encoding === "base64") {
15070
+ decodedData = Buffer.from(acountData, "base64");
15071
+ }
15072
+ if (!decodedData) {
15073
+ throw new Error("Failed to decode base64 account data");
15074
+ }
15075
+ allAccounts.push({
15076
+ pubkey: new PublicKey(pubkey),
15077
+ account: {
15078
+ ...account,
15079
+ owner: new PublicKey(account.owner),
15080
+ data: decodedData
15081
+ }
15082
+ });
15083
+ });
15084
+ paginationKey = data.result.paginationKey;
15085
+ }while (paginationKey)
15086
+ return allAccounts;
15087
+ }
15011
15088
  /**
15012
15089
  * Fetches all the token accounts owned by the specified pubkey.
15013
15090
  */ async function getTokenAccountsByOwner(connection, owner) {
@@ -19408,7 +19485,8 @@ class StakingClient {
19408
19485
  const { programId: stakePoolProgram, poolMint, withdrawAuthority, feeAccount, tokenProgramId: tokenProgram, validatorList, reserveStake } = await this.getStakePoolAccountData(stakePool);
19409
19486
  const poolTokensFrom = this.base.getVaultAta(poolMint, tokenProgram);
19410
19487
  // The reserve stake account should NOT be used for withdrawals unless we have no other options.
19411
- const validatorStakeCandidates = (await getStakeAccountsWithStates(this.base.provider.connection, withdrawAuthority)).filter((s)=>!s.address.equals(reserveStake));
19488
+ // And only active validator stake accounts should be used.
19489
+ const validatorStakeCandidates = (await getStakeAccountsWithStates(this.base.provider.connection, withdrawAuthority)).filter((s)=>!s.address.equals(reserveStake) && s.state === "active");
19412
19490
  const validatorStakeAccount = validatorStakeCandidates.length === 0 ? reserveStake : validatorStakeCandidates[0].address;
19413
19491
  const [stakeAccount, createStakeAccountIx] = await this.createStakeAccount(glamSigner);
19414
19492
  const postInstructions = deactivate ? [
@@ -19837,7 +19915,6 @@ class MintClient {
19837
19915
  });
19838
19916
  }
19839
19917
  async update(mintModel, txOptions = {}) {
19840
- // @ts-ignore
19841
19918
  const tx = await this.base.program.methods.updateMint(0, new MintIdlModel(mintModel)).accounts({
19842
19919
  glamState: this.base.statePda,
19843
19920
  glamMint: this.base.mintPda
@@ -19861,6 +19938,19 @@ class MintClient {
19861
19938
  const vTx = await this.base.intoVersionedTransaction(tx, txOptions);
19862
19939
  return await this.base.sendAndConfirm(vTx);
19863
19940
  }
19941
+ async setPermissionlessFulfill(enabled, txOptions = {}) {
19942
+ const stateModel = await this.base.fetchStateModel();
19943
+ const notifyAndSettle = stateModel.mints?.[0]?.notifyAndSettle;
19944
+ if (!notifyAndSettle) {
19945
+ throw new Error("Mint does not have notifyAndSettle configured.");
19946
+ }
19947
+ return await this.update({
19948
+ notifyAndSettle: {
19949
+ ...notifyAndSettle,
19950
+ permissionlessFulfillment: enabled
19951
+ }
19952
+ }, txOptions);
19953
+ }
19864
19954
  async closeMintIx() {
19865
19955
  return await this.base.program.methods.closeMint(0).accounts({
19866
19956
  glamState: this.base.statePda,
@@ -21070,26 +21160,39 @@ class KaminoLendingClient {
21070
21160
  }
21071
21161
  }
21072
21162
  class KaminoFarmClient {
21073
- async findAndParseFarmStates(owner) {
21074
- const accounts = await this.base.provider.connection.getProgramAccounts(KAMINO_FARM_PROGRAM, {
21075
- filters: [
21076
- {
21077
- dataSize: 920
21078
- },
21079
- {
21080
- memcmp: {
21081
- offset: 48,
21082
- bytes: owner.toBase58()
21083
- }
21163
+ async findAndParseUserStates(owner) {
21164
+ const accounts = await getProgramAccountsV2(KAMINO_FARM_PROGRAM, 10, [
21165
+ {
21166
+ dataSize: 920
21167
+ },
21168
+ {
21169
+ memcmp: {
21170
+ offset: 48,
21171
+ bytes: owner.toBase58()
21084
21172
  }
21085
- ]
21086
- });
21087
- return accounts.map((account)=>{
21088
- const data = account.account.data;
21089
- const farmState = new PublicKey(data.subarray(16, 48));
21173
+ }
21174
+ ]);
21175
+ return accounts.map(({ pubkey, account })=>{
21176
+ // farmState: [16, 48]
21177
+ // owner: [48, 80]
21178
+ // isFarmDelegated + padding: [80, 88]
21179
+ // rewardsTallyScaled: [88, 248]
21180
+ // unclaimedRewards[0..10]: [248, 328]
21181
+ const farmState = new PublicKey(account.data.subarray(16, 48));
21182
+ const rewardsOffset = 248;
21183
+ const numRewards = 10;
21184
+ const rewardSize = 8;
21185
+ const rewardsData = account.data.subarray(rewardsOffset, rewardsOffset + numRewards * rewardSize);
21186
+ const unclaimedRewards = Array.from({
21187
+ length: numRewards
21188
+ }, (_, i)=>{
21189
+ const rewardData = rewardsData.subarray(i * rewardSize, (i + 1) * rewardSize);
21190
+ return new BN(rewardData, "le");
21191
+ });
21090
21192
  return {
21091
- userFarmState: account.pubkey,
21092
- farmState
21193
+ userState: pubkey,
21194
+ farmState,
21195
+ unclaimedRewards
21093
21196
  };
21094
21197
  });
21095
21198
  }
@@ -21149,20 +21252,23 @@ class KaminoFarmClient {
21149
21252
  }
21150
21253
  async harvestTx(txOptions = {}) {
21151
21254
  const glamSigner = txOptions.signer || this.base.getSigner();
21152
- const vault = this.base.vaultPda;
21153
- const farmStates = await this.findAndParseFarmStates(vault);
21255
+ const farmStates = await this.findAndParseUserStates(this.base.vaultPda);
21154
21256
  const parsedFarms = await this.fetchAndParseFarms(farmStates.map((f)=>f.farmState));
21155
21257
  const tx = new Transaction();
21156
- for (const { userFarmState, farmState } of farmStates){
21258
+ console.log("Building transaction to harvest the following rewards:");
21259
+ for (const { userState, farmState, unclaimedRewards } of farmStates){
21157
21260
  const { globalConfig, rewards } = parsedFarms.get(farmState.toBase58());
21158
21261
  for (const { index, mint, tokenProgram, rewardsVault } of rewards){
21159
- console.log("Reward token:", mint.toBase58());
21262
+ if (unclaimedRewards[index].eq(new BN(0))) {
21263
+ continue;
21264
+ }
21265
+ console.log(`userState: ${userState}, farmState: ${farmState}, unclaimedReward: ${unclaimedRewards[index]}, token: ${mint}`);
21160
21266
  const vaultAta = this.base.getVaultAta(mint, tokenProgram);
21161
- const createAtaIx = createAssociatedTokenAccountIdempotentInstruction(glamSigner, vaultAta, vault, mint, tokenProgram);
21267
+ const createAtaIx = createAssociatedTokenAccountIdempotentInstruction(glamSigner, vaultAta, this.base.vaultPda, mint, tokenProgram);
21162
21268
  const harvestIx = await this.base.program.methods.kaminoFarmHarvestReward(new BN(index)).accounts({
21163
21269
  glamState: this.base.statePda,
21164
21270
  glamSigner,
21165
- userState: userFarmState,
21271
+ userState,
21166
21272
  farmState,
21167
21273
  globalConfig,
21168
21274
  rewardMint: mint,
@@ -21176,6 +21282,9 @@ class KaminoFarmClient {
21176
21282
  tx.add(createAtaIx, harvestIx);
21177
21283
  }
21178
21284
  }
21285
+ if (tx.instructions.length === 0) {
21286
+ throw new Error("No rewards to harvest");
21287
+ }
21179
21288
  const vTx = await this.base.intoVersionedTransaction(tx, txOptions);
21180
21289
  return vTx;
21181
21290
  }
@@ -21585,14 +21694,43 @@ class MeteoraDlmmClient {
21585
21694
  }
21586
21695
 
21587
21696
  class InvestClient {
21588
- async subscribe(asset, amount, mintId = 0, queued = false, txOptions = {}) {
21697
+ /**
21698
+ * Subscribe to a tokenized vault
21699
+ *
21700
+ * @param asset Deposit asset
21701
+ * @param amount
21702
+ * @param mintId
21703
+ * @param queued by default false, set to true to subscribe in queued mode
21704
+ * @param txOptions
21705
+ * @returns
21706
+ */ async subscribe(asset, amount, mintId = 0, queued = false, txOptions = {}) {
21589
21707
  const tx = await (queued ? this.queuedSubscribeTx(asset, amount, mintId, txOptions) : this.subscribeTx(asset, amount, mintId, txOptions));
21590
21708
  return await this.base.sendAndConfirm(tx);
21591
21709
  }
21592
- async queuedRedeem(amount, mintId = 0, txOptions = {}) {
21710
+ /**
21711
+ * Request to redeem share tokens of a tokenized vault in queued mode
21712
+ *
21713
+ * @param amount
21714
+ * @param mintId
21715
+ * @param txOptions
21716
+ * @returns
21717
+ */ async queuedRedeem(amount, mintId = 0, txOptions = {}) {
21593
21718
  const tx = await this.queuedRedeemTx(amount, mintId, txOptions);
21594
21719
  return await this.base.sendAndConfirm(tx);
21595
21720
  }
21721
+ /**
21722
+ * Redeem share tokens of a tokenized vault instantly. Preconditions:
21723
+ * 1. The vault must allow permissionless fulfillment
21724
+ * 2. The vault must have sufficient liquidity
21725
+ *
21726
+ * @param amount
21727
+ * @param mintId
21728
+ * @param txOptions
21729
+ * @returns
21730
+ */ async instantRedeem(amount, mintId = 0, txOptions = {}) {
21731
+ const tx = await this.instantRedeemTx(amount, mintId, txOptions);
21732
+ return await this.base.sendAndConfirm(tx);
21733
+ }
21596
21734
  async fulfill(mintId = 0, txOptions = {}) {
21597
21735
  const tx = await this.fulfillTx(mintId, txOptions);
21598
21736
  return await this.base.sendAndConfirm(tx);
@@ -21640,7 +21778,6 @@ class InvestClient {
21640
21778
  signerPolicy = getAccountPolicyPda(this.base.getMintAta(signer));
21641
21779
  console.log(`signerPolicy: ${signerPolicy} for signer ${signer} and token account ${mintTo}`);
21642
21780
  }
21643
- // @ts-ignore
21644
21781
  const tx = await this.base.program.methods.subscribe(0, amount).accounts({
21645
21782
  glamState: this.base.statePda,
21646
21783
  glamMint,
@@ -21682,6 +21819,58 @@ class InvestClient {
21682
21819
  }).preInstructions(preInstructions).postInstructions(postInstructions).transaction();
21683
21820
  return await this.base.intoVersionedTransaction(tx, txOptions);
21684
21821
  }
21822
+ async instantRedeemTx(amount, mintId = 0, txOptions = {}) {
21823
+ if (mintId !== 0) {
21824
+ throw new Error("mintId must be 0");
21825
+ }
21826
+ // Instant redemption flow is realized by enqueueing a redemption, fulfilling it, and then claiming the tokens in a single transaction.
21827
+ const preInstructions = txOptions.preInstructions || [];
21828
+ const signer = txOptions.signer || this.base.getSigner();
21829
+ const glamMint = this.base.mintPda;
21830
+ const stateModel = await this.base.fetchStateModel();
21831
+ const baseAsset = stateModel.baseAsset;
21832
+ const signerAta = this.base.getAta(baseAsset, signer);
21833
+ const fulfillIx = await this.base.program.methods.fulfill(mintId).accounts({
21834
+ glamState: this.base.statePda,
21835
+ glamMint,
21836
+ signer,
21837
+ asset: baseAsset,
21838
+ depositTokenProgram: TOKEN_PROGRAM_ID
21839
+ }).instruction();
21840
+ preInstructions.push(createAssociatedTokenAccountIdempotentInstruction(signer, signerAta, signer, baseAsset));
21841
+ const claimIx = await this.base.program.methods.claim(0).accounts({
21842
+ glamState: this.base.statePda,
21843
+ signer,
21844
+ tokenMint: baseAsset,
21845
+ claimTokenProgram: TOKEN_PROGRAM_ID
21846
+ }).instruction();
21847
+ const remainingAccounts = [];
21848
+ if (await this.base.isLockupEnabled()) {
21849
+ const extraMetasAccount = this.base.extraMetasPda;
21850
+ const signerPolicy = getAccountPolicyPda(this.base.getMintAta(signer));
21851
+ const escrow = this.base.escrowPda;
21852
+ const escrowPolicy = getAccountPolicyPda(this.base.getMintAta(escrow));
21853
+ remainingAccounts.push(...[
21854
+ extraMetasAccount,
21855
+ signerPolicy,
21856
+ escrowPolicy,
21857
+ TRANSFER_HOOK_PROGRAM
21858
+ ]);
21859
+ }
21860
+ const tx = await this.base.program.methods.queuedRedeem(0, amount).accounts({
21861
+ glamState: this.base.statePda,
21862
+ glamMint,
21863
+ signer
21864
+ }).remainingAccounts(remainingAccounts.map((pubkey)=>({
21865
+ pubkey,
21866
+ isSigner: false,
21867
+ isWritable: false
21868
+ }))).preInstructions(preInstructions).postInstructions([
21869
+ fulfillIx,
21870
+ claimIx
21871
+ ]).transaction();
21872
+ return await this.base.intoVersionedTransaction(tx, txOptions);
21873
+ }
21685
21874
  async queuedRedeemTx(amount, mintId = 0, txOptions = {}) {
21686
21875
  if (mintId !== 0) {
21687
21876
  throw new Error("mintId must be 0");
@@ -22505,4 +22694,4 @@ function getMarketOrderParams(params) {
22505
22694
  return Object.assign({}, DefaultOrderParams, optionalOrderParams, overridingParams);
22506
22695
  }
22507
22696
 
22508
- export { ALT_PROGRAM_ID, ASSETS_MAINNET, ASSETS_TESTS, AssetTier, BaseClient, ClusterNetwork, CompanyModel, ContractTier, ContractType, CreatedModel, DRIFT_PROGRAM_ID, DRIFT_VAULTS_PROGRAM_ID, DRIFT_VAULT_DEPOSITOR_SIZE, DefaultOrderParams, DelegateAcl, DepositDirection, DepositExplanation, DriftClient, DriftVaultsClient, ExchangeStatus, FuelOverflowStatus, FundOpenfundsModel, GLAM_REFERRER, GOVERNANCE_PROGRAM_ID, GlamClient, GlamError, GlamIdl, GlamIntegrations, GlamPermissions, GlamProtocolIdlJson, InsuranceFundOperation, JITO_STAKE_POOL, JITO_TIP_DEFAULT, JUP, JUPITER_API_DEFAULT, JUPITER_PROGRAM_ID, JUPSOL_STAKE_POOL, JUP_VOTE_PROGRAM, JupiterSwapClient, JupiterVoteClient, KAMINO_FARM_PROGRAM, KAMINO_LENDING_PROGRAM, KAMINO_OBTRIGATION_SIZE, KAMINO_SCOPE_PRICES, KAMINO_VAULTS_PROGRAM, LPAction, LiquidationType, MARINADE_NATIVE_STAKE_AUTHORITY, MARINADE_PROGRAM_ID, MEMO_PROGRAM, MERKLE_DISTRIBUTOR_PROGRAM, METEORA_DLMM_PROGRAM, METEORA_POSITION_SIZE, MSOL, ManagerModel, MarginMode, MarketStatus, MarketType, Metadata, MintIdlModel, MintModel, MintOpenfundsModel, ModifyOrderPolicy, OracleSource, OracleSourceNum, OrderAction, OrderActionExplanation, OrderStatus, OrderTriggerCondition, OrderType, PerpOperation, PlaceAndTakeOrderSuccessCondition, PositionDirection, PostOnlyParams, PriceDenom, ReferrerStatus, SANCTUM_STAKE_POOL_PROGRAM_ID, SEED_ESCROW, SEED_METADATA, SEED_MINT, SEED_STATE, SEED_VAULT, SOL_ORACLE, STAKE_ACCOUNT_SIZE, STAKE_POOLS, STAKE_POOLS_MAP, SettlePnlExplanation, SettlePnlMode, SpotBalanceType, SpotFulfillmentConfigStatus, SpotFulfillmentStatus, SpotFulfillmentType, SpotOperation, StakeAction, StateIdlModel, StateModel, SwapDirection, SwapReduceOnly, TRANSFER_HOOK_PROGRAM, TimeUnit, TradeSide, USDC, USDC_ORACLE, UserStatus, VoteAuthorize, WSOL, ZERO, decodeUser, fetchMeteoraPositions, fetchProgramLabels, fetchTokenPrices, fetchTokensList, findStakeAccounts, getAccountPolicyPda, getEscrowPda, getExtraMetasPda, getGlamProgram, getGlamProgramId, getLimitOrderParams, getMarketOrderParams, getMintPda, getOpenfundsPda, getOrderParams, getPriorityFeeEstimate, getSimulationResult, getSolAndTokenBalances, getStakeAccountsWithStates, getStatePda, getTokenAccountsByOwner, getTriggerLimitOrderParams, getTriggerMarketOrderParams, getVariant, getVaultPda, isBrowser, isOneOfVariant, isVariant, parseMeteoraPosition, parseProgramLogs, setsAreEqual };
22697
+ export { ALT_PROGRAM_ID, ASSETS_MAINNET, ASSETS_TESTS, AssetTier, BaseClient, ClusterNetwork, CompanyModel, ContractTier, ContractType, CreatedModel, DRIFT_PROGRAM_ID, DRIFT_VAULTS_PROGRAM_ID, DRIFT_VAULT_DEPOSITOR_SIZE, DefaultOrderParams, DelegateAcl, DepositDirection, DepositExplanation, DriftClient, DriftVaultsClient, ExchangeStatus, FuelOverflowStatus, FundOpenfundsModel, GLAM_REFERRER, GOVERNANCE_PROGRAM_ID, GlamClient, GlamError, GlamIdl, GlamIntegrations, GlamPermissions, GlamProtocolIdlJson, InsuranceFundOperation, JITO_STAKE_POOL, JITO_TIP_DEFAULT, JUP, JUPITER_API_DEFAULT, JUPITER_PROGRAM_ID, JUPSOL_STAKE_POOL, JUP_VOTE_PROGRAM, JupiterSwapClient, JupiterVoteClient, KAMINO_FARM_PROGRAM, KAMINO_LENDING_PROGRAM, KAMINO_OBTRIGATION_SIZE, KAMINO_SCOPE_PRICES, KAMINO_VAULTS_PROGRAM, LPAction, LiquidationType, MARINADE_NATIVE_STAKE_AUTHORITY, MARINADE_PROGRAM_ID, MEMO_PROGRAM, MERKLE_DISTRIBUTOR_PROGRAM, METEORA_DLMM_PROGRAM, METEORA_POSITION_SIZE, MSOL, ManagerModel, MarginMode, MarketStatus, MarketType, Metadata, MintIdlModel, MintModel, MintOpenfundsModel, ModifyOrderPolicy, OracleSource, OracleSourceNum, OrderAction, OrderActionExplanation, OrderStatus, OrderTriggerCondition, OrderType, PerpOperation, PlaceAndTakeOrderSuccessCondition, PositionDirection, PostOnlyParams, PriceDenom, ReferrerStatus, SANCTUM_STAKE_POOL_PROGRAM_ID, SEED_ESCROW, SEED_METADATA, SEED_MINT, SEED_STATE, SEED_VAULT, SOL_ORACLE, STAKE_ACCOUNT_SIZE, STAKE_POOLS, STAKE_POOLS_MAP, SettlePnlExplanation, SettlePnlMode, SpotBalanceType, SpotFulfillmentConfigStatus, SpotFulfillmentStatus, SpotFulfillmentType, SpotOperation, StakeAction, StateIdlModel, StateModel, SwapDirection, SwapReduceOnly, TRANSFER_HOOK_PROGRAM, TimeUnit, TradeSide, USDC, USDC_ORACLE, UserStatus, VoteAuthorize, WSOL, ZERO, decodeUser, fetchMeteoraPositions, fetchProgramLabels, fetchTokenPrices, fetchTokensList, findStakeAccounts, getAccountPolicyPda, getEscrowPda, getExtraMetasPda, getGlamProgram, getGlamProgramId, getLimitOrderParams, getMarketOrderParams, getMintPda, getOpenfundsPda, getOrderParams, getPriorityFeeEstimate, getProgramAccountsV2, getSimulationResult, getSolAndTokenBalances, getStakeAccountsWithStates, getStatePda, getTokenAccountsByOwner, getTriggerLimitOrderParams, getTriggerMarketOrderParams, getVariant, getVaultPda, isBrowser, isOneOfVariant, isVariant, parseMeteoraPosition, parseProgramLogs, setsAreEqual };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@glamsystems/glam-sdk",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "TypeScript SDK for the GLAM Protocol",
5
5
  "main": "./index.cjs.js",
6
6
  "module": "./index.esm.js",
@@ -4,12 +4,42 @@ import { BaseClient, TxOptions } from "./base";
4
4
  export declare class InvestClient {
5
5
  readonly base: BaseClient;
6
6
  constructor(base: BaseClient);
7
+ /**
8
+ * Subscribe to a tokenized vault
9
+ *
10
+ * @param asset Deposit asset
11
+ * @param amount
12
+ * @param mintId
13
+ * @param queued by default false, set to true to subscribe in queued mode
14
+ * @param txOptions
15
+ * @returns
16
+ */
7
17
  subscribe(asset: PublicKey, amount: BN, mintId?: number, queued?: boolean, txOptions?: TxOptions): Promise<TransactionSignature>;
18
+ /**
19
+ * Request to redeem share tokens of a tokenized vault in queued mode
20
+ *
21
+ * @param amount
22
+ * @param mintId
23
+ * @param txOptions
24
+ * @returns
25
+ */
8
26
  queuedRedeem(amount: BN, mintId?: number, txOptions?: TxOptions): Promise<TransactionSignature>;
27
+ /**
28
+ * Redeem share tokens of a tokenized vault instantly. Preconditions:
29
+ * 1. The vault must allow permissionless fulfillment
30
+ * 2. The vault must have sufficient liquidity
31
+ *
32
+ * @param amount
33
+ * @param mintId
34
+ * @param txOptions
35
+ * @returns
36
+ */
37
+ instantRedeem(amount: BN, mintId?: number, txOptions?: TxOptions): Promise<TransactionSignature>;
9
38
  fulfill(mintId?: number, txOptions?: TxOptions): Promise<TransactionSignature>;
10
39
  claim(asset: PublicKey, mintId?: number, txOptions?: TxOptions): Promise<TransactionSignature>;
11
40
  subscribeTx(asset: PublicKey, amount: BN, mintId?: number, txOptions?: TxOptions): Promise<VersionedTransaction>;
12
41
  queuedSubscribeTx(asset: PublicKey, amount: BN, mintId?: number, txOptions?: TxOptions): Promise<VersionedTransaction>;
42
+ instantRedeemTx(amount: BN, mintId?: number, txOptions?: TxOptions): Promise<VersionedTransaction>;
13
43
  queuedRedeemTx(amount: BN, mintId?: number, txOptions?: TxOptions): Promise<VersionedTransaction>;
14
44
  fulfillTx(mintId?: number, txOptions?: TxOptions): Promise<VersionedTransaction>;
15
45
  claimAssetTx(asset: PublicKey, mintId?: number, txOptions?: TxOptions): Promise<VersionedTransaction>;
@@ -149,9 +149,10 @@ export declare class KaminoLendingClient {
149
149
  export declare class KaminoFarmClient {
150
150
  readonly base: BaseClient;
151
151
  constructor(base: BaseClient);
152
- findAndParseFarmStates(owner: PublicKey): Promise<{
153
- userFarmState: PublicKey;
152
+ findAndParseUserStates(owner: PublicKey): Promise<{
153
+ userState: any;
154
154
  farmState: PublicKey;
155
+ unclaimedRewards: BN[];
155
156
  }[]>;
156
157
  parseFarm(data: Buffer): Promise<{
157
158
  globalConfig: PublicKey;
@@ -10,6 +10,7 @@ export declare class MintClient {
10
10
  update(mintModel: Partial<MintModel>, txOptions?: TxOptions): Promise<string>;
11
11
  updateApplyTimelock(txOptions?: TxOptions): Promise<string>;
12
12
  emergencyUpdate(mintModel: Partial<MintModel>, txOptions?: TxOptions): Promise<string>;
13
+ setPermissionlessFulfill(enabled: boolean, txOptions?: TxOptions): Promise<string>;
13
14
  closeMintIx(): Promise<anchor.web3.TransactionInstruction>;
14
15
  closeMint(txOptions?: TxOptions): Promise<string>;
15
16
  /**
@@ -6,6 +6,7 @@ export type StakeAccountInfo = {
6
6
  state: string;
7
7
  voter?: PublicKey;
8
8
  };
9
+ export declare function getProgramAccountsV2(programId: PublicKey, limit?: number, filters?: any[]): Promise<any[]>;
9
10
  /**
10
11
  * Fetches all the token accounts owned by the specified pubkey.
11
12
  */