├── img ├── 08-01.png ├── 08-02.png ├── 08-03.png ├── 09-01.png ├── 09-02.png └── example-01-01.png ├── example-05-closeATA ├── README.md └── index.js ├── package.json ├── example-01-subWallet ├── README.md └── index.ts ├── example-02-subNewPool ├── README.md └── index.ts ├── 02-balance ├── index.ts └── README.md ├── 05-on ├── index.ts └── README.md ├── example-03-subPrice ├── README.md └── index.ts ├── 01-wallet ├── index.ts └── README.md ├── LICENSE ├── 09-buffer ├── index.ts └── README.md ├── README.md ├── 03-transfer ├── index.ts └── README.md ├── 08-v0 ├── index.ts ├── alt.ts └── README.md ├── 06-send ├── README.md └── index.ts ├── 07-cu ├── index.ts └── README.md ├── .gitignore ├── 04-get ├── index.ts └── README.md └── example-04-analysisToken ├── README.md └── index.ts /img/08-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainBuff/solana-web3js/HEAD/img/08-01.png -------------------------------------------------------------------------------- /img/08-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainBuff/solana-web3js/HEAD/img/08-02.png -------------------------------------------------------------------------------- /img/08-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainBuff/solana-web3js/HEAD/img/08-03.png -------------------------------------------------------------------------------- /img/09-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainBuff/solana-web3js/HEAD/img/09-01.png -------------------------------------------------------------------------------- /img/09-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainBuff/solana-web3js/HEAD/img/09-02.png -------------------------------------------------------------------------------- /example-05-closeATA/README.md: -------------------------------------------------------------------------------- 1 | 当您的钱包中持有新代币时,都会为其创建一个特定的代币帐户。每次创建一个账户,都会支付约0.002 SOL的租金。本工具可帮你一键退租。 2 | -------------------------------------------------------------------------------- /img/example-01-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ChainBuff/solana-web3js/HEAD/img/example-01-01.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@solana/web3.js": "^1.95.4", 4 | "axios": "^1.7.7", 5 | "typescript": "^5.6.3" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /example-01-subWallet/README.md: -------------------------------------------------------------------------------- 1 | # 监听钱包 2 | 3 | 此例子结合 `onAccountChange` 和 `getSignaturesForAddress` 方法来订阅指定账户的改变并获取指定账户的最新交易。 4 | 5 | 获取到最新交易后,将通过telegram bot发送通知。 6 | 7 | 通过 `npx esrun example-01-subWallet/index.ts`运行。 8 | 9 | ![](../img/example-01-01.png) -------------------------------------------------------------------------------- /example-02-subNewPool/README.md: -------------------------------------------------------------------------------- 1 | # 监听raydium新流动池创建 2 | 3 | 此例子使用 `onLogs` 方法来订阅和过滤raydium v4地址 `675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8` 的新流动池创建。 4 | 5 | 通过 `npx esrun example-02-subNewPool/index.ts`运行。 6 | 7 | ``` 8 | 新流动池被创建: https://solscan.io/tx/28XF272z2ipWMfQ2dtdJgRFiSuJojCaxKknXRyekbzKd1ygtrjciikTjcvTCDeHzTJfe9hPvnkDQuMztaRWRdbGa 9 | 新流动池被创建: https://solscan.io/tx/4pMQnmpnoGcaSmH1apS91Gknj94vedDkdfaAQKMeaq3GqU6WFq5o5uySRTCTCQcaQqpL9F5Cjw3ncrtnqEdMHa4x 10 | ``` -------------------------------------------------------------------------------- /02-balance/index.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js'; 2 | 3 | // 创建RPC连接 4 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 5 | // const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 6 | 7 | async function main() { 8 | 9 | // 查询Jito1的SOL余额 10 | const publicKey = new PublicKey('CXPeim1wQMkcTvEHx9QdhgKREYYJD8bnaCCqPRwJ1to1'); 11 | const balance = await connection.getBalance(publicKey); 12 | console.log(`Jito1余额: ${balance / LAMPORTS_PER_SOL} SOL`); // 转换为 SOL 单位 13 | } 14 | 15 | main(); -------------------------------------------------------------------------------- /example-02-subNewPool/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey 4 | } from '@solana/web3.js'; 5 | 6 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 7 | // const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 8 | const raydiumV4PublicKey = new PublicKey('675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8'); 9 | 10 | connection.onLogs( 11 | raydiumV4PublicKey, 12 | ({ logs, err, signature }) => { 13 | if (err) return; 14 | 15 | if (logs && logs.some(log => log.includes("initialize2"))) { 16 | console.log(`新流动池被创建: https://solscan.io/tx/${signature}`); 17 | } 18 | }, 19 | "confirmed" 20 | ); -------------------------------------------------------------------------------- /05-on/index.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from '@solana/web3.js'; 2 | 3 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 4 | // const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 5 | 6 | async function main() { 7 | 8 | // 1. onAccountChange 9 | // connection.onAccountChange(new PublicKey("orcACRJYTFjTeo2pV8TfYRTpmqfoYgbVi9GeANXTCc8"), (accountInfo) => { 10 | // console.log(`账户变化: ${JSON.stringify(accountInfo)}\n`); 11 | // }); 12 | 13 | // 2. onLogs 14 | connection.onLogs(new PublicKey("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"), (logs) => { 15 | console.log(`日志: ${JSON.stringify(logs)}\n`); 16 | }); 17 | 18 | } 19 | 20 | main(); -------------------------------------------------------------------------------- /example-03-subPrice/README.md: -------------------------------------------------------------------------------- 1 | # 监听raydium clmm代币的实时价格 2 | 3 | 此例子结合 `onSlotUpdate` 和 解析buffer的方法来实时订阅raydium clmm WSOL/USDC流动池中WSOL的价格。 4 | 5 | 通过 `npx esrun example-03-subPrice/index.ts` 运行 6 | 7 | ``` 8 | sqrtPriceX64Value at offset 253: 8617564287599812924 9 | WSOL价格: 218.23762107593976 10 | --- 11 | 12 | sqrtPriceX64Value at offset 253: 8618158284616202734 13 | WSOL价格: 218.2677077591724 14 | --- 15 | 16 | sqrtPriceX64Value at offset 253: 8617802344396074973 17 | WSOL价格: 218.24967869796686 18 | --- 19 | 20 | sqrtPriceX64Value at offset 253: 8616982572722284816 21 | WSOL价格: 218.2081585081117 22 | --- 23 | 24 | sqrtPriceX64Value at offset 253: 8617078429812061202 25 | WSOL价格: 218.21301332015403 26 | --- 27 | 28 | sqrtPriceX64Value at offset 253: 8616930983791568854 29 | WSOL价格: 218.2055457392501 30 | --- 31 | ``` -------------------------------------------------------------------------------- /01-wallet/index.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from "@solana/web3.js"; 2 | import fs from "fs"; 3 | import { Buffer } from 'buffer'; 4 | 5 | // 创建钱包 6 | const wallet = Keypair.generate(); 7 | 8 | // 获取公钥和私钥 9 | const publicKey = wallet.publicKey.toBase58(); 10 | const secretKey = wallet.secretKey; // 一个 Uint8Array 11 | 12 | // 打印 13 | console.log("钱包公钥:", publicKey); 14 | console.log("钱包私钥:", secretKey); 15 | console.log("钱包私钥(base64):", Buffer.from(secretKey).toString("base64")); 16 | 17 | // 保存Uint8Array私钥 18 | fs.writeFileSync("wallet.json", JSON.stringify(Array.from(secretKey))); 19 | 20 | // 导入钱包 21 | // const secretKey = Uint8Array.from(JSON.parse(fs.readFileSync("wallet.json"))); 22 | // const wallet = Keypair.fromSecretKey(secretKey); 23 | 24 | // console.log("钱包公钥:", wallet.publicKey.toString()); 25 | // console.log("钱包私钥:", wallet.secretKey); 26 | // console.log("钱包私钥(base64):", Buffer.from(secretKey).toString("base64")); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 ChainBuff Community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /09-buffer/index.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from '@solana/web3.js'; 2 | import BN from 'bn.js'; 3 | 4 | // 创建RPC连接 5 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 6 | // const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 7 | 8 | async function main() { 9 | 10 | const poolAccountPublicKey = new PublicKey('8sLbNZoA1cfnvMJLPfp98ZLAnFSYCFApfJKMbiXNLwxj'); 11 | const accountInfo = await connection.getAccountInfo(poolAccountPublicKey); 12 | const dataBuffer = accountInfo?.data; 13 | if (!dataBuffer) { 14 | throw new Error("Account data not found"); 15 | } 16 | console.log(dataBuffer) 17 | 18 | const offset = 253 19 | const sqrtPriceX64Buffer = dataBuffer.slice(offset, offset + 16); // 读取16个字节 20 | const sqrtPriceX64Value = new BN(sqrtPriceX64Buffer, 'le'); // 使用小端字节序创建BN实例 21 | console.log(`sqrtPriceX64Value at offset ${offset}:`, sqrtPriceX64Value.toString()); 22 | 23 | // 计算价格 24 | const sqrtPriceX64BigInt = BigInt(sqrtPriceX64Value.toString()); 25 | const sqrtPriceX64Float = Number(sqrtPriceX64BigInt) / (2 ** 64); 26 | const price = sqrtPriceX64Float ** 2 * 1e9 / 1e6; 27 | console.log(`WSOL价格:`, price.toString()) 28 | } 29 | 30 | main(); -------------------------------------------------------------------------------- /example-03-subPrice/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey 4 | } from '@solana/web3.js'; 5 | import BN from 'bn.js'; 6 | 7 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 8 | // const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 9 | 10 | connection.onAccountChange( 11 | new PublicKey('8sLbNZoA1cfnvMJLPfp98ZLAnFSYCFApfJKMbiXNLwxj'), 12 | async (accountInfo) => { 13 | const dataBuffer = accountInfo?.data; 14 | if (!dataBuffer) { 15 | throw new Error("Account data not found"); 16 | } 17 | 18 | const offset = 253 19 | const sqrtPriceX64Buffer = dataBuffer.slice(offset, offset + 16); // 读取16个字节 20 | const sqrtPriceX64Value = new BN(sqrtPriceX64Buffer, 'le'); // 使用小端字节序创建BN实例 21 | console.log(`sqrtPriceX64Value at offset ${offset}:`, sqrtPriceX64Value.toString()); 22 | 23 | // 计算价格 24 | const sqrtPriceX64BigInt = BigInt(sqrtPriceX64Value.toString()); 25 | const sqrtPriceX64Float = Number(sqrtPriceX64BigInt) / (2 ** 64); 26 | const price = sqrtPriceX64Float ** 2 * 1e9 / 1e6; 27 | console.log(`WSOL价格:`, price.toString()) 28 | console.log('---\n') 29 | }, 30 | 'confirmed' 31 | ); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solana Web3.js 教程 2 | 3 | > 本教程由Buff社区成员编写,每周更新1-3节。 4 | 5 | Solana Web3.js 库是一个用于与 Solana 区块链进行交互的 JavaScript 基础库。 6 | 7 | 本教程旨在提供一些简单的例子,帮助你快速上手 Solana 区块链开发。 8 | 9 | --- 10 | 11 | 在阅读之前,需要运行如下来安装@solana/web3.js,本教程使用的版本为1.95.4。 12 | 13 | ``` 14 | $ npm install @solana/web3.js@1.95.4 15 | ``` 16 | 17 | 之后,可通过 `npx esrun xxx/index.ts` 来运行每节的代码。 18 | 19 | ## 目录 20 | 21 | 0. 基础术语表 22 | 23 | ### 基础 24 | 25 | 1. [创建钱包 & 导入钱包](./01-wallet/) 26 | 2. [获取账户下的 SOL 余额](./02-balance/) 27 | 3. [发送自己的第一笔转账交易](./03-transfer/) 28 | 4. [读取链上数据(一):`get` 读取](./04-get/) 29 | 5. [读取链上数据(二):`on` 订阅](./05-on/) 30 | 6. [写入链上数据:`send` 发送交易](./06-send/) 31 | 32 | ### 进阶 33 | 34 | 7. [添加优先费](./07-cu/) 35 | 8. [`v0` 交易](./08-v0/) 36 | 9. [解析buffer](./09-buffer/) 37 | 10. 38 | 39 | ### 实战 40 | 41 | 1. [监听钱包](./example-01-subWallet/) 42 | 2. [监听raydium v4新流动池创建](./example-02-subNewPool/) 43 | 3. [监听raydium clmm代币的实时价格](./example-03-subPrice/) 44 | 4. [获取代币持有比例](./example-04-analysisToken/) 45 | 5. [关闭代币账户退租](./example-05-closeATA/) 46 | 47 | ## 参考 48 | 49 | - https://solana-labs.github.io/solana-web3.js/v1.x 50 | - https://solana.com/zh/developers/cookbook 51 | - https://solana.com/docs 52 | 53 | ## 捐赠 54 | 55 | 如果你想支持Buff社区的发展,可通过向 `buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ` 地址捐赠Solana链上资产。 56 | 57 | 社区资金将被用于奖励社区贡献者,贡献包括但不限于 `PR`。 58 | -------------------------------------------------------------------------------- /02-balance/README.md: -------------------------------------------------------------------------------- 1 | # 获取账户下的 SOL 余额 2 | 3 | > RPC端口是与Solana链上交互的媒介。 4 | 5 | 本部分将介绍如何使用Connection类创建一个RPC连接,并使用getBalance方法获取账户下的SOL余额。 6 | 7 | ## 创建RPC连接 8 | 9 | `Connection` 类是与Solana区块链交互的核心类,它提供了多种方法来与区块链进行交互。通过给定RPC端口和确认级别,可以创建一个Connection实例,如下: 10 | 11 | ```ts 12 | import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js'; 13 | 14 | // 创建RPC连接 15 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 16 | ``` 17 | 18 | `https://api.mainnet-beta.solana.com` 是solana官方提供的RPC端口,`confirmed` 是默认的确认级别。 19 | 20 | > 注意: 21 | > `processed` 是较低的确认级别,意味着查询的数据是经过验证但尚未完全确认的。`confirmed` 表示节点已经将交易写入区块链,但也不一定被最终确认。如果需要更高的确认级别,可以使用 `finalized`。 22 | 23 | ## 查询账户余额 24 | 25 | `getBalance` 方法用于查询指定账户下的SOL余额,返回值是账户余额的lamports数量,需要除以 `LAMPORTS_PER_SOL` 转换为SOL单位。 26 | 27 | > 注意: 28 | > `LAMPORTS_PER_SOL` 是1个SOL的lamports数量,等于10^9。 29 | 30 | 此处我们将查询Jito1的SOL余额,其公钥为 `CXPeim1wQMkcTvEHx9QdhgKREYYJD8bnaCCqPRwJ1to1`。 31 | 32 | ```ts 33 | async function main() { 34 | 35 | // 查询Jito1的SOL余额 36 | const publicKey = new PublicKey('CXPeim1wQMkcTvEHx9QdhgKREYYJD8bnaCCqPRwJ1to1'); 37 | const balance = await connection.getBalance(publicKey); 38 | console.log(`Jito1余额: ${balance / LAMPORTS_PER_SOL} SOL`); // 转换为 SOL 单位 39 | } 40 | 41 | main(); 42 | ``` 43 | 44 | 通过 `npx esrun 02-balance/index.ts` 运行上述代码,可以看到Jito1当前的SOL余额为9.999906999 SOL。 45 | 46 | ``` 47 | Jito1余额: 9.999906999 SOL 48 | ``` -------------------------------------------------------------------------------- /example-01-subWallet/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey 4 | } from '@solana/web3.js'; 5 | import axios from 'axios'; 6 | 7 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 8 | // const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 9 | const publicKey = new PublicKey('orcACRJYTFjTeo2pV8TfYRTpmqfoYgbVi9GeANXTCc8'); 10 | const botToken = ''; 11 | const chatId = ''; 12 | 13 | async function sendMessage(message) { 14 | const url = `https://api.telegram.org/bot${botToken}/sendMessage`; 15 | 16 | try { 17 | await axios.post(url, { 18 | chat_id: chatId, 19 | text: message, 20 | parse_mode: 'HTML', 21 | disable_web_page_preview: true, 22 | }); 23 | 24 | } catch (error) { 25 | console.error('Error sending message:', error.message); 26 | } 27 | } 28 | 29 | // async function test() { 30 | // const sig = await connection.getSignaturesForAddress(publicKey, {limit: 1}, 'confirmed'); 31 | // await sendMessage(`新交易!\n\nhttps://solscan.io/tx/${sig[0].signature}`) 32 | // } 33 | 34 | // test(); 35 | 36 | connection.onAccountChange( 37 | publicKey, 38 | async () => { 39 | const sig = await connection.getSignaturesForAddress(publicKey, {limit: 1}, 'confirmed'); 40 | await sendMessage(`新交易!\n\nhttps://solscan.io/tx/${sig[0].signature}`) 41 | }, 42 | 'confirmed' 43 | ); -------------------------------------------------------------------------------- /03-transfer/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey, 4 | Keypair, 5 | Transaction, 6 | SystemProgram, 7 | sendAndConfirmTransaction 8 | } from '@solana/web3.js'; 9 | import fs from "fs"; 10 | 11 | // 创建RPC连接 12 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 13 | // const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 14 | 15 | // 本地导入钱包 16 | const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("wallet.json"))); 17 | // const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2.json"))); 18 | const fromWallet = Keypair.fromSecretKey(fromSecretKey); 19 | 20 | async function main() { 21 | 22 | // 创建交易 23 | const transaction = new Transaction(); 24 | 25 | // 目标地址 26 | const toAddress = new PublicKey('buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ'); 27 | 28 | // 添加转账指令 29 | const instruction = SystemProgram.transfer({ 30 | fromPubkey: fromWallet.publicKey, 31 | toPubkey: toAddress, 32 | lamports: 1000, // 1000 lamports 33 | }); 34 | transaction.add(instruction); 35 | 36 | // 模拟交易 37 | const simulateResult = await connection.simulateTransaction(transaction, [fromWallet]); 38 | console.log("模拟交易结果: ", simulateResult); 39 | 40 | // 发送交易 41 | const signature = await sendAndConfirmTransaction(connection, transaction, [fromWallet]); 42 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 43 | } 44 | 45 | main(); -------------------------------------------------------------------------------- /08-v0/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey, 4 | Keypair, 5 | TransactionMessage, 6 | VersionedTransaction, 7 | SystemProgram, 8 | } from '@solana/web3.js'; 9 | import fs from "fs"; 10 | 11 | // 创建RPC连接 12 | // const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 13 | const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 14 | 15 | // 本地导入钱包 16 | // const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("wallet.json"))); 17 | const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2.json"))); 18 | const fromWallet = Keypair.fromSecretKey(fromSecretKey); 19 | 20 | async function main() { 21 | 22 | // 目标地址 23 | const toAddress = new PublicKey('buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ'); 24 | 25 | // 转账指令 26 | const instruction = SystemProgram.transfer({ 27 | fromPubkey: fromWallet.publicKey, 28 | toPubkey: toAddress, 29 | lamports: 1000, // 1000 lamports 30 | }); 31 | 32 | // 创建v0 message 33 | const { blockhash } = await connection.getLatestBlockhash(); 34 | const messageV0 = new TransactionMessage({ 35 | payerKey: fromWallet.publicKey, 36 | recentBlockhash: blockhash, // 最近的区块hash 37 | instructions: [instruction], // 指令数组 38 | }).compileToV0Message(); 39 | 40 | // 创建v0交易并签名 41 | const transaction = new VersionedTransaction(messageV0); 42 | transaction.sign([fromWallet]); 43 | 44 | // 模拟交易 45 | const simulateResult = await connection.simulateTransaction(transaction); 46 | console.log("模拟交易结果: ", simulateResult); 47 | 48 | // 发送交易 49 | const signature = await connection.sendTransaction(transaction); 50 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 51 | } 52 | 53 | main(); -------------------------------------------------------------------------------- /01-wallet/README.md: -------------------------------------------------------------------------------- 1 | # 创建钱包 & 导入钱包 2 | 3 | > 私钥是账户的唯一凭证,请妥善保管。 4 | 5 | 本部分将介绍如何使用web3.js库创建自己的钱包。 6 | 7 | ## 创建钱包 8 | 9 | Solana 钱包是指一对 `私钥` 和 `公钥`,它们是用于访问和管理 Solana 上账户的身份凭证。密钥对通过随机数生成,以确保每个密钥对是唯一的。 10 | 11 | - 私钥:一个保密的、用于证明账户所有权的密钥。私钥可以用来生成数字签名和授权交易等。私钥一旦泄露,其他人可以使用它控制你的账户。 12 | - 公钥:与私钥配对的公开部分。公钥是你的账户地址,其他人可以通过公钥向你发送资产或查询账户余额,但无法使用它来授权操作。 13 | 14 | 15 | ```ts 16 | import { Keypair } from "@solana/web3.js"; 17 | import fs from "fs"; 18 | import { Buffer } from 'buffer'; 19 | 20 | // 创建钱包 21 | const wallet = Keypair.generate(); 22 | 23 | // 获取公钥和私钥 24 | const publicKey = wallet.publicKey.toBase58(); 25 | const secretKey = wallet.secretKey; // 一个 Uint8Array 26 | 27 | // 打印 28 | console.log("钱包公钥:", publicKey); 29 | console.log("钱包私钥:", secretKey); 30 | console.log("钱包私钥(base64):", Buffer.from(secretKey).toString("base64")); 31 | 32 | // 保存 Uint8Array 私钥 33 | fs.writeFileSync("wallet.json", JSON.stringify(Array.from(secretKey))); 34 | ``` 35 | 36 | 通过 `npx esrun 01-wallet/index.ts` 运行,输出如下: 37 | 38 | ```bash 39 | 钱包公钥: EkfAVHeFtDUmGQJH5e67i784wKKNA7jyStKywQWysY73 40 | 钱包私钥: Uint8Array(64) [ 41 | 180, 206, 18, 236, 242, 179, 168, 142, 181, 66, 42 | 158, 123, 232, 162, 205, 195, 192, 56, 117, 152, 43 | 238, 67, 141, 162, 250, 60, 104, 153, 79, 96, 44 | 49, 234, 204, 87, 14, 120, 218, 77, 112, 188, 45 | 235, 139, 1, 134, 201, 208, 112, 25, 2, 151, 46 | 227, 188, 25, 69, 178, 196, 146, 227, 179, 14, 47 | 118, 115, 233, 234 48 | ] 49 | 钱包私钥(base64): tM4S7PKzqI61Qp576KLNw8A4dZjuQ42i+jxomU9gMerMVw542k1wvOuLAYbJ0HAZApfjvBlFssSS47MOdnPp6g== 50 | ``` 51 | 52 | 私钥被保存到此项目根目录的 `wallet.json` 文件中。公钥长度为 32 字节, 通常以 Base58 编码;私钥长度为 64 字节, 通常以 Base64 编码。 53 | 54 | ## 导入钱包 55 | 56 | 从刚才新保存的 `wallet.json` 文件中导入私钥来恢复钱包。 57 | 58 | ```ts 59 | const secretKey = Uint8Array.from(JSON.parse(fs.readFileSync("wallet.json"))); 60 | const wallet = Keypair.fromSecretKey(secretKey); 61 | 62 | console.log("钱包公钥:", wallet.publicKey.toString()); 63 | console.log("钱包私钥:", wallet.secretKey); 64 | console.log("钱包私钥(base64):", Buffer.from(secretKey).toString("base64")); 65 | ``` 66 | 67 | 运行后输出应与之前的输出一致。 68 | 69 | 70 | -------------------------------------------------------------------------------- /06-send/README.md: -------------------------------------------------------------------------------- 1 | # 写入链上数据:`send` 发送交易 2 | 3 | 本部分将带你使用几种常用的发送交易方法,功能上都大同小异,分别是 `sendAndConfirmTransaction`, `sendRawTransaction` 和 `sendEncodedTransaction`。 4 | 5 | ## 1. sendAndConfirmTransaction 6 | 7 | 发送交易并等待其确认,带有自动确认机制。适用于希望简化交易确认的场景。 8 | 9 | ```ts 10 | const signature = await sendAndConfirmTransaction(connection, transaction, [fromWallet], { 11 | skipPreflight: false 12 | }); 13 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 14 | ``` 15 | 16 | 通过 `npx esrun 06-send/index.ts` 运行后,可查看模拟交易的结果和交易是否发送成功。 17 | 18 | ``` 19 | 模拟交易结果: { 20 | context: { apiVersion: '2.0.3', slot: 300771879 }, 21 | value: { 22 | accounts: null, 23 | err: null, 24 | innerInstructions: null, 25 | logs: [ 26 | 'Program 11111111111111111111111111111111 invoke [1]', 27 | 'Program 11111111111111111111111111111111 success' 28 | ], 29 | replacementBlockhash: null, 30 | returnData: null, 31 | unitsConsumed: 150 32 | } 33 | } 34 | 交易已发送: https://solscan.io/tx/4W4QvBfAzezyxatAbE53awibDytLkZmcagnBihz8QSaqhMTJFN5AoQjfxggm7PGQgYmTvTHWhmDSsi4JEn4wCFwX 35 | ``` 36 | 37 | ## 2. sendRawTransaction 38 | 39 | 发送已签名和序列化的交易。 40 | 41 | ```ts 42 | 43 | const { blockhash } = await connection.getLatestBlockhash(); 44 | transaction.recentBlockhash = blockhash; 45 | transaction.feePayer = fromWallet.publicKey; 46 | transaction.sign(fromWallet); 47 | const rawTransaction = transaction.serialize(); 48 | 49 | const signature = await connection.sendRawTransaction(rawTransaction, { 50 | skipPreflight: false 51 | }) 52 | console.log("交易签名:", signature) 53 | ``` 54 | 55 | ``` 56 | 交易签名:3CuP3PpSMMknoB88kWEzAC6dxTAYx1x5oo9KjFSzaCydZRcSdpogBdLLJbKEVHp8nmPfyxB3UhUQtnM6YNFNsA6A 57 | ``` 58 | 59 | ## 3. sendEncodedTransaction 60 | 61 | 发送经过 base64 编码的交易数据,具有更好的兼容性。 62 | 63 | ```ts 64 | const base64Transaction = rawTransaction.toString('base64'); 65 | const signature = await connection.sendEncodedTransaction(base64Transaction, { 66 | skipPreflight: false 67 | }); 68 | console.log("交易签名:", signature) 69 | ``` 70 | 71 | ``` 72 | 交易签名: 4NnavZedvvx5s7KTYnuVcvbZiV4dsUDUswJBe11E7FV2txcCYM8ypjbDmvHzWFvqKf9t3RZLkEo7Ek2HxoDyCcV8 73 | ``` 74 | 75 | > 注意:若将skipPreflight设置为true,代表你将跳过交易发送前的模拟过程,这会加快提交速度,但也可能导致交易失败,丢失交易费用。 -------------------------------------------------------------------------------- /07-cu/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey, 4 | Keypair, 5 | Transaction, 6 | SystemProgram, 7 | ComputeBudgetProgram, 8 | sendAndConfirmTransaction 9 | } from '@solana/web3.js'; 10 | import fs from "fs"; 11 | 12 | // 创建RPC连接 13 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 14 | // const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 15 | 16 | // 本地导入钱包 17 | // const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("wallet.json"))); 18 | const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2.json"))); 19 | const fromWallet = Keypair.fromSecretKey(fromSecretKey); 20 | 21 | async function main() { 22 | 23 | // 创建交易 24 | const transaction = new Transaction(); 25 | 26 | // CU价格 27 | const computeUnitPriceInstruction = ComputeBudgetProgram.setComputeUnitPrice({ 28 | microLamports: 5 29 | }); 30 | transaction.add(computeUnitPriceInstruction); 31 | 32 | // CU数量 33 | // const computeUnitLimitInstruction = ComputeBudgetProgram.setComputeUnitLimit({ 34 | // units: 500, 35 | // }); 36 | // transaction.add(computeUnitLimitInstruction); 37 | 38 | // 目标地址 39 | const toAddress = new PublicKey('buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ'); 40 | 41 | // 添加转账指令 42 | const instruction1 = SystemProgram.transfer({ 43 | fromPubkey: fromWallet.publicKey, 44 | toPubkey: toAddress, 45 | lamports: 1000, // 1000 lamports 46 | }); 47 | transaction.add(instruction1); 48 | 49 | // // 添加转账指令 50 | // const instruction2 = SystemProgram.transfer({ 51 | // fromPubkey: fromWallet.publicKey, 52 | // toPubkey: toAddress, 53 | // lamports: 1000, // 1000 lamports 54 | // }); 55 | // transaction.add(instruction2); 56 | 57 | // 模拟交易 58 | const simulateResult = await connection.simulateTransaction(transaction, [fromWallet]); 59 | console.log("模拟交易结果: ", simulateResult); 60 | 61 | // 发送交易 62 | const signature = await sendAndConfirmTransaction(connection, transaction, [fromWallet]); 63 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 64 | } 65 | 66 | main(); -------------------------------------------------------------------------------- /09-buffer/README.md: -------------------------------------------------------------------------------- 1 | # 解析buffer 2 | 3 | 解析buffer实际上就是解析链上存储的原始二进制数据。 4 | 5 | 在一般情况下,我们其实并不清楚每段二进制数据的结构,而且往往我们只需要这段二进制数据中的某个字段。因此,此处将介绍手动解析链上二进制数据的方法,以获取raydium clmm WSOL/USDC流动池的WSOL价格为例,其地址为 `8sLbNZoA1cfnvMJLPfp98ZLAnFSYCFApfJKMbiXNLwxj`。 6 | 7 | > 获取流动池价格在套利和流动性挖矿场景中经常使用。 8 | 9 | ## 解析WSOL/USDC流动池中WSOL的USDC相对价格 10 | 11 | 在raydium clmm协议流动池中,代币价格被显式保存在流动池账户地址的账户数据中,如下: 12 | 13 | ![](../img/09-01.png) 14 | 15 | 此处假定我们并不清楚存储数据的结构,而是使用手动解析的方法来获取此价格。 16 | 17 | sqrtPriceX64的数据类型为 `u128`,占用16个字节。因此,只需要获取到sqrtPriceX64的起始偏移量(offset),往后取16字节进行解析即可。 18 | 19 | 其他数据类型的长度已在图中被标注,其中 `u8` 为1,`pubkey`为32,`u16`为2。 20 | 21 | > 注意:在Solana的账户数据中,最初的8字节被用于前缀标识符(discriminator)。 22 | 23 | 所以,sqrtPriceX64的起始偏移量应为 $8 + 1 + 32*7 + 1 + 1 + 2 + 16 = 253$ 24 | 25 | ![](../img/09-02.png) 26 | 27 | ```ts 28 | import { Connection, PublicKey } from '@solana/web3.js'; 29 | import BN from 'bn.js'; 30 | 31 | // 创建RPC连接 32 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 33 | 34 | async function main() { 35 | 36 | const poolAccountPublicKey = new PublicKey('8sLbNZoA1cfnvMJLPfp98ZLAnFSYCFApfJKMbiXNLwxj'); 37 | const accountInfo = await connection.getAccountInfo(poolAccountPublicKey); 38 | const dataBuffer = accountInfo?.data; 39 | if (!dataBuffer) { 40 | throw new Error("Account data not found"); 41 | } 42 | console.log(dataBuffer) 43 | 44 | const offset = 253 45 | const sqrtPriceX64Buffer = dataBuffer.slice(offset, offset + 16); // 读取16个字节 46 | const sqrtPriceX64Value = new BN(sqrtPriceX64Buffer, 'le'); // 使用小端字节序创建BN实例 47 | console.log(`sqrtPriceX64Value at offset ${offset}:`, sqrtPriceX64Value.toString()); 48 | 49 | // 计算价格 50 | const sqrtPriceX64BigInt = BigInt(sqrtPriceX64Value.toString()); 51 | const sqrtPriceX64Float = Number(sqrtPriceX64BigInt) / (2 ** 64); 52 | const price = sqrtPriceX64Float ** 2 * 1e9 / 1e6; 53 | console.log(`WSOL价格:`, price.toString()) 54 | } 55 | 56 | main(); 57 | ``` 58 | 59 | 通过 `npx esrun 09-buffer/index.ts` 运行,输出应如下: 60 | 61 | ``` 62 | 63 | sqrtPriceX64Value at offset 253: 8622437757683733036 64 | WSOL价格: 218.4845296506469 65 | ``` -------------------------------------------------------------------------------- /example-05-closeATA/index.js: -------------------------------------------------------------------------------- 1 | const { Connection, PublicKey, Transaction, SystemProgram, sendAndConfirmTransaction, Keypair } = require("@solana/web3.js"); 2 | const SPLTOKEN = require('@solana/spl-token'); 3 | const bs58 = require('bs58'); 4 | 5 | const RPC_ENDPOINT = 'https://aged-stylish-glade.solana-mainnet.quiknode.pro/xxx/'; 6 | 7 | const connection = new Connection(RPC_ENDPOINT, 'confirmed'); 8 | 9 | async function closeTokenAccounts(ownerAddress, payerKeyPair) { 10 | const ownerPublicKey = new PublicKey(ownerAddress); 11 | 12 | const tokenAccounts = await connection.getTokenAccountsByOwner(ownerPublicKey, { programId: SPLTOKEN.TOKEN_PROGRAM_ID }); 13 | const accountInfos = tokenAccounts.value; 14 | 15 | const processedAccounts = accountInfos.map(infos => { 16 | //解密getTokenAccountsByOwner返回的令牌账户data信息 17 | const decoded = SPLTOKEN.AccountLayout.decode(infos.account.data); 18 | return { 19 | pubkey: infos.pubkey.toBase58(), 20 | mint: decoded.mint.toBase58(), 21 | lamports: parseInt(infos.account.lamports, 10),//剩余租金 22 | amount: parseInt(decoded.amount, 10), //持币数量 23 | isNative:decoded.isNative.toString(), //是否平台币 24 | closeAuthorityOption: decoded.closeAuthorityOption.toString() //关闭权限 0是关 1是开 25 | }; 26 | }); 27 | 28 | let transaction = new Transaction(); 29 | for (let accountinfo of processedAccounts) { 30 | if (accountinfo.isNative == 0 && accountinfo.amount == "0" && accountinfo.closeAuthorityOption == "0") { 31 | // 创建关闭账户的指令 32 | const closeAccountInstruction = SPLTOKEN.createCloseAccountInstruction( 33 | new PublicKey(accountinfo.pubkey), 34 | ownerPublicKey, 35 | ownerPublicKey, 36 | [], 37 | ); 38 | transaction.add(closeAccountInstruction); 39 | } 40 | } 41 | 42 | try { 43 | const signature = await sendAndConfirmTransaction(connection, transaction, [payerKeyPair]); 44 | console.error('signature:', signature); 45 | } catch (error) { 46 | console.error('交易发送失败:', error); 47 | } 48 | } 49 | 50 | const walletPrivateKey = ''; // 发送者私钥 51 | const payerKeyPair = Keypair.fromSecretKey(bs58.decode(walletPrivateKey)); 52 | 53 | const walletPublic = ''; // 发送者公钥 54 | 55 | closeTokenAccounts(walletPublic, payerKeyPair).catch(console.error); 56 | -------------------------------------------------------------------------------- /03-transfer/README.md: -------------------------------------------------------------------------------- 1 | # 发送第一笔转账交易 2 | 3 | > 发送交易是改变链上状态的唯一方式。 4 | 5 | 本部分将介绍如何创建并发送自己的第一笔转账交易。 6 | 7 | ## 创建RPC连接和导入钱包 8 | 9 | 首先,结合我们之前学到的,需要先创建一个RPC连接和导入我们的钱包私钥。 10 | 11 | ```ts 12 | import { 13 | Connection, 14 | PublicKey, 15 | Keypair, 16 | Transaction, 17 | SystemProgram, 18 | sendAndConfirmTransaction 19 | } from '@solana/web3.js'; 20 | import fs from "fs"; 21 | 22 | // 创建RPC连接 23 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 24 | 25 | // 本地导入钱包 26 | const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("wallet.json"))); 27 | const fromWallet = Keypair.fromSecretKey(fromSecretKey); 28 | ``` 29 | 30 | ## 创建、模拟和发送转账交易 31 | 32 | 接下来,我们将创建一条交易,并在其中添加一条向 `buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ` 的转账指令。之后,可以先模拟交易是否会成功。若模拟成功,则会真正的发送此笔交易。 33 | 34 | > 注意: 35 | > `buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ` 为Buff社区公共资金账户,可以换成任何其他的账户。 36 | 37 | ```ts 38 | async function main() { 39 | 40 | // 创建交易 41 | const transaction = new Transaction(); 42 | 43 | // 目标地址 44 | const toAddress = new PublicKey('buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ'); 45 | 46 | // 添加转账指令 47 | const instruction = SystemProgram.transfer({ 48 | fromPubkey: fromWallet.publicKey, 49 | toPubkey: toAddress, 50 | lamports: 1000, // 1000 lamports 51 | }); 52 | transaction.add(instruction); 53 | 54 | // 模拟交易 55 | const simulateResult = await connection.simulateTransaction(transaction, [fromWallet]); 56 | console.log("模拟交易结果: ", simulateResult); 57 | 58 | // 发送交易 59 | const signature = await sendAndConfirmTransaction(connection, transaction, [fromWallet]); 60 | console.log(`交易已发送, https://solscan.io/tx/${signature}`); 61 | } 62 | 63 | main(); 64 | ``` 65 | 66 | 通过 `npx esrun 03-transfer/index.ts` 运行脚本后,输出应如下: 67 | 68 | ```bash 69 | 模拟交易结果: { 70 | context: { apiVersion: '2.0.3', slot: 300547622 }, 71 | value: { 72 | accounts: null, 73 | err: null, 74 | innerInstructions: null, 75 | logs: [ 76 | 'Program 11111111111111111111111111111111 invoke [1]', 77 | 'Program 11111111111111111111111111111111 success' 78 | ], 79 | replacementBlockhash: null, 80 | returnData: null, 81 | unitsConsumed: 150 82 | } 83 | } 84 | 交易已发送: https://solscan.io/tx/3Vfp5qPhF14bNb2jLtTccabCDbHUmxqtXerUvPEjKb6RpJ8jU3H9M9JgcUbDPtgesB3WFP9M8VZTzECgBavnjxaC 85 | ``` 86 | 87 | 通过以上这条交易,我们成功的给 `buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ` 转了1000个lamports,可以通过区块链浏览器[查看这笔交易](https://solscan.io/tx/3Vfp5qPhF14bNb2jLtTccabCDbHUmxqtXerUvPEjKb6RpJ8jU3H9M9JgcUbDPtgesB3WFP9M8VZTzECgBavnjxaC)。 -------------------------------------------------------------------------------- /06-send/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey, 4 | Keypair, 5 | Transaction, 6 | SystemProgram, 7 | sendAndConfirmTransaction, 8 | } from '@solana/web3.js'; 9 | import fs from "fs"; 10 | 11 | // 创建RPC连接 12 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 13 | // const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 14 | 15 | // 本地导入钱包 16 | // const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("wallet.json"))); 17 | const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2.json"))); 18 | const fromWallet = Keypair.fromSecretKey(fromSecretKey); 19 | 20 | async function main() { 21 | 22 | // 创建交易 23 | const transaction = new Transaction(); 24 | 25 | // 目标地址 26 | const toAddress = new PublicKey('buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ'); 27 | 28 | // 添加转账指令 29 | const instruction = SystemProgram.transfer({ 30 | fromPubkey: fromWallet.publicKey, 31 | toPubkey: toAddress, 32 | lamports: 1000, // 1000 lamports 33 | }); 34 | transaction.add(instruction); 35 | 36 | // 模拟交易 37 | const simulateResult = await connection.simulateTransaction(transaction, [fromWallet]); 38 | console.log("模拟交易结果: ", simulateResult); 39 | 40 | // 发送交易 41 | // const signature = await sendAndConfirmTransaction(connection, transaction, [fromWallet]); 42 | // console.log(`交易已发送: https://solscan.io/tx/${signature}`); 43 | 44 | // 发送交易 45 | // 1. sendAndConfirmTransaction 46 | const signature = await sendAndConfirmTransaction(connection, transaction, [fromWallet], { 47 | skipPreflight: false 48 | }); 49 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 50 | 51 | // const { blockhash } = await connection.getLatestBlockhash(); 52 | // transaction.recentBlockhash = blockhash; 53 | // transaction.feePayer = fromWallet.publicKey; 54 | // transaction.sign(fromWallet); 55 | // const rawTransaction = transaction.serialize(); 56 | 57 | // 2. sendRawTransaction 58 | // const signature = await connection.sendRawTransaction(rawTransaction, { 59 | // skipPreflight: false 60 | // }) 61 | // console.log("交易签名:", signature) 62 | 63 | // 3. sendEncodedTransaction 64 | // const base64Transaction = rawTransaction.toString('base64'); 65 | // const signature = await connection.sendEncodedTransaction(base64Transaction, { 66 | // skipPreflight: false 67 | // }); 68 | // console.log("交易签名:", signature) 69 | 70 | } 71 | 72 | main(); -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* -------------------------------------------------------------------------------- /04-get/index.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js'; 2 | 3 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 4 | // const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 5 | 6 | async function main() { 7 | 8 | // 1. getSlot 9 | const slot = await connection.getSlot(); 10 | console.log(`当前slot: ${slot}\n`); 11 | 12 | // 2. getBalance 13 | // const balance = await connection.getBalance(new PublicKey("CXPeim1wQMkcTvEHx9QdhgKREYYJD8bnaCCqPRwJ1to1")); 14 | // console.log(`J1to1余额: ${balance / LAMPORTS_PER_SOL} SOL\n`); 15 | 16 | // 3. getTokenAccountBalance 17 | // const tokenAccountBalance = await connection.getTokenAccountBalance(new PublicKey("HGtAdvmncQSk59mAxdh2M7GTUq1aB9WTwh7w7LwvbTBT")); 18 | // console.log(`token账户余额: ${JSON.stringify(tokenAccountBalance)}\n`); 19 | 20 | // 4. getFirstAvailableBlock 21 | // const firstAvailableBlock = await connection.getFirstAvailableBlock(); 22 | // console.log(`首个可用区块: ${firstAvailableBlock}\n`); 23 | 24 | // 5. getLatestBlockhash 25 | // const latestBlockhash = await connection.getLatestBlockhash(); 26 | // console.log(`最新区块哈希: ${latestBlockhash.blockhash}\n`); 27 | 28 | // 6. getParsedAccountInfo 29 | // const parsedAccountInfo = await connection.getParsedAccountInfo(new PublicKey('8sLbNZoA1cfnvMJLPfp98ZLAnFSYCFApfJKMbiXNLwxj'), "confirmed"); 30 | // console.log("已解析的账户信息:", parsedAccountInfo) 31 | 32 | // 7. getParsedTransaction 33 | // const parsedTransaction = await connection.getParsedTransaction('3Vfp5qPhF14bNb2jLtTccabCDbHUmxqtXerUvPEjKb6RpJ8jU3H9M9JgcUbDPtgesB3WFP9M8VZTzECgBavnjxaC', { 34 | // commitment: "confirmed", 35 | // maxSupportedTransactionVersion: 0 36 | // }); 37 | // console.log(`已解析的交易: ${JSON.stringify(parsedTransaction)}\n`); 38 | 39 | // 8. getSignaturesForAddress 40 | // const signatures = await connection.getSignaturesForAddress(new PublicKey("web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2"), { 41 | // limit: 3 42 | // }); 43 | // console.log(`最近的3笔交易签名: ${JSON.stringify(signatures)}\n`); 44 | 45 | // 9. getTokenAccountsByOwner 46 | // const tokenAccountsByOwner = await connection.getTokenAccountsByOwner(new PublicKey("web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2"), { 47 | // mint: new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") 48 | // }, "confirmed"); 49 | // console.log(`token账户: ${JSON.stringify(tokenAccountsByOwner)}\n`); 50 | 51 | // 10. getTokenLargestAccounts 52 | // const tokenLargestAccounts = await connection.getTokenLargestAccounts(new PublicKey("Dp4fXozKtwgK1cL5KQeeNbuAgFpJtY3FbAvL8JrWpump")); 53 | // console.log(`20个token最大持有者账户: ${JSON.stringify(tokenLargestAccounts)}\n`); 54 | 55 | // 11. getTokenSupply 56 | // const supplyInfo = await connection.getTokenSupply(new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v')); 57 | // console.log(`Total supply: ${supplyInfo.value.amount}\n`); 58 | 59 | // 12. getParsedProgramAccounts 60 | // const mintAddress = new PublicKey("Dp4fXozKtwgK1cL5KQeeNbuAgFpJtY3FbAvL8JrWpump") 61 | // const accounts = await connection.getParsedProgramAccounts(new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), { 62 | // filters: [ 63 | // { 64 | // dataSize: 165, // Token 账户的数据大小是 165 字节 65 | // }, 66 | // { 67 | // memcmp: { 68 | // offset: 0, // 0 偏移表示 Token Mint 地址的位置 69 | // bytes: mintAddress.toBase58(), 70 | // }, 71 | // }, 72 | // ], 73 | // }); 74 | // console.log("前3个账户:", accounts.slice(0, 3)) 75 | 76 | } 77 | 78 | main(); -------------------------------------------------------------------------------- /example-04-analysisToken/README.md: -------------------------------------------------------------------------------- 1 | ## 分析token持有者 2 | 3 | 通过 `getParsedProgramAccounts` 查询所有代币账户,然后根据 `mint` 地址过滤出持有该代币的账户。 4 | 5 | 这样就可以构建一个简单的 token 持有者列表,同时对 token 的持仓进行一个分析。 6 | 7 | 分析结果如下: 8 | 9 | ``` 10 | 前10大持有者分析: 11 | ================================================== 12 | 1. 地址: 5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1 13 | 持仓量: 345,412,370.048 14 | 占比: 34.55% 15 | 16 | 2. 地址: D6kyD96Tfkz5fA8JYhWkRPCdJxJ1dAk2ALpchjyUxApu 17 | 持仓量: 35,007,630.972 18 | 占比: 3.50% 19 | 20 | 3. 地址: ALCzpmL4jHVQNooT8PrxjiFDU5YYynVDqLam5FrPgn7b 21 | 持仓量: 33,554,743.151 22 | 占比: 3.36% 23 | 24 | 4. 地址: APUnpSv4HDpNPfed4dPqAnz75REWYbM5vTHaTnApLe1P 25 | 持仓量: 24,850,304.554 26 | 占比: 2.49% 27 | 28 | 5. 地址: 6xBTJa2VzLUnZnSPbvaTNKGYMoxG4Pm3d6B6nsPVbWUU 29 | 持仓量: 23,736,153.23 30 | 占比: 2.37% 31 | 32 | 6. 地址: GR3a6esWvmcE33LqwW1FgQEy7E6Mq8cE2uEWXc4VtcsK 33 | 持仓量: 21,725,403.233 34 | 占比: 2.17% 35 | 36 | 7. 地址: 2dM6wMKS2is3jpwMueXvLfCgwveMiwB1iQNowkjx3x8S 37 | 持仓量: 20,923,943.722 38 | 占比: 2.09% 39 | 40 | 8. 地址: FFL94Rd9ZBorakrH7okwHNMrhiWwK6WT53PazjATW2zg 41 | 持仓量: 18,385,694.794 42 | 占比: 1.84% 43 | 44 | 9. 地址: 5aXGUFvh2v5wFzYAsfMsX7YLEpFw4uPQza9UatRZnL4c 45 | 持仓量: 16,428,619.503 46 | 占比: 1.64% 47 | 48 | 10. 地址: Cw4vsaQ9JpeS4CRn66KmF5oRazo6C7AwMk1BSVYTMjQR 49 | 持仓量: 16,397,086.408 50 | 占比: 1.64% 51 | 52 | 53 | 代币持仓分布分析报告: 54 | ================================================== 55 | 总供应量: 999,837,357.511 56 | 总持有者: 7,101 57 | ================================================== 58 | 59 | 持仓区间: 1-100 60 | 持有者数量: 402 (5.66%) 61 | 持仓总量: 4,136.054 (0.00%) 62 | 平均持仓: 10.29 63 | 主要持有者: 64 | - 4xr8U1TMkKHi7H2BC2dYVv3yXnUkeUphvmauK541SRsX 65 | - 8GbasKKd4yHH83CvS4mCuMEJvLemSrrV7De5hmNQ59Hb 66 | - F9Br3tp1kKqNuXSZgFMvevWocn8YyX5H89g76cCBqiv 67 | - PCnb69Pi1T8DkxMBQahtteRZ9QdC5QbQS44DeUDaDum 68 | - 2a2NqsUWFR8eFaXNkhhuFSpVkTbctGao9ZZvbkLGcrEm 69 | 70 | 持仓区间: 101-1,000 71 | 持有者数量: 83 (1.17%) 72 | 持仓总量: 47,501.135 (0.00%) 73 | 平均持仓: 572.30 74 | 主要持有者: 75 | - 79P6UsqG1Y2cB2xcmKcaCoBn3Gba44ACVgS9xmQFULs7 76 | - C7VDfuACjqoT6rckWusPk8NSy6JmvYgcGmu4w6p7V3yx 77 | - Axd1HqUHxxW1peeGEwNmeckxuNATDJFeeEEMsmkJ6gGn 78 | - AqSR8UrBUG2dsT4LRMF9ceSayXu8oMXxfXc1FZbpHcbp 79 | - DoiUTaHyuEsMFQGanitoSScdEh78AWGpuNe5i1jp3Xdd 80 | 81 | 持仓区间: 1,001-10,000 82 | 持有者数量: 274 (3.86%) 83 | 持仓总量: 1,193,665.167 (0.12%) 84 | 平均持仓: 4356.44 85 | 主要持有者: 86 | - 35tj37dCmiLxTV4cLRwXq6ycEhUqzG25PMaz4akMCHzV 87 | - 9vPVcvWomyDirhif6TeAa1tLPi5DkznFHmNsLHb6BWkf 88 | - 7APv2exT4QyhEHkDyEwY4TJR3pCSQPc7T3P9ZPA6sQNG 89 | - EfB3Q81JNNXuRsHt6gDN8L1CEqCuj5Gxgwgruj8bJdDi 90 | - Ci4s3nrWuhQriwgpEHwB8tUbzsx724bqugo5snHYJR7u 91 | 92 | 持仓区间: 10,001-100,000 93 | 持有者数量: 333 (4.69%) 94 | 持仓总量: 12,212,409.527 (1.22%) 95 | 平均持仓: 36673.90 96 | 主要持有者: 97 | - HKtfKesi64ubxkoBkkDEvgEtXAEmwprtwbxgSxyDQXqN 98 | - An2vTG4AgYTtK7ED9qF6RaLsSmVRoe5QSxrke441wrjN 99 | - HwmkZo36DMvbRjsereuLmGFBdeJPSFKgfVWf6jUZd76Y 100 | - Et1M87kKDLpxMywjVKoFUusBFEW7rQzSKsffTCvgqBxd 101 | - EZgyr2aZESMvgzSfa2JjxjKrRCeYe2uc7YHTLt1eEKMM 102 | 103 | 持仓区间: 100,001-1,000,000 104 | 持有者数量: 209 (2.94%) 105 | 持仓总量: 69,417,348.63 (6.94%) 106 | 平均持仓: 332140.42 107 | 主要持有者: 108 | - 4EKUCa6YCDwjHS5giQ4DJpvdyZ4XoRsmDZNSPwPgScwC 109 | - HkhCAiPabzFmUiHQAASg7rYiHthJ4sqGH5ygHHVhWsRf 110 | - T2ESjZiPmv7Fb2rAWebLpYnyTa8yyVCWaGMot7DpGjG 111 | - EpmSLKSnwZxpXzQkE76woiCTF6UkFmjAsSWpovANVMRy 112 | - 973rVqBCtaExEVGvEoYqyJta6THeXCC9pCRZuPoEUCMc 113 | 114 | 持仓区间: 1,000,000+ 115 | 持有者数量: 83 (1.17%) 116 | 持仓总量: 916,962,296.997 (91.71%) 117 | 平均持仓: 11047738.52 118 | 主要持有者: 119 | - 5Q544fKrFoe6tsEbD7S8EmxGTJYAKtTVhAW5Q5pge4j1 120 | - D6kyD96Tfkz5fA8JYhWkRPCdJxJ1dAk2ALpchjyUxApu 121 | - ALCzpmL4jHVQNooT8PrxjiFDU5YYynVDqLam5FrPgn7b 122 | - APUnpSv4HDpNPfed4dPqAnz75REWYbM5vTHaTnApLe1P 123 | - 6xBTJa2VzLUnZnSPbvaTNKGYMoxG4Pm3d6B6nsPVbWUU 124 | 125 | ``` -------------------------------------------------------------------------------- /05-on/README.md: -------------------------------------------------------------------------------- 1 | # 读取链上数据(二):`on` 订阅 2 | 3 | 本部分将带你认识一些常用的订阅链上数据的方法以及使用场景。 4 | 5 | 与单次的链上数据读取不同,订阅是RPC节点以数据流的方式向客户端推送数据。这些方法同样属于 `Connecion` 类,方法名以 `on` 开头。 6 | 7 | 还是先创建RPC连接: 8 | 9 | ```ts 10 | import { Connection, PublicKey } from '@solana/web3.js'; 11 | 12 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 13 | ``` 14 | 15 | ## 1. onAccountChange 和 onProgramAccountChange 16 | 17 | 用于实时监听特定账户的状态变化。当账户的余额或存储的数据发生变化时,会触发回调函数,并提供更新后的账户信息。 18 | 19 | > 这在钱包监控上会很有用。 20 | 21 | ```ts 22 | // 监听orcACRJYTFjTeo2pV8TfYRTpmqfoYgbVi9GeANXTCc8账户是否发生变化 23 | connection.onAccountChange(new PublicKey("orcACRJYTFjTeo2pV8TfYRTpmqfoYgbVi9GeANXTCc8"), (accountInfo) => { 24 | console.log(`账户变化: ${JSON.stringify(accountInfo)}\n`); 25 | }); 26 | ``` 27 | 28 | ``` 29 | 账户变化: {"lamports":54656348509,"data":{"type":"Buffer","data":[]},"owner":"11111111111111111111111111111111","executable":false,"rentEpoch":18446744073709552000,"space":0} 30 | ``` 31 | 32 | `onProgramAccountChange` 类似,只不过是对程序账户的状态订阅。 33 | 34 | ## 2. onLogs 35 | 36 | 用于实时监听网络上的日志,也可以指定监听某账户下的日志。 37 | 38 | > 这在监听新流动池创建和监控钱包买卖时很有用。 39 | 40 | ```ts 41 | // 监听raydium v4的日志 42 | connection.onLogs(new PublicKey("675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8"), (logs) => { 43 | console.log(`日志: ${JSON.stringify(logs)}\n`); 44 | }); 45 | ``` 46 | 47 | ``` 48 | 日志: {"signature":"5FziCd9SRzEJyXcxRotJqamGdcTfdMKiWB4GPvg2HiWFpzzyvnBGTaV6Vd8KANS85yHjszE61BxFc1gQQgSdATSg","err":null,"logs":["Program ComputeBudget111111111111111111111111111111 invoke [1]","Program ComputeBudget111111111111111111111111111111 success","Program 6rHBDckrDivyp5UarGvCqobhtdfuBf2p7E42zXNbKBGm invoke [1]","Program log: Instruction: Finish","Program 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 invoke [2]","Program log: ray_log: A0cBmjWFAAAAbK8wbAAAAAACAAAAAAAAAEcBmjWFAAAA/2uOg3AUAADPqGwiEQAAAOq+oWwAAAAA","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]","Program log: Instruction: Transfer","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4645 of 101749 compute units","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]","Program log: Instruction: Transfer","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 4736 of 94123 compute units","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success","Program 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 consumed 32059 of 120402 compute units","Program 675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8 success","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]","Program log: Instruction: CloseAccount","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 2916 of 85643 compute units","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [2]","Program log: Instruction: CloseAccount","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 3014 of 78745 compute units","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success","Program 11111111111111111111111111111111 invoke [2]","Program 11111111111111111111111111111111 success","Program 11111111111111111111111111111111 invoke [2]","Program 11111111111111111111111111111111 success","Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL invoke [2]","Program log: Create","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]","Program log: Instruction: GetAccountDataSize","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1569 of 58189 compute units","Program return: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA pQAAAAAAAAA=","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success","Program 11111111111111111111111111111111 invoke [3]","Program 11111111111111111111111111111111 success","Program 11111111111111111111111111111111 invoke [3]","Program 11111111111111111111111111111111 success","Program log: Initialize the associated token account","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]","Program log: Instruction: InitializeImmutableOwner","Program log: Please upgrade to SPL Token 2022 for immutable owner support","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 1405 of 50213 compute units","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA invoke [3]","Program log: Instruction: InitializeAccount3","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA consumed 3158 of 46329 compute units","Program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA success","Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL consumed 22297 of 65164 compute units","Program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL success","Program 6rHBDckrDivyp5UarGvCqobhtdfuBf2p7E42zXNbKBGm consumed 108080 of 149850 compute units","Program 6rHBDckrDivyp5UarGvCqobhtdfuBf2p7E42zXNbKBGm success"]} 49 | ``` -------------------------------------------------------------------------------- /example-04-analysisToken/index.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey, ParsedAccountData } from "@solana/web3.js"; 2 | 3 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 4 | 5 | async function getTokenHolders(mintAddress: string) { 6 | // 1. 参数说明:mintAddress 是代币的 Mint 地址 7 | // 例如:USDC 的 Mint 地址是 "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" 8 | const mint = new PublicKey(mintAddress); 9 | 10 | // 2. 调用 getParsedProgramAccounts 查询所有代币账户 11 | const accounts = await connection.getParsedProgramAccounts( 12 | // Token 程序的地址(固定值) 13 | new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), 14 | { 15 | filters: [ 16 | // Token 账户的大小固定为 165 字节 17 | { dataSize: 165 }, 18 | { 19 | // 在数据开始位置匹配 Mint 地址 20 | memcmp: { 21 | offset: 0, 22 | bytes: mint.toBase58(), 23 | }, 24 | }, 25 | ], 26 | } 27 | ); 28 | 29 | // 3. 处理返回数据 30 | const holders = accounts.map(account => ({ 31 | // 代币账户地址 32 | address: account.pubkey.toString(), 33 | // 代币余额(已考虑小数位) 34 | amount: (account.account.data as ParsedAccountData).parsed.info.tokenAmount.uiAmount, 35 | // 代币账户所有者(钱包地址) 36 | owner: (account.account.data as ParsedAccountData).parsed.info.owner 37 | })); 38 | 39 | // 4. 按持有量从大到小排序 40 | return holders.sort((a, b) => b.amount - a.amount); 41 | } 42 | 43 | interface TokenDistribution { 44 | range: string; 45 | holders: number; 46 | totalAmount: number; 47 | percentage: string; 48 | addresses: string[]; 49 | } 50 | 51 | async function getTokenDistribution(holders: any[]) { 52 | // 计算总供应量 53 | const totalSupply = holders.reduce((sum, h) => sum + Number(h.amount), 0); 54 | 55 | // 定义分布区间(可以根据需要调整) 56 | const ranges = [ 57 | { min: 0, max: 100, label: "1-100" }, 58 | { min: 100, max: 1000, label: "101-1,000" }, 59 | { min: 1000, max: 10000, label: "1,001-10,000" }, 60 | { min: 10000, max: 100000, label: "10,001-100,000" }, 61 | { min: 100000, max: 1000000, label: "100,001-1,000,000" }, 62 | { min: 1000000, max: Infinity, label: "1,000,000+" } 63 | ]; 64 | 65 | // 初始化结果 66 | const distribution: { [key: string]: TokenDistribution } = {}; 67 | ranges.forEach(range => { 68 | distribution[range.label] = { 69 | range: range.label, 70 | holders: 0, 71 | totalAmount: 0, 72 | percentage: "0%", 73 | addresses: [] 74 | }; 75 | }); 76 | 77 | // 统计分布 78 | holders.forEach(holder => { 79 | const amount = Number(holder.amount); 80 | const range = ranges.find(r => amount > r.min && amount <= r.max); 81 | if (range) { 82 | const label = range.label; 83 | distribution[label].holders++; 84 | distribution[label].totalAmount += amount; 85 | distribution[label].addresses.push(holder.owner); 86 | } 87 | }); 88 | 89 | // 计算百分比 90 | Object.values(distribution).forEach(d => { 91 | d.percentage = ((Number(d.totalAmount) / Number(totalSupply)) * 100).toFixed(2) + '%'; 92 | }); 93 | 94 | // 生成报告 95 | console.log("\n代币持仓分布分析报告:"); 96 | console.log("=".repeat(50)); 97 | console.log(`总供应量: ${totalSupply.toLocaleString()}`); 98 | console.log(`总持有者: ${holders.length.toLocaleString()}`); 99 | console.log("=".repeat(50)); 100 | 101 | Object.values(distribution).forEach(d => { 102 | if (d.holders > 0) { 103 | console.log(`\n持仓区间: ${d.range}`); 104 | console.log(`持有者数量: ${d.holders.toLocaleString()} (${((d.holders/holders.length)*100).toFixed(2)}%)`); 105 | console.log(`持仓总量: ${d.totalAmount.toLocaleString()} (${d.percentage})`); 106 | console.log(`平均持仓: ${(Number(d.totalAmount)/Number(d.holders)).toFixed(2)}`); 107 | 108 | // 只显示前5个大户地址 109 | if (d.addresses.length > 0) { 110 | console.log("主要持有者: "); 111 | d.addresses.slice(0, 5).forEach(addr => 112 | console.log(` - ${addr}`) 113 | ); 114 | } 115 | } 116 | }); 117 | 118 | return distribution; 119 | } 120 | 121 | 122 | 123 | async function main() { 124 | const holders = await getTokenHolders("Dp4fXozKtwgK1cL5KQeeNbuAgFpJtY3FbAvL8JrWpump"); 125 | // 计算总供应量 126 | const totalSupply = holders.reduce((sum, h) => sum + Number(h.amount), 0); 127 | 128 | const topHolders = holders.slice(0, 10); // 前10大持有者 129 | console.log("\n前10大持有者分析:"); 130 | console.log("=".repeat(50)); 131 | topHolders.forEach((holder, index) => { 132 | console.log(`${index + 1}. 地址: ${holder.owner}`); 133 | console.log(` 持仓量: ${Number(holder.amount).toLocaleString()}`); 134 | console.log(` 占比: ${((Number(holder.amount) / Number(totalSupply)) * 100).toFixed(2)}%\n`); 135 | }); 136 | 137 | await getTokenDistribution(holders) 138 | } 139 | 140 | main(); 141 | -------------------------------------------------------------------------------- /08-v0/alt.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | PublicKey, 4 | Keypair, 5 | TransactionMessage, 6 | VersionedTransaction, 7 | SystemProgram, 8 | AddressLookupTableProgram 9 | } from '@solana/web3.js'; 10 | import fs from "fs"; 11 | 12 | // 创建RPC连接 13 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 14 | // const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 15 | 16 | // 本地导入钱包 17 | // const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("wallet.json"))); 18 | const secretKey = Uint8Array.from(JSON.parse(fs.readFileSync("web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2.json"))); 19 | const payer = Keypair.fromSecretKey(secretKey); 20 | 21 | async function createALT() { 22 | 23 | // 获取当前slot 24 | const slot = await connection.getSlot("confirmed"); 25 | 26 | // 创建ALT 27 | const [lookupTableInstruction, lookupTableAddress] = 28 | AddressLookupTableProgram.createLookupTable({ 29 | authority: payer.publicKey, 30 | payer: payer.publicKey, 31 | recentSlot: slot, 32 | }); 33 | 34 | console.log("lookup table address:", lookupTableAddress.toBase58()); 35 | 36 | // 创建v0 message 37 | const { blockhash } = await connection.getLatestBlockhash(); 38 | const messageV0 = new TransactionMessage({ 39 | payerKey: payer.publicKey, 40 | recentBlockhash: blockhash, // 最近的区块hash 41 | instructions: [lookupTableInstruction], // 指令数组 42 | }).compileToV0Message(); 43 | 44 | // 创建v0交易并签名 45 | const transaction = new VersionedTransaction(messageV0); 46 | transaction.sign([payer]); 47 | 48 | // 模拟交易 49 | const simulateResult = await connection.simulateTransaction(transaction); 50 | console.log("模拟交易结果: ", simulateResult); 51 | 52 | // 发送交易 53 | const signature = await connection.sendTransaction(transaction); 54 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 55 | } 56 | 57 | async function addAddresses() { 58 | 59 | const lookupTableAddress = new PublicKey('2qqXrZZSG9naivqMyWHHUDRFVNh3YthsTbN5EPU8Poo5') 60 | 61 | // 添加账户到ALT 62 | const extendInstruction = AddressLookupTableProgram.extendLookupTable({ 63 | lookupTable: lookupTableAddress, 64 | payer: payer.publicKey, 65 | authority: payer.publicKey, 66 | addresses: [ 67 | payer.publicKey, 68 | new PublicKey('buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ'), 69 | SystemProgram.programId, // 70 | ], 71 | }); 72 | 73 | // 创建v0 message 74 | const { blockhash } = await connection.getLatestBlockhash(); 75 | const messageV0 = new TransactionMessage({ 76 | payerKey: payer.publicKey, 77 | recentBlockhash: blockhash, // 最近的区块hash 78 | instructions: [extendInstruction], // 指令数组 79 | }).compileToV0Message(); 80 | 81 | // 创建v0交易并签名 82 | const transaction = new VersionedTransaction(messageV0); 83 | transaction.sign([payer]); 84 | 85 | // 模拟交易 86 | const simulateResult = await connection.simulateTransaction(transaction); 87 | console.log("模拟交易结果: ", simulateResult); 88 | 89 | // 发送交易 90 | const signature = await connection.sendTransaction(transaction); 91 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 92 | 93 | } 94 | 95 | async function transfer() { 96 | 97 | const lookupTableAddress = new PublicKey('2qqXrZZSG9naivqMyWHHUDRFVNh3YthsTbN5EPU8Poo5') 98 | 99 | // 获取ALT 100 | const ALT = await connection.getAddressLookupTable(lookupTableAddress); 101 | const lookupTableAccount = ALT.value; 102 | if (!ALT.value) { 103 | throw new Error("lookupTableAccount不存在"); 104 | } 105 | console.log('lookupTableAccount:', lookupTableAccount) 106 | 107 | // 目标地址 108 | const toAddress = new PublicKey('buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ'); 109 | 110 | // 转账指令 111 | const instruction = SystemProgram.transfer({ 112 | fromPubkey: payer.publicKey, 113 | toPubkey: toAddress, 114 | lamports: 1000, // 1000 lamports 115 | }); 116 | 117 | // 创建v0 message 118 | const { blockhash } = await connection.getLatestBlockhash(); 119 | const messageV0 = new TransactionMessage({ 120 | payerKey: payer.publicKey, 121 | recentBlockhash: blockhash, // 最近的区块hash 122 | instructions: [instruction], // 指令数组 123 | }).compileToV0Message([lookupTableAccount]); 124 | 125 | // 创建v0交易并签名 126 | const transaction = new VersionedTransaction(messageV0); 127 | transaction.sign([payer]); 128 | 129 | // 模拟交易 130 | const simulateResult = await connection.simulateTransaction(transaction); 131 | console.log("模拟交易结果: ", simulateResult); 132 | 133 | // 发送交易 134 | const signature = await connection.sendTransaction(transaction); 135 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 136 | 137 | } 138 | 139 | async function parseTx() { 140 | 141 | const parsedTransaction1 = await connection.getParsedTransaction('4LwygRtiF9ZCrbGKoh8MEzmxowaRHPaDc1nsinkv72uXU2cUCuZ8YskBBgsvbBEMZ5Pqpf6C6WcXtCkqAuLZand1', { 142 | commitment: "confirmed", 143 | maxSupportedTransactionVersion: 0 144 | }); 145 | console.log(`已解析的v0交易: ${JSON.stringify(parsedTransaction1)}\n`); 146 | 147 | } 148 | 149 | 150 | // 创建ALT 151 | // createALT(); 152 | 153 | // 为ALT添加账户 154 | // addAddresses(); 155 | 156 | // 使用ALT的转账 157 | // transfer(); 158 | 159 | // 获取解析的v0交易 160 | parseTx(); -------------------------------------------------------------------------------- /07-cu/README.md: -------------------------------------------------------------------------------- 1 | # 添加优先费 2 | 3 | 优先费是在交易的基础费用(5000 Lamports)基础上额外支付的费用。通过为交易添加优先费,可以在区块空间有限的情况下,提高交易被处理的优先级。 4 | 5 | Solana上计算交易费用的最小组成部分为 `计算单元(Compute Units, CU)`,其用于衡量交易的计算资源占用。 6 | 7 | 优先费用的计算方法为 `CU数量*每CU的价格`,此处价格单位为 `microLamports`。 8 | 9 | > 1 Lamports = 10^6 microLamports 10 | 11 | 因此,交易的优先费应分为两部分,分别是 `CU数量` 和 `CU价格`。通过Compute Budget程序指令,可以自定义我们交易的优先费用。 12 | 13 | ## Compute Budget程序指令 14 | 15 | 本部分将讲解设置优先费用的两个方法,分别是 `setComputeUnitPrice` 和 `setComputeUnitLimit`。 16 | 17 | 同样,先创建RPC连接并导入我们的钱包: 18 | 19 | ```ts 20 | import { 21 | Connection, 22 | PublicKey, 23 | Keypair, 24 | Transaction, 25 | SystemProgram, 26 | ComputeBudgetProgram, 27 | sendAndConfirmTransaction 28 | } from '@solana/web3.js'; 29 | import fs from "fs"; 30 | 31 | // 创建RPC连接 32 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 33 | ``` 34 | 35 | ### setComputeUnitPrice 36 | 37 | 默认情况下,交易CU数量分配为 `200000*指令数量`。 38 | 39 | > 注意若只设置setComputeUnitPrice,此setComputeUnitPrice指令不会被计算在此处的指令数量内。 40 | 41 | 通过setComputeUnitPrice设置每CU的价格为5 microLamports,可以看到[这条仅包含1个转账指令的交易](https://solscan.io/tx/34eohoyTp2oZ1jtFNtcEUp9oe2QfRf5HRarexCbKnUm93ga3sGjP8Aduwd8xcbRrZk9HNdRJ9rqWZ8peGhruPfuK)优先费用为0.000000001 SOL,即 `200000*1*5 = 1 Lamports`。 42 | 43 | ```ts 44 | async function main() { 45 | 46 | // 创建交易 47 | const transaction = new Transaction(); 48 | 49 | // CU价格 50 | const computeUnitPriceInstruction = ComputeBudgetProgram.setComputeUnitPrice({ 51 | microLamports: 5 52 | }); 53 | transaction.add(computeUnitPriceInstruction); 54 | 55 | // 目标地址 56 | const toAddress = new PublicKey('buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ'); 57 | 58 | // 添加转账指令 59 | const instruction1 = SystemProgram.transfer({ 60 | fromPubkey: fromWallet.publicKey, 61 | toPubkey: toAddress, 62 | lamports: 1000, // 1000 lamports 63 | }); 64 | transaction.add(instruction1); 65 | 66 | // 模拟交易 67 | const simulateResult = await connection.simulateTransaction(transaction, [fromWallet]); 68 | console.log("模拟交易结果: ", simulateResult); 69 | 70 | // 发送交易 71 | const signature = await sendAndConfirmTransaction(connection, transaction, [fromWallet]); 72 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 73 | } 74 | 75 | main(); 76 | ``` 77 | 78 | 通过 `npx esrun 07-cu/index.ts `运行,输出应如下: 79 | 80 | ``` 81 | 模拟交易结果: { 82 | context: { apiVersion: '2.0.3', slot: 301579053 }, 83 | value: { 84 | accounts: null, 85 | err: null, 86 | innerInstructions: null, 87 | logs: [ 88 | 'Program ComputeBudget111111111111111111111111111111 invoke [1]', 89 | 'Program ComputeBudget111111111111111111111111111111 success', 90 | 'Program 11111111111111111111111111111111 invoke [1]', 91 | 'Program 11111111111111111111111111111111 success' 92 | ], 93 | replacementBlockhash: null, 94 | returnData: null, 95 | unitsConsumed: 300 96 | } 97 | } 98 | 交易已发送: https://solscan.io/tx/34eohoyTp2oZ1jtFNtcEUp9oe2QfRf5HRarexCbKnUm93ga3sGjP8Aduwd8xcbRrZk9HNdRJ9rqWZ8peGhruPfuK 99 | ``` 100 | 101 | 以下为其他的几个交易例子: 102 | 103 | - 每CU的价格为1 microLamports,1条转账指令。因不足1 Lamports,被按照1 Lamports处理:[优先费为1 Lamports](https://solscan.io/tx/5WxZ9uST4Raz3fyCJyodLcx3Ruyy2JYPvJa7kD28ehn9VquPkV6jm6pzAGFGt8c1fYF7yhFpptTTJCX6PYsJr8i9) 104 | - 每CU的价格为25 microLamports,1条转账指令:[优先费为5 Lamports](https://solscan.io/tx/EWgoUVMGLNEzAoGiY79NWzhzFHFk8bw5ivGXBE82afsCL5E3o71jvVQKFPS3f1NvggdMQGc6naWHLphFS2oqaYX) 105 | - 每CU的价格为5 microLamports,2条转账指令:[优先费为2 Lamports](https://solscan.io/tx/4SbD6b1aFG4fXErzFcGzx6xr3RXFVB66Xm2yXwe2PP4qsmzRQKfzeibeJuWPtrEccnsky2MW9wm3UtjrRfJL1YsE) 106 | 107 | ### setComputeUnitLimit 108 | 109 | 实际上,简单转账指令的计算资源消耗仅为150个CU,而默认的每条指令CU的数量为200000,这往往会导致优先费用的额外支出。 110 | 111 | 因此,可以通过 `setComputeUnitLimit` 来为我们的交易确定CU上限。 112 | 113 | > 每条交易的计算预算CU数量最大上限为 `1400000`。 114 | 115 | 因优先费不足1 Lamports会被按照1 Lamports处理,此处为了推算,取CU上限为500(实际3条指令花费为450)。若取CU价格为4000 microLamports,优先费应为2 Lamports,[此处可查看这条交易](https://solscan.io/tx/41APCjw2ifHkqV3Ha7S7Q1LADb9S396acsCqErdk6Tp3xcCMLge1FueRnj4dfTSbhgeA9DBvfPNRJxoZuYEqCQUS)。 116 | 117 | ```ts 118 | async function main() { 119 | 120 | // 创建交易 121 | const transaction = new Transaction(); 122 | 123 | // CU价格 124 | const computeUnitPriceInstruction = ComputeBudgetProgram.setComputeUnitPrice({ 125 | microLamports: 4000 126 | }); 127 | transaction.add(computeUnitPriceInstruction); 128 | 129 | // CU数量 130 | const computeUnitLimitInstruction = ComputeBudgetProgram.setComputeUnitLimit({ 131 | units: 500, 132 | }); 133 | transaction.add(computeUnitLimitInstruction); 134 | 135 | // 目标地址 136 | const toAddress = new PublicKey('buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ'); 137 | 138 | // 添加转账指令 139 | const instruction1 = SystemProgram.transfer({ 140 | fromPubkey: fromWallet.publicKey, 141 | toPubkey: toAddress, 142 | lamports: 1000, // 1000 lamports 143 | }); 144 | transaction.add(instruction1); 145 | 146 | // 模拟交易 147 | const simulateResult = await connection.simulateTransaction(transaction, [fromWallet]); 148 | console.log("模拟交易结果: ", simulateResult); 149 | 150 | // 发送交易 151 | const signature = await sendAndConfirmTransaction(connection, transaction, [fromWallet]); 152 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 153 | } 154 | 155 | main(); 156 | ``` 157 | 158 | 再次运行,输出应如下: 159 | 160 | ``` 161 | 模拟交易结果: { 162 | context: { apiVersion: '2.0.3', slot: 301580071 }, 163 | value: { 164 | accounts: null, 165 | err: null, 166 | innerInstructions: null, 167 | logs: [ 168 | 'Program ComputeBudget111111111111111111111111111111 invoke [1]', 169 | 'Program ComputeBudget111111111111111111111111111111 success', 170 | 'Program ComputeBudget111111111111111111111111111111 invoke [1]', 171 | 'Program ComputeBudget111111111111111111111111111111 success', 172 | 'Program 11111111111111111111111111111111 invoke [1]', 173 | 'Program 11111111111111111111111111111111 success' 174 | ], 175 | replacementBlockhash: null, 176 | returnData: null, 177 | unitsConsumed: 450 178 | } 179 | } 180 | 交易已发送: https://solscan.io/tx/41APCjw2ifHkqV3Ha7S7Q1LADb9S396acsCqErdk6Tp3xcCMLge1FueRnj4dfTSbhgeA9DBvfPNRJxoZuYEqCQUS 181 | ``` 182 | 183 | --- 184 | 185 | ## 总结 186 | 187 | 通过 `setComputeUnitPrice` 和 `setComputeUnitLimit` 指令,可以帮助我们在实际应用中灵活的定义自己的优先费。一般情况下,需要对自己的交易CU数量占用有所预估,固定此值,来灵活的调整CU价格参数,以保证自己的交易优先费随网络优先费水平动态的变化。 -------------------------------------------------------------------------------- /04-get/README.md: -------------------------------------------------------------------------------- 1 | # 读取链上数据(一):`get` 读取 2 | 3 | 本部分将带你认识一些常用的读取链上数据的方法以及使用场景。 4 | 5 | 这些方法属于 `Connecion` 类,为单次的链上数据读取,方法名以 `get` 开头。 6 | 7 | 在使用这些方法之前还是需要先创建RPC连接。 8 | 9 | ```ts 10 | import { Connection, PublicKey, LAMPORTS_PER_SOL } from '@solana/web3.js'; 11 | 12 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 13 | ``` 14 | 15 | ## 1. getSlot 16 | 17 | 获取当前的slot。 18 | 19 | ```ts 20 | const slot = await connection.getSlot(); 21 | console.log(`当前slot: ${slot}\n`); 22 | ``` 23 | 24 | ``` 25 | 当前slot: 300579039 26 | ``` 27 | 28 | ## 2. getBalance 29 | 30 | 获取指定账户的SOL余额。 31 | 32 | ```ts 33 | // 查询Jito1的SOL余额 34 | const balance = await connection.getBalance(new PublicKey("CXPeim1wQMkcTvEHx9QdhgKREYYJD8bnaCCqPRwJ1to1")); 35 | console.log(`J1to1余额: ${balance / LAMPORTS_PER_SOL} SOL\n`); 36 | ``` 37 | 38 | ``` 39 | J1to1余额: 12.172897148 SOL 40 | ``` 41 | 42 | ## 3. getTokenAccountBalance 43 | 44 | 查询指定代币账户的余额。 45 | 46 | > Solana 会为每个代币持有者分配一个代币账户,每个账户需要支付0.002SOL的租金。当你不再需要此代币账户时,可以选择关闭并取回租金。 47 | 48 | ```ts 49 | // 获取web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2账户的USDC余额 50 | // https://solscan.io/account/HGtAdvmncQSk59mAxdh2M7GTUq1aB9WTwh7w7LwvbTBT 51 | const tokenAccountBalance = await connection.getTokenAccountBalance(new PublicKey("HGtAdvmncQSk59mAxdh2M7GTUq1aB9WTwh7w7LwvbTBT")); 52 | console.log(`token账户余额: ${JSON.stringify(tokenAccountBalance)}\n`); 53 | ``` 54 | 55 | ``` 56 | token账户余额: {"context":{"apiVersion":"2.0.3","slot":300580444},"value":{"amount":"2000000","decimals":6,"uiAmount":2,"uiAmountString":"2"}} 57 | ``` 58 | 59 | ## 4. getFirstAvailableBlock 60 | 61 | 获取当前RPC节点可以访问的最早区块号。 62 | 63 | > 因Solana数据量庞大,一般的RPC节点无法完整存储区块数据,只能在本地保存一小部分快照。因此,如果你想分析区块的历史交易,购买大型RPC服务商的节点是个不错的选择。 64 | 65 | ```ts 66 | const firstAvailableBlock = await connection.getFirstAvailableBlock(); 67 | console.log(`首个可用区块: ${firstAvailableBlock}\n`); 68 | ``` 69 | 70 | ``` 71 | 首个可用区块: 300439300 72 | ``` 73 | 74 | ## 5. getLatestBlockhash 75 | 76 | 获取最新的区块hash。 77 | 78 | > 在发送交易时经常调用。 79 | 80 | ```ts 81 | const latestBlockhash = await connection.getLatestBlockhash(); 82 | console.log(`最新区块哈希: ${latestBlockhash.blockhash}\n`); 83 | ``` 84 | 85 | ``` 86 | 最新区块哈希: Hik7iYgKiALmPXp8HTAqok3kDQuSYWCaPLNa7rLNeu6v 87 | ``` 88 | 89 | ## 6. getParsedAccountInfo 90 | 91 | 获取已解析的账户详细信息。 92 | 93 | > 在获取链上流动池信息时会非常有用。 94 | 95 | `getMultipleParsedAccounts` 可以一次性查询多个账户信息。 96 | 97 | ```ts 98 | // 获取raydium clmm WSOL/USDC 流动池的账户信息 99 | const accountInfo = await connection.getAccountInfo(new PublicKey('8sLbNZoA1cfnvMJLPfp98ZLAnFSYCFApfJKMbiXNLwxj'), "confirmed"); 100 | console.log("账户信息:", accountInfo) 101 | ``` 102 | 103 | ``` 104 | { 105 | data: , 106 | executable: false, 107 | lamports: 865597210, 108 | owner: PublicKey [PublicKey(CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK)] { 109 | _bn: 110 | }, 111 | rentEpoch: 18446744073709552000, 112 | space: 1544 113 | } 114 | ``` 115 | 116 | 117 | ## 7. getParsedTransaction 118 | 119 | 获取已解析的交易详细信息。 120 | 121 | `getParsedTransactions` 可一次性查询多条交易。 122 | 123 | > 在需要解析交易的监听场景下会非常有用。 124 | 125 | ```ts 126 | // 解析一笔转账SOL交易 127 | const parsedTransaction = await connection.getParsedTransaction('3Vfp5qPhF14bNb2jLtTccabCDbHUmxqtXerUvPEjKb6RpJ8jU3H9M9JgcUbDPtgesB3WFP9M8VZTzECgBavnjxaC', { 128 | commitment: "confirmed", 129 | maxSupportedTransactionVersion: 0 130 | }); 131 | console.log(`已解析的交易: ${JSON.stringify(parsedTransaction)}\n`); 132 | ``` 133 | 134 | ``` 135 | 已解析的交易: {"blockTime":1731232782,"meta":{"computeUnitsConsumed":150,"err":null,"fee":5000,"innerInstructions":[],"logMessages":["Program 11111111111111111111111111111111 invoke [1]","Program 11111111111111111111111111111111 success"],"postBalances":[7826454,1993200,1],"postTokenBalances":[],"preBalances":[7832454,1992200,1],"preTokenBalances":[],"rewards":[],"status":{"Ok":null}},"slot":300547625,"transaction":{"message":{"accountKeys":[{"pubkey":"web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2","signer":true,"source":"transaction","writable":true},{"pubkey":"buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ","signer":false,"source":"transaction","writable":true},{"pubkey":"11111111111111111111111111111111","signer":false,"source":"transaction","writable":false}],"instructions":[{"parsed":{"info":{"destination":"buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ","lamports":1000,"source":"web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2"},"type":"transfer"},"program":"system","programId":"11111111111111111111111111111111","stackHeight":null}],"recentBlockhash":"7F3ptA9dwyosGYK2RMZneutNEfc6PruonnZcqVH35wyG"},"signatures":["3Vfp5qPhF14bNb2jLtTccabCDbHUmxqtXerUvPEjKb6RpJ8jU3H9M9JgcUbDPtgesB3WFP9M8VZTzECgBavnjxaC"]},"version":"legacy"} 136 | ``` 137 | 138 | ## 8. getSignaturesForAddress 139 | 140 | 获取与指定账户地址相关的交易签名列表。 141 | 142 | > 这在监听账户时会非常有用。 143 | 144 | ```ts 145 | // 获取web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2账户下最新的3笔交易 146 | const signatures = await connection.getSignaturesForAddress(new PublicKey("web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2"), { 147 | limit: 3 148 | }); 149 | console.log(`最近的3笔交易签名: ${JSON.stringify(signatures)}\n`); 150 | ``` 151 | 152 | ``` 153 | 最近的3笔交易签名: [{"blockTime":1731241155,"confirmationStatus":"finalized","err":null,"memo":null,"signature":"5sFePYo4zAX2uiGmt1LmDBfoYDxXGhiix1of8K3DPD3Kua4fGStbMtKWvyUc1u1fdtrVS8DM51pgA9Us9GsaDRjm","slot":300566648},{"blockTime":1731232782,"confirmationStatus":"finalized","err":null,"memo":null,"signature":"3Vfp5qPhF14bNb2jLtTccabCDbHUmxqtXerUvPEjKb6RpJ8jU3H9M9JgcUbDPtgesB3WFP9M8VZTzECgBavnjxaC","slot":300547625},{"blockTime":1731232657,"confirmationStatus":"finalized","err":null,"memo":null,"signature":"4aZJwh2srekTB3w7VzF91rNv7oB1ZPMGwds1ohnivejHMUdSw7Eacp5kLkcChJuU2MmjewrusVNbHa2aCpjwTy6M","slot":300547340}] 154 | ``` 155 | 156 | 157 | ## 9. getTokenAccountsByOwner 158 | 159 | 用于查询某个账户下所有的代币账户。 160 | 161 | ```ts 162 | const tokenAccountsByOwner = await connection.getTokenAccountsByOwner(new PublicKey("web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2"), { 163 | mint: new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v") 164 | }, "confirmed"); 165 | console.log(`token账户: ${JSON.stringify(tokenAccountsByOwner)}\n`); 166 | ``` 167 | 168 | ``` 169 | token账户: {"context":{"apiVersion":"2.0.3","slot":300580814},"value":[{"account":{"data":{"type":"Buffer","data":[198,250,122,243,190,219,173,58,61,101,243,106,171,201,116,49,177,187,228,194,210,246,224,228,124,166,2,3,69,47,93,97,13,255,221,17,24,19,199,35,78,149,150,80,234,75,209,227,110,41,100,154,108,37,103,158,230,205,202,31,47,49,116,137,128,132,30,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]},"executable":false,"lamports":2039280,"owner":"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA","rentEpoch":18446744073709552000,"space":165},"pubkey":"HGtAdvmncQSk59mAxdh2M7GTUq1aB9WTwh7w7LwvbTBT"}]} 170 | ``` 171 | 172 | ## 10. getTokenLargestAccounts 173 | 174 | 查询某个代币的20个最大持有者账户。 175 | 176 | ```ts 177 | // 获取代币mint地址为Dp4fXozKtwgK1cL5KQeeNbuAgFpJtY3FbAvL8JrWpump的前20持有者 178 | const tokenLargestAccounts = await connection.getTokenLargestAccounts(new PublicKey("Dp4fXozKtwgK1cL5KQeeNbuAgFpJtY3FbAvL8JrWpump")); 179 | console.log(`20个token最大持有者账户: ${JSON.stringify(tokenLargestAccounts)}\n`); 180 | ``` 181 | 182 | ``` 183 | 20个token最大持有者账户: {"context":{"apiVersion":"2.0.3","slot":300581319},"value":[{"address":"5hWv7FkSbyjyMNxKutWkA41azBFLkZNv3seLhZMeVR9f","amount":"152069454264255","decimals":6,"uiAmount":152069454.264255,"uiAmountString":"152069454.264255"},{"address":"EREWvGJSLVZ7cYqR9UBHags8Nu69UJWJTyUs5x1PxSVu","amount":"33554743151407","decimals":6,"uiAmount":33554743.151407,"uiAmountString":"33554743.151407"},{"address":"CsZcHJ9PgteaQfcNAsJhveM97THJ1erJYDCKxPv7zyoJ","amount":"23722304864271","decimals":6,"uiAmount":23722304.864271,"uiAmountString":"23722304.864271"},{"address":"BeJNuqkM7fLTQ9ayXxmRR2HcxwCtYdE5A83pDfkoK2ac","amount":"20153660767179","decimals":6,"uiAmount":20153660.767179,"uiAmountString":"20153660.767179"},{"address":"ECq3rtYeuGqjRYB5mnAQVkmnxEEhqZrCsj9iDXMLzMas","amount":"19542872794233","decimals":6,"uiAmount":19542872.794233,"uiAmountString":"19542872.794233"},{"address":"3eQwYYPvPRNT95ijnV7MhiuDf3UvFppgmbUfzGr3swGy","amount":"18960282274790","decimals":6,"uiAmount":18960282.27479,"uiAmountString":"18960282.27479"},{"address":"A9X8AbYgRUDgv76oJb3owJ9KZNQyMHYYoBUAcXYTQb4X","amount":"18385694793997","decimals":6,"uiAmount":18385694.793997,"uiAmountString":"18385694.793997"},{"address":"3tBpHjkbA2iPgLiynkh8aocx25cZw5giDsKgBMt18FQY","amount":"16192533170352","decimals":6,"uiAmount":16192533.170352,"uiAmountString":"16192533.170352"},{"address":"CKzAxaWfCvN2E2gsHZuh9ahkFURmWgrjcJmQJfS39JXw","amount":"15336629610352","decimals":6,"uiAmount":15336629.610352,"uiAmountString":"15336629.610352"},{"address":"AkRDFNqBny8QSWrm4hVHGz76AANHBYUySJ2FMMPJgFvc","amount":"14313037432834","decimals":6,"uiAmount":14313037.432834,"uiAmountString":"14313037.432834"},{"address":"7o9C3KFMinhVyCpAL18mjvWUAyNWECgfugoDEdEgVS3r","amount":"14278373348178","decimals":6,"uiAmount":14278373.348178,"uiAmountString":"14278373.348178"},{"address":"AgY1NbsCMaon6hjfcBMQjSaRyw8sUZNaoDht6H7Zw6GT","amount":"13601918495029","decimals":6,"uiAmount":13601918.495029,"uiAmountString":"13601918.495029"},{"address":"H1Qm7UNCdfhrbmjzzSKBN28xScZoBrU4CiTrQLfZwnTN","amount":"13212871578892","decimals":6,"uiAmount":13212871.578892,"uiAmountString":"13212871.578892"},{"address":"CLRVbDx6QkcSCGAkMSfmQuTFqpZun3tFRZHoCd7GV2MP","amount":"13045037329632","decimals":6,"uiAmount":13045037.329632,"uiAmountString":"13045037.329632"},{"address":"F6f91snaYJvioLtx5ESqKZTLxNuZn3erv78yBDvkP3kH","amount":"12234592572102","decimals":6,"uiAmount":12234592.572102,"uiAmountString":"12234592.572102"},{"address":"8D9v7JRy8uVLiqFcxQJjmPhdFttJcX6E5Kmn4WDFV3tM","amount":"11847710475369","decimals":6,"uiAmount":11847710.475369,"uiAmountString":"11847710.475369"},{"address":"Dvi82ZRJjey2eXV27U2Y8BGPbEkfq3B6t7pLsLCA4Ajh","amount":"11234459181997","decimals":6,"uiAmount":11234459.181997,"uiAmountString":"11234459.181997"},{"address":"HT1nud5TfKgnr2eG1bRB6eutRRNrHTS5uez4fFxL9txo","amount":"10980211007652","decimals":6,"uiAmount":10980211.007652,"uiAmountString":"10980211.007652"},{"address":"DrGFmy45YwcbUTZFn2XrKvVUTBbBY1jjGfMKGLFEkSmK","amount":"10536406834949","decimals":6,"uiAmount":10536406.834949,"uiAmountString":"10536406.834949"},{"address":"EkPJUGTRxEsDeLU7LbizUUgnA19GPzerUjLvEGAc9Zbi","amount":"10460309988199","decimals":6,"uiAmount":10460309.988199,"uiAmountString":"10460309.988199"}]} 184 | ``` 185 | 186 | ## 11. getTokenSupply 187 | 188 | 获取代币的供应量。 189 | 190 | ```ts 191 | // 获取USDC的供应量 192 | const supplyInfo = await connection.getTokenSupply(new PublicKey('EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v')); 193 | console.log(`Total supply: ${supplyInfo.value.amount}\n`); 194 | ``` 195 | 196 | ``` 197 | Total supply: 3277049395067962 198 | ``` 199 | 200 | ## 12. getParsedProgramAccounts 201 | 202 | 批量获取某个程序账户下的所有账户信息。 203 | 204 | > 在获取某个代币的所有持有者时会非常有用。 205 | 206 | ```ts 207 | // 获取代币mint地址为Dp4fXozKtwgK1cL5KQeeNbuAgFpJtY3FbAvL8JrWpump的所有持有者 208 | const mintAddress = new PublicKey("Dp4fXozKtwgK1cL5KQeeNbuAgFpJtY3FbAvL8JrWpump") 209 | const accounts = await connection.getParsedProgramAccounts(new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'), { 210 | filters: [ 211 | { 212 | dataSize: 165, // Token 账户的数据大小是 165 字节 213 | }, 214 | { 215 | memcmp: { 216 | offset: 0, // 0 偏移表示 Token Mint 地址的位置 217 | bytes: mintAddress.toBase58(), 218 | }, 219 | }, 220 | ], 221 | }); 222 | 223 | // 只打印3个持有者 224 | console.log("前3个账户:", accounts.slice(0, 3)) 225 | 226 | ``` 227 | 228 | ``` 229 | 前3个账户: [ 230 | { 231 | account: { 232 | data: [Object], 233 | executable: false, 234 | lamports: 2039280, 235 | owner: [PublicKey [PublicKey(TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)]], 236 | rentEpoch: 18446744073709552000, 237 | space: 165 238 | }, 239 | pubkey: PublicKey [PublicKey(Avd9odZRLXLJTofuGSwPnUjTweniq7hae1fiyqeKMMiG)] { 240 | _bn: 241 | } 242 | }, 243 | { 244 | account: { 245 | data: [Object], 246 | executable: false, 247 | lamports: 2039280, 248 | owner: [PublicKey [PublicKey(TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)]], 249 | rentEpoch: 18446744073709552000, 250 | space: 165 251 | }, 252 | pubkey: PublicKey [PublicKey(53P2PjAqKcVm7iE2ZKGF5BDWSxHY7EPvhQw7iTvMYjWn)] { 253 | _bn: 254 | } 255 | }, 256 | { 257 | account: { 258 | data: [Object], 259 | executable: false, 260 | lamports: 2039280, 261 | owner: [PublicKey [PublicKey(TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA)]], 262 | rentEpoch: 18446744073709552000, 263 | space: 165 264 | }, 265 | pubkey: PublicKey [PublicKey(7EyFQhPg4S61U1FHnbL7BK1pnhFFyGLGXc6C99RGJCei)] { 266 | _bn: 267 | } 268 | } 269 | ] 270 | ``` -------------------------------------------------------------------------------- /08-v0/README.md: -------------------------------------------------------------------------------- 1 | # `v0` 交易 2 | 3 | 版本化交易(Versioned Transactions)或称 `v0` 交易是Solana更新过程中引入的一种新的交易类型。因传统(legacy)交易账户数量的限制,`v0` 交易中引入了地址查找表(Address Lookup Tables)功能,用于压缩账户数量,将账户数量限制由35个提升到了64个。 4 | 5 | > `v0` 交易在链上套利场景中被广泛应用。 6 | 7 | 本节将讲解如何创建`v0` 交易和如何使用地址查找表。 8 | 9 | ## 创建`v0` 交易 10 | 11 | 以下是创建 `v0`交易的示例。 12 | 13 | > 如无特殊要求,推荐所有交易都使用 `v0` 类型。 14 | 15 | ```ts 16 | import { 17 | Connection, 18 | PublicKey, 19 | Keypair, 20 | TransactionMessage, 21 | VersionedTransaction, 22 | SystemProgram, 23 | } from '@solana/web3.js'; 24 | import fs from "fs"; 25 | 26 | // 创建RPC连接 27 | const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 28 | 29 | // 本地导入钱包 30 | // const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("wallet.json"))); 31 | const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2.json"))); 32 | const fromWallet = Keypair.fromSecretKey(fromSecretKey); 33 | ``` 34 | 35 | ```ts 36 | async function main() { 37 | 38 | // 目标地址 39 | const toAddress = new PublicKey('buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ'); 40 | 41 | // 转账指令 42 | const instruction = SystemProgram.transfer({ 43 | fromPubkey: fromWallet.publicKey, 44 | toPubkey: toAddress, 45 | lamports: 1000, // 1000 lamports 46 | }); 47 | 48 | // 创建v0 message 49 | const { blockhash } = await connection.getLatestBlockhash(); 50 | const messageV0 = new TransactionMessage({ 51 | payerKey: fromWallet.publicKey, 52 | recentBlockhash: blockhash, // 最近的区块hash 53 | instructions: [instruction], // 指令数组 54 | }).compileToV0Message(); 55 | 56 | // 创建v0交易并签名 57 | const transaction = new VersionedTransaction(messageV0); 58 | transaction.sign([fromWallet]); 59 | 60 | // 模拟交易 61 | const simulateResult = await connection.simulateTransaction(transaction); 62 | console.log("模拟交易结果: ", simulateResult); 63 | 64 | // 发送交易 65 | const signature = await connection.sendTransaction(transaction); 66 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 67 | } 68 | 69 | main(); 70 | ``` 71 | 72 | 通过运行 `npx esrun 08-v0/index.ts`,输出如下: 73 | 74 | ``` 75 | 模拟交易结果: { 76 | context: { apiVersion: '2.0.3', slot: 301586586 }, 77 | value: { 78 | accounts: null, 79 | err: null, 80 | innerInstructions: null, 81 | logs: [ 82 | 'Program 11111111111111111111111111111111 invoke [1]', 83 | 'Program 11111111111111111111111111111111 success' 84 | ], 85 | replacementBlockhash: null, 86 | returnData: null, 87 | unitsConsumed: 150 88 | } 89 | } 90 | 交易已发送: https://solscan.io/tx/5BLjkVYjkLHa7rz7616r6Nx4fbMYe4Y3mBj6ucihrB7hXmjfe59V16MHfPsVYhECZs8qBU6n39kzxLQgm89pQ8k1 91 | ``` 92 | 93 | 区块链浏览器中可以[查看此交易](https://solscan.io/tx/5BLjkVYjkLHa7rz7616r6Nx4fbMYe4Y3mBj6ucihrB7hXmjfe59V16MHfPsVYhECZs8qBU6n39kzxLQgm89pQ8k1)的版本。 94 | 95 | ![](../img/08-01.png) 96 | 97 | ## 地址查找表 98 | 99 | 通过与 `AddressLookupTableProgram` 进行交互,可以创建自己的地址查找表并在交易中引入自己的查找表。 100 | 101 | ### 创建ALT 102 | 103 | ```ts 104 | import { 105 | Connection, 106 | PublicKey, 107 | Keypair, 108 | TransactionMessage, 109 | VersionedTransaction, 110 | SystemProgram, 111 | AddressLookupTableProgram 112 | } from '@solana/web3.js'; 113 | import fs from "fs"; 114 | 115 | // 创建RPC连接 116 | // const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed"); 117 | // const connection = new Connection("https://mainnet-ams.chainbuff.com", "confirmed"); 118 | const connection = new Connection("https://chrissy-w0sbco-fast-mainnet.helius-rpc.com", "confirmed"); 119 | 120 | // 本地导入钱包 121 | // const fromSecretKey = Uint8Array.from(JSON.parse(fs.readFileSync("wallet.json"))); 122 | const secretKey = Uint8Array.from(JSON.parse(fs.readFileSync("web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2.json"))); 123 | const payer = Keypair.fromSecretKey(secretKey); 124 | ``` 125 | 126 | ```ts 127 | async function createALT() { 128 | 129 | // 获取当前slot 130 | const slot = await connection.getSlot("confirmed"); 131 | 132 | // 创建ALT 133 | const [lookupTableInstruction, lookupTableAddress] = 134 | AddressLookupTableProgram.createLookupTable({ 135 | authority: payer.publicKey, 136 | payer: payer.publicKey, 137 | recentSlot: slot, 138 | }); 139 | 140 | console.log("lookup table address:", lookupTableAddress.toBase58()); 141 | 142 | // 创建v0 message 143 | const { blockhash } = await connection.getLatestBlockhash(); 144 | const messageV0 = new TransactionMessage({ 145 | payerKey: payer.publicKey, 146 | recentBlockhash: blockhash, // 最近的区块hash 147 | instructions: [lookupTableInstruction], // 指令数组 148 | }).compileToV0Message(); 149 | 150 | // 创建v0交易并签名 151 | const transaction = new VersionedTransaction(messageV0); 152 | transaction.sign([payer]); 153 | 154 | // 模拟交易 155 | const simulateResult = await connection.simulateTransaction(transaction); 156 | console.log("模拟交易结果: ", simulateResult); 157 | 158 | // 发送交易 159 | const signature = await connection.sendTransaction(transaction); 160 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 161 | } 162 | 163 | // 创建ALT 164 | createALT(); 165 | ``` 166 | 167 | 通过 `npx esrun 08-v0/alt.ts` 运行,输出如下: 168 | 169 | ``` 170 | ALT账户地址 2qqXrZZSG9naivqMyWHHUDRFVNh3YthsTbN5EPU8Poo5 171 | 模拟交易结果: { 172 | context: { apiVersion: '2.0.3', slot: 301694685 }, 173 | value: { 174 | accounts: null, 175 | err: null, 176 | innerInstructions: null, 177 | logs: [ 178 | 'Program AddressLookupTab1e1111111111111111111111111 invoke [1]', 179 | 'Program 11111111111111111111111111111111 invoke [2]', 180 | 'Program 11111111111111111111111111111111 success', 181 | 'Program 11111111111111111111111111111111 invoke [2]', 182 | 'Program 11111111111111111111111111111111 success', 183 | 'Program 11111111111111111111111111111111 invoke [2]', 184 | 'Program 11111111111111111111111111111111 success', 185 | 'Program AddressLookupTab1e1111111111111111111111111 success' 186 | ], 187 | replacementBlockhash: null, 188 | returnData: null, 189 | unitsConsumed: 1200 190 | } 191 | } 192 | 交易已发送: https://solscan.io/tx/5jeF2fY2B83ETueuzcdF5bjXpB949gW9FxMEietnpCDgW7LFgsKFxcnLDYxcE1RDBKcPjMYw3sJQLKv2TxjPajCT 193 | ``` 194 | 195 | 这样我们就创建了一个自己的地址查找表,其地址为 `2qqXrZZSG9naivqMyWHHUDRFVNh3YthsTbN5EPU8Poo5`。 196 | 197 | ### 添加账户地址到ALT 198 | 199 | ```ts 200 | async function addAddresses() { 201 | 202 | const lookupTableAddress = new PublicKey('2qqXrZZSG9naivqMyWHHUDRFVNh3YthsTbN5EPU8Poo5') 203 | 204 | // 添加账户到ALT 205 | const extendInstruction = AddressLookupTableProgram.extendLookupTable({ 206 | lookupTable: lookupTableAddress, 207 | payer: payer.publicKey, 208 | authority: payer.publicKey, 209 | addresses: [ 210 | payer.publicKey, 211 | new PublicKey('buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ'), 212 | SystemProgram.programId, // 213 | ], 214 | }); 215 | 216 | // 创建v0 message 217 | const { blockhash } = await connection.getLatestBlockhash(); 218 | const messageV0 = new TransactionMessage({ 219 | payerKey: payer.publicKey, 220 | recentBlockhash: blockhash, // 最近的区块hash 221 | instructions: [extendInstruction], // 指令数组 222 | }).compileToV0Message(); 223 | 224 | // 创建v0交易并签名 225 | const transaction = new VersionedTransaction(messageV0); 226 | transaction.sign([payer]); 227 | 228 | // 模拟交易 229 | const simulateResult = await connection.simulateTransaction(transaction); 230 | console.log("模拟交易结果: ", simulateResult); 231 | 232 | // 发送交易 233 | const signature = await connection.sendTransaction(transaction); 234 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 235 | 236 | } 237 | 238 | addAddresses() 239 | ``` 240 | 241 | ``` 242 | 模拟交易结果: { 243 | context: { apiVersion: '2.0.3', slot: 301695975 }, 244 | value: { 245 | accounts: null, 246 | err: null, 247 | innerInstructions: null, 248 | logs: [ 249 | 'Program AddressLookupTab1e1111111111111111111111111 invoke [1]', 250 | 'Program 11111111111111111111111111111111 invoke [2]', 251 | 'Program 11111111111111111111111111111111 success', 252 | 'Program AddressLookupTab1e1111111111111111111111111 success' 253 | ], 254 | replacementBlockhash: null, 255 | returnData: null, 256 | unitsConsumed: 900 257 | } 258 | } 259 | 交易已发送: https://solscan.io/tx/ZRM6NDdtFkH4dRxBNe3r4mEg8yNF87UCs8UE2vycec1Y88XcDHWVbU6Wa7den3a9o6EwzdVFQr6PvW2i19Qv5FF 260 | ``` 261 | 262 | 可以在我们的地址查找表账户中查看刚刚添加的账户地址。 263 | 264 | ![](../img/08-02.png) 265 | 266 | ### 在 `v0` 交易中使用ALT 267 | 268 | ```ts 269 | async function transfer() { 270 | 271 | const lookupTableAddress = new PublicKey('2qqXrZZSG9naivqMyWHHUDRFVNh3YthsTbN5EPU8Poo5') 272 | 273 | // 获取ALT 274 | const ALT = await connection.getAddressLookupTable(lookupTableAddress); 275 | const lookupTableAccount = ALT.value; 276 | if (!ALT.value) { 277 | throw new Error("lookupTableAccount不存在"); 278 | } 279 | console.log('lookupTableAccount:', lookupTableAccount) 280 | 281 | // 目标地址 282 | const toAddress = new PublicKey('buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ'); 283 | 284 | // 转账指令 285 | const instruction = SystemProgram.transfer({ 286 | fromPubkey: payer.publicKey, 287 | toPubkey: toAddress, 288 | lamports: 1000, // 1000 lamports 289 | }); 290 | 291 | // 创建v0 message 292 | const { blockhash } = await connection.getLatestBlockhash(); 293 | const messageV0 = new TransactionMessage({ 294 | payerKey: payer.publicKey, 295 | recentBlockhash: blockhash, // 最近的区块hash 296 | instructions: [instruction], // 指令数组 297 | }).compileToV0Message([lookupTableAccount]); 298 | 299 | // 创建v0交易并签名 300 | const transaction = new VersionedTransaction(messageV0); 301 | transaction.sign([payer]); 302 | 303 | // 模拟交易 304 | const simulateResult = await connection.simulateTransaction(transaction); 305 | console.log("模拟交易结果: ", simulateResult); 306 | 307 | // 发送交易 308 | const signature = await connection.sendTransaction(transaction); 309 | console.log(`交易已发送: https://solscan.io/tx/${signature}`); 310 | 311 | } 312 | 313 | transfer() 314 | ``` 315 | 316 | ``` 317 | lookupTableAccount: AddressLookupTableAccount { 318 | key: PublicKey [PublicKey(2qqXrZZSG9naivqMyWHHUDRFVNh3YthsTbN5EPU8Poo5)] { 319 | _bn: 320 | }, 321 | state: { 322 | deactivationSlot: 18446744073709551615n, 323 | lastExtendedSlot: 301695984, 324 | lastExtendedSlotStartIndex: 0, 325 | authority: PublicKey [PublicKey(web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2)] { 326 | _bn: 327 | }, 328 | addresses: [ 329 | [PublicKey [PublicKey(web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2)]], 330 | [PublicKey [PublicKey(buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ)]], 331 | [PublicKey [PublicKey(11111111111111111111111111111111)]] 332 | ] 333 | } 334 | } 335 | 模拟交易结果: { 336 | context: { apiVersion: '2.0.3', slot: 301701358 }, 337 | value: { 338 | accounts: null, 339 | err: null, 340 | innerInstructions: null, 341 | logs: [ 342 | 'Program 11111111111111111111111111111111 invoke [1]', 343 | 'Program 11111111111111111111111111111111 success' 344 | ], 345 | replacementBlockhash: null, 346 | returnData: null, 347 | unitsConsumed: 150 348 | } 349 | } 350 | 交易已发送: https://solscan.io/tx/4LwygRtiF9ZCrbGKoh8MEzmxowaRHPaDc1nsinkv72uXU2cUCuZ8YskBBgsvbBEMZ5Pqpf6C6WcXtCkqAuLZand1 351 | ``` 352 | 353 | ![](../img/08-03.png) 354 | 355 | 调用 `getParsedTransaction` 来获取这笔v0交易,如下: 356 | 357 | ```ts 358 | async function parseTx() { 359 | 360 | const parsedTransaction = await connection.getParsedTransaction('4LwygRtiF9ZCrbGKoh8MEzmxowaRHPaDc1nsinkv72uXU2cUCuZ8YskBBgsvbBEMZ5Pqpf6C6WcXtCkqAuLZand1', { 361 | commitment: "confirmed", 362 | maxSupportedTransactionVersion: 0 363 | }); 364 | console.log(`已解析的v0交易: ${JSON.stringify(parsedTransaction)}\n`); 365 | 366 | } 367 | 368 | parseTx() 369 | ``` 370 | 371 | ``` 372 | 已解析的v0交易: {"blockTime":1731742689,"meta":{"computeUnitsConsumed":150,"err":null,"fee":5000,"innerInstructions":[],"logMessages":["Program 11111111111111111111111111111111 invoke [1]","Program 11111111111111111111111111111111 success"],"postBalances":[5770640,1,2018400],"postTokenBalances":[],"preBalances":[5776640,1,2017400],"preTokenBalances":[],"rewards":[],"status":{"Ok":null}},"slot":301701368,"transaction":{"message":{"accountKeys":[{"pubkey":"web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2","signer":true,"source":"transaction","writable":true},{"pubkey":"11111111111111111111111111111111","signer":false,"source":"transaction","writable":false},{"pubkey":"buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ","signer":false,"source":"lookupTable","writable":true}],"addressTableLookups":[{"accountKey":"2qqXrZZSG9naivqMyWHHUDRFVNh3YthsTbN5EPU8Poo5","readonlyIndexes":[],"writableIndexes":[1]}],"instructions":[{"parsed":{"info":{"destination":"buffaAJKmNLao65TDTUGq8oB9HgxkfPLGqPMFQapotJ","lamports":1000,"source":"web3xFMwEPrc92NeeXdAigni95NDnnd2NPuajTirao2"},"type":"transfer"},"program":"system","programId":"11111111111111111111111111111111","stackHeight":null}],"recentBlockhash":"DcQMezPzouNnbrHufbhrpjFftMxVpDKX4vwCGc2NQHKZ"},"signatures":["4LwygRtiF9ZCrbGKoh8MEzmxowaRHPaDc1nsinkv72uXU2cUCuZ8YskBBgsvbBEMZ5Pqpf6C6WcXtCkqAuLZand1"]},"version":0} 373 | ``` 374 | --------------------------------------------------------------------------------