Skip to content

Commit 0d4d302

Browse files
author
Nasus
committed
init
0 parents  commit 0d4d302

File tree

6 files changed

+355
-0
lines changed

6 files changed

+355
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dist/
2+
node_modules/

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# SOLANA USDC 批量转账
2+
## 安装依赖
3+
```bash
4+
npm i --save
5+
```
6+
## 导入私钥
7+
将私钥粘贴至`main.ts`21行 `privateKey`
8+
## 导入收款人列表
9+
将收款人钱包地址及接收数量粘贴至`main.ts`28行 `addresses`对象内
10+
## 运行
11+
```bash
12+
tsc
13+
npm run dev
14+
```

package.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "solanabatchtransfer",
3+
"version": "1.0.0",
4+
"description": "",
5+
"main": "index.js",
6+
"scripts": {
7+
"test": "echo \"Error: no test specified\" && exit 1",
8+
"debug": "node --inspect-brk dist/main.js",
9+
"dev": "node dist/main.js"
10+
11+
},
12+
"author": "Nasus",
13+
"license": "ISC",
14+
"dependencies": {
15+
"@project-serum/borsh": "^0.2.2",
16+
"@solana/web3.js": "^1.20.0",
17+
"bs58": "^5.0.0",
18+
"buffer-layout": "^1.2.1"
19+
},
20+
"devDependencies": {
21+
"@types/node": "^15.12.4",
22+
"ts-node": "^10.0.0",
23+
"typescript": "^4.3.4"
24+
}
25+
}

src/main.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import {
2+
Connection,
3+
PublicKey,
4+
Transaction,
5+
Keypair,
6+
sendAndConfirmTransaction
7+
} from "@solana/web3.js";
8+
import {
9+
transferToken,
10+
findAssociatedTokenAddress,
11+
createAssociatedTokenAccount,
12+
TOKEN_ACCOUNT_LAYOUT,
13+
} from "./utils";
14+
import * as base58 from 'bs58';
15+
const connection = new Connection(`https://solana-api.projectserum.com`, { confirmTransactionInitialTimeout: 2 * 60 * 1000 });
16+
17+
const USDC = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
18+
/**
19+
* 私钥
20+
*/
21+
const privateKey = ``;
22+
const WALLET = Keypair.fromSecretKey(base58.decode(privateKey), { skipValidation: false });
23+
const OWNER = WALLET.publicKey;
24+
const secreKey = WALLET.secretKey;
25+
console.log(`私钥还原: ${base58.encode(secreKey)}`);
26+
console.log(`公钥: ${OWNER.toBase58()}`);
27+
28+
const addresses: { [addr: string]: number } = {
29+
"address": 1,
30+
"3iVeyGyBAfxyrDBApHPGmPksDAEMNqKvmF4Dd5wWjFwX": 1,
31+
// "4BQcjumDTD9iyznDT7RNfD1ZwcpKdepRLa7uMooSwpyi": 2,
32+
};
33+
34+
async function main() {
35+
for (const [addr, amount] of Object.entries(addresses)) {
36+
console.log(`${addr}: ${amount}`);
37+
38+
let destinationAddress: PublicKey;
39+
let destinationOwner: PublicKey;
40+
41+
let address = null;
42+
try {
43+
address = new PublicKey(addr);
44+
} catch (error) {
45+
console.error(error);
46+
}
47+
if (!address) {
48+
continue;
49+
}
50+
const addressInfo = await connection.getAccountInfo(address);
51+
52+
if (!addressInfo || addressInfo.data.length === 0) {
53+
destinationOwner = address;
54+
destinationAddress = await findAssociatedTokenAddress(
55+
destinationOwner,
56+
USDC
57+
);
58+
} else if (addressInfo.data.length === TOKEN_ACCOUNT_LAYOUT.span) {
59+
const { mint, owner } = TOKEN_ACCOUNT_LAYOUT.decode(addressInfo.data);
60+
61+
if (!USDC.equals(mint)) {
62+
throw new Error(`invalid address: ${addr} is not USDC token account`);
63+
}
64+
65+
destinationAddress = address;
66+
destinationOwner = owner;
67+
} else {
68+
throw new Error(`invalid address: ${addr}`);
69+
}
70+
71+
const recentBlockhash = await connection.getLatestBlockhash();
72+
73+
const transaction = new Transaction({
74+
recentBlockhash: recentBlockhash.blockhash,
75+
});
76+
77+
const tokenAccountInfo = await connection.getAccountInfo(
78+
destinationAddress
79+
);
80+
if (!tokenAccountInfo) {
81+
transaction.add(
82+
await createAssociatedTokenAccount(OWNER, USDC, destinationOwner)
83+
);
84+
}
85+
86+
const source = await findAssociatedTokenAddress(OWNER, USDC);
87+
transaction.add(
88+
transferToken({
89+
source,
90+
dest: destinationAddress,
91+
amount: amount * 10 ** 6,
92+
owner: OWNER,
93+
})
94+
);
95+
transaction.sign(WALLET);
96+
let signature = await sendAndConfirmTransaction(
97+
connection,
98+
transaction,
99+
[WALLET],
100+
);
101+
// const txid = await connection.sendTransaction(transaction, [WALLET], {
102+
// skipPreflight: false,
103+
// });
104+
console.log(`${addr}, ${amount} USDC, txid: https://solscan.io/tx/${signature}`);
105+
}
106+
}
107+
108+
main();

