@zebec-network/zebec-vault-sdk 3.0.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,170 +380,619 @@ 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);
571
- const receiverVaultSignerAta = (0, solana_common_1.getAssociatedTokenAddressSync)(streamToken, receiverVaultSigner, 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);
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);
574
998
  const withdrawAccount = streamConfigInfo.withdrawAccount;
@@ -580,8 +1004,8 @@ class ZebecVaultService {
580
1004
  const amount = new anchor_1.BN((0, bignumber_js_1.BigNumber)(params.amount).times(constants_1.TEN_BIGNUM.pow(streamTokenDecimals)).toFixed(0));
581
1005
  const cliffPercentage = new anchor_1.BN((0, core_utils_1.percentToBps)(params.cliffPercentage));
582
1006
  const STREAM_NAME_BUFFER_SIZE = 128;
583
- const streamNameBuffer = Buffer.alloc(STREAM_NAME_BUFFER_SIZE);
584
- streamNameBuffer.fill(anchor_1.utils.bytes.utf8.encode(params.streamName), 0);
1007
+ const streamNameArray = new Uint8Array(STREAM_NAME_BUFFER_SIZE);
1008
+ streamNameArray.set(anchor_1.utils.bytes.utf8.encode(params.streamName));
585
1009
  const ix = await this.getCreateStreamFromVaultInstruction(vaultOwner, vault, vaultSigner, vaultSignerAta, receiverVaultSigner, receiverVaultSignerAta, streamToken, streamMetadata, streamConfig, withdrawAccount, streamVault, streamVaultAta, this.streamProgramId, {
586
1010
  amount,
587
1011
  automaticWithdrawal: params.automaticWithdrawal,
@@ -596,100 +1020,420 @@ class ZebecVaultService {
596
1020
  rateUpdatable: params.rateUpdatable,
597
1021
  startNow: params.startNow,
598
1022
  startTime: new anchor_1.BN(params.startTime),
599
- streamName: streamNameBuffer,
1023
+ streamName: streamNameArray,
600
1024
  transferableByRecipient: params.transferableByRecipient,
601
1025
  transferableBySender: params.transferableBySender,
602
1026
  });
603
1027
  return this._createTransactionPayload(vaultOwner, [ix], [streamMetatdataKeypair]);
604
1028
  }
605
- async _createTransactionPayload(payerKey, instructions, signers, addressLookupTableAccounts) {
606
- const errorMap = new Map();
607
- this.vaultV1Program.idl.errors.forEach((error) => errorMap.set(error.code, error.msg));
608
- let signTransaction = undefined;
609
- const provider = this.provider;
610
- if (provider instanceof anchor_1.AnchorProvider) {
611
- signTransaction = async (tx) => {
612
- return provider.wallet.signTransaction(tx);
1029
+ async createMultipleStreamFromVault(params) {
1030
+ const vaultOwner = (0, anchor_1.translateAddress)(params.vaultOwner);
1031
+ const [vault] = (0, pda_1.deriveUserVault)(vaultOwner, this.vaultV1ProgramId);
1032
+ const [vaultSigner] = (0, pda_1.deriveVaultSigner)(vault, this.vaultV1ProgramId);
1033
+ const transactionArtifacts = await Promise.all(params.streamInfo.map(async (info) => {
1034
+ const receiver = (0, anchor_1.translateAddress)(info.receiver);
1035
+ const [receiverVault] = (0, pda_1.deriveUserVault)(receiver, this.vaultV1ProgramId);
1036
+ const [receiverVaultSigner] = (0, pda_1.deriveVaultSigner)(receiverVault, this.vaultV1ProgramId);
1037
+ const streamToken = (0, anchor_1.translateAddress)(info.streamToken);
1038
+ const vaultSignerAta = (0, solana_common_1.getAssociatedTokenAddressSync)(streamToken, vaultSigner, true);
1039
+ const receiverVaultSignerAta = (0, solana_common_1.getAssociatedTokenAddressSync)(streamToken, receiverVaultSigner, true);
1040
+ const [streamConfig] = (0, pda_1.deriveStreamConfigPda)(this.streamProgramId);
1041
+ const streamConfigInfo = await this.streamProgram.account.streamConfig.fetch(streamConfig, this.connection.commitment);
1042
+ const withdrawAccount = streamConfigInfo.withdrawAccount;
1043
+ const streamMetatdataKeypair = info.streamMetadataKeypair ?? web3_js_1.Keypair.generate();
1044
+ const streamMetadata = streamMetatdataKeypair.publicKey;
1045
+ const [streamVault] = (0, pda_1.deriveStreamVaultPda)(streamMetadata, this.streamProgramId);
1046
+ const streamVaultAta = (0, solana_common_1.getAssociatedTokenAddressSync)(streamToken, streamVault, true);
1047
+ const streamTokenDecimals = await (0, solana_common_1.getMintDecimals)(this.connection, streamToken);
1048
+ const amount = new anchor_1.BN((0, bignumber_js_1.BigNumber)(info.amount).times(constants_1.TEN_BIGNUM.pow(streamTokenDecimals)).toFixed(0));
1049
+ const cliffPercentage = new anchor_1.BN((0, core_utils_1.percentToBps)(info.cliffPercentage));
1050
+ const STREAM_NAME_BUFFER_SIZE = 128;
1051
+ const streamNameArray = new Uint8Array(STREAM_NAME_BUFFER_SIZE);
1052
+ streamNameArray.set(anchor_1.utils.bytes.utf8.encode(info.streamName));
1053
+ const ix = await this.getCreateStreamFromVaultInstruction(vaultOwner, vault, vaultSigner, vaultSignerAta, receiverVaultSigner, receiverVaultSignerAta, streamToken, streamMetadata, streamConfig, withdrawAccount, streamVault, streamVaultAta, this.streamProgramId, {
1054
+ amount,
1055
+ automaticWithdrawal: info.automaticWithdrawal,
1056
+ autoWithdrawFrequency: new anchor_1.BN(info.autoWithdrawFrequency),
1057
+ cancelableByRecipient: info.cancelableByRecipient,
1058
+ cancelableBySender: info.cancelableBySender,
1059
+ canTopup: info.canTopup,
1060
+ cliffPercentage,
1061
+ duration: new anchor_1.BN(info.duration),
1062
+ isPausable: info.isPausable,
1063
+ numberOfWithdrawls: new anchor_1.BN(Math.floor(info.duration / info.autoWithdrawFrequency)),
1064
+ rateUpdatable: info.rateUpdatable,
1065
+ startNow: info.startNow,
1066
+ startTime: new anchor_1.BN(info.startTime),
1067
+ streamName: streamNameArray,
1068
+ transferableByRecipient: info.transferableByRecipient,
1069
+ transferableBySender: info.transferableBySender,
1070
+ });
1071
+ return {
1072
+ instructions: [ix],
1073
+ signers: [streamMetatdataKeypair],
613
1074
  };
1075
+ }));
1076
+ return this._createMultiTransactionPayload(vaultOwner, transactionArtifacts);
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");
614
1135
  }
615
- return new solana_common_1.TransactionPayload(this.provider.connection, errorMap, instructions, payerKey, signers, addressLookupTableAccounts, signTransaction);
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]);
616
1159
  }
617
- async getVaultInfoOfUser(user) {
618
- user = user ? (0, anchor_1.translateAddress)(user) : this.provider.publicKey;
619
- if (!user) {
620
- throw new Error("Either provide a user or use AnchorProvider for provider in the service");
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");
621
1164
  }
622
- const [vault] = (0, pda_1.deriveUserVault)(user, this.vaultV1ProgramId);
623
- const vaultAccount = await this.vaultV1Program.account.vault.fetchNullable(vault, this.connection.commitment);
624
- if (!vaultAccount) {
625
- return null;
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");
626
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()));
627
1232
  return {
628
- createdDate: vaultAccount.createdDate.toNumber(),
629
- owner: vaultAccount.owner,
630
- signerBump: vaultAccount.signerBump,
631
- vault,
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
632
1265
  };
633
1266
  }
