@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/artifacts/index.d.ts +3 -1
- package/dist/artifacts/index.js +4 -1
- package/dist/artifacts/zebec_instant_card.d.ts +6 -0
- package/dist/artifacts/zebec_instant_card.json +6 -0
- package/dist/artifacts/zebec_stake_v1.d.ts +1551 -0
- package/dist/artifacts/zebec_stake_v1.js +2 -0
- package/dist/artifacts/zebec_stake_v1.json +1125 -0
- package/dist/constants.d.ts +3 -1
- package/dist/constants.js +10 -2
- package/dist/errors.d.ts +30 -0
- package/dist/errors.js +57 -0
- package/dist/pda.d.ts +5 -0
- package/dist/pda.js +36 -2
- package/dist/service.d.ts +76 -144
- package/dist/service.js +1025 -344
- package/dist/types.d.ts +227 -1
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +8 -0
- package/package.json +3 -2
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
|
-
|
|
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
|
|
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 =
|
|
264
|
-
const
|
|
265
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
409
|
-
|
|
410
|
-
if (
|
|
411
|
-
throw new 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,
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
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
|
|
563
|
-
const
|
|
564
|
-
const
|
|
565
|
-
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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.
|
|
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)
|
|
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
|
}
|