@zebec-network/zebec-vault-sdk 3.1.0 → 4.0.0

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/dist/service.js CHANGED
@@ -7,11 +7,13 @@ exports.ZebecVaultService = void 0;
7
7
  const assert_1 = __importDefault(require("assert"));
8
8
  const bignumber_js_1 = require("bignumber.js");
9
9
  const anchor_1 = require("@coral-xyz/anchor");
10
+ const spl_token_1 = require("@solana/spl-token");
10
11
  const web3_js_1 = require("@solana/web3.js");
11
12
  const core_utils_1 = require("@zebec-network/core-utils");
12
13
  const solana_common_1 = require("@zebec-network/solana-common");
13
14
  const artifacts_1 = require("./artifacts");
14
15
  const constants_1 = require("./constants");
16
+ const errors_1 = require("./errors");
15
17
  const pda_1 = require("./pda");
16
18
  const utils_1 = require("./utils");
17
19
  class ZebecVaultService {
@@ -19,19 +21,22 @@ class ZebecVaultService {
19
21
  vaultV1Program;
20
22
  cardV2Program;
21
23
  streamProgram;
24
+ stakeProgram;
22
25
  network;
23
- constructor(provider, vaultV1Program, cardV2Program, streamProgram, network) {
26
+ constructor(provider, vaultV1Program, cardV2Program, streamProgram, stakeProgram, network) {
24
27
  this.provider = provider;
25
28
  this.vaultV1Program = vaultV1Program;
26
29
  this.cardV2Program = cardV2Program;
27
30
  this.streamProgram = streamProgram;
31
+ this.stakeProgram = stakeProgram;
28
32
  this.network = network;
29
33
  }
30
34
  static create(provider, network) {
31
35
  const vaultV1Program = new anchor_1.Program(artifacts_1.ZEBEC_VAULT_V1_IDL, provider);
32
36
  const cardV2Program = new anchor_1.Program(artifacts_1.ZEBEC_CARD_V2_IDL, provider);
33
37
  const streamProgram = new anchor_1.Program(artifacts_1.ZEBEC_STREAM_IDL, provider);
34
- return new ZebecVaultService(provider, vaultV1Program, cardV2Program, streamProgram, network);
38
+ const stakingProgram = new anchor_1.Program(artifacts_1.ZEBEC_STAKE_IDL_V1, provider);
39
+ return new ZebecVaultService(provider, vaultV1Program, cardV2Program, streamProgram, stakingProgram, network);
35
40
  }
36
41
  async getCreateVaultInstruction(payer, owner, signerBump) {
37
42
  return this.vaultV1Program.methods
@@ -142,89 +147,6 @@ class ZebecVaultService {
142
147
  .remainingAccounts(remainingAccounts)
143
148
  .instruction();
144
149
  }
145
- async getSwapAndCreateSilverCardInstruction(cardVault, cardVaultAta, inputMint, inputMintProgram, outputMint, revenueVault, revenueVaultAta, vaultOwner, data, remainingAccounts) {
146
- const { currency, emailHash, index, swapData } = data;
147
- return this.vaultV1Program.methods
148
- .swapAndCreateSilverCard({
149
- currency,
150
- emailHash,
151
- index,
152
- swapData,
153
- })
154
- .accounts({
155
- cardVault,
156
- cardVaultAta,
157
- inputMint,
158
- inputMintProgram,
159
- outputMint,
160
- outputMintProgram: solana_common_1.TOKEN_PROGRAM_ID,
161
- revenueVault,
162
- revenueVaultAta,
163
- vaultOwner,
164
- })
165
- .remainingAccounts(remainingAccounts)
166
- .instruction();
167
- }
168
- async getSwapAndLoadCarbonCardInstruction(cardVault, cardVaultAta, inputMint, inputMintProgram, outputMint, revenueVault, revenueVaultAta, vaultOwner, data, remainingAccounts) {
169
- const { currency, emailHash, index, swapData, reloadCardId } = data;
170
- return this.vaultV1Program.methods
171
- .swapAndLoadCarbonCard({
172
- currency,
173
- emailHash,
174
- index,
175
- reloadCardId,
176
- swapData,
177
- })
178
- .accounts({
179
- cardVault,
180
- cardVaultAta,
181
- inputMint,
182
- inputMintProgram,
183
- outputMint,
184
- outputMintProgram: solana_common_1.TOKEN_PROGRAM_ID,
185
- revenueVault,
186
- revenueVaultAta,
187
- vaultOwner,
188
- })
189
- .remainingAccounts(remainingAccounts)
190
- .instruction();
191
- }
192
- async getCreateStreamFromVaultInstruction(vaultOwner, vault, vaultSigner, vaultSignerAta, receiver, receiverAta, streamToken, streamMetadata, streamConfig, withdrawAccount, streamVault, streamVaultAta, zebecStreamProgram, streamData) {
193
- return this.vaultV1Program.methods
194
- .createStream({
195
- amount: streamData.amount,
196
- automaticWithdrawal: Number(streamData.automaticWithdrawal),
197
- cancelableByRecipient: Number(streamData.cancelableByRecipient),
198
- cancelableBySender: Number(streamData.cancelableBySender),
199
- canTopup: Number(streamData.canTopup),
200
- cliffPercentage: streamData.cliffPercentage,
201
- duration: streamData.duration,
202
- isPausable: Number(streamData.isPausable),
203
- rateUpdatable: Number(streamData.rateUpdatable),
204
- startNow: Number(streamData.startNow),
205
- startTime: streamData.startTime,
206
- streamFrequency: streamData.autoWithdrawFrequency,
207
- streamName: Array.from(streamData.streamName),
208
- transferableByRecipient: Number(streamData.transferableByRecipient),
209
- transferableBySender: Number(streamData.transferableBySender),
210
- })
211
- .accountsPartial({
212
- receiver,
213
- streamToken,
214
- vaultSignerAta,
215
- withdrawAccount,
216
- streamMetadata,
217
- vaultOwner,
218
- vault,
219
- vaultSigner,
220
- receiverAta,
221
- streamConfig,
222
- streamVault,
223
- streamVaultAta,
224
- zebecStreamProgram,
225
- })
226
- .instruction();
227
- }
228
150
  async createVault(params) {
229
151
  const payer = params.payer ? (0, anchor_1.translateAddress)(params.payer) : this.provider.publicKey;
230
152
  if (!payer) {
@@ -253,27 +175,92 @@ class ZebecVaultService {
253
175
  const ix = await this.getWithdrawSolInstruction(withdrawer, amount);
254
176
  return this._createTransactionPayload(withdrawer, [ix]);
255
177
  }
256
- async depositToken(params) {
178
+ async deposit(params) {
257
179
  const depositor = params.depositor ? (0, anchor_1.translateAddress)(params.depositor) : this.provider.publicKey;
258
180
  if (!depositor) {
259
181
  throw new Error("Either provide a depositor or use AnchorProvider for provider in the service");
260
182
  }
261
183
  const tokenMint = (0, anchor_1.translateAddress)(params.tokenMint);
184
+ const isSolWrapNeeded = tokenMint.equals(solana_common_1.WSOL);
262
185
  const decimals = await (0, solana_common_1.getMintDecimals)(this.provider.connection, tokenMint);
263
- const amount = new anchor_1.BN((0, solana_common_1.parseToken)(params.amount, decimals).toString());
264
- const ix = await this.getDepositTokenInstruction(depositor, tokenMint, amount, decimals);
265
- return this._createTransactionPayload(depositor, [ix]);
186
+ const amount = (0, solana_common_1.parseToken)(params.amount, decimals).toString();
187
+ const instructions = [];
188
+ if (isSolWrapNeeded) {
189
+ const wrapIxns = await this.getWrapSolInstructions(depositor, amount);
190
+ instructions.push(...wrapIxns);
191
+ }
192
+ const ix = await this.getDepositTokenInstruction(depositor, tokenMint, new anchor_1.BN(amount), decimals);
193
+ instructions.push(ix);
194
+ if (isSolWrapNeeded) {
195
+ const unwrapIx = await this.getUnwrapWsolInstruction(depositor, depositor);
196
+ instructions.push(unwrapIx);
197
+ }
198
+ return this._createTransactionPayload(depositor, instructions);
199
+ }
200
+ async getWrapSolInstructions(owner, amountToSync) {
201
+ const wrapIxns = [];
202
+ // Get the associated token account address for WSOL
203
+ const wsolTokenAccount = (0, solana_common_1.getAssociatedTokenAddressSync)(solana_common_1.WSOL, // mint (Wrapped SOL)
204
+ owner, // owner
205
+ true);
206
+ const wsolTokenAccountInfo = await this.connection.getAccountInfo(wsolTokenAccount, this.connection.commitment);
207
+ if (!wsolTokenAccountInfo) {
208
+ // create WSOL token account instructions
209
+ const createWsolTokenAccountIx = (0, solana_common_1.createAssociatedTokenAccountInstruction)(owner, // payer
210
+ wsolTokenAccount, // associated token account address
211
+ owner, // owner
212
+ solana_common_1.WSOL);
213
+ wrapIxns.push(createWsolTokenAccountIx);
214
+ }
215
+ // transfer SOL from vault signer account to WSOL token account instructions
216
+ const transferSolToWsolAccountIx = web3_js_1.SystemProgram.transfer({
217
+ fromPubkey: owner,
218
+ toPubkey: wsolTokenAccount,
219
+ lamports: BigInt(amountToSync),
220
+ });
221
+ wrapIxns.push(transferSolToWsolAccountIx);
222
+ // wrap instructions
223
+ const wrapIx = (0, spl_token_1.createSyncNativeInstruction)(wsolTokenAccount);
224
+ wrapIxns.push(wrapIx);
225
+ return wrapIxns;
266
226
  }
267
- async withdrawToken(params) {
227
+ async getUnwrapWsolInstruction(owner, destination) {
228
+ // Get the associated token account address for WSOL
229
+ const wsolTokenAccount = (0, solana_common_1.getAssociatedTokenAddressSync)(solana_common_1.WSOL, // mint (Wrapped SOL)
230
+ owner, // owner
231
+ true);
232
+ const closeWsolAccountIx = (0, spl_token_1.createCloseAccountInstruction)(wsolTokenAccount, // account to be closed
233
+ destination, // destination
234
+ owner);
235
+ return closeWsolAccountIx;
236
+ }
237
+ async withdraw(params) {
268
238
  const withdrawer = params.withdrawer ? (0, anchor_1.translateAddress)(params.withdrawer) : this.provider.publicKey;
269
239
  if (!withdrawer) {
270
240
  throw new Error("Either provide a withdrawer or use AnchorProvider for provider in the service");
271
241
  }
272
242
  const tokenMint = (0, anchor_1.translateAddress)(params.tokenMint);
243
+ const isSolUnwrapNeeded = tokenMint.equals(solana_common_1.WSOL);
273
244
  const decimals = await (0, solana_common_1.getMintDecimals)(this.provider.connection, tokenMint);
274
245
  const amount = new anchor_1.BN((0, solana_common_1.parseToken)(params.amount, decimals).toString());
246
+ const instructions = [];
247
+ const wsolTokenAccount = (0, solana_common_1.getAssociatedTokenAddressSync)(solana_common_1.WSOL, // mint (Wrapped SOL)
248
+ withdrawer, // owner
249
+ true);
250
+ if (isSolUnwrapNeeded) {
251
+ const wsolAccountInfo = await this.connection.getAccountInfo(wsolTokenAccount, this.connection.commitment);
252
+ if (!wsolAccountInfo) {
253
+ const createWsolAccountIx = (0, solana_common_1.createAssociatedTokenAccountInstruction)(withdrawer, wsolTokenAccount, withdrawer, solana_common_1.WSOL);
254
+ instructions.push(createWsolAccountIx);
255
+ }
256
+ }
275
257
  const ix = await this.getWithdrawTokenInstruction(withdrawer, tokenMint, amount, decimals);
276
- return this._createTransactionPayload(withdrawer, [ix]);
258
+ instructions.push(ix);
259
+ if (isSolUnwrapNeeded) {
260
+ const unwrapIx = await this.getUnwrapWsolInstruction(withdrawer, withdrawer);
261
+ instructions.push(unwrapIx);
262
+ }
263
+ return this._createTransactionPayload(withdrawer, instructions);
277
264
  }
278
265
  async createProposal(params) {
279
266
  const proposer = params.proposer ? (0, anchor_1.translateAddress)(params.proposer) : this.provider.publicKey;
@@ -281,11 +268,7 @@ class ZebecVaultService {
281
268
  throw new Error("Either provide a proposer or use AnchorProvider for provider in the service");
282
269
  }
283
270
  const proposalKeypair = params.proposalKeypair ?? web3_js_1.Keypair.generate();
284
- const actions = params.actions.map((ix) => ({
285
- accountSpecs: ix.keys,
286
- data: ix.data,
287
- programId: ix.programId,
288
- }));
271
+ const actions = params.actions.map(utils_1.transactionInstructionToPropoalAction);
289
272
  const proposalAccountSize = (0, utils_1.calculateProposalSize)(params.name, actions);
290
273
  if (proposalAccountSize > 10_000) {
291
274
  throw new Error("Proposal size exceeds maximum allowed size of 10,000 bytes");
@@ -303,11 +286,7 @@ class ZebecVaultService {
303
286
  if (!proposalAccount) {
304
287
  throw new Error("Proposal account not found");
305
288
  }
306
- const newActions = params.actions.map((ix) => ({
307
- accountSpecs: ix.keys,
308
- data: ix.data,
309
- programId: ix.programId,
310
- }));
289
+ const newActions = params.actions.map(utils_1.transactionInstructionToPropoalAction);
311
290
  const allActions = proposalAccount.actions;
312
291
  allActions.push(...newActions);
313
292
  const newProposalAccountSize = (0, utils_1.calculateProposalSize)(proposalAccount.name, allActions);
@@ -373,11 +352,7 @@ class ZebecVaultService {
373
352
  }
374
353
  const [vault] = (0, pda_1.deriveUserVault)(proposer, this.vaultV1ProgramId);
375
354
  const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
376
- const actions = params.actions.map((ix) => ({
377
- accountSpecs: ix.keys,
378
- data: ix.data,
379
- programId: ix.programId,
380
- }));
355
+ const actions = params.actions.map(utils_1.transactionInstructionToPropoalAction);
381
356
  const remainingAccounts = actions.reduce((acc, current) => {
382
357
  const accounts = current.accountSpecs.map((spec) => ({
383
358
  pubkey: spec.pubkey,
@@ -405,169 +380,618 @@ class ZebecVaultService {
405
380
  }
406
381
  return this._createTransactionPayload(proposer, [ix], params.partialSigners, addressLookupTableAccounts);
407
382
  }
408
- async swapAndCreateSilverCard(params) {
409
- const { vaultOwnerAddress, quoteInfo, emailHash, wrapAndUnwrapSol } = params;
410
- if ("error" in quoteInfo) {
411
- throw new Error(quoteInfo.error);
383
+ async getVaultInfoOfUser(user) {
384
+ user = user ? (0, anchor_1.translateAddress)(user) : this.provider.publicKey;
385
+ if (!user) {
386
+ throw new Error("Either provide a user or use AnchorProvider for provider in the service");
412
387
  }
413
- (0, core_utils_1.assertBufferSize)(emailHash, 32);
414
- // const [cardConfig] = deriveCardConfigPda(this.cardV2ProgramId);
415
- // console.log("cardConfig:", cardConfig.toString());
416
- // const cardConfigInfo = await this.cardV2Program.account.card.fetch(cardConfig, this.connection.commitment);
417
- const vaultOwner = (0, anchor_1.translateAddress)(vaultOwnerAddress);
418
- const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
419
- const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
420
- const inputMint = (0, anchor_1.translateAddress)(quoteInfo.inputMint);
421
- const usdc = (0, anchor_1.translateAddress)(quoteInfo.outputMint);
422
- // const [userPurchaseRecord] = deriveUserPurchaseRecordPda(user, this.vaultV1ProgramId);
423
- // const revenueVault = cardConfigInfo.revenueVault;
424
- const revenueVault = web3_js_1.Keypair.generate().publicKey;
425
- // const cardVault = cardConfigInfo.cardVault;
426
- const cardVault = web3_js_1.Keypair.generate().publicKey;
427
- const cardVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(usdc, cardVault, true);
428
- const revenueVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(usdc, revenueVault, true);
429
- // const amount =
430
- // quoteInfo.swapMode === "ExactIn"
431
- // ? new BigNumber(quoteInfo.otherAmountThreshold)
432
- // : new BigNumber(quoteInfo.outAmount);
433
- // if (!usdc.equals(cardConfigInfo.usdcMint)) {
434
- // throw new Error(`Invalid usdc: ${usdc.toString()}`);
435
- // }
436
- // const userInputMintAta = getAssociatedTokenAddressSync(inputMint, user);
437
- const { swapTransaction } = await (await fetch("https://lite-api.jup.ag/swap/v1/swap", {
438
- method: "POST",
439
- headers: {
440
- "Content-Type": "application/json",
441
- },
442
- body: JSON.stringify({
443
- // quoteResponse from /quote api
444
- quoteResponse: quoteInfo,
445
- // user public key to be used for the swap
446
- userPublicKey: vaultSigner.toString(),
447
- // auto wrap and unwrap SOL. default is true
448
- wrapAndUnwrapSol: wrapAndUnwrapSol ?? false,
449
- // feeAccount is optional. Use if you want to charge a fee. feeBps must have been passed in /quote API.
450
- // feeAccount: "fee_account_public_key"
451
- }),
452
- })).json();
453
- // deserialize the transaction
454
- const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
455
- const transaction = web3_js_1.VersionedTransaction.deserialize(swapTransactionBuf);
456
- // get address lookup table accounts
457
- const addressLookupTableAccounts = await Promise.all(transaction.message.addressTableLookups.map(async (lookup) => {
458
- const data = await this.connection.getAccountInfo(lookup.accountKey).then((res) => res.data);
459
- return new web3_js_1.AddressLookupTableAccount({
460
- key: lookup.accountKey,
461
- state: web3_js_1.AddressLookupTableAccount.deserialize(data),
462
- });
463
- }));
464
- const lookupTableData = await new web3_js_1.Connection((0, web3_js_1.clusterApiUrl)("devnet"))
465
- .getAccountInfo((0, anchor_1.translateAddress)(constants_1.CARD_LOOKUP_TABLE_ADDRESS))
466
- .then((res) => res.data);
467
- addressLookupTableAccounts.push(new web3_js_1.AddressLookupTableAccount({
468
- key: (0, anchor_1.translateAddress)(constants_1.CARD_LOOKUP_TABLE_ADDRESS),
469
- state: web3_js_1.AddressLookupTableAccount.deserialize(lookupTableData),
470
- }));
471
- // console.log("address lookup table:\n", addressLookupTableAccounts);
472
- // decompile transaction message and add transfer instruction
473
- const message = web3_js_1.TransactionMessage.decompile(transaction.message, {
474
- addressLookupTableAccounts: addressLookupTableAccounts,
475
- });
476
- const swapInstruction = message.instructions.find((ix) => ix.programId.equals((0, anchor_1.translateAddress)(constants_1.JUPITER_AGGREGATOR_PROGRAM_ID)));
477
- (0, assert_1.default)(swapInstruction, "Swap instruction not found in the transaction message");
478
- const otherIxs = message.instructions.filter((ix) => !ix.programId.equals((0, anchor_1.translateAddress)(constants_1.JUPITER_AGGREGATOR_PROGRAM_ID)));
479
- const index = new anchor_1.BN(params.nextCardCounter.toString());
480
- const swapAndCreateSilverCardIx = await this.getSwapAndCreateSilverCardInstruction(cardVault, cardVaultAta, inputMint, solana_common_1.TOKEN_PROGRAM_ID, usdc, revenueVault, revenueVaultAta, vaultOwner, {
481
- index,
482
- currency: params.currency,
483
- emailHash: Array.from(emailHash),
484
- swapData: swapInstruction.data,
485
- }, swapInstruction.keys);
486
- otherIxs.push(swapAndCreateSilverCardIx);
487
- return this._createTransactionPayload(vaultOwner, otherIxs, [], addressLookupTableAccounts);
488
- }
489
- async swapAndLoadCarbonCard(params) {
490
- const { vaultOwnerAddress, quoteInfo, emailHash, reloadCardId } = params;
491
- if ("error" in quoteInfo) {
492
- throw new Error(quoteInfo.error);
388
+ const [vault] = (0, pda_1.deriveUserVault)(user, this.vaultV1ProgramId);
389
+ const vaultAccount = await this.vaultV1Program.account.vault.fetchNullable(vault, this.connection.commitment);
390
+ if (!vaultAccount) {
391
+ return null;
493
392
  }
494
- (0, core_utils_1.assertBufferSize)(emailHash, 32);
495
- const [cardConfig] = (0, pda_1.deriveCardConfigPda)(this.cardV2ProgramId);
496
- console.log("cardConfig:", cardConfig.toString());
497
- const cardConfigInfo = await this.cardV2Program.account.card.fetch(cardConfig, this.connection.commitment);
498
- const vaultOwner = (0, anchor_1.translateAddress)(vaultOwnerAddress);
499
- const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
500
- const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
501
- // const feePayer = user;
502
- const inputMint = (0, anchor_1.translateAddress)(quoteInfo.inputMint);
503
- const usdc = (0, anchor_1.translateAddress)(quoteInfo.outputMint);
504
- // const [userPurchaseRecord] = deriveUserPurchaseRecordPda(user, this.vaultV1ProgramId);
505
- const revenueVault = cardConfigInfo.revenueVault;
506
- const cardVault = cardConfigInfo.cardVault;
507
- const cardVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(usdc, cardVault, true);
508
- const revenueVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(usdc, revenueVault, true);
509
- const { swapTransaction } = await (await fetch("https://lite-api.jup.ag/swap/v1/swap", {
510
- method: "POST",
511
- headers: {
512
- "Content-Type": "application/json",
513
- },
514
- body: JSON.stringify({
515
- // quoteResponse from /quote api
516
- quoteResponse: quoteInfo,
517
- // user public key to be used for the swap
518
- userPublicKey: vaultSigner.toString(),
519
- // auto wrap and unwrap SOL. default is true
520
- wrapAndUnwrapSol: false,
521
- // feeAccount is optional. Use if you want to charge a fee. feeBps must have been passed in /quote API.
522
- // feeAccount: "fee_account_public_key"
523
- }),
524
- })).json();
525
- // deserialize the transaction
526
- const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
527
- const transaction = web3_js_1.VersionedTransaction.deserialize(swapTransactionBuf);
528
- // get address lookup table accounts
529
- const addressLookupTableAccounts = await Promise.all(transaction.message.addressTableLookups.map(async (lookup) => {
530
- const data = await this.connection.getAccountInfo(lookup.accountKey).then((res) => res.data);
531
- return new web3_js_1.AddressLookupTableAccount({
532
- key: lookup.accountKey,
533
- state: web3_js_1.AddressLookupTableAccount.deserialize(data),
534
- });
535
- }));
536
- const lookupTableData = await new web3_js_1.Connection((0, web3_js_1.clusterApiUrl)("devnet"))
537
- .getAccountInfo((0, anchor_1.translateAddress)(constants_1.CARD_LOOKUP_TABLE_ADDRESS))
538
- .then((res) => res.data);
539
- addressLookupTableAccounts.push(new web3_js_1.AddressLookupTableAccount({
540
- key: (0, anchor_1.translateAddress)(constants_1.CARD_LOOKUP_TABLE_ADDRESS),
541
- state: web3_js_1.AddressLookupTableAccount.deserialize(lookupTableData),
542
- }));
543
- // console.log("address lookup table:\n", addressLookupTableAccounts);
544
- // decompile transaction message and add transfer instruction
545
- const message = web3_js_1.TransactionMessage.decompile(transaction.message, {
546
- addressLookupTableAccounts: addressLookupTableAccounts,
393
+ return {
394
+ createdDate: vaultAccount.createdDate.toNumber(),
395
+ owner: vaultAccount.owner,
396
+ signerBump: vaultAccount.signerBump,
397
+ vault,
398
+ };
399
+ }
400
+ async getAllVaultsInfo() {
401
+ const vaultAccount = this.vaultV1Program.idl.accounts.find((acc) => acc.name === "vault");
402
+ (0, assert_1.default)(vaultAccount, "Vault account not found in IDL");
403
+ const accountInfos = await this.connection.getProgramAccounts(this.vaultV1ProgramId, {
404
+ commitment: this.connection.commitment,
405
+ filters: [
406
+ {
407
+ memcmp: {
408
+ offset: 0, // offset for discriminator in Vault
409
+ bytes: anchor_1.utils.bytes.bs58.encode(vaultAccount.discriminator),
410
+ encoding: "base58",
411
+ },
412
+ },
413
+ ],
547
414
  });
548
- const swapInstruction = message.instructions.find((ix) => ix.programId.equals((0, anchor_1.translateAddress)(constants_1.JUPITER_AGGREGATOR_PROGRAM_ID)));
549
- (0, assert_1.default)(swapInstruction, "Swap instruction not found in the transaction message");
550
- const otherIxs = message.instructions.filter((ix) => !ix.programId.equals((0, anchor_1.translateAddress)(constants_1.JUPITER_AGGREGATOR_PROGRAM_ID)));
551
- const index = new anchor_1.BN(params.nextCardCounter.toString());
552
- const swapAndCreateSilverCardIx = await this.getSwapAndLoadCarbonCardInstruction(cardVault, cardVaultAta, inputMint, solana_common_1.TOKEN_PROGRAM_ID, usdc, revenueVault, revenueVaultAta, vaultOwner, {
553
- index,
554
- currency: params.currency,
555
- emailHash: Array.from(emailHash),
556
- swapData: swapInstruction.data,
557
- reloadCardId,
558
- }, swapInstruction.keys);
559
- otherIxs.push(swapAndCreateSilverCardIx);
560
- return this._createTransactionPayload(vaultOwner, otherIxs, [], addressLookupTableAccounts);
415
+ const vaults = accountInfos.map((accountInfo) => {
416
+ const vault = this.vaultV1Program.coder.accounts.decode(vaultAccount.name, accountInfo.account.data);
417
+ return {
418
+ vault: accountInfo.pubkey,
419
+ owner: vault.owner,
420
+ createdDate: vault.createdDate.toNumber(),
421
+ signerBump: vault.signerBump,
422
+ };
423
+ });
424
+ return vaults;
561
425
  }
562
- async createStreamFromVault(params) {
563
- const vaultOwner = (0, anchor_1.translateAddress)(params.sender);
564
- const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
565
- const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
566
- const receiver = (0, anchor_1.translateAddress)(params.receiver);
567
- const [receiverVault] = (0, pda_1.deriveUserVault)(receiver, this.vaultV1ProgramId);
568
- const [receiverVaultSigner] = (0, pda_1.deriveVaultSigner)(receiverVault, this.vaultV1ProgramId);
569
- const streamToken = (0, anchor_1.translateAddress)(params.streamToken);
570
- const vaultSignerAta = (0, solana_common_1.getAssociatedTokenAddressSync)(streamToken, vaultSigner, true);
426
+ async getProposalsInfoOfVault(vaultAddress) {
427
+ const vault = (0, anchor_1.translateAddress)(vaultAddress);
428
+ const proposalAccount = this.vaultV1Program.idl.accounts.find((acc) => acc.name === "proposal");
429
+ (0, assert_1.default)(proposalAccount, "Proposal account not found in IDL");
430
+ const accountInfos = await this.connection.getProgramAccounts(this.vaultV1ProgramId, {
431
+ commitment: this.connection.commitment,
432
+ filters: [
433
+ {
434
+ memcmp: {
435
+ offset: 0, // offset for discriminator in Proposal
436
+ bytes: anchor_1.utils.bytes.bs58.encode(proposalAccount.discriminator),
437
+ encoding: "base58",
438
+ },
439
+ },
440
+ {
441
+ memcmp: {
442
+ offset: 8, // offset for owner field in Proposal
443
+ bytes: vault.toBase58(),
444
+ encoding: "base58",
445
+ },
446
+ },
447
+ ],
448
+ });
449
+ const proposals = accountInfos.map((accountInfo) => {
450
+ const proposal = this.vaultV1Program.coder.accounts.decode(proposalAccount.name, accountInfo.account.data);
451
+ return {
452
+ proposal: accountInfo.pubkey,
453
+ vault: proposal.vault,
454
+ proposalStage: proposal.proposalStage,
455
+ createdDate: proposal.createdDate.toNumber(),
456
+ expiryDate: proposal.expiryDate.toNumber(),
457
+ name: proposal.name,
458
+ actions: proposal.actions,
459
+ isExecuted: proposal.isExecuted,
460
+ };
461
+ });
462
+ return proposals;
463
+ }
464
+ async getCreateSilverCardInstruction(feePayer, cardVault, cardVaultAta, inputMint, revenueVault, revenueVaultAta, usdcToken, user, createSilverCardData) {
465
+ const { amount, index, currency, emailHash } = createSilverCardData;
466
+ return this.cardV2Program.methods
467
+ .createSilverCard({
468
+ amount,
469
+ index,
470
+ currency,
471
+ emailHash,
472
+ })
473
+ .accounts({
474
+ cardVault,
475
+ cardVaultAta,
476
+ feePayer,
477
+ revenueVault,
478
+ revenueVaultAta,
479
+ usdcToken,
480
+ user,
481
+ inputMint,
482
+ })
483
+ .instruction();
484
+ }
485
+ async getLoadCarbonCardInstruction(feePayer, cardVault, cardVaultAta, inputMint, revenueVault, revenueVaultAta, usdcToken, user, createCarbonCardData) {
486
+ const { amount, index, currency, emailHash, reloadCardId } = createCarbonCardData;
487
+ return this.cardV2Program.methods
488
+ .loadCarbonCard({
489
+ amount,
490
+ index,
491
+ currency,
492
+ emailHash,
493
+ reloadCardId,
494
+ })
495
+ .accounts({
496
+ cardVault,
497
+ cardVaultAta,
498
+ feePayer,
499
+ revenueVault,
500
+ revenueVaultAta,
501
+ usdcToken,
502
+ user,
503
+ inputMint,
504
+ })
505
+ .instruction();
506
+ }
507
+ async getSwapAndCreateSilverCardInstruction(cardVault, cardVaultAta, inputMint, inputMintProgram, outputMint, revenueVault, revenueVaultAta, vaultOwner, data, remainingAccounts) {
508
+ const { currency, emailHash, index, swapData } = data;
509
+ return this.vaultV1Program.methods
510
+ .swapAndCreateSilverCard({
511
+ currency,
512
+ emailHash,
513
+ index,
514
+ swapData,
515
+ })
516
+ .accounts({
517
+ cardVault,
518
+ cardVaultAta,
519
+ inputMint,
520
+ inputMintProgram,
521
+ outputMint,
522
+ outputMintProgram: solana_common_1.TOKEN_PROGRAM_ID,
523
+ revenueVault,
524
+ revenueVaultAta,
525
+ vaultOwner,
526
+ })
527
+ .remainingAccounts(remainingAccounts)
528
+ .instruction();
529
+ }
530
+ async getSwapAndLoadCarbonCardInstruction(cardVault, cardVaultAta, inputMint, inputMintProgram, outputMint, revenueVault, revenueVaultAta, vaultOwner, data, remainingAccounts) {
531
+ const { currency, emailHash, index, swapData, reloadCardId } = data;
532
+ return this.vaultV1Program.methods
533
+ .swapAndLoadCarbonCard({
534
+ currency,
535
+ emailHash,
536
+ index,
537
+ reloadCardId,
538
+ swapData,
539
+ })
540
+ .accounts({
541
+ cardVault,
542
+ cardVaultAta,
543
+ inputMint,
544
+ inputMintProgram,
545
+ outputMint,
546
+ outputMintProgram: solana_common_1.TOKEN_PROGRAM_ID,
547
+ revenueVault,
548
+ revenueVaultAta,
549
+ vaultOwner,
550
+ })
551
+ .remainingAccounts(remainingAccounts)
552
+ .instruction();
553
+ }
554
+ async _checkAmountIsWithinDailyCardLimit(cardConfigInfo, userPurchaseRecord, amount) {
555
+ const dailyCardBuyLimit = (0, bignumber_js_1.BigNumber)(cardConfigInfo.dailyCardBuyLimit.toString());
556
+ console.debug("dailyCardBuyLimit:", dailyCardBuyLimit.toFixed());
557
+ const today = new Date();
558
+ const userPurchaseRecordInfo = await this.cardV2Program.account.userPurchaseRecord.fetchNullable(userPurchaseRecord, "confirmed");
559
+ if (!userPurchaseRecordInfo) {
560
+ console.debug("No user purchase record exists.");
561
+ return;
562
+ }
563
+ const lastCardBoughtDate = new Date(userPurchaseRecordInfo.dateTimeInUnix.toNumber() * 1000);
564
+ let cardBoughtInADay = (0, bignumber_js_1.BigNumber)(0);
565
+ if ((0, core_utils_1.areDatesOfSameDay)(today, lastCardBoughtDate)) {
566
+ cardBoughtInADay = (0, bignumber_js_1.BigNumber)(userPurchaseRecordInfo.totalBoughtPerDay.toString()).plus(amount);
567
+ }
568
+ else {
569
+ cardBoughtInADay = amount;
570
+ }
571
+ if (cardBoughtInADay.isGreaterThan(dailyCardBuyLimit)) {
572
+ throw new errors_1.DailyCardLimitReachedError(dailyCardBuyLimit.div(solana_common_1.UNITS_PER_USDC).toFixed(), cardBoughtInADay.div(solana_common_1.UNITS_PER_USDC).toFixed());
573
+ }
574
+ }
575
+ async _checkAmountIsWithinProviderRange(cardConfigInfo, amount) {
576
+ const minRange = (0, bignumber_js_1.BigNumber)(cardConfigInfo.providerConfig.minCardAmount.toString());
577
+ const maxRange = (0, bignumber_js_1.BigNumber)(cardConfigInfo.providerConfig.maxCardAmount.toString());
578
+ if (amount.isLessThan(minRange) || amount.isGreaterThan(maxRange)) {
579
+ throw new errors_1.AmountOutOfRangeError(minRange.div(solana_common_1.UNITS_PER_USDC).toFixed(), maxRange.div(solana_common_1.UNITS_PER_USDC).toFixed(), amount.div(solana_common_1.UNITS_PER_USDC).toFixed());
580
+ }
581
+ }
582
+ async getNextCardIndex() {
583
+ const [cardConfig] = (0, pda_1.deriveCardConfigPda)(this.cardV2Program.programId);
584
+ const decoded = await this.cardV2Program.account.card.fetch(cardConfig, "confirmed");
585
+ return BigInt(decoded.index.addn(1).toString());
586
+ }
587
+ async createSilverCard(params) {
588
+ const vaultOwner = params.vaultOwnerAddress ? (0, anchor_1.translateAddress)(params.vaultOwnerAddress) : this.provider.publicKey;
589
+ if (!vaultOwner) {
590
+ throw new Error("Either provide a caller or use AnchorProvider for provider in the service");
591
+ }
592
+ (0, core_utils_1.assertBufferSize)(params.emailHash, 32);
593
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
594
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
595
+ const usdcMint = (0, anchor_1.translateAddress)(params.usdcAddress);
596
+ const amount = (0, bignumber_js_1.BigNumber)(params.amount);
597
+ const [cardConfig] = (0, pda_1.deriveCardConfigPda)(this.cardV2Program.programId);
598
+ const [userPurchaseRecord] = (0, pda_1.deriveUserPurchaseRecordPda)(vaultSigner, this.cardV2Program.programId);
599
+ const cardConfigInfo = await this.cardV2Program.account.card.fetch(cardConfig, "confirmed");
600
+ const cardVault = cardConfigInfo.cardVault;
601
+ const revenueVault = cardConfigInfo.revenueVault;
602
+ const cardVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(usdcMint, cardVault, true);
603
+ const revenueVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(usdcMint, revenueVault, true);
604
+ const parsedAmount = amount.times(solana_common_1.UNITS_PER_USDC).decimalPlaces(0, bignumber_js_1.BigNumber.ROUND_DOWN);
605
+ await this._checkAmountIsWithinProviderRange(cardConfigInfo, parsedAmount);
606
+ await this._checkAmountIsWithinDailyCardLimit(cardConfigInfo, userPurchaseRecord, parsedAmount);
607
+ const index = new anchor_1.BN(params.nextCardIndex.toString());
608
+ const ix = await this.getCreateSilverCardInstruction(vaultOwner, cardVault, cardVaultAta, usdcMint, revenueVault, revenueVaultAta, usdcMint, vaultSigner, {
609
+ amount: new anchor_1.BN(parsedAmount.toFixed()),
610
+ index,
611
+ currency: params.currency,
612
+ emailHash: Array.from(params.emailHash),
613
+ });
614
+ const actions = [(0, utils_1.transactionInstructionToPropoalAction)(ix)];
615
+ const remainingAccounts = actions.reduce((acc, current) => {
616
+ const accounts = current.accountSpecs.map((spec) => ({
617
+ pubkey: spec.pubkey,
618
+ isSigner: spec.pubkey.equals(vaultSigner) ? false : spec.isSigner,
619
+ isWritable: spec.isWritable,
620
+ }));
621
+ acc.push(...accounts, {
622
+ isSigner: false,
623
+ isWritable: false,
624
+ pubkey: current.programId,
625
+ });
626
+ return acc;
627
+ }, []);
628
+ const lookupTables = await this.connection.getAddressLookupTable((0, anchor_1.translateAddress)(constants_1.CARD_LOOKUP_TABLE_ADDRESS[this.network]));
629
+ const lookupTableAccount = lookupTables.value;
630
+ (0, assert_1.default)(lookupTableAccount, "Lookup table account not found");
631
+ const executeProposalDirectIx = await this.getExecuteProposalDirectInstruction(vaultOwner, actions, remainingAccounts);
632
+ return this._createTransactionPayload(vaultOwner, [executeProposalDirectIx], [], [lookupTableAccount]);
633
+ }
634
+ async loadCarbonCard(params) {
635
+ const vaultOwner = params.vaultOwnerAddress ? (0, anchor_1.translateAddress)(params.vaultOwnerAddress) : this.provider.publicKey;
636
+ if (!vaultOwner) {
637
+ throw new Error("Either provide a caller or use AnchorProvider for provider in the service");
638
+ }
639
+ (0, core_utils_1.assertBufferSize)(params.emailHash, 32);
640
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
641
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
642
+ const usdcMint = (0, anchor_1.translateAddress)(params.usdcAddress);
643
+ const amount = (0, bignumber_js_1.BigNumber)(params.amount);
644
+ const [cardConfig] = (0, pda_1.deriveCardConfigPda)(this.cardV2Program.programId);
645
+ const [userPurchaseRecord] = (0, pda_1.deriveUserPurchaseRecordPda)(vaultSigner, this.cardV2Program.programId);
646
+ const cardConfigInfo = await this.cardV2Program.account.card.fetch(cardConfig, "confirmed");
647
+ const cardVault = cardConfigInfo.cardVault;
648
+ const revenueVault = cardConfigInfo.revenueVault;
649
+ const cardVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(usdcMint, cardVault, true);
650
+ const revenueVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(usdcMint, revenueVault, true);
651
+ const parsedAmount = amount.times(solana_common_1.UNITS_PER_USDC).decimalPlaces(0, bignumber_js_1.BigNumber.ROUND_DOWN);
652
+ await this._checkAmountIsWithinProviderRange(cardConfigInfo, parsedAmount);
653
+ await this._checkAmountIsWithinDailyCardLimit(cardConfigInfo, userPurchaseRecord, parsedAmount);
654
+ const index = new anchor_1.BN(params.nextCardIndex.toString());
655
+ const ix = await this.getLoadCarbonCardInstruction(vaultOwner, cardVault, cardVaultAta, usdcMint, revenueVault, revenueVaultAta, usdcMint, vaultSigner, {
656
+ amount: new anchor_1.BN(parsedAmount.toFixed()),
657
+ index,
658
+ currency: params.currency,
659
+ emailHash: Array.from(params.emailHash),
660
+ reloadCardId: params.reloadCardId,
661
+ });
662
+ const actions = [(0, utils_1.transactionInstructionToPropoalAction)(ix)];
663
+ const remainingAccounts = actions.reduce((acc, current) => {
664
+ const accounts = current.accountSpecs.map((spec) => ({
665
+ pubkey: spec.pubkey,
666
+ isSigner: spec.pubkey.equals(vaultSigner) ? false : spec.isSigner,
667
+ isWritable: spec.isWritable,
668
+ }));
669
+ acc.push(...accounts, {
670
+ isSigner: false,
671
+ isWritable: false,
672
+ pubkey: current.programId,
673
+ });
674
+ return acc;
675
+ }, []);
676
+ const lookupTables = await this.connection.getAddressLookupTable((0, anchor_1.translateAddress)(constants_1.CARD_LOOKUP_TABLE_ADDRESS[this.network]));
677
+ const lookupTableAccount = lookupTables.value;
678
+ (0, assert_1.default)(lookupTableAccount, "Lookup table account not found");
679
+ const executeProposalDirectIx = await this.getExecuteProposalDirectInstruction(vaultOwner, actions, remainingAccounts);
680
+ return this._createTransactionPayload(vaultOwner, [executeProposalDirectIx], [], [lookupTableAccount]);
681
+ }
682
+ async swapAndCreateSilverCard(params) {
683
+ const { vaultOwnerAddress, quoteInfo, emailHash, wrapAndUnwrapSol } = params;
684
+ if ("error" in quoteInfo) {
685
+ throw new Error(quoteInfo.error);
686
+ }
687
+ (0, core_utils_1.assertBufferSize)(emailHash, 32);
688
+ const vaultOwner = (0, anchor_1.translateAddress)(vaultOwnerAddress);
689
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
690
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
691
+ const inputMint = (0, anchor_1.translateAddress)(quoteInfo.inputMint);
692
+ const usdc = (0, anchor_1.translateAddress)(quoteInfo.outputMint);
693
+ const [userPurchaseRecord] = (0, pda_1.deriveUserPurchaseRecordPda)(vaultSigner, this.cardV2ProgramId);
694
+ const [cardConfig] = (0, pda_1.deriveCardConfigPda)(this.cardV2ProgramId);
695
+ const cardConfigInfo = await this.cardV2Program.account.card.fetch(cardConfig, this.connection.commitment);
696
+ const revenueVault = cardConfigInfo.revenueVault;
697
+ const cardVault = cardConfigInfo.cardVault;
698
+ const cardVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(usdc, cardVault, true);
699
+ const revenueVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(usdc, revenueVault, true);
700
+ const amount = quoteInfo.swapMode === "ExactIn"
701
+ ? new bignumber_js_1.BigNumber(quoteInfo.otherAmountThreshold)
702
+ : new bignumber_js_1.BigNumber(quoteInfo.outAmount);
703
+ if (!usdc.equals(cardConfigInfo.usdcMint)) {
704
+ throw new Error(`Invalid usdc: ${usdc.toString()}`);
705
+ }
706
+ const userInputMintAta = (0, solana_common_1.getAssociatedTokenAddressSync)(inputMint, vaultSigner, true);
707
+ const isSolWrapUnwrapNeeded = inputMint.equals(solana_common_1.WSOL) && wrapAndUnwrapSol;
708
+ // check if user has enough balance
709
+ if (!inputMint.equals(solana_common_1.WSOL) || !isSolWrapUnwrapNeeded) {
710
+ const userInputMintAtaInfo = await this.connection.getAccountInfo(userInputMintAta, this.connection.commitment);
711
+ if (!userInputMintAtaInfo) {
712
+ throw new errors_1.AssociatedTokenAccountDoesNotExistsError("User doesn't have associated token account of input mint: " + inputMint.toString());
713
+ }
714
+ const resAndCtx = await this.connection.getTokenAccountBalance(userInputMintAta, this.connection.commitment);
715
+ const balance = resAndCtx.value.amount;
716
+ if (balance === "" || (0, bignumber_js_1.BigNumber)(balance).lt(quoteInfo.inAmount)) {
717
+ throw new errors_1.NotEnoughBalanceError("User doesn't have enough input mint balance");
718
+ }
719
+ }
720
+ else {
721
+ const balance = await this.connection.getBalance(vaultSigner, "confirmed");
722
+ if ((0, bignumber_js_1.BigNumber)(balance).lt(quoteInfo.inAmount)) {
723
+ throw new errors_1.NotEnoughBalanceError("User doesn't have enough SOL balance");
724
+ }
725
+ }
726
+ const customTokenFees = await this.getCardCustomTokenFees();
727
+ const customTokenFee = customTokenFees.find((tf) => tf.tokenAddress === quoteInfo.inputMint);
728
+ const DEFAULT_SWAP_FEE = 5;
729
+ const swapFee = customTokenFee ? (0, bignumber_js_1.BigNumber)(customTokenFee.fee) : (0, bignumber_js_1.BigNumber)(DEFAULT_SWAP_FEE);
730
+ const amountAfterFeeDeduction = amount.minus(amount.times(swapFee.div(100)));
731
+ await this._checkAmountIsWithinProviderRange(cardConfigInfo, amountAfterFeeDeduction);
732
+ await this._checkAmountIsWithinDailyCardLimit(cardConfigInfo, userPurchaseRecord, amountAfterFeeDeduction);
733
+ const { swapTransaction } = await (await fetch(constants_1.JUPITER_SWAP_API, {
734
+ method: "POST",
735
+ headers: {
736
+ "Content-Type": "application/json",
737
+ },
738
+ body: JSON.stringify({
739
+ // quoteResponse from /quote api
740
+ quoteResponse: quoteInfo,
741
+ // user public key to be used for the swap
742
+ userPublicKey: vaultSigner.toString(),
743
+ // auto wrap and unwrap SOL. default is true
744
+ wrapAndUnwrapSol: false,
745
+ // feeAccount is optional. Use if you want to charge a fee. feeBps must have been passed in /quote API.
746
+ // feeAccount: "fee_account_public_key"
747
+ }),
748
+ })).json();
749
+ // deserialize the transaction
750
+ const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
751
+ const transaction = web3_js_1.VersionedTransaction.deserialize(swapTransactionBuf);
752
+ // get address lookup table accounts
753
+ const addressLookupTableAccounts = await Promise.all(transaction.message.addressTableLookups.map(async (lookup) => {
754
+ const data = await this.connection.getAccountInfo(lookup.accountKey).then((res) => res.data);
755
+ return new web3_js_1.AddressLookupTableAccount({
756
+ key: lookup.accountKey,
757
+ state: web3_js_1.AddressLookupTableAccount.deserialize(data),
758
+ });
759
+ }));
760
+ const lookupTableData = await new web3_js_1.Connection((0, web3_js_1.clusterApiUrl)("devnet"))
761
+ .getAccountInfo((0, anchor_1.translateAddress)(constants_1.CARD_LOOKUP_TABLE_ADDRESS[this.network]))
762
+ .then((res) => res.data);
763
+ addressLookupTableAccounts.push(new web3_js_1.AddressLookupTableAccount({
764
+ key: (0, anchor_1.translateAddress)(constants_1.CARD_LOOKUP_TABLE_ADDRESS[this.network]),
765
+ state: web3_js_1.AddressLookupTableAccount.deserialize(lookupTableData),
766
+ }));
767
+ // console.log("address lookup table:\n", addressLookupTableAccounts);
768
+ // decompile transaction message and add transfer instruction
769
+ const message = web3_js_1.TransactionMessage.decompile(transaction.message, {
770
+ addressLookupTableAccounts: addressLookupTableAccounts,
771
+ });
772
+ const swapInstruction = message.instructions.find((ix) => ix.programId.equals((0, anchor_1.translateAddress)(constants_1.JUPITER_AGGREGATOR_PROGRAM_ID)));
773
+ (0, assert_1.default)(swapInstruction, "Swap instruction not found in the transaction message");
774
+ const otherIxs = message.instructions.filter((ix) => !ix.programId.equals((0, anchor_1.translateAddress)(constants_1.JUPITER_AGGREGATOR_PROGRAM_ID)) &&
775
+ !ix.programId.equals(web3_js_1.ComputeBudgetProgram.programId));
776
+ const index = new anchor_1.BN(params.nextCardCounter.toString());
777
+ const swapAndCreateSilverCardIx = await this.getSwapAndCreateSilverCardInstruction(vaultOwner, cardVault, cardVaultAta, revenueVault, revenueVaultAta, inputMint, usdc, vaultSigner, {
778
+ index,
779
+ currency: params.currency,
780
+ emailHash: Array.from(emailHash),
781
+ swapData: swapInstruction.data,
782
+ }, swapInstruction.keys);
783
+ otherIxs.push(swapAndCreateSilverCardIx);
784
+ return this._createTransactionPayload(vaultOwner, otherIxs, [], addressLookupTableAccounts);
785
+ }
786
+ async swapAndLoadCarbonCard(params) {
787
+ const { vaultOwnerAddress, quoteInfo, emailHash, reloadCardId, wrapAndUnwrapSol } = params;
788
+ if ("error" in quoteInfo) {
789
+ throw new Error(quoteInfo.error);
790
+ }
791
+ (0, core_utils_1.assertBufferSize)(emailHash, 32);
792
+ const vaultOwner = (0, anchor_1.translateAddress)(vaultOwnerAddress);
793
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
794
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
795
+ const inputMint = (0, anchor_1.translateAddress)(quoteInfo.inputMint);
796
+ const usdc = (0, anchor_1.translateAddress)(quoteInfo.outputMint);
797
+ const [userPurchaseRecord] = (0, pda_1.deriveUserPurchaseRecordPda)(vaultSigner, this.cardV2ProgramId);
798
+ const [cardConfig] = (0, pda_1.deriveCardConfigPda)(this.cardV2ProgramId);
799
+ const cardConfigInfo = await this.cardV2Program.account.card.fetch(cardConfig, this.connection.commitment);
800
+ const revenueVault = cardConfigInfo.revenueVault;
801
+ const cardVault = cardConfigInfo.cardVault;
802
+ const cardVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(usdc, cardVault, true);
803
+ const revenueVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(usdc, revenueVault, true);
804
+ const amount = quoteInfo.swapMode === "ExactIn"
805
+ ? new bignumber_js_1.BigNumber(quoteInfo.otherAmountThreshold)
806
+ : new bignumber_js_1.BigNumber(quoteInfo.outAmount);
807
+ if (!usdc.equals(cardConfigInfo.usdcMint)) {
808
+ throw new Error(`Invalid usdc: ${usdc.toString()}`);
809
+ }
810
+ const userInputMintAta = (0, solana_common_1.getAssociatedTokenAddressSync)(inputMint, vaultSigner, true);
811
+ const isSolWrapUnwrapNeeded = inputMint.equals(solana_common_1.WSOL) && wrapAndUnwrapSol;
812
+ // check if user has enough balance
813
+ if (!inputMint.equals(solana_common_1.WSOL) || !isSolWrapUnwrapNeeded) {
814
+ const userInputMintAtaInfo = await this.connection.getAccountInfo(userInputMintAta, this.connection.commitment);
815
+ if (!userInputMintAtaInfo) {
816
+ throw new errors_1.AssociatedTokenAccountDoesNotExistsError("User doesn't have associated token account of input mint: " + inputMint.toString());
817
+ }
818
+ const resAndCtx = await this.connection.getTokenAccountBalance(userInputMintAta, this.connection.commitment);
819
+ const balance = resAndCtx.value.amount;
820
+ if (balance === "" || (0, bignumber_js_1.BigNumber)(balance).lt(quoteInfo.inAmount)) {
821
+ throw new errors_1.NotEnoughBalanceError("User doesn't have enough input mint balance");
822
+ }
823
+ }
824
+ else {
825
+ const balance = await this.connection.getBalance(vaultSigner, "confirmed");
826
+ if ((0, bignumber_js_1.BigNumber)(balance).lt(quoteInfo.inAmount)) {
827
+ throw new errors_1.NotEnoughBalanceError("User doesn't have enough SOL balance");
828
+ }
829
+ }
830
+ const customTokenFees = await this.getCardCustomTokenFees();
831
+ const customTokenFee = customTokenFees.find((tf) => tf.tokenAddress === quoteInfo.inputMint);
832
+ const DEFAULT_SWAP_FEE = 5;
833
+ const swapFee = customTokenFee ? (0, bignumber_js_1.BigNumber)(customTokenFee.fee) : (0, bignumber_js_1.BigNumber)(DEFAULT_SWAP_FEE);
834
+ const amountAfterFeeDeduction = amount.minus(amount.times(swapFee.div(100)));
835
+ await this._checkAmountIsWithinProviderRange(cardConfigInfo, amountAfterFeeDeduction);
836
+ await this._checkAmountIsWithinDailyCardLimit(cardConfigInfo, userPurchaseRecord, amountAfterFeeDeduction);
837
+ const { swapTransaction } = await (await fetch(constants_1.JUPITER_SWAP_API, {
838
+ method: "POST",
839
+ headers: {
840
+ "Content-Type": "application/json",
841
+ },
842
+ body: JSON.stringify({
843
+ // quoteResponse from /quote api
844
+ quoteResponse: quoteInfo,
845
+ // user public key to be used for the swap
846
+ userPublicKey: vaultSigner.toString(),
847
+ // auto wrap and unwrap SOL. default is true
848
+ wrapAndUnwrapSol: false,
849
+ // feeAccount is optional. Use if you want to charge a fee. feeBps must have been passed in /quote API.
850
+ // feeAccount: "fee_account_public_key"
851
+ }),
852
+ })).json();
853
+ // deserialize the transaction
854
+ const swapTransactionBuf = Buffer.from(swapTransaction, "base64");
855
+ const transaction = web3_js_1.VersionedTransaction.deserialize(swapTransactionBuf);
856
+ // get address lookup table accounts
857
+ const addressLookupTableAccounts = await Promise.all(transaction.message.addressTableLookups.map(async (lookup) => {
858
+ const data = await this.connection.getAccountInfo(lookup.accountKey).then((res) => res.data);
859
+ return new web3_js_1.AddressLookupTableAccount({
860
+ key: lookup.accountKey,
861
+ state: web3_js_1.AddressLookupTableAccount.deserialize(data),
862
+ });
863
+ }));
864
+ const lookupTableData = await new web3_js_1.Connection((0, web3_js_1.clusterApiUrl)("devnet"))
865
+ .getAccountInfo((0, anchor_1.translateAddress)(constants_1.CARD_LOOKUP_TABLE_ADDRESS[this.network]))
866
+ .then((res) => res.data);
867
+ addressLookupTableAccounts.push(new web3_js_1.AddressLookupTableAccount({
868
+ key: (0, anchor_1.translateAddress)(constants_1.CARD_LOOKUP_TABLE_ADDRESS[this.network]),
869
+ state: web3_js_1.AddressLookupTableAccount.deserialize(lookupTableData),
870
+ }));
871
+ // console.log("address lookup table:\n", addressLookupTableAccounts);
872
+ // decompile transaction message and add transfer instruction
873
+ const message = web3_js_1.TransactionMessage.decompile(transaction.message, {
874
+ addressLookupTableAccounts: addressLookupTableAccounts,
875
+ });
876
+ const swapInstruction = message.instructions.find((ix) => ix.programId.equals((0, anchor_1.translateAddress)(constants_1.JUPITER_AGGREGATOR_PROGRAM_ID)));
877
+ (0, assert_1.default)(swapInstruction, "Swap instruction not found in the transaction message");
878
+ const otherIxs = message.instructions.filter((ix) => !ix.programId.equals((0, anchor_1.translateAddress)(constants_1.JUPITER_AGGREGATOR_PROGRAM_ID)) &&
879
+ !ix.programId.equals(web3_js_1.ComputeBudgetProgram.programId));
880
+ const index = new anchor_1.BN(params.nextCardCounter.toString());
881
+ const swapAndLoadCarbonCardIx = await this.getSwapAndLoadCarbonCardInstruction(cardVault, cardVaultAta, inputMint, solana_common_1.TOKEN_PROGRAM_ID, usdc, revenueVault, revenueVaultAta, vaultOwner, {
882
+ index,
883
+ currency: params.currency,
884
+ emailHash: Array.from(emailHash),
885
+ swapData: swapInstruction.data,
886
+ reloadCardId,
887
+ }, swapInstruction.keys);
888
+ otherIxs.push(swapAndLoadCarbonCardIx);
889
+ return this._createTransactionPayload(vaultOwner, otherIxs, [], addressLookupTableAccounts);
890
+ }
891
+ async getCardCustomTokenFees() {
892
+ const [tokeFeeMapPda] = (0, pda_1.deriveTokenFeeMapPda)(this.cardV2ProgramId);
893
+ const decoded = await this.cardV2Program.account.customFeeMap.fetch(tokeFeeMapPda, this.connection.commitment);
894
+ const tokenFeeList = decoded.feeMap.map((item) => ({
895
+ tokenAddress: item.tokenAddress,
896
+ fee: (0, core_utils_1.bpsToPercent)(item.fee.toNumber()),
897
+ }));
898
+ return tokenFeeList;
899
+ }
900
+ async getCreateStreamFromVaultInstruction(vaultOwner, vault, vaultSigner, vaultSignerAta, receiver, receiverAta, streamToken, streamMetadata, streamConfig, withdrawAccount, streamVault, streamVaultAta, zebecStreamProgram, streamData) {
901
+ return this.vaultV1Program.methods
902
+ .createStream({
903
+ amount: streamData.amount,
904
+ automaticWithdrawal: Number(streamData.automaticWithdrawal),
905
+ cancelableByRecipient: Number(streamData.cancelableByRecipient),
906
+ cancelableBySender: Number(streamData.cancelableBySender),
907
+ canTopup: Number(streamData.canTopup),
908
+ cliffPercentage: streamData.cliffPercentage,
909
+ duration: streamData.duration,
910
+ isPausable: Number(streamData.isPausable),
911
+ rateUpdatable: Number(streamData.rateUpdatable),
912
+ startNow: Number(streamData.startNow),
913
+ startTime: streamData.startTime,
914
+ streamFrequency: streamData.autoWithdrawFrequency,
915
+ streamName: Array.from(streamData.streamName),
916
+ transferableByRecipient: Number(streamData.transferableByRecipient),
917
+ transferableBySender: Number(streamData.transferableBySender),
918
+ })
919
+ .accountsPartial({
920
+ receiver,
921
+ streamToken,
922
+ vaultSignerAta,
923
+ withdrawAccount,
924
+ streamMetadata,
925
+ vaultOwner,
926
+ vault,
927
+ vaultSigner,
928
+ receiverAta,
929
+ streamConfig,
930
+ streamVault,
931
+ streamVaultAta,
932
+ zebecStreamProgram,
933
+ })
934
+ .instruction();
935
+ }
936
+ async getPauseResumeStreamInstruction(streamMetadata, user) {
937
+ return this.streamProgram.methods
938
+ .pauseResumeStream()
939
+ .accounts({
940
+ streamMetadata,
941
+ user,
942
+ })
943
+ .instruction();
944
+ }
945
+ async getCancelStreamInstruction(feePayer, otherParty, otherPartyAta, signer, signerAta, streamMetadata, streamToken, streamVault, streamVaultAta) {
946
+ return this.streamProgram.methods
947
+ .cancelStream()
948
+ .accountsPartial({
949
+ feePayer,
950
+ otherParty,
951
+ otherPartyAta,
952
+ signer,
953
+ signerAta,
954
+ streamMetadata,
955
+ streamToken,
956
+ streamVault,
957
+ streamVaultAta,
958
+ })
959
+ .instruction();
960
+ }
961
+ async getWithdrawStreamInstruction(receiver, receiverAta, streamMetadata, streamToken, streamVault, streamVaultAta, withdrawer, feePayer) {
962
+ return this.streamProgram.methods
963
+ .withdrawStream()
964
+ .accountsPartial({
965
+ streamMetadata,
966
+ streamToken,
967
+ streamVault,
968
+ streamVaultAta,
969
+ receiver,
970
+ receiverAta,
971
+ withdrawer,
972
+ feePayer,
973
+ })
974
+ .instruction();
975
+ }
976
+ async getChangeStreamReceiverInstruction(streamMetadata, newRecipient, signer) {
977
+ return this.streamProgram.methods
978
+ .changeRecipient()
979
+ .accounts({
980
+ newRecipient,
981
+ stream: streamMetadata,
982
+ signer,
983
+ })
984
+ .instruction();
985
+ }
986
+ async createStreamFromVault(params) {
987
+ const vaultOwner = (0, anchor_1.translateAddress)(params.vaultOwner);
988
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
989
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
990
+ const receiver = (0, anchor_1.translateAddress)(params.receiver);
991
+ const [receiverVault] = (0, pda_1.deriveUserVault)(receiver, this.vaultV1ProgramId);
992
+ const [receiverVaultSigner] = (0, pda_1.deriveVaultSigner)(receiverVault, this.vaultV1ProgramId);
993
+ const streamToken = (0, anchor_1.translateAddress)(params.streamToken);
994
+ const vaultSignerAta = (0, solana_common_1.getAssociatedTokenAddressSync)(streamToken, vaultSigner, true);
571
995
  const receiverVaultSignerAta = (0, solana_common_1.getAssociatedTokenAddressSync)(streamToken, receiverVaultSigner, true);
572
996
  const [streamConfig] = (0, pda_1.deriveStreamConfigPda)(this.streamProgramId);
573
997
  const streamConfigInfo = await this.streamProgram.account.streamConfig.fetch(streamConfig, this.connection.commitment);
@@ -603,7 +1027,7 @@ class ZebecVaultService {
603
1027
  return this._createTransactionPayload(vaultOwner, [ix], [streamMetatdataKeypair]);
604
1028
  }
605
1029
  async createMultipleStreamFromVault(params) {
606
- const vaultOwner = (0, anchor_1.translateAddress)(params.sender);
1030
+ const vaultOwner = (0, anchor_1.translateAddress)(params.vaultOwner);
607
1031
  const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
608
1032
  const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
609
1033
  const transactionArtifacts = await Promise.all(params.streamInfo.map(async (info) => {
@@ -625,7 +1049,7 @@ class ZebecVaultService {
625
1049
  const cliffPercentage = new anchor_1.BN((0, core_utils_1.percentToBps)(info.cliffPercentage));
626
1050
  const STREAM_NAME_BUFFER_SIZE = 128;
627
1051
  const streamNameArray = new Uint8Array(STREAM_NAME_BUFFER_SIZE);
628
- streamNameArray.set(anchor_1.utils.bytes.utf8.encode(info.streamName), 0);
1052
+ streamNameArray.set(anchor_1.utils.bytes.utf8.encode(info.streamName));
629
1053
  const ix = await this.getCreateStreamFromVaultInstruction(vaultOwner, vault, vaultSigner, vaultSignerAta, receiverVaultSigner, receiverVaultSignerAta, streamToken, streamMetadata, streamConfig, withdrawAccount, streamVault, streamVaultAta, this.streamProgramId, {
630
1054
  amount,
631
1055
  automaticWithdrawal: info.automaticWithdrawal,
@@ -651,6 +1075,337 @@ class ZebecVaultService {
651
1075
  }));
652
1076
  return this._createMultiTransactionPayload(vaultOwner, transactionArtifacts);
653
1077
  }
1078
+ async cancelStream(params) {
1079
+ const vaultOwner = params.vaultOwner ? (0, anchor_1.translateAddress)(params.vaultOwner) : this.provider.publicKey;
1080
+ if (!vaultOwner) {
1081
+ throw new Error("Either provide a caller or use AnchorProvider for provider in the service");
1082
+ }
1083
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
1084
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
1085
+ const streamMetadata = (0, anchor_1.translateAddress)(params.streamMetadata);
1086
+ const streamMetadataAccount = await this.streamProgram.account.paymentStream.fetch(streamMetadata);
1087
+ const streamToken = streamMetadataAccount.financials.streamToken;
1088
+ const streamer = streamMetadataAccount.parties.sender;
1089
+ const receiver = streamMetadataAccount.parties.receiver;
1090
+ let otherParty;
1091
+ if (vaultSigner.equals(streamer)) {
1092
+ otherParty = receiver;
1093
+ }
1094
+ else if (vaultSigner.equals(receiver)) {
1095
+ otherParty = streamer;
1096
+ }
1097
+ else {
1098
+ throw new Error("User must be either streamer or receiver");
1099
+ }
1100
+ const feePayer = vaultOwner;
1101
+ const userAta = (0, solana_common_1.getAssociatedTokenAddressSync)(streamToken, vaultSigner, true);
1102
+ const otherPartyAta = (0, solana_common_1.getAssociatedTokenAddressSync)(streamToken, otherParty, true);
1103
+ const [streamVault] = (0, pda_1.deriveStreamVaultPda)(streamMetadata, this.streamProgramId);
1104
+ const streamVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(streamToken, streamVault, true);
1105
+ const ix = await this.getCancelStreamInstruction(feePayer, otherParty, otherPartyAta, vaultSigner, userAta, streamMetadata, streamToken, streamVault, streamVaultAta);
1106
+ const actions = [(0, utils_1.transactionInstructionToPropoalAction)(ix)];
1107
+ const remainingAccounts = actions.reduce((acc, current) => {
1108
+ const accounts = current.accountSpecs.map((spec) => ({
1109
+ pubkey: spec.pubkey,
1110
+ isSigner: spec.pubkey.equals(vaultSigner) ? false : spec.isSigner,
1111
+ isWritable: spec.isWritable,
1112
+ }));
1113
+ acc.push(...accounts, {
1114
+ isSigner: false,
1115
+ isWritable: false,
1116
+ pubkey: current.programId,
1117
+ });
1118
+ return acc;
1119
+ }, []);
1120
+ const ixFinal = await this.getExecuteProposalDirectInstruction(vaultOwner, actions, remainingAccounts);
1121
+ return this._createTransactionPayload(feePayer, [ixFinal]);
1122
+ }
1123
+ async pauseResumeStream(params) {
1124
+ const vaultOwner = params.vaultOwner ? (0, anchor_1.translateAddress)(params.vaultOwner) : this.provider.publicKey;
1125
+ if (!vaultOwner) {
1126
+ throw new Error("Either provide a caller or use AnchorProvider for provider in the service");
1127
+ }
1128
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
1129
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
1130
+ const streamMetadata = (0, anchor_1.translateAddress)(params.streamMetadata);
1131
+ const streamMetadataAccount = await this.streamProgram.account.paymentStream.fetch(streamMetadata);
1132
+ const user = streamMetadataAccount.parties.sender;
1133
+ if (!vaultSigner.equals(user)) {
1134
+ throw new Error("Only the streamer can pause/resume the stream");
1135
+ }
1136
+ const ix = await this.getPauseResumeStreamInstruction(streamMetadata, user);
1137
+ const actions = [
1138
+ {
1139
+ accountSpecs: ix.keys,
1140
+ data: ix.data,
1141
+ programId: ix.programId,
1142
+ },
1143
+ ];
1144
+ const remainingAccounts = actions.reduce((acc, current) => {
1145
+ const accounts = current.accountSpecs.map((spec) => ({
1146
+ pubkey: spec.pubkey,
1147
+ isSigner: spec.pubkey.equals(vaultSigner) ? false : spec.isSigner,
1148
+ isWritable: spec.isWritable,
1149
+ }));
1150
+ acc.push(...accounts, {
1151
+ isSigner: false,
1152
+ isWritable: false,
1153
+ pubkey: current.programId,
1154
+ });
1155
+ return acc;
1156
+ }, []);
1157
+ const ixFinal = await this.getExecuteProposalDirectInstruction(vaultOwner, actions, remainingAccounts);
1158
+ return this._createTransactionPayload(vaultOwner, [ixFinal]);
1159
+ }
1160
+ async changeStreamReceiver(params) {
1161
+ const vaultOwner = params.vaultOwner ? (0, anchor_1.translateAddress)(params.vaultOwner) : this.provider.publicKey;
1162
+ if (!vaultOwner) {
1163
+ throw new Error("Either provide a vaultOwner or use AnchorProvider for provider in the service");
1164
+ }
1165
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
1166
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
1167
+ const streamMetadata = (0, anchor_1.translateAddress)(params.streamMetadata);
1168
+ const newRecipient = (0, anchor_1.translateAddress)(params.newRecipient);
1169
+ const ix = await this.getChangeStreamReceiverInstruction(streamMetadata, newRecipient, vaultSigner);
1170
+ const actions = [(0, utils_1.transactionInstructionToPropoalAction)(ix)];
1171
+ const remainingAccounts = actions.reduce((acc, current) => {
1172
+ const accounts = current.accountSpecs.map((spec) => ({
1173
+ pubkey: spec.pubkey,
1174
+ isSigner: spec.pubkey.equals(vaultSigner) ? false : spec.isSigner,
1175
+ isWritable: spec.isWritable,
1176
+ }));
1177
+ acc.push(...accounts, {
1178
+ isSigner: false,
1179
+ isWritable: false,
1180
+ pubkey: current.programId,
1181
+ });
1182
+ return acc;
1183
+ }, []);
1184
+ const ixFinal = await this.getExecuteProposalDirectInstruction(vaultOwner, actions, remainingAccounts);
1185
+ return this._createTransactionPayload(vaultOwner, [ixFinal]);
1186
+ }
1187
+ async withdrawStream(params) {
1188
+ const vaultOwner = params.vaultOwner ? (0, anchor_1.translateAddress)(params.vaultOwner) : this.provider.publicKey;
1189
+ if (!vaultOwner) {
1190
+ throw new Error("Either provide a caller or use AnchorProvider for provider in the service");
1191
+ }
1192
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
1193
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
1194
+ const feePayer = vaultOwner;
1195
+ const withdrawer = vaultSigner;
1196
+ const streamMetadata = (0, anchor_1.translateAddress)(params.streamMetadata);
1197
+ const streamMetadataAccount = await this.streamProgram.account.paymentStream.fetch(streamMetadata);
1198
+ const streamToken = streamMetadataAccount.financials.streamToken;
1199
+ const vaultSignerAta = (0, solana_common_1.getAssociatedTokenAddressSync)(streamToken, vaultSigner, true);
1200
+ const [streamVault] = (0, pda_1.deriveStreamVaultPda)(streamMetadata, this.streamProgramId);
1201
+ const streamVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(streamToken, streamVault, true);
1202
+ const ix = await this.getWithdrawStreamInstruction(vaultSigner, vaultSignerAta, streamMetadata, streamToken, streamVault, streamVaultAta, withdrawer, feePayer);
1203
+ const actions = [(0, utils_1.transactionInstructionToPropoalAction)(ix)];
1204
+ const remainingAccounts = actions.reduce((acc, current) => {
1205
+ const accounts = current.accountSpecs.map((spec) => ({
1206
+ pubkey: spec.pubkey,
1207
+ isSigner: spec.pubkey.equals(vaultSigner) ? false : spec.isSigner,
1208
+ isWritable: spec.isWritable,
1209
+ }));
1210
+ acc.push(...accounts, {
1211
+ isSigner: false,
1212
+ isWritable: false,
1213
+ pubkey: current.programId,
1214
+ });
1215
+ return acc;
1216
+ }, []);
1217
+ const ixFinal = await this.getExecuteProposalDirectInstruction(vaultOwner, actions, remainingAccounts);
1218
+ return this._createTransactionPayload(feePayer, [ixFinal]);
1219
+ }
1220
+ async getStreamMetadataInfo(streamMetadata, commitment) {
1221
+ const metadataInfo = await this.streamProgram.account.paymentStream.fetch((0, anchor_1.translateAddress)(streamMetadata), commitment ?? this.connection.commitment);
1222
+ const streamToken = metadataInfo.financials.streamToken;
1223
+ const streamTokenDecimals = await (0, solana_common_1.getMintDecimals)(this.connection, streamToken);
1224
+ const unitsPerStreamToken = constants_1.TEN_BIGNUM.pow(streamTokenDecimals);
1225
+ const depositedAmount = (0, bignumber_js_1.BigNumber)(metadataInfo.financials.depositedAmount.toString())
1226
+ .div(unitsPerStreamToken)
1227
+ .toFixed();
1228
+ const withdrawnAmount = (0, bignumber_js_1.BigNumber)(metadataInfo.financials.withdrawnAmount.toString())
1229
+ .div(unitsPerStreamToken)
1230
+ .toFixed();
1231
+ const cliffPercentage = Number((0, core_utils_1.bpsToPercent)(metadataInfo.financials.cliffPercentage.toNumber()));
1232
+ return {
1233
+ address: (0, anchor_1.translateAddress)(streamMetadata),
1234
+ parties: {
1235
+ sender: metadataInfo.parties.sender,
1236
+ receiver: metadataInfo.parties.receiver,
1237
+ },
1238
+ financials: {
1239
+ streamToken,
1240
+ cliffPercentage,
1241
+ depositedAmount,
1242
+ withdrawnAmount,
1243
+ },
1244
+ schedule: {
1245
+ startTime: metadataInfo.schedule.startTime.toNumber(),
1246
+ endTime: metadataInfo.schedule.endTime.toNumber(),
1247
+ lastWithdrawTime: metadataInfo.schedule.lastWithdrawTime.toNumber(),
1248
+ frequency: metadataInfo.schedule.frequency.toNumber(),
1249
+ duration: metadataInfo.schedule.duration.toNumber(),
1250
+ pausedTimestamp: metadataInfo.schedule.pausedTimestamp.toNumber(),
1251
+ pausedInterval: metadataInfo.schedule.pausedInterval.toNumber(),
1252
+ canceledTimestamp: metadataInfo.schedule.canceledTimestamp.toNumber(),
1253
+ },
1254
+ permissions: {
1255
+ cancelableBySender: Boolean(metadataInfo.permissions.cancelableBySender),
1256
+ cancelableByRecipient: Boolean(metadataInfo.permissions.cancelableBySender),
1257
+ automaticWithdrawal: Boolean(metadataInfo.permissions.automaticWithdrawal),
1258
+ transferableBySender: Boolean(metadataInfo.permissions.transferableBySender),
1259
+ transferableByRecipient: Boolean(metadataInfo.permissions.transferableByRecipient),
1260
+ canTopup: Boolean(metadataInfo.permissions.canTopup),
1261
+ isPausable: Boolean(metadataInfo.permissions.isPausable),
1262
+ rateUpdatable: Boolean(metadataInfo.permissions.rateUpdatable),
1263
+ },
1264
+ streamName: anchor_1.utils.bytes.utf8.decode(Uint8Array.from(metadataInfo.streamName).filter((byte) => byte !== 0)), // Remove padding zeros
1265
+ };
1266
+ }
1267
+ async getStakeInstruction(feePayer, lockup, stakeToken, stakeVault, staker, userNonce, stakePda, stakeVaultTokenAccount, data) {
1268
+ return this.stakeProgram.methods
1269
+ .stakeZbcn(data)
1270
+ .accountsPartial({
1271
+ stakeToken,
1272
+ feePayer,
1273
+ staker,
1274
+ lockup,
1275
+ stakeVault,
1276
+ userNonce,
1277
+ stakePda,
1278
+ stakeVaultTokenAccount,
1279
+ })
1280
+ .instruction();
1281
+ }
1282
+ async getUnstakeInstruction(feePayer, feeVault, lockup, stakePda, rewardToken, rewardVault, stakeToken, stakeVault, staker, stakerTokenAccount, nonce) {
1283
+ return this.stakeProgram.methods
1284
+ .unstakeZbcn(nonce)
1285
+ .accountsPartial({
1286
+ feePayer,
1287
+ feeVault,
1288
+ rewardToken,
1289
+ stakeToken,
1290
+ staker,
1291
+ lockup,
1292
+ stakeVault,
1293
+ stakePda,
1294
+ rewardVault,
1295
+ stakerTokenAccount,
1296
+ })
1297
+ .instruction();
1298
+ }
1299
+ async stake(params) {
1300
+ const vaultOwner = params.vaultOwner ? (0, anchor_1.translateAddress)(params.vaultOwner) : this.provider.publicKey;
1301
+ if (!vaultOwner) {
1302
+ throw new Error("MissingArgument: Please provide either vaultOwner address or publicKey in provider");
1303
+ }
1304
+ const feePayer = vaultOwner;
1305
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
1306
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
1307
+ const lockup = (0, pda_1.deriveLockupAddress)(params.lockupName, this.stakeProgramId);
1308
+ const lockupAccount = await this.stakeProgram.account.lockup.fetchNullable(lockup, this.connection.commitment);
1309
+ if (!lockupAccount) {
1310
+ throw new Error("Lockup account does not exists for address: " + lockup);
1311
+ }
1312
+ const lockPeriods = lockupAccount.stakeInfo.durationMap.map((item) => item.duration.toNumber());
1313
+ if (!lockPeriods.includes(params.lockPeriod)) {
1314
+ throw new Error("Invalid lockperiod. Available options are: " + lockPeriods.map((l) => l.toString()).concat(", "));
1315
+ }
1316
+ const stakeToken = lockupAccount.stakedToken.tokenAddress;
1317
+ const stakeVault = (0, pda_1.deriveStakeVaultAddress)(lockup, this.stakeProgramId);
1318
+ const userNonce = (0, pda_1.deriveUserNonceAddress)(vaultSigner, lockup, this.stakeProgramId);
1319
+ const userNonceAccount = await this.stakeProgram.account.userNonce.fetchNullable(userNonce, this.connection.commitment);
1320
+ let nonce = BigInt(0);
1321
+ if (userNonceAccount) {
1322
+ nonce = BigInt(userNonceAccount.nonce.toString());
1323
+ }
1324
+ const stakePda = (0, pda_1.deriveStakeAddress)(vaultSigner, lockup, nonce, this.stakeProgramId);
1325
+ const stakeVaultTokenAccount = (0, solana_common_1.getAssociatedTokenAddressSync)(stakeToken, stakeVault, true);
1326
+ const stakeTokenDecimals = await (0, solana_common_1.getMintDecimals)(this.connection, stakeToken);
1327
+ const UNITS_PER_STAKE_TOKEN = constants_1.TEN_BIGNUM.pow(stakeTokenDecimals);
1328
+ const instruction = await this.getStakeInstruction(feePayer, lockup, stakeToken, stakeVault, vaultSigner, userNonce, stakePda, stakeVaultTokenAccount, {
1329
+ amount: new anchor_1.BN((0, bignumber_js_1.BigNumber)(params.amount).times(UNITS_PER_STAKE_TOKEN).toFixed(0)),
1330
+ lockPeriod: new anchor_1.BN(params.lockPeriod),
1331
+ nonce: new anchor_1.BN(params.nonce.toString()),
1332
+ });
1333
+ const actions = [(0, utils_1.transactionInstructionToPropoalAction)(instruction)];
1334
+ const remainingAccounts = actions.reduce((acc, current) => {
1335
+ const accounts = current.accountSpecs.map((spec) => ({
1336
+ pubkey: spec.pubkey,
1337
+ isSigner: spec.pubkey.equals(vaultSigner) ? false : spec.isSigner,
1338
+ isWritable: spec.isWritable,
1339
+ }));
1340
+ acc.push(...accounts, {
1341
+ isSigner: false,
1342
+ isWritable: false,
1343
+ pubkey: current.programId,
1344
+ });
1345
+ return acc;
1346
+ }, []);
1347
+ const ixFinal = await this.getExecuteProposalDirectInstruction(vaultOwner, actions, remainingAccounts);
1348
+ const lookupTables = await this.connection.getAddressLookupTable((0, anchor_1.translateAddress)(constants_1.STAKE_LOOKUP_TABLE_ADDRESS[this.network]));
1349
+ const lookupTableAccount = lookupTables.value;
1350
+ (0, assert_1.default)(lookupTableAccount, "Lookup table account not found");
1351
+ return this._createTransactionPayload(vaultOwner, [ixFinal], [], [lookupTableAccount]);
1352
+ }
1353
+ async unstake(params) {
1354
+ const vaultOwner = params.vaultOwner ? (0, anchor_1.translateAddress)(params.vaultOwner) : this.provider.publicKey;
1355
+ if (!vaultOwner) {
1356
+ throw new Error("MissingArgument: Please provide either staker address or publicKey in provider");
1357
+ }
1358
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
1359
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
1360
+ const feePayer = vaultOwner;
1361
+ const lockup = (0, pda_1.deriveLockupAddress)(params.lockupName, this.stakeProgramId);
1362
+ const lockupAccount = await this.stakeProgram.account.lockup.fetchNullable(lockup, this.connection.commitment);
1363
+ if (!lockupAccount) {
1364
+ throw new Error("Lockup account does not exists for address: " + lockup);
1365
+ }
1366
+ const stakeToken = lockupAccount.stakedToken.tokenAddress;
1367
+ const rewardToken = lockupAccount.rewardToken.tokenAddress;
1368
+ const feeVault = lockupAccount.feeInfo.feeVault;
1369
+ const stakePda = (0, pda_1.deriveStakeAddress)(vaultSigner, lockup, params.nonce, this.stakeProgramId);
1370
+ const rewardVault = (0, pda_1.deriveRewardVaultAddress)(lockup, this.stakeProgramId);
1371
+ const stakeVault = (0, pda_1.deriveStakeVaultAddress)(lockup, this.stakeProgramId);
1372
+ const stakerTokenAccount = (0, solana_common_1.getAssociatedTokenAddressSync)(stakeToken, vaultSigner, true);
1373
+ const instruction = await this.getUnstakeInstruction(feePayer, feeVault, lockup, stakePda, rewardToken, rewardVault, stakeToken, stakeVault, vaultSigner, stakerTokenAccount, new anchor_1.BN(params.nonce.toString()));
1374
+ const actions = [(0, utils_1.transactionInstructionToPropoalAction)(instruction)];
1375
+ const remainingAccounts = actions.reduce((acc, current) => {
1376
+ const accounts = current.accountSpecs.map((spec) => ({
1377
+ pubkey: spec.pubkey,
1378
+ isSigner: spec.pubkey.equals(vaultSigner) ? false : spec.isSigner,
1379
+ isWritable: spec.isWritable,
1380
+ }));
1381
+ acc.push(...accounts, {
1382
+ isSigner: false,
1383
+ isWritable: false,
1384
+ pubkey: current.programId,
1385
+ });
1386
+ return acc;
1387
+ }, []);
1388
+ const ixFinal = await this.getExecuteProposalDirectInstruction(vaultOwner, actions, remainingAccounts);
1389
+ const lookupTables = await this.connection.getAddressLookupTable((0, anchor_1.translateAddress)(constants_1.STAKE_LOOKUP_TABLE_ADDRESS[this.network]));
1390
+ const lookupTableAccount = lookupTables.value;
1391
+ (0, assert_1.default)(lookupTableAccount, "Lookup table account not found");
1392
+ return this._createTransactionPayload(vaultOwner, [ixFinal], [], [lookupTableAccount]);
1393
+ }
1394
+ async getStakeUserNonceInfo(lockupName, vaultOwnerAddress, commitment) {
1395
+ const vaultOwner = (0, anchor_1.translateAddress)(vaultOwnerAddress);
1396
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
1397
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
1398
+ const lockup = (0, pda_1.deriveLockupAddress)(lockupName, this.stakeProgramId);
1399
+ const userNonceAddress = (0, pda_1.deriveUserNonceAddress)(vaultSigner, lockup, this.stakeProgramId);
1400
+ const userNonceAccount = await this.stakeProgram.account.userNonce.fetchNullable(userNonceAddress, commitment ?? this.connection.commitment);
1401
+ if (!userNonceAccount) {
1402
+ return null;
1403
+ }
1404
+ return {
1405
+ address: userNonceAddress.toString(),
1406
+ nonce: BigInt(userNonceAccount.nonce.toString()),
1407
+ };
1408
+ }
654
1409
  async _createTransactionPayload(payerKey, instructions, signers, addressLookupTableAccounts) {
655
1410
  const errorMap = new Map();
656
1411
  this.vaultV1Program.idl.errors.forEach((error) => errorMap.set(error.code, error.msg));
@@ -680,83 +1435,6 @@ class ZebecVaultService {
680
1435
  }));
681
1436
  return new solana_common_1.MultiTransactionPayload(this.connection, errorMap, transactionData, signAllTransactions);
682
1437
  }
683
- async getVaultInfoOfUser(user) {
684
- user = user ? (0, anchor_1.translateAddress)(user) : this.provider.publicKey;
685
- if (!user) {
686
- throw new Error("Either provide a user or use AnchorProvider for provider in the service");
687
- }
688
- const [vault] = (0, pda_1.deriveUserVault)(user, this.vaultV1ProgramId);
689
- const vaultAccount = await this.vaultV1Program.account.vault.fetchNullable(vault, this.connection.commitment);
690
- if (!vaultAccount) {
691
- return null;
692
- }
693
- return {
694
- createdDate: vaultAccount.createdDate.toNumber(),
695
- owner: vaultAccount.owner,
696
- signerBump: vaultAccount.signerBump,
697
- vault,
698
- };
699
- }
700
- async getAllVaultsInfo() {
701
- const accountInfos = await this.connection.getProgramAccounts(this.vaultV1ProgramId, {
702
- commitment: this.connection.commitment,
703
- filters: [
704
- {
705
- memcmp: {
706
- offset: 0, // offset for discriminator in Vault
707
- bytes: anchor_1.utils.bytes.bs58.encode(this.vaultV1Program.idl.accounts[1].discriminator),
708
- encoding: "base58",
709
- },
710
- },
711
- ],
712
- });
713
- const vaults = accountInfos.map((accountInfo) => {
714
- const vaultAccount = this.vaultV1Program.coder.accounts.decode(this.vaultV1Program.idl.accounts[1].name, accountInfo.account.data);
715
- return {
716
- vault: accountInfo.pubkey,
717
- owner: vaultAccount.owner,
718
- createdDate: vaultAccount.createdDate.toNumber(),
719
- signerBump: vaultAccount.signerBump,
720
- };
721
- });
722
- return vaults;
723
- }
724
- async getProposalsInfoOfVault(vault) {
725
- const _vault = (0, anchor_1.translateAddress)(vault);
726
- const accountInfos = await this.connection.getProgramAccounts(this.vaultV1ProgramId, {
727
- commitment: this.connection.commitment,
728
- filters: [
729
- {
730
- memcmp: {
731
- offset: 0, // offset for discriminator in Proposal
732
- bytes: anchor_1.utils.bytes.bs58.encode(this.vaultV1Program.idl.accounts[0].discriminator),
733
- encoding: "base58",
734
- },
735
- },
736
- {
737
- memcmp: {
738
- offset: 8, // offset for owner field in Proposal
739
- bytes: _vault.toBase58(),
740
- encoding: "base58",
741
- },
742
- },
743
- ],
744
- });
745
- const proposals = accountInfos.map((accountInfo) => {
746
- const proposalAccount = this.vaultV1Program.coder.accounts.decode(this.vaultV1Program.idl.accounts[0].name, accountInfo.account.data);
747
- return {
748
- proposal: accountInfo.pubkey,
749
- vault: proposalAccount.vault,
750
- proposalStage: proposalAccount.proposalStage,
751
- createdDate: proposalAccount.createdDate.toNumber(),
752
- expiryDate: proposalAccount.expiryDate.toNumber(),
753
- name: proposalAccount.name,
754
- actions: proposalAccount.actions,
755
- isExecuted: proposalAccount.isExecuted,
756
- };
757
- });
758
- return proposals;
759
- }
760
1438
  get vaultV1ProgramId() {
761
1439
  return this.vaultV1Program.programId;
762
1440
  }
@@ -766,6 +1444,9 @@ class ZebecVaultService {
766
1444
  get streamProgramId() {
767
1445
  return this.streamProgram.programId;
768
1446
  }
1447
+ get stakeProgramId() {
1448
+ return this.stakeProgram.programId;
1449
+ }
769
1450
  get connection() {
770
1451
  return this.provider.connection;
771
1452
  }