634
- async getAllVaultsInfo() {
635
- const accountInfos = await this.connection.getProgramAccounts(this.vaultV1ProgramId, {
636
- commitment: this.connection.commitment,
637
- filters: [
638
- {
639
- memcmp: {
640
- offset: 0, // offset for discriminator in Vault
641
- bytes: anchor_1.utils.bytes.bs58.encode(this.vaultV1Program.idl.accounts[1].discriminator),
642
- encoding: "base58",
643
- },
644
- },
645
- ],
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()),
646
1332
  });
647
- const vaults = accountInfos.map((accountInfo) => {
648
- const vaultAccount = this.vaultV1Program.coder.accounts.decode(this.vaultV1Program.idl.accounts[1].name, accountInfo.account.data);
649
- return {
650
- vault: accountInfo.pubkey,
651
- owner: vaultAccount.owner,
652
- createdDate: vaultAccount.createdDate.toNumber(),
653
- signerBump: vaultAccount.signerBump,
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
+ }
1409
+ async _createTransactionPayload(payerKey, instructions, signers, addressLookupTableAccounts) {
1410
+ const errorMap = new Map();
1411
+ this.vaultV1Program.idl.errors.forEach((error) => errorMap.set(error.code, error.msg));
1412
+ let signTransaction = undefined;
1413
+ const provider = this.provider;
1414
+ if (provider instanceof anchor_1.AnchorProvider) {
1415
+ signTransaction = async (tx) => {
1416
+ return provider.wallet.signTransaction(tx);
654
1417
  };
655
- });
656
- return vaults;
1418
+ }
1419
+ return new solana_common_1.TransactionPayload(this.provider.connection, errorMap, { instructions, feePayer: payerKey, signers, addressLookupTableAccounts }, signTransaction);
657
1420
  }