src/utils.ts

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { PublicKey, PublicKeyInitData, TransactionInstruction } from "@solana/web3.js";
2+
import { nu64 } from "buffer-layout";
3+
import { publicKey, struct, u32, u64, u8 } from "@project-serum/borsh";
4+
5+
export const SYSTEM_PROGRAM_ID = new PublicKey(
6+
"11111111111111111111111111111111"
7+
);
8+
export const TOKEN_PROGRAM_ID = new PublicKey(
9+
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
10+
);
11+
export const MEMO_PROGRAM_ID = new PublicKey(
12+
"Memo1UhkJRfHyvLMcVucJwxXeuD728EqVDDwQDxFMNo"
13+
);
14+
export const RENT_PROGRAM_ID = new PublicKey(
15+
"SysvarRent111111111111111111111111111111111"
16+
);
17+
export const CLOCK_PROGRAM_ID = new PublicKey(
18+
"SysvarC1ock11111111111111111111111111111111"
19+
);
20+
export const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey(
21+
"ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
22+
);
23+
24+
export const TOKEN_ACCOUNT_LAYOUT = struct([
25+
publicKey("mint"),
26+
publicKey("owner"),
27+
u64("amount"),
28+
u32("delegateOption"),
29+
publicKey("delegate"),
30+
u8("state"),
31+
u32("isNativeOption"),
32+
u64("isNative"),
33+
u64("delegatedAmount"),
34+
u32("closeAuthorityOption"),
35+
publicKey("closeAuthority"),
36+
]);
37+
38+
export function isValidPublicKey(key: PublicKeyInitData) {
39+
if (!key) {
40+
return false;
41+
}
42+
try {
43+
new PublicKey(key);
44+
return true;
45+
} catch {
46+
return false;
47+
}
48+
}
49+
50+
export async function findProgramAddress(
51+
seeds: Array<Buffer | Uint8Array>,
52+
programId: PublicKey
53+
) {
54+
const [publicKey, nonce] = await PublicKey.findProgramAddress(
55+
seeds,
56+
programId
57+
);
58+
return { publicKey, nonce };
59+
}
60+
/**
61+
* 查找用户spl—token账户地址
62+
* @param walletAddress 用户钱包地址
63+
* @param tokenMintAddress USDC Token 地址
64+
* @returns
65+
*/
66+
export async function findAssociatedTokenAddress(
67+
walletAddress: PublicKey,
68+
tokenMintAddress: PublicKey
69+
) {
70+
const { publicKey } = await findProgramAddress(
71+
[
72+
walletAddress.toBuffer(),
73+
TOKEN_PROGRAM_ID.toBuffer(),
74+
tokenMintAddress.toBuffer(),
75+
],
76+
ASSOCIATED_TOKEN_PROGRAM_ID
77+
);
78+
return publicKey;
79+
}
80+
81+
export async function createAssociatedTokenAccount(
82+
payer: PublicKey,
83+
tokenMintAddress: PublicKey,
84+
owner: PublicKey
85+
) {
86+
const associatedTokenAddress = await findAssociatedTokenAddress(
87+
owner,
88+
tokenMintAddress
89+
);
90+
91+
const keys = [
92+
{
93+
pubkey: payer,
94+
isSigner: true,
95+
isWritable: true,
96+
},
97+
{
98+
pubkey: associatedTokenAddress,
99+
isSigner: false,
100+
isWritable: true,
101+
},
102+
{
103+
pubkey: owner,
104+
isSigner: false,
105+
isWritable: false,
106+
},
107+
{
108+
pubkey: tokenMintAddress,
109+
isSigner: false,
110+
isWritable: false,
111+
},
112+
{
113+
pubkey: SYSTEM_PROGRAM_ID,
114+
isSigner: false,
115+
isWritable: false,
116+
},
117+
{
118+
pubkey: TOKEN_PROGRAM_ID,
119+
isSigner: false,
120+
isWritable: false,
121+
},
122+
{
123+
pubkey: RENT_PROGRAM_ID,
124+
isSigner: false,
125+
isWritable: false,
126+
},
127+
];
128+
129+
return new TransactionInstruction({
130+
keys,
131+
programId: ASSOCIATED_TOKEN_PROGRAM_ID,
132+
data: Buffer.from([]),
133+
});
134+
}
135+
/**
136+
* 转账交易
137+
* @param param
138+
* @returns
139+
*/
140+
export function transferToken(param: ITransferTokenParams) {
141+
let keys = [
142+
{ pubkey: param.source, isSigner: false, isWritable: true },
143+
{ pubkey: param.dest, isSigner: false, isWritable: true },
144+
{ pubkey: param.owner, isSigner: true, isWritable: false },
145+
];
146+
147+
const layout = struct([u8("instruction"), nu64("amount")]);
148+
149+
const data = Buffer.alloc(layout.span);
150+
layout.encode(
151+
{
152+
instruction: 3,
153+
amount: param.amount,
154+
},
155+
data
156+
);
157+
158+
return new TransactionInstruction({
159+
keys,
160+
data,
161+
programId: TOKEN_PROGRAM_ID,
162+
});
163+
}
164+
165+
interface ITransferTokenParams {
166+
/**
167+
* 转账源账户
168+
*/
169+
source: PublicKey;
170+
/**
171+
* 转账目标账户
172+
*/
173+
dest: PublicKey;
174+
/**
175+
* 转账人公钥
176+
*/
177+
owner: PublicKey;
178+
/**
179+
* 转账金额
180+
*/
181+
amount: number;
182+
}

tsconfig.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"ts-node": {
3+
"compilerOptions": {
4+
"module": "commonjs"
5+
}
6+
},
7+
"compilerOptions": {
8+
"declaration": true,
9+
"moduleResolution": "node",
10+
"module": "commonjs",
11+
"target": "es2021",
12+
"experimentalDecorators": true,
13+
"outDir": "dist",
14+
"forceConsistentCasingInFileNames": true,
15+
"sourceMap": true,
16+
"allowJs": true
17+
},
18+
"include": [
19+
"src/**/*"
20+
],
21+
"exclude": [
22+
"node_modules"
23+
]
24+
}

0 commit comments

Comments
 (0)