@symmetry-hq/sdk 1.0.1
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/src/constants.d.ts +23 -0
- package/dist/src/constants.js +38 -0
- package/dist/src/index.d.ts +804 -0
- package/dist/src/index.js +2097 -0
- package/dist/src/instructions/automation/auction.d.ts +6 -0
- package/dist/src/instructions/automation/auction.js +40 -0
- package/dist/src/instructions/automation/claimBounty.d.ts +12 -0
- package/dist/src/instructions/automation/claimBounty.js +44 -0
- package/dist/src/instructions/automation/flashSwap.d.ts +21 -0
- package/dist/src/instructions/automation/flashSwap.js +74 -0
- package/dist/src/instructions/automation/priceUpdate.d.ts +19 -0
- package/dist/src/instructions/automation/priceUpdate.js +89 -0
- package/dist/src/instructions/automation/rebalanceIntent.d.ts +32 -0
- package/dist/src/instructions/automation/rebalanceIntent.js +117 -0
- package/dist/src/instructions/automation/rebalanceSwap.d.ts +11 -0
- package/dist/src/instructions/automation/rebalanceSwap.js +42 -0
- package/dist/src/instructions/management/addBounty.d.ts +7 -0
- package/dist/src/instructions/management/addBounty.js +41 -0
- package/dist/src/instructions/management/admin.d.ts +9 -0
- package/dist/src/instructions/management/admin.js +53 -0
- package/dist/src/instructions/management/claimFees.d.ts +15 -0
- package/dist/src/instructions/management/claimFees.js +95 -0
- package/dist/src/instructions/management/createBasket.d.ts +21 -0
- package/dist/src/instructions/management/createBasket.js +98 -0
- package/dist/src/instructions/management/edit.d.ts +51 -0
- package/dist/src/instructions/management/edit.js +477 -0
- package/dist/src/instructions/management/luts.d.ts +30 -0
- package/dist/src/instructions/management/luts.js +99 -0
- package/dist/src/instructions/pda.d.ts +25 -0
- package/dist/src/instructions/pda.js +128 -0
- package/dist/src/instructions/user/deposit.d.ts +20 -0
- package/dist/src/instructions/user/deposit.js +100 -0
- package/dist/src/instructions/user/withdraw.d.ts +8 -0
- package/dist/src/instructions/user/withdraw.js +36 -0
- package/dist/src/jup.d.ts +49 -0
- package/dist/src/jup.js +80 -0
- package/dist/src/keeperMonitor.d.ts +52 -0
- package/dist/src/keeperMonitor.js +624 -0
- package/dist/src/layouts/basket.d.ts +191 -0
- package/dist/src/layouts/basket.js +51 -0
- package/dist/src/layouts/config.d.ts +281 -0
- package/dist/src/layouts/config.js +237 -0
- package/dist/src/layouts/fraction.d.ts +20 -0
- package/dist/src/layouts/fraction.js +164 -0
- package/dist/src/layouts/intents/bounty.d.ts +18 -0
- package/dist/src/layouts/intents/bounty.js +19 -0
- package/dist/src/layouts/intents/intent.d.ts +209 -0
- package/dist/src/layouts/intents/intent.js +97 -0
- package/dist/src/layouts/intents/rebalanceIntent.d.ts +212 -0
- package/dist/src/layouts/intents/rebalanceIntent.js +94 -0
- package/dist/src/layouts/lookupTable.d.ts +7 -0
- package/dist/src/layouts/lookupTable.js +10 -0
- package/dist/src/layouts/oracle.d.ts +63 -0
- package/dist/src/layouts/oracle.js +96 -0
- package/dist/src/states/basket.d.ts +14 -0
- package/dist/src/states/basket.js +479 -0
- package/dist/src/states/config.d.ts +3 -0
- package/dist/src/states/config.js +71 -0
- package/dist/src/states/intents/intent.d.ts +10 -0
- package/dist/src/states/intents/intent.js +316 -0
- package/dist/src/states/intents/rebalanceIntent.d.ts +42 -0
- package/dist/src/states/intents/rebalanceIntent.js +680 -0
- package/dist/src/states/oracles/constants.d.ts +9 -0
- package/dist/src/states/oracles/constants.js +15 -0
- package/dist/src/states/oracles/oracle.d.ts +24 -0
- package/dist/src/states/oracles/oracle.js +168 -0
- package/dist/src/states/oracles/pythOracle.d.ts +132 -0
- package/dist/src/states/oracles/pythOracle.js +609 -0
- package/dist/src/states/oracles/raydiumClmmOracle.d.ts +184 -0
- package/dist/src/states/oracles/raydiumClmmOracle.js +843 -0
- package/dist/src/states/oracles/raydiumCpmmOracle.d.ts +120 -0
- package/dist/src/states/oracles/raydiumCpmmOracle.js +540 -0
- package/dist/src/states/oracles/switchboardOracle.d.ts +0 -0
- package/dist/src/states/oracles/switchboardOracle.js +1 -0
- package/dist/src/states/withdrawBasketFees.d.ts +10 -0
- package/dist/src/states/withdrawBasketFees.js +154 -0
- package/dist/src/txUtils.d.ts +65 -0
- package/dist/src/txUtils.js +306 -0
- package/dist/test.d.ts +1 -0
- package/dist/test.js +561 -0
- package/package.json +31 -0
- package/src/constants.ts +40 -0
- package/src/index.ts +2431 -0
- package/src/instructions/automation/auction.ts +55 -0
- package/src/instructions/automation/claimBounty.ts +69 -0
- package/src/instructions/automation/flashSwap.ts +104 -0
- package/src/instructions/automation/priceUpdate.ts +117 -0
- package/src/instructions/automation/rebalanceIntent.ts +181 -0
- package/src/instructions/management/addBounty.ts +55 -0
- package/src/instructions/management/admin.ts +72 -0
- package/src/instructions/management/claimFees.ts +129 -0
- package/src/instructions/management/createBasket.ts +138 -0
- package/src/instructions/management/edit.ts +602 -0
- package/src/instructions/management/luts.ts +157 -0
- package/src/instructions/pda.ts +151 -0
- package/src/instructions/user/deposit.ts +143 -0
- package/src/instructions/user/withdraw.ts +53 -0
- package/src/jup.ts +113 -0
- package/src/keeperMonitor.ts +585 -0
- package/src/layouts/basket.ts +233 -0
- package/src/layouts/config.ts +576 -0
- package/src/layouts/fraction.ts +164 -0
- package/src/layouts/intents/bounty.ts +35 -0
- package/src/layouts/intents/intent.ts +324 -0
- package/src/layouts/intents/rebalanceIntent.ts +306 -0
- package/src/layouts/lookupTable.ts +14 -0
- package/src/layouts/oracle.ts +157 -0
- package/src/states/basket.ts +527 -0
- package/src/states/config.ts +62 -0
- package/src/states/intents/intent.ts +311 -0
- package/src/states/intents/rebalanceIntent.ts +751 -0
- package/src/states/oracles/constants.ts +13 -0
- package/src/states/oracles/oracle.ts +212 -0
- package/src/states/oracles/pythOracle.ts +874 -0
- package/src/states/oracles/raydiumClmmOracle.ts +1193 -0
- package/src/states/oracles/raydiumCpmmOracle.ts +784 -0
- package/src/states/oracles/switchboardOracle.ts +0 -0
- package/src/states/withdrawBasketFees.ts +160 -0
- package/src/txUtils.ts +424 -0
- package/test.ts +609 -0
- package/tsconfig.json +101 -0
|
File without changes
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { AccountInfo, Connection, GetProgramAccountsFilter, GetProgramAccountsResponse, PublicKey } from '@solana/web3.js';
|
|
2
|
+
import { BASKETS_V3_PROGRAM_ID, MAX_MANAGERS_PER_BASKET, MAX_SUPPORTED_TOKENS_PER_BASKET } from '../constants';
|
|
3
|
+
import { FormattedWithdrawBasketFees, WithdrawBasketFees, WithdrawBasketFeesLayout } from '../layouts/basket';
|
|
4
|
+
import { getMultipleAccountsInfoBatched } from '../txUtils';
|
|
5
|
+
|
|
6
|
+
export function addFieldsToWithdrawBasketFees(withdrawBasketFees: WithdrawBasketFees): WithdrawBasketFees {
|
|
7
|
+
// Filter out default/empty owners
|
|
8
|
+
let numOwners = MAX_MANAGERS_PER_BASKET;
|
|
9
|
+
while (numOwners > 0 && withdrawBasketFees.owners[numOwners - 1].equals(PublicKey.default)) {
|
|
10
|
+
numOwners--;
|
|
11
|
+
}
|
|
12
|
+
withdrawBasketFees.owners = withdrawBasketFees.owners.slice(0, numOwners);
|
|
13
|
+
withdrawBasketFees.ownersWeights = withdrawBasketFees.ownersWeights.slice(0, numOwners);
|
|
14
|
+
|
|
15
|
+
// Filter out default/empty tokens
|
|
16
|
+
let numTokens = MAX_SUPPORTED_TOKENS_PER_BASKET;
|
|
17
|
+
while (numTokens > 0 && withdrawBasketFees.accumulatedTokens[numTokens - 1].equals(PublicKey.default)) {
|
|
18
|
+
numTokens--;
|
|
19
|
+
}
|
|
20
|
+
withdrawBasketFees.accumulatedTokens = withdrawBasketFees.accumulatedTokens.slice(0, numTokens);
|
|
21
|
+
withdrawBasketFees.accumulatedFees = withdrawBasketFees.accumulatedFees.slice(0, numTokens);
|
|
22
|
+
|
|
23
|
+
let formatted: FormattedWithdrawBasketFees = {
|
|
24
|
+
pubkey: withdrawBasketFees.ownAddress!.toBase58(),
|
|
25
|
+
basket: withdrawBasketFees.basket.toBase58(),
|
|
26
|
+
rent_payer: withdrawBasketFees.rentPayer.toBase58(),
|
|
27
|
+
owners: withdrawBasketFees.owners.map(owner => owner.toBase58()),
|
|
28
|
+
owners_weights: withdrawBasketFees.ownersWeights,
|
|
29
|
+
accumulated_tokens: withdrawBasketFees.accumulatedTokens.map(token => token.toBase58()),
|
|
30
|
+
accumulated_fees: withdrawBasketFees.accumulatedFees.map(fee => parseInt(fee.toString())),
|
|
31
|
+
};
|
|
32
|
+
withdrawBasketFees.formatted = formatted;
|
|
33
|
+
return withdrawBasketFees;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function fetchWithdrawBasketFees(
|
|
37
|
+
connection: Connection,
|
|
38
|
+
withdrawBasketFeesAddress: PublicKey,
|
|
39
|
+
): Promise<WithdrawBasketFees> {
|
|
40
|
+
const accountInfo = await connection.getAccountInfo(withdrawBasketFeesAddress);
|
|
41
|
+
if (!accountInfo) throw new Error("WithdrawBasketFees account not found");
|
|
42
|
+
let withdrawBasketFees = WithdrawBasketFeesLayout.decode(accountInfo.data.slice(8));
|
|
43
|
+
withdrawBasketFees.ownAddress = withdrawBasketFeesAddress;
|
|
44
|
+
withdrawBasketFees = addFieldsToWithdrawBasketFees(withdrawBasketFees);
|
|
45
|
+
return withdrawBasketFees;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function fetchWithdrawBasketFeesMultiple(
|
|
49
|
+
connection: Connection,
|
|
50
|
+
withdrawBasketFeesAddresses: PublicKey[],
|
|
51
|
+
): Promise<Map<string, WithdrawBasketFees>> {
|
|
52
|
+
let multipleAccountsInfo = await getMultipleAccountsInfoBatched(connection, withdrawBasketFeesAddresses);
|
|
53
|
+
let withdrawBasketFeesList: WithdrawBasketFees[] = withdrawBasketFeesAddresses.map(address => {
|
|
54
|
+
let ai = multipleAccountsInfo.get(address.toBase58());
|
|
55
|
+
if (!ai) return null;
|
|
56
|
+
let withdrawBasketFees = { ...WithdrawBasketFeesLayout.decode(ai.data.slice(8)), ownAddress: address };
|
|
57
|
+
withdrawBasketFees = addFieldsToWithdrawBasketFees(withdrawBasketFees);
|
|
58
|
+
return withdrawBasketFees;
|
|
59
|
+
}).filter((item): item is WithdrawBasketFees => item !== null);
|
|
60
|
+
|
|
61
|
+
let withdrawBasketFeesMap: Map<string, WithdrawBasketFees> = new Map();
|
|
62
|
+
for (let withdrawBasketFees of withdrawBasketFeesList) {
|
|
63
|
+
withdrawBasketFeesMap.set(withdrawBasketFees.ownAddress!.toBase58(), withdrawBasketFees);
|
|
64
|
+
}
|
|
65
|
+
return withdrawBasketFeesMap;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface WithdrawBasketFeesFilter {
|
|
69
|
+
type: "basket" | "manager" | "creator" | "host" | "symmetry";
|
|
70
|
+
pubkey: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function fetchWithdrawBasketFeesList(
|
|
74
|
+
connection: Connection,
|
|
75
|
+
filter?: WithdrawBasketFeesFilter,
|
|
76
|
+
): Promise<WithdrawBasketFees[]> {
|
|
77
|
+
|
|
78
|
+
const MANAGERS_ARRAY_OFFSET = 8 + 32 + 32;
|
|
79
|
+
const MANAGER_PUBKEY_SIZE = 32;
|
|
80
|
+
|
|
81
|
+
if (filter?.type === "manager") {
|
|
82
|
+
try {
|
|
83
|
+
const managerPubkey = new PublicKey(filter.pubkey);
|
|
84
|
+
const managerBytes = Buffer.from(managerPubkey.toBytes());
|
|
85
|
+
const managerSliceLength = MAX_MANAGERS_PER_BASKET * MANAGER_PUBKEY_SIZE;
|
|
86
|
+
|
|
87
|
+
// Request only manager bytes for each basket (single GPA), then filter locally.
|
|
88
|
+
const managerSlices = await connection.getProgramAccounts(BASKETS_V3_PROGRAM_ID, {
|
|
89
|
+
commitment: "confirmed",
|
|
90
|
+
filters: [{ dataSize: 8 + WithdrawBasketFeesLayout.getSpan() }],
|
|
91
|
+
dataSlice: {
|
|
92
|
+
offset: MANAGERS_ARRAY_OFFSET,
|
|
93
|
+
length: managerSliceLength,
|
|
94
|
+
},
|
|
95
|
+
encoding: 'base64',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const matchedPubkeys: PublicKey[] = [];
|
|
99
|
+
for (const basketAccount of managerSlices) {
|
|
100
|
+
const managersData = basketAccount.account.data;
|
|
101
|
+
for (let i = 0; i < MAX_MANAGERS_PER_BASKET; i++) {
|
|
102
|
+
const start = i * MANAGER_PUBKEY_SIZE;
|
|
103
|
+
const end = start + MANAGER_PUBKEY_SIZE;
|
|
104
|
+
if (managersData.subarray(start, end).equals(managerBytes)) {
|
|
105
|
+
matchedPubkeys.push(basketAccount.pubkey);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (matchedPubkeys.length === 0) return [];
|
|
112
|
+
const matchedAccountsInfo = await getMultipleAccountsInfoBatched(connection, matchedPubkeys);
|
|
113
|
+
|
|
114
|
+
let withdrawBasketFeesList: WithdrawBasketFees[] = [];
|
|
115
|
+
for (let pubkey of matchedPubkeys) {
|
|
116
|
+
const accountInfo = matchedAccountsInfo.get(pubkey.toBase58());
|
|
117
|
+
if (!accountInfo) continue;
|
|
118
|
+
let withdrawBasketFees = WithdrawBasketFeesLayout.decode(accountInfo.data.slice(8));
|
|
119
|
+
withdrawBasketFees.ownAddress = pubkey;
|
|
120
|
+
withdrawBasketFees = addFieldsToWithdrawBasketFees(withdrawBasketFees);
|
|
121
|
+
withdrawBasketFeesList.push(withdrawBasketFees);
|
|
122
|
+
}
|
|
123
|
+
return withdrawBasketFeesList;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// Fallback for RPC providers that might not support/serve this path reliably.
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let filters: GetProgramAccountsFilter[] = [{ dataSize: 8 + WithdrawBasketFeesLayout.getSpan() }];
|
|
130
|
+
|
|
131
|
+
if (filter?.type === "creator" || filter?.type === "host" || filter?.type === "symmetry")
|
|
132
|
+
filters.push({
|
|
133
|
+
memcmp: {
|
|
134
|
+
offset: 8 + 32 + 32,
|
|
135
|
+
bytes: filter.pubkey
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (filter?.type === "basket")
|
|
140
|
+
filters.push({
|
|
141
|
+
memcmp: {
|
|
142
|
+
offset: 8,
|
|
143
|
+
bytes: filter.pubkey
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
let results = await connection.getProgramAccounts(BASKETS_V3_PROGRAM_ID, {
|
|
148
|
+
commitment: "confirmed",
|
|
149
|
+
filters: filters,
|
|
150
|
+
encoding: 'base64'
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
let withdrawBasketFeesList = results.map(account => {
|
|
154
|
+
let withdrawBasketFees = WithdrawBasketFeesLayout.decode(account.account.data.slice(8));
|
|
155
|
+
withdrawBasketFees.ownAddress = account.pubkey;
|
|
156
|
+
withdrawBasketFees = addFieldsToWithdrawBasketFees(withdrawBasketFees);
|
|
157
|
+
return withdrawBasketFees;
|
|
158
|
+
});
|
|
159
|
+
return withdrawBasketFeesList;
|
|
160
|
+
}
|
package/src/txUtils.ts
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { AccountInfo, SystemProgram, Transaction } from '@solana/web3.js';
|
|
2
|
+
import {
|
|
3
|
+
AddressLookupTableAccount, ComputeBudgetProgram, Connection, Keypair, PublicKey, Signer,
|
|
4
|
+
TransactionInstruction, TransactionMessage, VersionedTransaction
|
|
5
|
+
} from '@solana/web3.js';
|
|
6
|
+
import { getAta } from './instructions/pda';
|
|
7
|
+
import { MINTS } from './constants';
|
|
8
|
+
import { createAssociatedTokenAccountIdempotentInstruction, createSyncNativeInstruction } from '@solana/spl-token';
|
|
9
|
+
|
|
10
|
+
type TransactionSignature = string;
|
|
11
|
+
|
|
12
|
+
export interface Wallet {
|
|
13
|
+
signTransaction<T extends Transaction | VersionedTransaction>(tx: T): Promise<T>;
|
|
14
|
+
signAllTransactions<T extends Transaction | VersionedTransaction>(txs: T[]): Promise<T[]>;
|
|
15
|
+
publicKey: PublicKey;
|
|
16
|
+
/** Keypair of the configured payer (Node only) */
|
|
17
|
+
payer?: Keypair;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function wrapWsolIxs(
|
|
21
|
+
connection: Connection,
|
|
22
|
+
wallet: PublicKey,
|
|
23
|
+
amount: number,
|
|
24
|
+
): Promise<TransactionInstruction[]> {
|
|
25
|
+
if (amount == 0) return [];
|
|
26
|
+
let wsolIxs: TransactionInstruction[] = [];
|
|
27
|
+
let wsolAta = getAta(wallet, MINTS["mainnet"].WSOL);
|
|
28
|
+
let wsolBalance = 0;
|
|
29
|
+
let accountExists = false;
|
|
30
|
+
try {
|
|
31
|
+
let info = await connection.getTokenAccountBalance(wsolAta);
|
|
32
|
+
wsolBalance = parseInt(info.value.amount);
|
|
33
|
+
accountExists = wsolBalance >= 0;
|
|
34
|
+
} catch {}
|
|
35
|
+
if (!accountExists)
|
|
36
|
+
wsolIxs.push(
|
|
37
|
+
createAssociatedTokenAccountIdempotentInstruction(
|
|
38
|
+
wallet,
|
|
39
|
+
wsolAta,
|
|
40
|
+
wallet,
|
|
41
|
+
MINTS["mainnet"].WSOL,
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
if (wsolBalance < amount)
|
|
45
|
+
wsolIxs.push(
|
|
46
|
+
SystemProgram.transfer({
|
|
47
|
+
fromPubkey: wallet,
|
|
48
|
+
toPubkey: wsolAta,
|
|
49
|
+
lamports: amount - wsolBalance,
|
|
50
|
+
}),
|
|
51
|
+
createSyncNativeInstruction(wsolAta),
|
|
52
|
+
);
|
|
53
|
+
return wsolIxs;
|
|
54
|
+
}
|
|
55
|
+
export interface TxData {
|
|
56
|
+
payer: PublicKey,
|
|
57
|
+
instructions: TransactionInstruction[],
|
|
58
|
+
lookupTables: PublicKey[],
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface TxBatchData {
|
|
62
|
+
batches: TxData[][],
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface VersionedTxs {
|
|
66
|
+
blockhash: string;
|
|
67
|
+
lastValidBlockHeight: number;
|
|
68
|
+
batches: VersionedTransaction[][];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface TxPayloadIxAccountMeta {
|
|
72
|
+
pubkey: string,
|
|
73
|
+
is_signer: boolean,
|
|
74
|
+
is_writable: boolean,
|
|
75
|
+
}
|
|
76
|
+
export interface TxPayloadIx {
|
|
77
|
+
program_id: string,
|
|
78
|
+
accounts: TxPayloadIxAccountMeta[],
|
|
79
|
+
data: string,
|
|
80
|
+
}
|
|
81
|
+
export interface TxPayload {
|
|
82
|
+
tx_b64: string,
|
|
83
|
+
message_version: "0" | "legacy",
|
|
84
|
+
recent_blockhash: string,
|
|
85
|
+
payer: string,
|
|
86
|
+
lookup_tables: string[],
|
|
87
|
+
instructions: TxPayloadIx[],
|
|
88
|
+
}
|
|
89
|
+
export interface TxPayloadBatch {
|
|
90
|
+
transactions: TxPayload[];
|
|
91
|
+
}
|
|
92
|
+
export interface TxPayloadBatchSequence {
|
|
93
|
+
batches: TxPayloadBatch[];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface BasketCreationTx extends TxPayloadBatchSequence {
|
|
97
|
+
mint: string;
|
|
98
|
+
basket: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function prepareTxPayloadBatchSequence(
|
|
102
|
+
txBatchData: TxBatchData,
|
|
103
|
+
versionedTxs?: VersionedTxs,
|
|
104
|
+
): TxPayloadBatchSequence {
|
|
105
|
+
return {
|
|
106
|
+
batches: txBatchData.batches.map((txDatas, i) => {
|
|
107
|
+
return {
|
|
108
|
+
transactions: txDatas.map((txData, j) => {
|
|
109
|
+
return {
|
|
110
|
+
tx_b64: versionedTxs ? Buffer.from(versionedTxs.batches[i][j].serialize()).toString('base64') : "",
|
|
111
|
+
message_version: versionedTxs ? (versionedTxs.batches[i][j].message.version == 0 ? "0" : "legacy") : "0",
|
|
112
|
+
recent_blockhash: versionedTxs ? versionedTxs.batches[i][j].message.recentBlockhash : "",
|
|
113
|
+
payer: txData.payer.toBase58(),
|
|
114
|
+
lookup_tables: txData.lookupTables.map(lookupTable => lookupTable.toBase58()),
|
|
115
|
+
instructions: txData.instructions.map(
|
|
116
|
+
instruction => ({
|
|
117
|
+
program_id: instruction.programId.toBase58(),
|
|
118
|
+
accounts: instruction.keys.map(account => ({
|
|
119
|
+
pubkey: account.pubkey.toBase58(),
|
|
120
|
+
is_signer: account.isSigner,
|
|
121
|
+
is_writable: account.isWritable,
|
|
122
|
+
})),
|
|
123
|
+
data: instruction.data.toString('base64'),
|
|
124
|
+
})
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function delay(ms: number): Promise<void> {
|
|
134
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function getMultipleAccountsInfoBatched(
|
|
138
|
+
connection: Connection,
|
|
139
|
+
pubkeys: PublicKey[],
|
|
140
|
+
): Promise<Map<string, AccountInfo<Buffer> | null>> {
|
|
141
|
+
let uniquePubkeys: PublicKey[] = [];
|
|
142
|
+
for (const pubkey of pubkeys) {
|
|
143
|
+
if (uniquePubkeys.find(x => x.equals(pubkey)) === undefined) {
|
|
144
|
+
uniquePubkeys.push(pubkey);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
let chunkSize = 100;
|
|
148
|
+
let promises: Promise<(AccountInfo<Buffer> | null)[]>[] = [];
|
|
149
|
+
for (let i = 0; i < uniquePubkeys.length; i += chunkSize) {
|
|
150
|
+
let chunk = uniquePubkeys.slice(i, i + chunkSize);
|
|
151
|
+
promises.push(connection.getMultipleAccountsInfo(chunk, "confirmed"));
|
|
152
|
+
}
|
|
153
|
+
let resolve = await Promise.all(promises);
|
|
154
|
+
let infos: (AccountInfo<Buffer> | null)[] = [];
|
|
155
|
+
resolve.forEach(result => {
|
|
156
|
+
infos.push(...result);
|
|
157
|
+
});
|
|
158
|
+
let allResults: Map<string, AccountInfo<Buffer> | null> = new Map();
|
|
159
|
+
infos.forEach((info, idx) => {
|
|
160
|
+
allResults.set(uniquePubkeys[idx].toBase58(), info);
|
|
161
|
+
});
|
|
162
|
+
return allResults;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function getAddressLookupTableAccounts(
|
|
166
|
+
connection: Connection,
|
|
167
|
+
keys: PublicKey[]
|
|
168
|
+
): Promise<Map<string, AddressLookupTableAccount>> {
|
|
169
|
+
const addressLookupTableAccountInfos = await getMultipleAccountsInfoBatched(connection, keys);
|
|
170
|
+
let addressLookupTableAccounts: Map<string, AddressLookupTableAccount> = new Map();
|
|
171
|
+
for (const key of keys) {
|
|
172
|
+
const accountInfo = addressLookupTableAccountInfos.get(key.toBase58());
|
|
173
|
+
if (!key.equals(PublicKey.default) && accountInfo) {
|
|
174
|
+
const addressLookupTableAccount = new AddressLookupTableAccount({
|
|
175
|
+
key: new PublicKey(key),
|
|
176
|
+
state: AddressLookupTableAccount.deserialize(accountInfo.data),
|
|
177
|
+
});
|
|
178
|
+
addressLookupTableAccounts.set(key.toBase58(), addressLookupTableAccount);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return addressLookupTableAccounts;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export async function getMultipleAddressLookupTableAccounts(
|
|
185
|
+
connection: Connection,
|
|
186
|
+
batches: PublicKey[][][],
|
|
187
|
+
): Promise<AddressLookupTableAccount[][][]> {
|
|
188
|
+
let allLuts: PublicKey[] = [];
|
|
189
|
+
batches.forEach(batch => batch.forEach(luts => luts.forEach(lut => allLuts.push(lut))));
|
|
190
|
+
let addressLookupTableAccounts = await getAddressLookupTableAccounts(connection, allLuts);
|
|
191
|
+
let result: AddressLookupTableAccount[][][] = [];
|
|
192
|
+
for (const batch of batches) {
|
|
193
|
+
let batchLuts: AddressLookupTableAccount[][] = [];
|
|
194
|
+
for (const luts of batch) {
|
|
195
|
+
let batchLut: AddressLookupTableAccount[] = [];
|
|
196
|
+
for (const lut of luts) {
|
|
197
|
+
batchLut.push(addressLookupTableAccounts.get(lut.toBase58())!);
|
|
198
|
+
}
|
|
199
|
+
batchLuts.push(batchLut);
|
|
200
|
+
}
|
|
201
|
+
result.push(batchLuts);
|
|
202
|
+
}
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function compileVersionedTransaction(
|
|
207
|
+
blockhash: string,
|
|
208
|
+
addressLookupTableAccounts: AddressLookupTableAccount[],
|
|
209
|
+
payerPubkey: PublicKey,
|
|
210
|
+
ixs: TransactionInstruction[],
|
|
211
|
+
): VersionedTransaction {
|
|
212
|
+
const txMessage = new TransactionMessage({
|
|
213
|
+
payerKey: payerPubkey,
|
|
214
|
+
recentBlockhash: blockhash,
|
|
215
|
+
instructions: ixs,
|
|
216
|
+
});
|
|
217
|
+
let versionedTx = new VersionedTransaction(
|
|
218
|
+
txMessage.compileToV0Message(addressLookupTableAccounts)
|
|
219
|
+
);
|
|
220
|
+
try {
|
|
221
|
+
let tt = versionedTx.serialize().length;
|
|
222
|
+
if (tt > 1232) {
|
|
223
|
+
throw new Error("Transaction too large");
|
|
224
|
+
}
|
|
225
|
+
} catch (e: any) {
|
|
226
|
+
throw new Error(e.message);
|
|
227
|
+
}
|
|
228
|
+
return versionedTx;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export async function sendVersionedTransaction(
|
|
232
|
+
connection: Connection,
|
|
233
|
+
tx: VersionedTransaction,
|
|
234
|
+
blockhash: string,
|
|
235
|
+
lastValidBlockHeight: number,
|
|
236
|
+
simulateTransactions: boolean,
|
|
237
|
+
): Promise<TransactionSignature> {
|
|
238
|
+
const serializedTx = tx.serialize();
|
|
239
|
+
let txId: TransactionSignature;
|
|
240
|
+
if (simulateTransactions) {
|
|
241
|
+
txId = await connection.sendRawTransaction(
|
|
242
|
+
serializedTx,
|
|
243
|
+
{ preflightCommitment: "confirmed", maxRetries: 0 }
|
|
244
|
+
).catch(e => {
|
|
245
|
+
console.log(e.message);
|
|
246
|
+
throw new Error("Simulation failed");
|
|
247
|
+
});
|
|
248
|
+
for (let i = 0; i < 4; i++) {
|
|
249
|
+
await delay(3000).then(() =>
|
|
250
|
+
connection.sendRawTransaction(
|
|
251
|
+
serializedTx,
|
|
252
|
+
{preflightCommitment: "confirmed", maxRetries: 0}
|
|
253
|
+
).catch(() => {})
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
console.log("Simulation txId:", txId);
|
|
257
|
+
} else {
|
|
258
|
+
txId = await connection.sendRawTransaction(
|
|
259
|
+
serializedTx,
|
|
260
|
+
{ skipPreflight: true, maxRetries: 0 }
|
|
261
|
+
);
|
|
262
|
+
for (let i = 0; i < 4; i++) {
|
|
263
|
+
await delay(3000).then(() =>
|
|
264
|
+
connection.sendRawTransaction(
|
|
265
|
+
serializedTx,
|
|
266
|
+
{preflightCommitment: "confirmed", maxRetries: 0}
|
|
267
|
+
).catch(() => {})
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
console.log("Sending tx:", txId);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
let confirmation = null;
|
|
274
|
+
let result = null;
|
|
275
|
+
|
|
276
|
+
connection.confirmTransaction({
|
|
277
|
+
blockhash,
|
|
278
|
+
lastValidBlockHeight,
|
|
279
|
+
signature: txId,
|
|
280
|
+
}, "confirmed").catch(() => null).then((res) => confirmation = res);
|
|
281
|
+
|
|
282
|
+
let iterations = 10;
|
|
283
|
+
while (confirmation === null && result === null && iterations > 0) {
|
|
284
|
+
await delay(1000);
|
|
285
|
+
result = await connection.getTransaction(txId, {
|
|
286
|
+
commitment: "confirmed",
|
|
287
|
+
maxSupportedTransactionVersion: 0,
|
|
288
|
+
}).catch(() => null);
|
|
289
|
+
if (result && result.meta && result.meta?.err) {
|
|
290
|
+
console.log(result.meta.err);
|
|
291
|
+
throw new Error(txId);
|
|
292
|
+
}
|
|
293
|
+
iterations--;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (result) return txId;
|
|
297
|
+
|
|
298
|
+
//@ts-ignore
|
|
299
|
+
if (!confirmation || confirmation.value.err) {
|
|
300
|
+
//@ts-ignore
|
|
301
|
+
console.log(confirmation?.value.err);
|
|
302
|
+
throw new Error(txId);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
return txId;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
export async function prepareVersionedTxs(
|
|
309
|
+
connection: Connection,
|
|
310
|
+
txBatchData: TxBatchData,
|
|
311
|
+
): Promise<VersionedTxs> {
|
|
312
|
+
const { batches } = txBatchData;
|
|
313
|
+
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash("confirmed");
|
|
314
|
+
let multipleLookupTableAddresses: PublicKey[][][] = batches.map(batch => batch.map(txData => txData.lookupTables));
|
|
315
|
+
const multipleAddressLookupTableAccounts: AddressLookupTableAccount[][][] = await getMultipleAddressLookupTableAccounts(
|
|
316
|
+
connection,
|
|
317
|
+
multipleLookupTableAddresses
|
|
318
|
+
);
|
|
319
|
+
const batchTxs = batches.map((batch, batchIndex) => {
|
|
320
|
+
const txs = batch.map((txData, txIndex) => {
|
|
321
|
+
let tx = null;
|
|
322
|
+
try {
|
|
323
|
+
tx = compileVersionedTransaction(
|
|
324
|
+
blockhash,
|
|
325
|
+
multipleAddressLookupTableAccounts[batchIndex][txIndex],
|
|
326
|
+
txData.payer,
|
|
327
|
+
txData.instructions,
|
|
328
|
+
);
|
|
329
|
+
} catch (e: any) {
|
|
330
|
+
console.log("Error signing tx:", e.message);
|
|
331
|
+
}
|
|
332
|
+
return tx;
|
|
333
|
+
}).filter(tx => tx !== null);
|
|
334
|
+
return txs;
|
|
335
|
+
});
|
|
336
|
+
return {
|
|
337
|
+
blockhash,
|
|
338
|
+
lastValidBlockHeight,
|
|
339
|
+
batches: batchTxs,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
export async function sendVersionedTxs(
|
|
344
|
+
connection: Connection,
|
|
345
|
+
versionedTxs: VersionedTxs,
|
|
346
|
+
simulateTransactions: boolean = false,
|
|
347
|
+
): Promise<TransactionSignature[][]> {
|
|
348
|
+
const { batches, blockhash, lastValidBlockHeight } = versionedTxs;
|
|
349
|
+
let batchTxIds: TransactionSignature[][] = [];
|
|
350
|
+
|
|
351
|
+
for (const batch of batches) {
|
|
352
|
+
let txIds = await Promise.all(
|
|
353
|
+
batch.map(tx =>
|
|
354
|
+
sendVersionedTransaction(
|
|
355
|
+
connection,
|
|
356
|
+
tx,
|
|
357
|
+
blockhash,
|
|
358
|
+
lastValidBlockHeight,
|
|
359
|
+
simulateTransactions
|
|
360
|
+
).catch(e => {console.log("Error sending tx:", e.message); return "Error"})
|
|
361
|
+
)
|
|
362
|
+
);
|
|
363
|
+
batchTxIds.push(txIds);
|
|
364
|
+
}
|
|
365
|
+
return batchTxIds;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
export async function signVersionedTxs(
|
|
370
|
+
wallet: Wallet,
|
|
371
|
+
versionedTxs: VersionedTxs,
|
|
372
|
+
): Promise<VersionedTxs> {
|
|
373
|
+
let txs: VersionedTransaction[] = [];
|
|
374
|
+
for (const batch of versionedTxs.batches)
|
|
375
|
+
for (const tx of batch)
|
|
376
|
+
txs.push(tx);
|
|
377
|
+
txs = await wallet.signAllTransactions(txs);
|
|
378
|
+
let pointer = 0;
|
|
379
|
+
for (let batchIndex = 0; batchIndex < versionedTxs.batches.length; batchIndex++)
|
|
380
|
+
for (let txIndex = 0; txIndex < versionedTxs.batches[batchIndex].length; txIndex++) {
|
|
381
|
+
versionedTxs.batches[batchIndex][txIndex] = txs[pointer];
|
|
382
|
+
pointer++;
|
|
383
|
+
}
|
|
384
|
+
return versionedTxs;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export async function signTxPayloadBatchSequence(
|
|
388
|
+
wallet: Wallet,
|
|
389
|
+
txPayloadBatchSequence: TxPayloadBatchSequence,
|
|
390
|
+
): Promise<TxPayloadBatchSequence> {
|
|
391
|
+
let txs: VersionedTransaction[] = [];
|
|
392
|
+
for (const batch of txPayloadBatchSequence.batches)
|
|
393
|
+
for (const tx of batch.transactions)
|
|
394
|
+
txs.push(
|
|
395
|
+
VersionedTransaction.deserialize(Buffer.from(tx.tx_b64, 'base64'))
|
|
396
|
+
);
|
|
397
|
+
txs = await wallet.signAllTransactions(txs);
|
|
398
|
+
let pointer = 0;
|
|
399
|
+
for (let batchIndex = 0; batchIndex < txPayloadBatchSequence.batches.length; batchIndex++)
|
|
400
|
+
for (let txIndex = 0; txIndex < txPayloadBatchSequence.batches[batchIndex].transactions.length; txIndex++) {
|
|
401
|
+
txPayloadBatchSequence.batches[batchIndex].transactions[txIndex].tx_b64 = Buffer.from(txs[pointer].serialize()).toString('base64');
|
|
402
|
+
pointer++;
|
|
403
|
+
}
|
|
404
|
+
return txPayloadBatchSequence;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
export async function sendTxPayloadBatchSequence(
|
|
408
|
+
connection: Connection,
|
|
409
|
+
txPayloadBatchSequence: TxPayloadBatchSequence,
|
|
410
|
+
simulateTransactions: boolean = false,
|
|
411
|
+
): Promise<TransactionSignature[][]> {
|
|
412
|
+
let txs: VersionedTransaction[][] = [];
|
|
413
|
+
for (const batch of txPayloadBatchSequence.batches) {
|
|
414
|
+
let batchTxs: VersionedTransaction[] = [];
|
|
415
|
+
for (const tx of batch.transactions)
|
|
416
|
+
batchTxs.push(VersionedTransaction.deserialize(Buffer.from(tx.tx_b64, 'base64')));
|
|
417
|
+
txs.push(batchTxs);
|
|
418
|
+
}
|
|
419
|
+
return await sendVersionedTxs(connection, {
|
|
420
|
+
blockhash: txPayloadBatchSequence.batches[0].transactions[0]?.recent_blockhash ?? "",
|
|
421
|
+
lastValidBlockHeight: 0,
|
|
422
|
+
batches: txs,
|
|
423
|
+
}, simulateTransactions);
|
|
424
|
+
}
|