658
- async getProposalsInfoOfVault(vault) {
659
- const _vault = (0, anchor_1.translateAddress)(vault);
660
- const accountInfos = await this.connection.getProgramAccounts(this.vaultV1ProgramId, {
661
- commitment: this.connection.commitment,
662
- filters: [
663
- {
664
- memcmp: {
665
- offset: 0, // offset for discriminator in Proposal
666
- bytes: anchor_1.utils.bytes.bs58.encode(this.vaultV1Program.idl.accounts[0].discriminator),
667
- encoding: "base58",
668
- },
669
- },
670
- {
671
- memcmp: {
672
- offset: 8, // offset for owner field in Proposal
673
- bytes: _vault.toBase58(),
674
- encoding: "base58",
675
- },
676
- },
677
- ],
678
- });
679
- const proposals = accountInfos.map((accountInfo) => {
680
- const proposalAccount = this.vaultV1Program.coder.accounts.decode(this.vaultV1Program.idl.accounts[0].name, accountInfo.account.data);
681
- return {
682
- proposal: accountInfo.pubkey,
683
- vault: proposalAccount.vault,
684
- proposalStage: proposalAccount.proposalStage,
685
- createdDate: proposalAccount.createdDate.toNumber(),
686
- expiryDate: proposalAccount.expiryDate.toNumber(),
687
- name: proposalAccount.name,
688
- actions: proposalAccount.actions,
689
- isExecuted: proposalAccount.isExecuted,
1421
+ async _createMultiTransactionPayload(payerKey, transactionArtifacts) {
1422
+ const errorMap = new Map();
1423
+ this.vaultV1Program.idl.errors.forEach((error) => errorMap.set(error.code, error.msg));
1424
+ let signAllTransactions = undefined;
1425
+ const provider = this.provider;
1426
+ if (provider instanceof anchor_1.AnchorProvider) {
1427
+ signAllTransactions = async (txns) => {
1428
+ return provider.wallet.signAllTransactions(txns);
690
1429
  };
691
- });
692
- return proposals;
1430
+ }
1431
+ const transactionData = transactionArtifacts.map((data) => ({
1432
+ instructions: data.instructions,
1433
+ feePayer: payerKey,
1434
+ signers: data.signers,
1435
+ }));
1436
+ return new solana_common_1.MultiTransactionPayload(this.connection, errorMap, transactionData, signAllTransactions);
693
1437
  }
694
1438
  get vaultV1ProgramId() {
695
1439
  return this.vaultV1Program.programId;
@@ -700,6 +1444,9 @@ class ZebecVaultService {
700
1444
  get streamProgramId() {
701
1445
  return this.streamProgram.programId;
702
1446
  }
1447
+ get stakeProgramId() {
1448
+ return this.stakeProgram.programId;
1449
+ }
703
1450
  get connection() {
704
1451
  return this.provider.connection;
705
1452
  }