Skip to content

Commit 510803c

Browse files
reimburse: ensure ata, ensure not transferred in last 10 txs, transfer
Signed-off-by: microwavedcola1 <[email protected]>
1 parent eadbedc commit 510803c

File tree

1 file changed

+221
-0
lines changed

1 file changed

+221
-0
lines changed

src/scripts/reimburse.ts

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
import {
2+
TOKEN_PROGRAM_ID,
3+
ASSOCIATED_TOKEN_PROGRAM_ID,
4+
Token,
5+
} from '@solana/spl-token';
6+
import {
7+
Commitment,
8+
Connection,
9+
Keypair,
10+
PublicKey,
11+
sendAndConfirmTransaction,
12+
SystemProgram,
13+
Transaction,
14+
TransactionInstruction,
15+
} from '@solana/web3.js';
16+
import BN from 'bn.js';
17+
import { MangoClient } from '../client';
18+
import { Cluster, Config } from '../config';
19+
import fs from 'fs';
20+
21+
const PAYER_KEYPAIR = process.env.MB_PAYER_KEYPAIR;
22+
const PAYER = Keypair.fromSecretKey(
23+
Buffer.from(JSON.parse(fs.readFileSync(PAYER_KEYPAIR!, 'utf-8'))),
24+
);
25+
const SOURCE = PAYER.publicKey;
26+
27+
const config = Config.ids();
28+
const cluster = 'mainnet' as Cluster;
29+
const connection = new Connection(
30+
config.cluster_urls[cluster],
31+
'confirmed' as Commitment,
32+
);
33+
34+
const groupName = 'mainnet.1';
35+
const groupIds = config.getGroup(cluster, groupName);
36+
if (!groupIds) {
37+
throw new Error(`Group ${groupName} not found`);
38+
}
39+
40+
const mangoProgramId = groupIds.mangoProgramId;
41+
const mangoGroupKey = groupIds.publicKey;
42+
const client = new MangoClient(connection, mangoProgramId);
43+
44+
export async function createAssociatedTokenAccountIdempotentInstruction(
45+
payer: PublicKey,
46+
owner: PublicKey,
47+
mint: PublicKey,
48+
ata: PublicKey,
49+
): Promise<TransactionInstruction> {
50+
return new TransactionInstruction({
51+
keys: [
52+
{ pubkey: payer, isSigner: true, isWritable: true },
53+
{ pubkey: ata, isSigner: false, isWritable: true },
54+
{ pubkey: owner, isSigner: false, isWritable: false },
55+
{ pubkey: mint, isSigner: false, isWritable: false },
56+
{
57+
pubkey: SystemProgram.programId,
58+
isSigner: false,
59+
isWritable: false,
60+
},
61+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
62+
],
63+
programId: ASSOCIATED_TOKEN_PROGRAM_ID,
64+
data: Buffer.from([0x1]),
65+
});
66+
}
67+
68+
async function tokenTransfer(
69+
mangoAccountOwnerPk: PublicKey,
70+
mint: PublicKey,
71+
nativeTokenAmountToReimburse: BN,
72+
) {
73+
const sourceAta = await Token.getAssociatedTokenAddress(
74+
ASSOCIATED_TOKEN_PROGRAM_ID,
75+
TOKEN_PROGRAM_ID,
76+
mint,
77+
SOURCE,
78+
);
79+
const destinationAta = await Token.getAssociatedTokenAddress(
80+
ASSOCIATED_TOKEN_PROGRAM_ID,
81+
TOKEN_PROGRAM_ID,
82+
mint,
83+
mangoAccountOwnerPk,
84+
);
85+
86+
// Verify that this tx has not happend in last 10 txs for the destinationAta
87+
const sigs = await connection.getConfirmedSignaturesForAddress2(
88+
destinationAta,
89+
);
90+
for (const sig of sigs.slice(0, 10)) {
91+
const meta = await connection.getParsedTransaction(
92+
sig.signature,
93+
'confirmed',
94+
);
95+
96+
if (
97+
meta?.transaction.message.instructions.length === 2 &&
98+
(meta?.transaction.message.instructions[1] as any).parsed &&
99+
(meta?.transaction.message.instructions[1] as any).parsed.type &&
100+
(meta?.transaction.message.instructions[1] as any).parsed.type ===
101+
'transfer'
102+
) {
103+
const res = (meta?.transaction.message.instructions[1] as any).parsed
104+
.info as {
105+
amount: string;
106+
destination: string;
107+
source: string;
108+
};
109+
if (
110+
res.amount === nativeTokenAmountToReimburse.toString() &&
111+
sourceAta.toBase58() === res.source &&
112+
destinationAta.toBase58() === res.destination
113+
) {
114+
console.log(` - already transferred`);
115+
return;
116+
}
117+
}
118+
}
119+
120+
// Build tx
121+
const tx = new Transaction();
122+
tx.add(
123+
await createAssociatedTokenAccountIdempotentInstruction(
124+
PAYER.publicKey,
125+
mangoAccountOwnerPk,
126+
mint,
127+
destinationAta,
128+
),
129+
);
130+
tx.add(
131+
await Token.createTransferInstruction(
132+
TOKEN_PROGRAM_ID,
133+
sourceAta,
134+
await Token.getAssociatedTokenAddress(
135+
ASSOCIATED_TOKEN_PROGRAM_ID,
136+
TOKEN_PROGRAM_ID,
137+
mint,
138+
mangoAccountOwnerPk,
139+
),
140+
PAYER.publicKey,
141+
[PAYER],
142+
nativeTokenAmountToReimburse.toNumber(), // throws `Note: Blob.encode[amount] requires (length 8) Buffer as src` when BN is used
143+
),
144+
);
145+
146+
// Send and confirm
147+
const sig = await sendAndConfirmTransaction(connection, tx, [PAYER], {
148+
skipPreflight: true,
149+
});
150+
console.log(` - transferrd, sig https://explorer.solana.com/tx/${sig}`);
151+
}
152+
153+
async function reimburseUser(
154+
mangoAccountOwnerPk: PublicKey,
155+
nativeTokenAmountsToReimburse: BN[],
156+
): Promise<void> {
157+
const group = await client.getMangoGroup(mangoGroupKey);
158+
const allTokens = 16;
159+
160+
// verify input from csv
161+
if (nativeTokenAmountsToReimburse.length !== allTokens) {
162+
throw new Error(
163+
`Mango V3 has ${allTokens} tokens, expected ${allTokens} token amounts to reimburse!`,
164+
);
165+
}
166+
167+
group.tokens.map(async (token, tokenIndex) => {
168+
const tokenConfig = groupIds?.tokens.find((tokenConfig) =>
169+
token.mint.equals(tokenConfig.mintKey),
170+
);
171+
172+
// token slot empty
173+
if (!tokenConfig) {
174+
return;
175+
}
176+
177+
// token is deactivated
178+
if (token.oracleInactive) {
179+
return;
180+
}
181+
182+
// skip if no reimbursements for mint
183+
const nativeTokenAmountToReimburse =
184+
nativeTokenAmountsToReimburse[tokenIndex];
185+
if (nativeTokenAmountToReimburse.eq(new BN(0))) {
186+
return;
187+
}
188+
189+
console.log(
190+
`Transferring ${nativeTokenAmountToReimburse} native ${tokenConfig.symbol} (mint - ${tokenConfig.mintKey}) to ${mangoAccountOwnerPk}`,
191+
);
192+
193+
return await tokenTransfer(
194+
mangoAccountOwnerPk,
195+
token.mint,
196+
nativeTokenAmountToReimburse,
197+
);
198+
});
199+
}
200+
201+
// example
202+
reimburseUser(new PublicKey('9Ut1gZJnd5D7EjPXm2DygYWZkZGpt5QSMEYAaVx2hur4'), [
203+
new BN(0),
204+
new BN(0),
205+
new BN(0),
206+
new BN(0),
207+
new BN(0),
208+
new BN(0),
209+
new BN(0),
210+
new BN(0),
211+
new BN(0),
212+
new BN(0),
213+
new BN(0),
214+
new BN(0),
215+
new BN(0),
216+
new BN(0),
217+
new BN(0),
218+
new BN(1), // USDC
219+
]);
220+
221+
// TODO read csv, grab mango accounts owner, grab token deposits per token, call reimburseUser

0 commit comments

Comments
 (0)