├── .github └── workflows │ └── docs.yml ├── .gitignore ├── LICENSE ├── README.en.md ├── README.md ├── advanced ├── address-lookup-table │ ├── create │ │ └── main.ts │ ├── extend │ │ └── main.ts │ ├── fetch-versioned-transaction │ │ └── main.ts │ └── use │ │ └── main.ts ├── durable-nonce │ ├── README.en.md │ ├── README.md │ ├── create-nonce-account │ │ ├── main.en.ts │ │ └── main.ts │ ├── query-nonce │ │ ├── main.en.ts │ │ └── main.ts │ └── use-nonce │ │ ├── main.en.ts │ │ └── main.ts ├── interact-with-program │ ├── README.en.md │ ├── README.md │ ├── close-account │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ ├── Xargo.toml │ │ ├── client │ │ │ ├── .gitignore │ │ │ ├── main.ts │ │ │ ├── package-lock.json │ │ │ └── package.json │ │ └── src │ │ │ └── lib.rs │ ├── echo │ │ ├── client │ │ │ ├── main.en.ts │ │ │ └── main.ts │ │ └── program │ │ │ ├── .DS_Store │ │ │ ├── .gitignore │ │ │ ├── Cargo.lock │ │ │ ├── Cargo.toml │ │ │ └── src │ │ │ └── lib.rs │ └── selector │ │ ├── client │ │ ├── main.en.ts │ │ └── main.ts │ │ └── program │ │ ├── .DS_Store │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── metaplex │ ├── get-tokenmeta │ │ ├── main.en.ts │ │ └── main.ts │ ├── get-wallet-has-a-specific-nft │ │ └── main.ts │ ├── mint-nft-new │ │ └── main.ts │ └── mint-nft │ │ └── main.ts ├── send-tx │ └── main.ts ├── solana-intro │ └── README.md └── token │ ├── README.en.md │ ├── README.md │ ├── close-account │ ├── main.en.ts │ └── main.ts │ ├── get-NFT-current-owner │ └── main.ts │ ├── get-all-token-account-by-owner │ ├── main.en.ts │ └── main.ts │ ├── set-authority │ ├── main.en.ts │ └── main.ts │ └── wrapped-sol │ ├── add-balance │ ├── main.en.ts │ └── main.ts │ └── create-token-account │ ├── main.en.ts │ └── main.ts ├── docs ├── .vuepress │ ├── .gitignore │ └── config.ts ├── README.md ├── advanced │ ├── durable-nonce │ │ ├── README.md │ │ ├── create-nonce-account.md │ │ ├── query-nonce.md │ │ └── use-nonce.md │ ├── metaplex │ │ ├── get-nft.md │ │ ├── get-tokenmeta.md │ │ └── mint-nft.md │ └── token │ │ ├── close-account.md │ │ ├── get-all-token-account-by-owner.md │ │ ├── set-authority.md │ │ └── wrapped-sol │ │ ├── add-balance.md │ │ └── create-token-account.md ├── tour │ ├── create-keypair.md │ ├── create-mint.md │ ├── create-token-account.md │ ├── get-mint.md │ ├── get-sol-balance.md │ ├── get-token-account.md │ ├── get-token-balance.md │ ├── mint-to.md │ ├── request-airdrop.md │ ├── retrieve-keypair.md │ ├── token-transfer.md │ └── transfer.md └── zh │ ├── README.md │ ├── advanced │ ├── durable-nonce │ │ ├── README.md │ │ ├── create-nonce-account.md │ │ ├── query-nonce.md │ │ └── use-nonce.md │ ├── metaplex │ │ ├── get-nft.md │ │ ├── get-tokenmeta.md │ │ └── mint-nft.md │ ├── send-tx.md │ └── token │ │ ├── close-account.md │ │ ├── get-all-token-account-by-owner.md │ │ ├── set-authority.md │ │ └── wrapped-sol │ │ ├── add-balance.md │ │ └── create-token-account.md │ └── tour │ ├── create-keypair.md │ ├── create-mint.md │ ├── create-token-account.md │ ├── get-mint.md │ ├── get-sol-balance.md │ ├── get-token-account.md │ ├── get-token-balance.md │ ├── mint-to.md │ ├── request-airdrop.md │ ├── retrieve-keypair.md │ ├── token-transfer.md │ └── transfer.md ├── helper └── const.ts ├── package-lock.json ├── package.json ├── program-101 ├── README.md ├── accounts │ ├── client │ │ └── main.ts │ └── program │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── data │ ├── client │ │ └── main.ts │ └── program │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── hello │ ├── client │ │ └── main.ts │ └── program │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── invoke │ ├── client │ │ └── main.ts │ └── program │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── invoke_signed │ ├── client │ │ └── main.ts │ └── program │ │ ├── .gitignore │ │ ├── Cargo.lock │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs └── serialize-deserialize │ ├── client │ └── main.ts │ └── program │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── lib.rs └── tour ├── README.md ├── README_EN.md ├── create-keypair ├── bip39.ts ├── bip44.ts └── private-key.ts ├── create-mint ├── main.en.ts └── main.ts ├── create-token-account ├── main.en.ts └── main.ts ├── get-mint ├── main.en.ts └── main.ts ├── get-sol-balance └── main.ts ├── get-token-account └── main.en.ts ├── get-token-balance ├── main.en.ts └── main.ts ├── mint-to ├── main.en.ts └── main.ts ├── request-airdrop └── main.ts ├── token-transfer ├── main.en.ts └── main.ts └── transfer └── main.ts /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | docs: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Setup Node.js 17 | uses: actions/setup-node@v3 18 | with: 19 | node-version: "16" 20 | 21 | - name: install dependencies 22 | run: npm install 23 | 24 | - name: Build VuePress site 25 | run: npm run docs:build 26 | 27 | # please check out the docs of the workflow for more details 28 | # @see https://github.com/crazy-max/ghaction-github-pages 29 | - name: Deploy to GitHub Pages 30 | uses: crazy-max/ghaction-github-pages@v2 31 | with: 32 | # deploy to gh-pages branch 33 | target_branch: gh-pages 34 | # deploy the default output dir of VuePress 35 | build_dir: docs/.vuepress/dist 36 | env: 37 | # @see https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | # vuepress 4 | docs/.vuepress/.temp/ 5 | docs/.vuepress/.cache/ 6 | docs/.vuepress/dist/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Yihau Chen 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 | -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # Solana Web3 Example 2 | 3 | there are some solana web3 example 4 | 5 | ## Guide 6 | 7 | ### Tour 8 | 9 | * [create keypair](tour/create-keypair/main.en.ts) 10 | * [retrieve keypair](tour/retrieve-keypair/main.en.ts) 11 | * [build connection](tour/create-connection/main.en.ts) 12 | * [request airdrop](tour/request-airdrop/main.en.ts) 13 | * [get sol balance](tour/get-sol-balance/main.en.ts) 14 | * [sol transfer](tour/transfer/main.en.ts) 15 | * [create mint](tour/create-mint/main.en.ts) 16 | * [get mint info](tour/get-mint/main.en.ts) 17 | * [create token naccount](tour/create-token-account/main.en.ts) 18 | * [mint token](tour/mint-to/main.en.ts) 19 | * [get token balance](tour/get-token-balance/main.en.ts) 20 | * [token transfer](tour/token-transfer/main.en.ts) 21 | 22 | ### Advanced 23 | 24 | * [Token](advanced/token/README.en.md) 25 | * [Close account](advanced/token/close-account/main.en.ts) 26 | * [Get all token accounts by owner](advanced/token/get-all-token-account-by-owner/main.en.ts) 27 | * [Wrapped SOL](advanced/token/wrapped-sol) 28 | * [NFT](advanced/metaplex) 29 | * [Durable Nonce](advanced/durable-nonce/README.en.md) 30 | * [Send Tx](advanced/send-tx/main.ts) 31 | * [interact with program](advanced/interact-with-program) 32 | 33 | ### Rpc 34 | 35 | 36 | ## Start 37 | 38 | install dependency 39 | 40 | ```bash 41 | npm install 42 | ``` 43 | 44 | run example 45 | 46 | ```bash 47 | npx ts-node -s 48 | ``` 49 | 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Solana Web3 Demo

2 |
3 | English 4 |
5 | 6 | ## Guide 7 | 8 | ### Tour 9 | 10 | * [創建新帳戶](tour/create-keypair/main.ts) 11 | * [私鑰還原帳戶](tour/retrieve-keypair/main.ts) 12 | * [建立基本連線](tour/create-connection/main.ts) 13 | * [領取測試幣](tour/request-airdrop/main.ts) 14 | * [SOL餘額查詢](tour/get-sol-balance/main.ts) 15 | * [SOL轉帳](tour/transfer/main.ts) 16 | * [創建代幣](tour/create-mint/main.ts) 17 | * [抓取代幣資訊](tour/get-mint/main.ts) 18 | * [創建代幣帳戶](tour/create-token-account/main.ts) 19 | * [增發代幣](tour/mint-to/main.ts) 20 | * [查詢代幣餘額](tour/get-token-balance/main.ts) 21 | * [代幣轉帳](tour/token-transfer/main.ts) 22 | 23 | ### Advanced 24 | 25 | * [代幣](advanced/token/README.md) 26 | * [關閉帳戶](advanced/token/close-account/main.ts) 27 | * [查詢使用者的代幣帳戶](advanced/token/get-all-token-account-by-owner/main.ts) 28 | * [Wrapped SOL](advanced/token/wrapped-sol) 29 | * [NFT](advanced/metaplex) 30 | * [Durable Nonce](advanced/durable-nonce/README.md) 31 | * [Send Tx](advanced/send-tx/main.ts) 32 | * [與program互動](advanced/interact-with-program) 33 | 34 | ### Rpc 35 | 36 | 37 | ## Start 38 | 39 | 安裝套件 40 | 41 | ```bash 42 | npm install 43 | ``` 44 | 45 | 執行範例 46 | 47 | ```bash 48 | npx ts-node -s 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /advanced/address-lookup-table/create/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, Transaction, AddressLookupTableProgram } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | 4 | // connection 5 | const connection = new Connection("https://api.devnet.solana.com"); 6 | 7 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 8 | const feePayer = Keypair.fromSecretKey( 9 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 10 | ); 11 | 12 | (async () => { 13 | let [instruction, addressLookupTablePubkey] = AddressLookupTableProgram.createLookupTable({ 14 | authority: feePayer.publicKey, 15 | payer: feePayer.publicKey, 16 | recentSlot: await connection.getSlot(), 17 | }); 18 | 19 | console.log(`addressLookupTablePubkey: ${addressLookupTablePubkey.toBase58()}`); 20 | 21 | let tx = new Transaction().add(instruction); 22 | tx.feePayer = feePayer.publicKey; 23 | let txhash = await connection.sendTransaction(tx, [feePayer]); 24 | console.log(`txhash: ${txhash}`); 25 | })(); 26 | -------------------------------------------------------------------------------- /advanced/address-lookup-table/extend/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, Transaction, PublicKey, AddressLookupTableProgram } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | 4 | // connection 5 | const connection = new Connection("https://api.devnet.solana.com"); 6 | 7 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 8 | const feePayer = Keypair.fromSecretKey( 9 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 10 | ); 11 | 12 | const addressLookupTablePubkey = new PublicKey("36Ey42Hi8FfvMk8yQyyR6i4b7dsUPMEYqJntbdjz5oHv"); 13 | 14 | (async () => { 15 | let tx = new Transaction().add( 16 | AddressLookupTableProgram.extendLookupTable({ 17 | lookupTable: addressLookupTablePubkey, 18 | authority: feePayer.publicKey, 19 | payer: feePayer.publicKey, 20 | addresses: [ 21 | new PublicKey("G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY"), 22 | new PublicKey("FUarP2p5EnxD66vVDL4PWRoWMzA56ZVHG24hpEDFShEz"), 23 | ], 24 | }) 25 | ); 26 | tx.feePayer = feePayer.publicKey; 27 | let txhash = await connection.sendTransaction(tx, [feePayer]); 28 | console.log(`txhash: ${txhash}`); 29 | })(); 30 | -------------------------------------------------------------------------------- /advanced/address-lookup-table/fetch-versioned-transaction/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from "@solana/web3.js"; 2 | 3 | // connection 4 | const connection = new Connection("https://api.devnet.solana.com"); 5 | 6 | (async () => { 7 | let tx = await connection.getParsedTransaction( 8 | "4zZBQVkkJUKbASJoKStzsnLh8ifA8ywMXS6GqWBZeGghBiedM3ve7ujaTKniRGVekZ5DUvkVzbhxTLfCRWr3Z29N", 9 | { maxSupportedTransactionVersion: 0 } 10 | ); 11 | console.log(JSON.stringify(tx, null, 2)); 12 | })(); 13 | -------------------------------------------------------------------------------- /advanced/address-lookup-table/use/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | Keypair, 4 | SystemProgram, 5 | PublicKey, 6 | LAMPORTS_PER_SOL, 7 | VersionedTransaction, 8 | MessageV0, 9 | } from "@solana/web3.js"; 10 | import * as bs58 from "bs58"; 11 | 12 | // connection 13 | const connection = new Connection("https://api.devnet.solana.com"); 14 | 15 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 16 | const feePayer = Keypair.fromSecretKey( 17 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 18 | ); 19 | 20 | const addressLookupTablePubkey = new PublicKey("36Ey42Hi8FfvMk8yQyyR6i4b7dsUPMEYqJntbdjz5oHv"); 21 | 22 | (async () => { 23 | let addressLookupTable = (await connection.getAddressLookupTable(addressLookupTablePubkey)).value!; 24 | 25 | let tx = new VersionedTransaction( 26 | MessageV0.compile({ 27 | payerKey: feePayer.publicKey, 28 | instructions: [ 29 | SystemProgram.transfer({ 30 | fromPubkey: feePayer.publicKey, 31 | toPubkey: new PublicKey("FUarP2p5EnxD66vVDL4PWRoWMzA56ZVHG24hpEDFShEz"), 32 | lamports: 0.01 * LAMPORTS_PER_SOL, 33 | }), 34 | ], 35 | recentBlockhash: (await connection.getLatestBlockhash()).blockhash, 36 | addressLookupTableAccounts: [addressLookupTable], 37 | }) 38 | ); 39 | tx.sign([feePayer]); 40 | 41 | let txhash = await connection.sendTransaction(tx); 42 | console.log(`txhash: ${txhash}`); 43 | })(); 44 | -------------------------------------------------------------------------------- /advanced/durable-nonce/README.en.md: -------------------------------------------------------------------------------- 1 | # Durable Nonce 2 | 3 | We need to pack a recent blockhash into our tx. If the blockhash is expired, our tx will be rejected. (aprox. 2 min). 4 | If you want to get rid of it, you can use durable nonce. Durable nonce has no valid time. 5 | 6 | ## Mechanism 7 | 8 | You will need to create a nonce account previously. After you created, there is a `nonce` will store in the account. 9 | Then we can trigger this mechanism by 10 | 11 | 1. use the `nonce` as a recent blockhash 12 | 2. put `nonce advance` in the first instruciton in this tx 13 | 14 | Here I seperate to 3 step to guide to use it. 15 | 16 | * [create nonce account](../durable-nonce/create-nonce-account/main.en.ts) 17 | * [fetch nonce account info](../durable-nonce/query-nonce/main.en.ts) 18 | * [use nonce](../durable-nonce/use-nonce/main.en.ts) -------------------------------------------------------------------------------- /advanced/durable-nonce/README.md: -------------------------------------------------------------------------------- 1 | # Durable Nonce 2 | 3 | 在solana發交易的時候,會在tx內包一塊最近的blockhash塞進去,之後一起簽名。而這個blockhash距離鏈上最新的區塊太遠的話,tx就會被拒絕。 (大概拿到之後兩分鐘就會過期) 這個機制使我們沒有辦法讓tx在本地放一陣子之後再送出。官方有提供一個解法叫做durable nonce。 4 | 5 | ## 機制 6 | 7 | 這個durable nonce會需要你先創一個nonce account。nonce account在創建完後同時裡面也會存在一個nonce。我們只要讓tx符合下面條件就可以啟動nonce的機制 8 | 9 | 1. 把 nonce 放在 blockhash (就不用放最近一塊的blockhash了) 10 | 2. tx的第一個instruction是advanced nonce的操作 11 | 12 | 滿足上面兩個條件就可以啟動durable nonce的機制,下面會分成幾個步驟帶大家一步一步操作。 13 | 14 | * [創建nonce account](../durable-nonce/create-nonce-account/main.ts) 15 | * [查詢nonce account的nonce](../durable-nonce/query-nonce/main.ts) 16 | * [使用nonce機制](../durable-nonce/use-nonce/main.ts) -------------------------------------------------------------------------------- /advanced/durable-nonce/create-nonce-account/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, SystemProgram, NONCE_ACCOUNT_LENGTH } from "@solana/web3.js"; 2 | import { CONNECTION, FEE_PAYER } from "../../../helper/const"; 3 | 4 | // create nonce account 5 | 6 | async function main() { 7 | let nonceAccount = Keypair.generate(); 8 | console.log(`nonce account: ${nonceAccount.publicKey.toBase58()}`); 9 | 10 | let tx = new Transaction(); 11 | tx.add( 12 | // create nonce account 13 | SystemProgram.createAccount({ 14 | fromPubkey: FEE_PAYER.publicKey, 15 | newAccountPubkey: nonceAccount.publicKey, 16 | lamports: await CONNECTION.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH), 17 | space: NONCE_ACCOUNT_LENGTH, 18 | programId: SystemProgram.programId, 19 | }), 20 | // init nonce account 21 | SystemProgram.nonceInitialize({ 22 | noncePubkey: nonceAccount.publicKey, // nonce account pubkey 23 | authorizedPubkey: FEE_PAYER.publicKey, // nonce account auth 24 | }) 25 | ); 26 | tx.feePayer = FEE_PAYER.publicKey; 27 | 28 | console.log(`txhash: ${await CONNECTION.sendTransaction(tx, [nonceAccount, FEE_PAYER])}`); 29 | } 30 | 31 | main().then( 32 | () => process.exit(), 33 | (err) => { 34 | console.error(err); 35 | process.exit(-1); 36 | } 37 | ); 38 | -------------------------------------------------------------------------------- /advanced/durable-nonce/create-nonce-account/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, SystemProgram, NONCE_ACCOUNT_LENGTH } from "@solana/web3.js"; 2 | import { CONNECTION, FEE_PAYER } from "../../../helper/const"; 3 | 4 | // 創建nonce account 5 | 6 | async function main() { 7 | let nonceAccount = Keypair.generate(); 8 | console.log(`nonce account: ${nonceAccount.publicKey.toBase58()}`); 9 | 10 | let tx = new Transaction(); 11 | tx.add( 12 | // 創建一個nonce account 13 | SystemProgram.createAccount({ 14 | fromPubkey: FEE_PAYER.publicKey, 15 | newAccountPubkey: nonceAccount.publicKey, 16 | lamports: await CONNECTION.getMinimumBalanceForRentExemption(NONCE_ACCOUNT_LENGTH), 17 | space: NONCE_ACCOUNT_LENGTH, 18 | programId: SystemProgram.programId, 19 | }), 20 | // 初始化nonce account 21 | SystemProgram.nonceInitialize({ 22 | noncePubkey: nonceAccount.publicKey, // nonce account pubkey 23 | authorizedPubkey: FEE_PAYER.publicKey, // 之後要操作nonce account的auth 24 | }) 25 | ); 26 | tx.feePayer = FEE_PAYER.publicKey; 27 | 28 | console.log(`txhash: ${await CONNECTION.sendTransaction(tx, [nonceAccount, FEE_PAYER])}`); 29 | } 30 | 31 | main().then( 32 | () => process.exit(), 33 | (err) => { 34 | console.error(err); 35 | process.exit(-1); 36 | } 37 | ); 38 | -------------------------------------------------------------------------------- /advanced/durable-nonce/query-nonce/main.en.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, NonceAccount } from "@solana/web3.js"; 2 | import { CONNECTION } from "../../../helper/const"; 3 | 4 | // query nonce account 5 | 6 | async function main() { 7 | let nonceAccountPubkey = new PublicKey("2ZKe8GmRAqFRj3AvVSFBTLHNNrH1uB23hwjHV3CzJGmf"); 8 | let accountInfo = await CONNECTION.getAccountInfo(nonceAccountPubkey); 9 | let nonceAccount = NonceAccount.fromAccountData(accountInfo.data); 10 | console.log(`auth: ${nonceAccount.authorizedPubkey.toBase58()}`) 11 | console.log(`nonce: ${nonceAccount.nonce}`) 12 | console.log(`fee calculator: ${nonceAccount.feeCalculator}`) 13 | } 14 | 15 | main().then( 16 | () => process.exit(), 17 | (err) => { 18 | console.error(err); 19 | process.exit(-1); 20 | } 21 | ); 22 | -------------------------------------------------------------------------------- /advanced/durable-nonce/query-nonce/main.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, NonceAccount } from "@solana/web3.js"; 2 | import { CONNECTION } from "../../../helper/const"; 3 | 4 | // 查詢nonce account帳戶詳細資料 5 | 6 | async function main() { 7 | let nonceAccountPubkey = new PublicKey("2ZKe8GmRAqFRj3AvVSFBTLHNNrH1uB23hwjHV3CzJGmf"); 8 | let accountInfo = await CONNECTION.getAccountInfo(nonceAccountPubkey); 9 | let nonceAccount = NonceAccount.fromAccountData(accountInfo.data); 10 | console.log(`auth: ${nonceAccount.authorizedPubkey.toBase58()}`) 11 | console.log(`nonce: ${nonceAccount.nonce}`) 12 | // 拿來計算fee的價目表,目前都只有收簽名數量 (1個簽名 5000 lamports = 0.000005 SOL) 13 | console.log(`fee calculator: ${nonceAccount.feeCalculator}`) 14 | } 15 | 16 | main().then( 17 | () => process.exit(), 18 | (err) => { 19 | console.error(err); 20 | process.exit(-1); 21 | } 22 | ); 23 | -------------------------------------------------------------------------------- /advanced/durable-nonce/use-nonce/main.en.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Keypair, 3 | Transaction, 4 | SystemProgram, 5 | Connection, 6 | NONCE_ACCOUNT_LENGTH, 7 | sendAndConfirmTransaction, 8 | PublicKey, 9 | NonceAccount, 10 | } from "@solana/web3.js"; 11 | import { CONNECTION, FEE_PAYER } from "../../../helper/const"; 12 | 13 | // use nonce 14 | 15 | // nonce account 16 | const nonceAccountPubkey = new PublicKey("2ZKe8GmRAqFRj3AvVSFBTLHNNrH1uB23hwjHV3CzJGmf"); 17 | 18 | async function main() { 19 | // here we use a tranfer as an example 20 | 21 | // create a random `to` 22 | let to = Keypair.generate(); 23 | 24 | let tx = new Transaction(); 25 | tx.add( 26 | // nonce advance must be the first insturction 27 | SystemProgram.nonceAdvance({ 28 | noncePubkey: nonceAccountPubkey, 29 | authorizedPubkey: FEE_PAYER.publicKey, 30 | }), 31 | // after that, you can append what you really want to do, here we append a transfer instruction 32 | SystemProgram.transfer({ 33 | fromPubkey: FEE_PAYER.publicKey, 34 | toPubkey: to.publicKey, 35 | lamports: 1e8, 36 | }) 37 | ); 38 | 39 | // then we use the `nonce` which stored in the nonce acocunt as a recent blockhash 40 | tx.recentBlockhash = "EFtM4FKWZS8WUPd7VFW2Lukzk2KEgCucibrjF2jZDPyZ"; 41 | tx.feePayer = FEE_PAYER.publicKey; 42 | tx.sign(FEE_PAYER); 43 | 44 | let rawtx = tx.serialize(); 45 | console.log(`txhash: ${await CONNECTION.sendRawTransaction(rawtx)}`); 46 | 47 | // !!! the nonce will be changed after you do `nonce advance` !!! 48 | } 49 | 50 | main().then( 51 | () => process.exit(), 52 | (err) => { 53 | console.error(err); 54 | process.exit(-1); 55 | } 56 | ); 57 | -------------------------------------------------------------------------------- /advanced/durable-nonce/use-nonce/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Keypair, 3 | Transaction, 4 | SystemProgram, 5 | Connection, 6 | NONCE_ACCOUNT_LENGTH, 7 | sendAndConfirmTransaction, 8 | PublicKey, 9 | NonceAccount, 10 | } from "@solana/web3.js"; 11 | import { CONNECTION, FEE_PAYER } from "../../../helper/const"; 12 | 13 | // 使用nonce機制 14 | 15 | // 剛剛產的nonce account 16 | const nonceAccountPubkey = new PublicKey("2ZKe8GmRAqFRj3AvVSFBTLHNNrH1uB23hwjHV3CzJGmf"); 17 | 18 | async function main() { 19 | // 這邊用transfer當作使用nonce機制的範例tx 20 | 21 | // 隨便產一個to的address 22 | let to = Keypair.generate(); 23 | 24 | // 創建tx 25 | let tx = new Transaction(); 26 | tx.add( 27 | // nonce advance的操作一定要在第一個instruction 28 | SystemProgram.nonceAdvance({ 29 | noncePubkey: nonceAccountPubkey, 30 | authorizedPubkey: FEE_PAYER.publicKey, 31 | }), 32 | // 後面放原本想要做的操作 33 | SystemProgram.transfer({ 34 | fromPubkey: FEE_PAYER.publicKey, 35 | toPubkey: to.publicKey, 36 | lamports: 1e8, 37 | }) 38 | ); 39 | 40 | // 這裡帶nonce account裡面存的nonce 41 | tx.recentBlockhash = "EFtM4FKWZS8WUPd7VFW2Lukzk2KEgCucibrjF2jZDPyZ"; 42 | tx.feePayer = FEE_PAYER.publicKey; 43 | tx.sign(FEE_PAYER); 44 | 45 | let rawtx = tx.serialize(); 46 | console.log(`txhash: ${await CONNECTION.sendRawTransaction(rawtx)}`); 47 | 48 | // 要注意每次nonce advance之後,nonce account存的nonce就不一樣了 49 | // 如果有想要再次使用,需要再去拿一次nonce 50 | } 51 | 52 | main().then( 53 | () => process.exit(), 54 | (err) => { 55 | console.error(err); 56 | process.exit(-1); 57 | } 58 | ); 59 | -------------------------------------------------------------------------------- /advanced/interact-with-program/README.en.md: -------------------------------------------------------------------------------- 1 | ## Guide 2 | 3 | all example will follow this folder struction 4 | 5 | ``` 6 | . 7 | ├── client 8 | └── program 9 | ``` 10 | 11 | I put introduction in the code, I recommend read program first then client. 12 | 13 | Remember to deploy prgoram before you execute client code. 14 | 15 | recommend you to read in this order 16 | 17 | - echo 18 | - selector 19 | - read / write state (comming soon) 20 | - pda (comming soon) -------------------------------------------------------------------------------- /advanced/interact-with-program/README.md: -------------------------------------------------------------------------------- 1 | ## 概述 2 | 3 | 這裡每個子範例的結構都會是 4 | 5 | ``` 6 | . 7 | ├── client 8 | └── program 9 | ``` 10 | 11 | 我會把相關說明都放在code裡,建議閱讀順序都是先看program之後再看client。 12 | 13 | 在執行client之前記得要部署program 14 | 15 | 建議閱讀順序 16 | 17 | - echo 18 | - seclector 19 | - read / write state (comming soon) 20 | - pda (comming soon) -------------------------------------------------------------------------------- /advanced/interact-with-program/close-account/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /advanced/interact-with-program/close-account/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["yihau.chen "] 3 | edition = "2018" 4 | name = "close-account" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | solana-program = "1.8.2" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "lib"] 12 | -------------------------------------------------------------------------------- /advanced/interact-with-program/close-account/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] -------------------------------------------------------------------------------- /advanced/interact-with-program/close-account/client/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /advanced/interact-with-program/close-account/client/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Connection, Transaction, SystemProgram, TransactionInstruction, PublicKey } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | 4 | (async function () { 5 | // 5pVyoAeURQHNMVU7DmfMHvCDNmTEYXWfEwc136GYhTKG for test 6 | const newFeePayer = Keypair.fromSecretKey( 7 | bs58.decode("5MaiiCavjCmn9Hs1o3eznqDEhRwxo7pXiAYez7keQUviUkauRiTMD8DrESdrNjN8zd9mTmVhRvBJeg5vhyvgrAhG") 8 | ); 9 | 10 | // 8DMP4Ytk6Znu4GxDrrQnGNvx7ZZdR4UaMom3HTH9CZqs for test 11 | const oriAccount = Keypair.fromSecretKey( 12 | bs58.decode("4UojpNP3oQBR1jbdcCyVyNyYJ1hnjaJWLrkzK4zEv8bdtVEuKyUX5Nfau24ZxqSFxwTYcVEBBrngXZRWcwf7B8os") 13 | ); 14 | 15 | const connection = new Connection("https://api.devnet.solana.com"); 16 | 17 | // 1) to simulate your current situation 18 | // let tx = new Transaction().add( 19 | // SystemProgram.allocate({ 20 | // accountPubkey: oriAccount.publicKey, 21 | // space: 100, 22 | // }) 23 | // ); 24 | // console.log(`txhash: ${await connection.sendTransaction(tx, [oriAccount])}`); 25 | 26 | // 2) deploy the program 27 | 28 | // 3) recover your account 29 | // const programId = new PublicKey("AfYY9LPWptXWv9YSZws4wehZMNX5o2YiUzKqmXEqJVyu"); 30 | // let tx = new Transaction().add( 31 | // SystemProgram.assign({ 32 | // accountPubkey: oriAccount.publicKey, 33 | // programId: programId, 34 | // }), 35 | // new TransactionInstruction({ 36 | // keys: [ 37 | // { 38 | // pubkey: oriAccount.publicKey, 39 | // isSigner: false, 40 | // isWritable: true, 41 | // }, 42 | // { 43 | // pubkey: newFeePayer.publicKey, 44 | // isSigner: false, 45 | // isWritable: true, 46 | // }, 47 | // ], 48 | // programId: programId, 49 | // }) 50 | // ); 51 | // tx.feePayer = newFeePayer.publicKey; 52 | // console.log(`txhash: ${await connection.sendTransaction(tx, [oriAccount, newFeePayer])}`); 53 | 54 | // 4) test for origin account 55 | // let tx = new Transaction().add( 56 | // SystemProgram.transfer({ 57 | // fromPubkey: oriAccount.publicKey, 58 | // toPubkey: newFeePayer.publicKey, 59 | // lamports: 1, 60 | // }) 61 | // ); 62 | // console.log(`txhash: ${await connection.sendTransaction(tx, [oriAccount])}`); 63 | })(); 64 | -------------------------------------------------------------------------------- /advanced/interact-with-program/close-account/client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "dependencies": { 13 | "@solana/web3.js": "^1.30.2", 14 | "bs58": "^4.0.1" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /advanced/interact-with-program/close-account/src/lib.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::next_account_info, account_info::AccountInfo, entrypoint, 3 | entrypoint::ProgramResult, pubkey::Pubkey, 4 | }; 5 | 6 | // Declare and export the program's entrypoint 7 | entrypoint!(process_instruction); 8 | 9 | // Program entrypoint's implementation 10 | fn process_instruction( 11 | _program_id: &Pubkey, 12 | accounts: &[AccountInfo], 13 | _instruction_data: &[u8], 14 | ) -> ProgramResult { 15 | let account_info_iter = &mut accounts.iter(); 16 | 17 | let source_account_info = next_account_info(account_info_iter)?; 18 | let dest_account_info = next_account_info(account_info_iter)?; 19 | 20 | let dest_starting_lamports = dest_account_info.lamports(); 21 | **dest_account_info.lamports.borrow_mut() = dest_starting_lamports 22 | .checked_add(source_account_info.lamports()) 23 | .unwrap(); 24 | **source_account_info.lamports.borrow_mut() = 0; 25 | 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /advanced/interact-with-program/echo/client/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, Connection, TransactionInstruction, PublicKey, AccountMeta } from "@solana/web3.js"; 2 | 3 | // remember to check your fee payer has enough SOL to send tx 4 | 5 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 6 | let feePayer = Keypair.fromSecretKey( 7 | Uint8Array.from([ 8 | 206, 54, 90, 62, 42, 169, 79, 30, 10, 214, 71, 58, 161, 79, 210, 133, 123, 207, 196, 142, 168, 155, 129, 108, 35, 9 | 155, 218, 75, 82, 233, 79, 40, 67, 120, 93, 30, 66, 81, 199, 231, 199, 75, 70, 229, 64, 75, 252, 105, 43, 152, 135, 10 | 212, 92, 179, 44, 129, 174, 181, 26, 186, 90, 20, 83, 69, 11 | ]) 12 | ); 13 | 14 | // your program 15 | let programId = new PublicKey("8FfKRwvdUCKKs63zvz7NK3cEqwQeT2ivkcuy3v9Ypsgn"); 16 | 17 | async function main() { 18 | let connection = new Connection("http://localhost:8899"); 19 | let randomAccount = new Keypair(); 20 | console.log(`random address: ${randomAccount.publicKey.toBase58()}`); 21 | 22 | // if you want to interact with you program, you need to know tx's struction. below are some main concepts. 23 | // a tx = many signatures + one message 24 | // a message = many instructions 25 | // an instruction = program id + account meta + data 26 | // account meta = pubeky + isSigner + isWritable 27 | // if isSigner is true, this account should sign this tx too 28 | // if isWritable is true, this account's data can be modify after this tx. 29 | 30 | // back to the topic. so for now, we are going to create an instruction to interact with our program 31 | let tx = new Transaction(); 32 | tx.add( 33 | new TransactionInstruction({ 34 | keys: [ 35 | { 36 | pubkey: feePayer.publicKey, 37 | isSigner: true, 38 | isWritable: true, 39 | }, 40 | { 41 | pubkey: randomAccount.publicKey, 42 | isSigner: false, 43 | isWritable: true, 44 | }, 45 | ], // account meta, program will receive the same order array 46 | data: Buffer.from(new Uint8Array([1, 2, 3, 4, 5])), // data 47 | programId: programId, 48 | }) 49 | ); 50 | console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer])}`); 51 | // after you sending, you can check tx on official explorer 52 | // it should log these data which we pass into tx. 53 | // in next chapter I'm going to tell you how to make branch to your program 54 | } 55 | 56 | main().then( 57 | () => process.exit(), 58 | (err) => { 59 | console.error(err); 60 | process.exit(-1); 61 | } 62 | ); 63 | -------------------------------------------------------------------------------- /advanced/interact-with-program/echo/client/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, Connection, TransactionInstruction, PublicKey, AccountMeta } from "@solana/web3.js"; 2 | 3 | // 發交易前記得確認feePayer有錢 4 | 5 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 6 | let feePayer = Keypair.fromSecretKey( 7 | Uint8Array.from([ 8 | 206, 54, 90, 62, 42, 169, 79, 30, 10, 214, 71, 58, 161, 79, 210, 133, 123, 207, 196, 142, 168, 155, 129, 108, 35, 9 | 155, 218, 75, 82, 233, 79, 40, 67, 120, 93, 30, 66, 81, 199, 231, 199, 75, 70, 229, 64, 75, 252, 105, 43, 152, 135, 10 | 212, 92, 179, 44, 129, 174, 181, 26, 186, 90, 20, 83, 69, 11 | ]) 12 | ); 13 | 14 | // 你剛剛部署的program 15 | let programId = new PublicKey("8FfKRwvdUCKKs63zvz7NK3cEqwQeT2ivkcuy3v9Ypsgn"); 16 | 17 | async function main() { 18 | let connection = new Connection("http://localhost:8899"); 19 | let randomAccount = new Keypair(); 20 | console.log(`random address: ${randomAccount.publicKey.toBase58()}`); 21 | 22 | // 跟program互動之前我們必須要先知道tx的結構,下面簡單介紹一下 23 | // 一個 tx = 多個簽名 + 一個message 24 | // 一個 message = 許多的insturcitons 25 | // 一個 instruction = program id + accountMeta + data 26 | // accountMeta = pubkey + isSigner + isWritable 27 | // 如果isSiger指定是true,那該個account也會需要簽名這個交易才有辦法發送 28 | // 如果isWritable是true,那program才能更改他的資料 29 | 30 | // 回歸正題,所以當我們需要跟我們的program互動時,會需要一個instruction來幫我們完成 31 | // 下面有一個很簡單的互動instruction 32 | let tx = new Transaction(); 33 | tx.add( 34 | new TransactionInstruction({ 35 | keys: [ 36 | { 37 | pubkey: feePayer.publicKey, 38 | isSigner: true, 39 | isWritable: true, 40 | }, 41 | { 42 | pubkey: randomAccount.publicKey, 43 | isSigner: false, 44 | isWritable: true, 45 | }, 46 | ], // account meta, 會依照這個順序傳到program內 47 | data: Buffer.from(new Uint8Array([1, 2, 3, 4, 5])), // data 48 | programId: programId, 49 | }) 50 | ); 51 | console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer])}`); 52 | // 執行完成後可以在官方的explorer上觀察一下這個tx的log 53 | // 應該會完整log出來我們剛剛上面傳入的訊息 54 | // 這樣我們就完成第一步了,下一篇我會跟大家說如何讓同一個prgoram有不同的分支 55 | } 56 | 57 | main().then( 58 | () => process.exit(), 59 | (err) => { 60 | console.error(err); 61 | process.exit(-1); 62 | } 63 | ); 64 | -------------------------------------------------------------------------------- /advanced/interact-with-program/echo/program/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihau/solana-web3-demo/b69029f4817a731120cada843a9700bfad364a9b/advanced/interact-with-program/echo/program/.DS_Store -------------------------------------------------------------------------------- /advanced/interact-with-program/echo/program/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /advanced/interact-with-program/echo/program/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.4.7" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "0.7.15" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "arrayref" 22 | version = "0.3.6" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 25 | 26 | [[package]] 27 | name = "arrayvec" 28 | version = "0.5.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 31 | 32 | [[package]] 33 | name = "atty" 34 | version = "0.2.14" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 37 | dependencies = [ 38 | "hermit-abi", 39 | "libc", 40 | "winapi", 41 | ] 42 | 43 | [[package]] 44 | name = "autocfg" 45 | version = "1.0.1" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 48 | 49 | [[package]] 50 | name = "base64" 51 | version = "0.12.3" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 54 | 55 | [[package]] 56 | name = "bincode" 57 | version = "1.3.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 60 | dependencies = [ 61 | "serde", 62 | ] 63 | 64 | [[package]] 65 | name = "blake3" 66 | version = "0.3.8" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3" 69 | dependencies = [ 70 | "arrayref", 71 | "arrayvec", 72 | "cc", 73 | "cfg-if 0.1.10", 74 | "constant_time_eq", 75 | "crypto-mac", 76 | "digest 0.9.0", 77 | ] 78 | 79 | [[package]] 80 | name = "block-buffer" 81 | version = "0.9.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 84 | dependencies = [ 85 | "block-padding", 86 | "generic-array 0.14.4", 87 | ] 88 | 89 | [[package]] 90 | name = "block-padding" 91 | version = "0.2.1" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" 94 | 95 | [[package]] 96 | name = "borsh" 97 | version = "0.9.1" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "18dda7dc709193c0d86a1a51050a926dc3df1cf262ec46a23a25dba421ea1924" 100 | dependencies = [ 101 | "borsh-derive", 102 | "hashbrown", 103 | ] 104 | 105 | [[package]] 106 | name = "borsh-derive" 107 | version = "0.9.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "684155372435f578c0fa1acd13ebbb182cc19d6b38b64ae7901da4393217d264" 110 | dependencies = [ 111 | "borsh-derive-internal", 112 | "borsh-schema-derive-internal", 113 | "proc-macro-crate", 114 | "proc-macro2", 115 | "syn", 116 | ] 117 | 118 | [[package]] 119 | name = "borsh-derive-internal" 120 | version = "0.9.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "2102f62f8b6d3edeab871830782285b64cc1830168094db05c8e458f209bc5c3" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | "syn", 127 | ] 128 | 129 | [[package]] 130 | name = "borsh-schema-derive-internal" 131 | version = "0.9.1" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "196c978c4c9b0b142d446ef3240690bf5a8a33497074a113ff9a337ccb750483" 134 | dependencies = [ 135 | "proc-macro2", 136 | "quote", 137 | "syn", 138 | ] 139 | 140 | [[package]] 141 | name = "bs58" 142 | version = "0.3.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" 145 | 146 | [[package]] 147 | name = "bv" 148 | version = "0.11.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" 151 | dependencies = [ 152 | "feature-probe", 153 | "serde", 154 | ] 155 | 156 | [[package]] 157 | name = "byteorder" 158 | version = "1.4.3" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 161 | 162 | [[package]] 163 | name = "cc" 164 | version = "1.0.71" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" 167 | 168 | [[package]] 169 | name = "cfg-if" 170 | version = "0.1.10" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 173 | 174 | [[package]] 175 | name = "cfg-if" 176 | version = "1.0.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 179 | 180 | [[package]] 181 | name = "constant_time_eq" 182 | version = "0.1.5" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 185 | 186 | [[package]] 187 | name = "cpufeatures" 188 | version = "0.2.1" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" 191 | dependencies = [ 192 | "libc", 193 | ] 194 | 195 | [[package]] 196 | name = "crunchy" 197 | version = "0.2.2" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 200 | 201 | [[package]] 202 | name = "crypto-mac" 203 | version = "0.8.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" 206 | dependencies = [ 207 | "generic-array 0.14.4", 208 | "subtle", 209 | ] 210 | 211 | [[package]] 212 | name = "curve25519-dalek" 213 | version = "2.1.3" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" 216 | dependencies = [ 217 | "byteorder", 218 | "digest 0.8.1", 219 | "rand_core", 220 | "subtle", 221 | "zeroize", 222 | ] 223 | 224 | [[package]] 225 | name = "digest" 226 | version = "0.8.1" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 229 | dependencies = [ 230 | "generic-array 0.12.4", 231 | ] 232 | 233 | [[package]] 234 | name = "digest" 235 | version = "0.9.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 238 | dependencies = [ 239 | "generic-array 0.14.4", 240 | ] 241 | 242 | [[package]] 243 | name = "either" 244 | version = "1.6.1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 247 | 248 | [[package]] 249 | name = "env_logger" 250 | version = "0.8.4" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" 253 | dependencies = [ 254 | "atty", 255 | "humantime", 256 | "log", 257 | "regex", 258 | "termcolor", 259 | ] 260 | 261 | [[package]] 262 | name = "feature-probe" 263 | version = "0.1.1" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" 266 | 267 | [[package]] 268 | name = "generic-array" 269 | version = "0.12.4" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" 272 | dependencies = [ 273 | "typenum", 274 | ] 275 | 276 | [[package]] 277 | name = "generic-array" 278 | version = "0.14.4" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 281 | dependencies = [ 282 | "serde", 283 | "typenum", 284 | "version_check", 285 | ] 286 | 287 | [[package]] 288 | name = "getrandom" 289 | version = "0.1.16" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 292 | dependencies = [ 293 | "cfg-if 1.0.0", 294 | "libc", 295 | "wasi", 296 | ] 297 | 298 | [[package]] 299 | name = "hashbrown" 300 | version = "0.9.1" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 303 | dependencies = [ 304 | "ahash", 305 | ] 306 | 307 | [[package]] 308 | name = "hello" 309 | version = "0.1.0" 310 | dependencies = [ 311 | "solana-program", 312 | ] 313 | 314 | [[package]] 315 | name = "hermit-abi" 316 | version = "0.1.19" 317 | source = "registry+https://github.com/rust-lang/crates.io-index" 318 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 319 | dependencies = [ 320 | "libc", 321 | ] 322 | 323 | [[package]] 324 | name = "hex" 325 | version = "0.4.3" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 328 | 329 | [[package]] 330 | name = "hmac" 331 | version = "0.8.1" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" 334 | dependencies = [ 335 | "crypto-mac", 336 | "digest 0.9.0", 337 | ] 338 | 339 | [[package]] 340 | name = "hmac-drbg" 341 | version = "0.3.0" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" 344 | dependencies = [ 345 | "digest 0.9.0", 346 | "generic-array 0.14.4", 347 | "hmac", 348 | ] 349 | 350 | [[package]] 351 | name = "humantime" 352 | version = "2.1.0" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 355 | 356 | [[package]] 357 | name = "itertools" 358 | version = "0.9.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" 361 | dependencies = [ 362 | "either", 363 | ] 364 | 365 | [[package]] 366 | name = "keccak" 367 | version = "0.1.0" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" 370 | 371 | [[package]] 372 | name = "lazy_static" 373 | version = "1.4.0" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 376 | 377 | [[package]] 378 | name = "libc" 379 | version = "0.2.103" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" 382 | 383 | [[package]] 384 | name = "libsecp256k1" 385 | version = "0.5.0" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "bd1137239ab33b41aa9637a88a28249e5e70c40a42ccc92db7f12cc356c1fcd7" 388 | dependencies = [ 389 | "arrayref", 390 | "base64", 391 | "digest 0.9.0", 392 | "hmac-drbg", 393 | "libsecp256k1-core", 394 | "libsecp256k1-gen-ecmult", 395 | "libsecp256k1-gen-genmult", 396 | "rand", 397 | "serde", 398 | "sha2", 399 | "typenum", 400 | ] 401 | 402 | [[package]] 403 | name = "libsecp256k1-core" 404 | version = "0.2.2" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" 407 | dependencies = [ 408 | "crunchy", 409 | "digest 0.9.0", 410 | "subtle", 411 | ] 412 | 413 | [[package]] 414 | name = "libsecp256k1-gen-ecmult" 415 | version = "0.2.1" 416 | source = "registry+https://github.com/rust-lang/crates.io-index" 417 | checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" 418 | dependencies = [ 419 | "libsecp256k1-core", 420 | ] 421 | 422 | [[package]] 423 | name = "libsecp256k1-gen-genmult" 424 | version = "0.2.1" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" 427 | dependencies = [ 428 | "libsecp256k1-core", 429 | ] 430 | 431 | [[package]] 432 | name = "log" 433 | version = "0.4.14" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 436 | dependencies = [ 437 | "cfg-if 1.0.0", 438 | ] 439 | 440 | [[package]] 441 | name = "memchr" 442 | version = "2.3.4" 443 | source = "registry+https://github.com/rust-lang/crates.io-index" 444 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 445 | 446 | [[package]] 447 | name = "memmap2" 448 | version = "0.1.0" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "d9b70ca2a6103ac8b665dc150b142ef0e4e89df640c9e6cf295d189c3caebe5a" 451 | dependencies = [ 452 | "libc", 453 | ] 454 | 455 | [[package]] 456 | name = "num-derive" 457 | version = "0.3.3" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" 460 | dependencies = [ 461 | "proc-macro2", 462 | "quote", 463 | "syn", 464 | ] 465 | 466 | [[package]] 467 | name = "num-traits" 468 | version = "0.2.14" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 471 | dependencies = [ 472 | "autocfg", 473 | ] 474 | 475 | [[package]] 476 | name = "opaque-debug" 477 | version = "0.3.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 480 | 481 | [[package]] 482 | name = "ppv-lite86" 483 | version = "0.2.10" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 486 | 487 | [[package]] 488 | name = "proc-macro-crate" 489 | version = "0.1.5" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" 492 | dependencies = [ 493 | "toml", 494 | ] 495 | 496 | [[package]] 497 | name = "proc-macro2" 498 | version = "1.0.29" 499 | source = "registry+https://github.com/rust-lang/crates.io-index" 500 | checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" 501 | dependencies = [ 502 | "unicode-xid", 503 | ] 504 | 505 | [[package]] 506 | name = "quote" 507 | version = "1.0.10" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 510 | dependencies = [ 511 | "proc-macro2", 512 | ] 513 | 514 | [[package]] 515 | name = "rand" 516 | version = "0.7.3" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 519 | dependencies = [ 520 | "getrandom", 521 | "libc", 522 | "rand_chacha", 523 | "rand_core", 524 | "rand_hc", 525 | ] 526 | 527 | [[package]] 528 | name = "rand_chacha" 529 | version = "0.2.2" 530 | source = "registry+https://github.com/rust-lang/crates.io-index" 531 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 532 | dependencies = [ 533 | "ppv-lite86", 534 | "rand_core", 535 | ] 536 | 537 | [[package]] 538 | name = "rand_core" 539 | version = "0.5.1" 540 | source = "registry+https://github.com/rust-lang/crates.io-index" 541 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 542 | dependencies = [ 543 | "getrandom", 544 | ] 545 | 546 | [[package]] 547 | name = "rand_hc" 548 | version = "0.2.0" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 551 | dependencies = [ 552 | "rand_core", 553 | ] 554 | 555 | [[package]] 556 | name = "regex" 557 | version = "1.4.6" 558 | source = "registry+https://github.com/rust-lang/crates.io-index" 559 | checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" 560 | dependencies = [ 561 | "aho-corasick", 562 | "memchr", 563 | "regex-syntax", 564 | ] 565 | 566 | [[package]] 567 | name = "regex-syntax" 568 | version = "0.6.25" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 571 | 572 | [[package]] 573 | name = "rustc_version" 574 | version = "0.2.3" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 577 | dependencies = [ 578 | "semver", 579 | ] 580 | 581 | [[package]] 582 | name = "rustversion" 583 | version = "1.0.5" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" 586 | 587 | [[package]] 588 | name = "semver" 589 | version = "0.9.0" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 592 | dependencies = [ 593 | "semver-parser", 594 | ] 595 | 596 | [[package]] 597 | name = "semver-parser" 598 | version = "0.7.0" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 601 | 602 | [[package]] 603 | name = "serde" 604 | version = "1.0.130" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 607 | dependencies = [ 608 | "serde_derive", 609 | ] 610 | 611 | [[package]] 612 | name = "serde_bytes" 613 | version = "0.11.5" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" 616 | dependencies = [ 617 | "serde", 618 | ] 619 | 620 | [[package]] 621 | name = "serde_derive" 622 | version = "1.0.130" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" 625 | dependencies = [ 626 | "proc-macro2", 627 | "quote", 628 | "syn", 629 | ] 630 | 631 | [[package]] 632 | name = "sha2" 633 | version = "0.9.8" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" 636 | dependencies = [ 637 | "block-buffer", 638 | "cfg-if 1.0.0", 639 | "cpufeatures", 640 | "digest 0.9.0", 641 | "opaque-debug", 642 | ] 643 | 644 | [[package]] 645 | name = "sha3" 646 | version = "0.9.1" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" 649 | dependencies = [ 650 | "block-buffer", 651 | "digest 0.9.0", 652 | "keccak", 653 | "opaque-debug", 654 | ] 655 | 656 | [[package]] 657 | name = "solana-frozen-abi" 658 | version = "1.8.0" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "2701ac3f93f76c1be615e2f9d5d7a60ab7955f8c2852dbb9e7208f8463bfd36f" 661 | dependencies = [ 662 | "bs58", 663 | "bv", 664 | "generic-array 0.14.4", 665 | "log", 666 | "memmap2", 667 | "rustc_version", 668 | "serde", 669 | "serde_derive", 670 | "sha2", 671 | "solana-frozen-abi-macro", 672 | "solana-logger", 673 | "thiserror", 674 | ] 675 | 676 | [[package]] 677 | name = "solana-frozen-abi-macro" 678 | version = "1.8.0" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "918ae2b975c8c918a70babdd09d368d6c8fc0beccf7b8373a3d90bfeeddf9628" 681 | dependencies = [ 682 | "proc-macro2", 683 | "quote", 684 | "rustc_version", 685 | "syn", 686 | ] 687 | 688 | [[package]] 689 | name = "solana-logger" 690 | version = "1.8.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "ba9e90da01bb0da7f10a1b195de09f52a00eeecf11e849a278104d814135a511" 693 | dependencies = [ 694 | "env_logger", 695 | "lazy_static", 696 | "log", 697 | ] 698 | 699 | [[package]] 700 | name = "solana-program" 701 | version = "1.8.0" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "283f672457a081e0f7a76283887575bfa3fe969efcb25cd5c6c3299ec0261a6f" 704 | dependencies = [ 705 | "bincode", 706 | "blake3", 707 | "borsh", 708 | "borsh-derive", 709 | "bs58", 710 | "bv", 711 | "curve25519-dalek", 712 | "hex", 713 | "itertools", 714 | "lazy_static", 715 | "libsecp256k1", 716 | "log", 717 | "num-derive", 718 | "num-traits", 719 | "rand", 720 | "rustc_version", 721 | "rustversion", 722 | "serde", 723 | "serde_bytes", 724 | "serde_derive", 725 | "sha2", 726 | "sha3", 727 | "solana-frozen-abi", 728 | "solana-frozen-abi-macro", 729 | "solana-logger", 730 | "solana-sdk-macro", 731 | "thiserror", 732 | ] 733 | 734 | [[package]] 735 | name = "solana-sdk-macro" 736 | version = "1.8.0" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "ec0b5edf33d59c47d5f71583e08f8e9845716302f49301ed696e065fbbfb9e79" 739 | dependencies = [ 740 | "bs58", 741 | "proc-macro2", 742 | "quote", 743 | "rustversion", 744 | "syn", 745 | ] 746 | 747 | [[package]] 748 | name = "subtle" 749 | version = "2.4.1" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 752 | 753 | [[package]] 754 | name = "syn" 755 | version = "1.0.80" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" 758 | dependencies = [ 759 | "proc-macro2", 760 | "quote", 761 | "unicode-xid", 762 | ] 763 | 764 | [[package]] 765 | name = "termcolor" 766 | version = "1.1.2" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 769 | dependencies = [ 770 | "winapi-util", 771 | ] 772 | 773 | [[package]] 774 | name = "thiserror" 775 | version = "1.0.29" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" 778 | dependencies = [ 779 | "thiserror-impl", 780 | ] 781 | 782 | [[package]] 783 | name = "thiserror-impl" 784 | version = "1.0.29" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" 787 | dependencies = [ 788 | "proc-macro2", 789 | "quote", 790 | "syn", 791 | ] 792 | 793 | [[package]] 794 | name = "toml" 795 | version = "0.5.8" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 798 | dependencies = [ 799 | "serde", 800 | ] 801 | 802 | [[package]] 803 | name = "typenum" 804 | version = "1.14.0" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 807 | 808 | [[package]] 809 | name = "unicode-xid" 810 | version = "0.2.2" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 813 | 814 | [[package]] 815 | name = "version_check" 816 | version = "0.9.3" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 819 | 820 | [[package]] 821 | name = "wasi" 822 | version = "0.9.0+wasi-snapshot-preview1" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 825 | 826 | [[package]] 827 | name = "winapi" 828 | version = "0.3.9" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 831 | dependencies = [ 832 | "winapi-i686-pc-windows-gnu", 833 | "winapi-x86_64-pc-windows-gnu", 834 | ] 835 | 836 | [[package]] 837 | name = "winapi-i686-pc-windows-gnu" 838 | version = "0.4.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 841 | 842 | [[package]] 843 | name = "winapi-util" 844 | version = "0.1.5" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 847 | dependencies = [ 848 | "winapi", 849 | ] 850 | 851 | [[package]] 852 | name = "winapi-x86_64-pc-windows-gnu" 853 | version = "0.4.0" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 856 | 857 | [[package]] 858 | name = "zeroize" 859 | version = "1.4.2" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970" 862 | -------------------------------------------------------------------------------- /advanced/interact-with-program/echo/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hello" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | solana-program = "=1.8.0" 8 | 9 | [features] 10 | test-bpf = [] 11 | 12 | [lib] 13 | crate-type = ["cdylib", "lib"] 14 | -------------------------------------------------------------------------------- /advanced/interact-with-program/echo/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey, 3 | }; 4 | 5 | // 這裡就像是program的main 6 | // 所有program一進來都會先到你指定的這個entrypoint上 7 | // 在執行時會傳入三個參數 8 | // program id => 這個就是當前的program的id 9 | // accounts => 由client傳入,這次操作需要的account,solana在發交易的時候就要把所有需要的accoutn都帶入,無法在program上動態加載 10 | // instruction_data => 由client傳入,需要的data,會是一個uint8的array,如何切分就要跟client協調好。 11 | 12 | // here is like a main function 13 | // when an instruction executes, it will run into here first. 14 | // it will also pass three arguments 15 | // - program id => this is the current program id 16 | // - accounts => passed by client, you will need to pass all accounts you need in this operation, you can't load them dynamically 17 | // - instruction_data => passed by client, the data you need, the format will be uint8 array. how to separate them into meaningful segment depends your prgoram 18 | 19 | entrypoint!(process_instruction); 20 | fn process_instruction( 21 | program_id: &Pubkey, 22 | accounts: &[AccountInfo], 23 | instruction_data: &[u8], 24 | ) -> ProgramResult { 25 | msg!("program id {:?}", program_id); 26 | for account in accounts { 27 | msg!("accounts {:?}", account); 28 | } 29 | msg!("data {:?}", instruction_data); 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /advanced/interact-with-program/selector/client/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, Connection, TransactionInstruction, PublicKey, AccountMeta } from "@solana/web3.js"; 2 | 3 | // remember to check your fee payer has enough SOL to send tx 4 | 5 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 6 | let feePayer = Keypair.fromSecretKey( 7 | Uint8Array.from([ 8 | 206, 54, 90, 62, 42, 169, 79, 30, 10, 214, 71, 58, 161, 79, 210, 133, 123, 207, 196, 142, 168, 155, 129, 108, 35, 9 | 155, 218, 75, 82, 233, 79, 40, 67, 120, 93, 30, 66, 81, 199, 231, 199, 75, 70, 229, 64, 75, 252, 105, 43, 152, 135, 10 | 212, 92, 179, 44, 129, 174, 181, 26, 186, 90, 20, 83, 69, 11 | ]) 12 | ); 13 | 14 | // your program 15 | let programId = new PublicKey("GgMdxkeXtAhLtNw3Tx4H3Cnd7WDVAuXL7mTr5NovF2zF"); 16 | 17 | async function main() { 18 | let connection = new Connection("http://localhost:8899"); 19 | { 20 | let tx = new Transaction(); 21 | tx.add( 22 | new TransactionInstruction({ 23 | keys: [], 24 | data: Buffer.from(new Uint8Array([1])), 25 | programId: programId, 26 | }) 27 | ); 28 | console.log(`instruction 1's txhash: ${await connection.sendTransaction(tx, [feePayer])}`); 29 | } 30 | 31 | { 32 | let tx = new Transaction(); 33 | tx.add( 34 | new TransactionInstruction({ 35 | keys: [], 36 | data: Buffer.from(new Uint8Array([2])), 37 | programId: programId, 38 | }) 39 | ); 40 | console.log(`instruction 2's txhash: ${await connection.sendTransaction(tx, [feePayer])}`); 41 | } 42 | 43 | { 44 | let tx = new Transaction(); 45 | tx.add( 46 | new TransactionInstruction({ 47 | keys: [], 48 | data: Buffer.from(new Uint8Array([3])), 49 | programId: programId, 50 | }) 51 | ); 52 | // you can't send this tx because simulation failed. if you want to send it anyway, you can use the second way to do it. 53 | console.log(`unknown instruction's txhash: ${await connection.sendTransaction(tx, [feePayer])}`); 54 | console.log(`unknown instruction's txhash: ${await connection.sendTransaction(tx, [feePayer], {skipPreflight: true})}`); 55 | } 56 | } 57 | 58 | main().then( 59 | () => process.exit(), 60 | (err) => { 61 | console.error(err); 62 | process.exit(-1); 63 | } 64 | ); 65 | -------------------------------------------------------------------------------- /advanced/interact-with-program/selector/client/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, Connection, TransactionInstruction, PublicKey, AccountMeta } from "@solana/web3.js"; 2 | 3 | // 發交易前記得確認feePayer有錢 4 | 5 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 6 | let feePayer = Keypair.fromSecretKey( 7 | Uint8Array.from([ 8 | 206, 54, 90, 62, 42, 169, 79, 30, 10, 214, 71, 58, 161, 79, 210, 133, 123, 207, 196, 142, 168, 155, 129, 108, 35, 9 | 155, 218, 75, 82, 233, 79, 40, 67, 120, 93, 30, 66, 81, 199, 231, 199, 75, 70, 229, 64, 75, 252, 105, 43, 152, 135, 10 | 212, 92, 179, 44, 129, 174, 181, 26, 186, 90, 20, 83, 69, 11 | ]) 12 | ); 13 | 14 | // 你剛剛部署的program 15 | let programId = new PublicKey("GgMdxkeXtAhLtNw3Tx4H3Cnd7WDVAuXL7mTr5NovF2zF"); 16 | 17 | async function main() { 18 | let connection = new Connection("http://localhost:8899"); 19 | { 20 | let tx = new Transaction(); 21 | tx.add( 22 | new TransactionInstruction({ 23 | keys: [], 24 | data: Buffer.from(new Uint8Array([1])), // data 25 | programId: programId, 26 | }) 27 | ); 28 | console.log(`instruction 1's txhash: ${await connection.sendTransaction(tx, [feePayer])}`); 29 | } 30 | 31 | { 32 | let tx = new Transaction(); 33 | tx.add( 34 | new TransactionInstruction({ 35 | keys: [], 36 | data: Buffer.from(new Uint8Array([2])), // data 37 | programId: programId, 38 | }) 39 | ); 40 | console.log(`instruction 2's txhash: ${await connection.sendTransaction(tx, [feePayer])}`); 41 | } 42 | 43 | { 44 | let tx = new Transaction(); 45 | tx.add( 46 | new TransactionInstruction({ 47 | keys: [], 48 | data: Buffer.from(new Uint8Array([3])), // data 49 | programId: programId, 50 | }) 51 | ); 52 | // 你會發現這個交易送不出去,他會被模擬交易擋下,如果你很想要發上鏈,可以用下面方法2來把它發出去 53 | console.log(`unknown instruction's txhash: ${await connection.sendTransaction(tx, [feePayer])}`); 54 | console.log(`unknown instruction's txhash: ${await connection.sendTransaction(tx, [feePayer], {skipPreflight: true})}`); 55 | } 56 | } 57 | 58 | main().then( 59 | () => process.exit(), 60 | (err) => { 61 | console.error(err); 62 | process.exit(-1); 63 | } 64 | ); 65 | -------------------------------------------------------------------------------- /advanced/interact-with-program/selector/program/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yihau/solana-web3-demo/b69029f4817a731120cada843a9700bfad364a9b/advanced/interact-with-program/selector/program/.DS_Store -------------------------------------------------------------------------------- /advanced/interact-with-program/selector/program/.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /advanced/interact-with-program/selector/program/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "ahash" 7 | version = "0.4.7" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e" 10 | 11 | [[package]] 12 | name = "aho-corasick" 13 | version = "0.7.15" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" 16 | dependencies = [ 17 | "memchr", 18 | ] 19 | 20 | [[package]] 21 | name = "arrayref" 22 | version = "0.3.6" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 25 | 26 | [[package]] 27 | name = "arrayvec" 28 | version = "0.5.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 31 | 32 | [[package]] 33 | name = "atty" 34 | version = "0.2.14" 35 | source = "registry+https://github.com/rust-lang/crates.io-index" 36 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 37 | dependencies = [ 38 | "hermit-abi", 39 | "libc", 40 | "winapi", 41 | ] 42 | 43 | [[package]] 44 | name = "autocfg" 45 | version = "1.0.1" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" 48 | 49 | [[package]] 50 | name = "base64" 51 | version = "0.12.3" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" 54 | 55 | [[package]] 56 | name = "bincode" 57 | version = "1.3.3" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" 60 | dependencies = [ 61 | "serde", 62 | ] 63 | 64 | [[package]] 65 | name = "blake3" 66 | version = "0.3.8" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "b64485778c4f16a6a5a9d335e80d449ac6c70cdd6a06d2af18a6f6f775a125b3" 69 | dependencies = [ 70 | "arrayref", 71 | "arrayvec", 72 | "cc", 73 | "cfg-if 0.1.10", 74 | "constant_time_eq", 75 | "crypto-mac", 76 | "digest 0.9.0", 77 | ] 78 | 79 | [[package]] 80 | name = "block-buffer" 81 | version = "0.9.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" 84 | dependencies = [ 85 | "block-padding", 86 | "generic-array 0.14.4", 87 | ] 88 | 89 | [[package]] 90 | name = "block-padding" 91 | version = "0.2.1" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" 94 | 95 | [[package]] 96 | name = "borsh" 97 | version = "0.9.1" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "18dda7dc709193c0d86a1a51050a926dc3df1cf262ec46a23a25dba421ea1924" 100 | dependencies = [ 101 | "borsh-derive", 102 | "hashbrown", 103 | ] 104 | 105 | [[package]] 106 | name = "borsh-derive" 107 | version = "0.9.1" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "684155372435f578c0fa1acd13ebbb182cc19d6b38b64ae7901da4393217d264" 110 | dependencies = [ 111 | "borsh-derive-internal", 112 | "borsh-schema-derive-internal", 113 | "proc-macro-crate", 114 | "proc-macro2", 115 | "syn", 116 | ] 117 | 118 | [[package]] 119 | name = "borsh-derive-internal" 120 | version = "0.9.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "2102f62f8b6d3edeab871830782285b64cc1830168094db05c8e458f209bc5c3" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | "syn", 127 | ] 128 | 129 | [[package]] 130 | name = "borsh-schema-derive-internal" 131 | version = "0.9.1" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "196c978c4c9b0b142d446ef3240690bf5a8a33497074a113ff9a337ccb750483" 134 | dependencies = [ 135 | "proc-macro2", 136 | "quote", 137 | "syn", 138 | ] 139 | 140 | [[package]] 141 | name = "bs58" 142 | version = "0.3.1" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" 145 | 146 | [[package]] 147 | name = "bv" 148 | version = "0.11.1" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" 151 | dependencies = [ 152 | "feature-probe", 153 | "serde", 154 | ] 155 | 156 | [[package]] 157 | name = "byteorder" 158 | version = "1.4.3" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 161 | 162 | [[package]] 163 | name = "cc" 164 | version = "1.0.71" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" 167 | 168 | [[package]] 169 | name = "cfg-if" 170 | version = "0.1.10" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 173 | 174 | [[package]] 175 | name = "cfg-if" 176 | version = "1.0.0" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 179 | 180 | [[package]] 181 | name = "constant_time_eq" 182 | version = "0.1.5" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 185 | 186 | [[package]] 187 | name = "cpufeatures" 188 | version = "0.2.1" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" 191 | dependencies = [ 192 | "libc", 193 | ] 194 | 195 | [[package]] 196 | name = "crunchy" 197 | version = "0.2.2" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 200 | 201 | [[package]] 202 | name = "crypto-mac" 203 | version = "0.8.0" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" 206 | dependencies = [ 207 | "generic-array 0.14.4", 208 | "subtle", 209 | ] 210 | 211 | [[package]] 212 | name = "curve25519-dalek" 213 | version = "2.1.3" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "4a9b85542f99a2dfa2a1b8e192662741c9859a846b296bef1c92ef9b58b5a216" 216 | dependencies = [ 217 | "byteorder", 218 | "digest 0.8.1", 219 | "rand_core", 220 | "subtle", 221 | "zeroize", 222 | ] 223 | 224 | [[package]] 225 | name = "digest" 226 | version = "0.8.1" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" 229 | dependencies = [ 230 | "generic-array 0.12.4", 231 | ] 232 | 233 | [[package]] 234 | name = "digest" 235 | version = "0.9.0" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" 238 | dependencies = [ 239 | "generic-array 0.14.4", 240 | ] 241 | 242 | [[package]] 243 | name = "either" 244 | version = "1.6.1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 247 | 248 | [[package]] 249 | name = "env_logger" 250 | version = "0.8.4" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" 253 | dependencies = [ 254 | "atty", 255 | "humantime", 256 | "log", 257 | "regex", 258 | "termcolor", 259 | ] 260 | 261 | [[package]] 262 | name = "feature-probe" 263 | version = "0.1.1" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" 266 | 267 | [[package]] 268 | name = "generic-array" 269 | version = "0.12.4" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" 272 | dependencies = [ 273 | "typenum", 274 | ] 275 | 276 | [[package]] 277 | name = "generic-array" 278 | version = "0.14.4" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 281 | dependencies = [ 282 | "serde", 283 | "typenum", 284 | "version_check", 285 | ] 286 | 287 | [[package]] 288 | name = "getrandom" 289 | version = "0.1.16" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" 292 | dependencies = [ 293 | "cfg-if 1.0.0", 294 | "libc", 295 | "wasi", 296 | ] 297 | 298 | [[package]] 299 | name = "hashbrown" 300 | version = "0.9.1" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" 303 | dependencies = [ 304 | "ahash", 305 | ] 306 | 307 | [[package]] 308 | name = "hermit-abi" 309 | version = "0.1.19" 310 | source = "registry+https://github.com/rust-lang/crates.io-index" 311 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 312 | dependencies = [ 313 | "libc", 314 | ] 315 | 316 | [[package]] 317 | name = "hex" 318 | version = "0.4.3" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 321 | 322 | [[package]] 323 | name = "hmac" 324 | version = "0.8.1" 325 | source = "registry+https://github.com/rust-lang/crates.io-index" 326 | checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" 327 | dependencies = [ 328 | "crypto-mac", 329 | "digest 0.9.0", 330 | ] 331 | 332 | [[package]] 333 | name = "hmac-drbg" 334 | version = "0.3.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" 337 | dependencies = [ 338 | "digest 0.9.0", 339 | "generic-array 0.14.4", 340 | "hmac", 341 | ] 342 | 343 | [[package]] 344 | name = "humantime" 345 | version = "2.1.0" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 348 | 349 | [[package]] 350 | name = "itertools" 351 | version = "0.9.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" 354 | dependencies = [ 355 | "either", 356 | ] 357 | 358 | [[package]] 359 | name = "keccak" 360 | version = "0.1.0" 361 | source = "registry+https://github.com/rust-lang/crates.io-index" 362 | checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" 363 | 364 | [[package]] 365 | name = "lazy_static" 366 | version = "1.4.0" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 369 | 370 | [[package]] 371 | name = "libc" 372 | version = "0.2.103" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" 375 | 376 | [[package]] 377 | name = "libsecp256k1" 378 | version = "0.5.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "bd1137239ab33b41aa9637a88a28249e5e70c40a42ccc92db7f12cc356c1fcd7" 381 | dependencies = [ 382 | "arrayref", 383 | "base64", 384 | "digest 0.9.0", 385 | "hmac-drbg", 386 | "libsecp256k1-core", 387 | "libsecp256k1-gen-ecmult", 388 | "libsecp256k1-gen-genmult", 389 | "rand", 390 | "serde", 391 | "sha2", 392 | "typenum", 393 | ] 394 | 395 | [[package]] 396 | name = "libsecp256k1-core" 397 | version = "0.2.2" 398 | source = "registry+https://github.com/rust-lang/crates.io-index" 399 | checksum = "d0f6ab710cec28cef759c5f18671a27dae2a5f952cdaaee1d8e2908cb2478a80" 400 | dependencies = [ 401 | "crunchy", 402 | "digest 0.9.0", 403 | "subtle", 404 | ] 405 | 406 | [[package]] 407 | name = "libsecp256k1-gen-ecmult" 408 | version = "0.2.1" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "ccab96b584d38fac86a83f07e659f0deafd0253dc096dab5a36d53efe653c5c3" 411 | dependencies = [ 412 | "libsecp256k1-core", 413 | ] 414 | 415 | [[package]] 416 | name = "libsecp256k1-gen-genmult" 417 | version = "0.2.1" 418 | source = "registry+https://github.com/rust-lang/crates.io-index" 419 | checksum = "67abfe149395e3aa1c48a2beb32b068e2334402df8181f818d3aee2b304c4f5d" 420 | dependencies = [ 421 | "libsecp256k1-core", 422 | ] 423 | 424 | [[package]] 425 | name = "log" 426 | version = "0.4.14" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 429 | dependencies = [ 430 | "cfg-if 1.0.0", 431 | ] 432 | 433 | [[package]] 434 | name = "memchr" 435 | version = "2.3.4" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" 438 | 439 | [[package]] 440 | name = "memmap2" 441 | version = "0.1.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "d9b70ca2a6103ac8b665dc150b142ef0e4e89df640c9e6cf295d189c3caebe5a" 444 | dependencies = [ 445 | "libc", 446 | ] 447 | 448 | [[package]] 449 | name = "num-derive" 450 | version = "0.3.3" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" 453 | dependencies = [ 454 | "proc-macro2", 455 | "quote", 456 | "syn", 457 | ] 458 | 459 | [[package]] 460 | name = "num-traits" 461 | version = "0.2.14" 462 | source = "registry+https://github.com/rust-lang/crates.io-index" 463 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 464 | dependencies = [ 465 | "autocfg", 466 | ] 467 | 468 | [[package]] 469 | name = "opaque-debug" 470 | version = "0.3.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 473 | 474 | [[package]] 475 | name = "ppv-lite86" 476 | version = "0.2.10" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" 479 | 480 | [[package]] 481 | name = "proc-macro-crate" 482 | version = "0.1.5" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" 485 | dependencies = [ 486 | "toml", 487 | ] 488 | 489 | [[package]] 490 | name = "proc-macro2" 491 | version = "1.0.29" 492 | source = "registry+https://github.com/rust-lang/crates.io-index" 493 | checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d" 494 | dependencies = [ 495 | "unicode-xid", 496 | ] 497 | 498 | [[package]] 499 | name = "quote" 500 | version = "1.0.10" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 503 | dependencies = [ 504 | "proc-macro2", 505 | ] 506 | 507 | [[package]] 508 | name = "rand" 509 | version = "0.7.3" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 512 | dependencies = [ 513 | "getrandom", 514 | "libc", 515 | "rand_chacha", 516 | "rand_core", 517 | "rand_hc", 518 | ] 519 | 520 | [[package]] 521 | name = "rand_chacha" 522 | version = "0.2.2" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 525 | dependencies = [ 526 | "ppv-lite86", 527 | "rand_core", 528 | ] 529 | 530 | [[package]] 531 | name = "rand_core" 532 | version = "0.5.1" 533 | source = "registry+https://github.com/rust-lang/crates.io-index" 534 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 535 | dependencies = [ 536 | "getrandom", 537 | ] 538 | 539 | [[package]] 540 | name = "rand_hc" 541 | version = "0.2.0" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 544 | dependencies = [ 545 | "rand_core", 546 | ] 547 | 548 | [[package]] 549 | name = "regex" 550 | version = "1.4.6" 551 | source = "registry+https://github.com/rust-lang/crates.io-index" 552 | checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" 553 | dependencies = [ 554 | "aho-corasick", 555 | "memchr", 556 | "regex-syntax", 557 | ] 558 | 559 | [[package]] 560 | name = "regex-syntax" 561 | version = "0.6.25" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" 564 | 565 | [[package]] 566 | name = "rustc_version" 567 | version = "0.2.3" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 570 | dependencies = [ 571 | "semver", 572 | ] 573 | 574 | [[package]] 575 | name = "rustversion" 576 | version = "1.0.5" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088" 579 | 580 | [[package]] 581 | name = "selector" 582 | version = "0.1.0" 583 | dependencies = [ 584 | "solana-program", 585 | ] 586 | 587 | [[package]] 588 | name = "semver" 589 | version = "0.9.0" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 592 | dependencies = [ 593 | "semver-parser", 594 | ] 595 | 596 | [[package]] 597 | name = "semver-parser" 598 | version = "0.7.0" 599 | source = "registry+https://github.com/rust-lang/crates.io-index" 600 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 601 | 602 | [[package]] 603 | name = "serde" 604 | version = "1.0.130" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "f12d06de37cf59146fbdecab66aa99f9fe4f78722e3607577a5375d66bd0c913" 607 | dependencies = [ 608 | "serde_derive", 609 | ] 610 | 611 | [[package]] 612 | name = "serde_bytes" 613 | version = "0.11.5" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" 616 | dependencies = [ 617 | "serde", 618 | ] 619 | 620 | [[package]] 621 | name = "serde_derive" 622 | version = "1.0.130" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "d7bc1a1ab1961464eae040d96713baa5a724a8152c1222492465b54322ec508b" 625 | dependencies = [ 626 | "proc-macro2", 627 | "quote", 628 | "syn", 629 | ] 630 | 631 | [[package]] 632 | name = "sha2" 633 | version = "0.9.8" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa" 636 | dependencies = [ 637 | "block-buffer", 638 | "cfg-if 1.0.0", 639 | "cpufeatures", 640 | "digest 0.9.0", 641 | "opaque-debug", 642 | ] 643 | 644 | [[package]] 645 | name = "sha3" 646 | version = "0.9.1" 647 | source = "registry+https://github.com/rust-lang/crates.io-index" 648 | checksum = "f81199417d4e5de3f04b1e871023acea7389672c4135918f05aa9cbf2f2fa809" 649 | dependencies = [ 650 | "block-buffer", 651 | "digest 0.9.0", 652 | "keccak", 653 | "opaque-debug", 654 | ] 655 | 656 | [[package]] 657 | name = "solana-frozen-abi" 658 | version = "1.8.0" 659 | source = "registry+https://github.com/rust-lang/crates.io-index" 660 | checksum = "2701ac3f93f76c1be615e2f9d5d7a60ab7955f8c2852dbb9e7208f8463bfd36f" 661 | dependencies = [ 662 | "bs58", 663 | "bv", 664 | "generic-array 0.14.4", 665 | "log", 666 | "memmap2", 667 | "rustc_version", 668 | "serde", 669 | "serde_derive", 670 | "sha2", 671 | "solana-frozen-abi-macro", 672 | "solana-logger", 673 | "thiserror", 674 | ] 675 | 676 | [[package]] 677 | name = "solana-frozen-abi-macro" 678 | version = "1.8.0" 679 | source = "registry+https://github.com/rust-lang/crates.io-index" 680 | checksum = "918ae2b975c8c918a70babdd09d368d6c8fc0beccf7b8373a3d90bfeeddf9628" 681 | dependencies = [ 682 | "proc-macro2", 683 | "quote", 684 | "rustc_version", 685 | "syn", 686 | ] 687 | 688 | [[package]] 689 | name = "solana-logger" 690 | version = "1.8.0" 691 | source = "registry+https://github.com/rust-lang/crates.io-index" 692 | checksum = "ba9e90da01bb0da7f10a1b195de09f52a00eeecf11e849a278104d814135a511" 693 | dependencies = [ 694 | "env_logger", 695 | "lazy_static", 696 | "log", 697 | ] 698 | 699 | [[package]] 700 | name = "solana-program" 701 | version = "1.8.0" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "283f672457a081e0f7a76283887575bfa3fe969efcb25cd5c6c3299ec0261a6f" 704 | dependencies = [ 705 | "bincode", 706 | "blake3", 707 | "borsh", 708 | "borsh-derive", 709 | "bs58", 710 | "bv", 711 | "curve25519-dalek", 712 | "hex", 713 | "itertools", 714 | "lazy_static", 715 | "libsecp256k1", 716 | "log", 717 | "num-derive", 718 | "num-traits", 719 | "rand", 720 | "rustc_version", 721 | "rustversion", 722 | "serde", 723 | "serde_bytes", 724 | "serde_derive", 725 | "sha2", 726 | "sha3", 727 | "solana-frozen-abi", 728 | "solana-frozen-abi-macro", 729 | "solana-logger", 730 | "solana-sdk-macro", 731 | "thiserror", 732 | ] 733 | 734 | [[package]] 735 | name = "solana-sdk-macro" 736 | version = "1.8.0" 737 | source = "registry+https://github.com/rust-lang/crates.io-index" 738 | checksum = "ec0b5edf33d59c47d5f71583e08f8e9845716302f49301ed696e065fbbfb9e79" 739 | dependencies = [ 740 | "bs58", 741 | "proc-macro2", 742 | "quote", 743 | "rustversion", 744 | "syn", 745 | ] 746 | 747 | [[package]] 748 | name = "subtle" 749 | version = "2.4.1" 750 | source = "registry+https://github.com/rust-lang/crates.io-index" 751 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 752 | 753 | [[package]] 754 | name = "syn" 755 | version = "1.0.80" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194" 758 | dependencies = [ 759 | "proc-macro2", 760 | "quote", 761 | "unicode-xid", 762 | ] 763 | 764 | [[package]] 765 | name = "termcolor" 766 | version = "1.1.2" 767 | source = "registry+https://github.com/rust-lang/crates.io-index" 768 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 769 | dependencies = [ 770 | "winapi-util", 771 | ] 772 | 773 | [[package]] 774 | name = "thiserror" 775 | version = "1.0.29" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "602eca064b2d83369e2b2f34b09c70b605402801927c65c11071ac911d299b88" 778 | dependencies = [ 779 | "thiserror-impl", 780 | ] 781 | 782 | [[package]] 783 | name = "thiserror-impl" 784 | version = "1.0.29" 785 | source = "registry+https://github.com/rust-lang/crates.io-index" 786 | checksum = "bad553cc2c78e8de258400763a647e80e6d1b31ee237275d756f6836d204494c" 787 | dependencies = [ 788 | "proc-macro2", 789 | "quote", 790 | "syn", 791 | ] 792 | 793 | [[package]] 794 | name = "toml" 795 | version = "0.5.8" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" 798 | dependencies = [ 799 | "serde", 800 | ] 801 | 802 | [[package]] 803 | name = "typenum" 804 | version = "1.14.0" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 807 | 808 | [[package]] 809 | name = "unicode-xid" 810 | version = "0.2.2" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 813 | 814 | [[package]] 815 | name = "version_check" 816 | version = "0.9.3" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 819 | 820 | [[package]] 821 | name = "wasi" 822 | version = "0.9.0+wasi-snapshot-preview1" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 825 | 826 | [[package]] 827 | name = "winapi" 828 | version = "0.3.9" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 831 | dependencies = [ 832 | "winapi-i686-pc-windows-gnu", 833 | "winapi-x86_64-pc-windows-gnu", 834 | ] 835 | 836 | [[package]] 837 | name = "winapi-i686-pc-windows-gnu" 838 | version = "0.4.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 841 | 842 | [[package]] 843 | name = "winapi-util" 844 | version = "0.1.5" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 847 | dependencies = [ 848 | "winapi", 849 | ] 850 | 851 | [[package]] 852 | name = "winapi-x86_64-pc-windows-gnu" 853 | version = "0.4.0" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 856 | 857 | [[package]] 858 | name = "zeroize" 859 | version = "1.4.2" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "bf68b08513768deaa790264a7fac27a58cbf2705cfcdc9448362229217d7e970" 862 | -------------------------------------------------------------------------------- /advanced/interact-with-program/selector/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "selector" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | solana-program = "=1.8.0" 8 | 9 | [features] 10 | test-bpf = [] 11 | 12 | [lib] 13 | crate-type = ["cdylib", "lib"] 14 | -------------------------------------------------------------------------------- /advanced/interact-with-program/selector/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, 3 | program_error::ProgramError, pubkey::Pubkey, 4 | }; 5 | 6 | entrypoint!(process_instruction); 7 | fn process_instruction( 8 | _program_id: &Pubkey, 9 | _accounts: &[AccountInfo], 10 | data: &[u8], 11 | ) -> ProgramResult { 12 | // 我們可以拆第一個byte當作是selector 13 | // we can split first byte as selector 14 | let (&instruction, _rest) = data 15 | .split_first() 16 | .ok_or(ProgramError::InvalidInstructionData)?; 17 | 18 | match instruction { 19 | 1 => { 20 | msg!("here is instruction 1"); 21 | } 22 | 2 => { 23 | msg!("here is instruction 2"); 24 | } 25 | _ => { 26 | msg!("no such instruction"); 27 | return Err(ProgramError::InvalidInstructionData); 28 | } 29 | } 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /advanced/metaplex/get-tokenmeta/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from "@solana/web3.js"; 2 | import { Metadata } from "@metaplex-foundation/mpl-token-metadata"; 3 | 4 | // NFT is a mint. just like SRM, USDC ..., the only different is that NFT's supply is 1 5 | // 6 | // if we want to get NFT's metadata, first we need to know what is the mint address. 7 | // here I take a random DAPE as an example 8 | // https://explorer.solana.com/address/9MwGzSyuQRqmBHqmYwE6wbP3vzRBj4WWiYxWns3rkR7A 9 | // 10 | // tokenmeta is a PDA a which derived by mint address 11 | // the formula is ['metadata', metadata_program_id, mint_id] 12 | // is it totally fine to forget it because sdk already wrapped it for us 13 | 14 | const connection = new Connection("https://api.mainnet-beta.solana.com"); 15 | 16 | (async () => { 17 | let mintPubkey = new PublicKey("9MwGzSyuQRqmBHqmYwE6wbP3vzRBj4WWiYxWns3rkR7A"); 18 | let tokenmetaPubkey = await Metadata.getPDA(mintPubkey); 19 | 20 | const tokenmeta = await Metadata.load(connection, tokenmetaPubkey); 21 | console.log(tokenmeta); 22 | })(); 23 | -------------------------------------------------------------------------------- /advanced/metaplex/get-tokenmeta/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from "@solana/web3.js"; 2 | import { Metadata } from "@metaplex-foundation/mpl-token-metadata"; 3 | 4 | // NFT 其實就跟 SRM, USDC ... 一樣,只是一個mint,唯一不同的地方就是他的發行量是1 5 | // 6 | // 如果你想要抓取NFT的metadata,首先你要先知道該NFT的mint地址 7 | // 下面我隨便抓一個NFT來當做例子 8 | // https://explorer.solana.com/address/9MwGzSyuQRqmBHqmYwE6wbP3vzRBj4WWiYxWns3rkR7A 9 | // 10 | // tokenmeta會存在一個PDA內,他的算法是依據NFT的mint地址算出來的 11 | // => ['metadata', metadata_program_id, mint_id] 12 | // 只要知道就可以了,因為SDK都幫我們包好了 13 | 14 | const connection = new Connection("https://api.mainnet-beta.solana.com"); 15 | 16 | (async () => { 17 | let mintPubkey = new PublicKey("9MwGzSyuQRqmBHqmYwE6wbP3vzRBj4WWiYxWns3rkR7A"); 18 | let tokenmetaPubkey = await Metadata.getPDA(mintPubkey); 19 | 20 | const tokenmeta = await Metadata.load(connection, tokenmetaPubkey); 21 | console.log(tokenmeta); 22 | })(); 23 | 24 | -------------------------------------------------------------------------------- /advanced/metaplex/get-wallet-has-a-specific-nft/main.ts: -------------------------------------------------------------------------------- 1 | import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; 2 | import { Connection } from "@solana/web3.js"; 3 | 4 | (async () => { 5 | let connection = new Connection("https://solana-api.projectserum.com"); 6 | console.log( 7 | await connection.getProgramAccounts(TOKEN_PROGRAM_ID, { 8 | filters: [ 9 | { 10 | dataSize: 165, 11 | }, 12 | { 13 | memcmp: { 14 | offset: 0, 15 | bytes: "your mint address here", 16 | }, 17 | }, 18 | { 19 | memcmp: { 20 | offset: 32, 21 | bytes: "your owner address here", 22 | }, 23 | }, 24 | { 25 | memcmp: { 26 | offset: 64, 27 | bytes: "Ahg1opVcGX", // bs58 for [1,0,0,0,0,0,0,0], it is a byte array for u64 little endian 28 | }, 29 | }, 30 | ], 31 | dataSlice: { 32 | offset: 0, 33 | length: 0, 34 | }, 35 | }) 36 | ); 37 | })(); 38 | -------------------------------------------------------------------------------- /advanced/metaplex/mint-nft-new/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Connection, PublicKey, Transaction, SystemProgram } from "@solana/web3.js"; 2 | import base58 from "bs58"; 3 | import { 4 | createAssociatedTokenAccountInstruction, 5 | createInitializeMintInstruction, 6 | createMintToCheckedInstruction, 7 | getAssociatedTokenAddress, 8 | getMinimumBalanceForRentExemptMint, 9 | MINT_SIZE, 10 | TOKEN_PROGRAM_ID, 11 | } from "@solana/spl-token"; 12 | import { 13 | PROGRAM_ID as MPL_TOKEN_METADATA_PROGRAM_ID, 14 | createCreateMetadataAccountV2Instruction, 15 | createCreateMasterEditionV3Instruction, 16 | } from "@metaplex-foundation/mpl-token-metadata"; 17 | 18 | let connection = new Connection("https://api.devnet.solana.com"); 19 | 20 | // 5pVyoAeURQHNMVU7DmfMHvCDNmTEYXWfEwc136GYhTKG 21 | const feePayer = Keypair.fromSecretKey( 22 | base58.decode("5MaiiCavjCmn9Hs1o3eznqDEhRwxo7pXiAYez7keQUviUkauRiTMD8DrESdrNjN8zd9mTmVhRvBJeg5vhyvgrAhG") 23 | ); 24 | 25 | (async () => { 26 | let mint = Keypair.generate(); 27 | console.log(`mint: ${mint.publicKey.toBase58()}`); 28 | 29 | let ata = await getAssociatedTokenAddress(mint.publicKey, feePayer.publicKey); 30 | 31 | let tokenMetadataPubkey = await getMetadataPDA(mint.publicKey); 32 | 33 | let masterEditionPubkey = await getMasterEditionPDA(mint.publicKey); 34 | 35 | let tx = new Transaction().add( 36 | SystemProgram.createAccount({ 37 | fromPubkey: feePayer.publicKey, 38 | newAccountPubkey: mint.publicKey, 39 | lamports: await getMinimumBalanceForRentExemptMint(connection), 40 | space: MINT_SIZE, 41 | programId: TOKEN_PROGRAM_ID, 42 | }), 43 | createInitializeMintInstruction(mint.publicKey, 0, feePayer.publicKey, feePayer.publicKey), 44 | createAssociatedTokenAccountInstruction(feePayer.publicKey, ata, feePayer.publicKey, mint.publicKey), 45 | createMintToCheckedInstruction(mint.publicKey, ata, feePayer.publicKey, 1, 0), 46 | createCreateMetadataAccountV2Instruction( 47 | { 48 | metadata: tokenMetadataPubkey, 49 | mint: mint.publicKey, 50 | mintAuthority: feePayer.publicKey, 51 | payer: feePayer.publicKey, 52 | updateAuthority: feePayer.publicKey, 53 | }, 54 | { 55 | createMetadataAccountArgsV2: { 56 | data: { 57 | name: "Fake SMS #1355", 58 | symbol: "FSMB", 59 | uri: "https://34c7ef24f4v2aejh75xhxy5z6ars4xv47gpsdrei6fiowptk2nqq.arweave.net/3wXyF1wvK6ARJ_9ue-O58CMuXrz5nyHEiPFQ6z5q02E", 60 | sellerFeeBasisPoints: 100, 61 | creators: [ 62 | { 63 | address: feePayer.publicKey, 64 | verified: true, 65 | share: 100, 66 | }, 67 | ], 68 | collection: null, 69 | uses: null, 70 | }, 71 | isMutable: true, 72 | }, 73 | } 74 | ), 75 | createCreateMasterEditionV3Instruction( 76 | { 77 | edition: masterEditionPubkey, 78 | mint: mint.publicKey, 79 | updateAuthority: feePayer.publicKey, 80 | mintAuthority: feePayer.publicKey, 81 | payer: feePayer.publicKey, 82 | metadata: tokenMetadataPubkey, 83 | }, 84 | { 85 | createMasterEditionArgs: { 86 | maxSupply: 0, 87 | }, 88 | } 89 | ) 90 | ); 91 | 92 | console.log(await connection.sendTransaction(tx, [feePayer, mint])); 93 | })(); 94 | 95 | async function getMetadataPDA(mint: PublicKey): Promise { 96 | const [publicKey] = await PublicKey.findProgramAddress( 97 | [Buffer.from("metadata"), MPL_TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer()], 98 | MPL_TOKEN_METADATA_PROGRAM_ID 99 | ); 100 | return publicKey; 101 | } 102 | 103 | async function getMasterEditionPDA(mint: PublicKey): Promise { 104 | const [publicKey] = await PublicKey.findProgramAddress( 105 | [Buffer.from("metadata"), MPL_TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer(), Buffer.from("edition")], 106 | MPL_TOKEN_METADATA_PROGRAM_ID 107 | ); 108 | return publicKey; 109 | } 110 | -------------------------------------------------------------------------------- /advanced/metaplex/mint-nft/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Connection, PublicKey, Transaction, SystemProgram } from "@solana/web3.js"; 2 | import base58 from "bs58"; 3 | import { 4 | createAssociatedTokenAccountInstruction, 5 | createInitializeMintInstruction, 6 | createMintToCheckedInstruction, 7 | getAssociatedTokenAddress, 8 | getMinimumBalanceForRentExemptMint, 9 | MINT_SIZE, 10 | TOKEN_PROGRAM_ID, 11 | } from "@solana/spl-token"; 12 | import { 13 | PROGRAM_ID as MPL_TOKEN_METADATA_PROGRAM_ID, 14 | createCreateMetadataAccountInstruction, 15 | createCreateMasterEditionInstruction, 16 | } from "@metaplex-foundation/mpl-token-metadata"; 17 | 18 | let connection = new Connection("https://api.devnet.solana.com"); 19 | 20 | // 5pVyoAeURQHNMVU7DmfMHvCDNmTEYXWfEwc136GYhTKG 21 | const feePayer = Keypair.fromSecretKey( 22 | base58.decode("5MaiiCavjCmn9Hs1o3eznqDEhRwxo7pXiAYez7keQUviUkauRiTMD8DrESdrNjN8zd9mTmVhRvBJeg5vhyvgrAhG") 23 | ); 24 | 25 | (async () => { 26 | let mint = Keypair.generate(); 27 | console.log(`mint: ${mint.publicKey.toBase58()}`); 28 | 29 | let ata = await getAssociatedTokenAddress(mint.publicKey, feePayer.publicKey); 30 | 31 | let tokenMetadataPubkey = await getMetadataPDA(mint.publicKey); 32 | 33 | let masterEditionPubkey = await getMasterEditionPDA(mint.publicKey); 34 | 35 | let tx = new Transaction().add( 36 | SystemProgram.createAccount({ 37 | fromPubkey: feePayer.publicKey, 38 | newAccountPubkey: mint.publicKey, 39 | lamports: await getMinimumBalanceForRentExemptMint(connection), 40 | space: MINT_SIZE, 41 | programId: TOKEN_PROGRAM_ID, 42 | }), 43 | createInitializeMintInstruction(mint.publicKey, 0, feePayer.publicKey, feePayer.publicKey), 44 | createAssociatedTokenAccountInstruction(feePayer.publicKey, ata, feePayer.publicKey, mint.publicKey), 45 | createMintToCheckedInstruction(mint.publicKey, ata, feePayer.publicKey, 1, 0), 46 | createCreateMetadataAccountInstruction( 47 | { 48 | metadata: tokenMetadataPubkey, 49 | mint: mint.publicKey, 50 | mintAuthority: feePayer.publicKey, 51 | payer: feePayer.publicKey, 52 | updateAuthority: feePayer.publicKey, 53 | }, 54 | { 55 | createMetadataAccountArgs: { 56 | data: { 57 | name: "Fake SMS #1355", 58 | symbol: "FSMB", 59 | uri: "https://34c7ef24f4v2aejh75xhxy5z6ars4xv47gpsdrei6fiowptk2nqq.arweave.net/3wXyF1wvK6ARJ_9ue-O58CMuXrz5nyHEiPFQ6z5q02E", 60 | sellerFeeBasisPoints: 100, 61 | creators: [ 62 | { 63 | address: feePayer.publicKey, 64 | verified: true, 65 | share: 100, 66 | }, 67 | ], 68 | }, 69 | isMutable: true, 70 | }, 71 | } 72 | ), 73 | createCreateMasterEditionInstruction( 74 | { 75 | edition: masterEditionPubkey, 76 | mint: mint.publicKey, 77 | updateAuthority: feePayer.publicKey, 78 | mintAuthority: feePayer.publicKey, 79 | payer: feePayer.publicKey, 80 | metadata: tokenMetadataPubkey, 81 | }, 82 | { 83 | createMasterEditionArgs: { 84 | maxSupply: 0, 85 | }, 86 | } 87 | ) 88 | ); 89 | 90 | console.log(await connection.sendTransaction(tx, [feePayer, mint])); 91 | })(); 92 | 93 | async function getMetadataPDA(mint: PublicKey): Promise { 94 | const [publicKey] = await PublicKey.findProgramAddress( 95 | [Buffer.from("metadata"), MPL_TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer()], 96 | MPL_TOKEN_METADATA_PROGRAM_ID 97 | ); 98 | return publicKey; 99 | } 100 | 101 | async function getMasterEditionPDA(mint: PublicKey): Promise { 102 | const [publicKey] = await PublicKey.findProgramAddress( 103 | [Buffer.from("metadata"), MPL_TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer(), Buffer.from("edition")], 104 | MPL_TOKEN_METADATA_PROGRAM_ID 105 | ); 106 | return publicKey; 107 | } 108 | -------------------------------------------------------------------------------- /advanced/send-tx/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Keypair, 3 | Transaction, 4 | SystemProgram, 5 | sendAndConfirmTransaction, 6 | sendAndConfirmRawTransaction, 7 | } from "@solana/web3.js"; 8 | import { CONNECTION, FEE_PAYER } from "../../helper/const"; 9 | 10 | // 發送tx 11 | 12 | // 在前面的例子我們都是用用sendTransaction的方式來發送交易,這邊介紹另外兩種方式 13 | // 可以依照不同情況自己選擇要使用哪一種 14 | 15 | async function main() { 16 | // 這邊拿transfer的tx當作例子 17 | let to = Keypair.generate(); 18 | console.log(`to: ${to.publicKey.toBase58()}`); 19 | 20 | let tx = new Transaction(); 21 | tx.add( 22 | SystemProgram.transfer({ 23 | fromPubkey: FEE_PAYER.publicKey, 24 | toPubkey: to.publicKey, 25 | lamports: 1, 26 | }) 27 | ); 28 | tx.feePayer = FEE_PAYER.publicKey; 29 | 30 | // 1. 簽名 + 發送 31 | // 只管發出交易,不管鏈上區塊狀況 (前面的基礎範例都是使用這種) 32 | // console.log(`txhash: ${await CONNECTION.sendTransaction(tx, [FEE_PAYER])}`); 33 | 34 | // 2. 簽名 + 發送 + 確認 35 | // 這個方法會等到區塊確認完成之後才會回傳txhash 36 | // console.log(`txhash: ${await sendAndConfirmTransaction(CONNECTION, tx, [FEE_PAYER])}`); 37 | 38 | // 3. 簽名 => 發送 39 | // 前兩種方式都是web3幫我們包好的方法,可以一鍵發送交易 40 | // 第三種比較接近跟原始的rpc互動 41 | // 對這種方法有概念後轉去其他的語言開發比較不會卡關 42 | // 在上面設定完 instruction 和 feePayer之後 43 | // 這邊會需要再多設定一個東西 `blockhash` 44 | // solana在發送交易時會需要將最近的blockhash包進tx內一起簽名送出 45 | // 如果鏈上發現你帶的blockhash距離鏈上最新的blockhash太遠的話 46 | // 會直接拒絕tx,如果你有需要暫存交易一陣子才發出的需求,可以參閱durable nonce篇的介紹 47 | 48 | // a. recent blockhash 49 | tx.recentBlockhash = (await CONNECTION.getRecentBlockhash()).blockhash; 50 | 51 | // b. 簽名 52 | tx.sign(...[FEE_PAYER]); 53 | 54 | // c. 序列化交易 55 | const rawTx = tx.serialize(); 56 | 57 | // d. base64 encode 58 | const encodedTransaction = Buffer.from(rawTx).toString("base64"); 59 | console.log(`txhash: ${await CONNECTION.sendEncodedTransaction(encodedTransaction)}`); 60 | 61 | // d步驟可以使用以下方法取代,上面是為了把完整步驟真實呈現 62 | // console.log(`txhash: ${await CONNECTION.sendRawTransaction(rawTx)}`); 63 | } 64 | 65 | main().then( 66 | () => process.exit(), 67 | (err) => { 68 | console.error(err); 69 | process.exit(-1); 70 | } 71 | ); 72 | -------------------------------------------------------------------------------- /advanced/solana-intro/README.md: -------------------------------------------------------------------------------- 1 | # 2 | 3 | ## 開始 4 | 5 | 首先如果想要開發solana,非常建議在本地端安裝solana的cli工具。他可以啟動local的節點以及包裝了許多方便的函式,在開發上可以變得更加快速。 6 | 7 | 可以參考[這裡](https://docs.solana.com/cli)的前幾篇,可以不用寫任何code就先體驗一下solana 8 | 9 | 如果有裝好cli工具的話可以在本地用 10 | 11 | 12 | ``` 13 | solana-test-validator 14 | ``` 15 | 16 | 來啟動一個測試節點加速開發流程 (目前windows還沒有支援使用) 17 | 18 | [官方的explorer](https://explorer.solana.com/)可以指定custom的網址 19 | 20 | 也就是說可以指定 http://localhost:8899 來對應到本地的節點方便看交易訊息。 21 | 22 | 23 | ## 世界觀 24 | 25 | ### 概述 26 | 27 | Solana跟Ethereum在設計概念上不太一樣。 28 | 29 | 在Ethereum上面大家都是把資料都塞到contract上,account跟contract互動結束之後,contract會把相對應的結果存在自己身上。但在solana的contract(官方稱呼為program,以下也都會用program來稱呼)只有邏輯的部分,而儲存資料的地方是account。 30 | 31 | ### Account 32 | 33 | Solana的Account有幾個比較重要的觀念 34 | 35 | #### Owner 36 | 37 | account的owner通常都是某一個program,一般我們持有的最基本的account都是屬於system program。而當只有account屬於某一個program的時候,這個program才有辦法寫資料進去。如果違反這個規定交易是會直接失敗的。但讀取就沒有這個限制,只要你不修改到裡面的資料,可以任意讀去任何想要的account。 38 | 39 | #### Rent 40 | 41 | 前面有提到account是拿來存資料的,而你的帳戶也會因為儲存資料的不同而收取一個類似`資料管理費`的錢。收的時機是 42 | 43 | 1. 你的帳戶有被包含到交易內 44 | 2. 每一個epoch結束 45 | 46 | 幣種都是原生代幣SOL,如果這個錢被扣到沒有的話,資料就會全部消失! 47 | 48 | 官方有給另外一個免收租的方式,就是你在這個account裡面一開始就給予足夠兩年份的租金份額。這樣你的帳戶會被標記成免租金帳戶,從此之後都不會跟你收錢。 49 | 50 | #### Program Derived Address 51 | 52 | 大家都知道在做tx的時候的時候,可以透過所屬的private key來簽名,達到授權的作用。而在Solana裡面不僅僅是普通的private key可以簽名,Program 也可以簽名。而這種只能被Program簽名授權的account官方稱之為 Program Derived Address。 53 | 54 | 這種Account的好處在於,僅有Program可以簽名。代表著相關授權都只能透過Program的條件來觸發。 55 | 56 | ### Transaction 57 | 58 | Solana的Tx結構比較不一樣,可以包含很多instruction。 59 | 60 | 同一筆Tx可以做到A轉帳給B,B轉帳給C,C轉帳給A的這種麻煩的操作。 61 | 62 | --- 63 | 關於詳細資訊,建議可以花點時間把這兩篇看完 64 | 65 | 1. https://docs.solana.com/developing/programming-model/accounts 66 | 2. https://docs.solana.com/developing/programming-model/calling-between-programs 67 | 68 | -------------------------------------------------------------------------------- /advanced/token/README.en.md: -------------------------------------------------------------------------------- 1 | # Token 2 | 3 | * [close token account](close-account/main.en.ts) 4 | * [wrapped SOL](wrapped-sol/) 5 | * [get all token accounts by owner](get-all-token-account-by-owner/main.en.ts) -------------------------------------------------------------------------------- /advanced/token/README.md: -------------------------------------------------------------------------------- 1 | # Token 2 | 3 | * [關閉token帳戶](close-account/main.ts) 4 | * [Wrapped SOL](wrapped-sol/) 5 | * [查詢使用者的代幣帳戶](get-all-token-account-by-owner/main.ts) -------------------------------------------------------------------------------- /advanced/token/close-account/main.en.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, Connection, Transaction } from "@solana/web3.js"; 2 | import { CONNECTION, ALICE, TEST_MINT, FEE_PAYER } from "../../../helper/const"; 3 | import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, NATIVE_MINT, Token } from "@solana/spl-token"; 4 | 5 | // close token account 6 | // if you don't want to use the token account, you can close it and retrieve rent back. 7 | // it has different limitation by different mint 8 | // 1. for wrapped SOL, you can close it directly, no limitation 9 | // 2. for other mint(like USDC, SRM ...), you need to transfer all token out first, make token balance 0, then you can close it 10 | 11 | async function main() { 12 | let tx = new Transaction(); 13 | tx.add( 14 | Token.createCloseAccountInstruction( 15 | TOKEN_PROGRAM_ID, // fixed 16 | new PublicKey("Dmysc2pPCGQSzkgkMtcZAtGFzYa7DzohqBxh7aF1YxG3"), // to be closed token account 17 | ALICE.publicKey, // rent's destination 18 | ALICE.publicKey, // token account authority 19 | [] // multisig 20 | ) 21 | ); 22 | tx.feePayer = FEE_PAYER.publicKey; 23 | 24 | console.log(`txhash: ${await CONNECTION.sendTransaction(tx, [ALICE, FEE_PAYER])}`); 25 | } 26 | 27 | main().then( 28 | () => process.exit(), 29 | (err) => { 30 | console.error(err); 31 | process.exit(-1); 32 | } 33 | ); 34 | -------------------------------------------------------------------------------- /advanced/token/close-account/main.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey, Connection, Transaction } from "@solana/web3.js"; 2 | import { CONNECTION, ALICE, TEST_MINT, FEE_PAYER } from "../../../helper/const"; 3 | import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID, NATIVE_MINT, Token } from "@solana/spl-token"; 4 | 5 | // close token account 6 | // 當某個token account你不想要再使用了,你可以使用此操作把裡面的rent回收。 7 | // 但在close的時候會因為mint不同而有不同限制 8 | // 1. 如果是 wrapped SOL, 你可以直接close, 沒有限制 9 | // 2. 如果是 其他的mint(USDC, SRM ...), 你需要先把token都轉出,讓token balance變成0之後才能close 10 | 11 | async function main() { 12 | let tx = new Transaction(); 13 | tx.add( 14 | Token.createCloseAccountInstruction( 15 | TOKEN_PROGRAM_ID, // 定值 16 | new PublicKey("Dmysc2pPCGQSzkgkMtcZAtGFzYa7DzohqBxh7aF1YxG3"), // 要回收的token account 17 | ALICE.publicKey, // rent要回收的地址 18 | ALICE.publicKey, // token account authority 19 | [] // multisig 20 | ) 21 | ); 22 | tx.feePayer = FEE_PAYER.publicKey; 23 | 24 | console.log(`txhash: ${await CONNECTION.sendTransaction(tx, [ALICE, FEE_PAYER])}`); 25 | } 26 | 27 | main().then( 28 | () => process.exit(), 29 | (err) => { 30 | console.error(err); 31 | process.exit(-1); 32 | } 33 | ); 34 | -------------------------------------------------------------------------------- /advanced/token/get-NFT-current-owner/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from "@solana/web3.js"; 2 | import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; 3 | import * as bs58 from "bs58"; 4 | // get NFT current owner 5 | 6 | const connection = new Connection("https://api.mainnet-beta.solana.com"); 7 | 8 | (async () => { 9 | let accountInfos = await connection.getProgramAccounts(TOKEN_PROGRAM_ID, { 10 | encoding: "base64", 11 | filters: [ 12 | { 13 | dataSize: 165, 14 | }, 15 | { 16 | memcmp: { 17 | offset: 0, 18 | bytes: "9MwGzSyuQRqmBHqmYwE6wbP3vzRBj4WWiYxWns3rkR7A", // your mint here 19 | }, 20 | }, 21 | { 22 | memcmp: { 23 | offset: 64, 24 | bytes: "Ahg1opVcGX", // this is base58 encode [1,0,0,0,0,0,0,0] 25 | }, 26 | }, 27 | ], 28 | }); 29 | 30 | // if everything goes right, it will be an array which only contain one item. 31 | // the item is a token account which hold the NFT. 32 | console.log(accountInfos); 33 | 34 | // you can parse its data to retrieve the real owner (SOL address) 35 | console.log(`owner: ${bs58.encode(accountInfos[0].account.data.slice(32, 64))}`); 36 | })(); 37 | -------------------------------------------------------------------------------- /advanced/token/get-all-token-account-by-owner/main.en.ts: -------------------------------------------------------------------------------- 1 | import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; 2 | import { Connection, PublicKey } from "@solana/web3.js"; 3 | import * as SPLToken from "@solana/spl-token"; 4 | 5 | // get token accounts by owner 6 | 7 | const connection = new Connection("https://api.devnet.solana.com"); 8 | 9 | async function main() { 10 | // 1. you can fetch all token account by an owner 11 | let response = await connection.getTokenAccountsByOwner( 12 | new PublicKey("27kVX7JpPZ1bsrSckbR76mV6GeRqtrjoddubfg2zBpHZ"), // owner here 13 | { 14 | programId: TOKEN_PROGRAM_ID, 15 | } 16 | ); 17 | response.value.forEach((e) => { 18 | console.log(`pubkey: ${e.pubkey.toBase58()}`); 19 | const accountInfo = SPLToken.AccountLayout.decode(e.account.data); 20 | console.log(`mint: ${new PublicKey(accountInfo.mint)}`); 21 | console.log(`amount: ${SPLToken.u64.fromBuffer(accountInfo.amount)}`); 22 | }); 23 | 24 | console.log("-------------------"); 25 | 26 | // 2. or just fetch specific mint for a owner 27 | let response2 = await connection.getTokenAccountsByOwner( 28 | new PublicKey("27kVX7JpPZ1bsrSckbR76mV6GeRqtrjoddubfg2zBpHZ"), // owner here 29 | { 30 | mint: new PublicKey("E4ZN2KmnVmpwLwjJNAwRjuQLeE5iFHLcAJ8LGB7FMaGQ"), 31 | } 32 | ); 33 | response2.value.forEach((e) => { 34 | console.log(`pubkey: ${e.pubkey.toBase58()}`); 35 | const accountInfo = SPLToken.AccountLayout.decode(e.account.data); 36 | console.log(`mint: ${new PublicKey(accountInfo.mint)}`); 37 | console.log(`amount: ${SPLToken.u64.fromBuffer(accountInfo.amount)}`); 38 | }); 39 | } 40 | 41 | main().then( 42 | () => process.exit(), 43 | (err) => { 44 | console.error(err); 45 | process.exit(-1); 46 | } 47 | ); 48 | -------------------------------------------------------------------------------- /advanced/token/get-all-token-account-by-owner/main.ts: -------------------------------------------------------------------------------- 1 | import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; 2 | import { Connection, PublicKey } from "@solana/web3.js"; 3 | import * as SPLToken from "@solana/spl-token"; 4 | 5 | // 用owner來抓取所有代幣帳戶 6 | 7 | const connection = new Connection("https://api.devnet.solana.com"); 8 | 9 | async function main() { 10 | // 1. 抓取所有onwer所有擁有的代幣帳戶 11 | let response = await connection.getTokenAccountsByOwner( 12 | new PublicKey("27kVX7JpPZ1bsrSckbR76mV6GeRqtrjoddubfg2zBpHZ"), // owner here 13 | { 14 | programId: TOKEN_PROGRAM_ID, 15 | } 16 | ); 17 | response.value.forEach((e) => { 18 | console.log(`pubkey: ${e.pubkey.toBase58()}`); 19 | const accountInfo = SPLToken.AccountLayout.decode(e.account.data); 20 | console.log(`mint: ${new PublicKey(accountInfo.mint)}`); 21 | console.log(`amount: ${SPLToken.u64.fromBuffer(accountInfo.amount)}`); 22 | }); 23 | 24 | console.log("-------------------"); 25 | 26 | // 2. 也可以抓取owner持有特定mint的所有帳戶 27 | let response2 = await connection.getTokenAccountsByOwner( 28 | new PublicKey("27kVX7JpPZ1bsrSckbR76mV6GeRqtrjoddubfg2zBpHZ"), // owner here 29 | { 30 | mint: new PublicKey("E4ZN2KmnVmpwLwjJNAwRjuQLeE5iFHLcAJ8LGB7FMaGQ"), // mint (token) 31 | } 32 | ); 33 | response2.value.forEach((e) => { 34 | console.log(`pubkey: ${e.pubkey.toBase58()}`); 35 | const accountInfo = SPLToken.AccountLayout.decode(e.account.data); 36 | console.log(`mint: ${new PublicKey(accountInfo.mint)}`); 37 | console.log(`amount: ${SPLToken.u64.fromBuffer(accountInfo.amount)}`); 38 | }); 39 | } 40 | 41 | main().then( 42 | () => process.exit(), 43 | (err) => { 44 | console.error(err); 45 | process.exit(-1); 46 | } 47 | ); 48 | -------------------------------------------------------------------------------- /advanced/token/set-authority/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from "@solana/web3.js"; 2 | import { CONNECTION, TEST_MINT, FEE_PAYER } from "../../../helper/const"; 3 | import { TOKEN_PROGRAM_ID, Token } from "@solana/spl-token"; 4 | 5 | // you can set/update your account's auth 6 | // there are 4 types => 'MintTokens' | 'FreezeAccount' | 'AccountOwner' | 'CloseAccount' 7 | // MintTokens and FreezeAccount are for mint account 8 | // AccountOwner and CloseAccount are for token account 9 | 10 | async function main() { 11 | let tx = new Transaction(); 12 | tx.add( 13 | Token.createSetAuthorityInstruction( 14 | TOKEN_PROGRAM_ID, // always token program. 15 | TEST_MINT, // mint acocunt or token account 16 | null, // new auth, if you want to turn off the auth, just pass null 17 | "MintTokens", // authority type, there are 4 types => 'MintTokens' | 'FreezeAccount' | 'AccountOwner' | 'CloseAccount' 18 | FEE_PAYER.publicKey, // original auth 19 | [] 20 | ) 21 | ); 22 | tx.feePayer = FEE_PAYER.publicKey; 23 | console.log(`txhash: ${await CONNECTION.sendTransaction(tx, [FEE_PAYER])}`); 24 | } 25 | 26 | main().then( 27 | () => process.exit(), 28 | (err) => { 29 | console.error(err); 30 | process.exit(-1); 31 | } 32 | ); 33 | -------------------------------------------------------------------------------- /advanced/token/set-authority/main.ts: -------------------------------------------------------------------------------- 1 | import { Transaction } from "@solana/web3.js"; 2 | import { CONNECTION, TEST_MINT, FEE_PAYER } from "../../../helper/const"; 3 | import { TOKEN_PROGRAM_ID, Token } from "@solana/spl-token"; 4 | 5 | // 你可以設定/更新你的帳戶權限 6 | // 這裡有四種權限可以指定更新 => 'MintTokens' | 'FreezeAccount' | 'AccountOwner' | 'CloseAccount' 7 | // MintTokens 和 FreezeAccount 是給mint account使用的 8 | // AccountOwner 和 CloseAccount 是給token account使用的 9 | 10 | async function main() { 11 | let tx = new Transaction(); 12 | tx.add( 13 | Token.createSetAuthorityInstruction( 14 | TOKEN_PROGRAM_ID, // token program id 15 | TEST_MINT, // 目標帳戶 (這邊用mint account來當例子) 16 | null, // 新的數值, 如果你想要拔掉該權限,可以直接傳null 17 | "MintTokens", // 剛剛所說的有四個型別的權限 18 | FEE_PAYER.publicKey, // 原本這個權限的帳戶 19 | [] 20 | ) 21 | ); 22 | tx.feePayer = FEE_PAYER.publicKey; 23 | console.log(`txhash: ${await CONNECTION.sendTransaction(tx, [FEE_PAYER])}`); 24 | } 25 | 26 | main().then( 27 | () => process.exit(), 28 | (err) => { 29 | console.error(err); 30 | process.exit(-1); 31 | } 32 | ); 33 | -------------------------------------------------------------------------------- /advanced/token/wrapped-sol/add-balance/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, SystemProgram, TransactionInstruction} from "@solana/web3.js"; 2 | 3 | import { ALICE, CONNECTION, FEE_PAYER, TEST_MINT } from "../../../../helper/const"; 4 | 5 | import * as SPLToken from "@solana/spl-token"; 6 | 7 | // add balance 8 | 9 | // wrapped sol can't use `mint to`. here are two ways we can increase our wrapped sol balance 10 | 11 | // 1. use tmp account 12 | // a. create a tmp account with init balance which is rent + amount 13 | // b. use `token transfer` to trasnfer wrapped sol to our token account 14 | // c. close the tmp account 15 | 16 | // 2. SyncNative 17 | // a. transfer SOL to our token account 18 | // b. do `SyncNative` instruction 19 | 20 | async function main() { 21 | let ata = await SPLToken.Token.getAssociatedTokenAddress( 22 | SPLToken.ASSOCIATED_TOKEN_PROGRAM_ID, 23 | SPLToken.TOKEN_PROGRAM_ID, 24 | SPLToken.NATIVE_MINT, 25 | ALICE.publicKey 26 | ); 27 | console.log(`ata: ${ata.toBase58()}`); 28 | 29 | // 1. use tmp account 30 | 31 | let tmpAccount = Keypair.generate(); 32 | // wrapped sol's decimals is 9 33 | let amount = 1e9; 34 | let tx1 = new Transaction(); 35 | tx1.add( 36 | SystemProgram.createAccount({ 37 | fromPubkey: FEE_PAYER.publicKey, 38 | newAccountPubkey: tmpAccount.publicKey, 39 | space: SPLToken.AccountLayout.span, 40 | lamports: (await SPLToken.Token.getMinBalanceRentForExemptAccount(CONNECTION)) + amount, // rent + amount 41 | programId: SPLToken.TOKEN_PROGRAM_ID, 42 | }), 43 | SPLToken.Token.createInitAccountInstruction( 44 | SPLToken.TOKEN_PROGRAM_ID, 45 | SPLToken.NATIVE_MINT, 46 | tmpAccount.publicKey, 47 | ALICE.publicKey 48 | ), 49 | SPLToken.Token.createTransferInstruction( 50 | SPLToken.TOKEN_PROGRAM_ID, 51 | tmpAccount.publicKey, 52 | ata, 53 | ALICE.publicKey, 54 | [], 55 | amount 56 | ), 57 | SPLToken.Token.createCloseAccountInstruction( 58 | SPLToken.TOKEN_PROGRAM_ID, 59 | tmpAccount.publicKey, 60 | ALICE.publicKey, 61 | ALICE.publicKey, 62 | [] 63 | ) 64 | ); 65 | tx1.feePayer = FEE_PAYER.publicKey; 66 | console.log(`tx1 txhash: ${await CONNECTION.sendTransaction(tx1, [FEE_PAYER, tmpAccount, ALICE])}`); 67 | 68 | // 2. SyncNative 69 | let tx2 = new Transaction(); 70 | tx2.add( 71 | SystemProgram.transfer({ 72 | fromPubkey: ALICE.publicKey, 73 | toPubkey: ata, 74 | lamports: amount, 75 | }), 76 | // I worte raw instruction because @solana/spl-token havn't export this interface out 77 | new TransactionInstruction({ 78 | keys: [ 79 | { 80 | pubkey: ata, 81 | isSigner: false, 82 | isWritable: true, 83 | }, 84 | ], 85 | data: Buffer.from(new Uint8Array([17])), 86 | programId: SPLToken.TOKEN_PROGRAM_ID, 87 | }) 88 | ); 89 | tx2.feePayer = FEE_PAYER.publicKey; 90 | 91 | console.log(`ata txhash: ${await CONNECTION.sendTransaction(tx2, [FEE_PAYER, ALICE])}`); 92 | } 93 | 94 | main().then( 95 | () => process.exit(), 96 | (err) => { 97 | console.error(err); 98 | process.exit(-1); 99 | } 100 | ); 101 | -------------------------------------------------------------------------------- /advanced/token/wrapped-sol/add-balance/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, SystemProgram, TransactionInstruction} from "@solana/web3.js"; 2 | 3 | import { ALICE, CONNECTION, FEE_PAYER, TEST_MINT } from "../../../../helper/const"; 4 | 5 | import * as SPLToken from "@solana/spl-token"; 6 | 7 | // 增加餘額 8 | 9 | // wrapped sol要增加餘額的方式就不是像是一般mint那樣可以使用mint to,下面有兩種方法可以達到我們的目的 10 | 11 | // 1. 暫時的token account 12 | // a. 先創建一個隨機的帳戶,並把我們需要的wrapped SOL amount也一起打進去 (所以總共會是 rent + amount) 13 | // b. 使用轉帳把剛剛的錢打到我們的目標帳戶上 14 | // c. 把我們剛剛的暫時的帳戶回收 15 | 16 | // 2. SyncNative 17 | // a. 轉SOL到token帳戶內 18 | // b. 做SyncNative來刷新餘額 19 | 20 | async function main() { 21 | let ata = await SPLToken.Token.getAssociatedTokenAddress( 22 | SPLToken.ASSOCIATED_TOKEN_PROGRAM_ID, 23 | SPLToken.TOKEN_PROGRAM_ID, 24 | SPLToken.NATIVE_MINT, 25 | ALICE.publicKey 26 | ); 27 | console.log(`ata: ${ata.toBase58()}`); 28 | 29 | // 1. 暫時的token account 30 | 31 | let tmpAccount = Keypair.generate(); 32 | // wrapped sol的decimals是9 33 | let amount = 1e9; 34 | let tx1 = new Transaction(); 35 | tx1.add( 36 | SystemProgram.createAccount({ 37 | fromPubkey: FEE_PAYER.publicKey, 38 | newAccountPubkey: tmpAccount.publicKey, 39 | space: SPLToken.AccountLayout.span, 40 | lamports: (await SPLToken.Token.getMinBalanceRentForExemptAccount(CONNECTION)) + amount, // rent + amount 41 | programId: SPLToken.TOKEN_PROGRAM_ID, 42 | }), 43 | SPLToken.Token.createInitAccountInstruction( 44 | SPLToken.TOKEN_PROGRAM_ID, 45 | SPLToken.NATIVE_MINT, 46 | tmpAccount.publicKey, 47 | ALICE.publicKey 48 | ), 49 | SPLToken.Token.createTransferInstruction( 50 | SPLToken.TOKEN_PROGRAM_ID, 51 | tmpAccount.publicKey, 52 | ata, 53 | ALICE.publicKey, 54 | [], 55 | amount 56 | ), 57 | SPLToken.Token.createCloseAccountInstruction( 58 | SPLToken.TOKEN_PROGRAM_ID, 59 | tmpAccount.publicKey, 60 | ALICE.publicKey, 61 | ALICE.publicKey, 62 | [] 63 | ) 64 | ); 65 | tx1.feePayer = FEE_PAYER.publicKey; 66 | console.log(`tx1 txhash: ${await CONNECTION.sendTransaction(tx1, [FEE_PAYER, tmpAccount, ALICE])}`); 67 | 68 | // 2. SyncNative 69 | let tx2 = new Transaction(); 70 | tx2.add( 71 | SystemProgram.transfer({ 72 | fromPubkey: ALICE.publicKey, 73 | toPubkey: ata, 74 | lamports: amount, 75 | }), 76 | // 因為spl-token那邊還沒把這個介面export出來,我們就直接寫原生操作 77 | new TransactionInstruction({ 78 | keys: [ 79 | { 80 | pubkey: ata, 81 | isSigner: false, 82 | isWritable: true, 83 | }, 84 | ], 85 | data: Buffer.from(new Uint8Array([17])), 86 | programId: SPLToken.TOKEN_PROGRAM_ID, 87 | }) 88 | ); 89 | tx2.feePayer = FEE_PAYER.publicKey; 90 | 91 | console.log(`ata txhash: ${await CONNECTION.sendTransaction(tx2, [FEE_PAYER, ALICE])}`); 92 | } 93 | 94 | main().then( 95 | () => process.exit(), 96 | (err) => { 97 | console.error(err); 98 | process.exit(-1); 99 | } 100 | ); 101 | -------------------------------------------------------------------------------- /advanced/token/wrapped-sol/create-token-account/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, SystemProgram } from "@solana/web3.js"; 2 | 3 | import { ALICE, CONNECTION, FEE_PAYER, TEST_MINT } from "../../../../helper/const"; 4 | 5 | import * as SPLToken from "@solana/spl-token"; 6 | 7 | // create token account 8 | 9 | // wrapped sol is a kind of mint. we need a token account to hold it. 10 | // (if you don't know how to create a token account, you can find it in tour/create-token-account) 11 | 12 | async function main() { 13 | let ata = await SPLToken.Token.getAssociatedTokenAddress( 14 | SPLToken.ASSOCIATED_TOKEN_PROGRAM_ID, 15 | SPLToken.TOKEN_PROGRAM_ID, 16 | SPLToken.NATIVE_MINT, // wrapped SOL's mint address 17 | ALICE.publicKey 18 | ); 19 | console.log(`ata: ${ata.toBase58()}`); 20 | 21 | let ataTx = new Transaction(); 22 | ataTx.add( 23 | SPLToken.Token.createAssociatedTokenAccountInstruction( 24 | SPLToken.ASSOCIATED_TOKEN_PROGRAM_ID, 25 | SPLToken.TOKEN_PROGRAM_ID, 26 | SPLToken.NATIVE_MINT, 27 | ata, 28 | ALICE.publicKey, 29 | FEE_PAYER.publicKey 30 | ) 31 | ); 32 | ataTx.feePayer = FEE_PAYER.publicKey; 33 | 34 | console.log(`ata txhash: ${await CONNECTION.sendTransaction(ataTx, [FEE_PAYER])}`); 35 | } 36 | 37 | main().then( 38 | () => process.exit(), 39 | (err) => { 40 | console.error(err); 41 | process.exit(-1); 42 | } 43 | ); 44 | -------------------------------------------------------------------------------- /advanced/token/wrapped-sol/create-token-account/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, SystemProgram } from "@solana/web3.js"; 2 | 3 | import { ALICE, CONNECTION, FEE_PAYER, TEST_MINT } from "../../../../helper/const"; 4 | 5 | import * as SPLToken from "@solana/spl-token"; 6 | 7 | // 創建Token Account 8 | 9 | // wrapped sol 本質上也是一種 mint,所以我們會需要一個token account來持有他 (不熟悉的朋友可以到tour/create-token-account複習下) 10 | 11 | async function main() { 12 | let ata = await SPLToken.Token.getAssociatedTokenAddress( 13 | SPLToken.ASSOCIATED_TOKEN_PROGRAM_ID, // 通常是固定值, associated token program id 14 | SPLToken.TOKEN_PROGRAM_ID, // 通常是固定值, token program id 15 | SPLToken.NATIVE_MINT, // mint 16 | ALICE.publicKey // token account auth (擁有token account權限的人) 17 | ); 18 | console.log(`ata: ${ata.toBase58()}`); 19 | 20 | let ataTx = new Transaction(); 21 | ataTx.add( 22 | SPLToken.Token.createAssociatedTokenAccountInstruction( 23 | SPLToken.ASSOCIATED_TOKEN_PROGRAM_ID, // 通常是固定值, associated token program id 24 | SPLToken.TOKEN_PROGRAM_ID, // 通常是固定值, token program id 25 | SPLToken.NATIVE_MINT, // mint (需要跟剛剛算ata時的mint是同一個) 26 | ata, // 剛剛算出來的 ata 27 | ALICE.publicKey, // token account owner (要跟剛剛算ata時的token account auth是同一個) 28 | FEE_PAYER.publicKey // payer, 建立帳戶付錢的人,等同於 SystemProgram.createAccount 的 from 29 | ) 30 | ); 31 | ataTx.feePayer = FEE_PAYER.publicKey; 32 | 33 | console.log(`ata txhash: ${await CONNECTION.sendTransaction(ataTx, [FEE_PAYER])}`); 34 | } 35 | 36 | main().then( 37 | () => process.exit(), 38 | (err) => { 39 | console.error(err); 40 | process.exit(-1); 41 | } 42 | ); 43 | -------------------------------------------------------------------------------- /docs/.vuepress/.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | .temp -------------------------------------------------------------------------------- /docs/.vuepress/config.ts: -------------------------------------------------------------------------------- 1 | import { defineUserConfig } from "vuepress"; 2 | import type { DefaultThemeOptions } from "vuepress"; 3 | import * as path from "path"; 4 | 5 | export default defineUserConfig({ 6 | base: "/solana-web3-demo/", 7 | lang: "en-US", 8 | title: "Solana Web3 Example", 9 | themeConfig: { 10 | navbar: [{ text: "GitHub", link: "https://github.com/yihau/solana-web3-demo" }], 11 | locales: { 12 | "/": { 13 | selectLanguageName: "English", 14 | sidebar: [ 15 | { 16 | text: "Getting Started", 17 | children: ["/README.md"], 18 | }, 19 | { 20 | text: "Tour", 21 | children: [ 22 | { text: "Create Keypair (Account)", link: "/tour/create-keypair" }, 23 | { text: "Request Airdrop", link: "/tour/request-airdrop" }, 24 | { text: "Get Balance (SOL)", link: "/tour/get-sol-balance" }, 25 | { text: "Transfer (SOL)", link: "/tour/transfer" }, 26 | { text: "Create Mint (Token)", link: "/tour/create-mint" }, 27 | { text: "Get Mint (Token)", link: "/tour/get-mint" }, 28 | { text: "Create Account (Token)", link: "/tour/create-token-account" }, 29 | { text: "Get Account (Token)", link: "/tour/get-token-account" }, 30 | { text: "Mint To (Token)", link: "/tour/mint-to" }, 31 | { text: "Get Balance (Token)", link: "/tour/get-token-balance" }, 32 | { text: "Transfer (Token)", link: "/tour/token-transfer" }, 33 | ], 34 | }, 35 | { 36 | text: "Advanced", 37 | children: [ 38 | { 39 | text: "Token", 40 | children: [ 41 | { 42 | text: "Wrapped SOL", 43 | children: [ 44 | { text: "Create Token Account", link: "/advanced/token/wrapped-sol/create-token-account" }, 45 | { text: "Add Balanace", link: "/advanced/token/wrapped-sol/add-balance" }, 46 | ], 47 | }, 48 | { text: "Close Account", link: "/advanced/token/close-account" }, 49 | { text: "Set Authority", link: "/advanced/token/set-authority" }, 50 | { text: "Get All Token Account By Owner", link: "/advanced/token/get-all-token-account-by-owner" }, 51 | ], 52 | }, 53 | { 54 | text: "Metaplex (NFT)", 55 | children: [ 56 | { text: "Get Token Metadata", link: "/advanced/metaplex/get-tokenmeta" }, 57 | { text: "Get NFT", link: "/advanced/metaplex/get-nft" }, 58 | { text: "Mint NFT", link: "/advanced/metaplex/mint-nft" }, 59 | ], 60 | }, 61 | { 62 | text: "Durable Nonce", 63 | link: "/advanced/durable-nonce/README.md", 64 | children: [ 65 | { text: "Create Nonce Account", link: "/advanced/durable-nonce/create-nonce-account" }, 66 | { text: "Get Nonce Account", link: "/advanced/durable-nonce/query-nonce" }, 67 | { text: "Use Nonce", link: "/advanced/durable-nonce/use-nonce" }, 68 | ], 69 | }, 70 | ], 71 | }, 72 | ], 73 | }, 74 | "/zh/": { 75 | selectLanguageName: "中文", 76 | sidebar: [ 77 | { 78 | text: "開始", 79 | children: ["/zh/README.md"], 80 | }, 81 | { 82 | text: "入門", 83 | children: [ 84 | { text: "創建錢包", link: "/zh/tour/create-keypair" }, 85 | { text: "拿測試幣", link: "/zh/tour/request-airdrop" }, 86 | { text: "抓取SOL餘額", link: "/zh/tour/get-sol-balance" }, 87 | { text: "SOL轉帳", link: "/zh/tour/transfer" }, 88 | { text: "創建代幣", link: "/zh/tour/create-mint" }, 89 | { text: "抓取代幣資訊", link: "/zh/tour/get-mint" }, 90 | { text: "創建代幣帳戶", link: "/zh/tour/create-token-account" }, 91 | { text: "抓取代幣帳戶資訊", link: "/zh/tour/get-token-account" }, 92 | { text: "增發代幣", link: "/zh/tour/mint-to" }, 93 | { text: "抓取代幣餘額", link: "/zh/tour/get-token-balance" }, 94 | { text: "代幣轉帳", link: "/zh/tour/token-transfer" }, 95 | ], 96 | }, 97 | { 98 | text: "進階", 99 | children: [ 100 | { 101 | text: "代幣", 102 | children: [ 103 | { 104 | text: "Wrapped SOL", 105 | children: [ 106 | { text: "創建帳戶", link: "/zh/advanced/token/wrapped-sol/create-token-account" }, 107 | { text: "增加持有量", link: "/zh/advanced/token/wrapped-sol/add-balance" }, 108 | ], 109 | }, 110 | { text: "關閉帳戶", link: "/zh/advanced/token/close-account" }, 111 | { text: "設定權限", link: "/zh/advanced/token/set-authority" }, 112 | { text: "根據Owner抓取代幣帳戶", link: "/zh/advanced/token/get-all-token-account-by-owner" }, 113 | ], 114 | }, 115 | { 116 | text: "Metaplex (NFT)", 117 | children: [ 118 | { text: "Token Metadata", link: "/zh/advanced/metaplex/get-tokenmeta" }, 119 | { text: "Get NFT", link: "/advanced/metaplex/get-nft" }, 120 | { text: "Mint NFT", link: "/advanced/metaplex/mint-nft" }, 121 | ], 122 | }, 123 | { 124 | text: "Durable Nonce", 125 | link: "/zh/advanced/durable-nonce/README.md", 126 | children: [ 127 | { text: "創建Nonce帳戶", link: "/zh/advanced/durable-nonce/create-nonce-account" }, 128 | { text: "抓取Nonce", link: "/zh/advanced/durable-nonce/query-nonce" }, 129 | { text: "使用Nonce", link: "/zh/advanced/durable-nonce/use-nonce" }, 130 | ], 131 | }, 132 | ], 133 | }, 134 | ], 135 | }, 136 | }, 137 | }, 138 | 139 | locales: { 140 | // The key is the path for the locale to be nested under. 141 | // As a special case, the default locale can use '/' as its path. 142 | "/": { 143 | lang: "en-US", 144 | title: "Solana Web3 Example", 145 | }, 146 | "/zh/": { 147 | lang: "zh-TW", 148 | title: "Solana Web3 Example", 149 | }, 150 | }, 151 | 152 | markdown: { 153 | importCode: { 154 | handleImportPath: (str) => str.replace(/^@/, path.resolve(__dirname, "../../")), 155 | }, 156 | }, 157 | 158 | plugins: [ 159 | [ 160 | "@vuepress/plugin-google-analytics", 161 | { 162 | id: "G-QBVBH32M0L", 163 | }, 164 | ], 165 | ], 166 | }); 167 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Intro 2 | 3 | There are lots of solana web3 examples here. 4 | 5 | Feel free to open an issue for missing content. 6 | 7 | ::: tip 8 | 9 | You will found some imports like below. 10 | 11 | `import { CONNECTION, TEST_MINT, FEE_PAYER } from "../../../helper/const";` 12 | 13 | This is for more focusing on the main topic. All const were defined [here](https://github.com/yihau/solana-web3-demo/blob/main/helper/const.ts) 14 | 15 | ::: -------------------------------------------------------------------------------- /docs/advanced/durable-nonce/README.md: -------------------------------------------------------------------------------- 1 | # Durable Nonce 2 | 3 | We need to pack a recent blockhash into our tx. If the blockhash is expired, our tx will be rejected. (aprox. 2 min). 4 | If you want to get rid of it, you can use durable nonce. Durable nonce has no valid time. 5 | 6 | ## Mechanism 7 | 8 | You will need to create a nonce account previously. After you created, there is a `nonce` will store in the account. 9 | Then we can trigger this mechanism by 10 | 11 | 1. use the `nonce` as a recent blockhash 12 | 2. put `nonce advance` in the first instruciton in this tx 13 | 14 | Here I seperate to 3 step to guide to use it. 15 | 16 | * [Create Nonce Account](./create-nonce-account.md) 17 | * [Get Nonce Account](./query-nonce.md) 18 | * [Use Nonce](./use-nonce.md) -------------------------------------------------------------------------------- /docs/advanced/durable-nonce/create-nonce-account.md: -------------------------------------------------------------------------------- 1 | # Create Nonce Account 2 | 3 | @[code](@/advanced/durable-nonce/create-nonce-account/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/advanced/durable-nonce/query-nonce.md: -------------------------------------------------------------------------------- 1 | # Query Nonce 2 | 3 | @[code](@/advanced/durable-nonce/query-nonce/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/advanced/durable-nonce/use-nonce.md: -------------------------------------------------------------------------------- 1 | # Use Nonce 2 | 3 | @[code](@/advanced/durable-nonce/use-nonce/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/advanced/metaplex/get-nft.md: -------------------------------------------------------------------------------- 1 | # Get NFT 2 | 3 | ## Does A Wallet Has A Specific NFT 4 | 5 | @[code](@/advanced/metaplex/get-wallet-has-a-specific-nft/main.ts) 6 | -------------------------------------------------------------------------------- /docs/advanced/metaplex/get-tokenmeta.md: -------------------------------------------------------------------------------- 1 | # Get Token Metadata 2 | 3 | @[code](@/advanced/metaplex/get-tokenmeta/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/advanced/metaplex/mint-nft.md: -------------------------------------------------------------------------------- 1 | # Mint NFT 2 | 3 | ## v1 toke metadata + v1 master edition 4 | 5 | @[code](@/advanced/metaplex/mint-nft/main.ts) 6 | 7 | ## v2 token metadata + v3 master edition 8 | 9 | @[code](@/advanced/metaplex/mint-nft-new/main.ts) 10 | -------------------------------------------------------------------------------- /docs/advanced/token/close-account.md: -------------------------------------------------------------------------------- 1 | # Close Account 2 | 3 | @[code](@/advanced/token/close-account/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/advanced/token/get-all-token-account-by-owner.md: -------------------------------------------------------------------------------- 1 | # Get All Token Account By Owner 2 | 3 | @[code](@/advanced/token/get-all-token-account-by-owner/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/advanced/token/set-authority.md: -------------------------------------------------------------------------------- 1 | # Set Authority 2 | 3 | @[code](@/advanced/token/set-authority/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/advanced/token/wrapped-sol/add-balance.md: -------------------------------------------------------------------------------- 1 | # Add Balance 2 | 3 | @[code](@/advanced/token/wrapped-sol/add-balance/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/advanced/token/wrapped-sol/create-token-account.md: -------------------------------------------------------------------------------- 1 | # Create Token Account 2 | 3 | @[code](@/advanced/token/wrapped-sol/create-token-account/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/tour/create-keypair.md: -------------------------------------------------------------------------------- 1 | # Create Keypair 2 | 3 | ## Private Key 4 | 5 | @[code](@/tour/create-keypair/private-key.ts) 6 | 7 | ## BIP39 8 | 9 | @[code](@/tour/create-keypair/bip39.ts) 10 | 11 | ## BIP44 12 | 13 | @[code](@/tour/create-keypair/bip44.ts) 14 | -------------------------------------------------------------------------------- /docs/tour/create-mint.md: -------------------------------------------------------------------------------- 1 | # Create Mint 2 | 3 | @[code](@/tour/create-mint/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/tour/create-token-account.md: -------------------------------------------------------------------------------- 1 | # Create Token Account 2 | 3 | @[code](@/tour/create-token-account/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/tour/get-mint.md: -------------------------------------------------------------------------------- 1 | # Get Mint 2 | 3 | @[code](@/tour/get-mint/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/tour/get-sol-balance.md: -------------------------------------------------------------------------------- 1 | # Get Sol Balance 2 | 3 | @[code ts{13,14}](@/tour/get-sol-balance/main.ts) 4 | -------------------------------------------------------------------------------- /docs/tour/get-token-account.md: -------------------------------------------------------------------------------- 1 | # Get Token Account 2 | 3 | @[code](@/tour/get-token-account/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/tour/get-token-balance.md: -------------------------------------------------------------------------------- 1 | # Get Token Balance 2 | 3 | @[code](@/tour/get-token-balance/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/tour/mint-to.md: -------------------------------------------------------------------------------- 1 | # Mint To 2 | 3 | @[code](@/tour/mint-to/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/tour/request-airdrop.md: -------------------------------------------------------------------------------- 1 | # Request Airdrop 2 | 3 | @[code](@/tour/request-airdrop/main.ts) 4 | 5 | ::: tip 6 | you can look up this tx on [https://explorer.solana.com/?cluster=devnet](https://explorer.solana.com/?cluster=devnet) 7 | ::: -------------------------------------------------------------------------------- /docs/tour/retrieve-keypair.md: -------------------------------------------------------------------------------- 1 | # Retrieve Keypair 2 | 3 | @[code](@/tour/retrieve-keypair/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/tour/token-transfer.md: -------------------------------------------------------------------------------- 1 | # Token Transfer 2 | 3 | @[code](@/tour/token-transfer/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/tour/transfer.md: -------------------------------------------------------------------------------- 1 | # Transfer 2 | 3 | @[code](@/tour/transfer/main.ts) 4 | 5 | 1. this tx do `alice` transfer 1 SOL to `4MWwxzWsWmHrsbfPFwE6LDq471nqNeNMsD6DS7y8nruw` and fee payer is `feePayer` 6 | 2. `tx.feePayer = feePayer.publicKey;` is dispensable. If you don't make it explicit, the first signer of this tx will be treated as a fee payer. 7 | -------------------------------------------------------------------------------- /docs/zh/README.md: -------------------------------------------------------------------------------- 1 | # 介紹 2 | 3 | 這裡有滿多solana web3的範例演練 4 | 5 | 如果有什麼敲碗或是想要討論的內容可以直接到github發issue 6 | 7 | ::: tip 8 | 9 | 你會在code裡面看到類似這樣的程式碼 10 | 11 | `import { CONNECTION, TEST_MINT, FEE_PAYER } from "../../../helper/const";` 12 | 13 | 這是為了讓程式碼更專注在主要的議題上,這些參數都定義在[這裡](https://github.com/yihau/solana-web3-demo/blob/main/helper/const.ts) 14 | 15 | ::: -------------------------------------------------------------------------------- /docs/zh/advanced/durable-nonce/README.md: -------------------------------------------------------------------------------- 1 | # Durable Nonce 2 | 3 | 在solana發交易的時候,會在tx內包一塊最近的blockhash塞進去,之後一起簽名。而這個blockhash距離鏈上最新的區塊太遠的話,tx就會被拒絕。 (大概拿到之後兩分鐘就會過期) 這個機制使我們沒有辦法讓tx在本地放一陣子之後再送出。官方有提供一個解法叫做durable nonce。 4 | 5 | ## 機制 6 | 7 | 這個durable nonce會需要你先創一個nonce account。nonce account在創建完後同時裡面也會存在一個nonce。我們只要讓tx符合下面條件就可以啟動nonce的機制 8 | 9 | 1. 把 nonce 放在 blockhash (就不用放最近一塊的blockhash了) 10 | 2. tx的第一個instruction是advanced nonce的操作 11 | 12 | 滿足上面兩個條件就可以啟動durable nonce的機制,下面會分成幾個步驟帶大家一步一步操作。 13 | 14 | * [創建Nonce帳戶](./create-nonce-account.md) 15 | * [抓取Nonce帳戶](./query-nonce.md) 16 | * [使用Nonce](./use-nonce.md) -------------------------------------------------------------------------------- /docs/zh/advanced/durable-nonce/create-nonce-account.md: -------------------------------------------------------------------------------- 1 | # Create Nonce Account 2 | 3 | @[code](@/advanced/durable-nonce/create-nonce-account/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/advanced/durable-nonce/query-nonce.md: -------------------------------------------------------------------------------- 1 | # Query Nonce 2 | 3 | @[code](@/advanced/durable-nonce/query-nonce/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/advanced/durable-nonce/use-nonce.md: -------------------------------------------------------------------------------- 1 | # Use Nonce 2 | 3 | @[code](@/advanced/durable-nonce/use-nonce/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/advanced/metaplex/get-nft.md: -------------------------------------------------------------------------------- 1 | # Get NFT 2 | 3 | ## Does A Wallet Has A Specific NFT 4 | 5 | @[code](@/advanced/metaplex/get-wallet-has-a-specific-nft/main.ts) 6 | -------------------------------------------------------------------------------- /docs/zh/advanced/metaplex/get-tokenmeta.md: -------------------------------------------------------------------------------- 1 | # Get Tokenmeta 2 | 3 | @[code](@/advanced/metaplex/get-tokenmeta/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/advanced/metaplex/mint-nft.md: -------------------------------------------------------------------------------- 1 | # Mint NFT 2 | 3 | ## v1 toke metadata + v1 master edition 4 | 5 | @[code](@/advanced/metaplex/mint-nft/main.ts) 6 | 7 | ## v2 token metadata + v3 master edition 8 | 9 | @[code](@/advanced/metaplex/mint-nft-new/main.ts) 10 | -------------------------------------------------------------------------------- /docs/zh/advanced/send-tx.md: -------------------------------------------------------------------------------- 1 | # Send Tx 2 | 3 | @[code](@/advanced/send-tx/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/advanced/token/close-account.md: -------------------------------------------------------------------------------- 1 | # Close Account 2 | 3 | @[code](@/advanced/token/close-account/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/advanced/token/get-all-token-account-by-owner.md: -------------------------------------------------------------------------------- 1 | # Get All Token Account By Owner 2 | 3 | @[code](@/advanced/token/get-all-token-account-by-owner/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/advanced/token/set-authority.md: -------------------------------------------------------------------------------- 1 | # Set Authority 2 | 3 | @[code](@/advanced/token/set-authority/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/advanced/token/wrapped-sol/add-balance.md: -------------------------------------------------------------------------------- 1 | # Add Balance 2 | 3 | @[code](@/advanced/token/wrapped-sol/add-balance/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/advanced/token/wrapped-sol/create-token-account.md: -------------------------------------------------------------------------------- 1 | # Create Token Account 2 | 3 | @[code](@/advanced/token/wrapped-sol/create-token-account/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/tour/create-keypair.md: -------------------------------------------------------------------------------- 1 | # 創建錢包 2 | 3 | ## 私鑰 4 | 5 | @[code](@/tour/create-keypair/private-key.ts) 6 | 7 | ## BIP39 8 | 9 | @[code](@/tour/create-keypair/bip39.ts) 10 | 11 | ## BIP44 12 | 13 | @[code](@/tour/create-keypair/bip44.ts) -------------------------------------------------------------------------------- /docs/zh/tour/create-mint.md: -------------------------------------------------------------------------------- 1 | # Create Mint 2 | 3 | @[code](@/tour/create-mint/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/tour/create-token-account.md: -------------------------------------------------------------------------------- 1 | # Create Token Account 2 | 3 | @[code](@/tour/create-token-account/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/tour/get-mint.md: -------------------------------------------------------------------------------- 1 | # Get Mint 2 | 3 | @[code](@/tour/get-mint/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/tour/get-sol-balance.md: -------------------------------------------------------------------------------- 1 | # Get Sol Balance 2 | 3 | @[code ts{13,14}](@/tour/get-sol-balance/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/tour/get-token-account.md: -------------------------------------------------------------------------------- 1 | # Get Token Account 2 | 3 | @[code](@/tour/get-token-account/main.en.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/tour/get-token-balance.md: -------------------------------------------------------------------------------- 1 | # Get Token Balance 2 | 3 | @[code](@/tour/get-token-balance/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/tour/mint-to.md: -------------------------------------------------------------------------------- 1 | # Mint To 2 | 3 | @[code](@/tour/mint-to/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/tour/request-airdrop.md: -------------------------------------------------------------------------------- 1 | # 拿測試幣 2 | 3 | @[code](@/tour/request-airdrop/main.ts) 4 | 5 | ::: tip 6 | 可以來這邊查詢剛剛的tx [https://explorer.solana.com/?cluster=devnet](https://explorer.solana.com/?cluster=devnet) 7 | ::: -------------------------------------------------------------------------------- /docs/zh/tour/retrieve-keypair.md: -------------------------------------------------------------------------------- 1 | # Retrieve Keypair 2 | 3 | @[code](@/tour/retrieve-keypair/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/tour/token-transfer.md: -------------------------------------------------------------------------------- 1 | # Token Transfer 2 | 3 | @[code](@/tour/token-transfer/main.ts) 4 | -------------------------------------------------------------------------------- /docs/zh/tour/transfer.md: -------------------------------------------------------------------------------- 1 | # Transfer 2 | 3 | @[code](@/tour/transfer/main.ts) 4 | 5 | 1. 這個交易是`alice`轉帳 1 SOL 給`4MWwxzWsWmHrsbfPFwE6LDq471nqNeNMsD6DS7y8nruw`,feePayer是`feePayer`。 6 | 2. `tx.feePayer = feePayer.publicKey;` 是可以省略的。如果沒有特別指定feePayer是誰,這個交易的第一個簽名的人會被當作是feePayer。 7 | -------------------------------------------------------------------------------- /helper/const.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Connection, PublicKey } from "@solana/web3.js"; 2 | 3 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 4 | export const FEE_PAYER = Keypair.fromSecretKey( 5 | Uint8Array.from([ 6 | 206, 54, 90, 62, 42, 169, 79, 30, 10, 214, 71, 58, 161, 79, 210, 133, 123, 207, 196, 142, 168, 155, 129, 108, 35, 7 | 155, 218, 75, 82, 233, 79, 40, 67, 120, 93, 30, 66, 81, 199, 231, 199, 75, 70, 229, 64, 75, 252, 105, 43, 152, 135, 8 | 212, 92, 179, 44, 129, 174, 181, 26, 186, 90, 20, 83, 69, 9 | ]) 10 | ); 11 | 12 | // 2hieMSXcsk3F3bZE8iDe7WGREZesRDnDjHtdPVjya4NA 13 | export const ALICE = Keypair.fromSecretKey( 14 | Uint8Array.from([ 15 | 16, 169, 21, 27, 237, 134, 45, 100, 113, 192, 66, 107, 174, 36, 175, 110, 195, 29, 133, 60, 134, 224, 92, 212, 74, 16 | 138, 10, 139, 228, 27, 48, 106, 25, 74, 1, 158, 20, 222, 117, 41, 169, 27, 181, 228, 44, 110, 140, 200, 168, 187, 17 | 24, 5, 88, 67, 100, 134, 162, 184, 245, 220, 111, 72, 139, 31, 18 | ]) 19 | ); 20 | 21 | // export const API_ENDPOINT = "https://api.devnet.solana.com"; 22 | export const API_ENDPOINT = "http://localhost:8899"; 23 | 24 | export const CONNECTION = new Connection(API_ENDPOINT); 25 | 26 | export const TEST_MINT = new PublicKey("E4ZN2KmnVmpwLwjJNAwRjuQLeE5iFHLcAJ8LGB7FMaGQ"); 27 | 28 | export const ALICE_TOKEN_ADDRESS_1 = new PublicKey("3ug8rjJdifEcTExVXFkaGuSjR3VZoDWi49ghA8gYpZiF") 29 | 30 | export const ALICE_TOKEN_ADDRESS_2 = new PublicKey("8Rs6tAqLUDzVc3GweP71sefv68ejsW8dG9ZssWpqfhBP") -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solana-web3-demo", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "docs:dev": "vuepress dev docs", 7 | "docs:build": "vuepress build docs" 8 | }, 9 | "dependencies": { 10 | "@metaplex-foundation/mpl-token-metadata": "^2.1.2", 11 | "@metaplex/js": "^4.10.1", 12 | "@solana/spl-token": "^0.2.0", 13 | "@solana/web3.js": "^1.63.0", 14 | "@vuepress/plugin-google-analytics": "^2.0.0-beta.27", 15 | "bip39": "^3.0.4", 16 | "bn.js": "^5.2.0", 17 | "borsh": "^0.7.0", 18 | "bs58": "^4.0.1", 19 | "buffer": "^6.0.3", 20 | "ed25519-hd-key": "^1.2.0", 21 | "tweetnacl": "^1.0.3", 22 | "vuepress": "^2.0.0-beta.27" 23 | }, 24 | "devDependencies": { 25 | "@types/bs58": "^4.0.1", 26 | "@types/node": "^16.11.6" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /program-101/README.md: -------------------------------------------------------------------------------- 1 | # Progrma 101 2 | 3 | ## Guide 4 | 5 | ### Hello 6 | 7 | a hello example to call our first program 8 | 9 | ### Accounts 10 | 11 | pack all accounts we need into our program 12 | 13 | ### Data 14 | 15 | make our program parse data and do more things 16 | 17 | ### serialize / deserialize 18 | 19 | store your state in your account 20 | 21 | ### Invoke 22 | 23 | call another program in your program by invoke 24 | 25 | ### Invoke Signed 26 | 27 | call another program in your program by invoke_signed 28 | -------------------------------------------------------------------------------- /program-101/accounts/client/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, Transaction, SystemProgram, PublicKey, TransactionInstruction } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | 4 | // connection 5 | const connection = new Connection("https://api.devnet.solana.com"); 6 | 7 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 8 | const feePayer = Keypair.fromSecretKey( 9 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 10 | ); 11 | 12 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 13 | const alice = Keypair.fromSecretKey( 14 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 15 | ); 16 | 17 | (async () => { 18 | let programId = new PublicKey("C8pMh4nKK3z8eHbR2RXN2UfRm65scxd5UJV6Fqkv9mr2"); 19 | 20 | // an account meta list is an array and program will receive a same order account info list when loaded. 21 | 22 | // an account meta including 23 | // - isSigner 24 | // - isWritable 25 | 26 | // if you assign some account is a signer, the account need to sign the tx 27 | // if your account's data will be modified after this tx, you should assign this account is writable 28 | 29 | let firstAccount = Keypair.generate(); 30 | console.log(`first account: ${firstAccount.publicKey.toBase58()}`); 31 | 32 | let secondAccount = Keypair.generate(); 33 | console.log(`second account: ${secondAccount.publicKey.toBase58()}`); 34 | 35 | let ins = new TransactionInstruction({ 36 | programId: programId, 37 | keys: [ 38 | { 39 | pubkey: firstAccount.publicKey, 40 | isSigner: true, 41 | isWritable: false, 42 | }, 43 | { 44 | pubkey: secondAccount.publicKey, 45 | isSigner: false, 46 | isWritable: false, 47 | }, 48 | ], 49 | data: Buffer.from([]), 50 | }); 51 | 52 | let tx = new Transaction().add(ins); 53 | 54 | let txhash = await connection.sendTransaction(tx, [feePayer, firstAccount]); // you can try to remove firstAccount and see what will happen 55 | console.log(`txhash: ${txhash}`); 56 | })(); 57 | -------------------------------------------------------------------------------- /program-101/accounts/program/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /program-101/accounts/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["yihau "] 3 | edition = "2018" 4 | name = "accounts" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | solana-program = "=1.9.13" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "lib"] 12 | -------------------------------------------------------------------------------- /program-101/accounts/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::{next_account_info, AccountInfo}, 3 | entrypoint, 4 | entrypoint::ProgramResult, 5 | msg, 6 | pubkey::Pubkey, 7 | }; 8 | 9 | entrypoint!(process_instruction); 10 | 11 | fn process_instruction( 12 | _program_id: &Pubkey, 13 | accounts: &[AccountInfo], 14 | _instruction_data: &[u8], 15 | ) -> ProgramResult { 16 | let account_info_iter = &mut accounts.iter(); 17 | 18 | let first_account_info = next_account_info(account_info_iter)?; 19 | msg!(&format!( 20 | "first: {} isSigner: {}, isWritable: {}", 21 | first_account_info.key.to_string(), 22 | first_account_info.is_signer, 23 | first_account_info.is_writable, 24 | )); 25 | 26 | let second_account_info = next_account_info(account_info_iter)?; 27 | msg!(&format!( 28 | "second: {} isSigner: {}, isWritable: {}", 29 | second_account_info.key.to_string(), 30 | second_account_info.is_signer, 31 | second_account_info.is_writable, 32 | )); 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /program-101/data/client/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, Transaction, SystemProgram, PublicKey, TransactionInstruction } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | 4 | // connection 5 | const connection = new Connection("https://api.devnet.solana.com"); 6 | 7 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 8 | const feePayer = Keypair.fromSecretKey( 9 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 10 | ); 11 | 12 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 13 | const alice = Keypair.fromSecretKey( 14 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 15 | ); 16 | 17 | (async () => { 18 | let programId = new PublicKey("Pp1xH7WfZEgfsjseWLLs4H8c4YT1xmdGWmenGBu62iE"); 19 | 20 | // data is the most powerful part in an instruction 21 | // we can pack everything into data, like number, pubkey ... whatever you want. 22 | 23 | // when we pack something into data, it should be a byte array 24 | // and a program will receive the same byte array as well. 25 | // how to serialize/deserialize is up to you. there are many different ways to make it. 26 | 27 | // here I use data to make a function selector, we will make a more complex example later. 28 | 29 | // our program will take the first byte as a function selector 30 | // then print remains 31 | 32 | { 33 | let ins = new TransactionInstruction({ 34 | programId: programId, 35 | keys: [], 36 | data: Buffer.from([0, 1, 2, 3, 4, 5, 6, 7, 8]), 37 | }); 38 | 39 | let tx = new Transaction().add(ins); 40 | 41 | let txhash = await connection.sendTransaction(tx, [feePayer]); 42 | console.log(`called first instruction txhash: ${txhash}`); 43 | } 44 | 45 | { 46 | let ins = new TransactionInstruction({ 47 | programId: programId, 48 | keys: [], 49 | data: Buffer.from([1, 8, 7, 6, 5, 4, 3, 2, 1]), 50 | }); 51 | 52 | let tx = new Transaction().add(ins); 53 | 54 | let txhash = await connection.sendTransaction(tx, [feePayer]); 55 | console.log(`called second instruction txhash: ${txhash}`); 56 | } 57 | })(); 58 | -------------------------------------------------------------------------------- /program-101/data/program/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /program-101/data/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["yihau "] 3 | edition = "2018" 4 | name = "data" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | solana-program = "=1.9.13" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "lib"] 12 | -------------------------------------------------------------------------------- /program-101/data/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, 3 | program_error::ProgramError, pubkey::Pubkey, 4 | }; 5 | 6 | entrypoint!(process_instruction); 7 | 8 | fn process_instruction( 9 | _program_id: &Pubkey, 10 | _accounts: &[AccountInfo], 11 | instruction_data: &[u8], 12 | ) -> ProgramResult { 13 | let (selector, rest) = instruction_data 14 | .split_first() 15 | .ok_or(ProgramError::InvalidInstructionData)?; 16 | 17 | match selector { 18 | 0 => msg!(&format!( 19 | "first instruction called with remain data: {:?}", 20 | rest, 21 | )), 22 | 1 => msg!(&format!( 23 | "second instruction called with remain data: {:?}", 24 | rest, 25 | )), 26 | _ => { 27 | msg!("invalid called"); 28 | return Err(ProgramError::InvalidInstructionData); 29 | } 30 | } 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /program-101/hello/client/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, Transaction, SystemProgram, PublicKey, TransactionInstruction } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | 4 | // connection 5 | const connection = new Connection("https://api.devnet.solana.com"); 6 | 7 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 8 | const feePayer = Keypair.fromSecretKey( 9 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 10 | ); 11 | 12 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 13 | const alice = Keypair.fromSecretKey( 14 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 15 | ); 16 | 17 | (async () => { 18 | let programId = new PublicKey("H9oXrmgoSdMRgeM9M4jM4RfuPUznHDjgZdBTgAmnDtRU"); 19 | 20 | // instruction is composed by 21 | // - program id 22 | // - account meta list 23 | // - data 24 | 25 | // in our first program, we don't parse any accounts and data. leave them empty here 26 | // we will make some changes later 27 | let ins = new TransactionInstruction({ 28 | programId: programId, 29 | keys: [], 30 | data: Buffer.from([]), 31 | }); 32 | 33 | let tx = new Transaction().add(ins); 34 | 35 | let txhash = await connection.sendTransaction(tx, [feePayer]); 36 | console.log(`txhash: ${txhash}`); 37 | })(); 38 | -------------------------------------------------------------------------------- /program-101/hello/program/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /program-101/hello/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["yihau "] 3 | edition = "2021" 4 | name = "hello" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | solana-program = "=1.9.14" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "lib"] 12 | -------------------------------------------------------------------------------- /program-101/hello/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::AccountInfo, entrypoint, entrypoint::ProgramResult, msg, pubkey::Pubkey, 3 | }; 4 | 5 | entrypoint!(process_instruction); 6 | 7 | fn process_instruction( 8 | _program_id: &Pubkey, 9 | _accounts: &[AccountInfo], 10 | _instruction_data: &[u8], 11 | ) -> ProgramResult { 12 | msg!("hello"); 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /program-101/invoke/client/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, Transaction, SystemProgram, PublicKey, TransactionInstruction } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | 4 | // connection 5 | const connection = new Connection("https://api.devnet.solana.com"); 6 | 7 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 8 | const feePayer = Keypair.fromSecretKey( 9 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 10 | ); 11 | 12 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 13 | const alice = Keypair.fromSecretKey( 14 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 15 | ); 16 | 17 | (async () => { 18 | let programId = new PublicKey("A4toBdAvsqE6RRgFGHBXgpKxAhU7dY2RRkWARhzYqjKm"); 19 | 20 | // `invoke` allows us to call another program in our program 21 | 22 | // we need to pack all needed accounts and program in the same instruction so that we can composed them in our prgoram 23 | 24 | // here we try to use invoke in our program 25 | { 26 | let ins = new TransactionInstruction({ 27 | programId: programId, 28 | keys: [ 29 | { 30 | pubkey: alice.publicKey, 31 | isSigner: true, 32 | isWritable: true, 33 | }, 34 | { 35 | pubkey: feePayer.publicKey, 36 | isSigner: false, 37 | isWritable: true, 38 | }, 39 | // remember you also need to pack the invoke instruciton's program here 40 | // we want to call system program's transfer in our prgoram 41 | // so we need to pack system program into instruction 42 | { 43 | pubkey: SystemProgram.programId, 44 | isSigner: false, 45 | isWritable: false, 46 | }, 47 | ], 48 | // we pack `amount` into data. [1, 0, 0, 0, 0, 0, 0, 0] is a little endian u64 which value is 1 49 | data: Buffer.from([1, 0, 0, 0, 0, 0, 0, 0]), 50 | }); 51 | 52 | let tx = new Transaction().add(ins); 53 | 54 | let txhash = await connection.sendTransaction(tx, [feePayer, alice]); 55 | console.log(`txhash: ${txhash}`); 56 | } 57 | })(); 58 | -------------------------------------------------------------------------------- /program-101/invoke/program/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /program-101/invoke/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["yihau "] 3 | edition = "2018" 4 | name = "invoke" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | solana-program = "=1.9.13" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "lib"] 12 | -------------------------------------------------------------------------------- /program-101/invoke/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::next_account_info, account_info::AccountInfo, entrypoint, 3 | entrypoint::ProgramResult, msg, program::invoke, program_error::ProgramError, pubkey::Pubkey, 4 | system_instruction, 5 | }; 6 | 7 | entrypoint!(process_instruction); 8 | 9 | fn process_instruction( 10 | _program_id: &Pubkey, 11 | accounts: &[AccountInfo], 12 | data: &[u8], 13 | ) -> ProgramResult { 14 | let account_info_iter = &mut accounts.iter(); 15 | 16 | let from_account_info = next_account_info(account_info_iter)?; 17 | let to_account_info = next_account_info(account_info_iter)?; 18 | let _system_program_account_info = next_account_info(account_info_iter)?; 19 | 20 | let mut fixed_data = [0u8; 8]; 21 | fixed_data.copy_from_slice(&data[0..8]); 22 | let amount = u64::from_le_bytes(fixed_data); 23 | 24 | invoke( 25 | &system_instruction::transfer(&from_account_info.key, &to_account_info.key, amount), 26 | &[from_account_info.clone(), to_account_info.clone()], 27 | )?; 28 | 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /program-101/invoke_signed/client/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | Keypair, 4 | Transaction, 5 | SystemProgram, 6 | PublicKey, 7 | TransactionInstruction, 8 | LAMPORTS_PER_SOL, 9 | } from "@solana/web3.js"; 10 | import * as bs58 from "bs58"; 11 | 12 | // connection 13 | const connection = new Connection("https://api.devnet.solana.com"); 14 | 15 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 16 | const feePayer = Keypair.fromSecretKey( 17 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 18 | ); 19 | 20 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 21 | const alice = Keypair.fromSecretKey( 22 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 23 | ); 24 | 25 | (async () => { 26 | let programId = new PublicKey("56eJREdmSWTXV6hxxqyJnEG97d26DsH5dcbaZpCHg827"); 27 | 28 | // `invoke_sigend` allows us to call another program in our program 29 | 30 | // we need to pack all needed accounts and program in the same instruction so that we can composed them in our prgoram 31 | 32 | // the different between invoke and invoke_signed is that 33 | // when we called invoke_signed, there is a extra input, seed 34 | // invoke_sigend will sign a PDA which calculated by the seed 35 | 36 | // *PDA is a address which derived by seed + prgoram id 37 | // only a correspond program can sign a PDA 38 | 39 | // prepare 1 SOL for pda 40 | let [pda, _] = await PublicKey.findProgramAddress([Buffer.from("PDA", "utf8")], programId); 41 | console.log(`PDA: ${pda.toBase58()}`); 42 | // let txhash = await connection.requestAirdrop(pda, 1 * LAMPORTS_PER_SOL); 43 | // await connection.confirmTransaction(txhash); 44 | 45 | // then we transfer 1 lamports from this PDA 46 | { 47 | let ins = new TransactionInstruction({ 48 | programId: programId, 49 | keys: [ 50 | { 51 | pubkey: pda, 52 | isSigner: false, // although it should be a signer, we leave false here because we can't generate the signaure locally 53 | isWritable: true, 54 | }, 55 | { 56 | pubkey: feePayer.publicKey, 57 | isSigner: false, 58 | isWritable: true, 59 | }, 60 | // remember you also need to pack the invoke instruciton's program here 61 | // we want to call system program's transfer in our prgoram 62 | // so we need to pack system program into instruction 63 | { 64 | pubkey: SystemProgram.programId, 65 | isSigner: false, 66 | isWritable: false, 67 | }, 68 | ], 69 | // we pack `amount` into data. [1, 0, 0, 0, 0, 0, 0, 0] is a little endian u64 which value is 1 70 | data: Buffer.from([1, 0, 0, 0, 0, 0, 0, 0]), 71 | }); 72 | 73 | let tx = new Transaction().add(ins); 74 | 75 | let txhash = await connection.sendTransaction(tx, [feePayer]); 76 | console.log(`txhash: ${txhash}`); 77 | } 78 | })(); 79 | -------------------------------------------------------------------------------- /program-101/invoke_signed/program/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /program-101/invoke_signed/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["yihau "] 3 | edition = "2018" 4 | name = "invoke_signed" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | solana-program = "=1.9.13" 9 | 10 | [lib] 11 | crate-type = ["cdylib", "lib"] 12 | -------------------------------------------------------------------------------- /program-101/invoke_signed/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | use solana_program::{ 2 | account_info::next_account_info, account_info::AccountInfo, entrypoint, 3 | entrypoint::ProgramResult, msg, program::invoke_signed, program_error::ProgramError, 4 | pubkey::Pubkey, system_instruction, 5 | }; 6 | 7 | entrypoint!(process_instruction); 8 | 9 | fn process_instruction( 10 | program_id: &Pubkey, 11 | accounts: &[AccountInfo], 12 | data: &[u8], 13 | ) -> ProgramResult { 14 | let account_info_iter = &mut accounts.iter(); 15 | 16 | let from_account_info = next_account_info(account_info_iter)?; 17 | let to_account_info = next_account_info(account_info_iter)?; 18 | let _system_program_account_info = next_account_info(account_info_iter)?; 19 | 20 | let mut fixed_data = [0u8; 8]; 21 | fixed_data.copy_from_slice(&data[0..8]); 22 | let amount = u64::from_le_bytes(fixed_data); 23 | 24 | let seed = b"PDA"; 25 | let (_, bump) = Pubkey::find_program_address(&[seed], program_id); 26 | 27 | invoke_signed( 28 | &system_instruction::transfer(&from_account_info.key, &to_account_info.key, amount), 29 | &[from_account_info.clone(), to_account_info.clone()], 30 | &[&[seed, &[bump]]], 31 | )?; 32 | 33 | Ok(()) 34 | } 35 | -------------------------------------------------------------------------------- /program-101/serialize-deserialize/client/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, Transaction, PublicKey, TransactionInstruction, SystemProgram } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | import * as borsh from "borsh"; 4 | 5 | // connection 6 | const connection = new Connection("https://api.devnet.solana.com"); 7 | 8 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 9 | const feePayer = Keypair.fromSecretKey( 10 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 11 | ); 12 | 13 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 14 | const alice = Keypair.fromSecretKey( 15 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 16 | ); 17 | 18 | class GreetingAccount { 19 | counter = 0; 20 | constructor(fields: { counter: number } | undefined = undefined) { 21 | if (fields) { 22 | this.counter = fields.counter; 23 | } 24 | } 25 | } 26 | 27 | const GreetingSchema = new Map([[GreetingAccount, { kind: "struct", fields: [["counter", "u32"]] }]]); 28 | 29 | const GREETING_SIZE = borsh.serialize(GreetingSchema, new GreetingAccount()).length; 30 | 31 | (async () => { 32 | let programId = new PublicKey("ETZX5pGLX6FUtduJuoKScvTY1TH53LAeCtPjAhXFQooM"); 33 | 34 | // 1. init + hello 35 | { 36 | let greetAccount = Keypair.generate(); 37 | console.log(`greet account: ${greetAccount.publicKey.toBase58()}`); 38 | 39 | let tx = new Transaction().add( 40 | // create account 41 | SystemProgram.createAccount({ 42 | fromPubkey: feePayer.publicKey, 43 | newAccountPubkey: greetAccount.publicKey, 44 | space: GREETING_SIZE, 45 | lamports: await connection.getMinimumBalanceForRentExemption(GREETING_SIZE), 46 | programId: programId, 47 | }), 48 | new TransactionInstruction({ 49 | programId: programId, 50 | keys: [ 51 | { 52 | pubkey: greetAccount.publicKey, 53 | isSigner: false, 54 | isWritable: true, 55 | }, 56 | ], 57 | data: Buffer.from([]), 58 | }) 59 | ); 60 | 61 | console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer, greetAccount])}`); 62 | } 63 | 64 | // 2. hello to exist account 65 | // { 66 | // let greetAccountPubkey = new PublicKey("D4FTRFfasgzaNZYPeMb2TahXZwt5N8HUDA7K1tb3DyNW"); 67 | 68 | // let tx = new Transaction().add( 69 | // new TransactionInstruction({ 70 | // programId: programId, 71 | // keys: [ 72 | // { 73 | // pubkey: greetAccountPubkey, 74 | // isSigner: false, 75 | // isWritable: true, 76 | // }, 77 | // ], 78 | // data: Buffer.from([]), 79 | // }) 80 | // ); 81 | 82 | // console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer])}`); 83 | // } 84 | 85 | // 3. get account 86 | // { 87 | // let greetAccountPubkey = new PublicKey("D4FTRFfasgzaNZYPeMb2TahXZwt5N8HUDA7K1tb3DyNW"); 88 | // let accountInfo = await connection.getAccountInfo(greetAccountPubkey); 89 | // if (!accountInfo) { 90 | // throw Error(`failed to get ${greetAccountPubkey}`); 91 | // } 92 | // console.log(accountInfo.data); 93 | 94 | // const greetAccount = borsh.deserialize(GreetingSchema, GreetingAccount, accountInfo.data); 95 | // console.log(`${JSON.stringify(greetAccount)}`); 96 | // } 97 | })(); 98 | -------------------------------------------------------------------------------- /program-101/serialize-deserialize/program/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /program-101/serialize-deserialize/program/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["yihau "] 3 | edition = "2018" 4 | name = "serialize-deserialize" 5 | version = "0.1.0" 6 | 7 | [dependencies] 8 | borsh = "0.9.0" 9 | solana-program = "=1.9.14" 10 | 11 | [lib] 12 | crate-type = ["cdylib", "lib"] 13 | -------------------------------------------------------------------------------- /program-101/serialize-deserialize/program/src/lib.rs: -------------------------------------------------------------------------------- 1 | use borsh::{BorshDeserialize, BorshSerialize}; 2 | use solana_program::{ 3 | account_info::{next_account_info, AccountInfo}, 4 | entrypoint, 5 | entrypoint::ProgramResult, 6 | msg, 7 | pubkey::Pubkey, 8 | }; 9 | 10 | entrypoint!(process_instruction); 11 | 12 | #[derive(BorshSerialize, BorshDeserialize, Debug)] 13 | pub struct GreetingAccount { 14 | pub counter: u32, 15 | } 16 | 17 | fn process_instruction( 18 | _program_id: &Pubkey, 19 | accounts: &[AccountInfo], 20 | _instruction_data: &[u8], 21 | ) -> ProgramResult { 22 | let accounts_iter = &mut accounts.iter(); 23 | 24 | let account = next_account_info(accounts_iter)?; 25 | 26 | let mut greeting_account = GreetingAccount::try_from_slice(&account.data.borrow())?; 27 | greeting_account.counter += 1; 28 | greeting_account.serialize(&mut &mut account.data.borrow_mut()[..])?; 29 | 30 | msg!("Greeted {} time(s)!", greeting_account.counter); 31 | 32 | Ok(()) 33 | } 34 | -------------------------------------------------------------------------------- /tour/README.md: -------------------------------------------------------------------------------- 1 | # Tour 2 | 3 | 建議以下順序閱讀 4 | 5 | * [創建新帳戶](../tour/create-keypair/main.ts) 6 | * [私鑰還原帳戶](../tour/retrieve-keypair/main.ts) 7 | * [建立基本連線](../tour/create-connection/main.ts) 8 | * [領取測試幣](../tour/request-airdrop/main.ts) 9 | * [SOL餘額查詢](../tour/get-sol-balance/main.ts) 10 | * [SOL轉帳](../tour/transfer/main.ts) 11 | * [創建代幣](../tour/create-mint/main.ts) 12 | * [抓取代幣資訊](../tour/get-mint/main.ts) 13 | * [創建代幣帳戶](../tour/create-token-account/main.ts) 14 | * [增發代幣](../tour/mint-to/main.ts) 15 | * [查詢代幣餘額](../tour/get-token-balance/main.ts) 16 | * [代幣轉帳](../tour/token-transfer/main.ts) 17 | -------------------------------------------------------------------------------- /tour/README_EN.md: -------------------------------------------------------------------------------- 1 | # Tour 2 | 3 | recommend you to read by this order 4 | 5 | * [create keypair](../tour/create-keypair/main.en.ts) 6 | * [retrieve keypair](../tour/retrieve-keypair/main.en.ts) 7 | * [build connection](../tour/create-connection/main.en.ts) 8 | * [request airdrop](../tour/request-airdrop/main.en.ts) 9 | * [get sol balance](../tour/get-sol-balance/main.en.ts) 10 | * [sol transfer](../tour/transfer/main.en.ts) 11 | * [create mint](../tour/create-mint/main.en.ts) 12 | * [get mint info](../tour/get-mint/main.en.ts) 13 | * [create token naccount](../tour/create-token-account/main.en.ts) 14 | * [mint token](../tour/mint-to/main.en.ts) 15 | * [get token balance](../tour/get-token-balance/main.en.ts) 16 | * [token transfer](../tour/token-transfer/main.en.ts) 17 | -------------------------------------------------------------------------------- /tour/create-keypair/bip39.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from "@solana/web3.js"; 2 | import * as bip39 from "bip39"; 3 | 4 | (async () => { 5 | // 1. 6 | // const mnemonic = bip39.generateMnemonic(); 7 | // console.log(mnemonic); 8 | // 2. 9 | const mnemonic = "pill tomorrow foster begin walnut borrow virtual kick shift mutual shoe scatter"; 10 | 11 | const seed = bip39.mnemonicToSeedSync(mnemonic, ""); // (mnemonic, password) 12 | const keypair = Keypair.fromSeed(seed.slice(0, 32)); 13 | console.log(`${keypair.publicKey.toBase58()}`); // 5ZWj7a1f8tWkjBESHKgrLmXshuXxqeY9SYcfbshpAqPG 14 | })(); 15 | -------------------------------------------------------------------------------- /tour/create-keypair/bip44.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from "@solana/web3.js"; 2 | import { derivePath } from "ed25519-hd-key"; 3 | import * as bip39 from "bip39"; 4 | 5 | (async () => { 6 | const mnemonic = "neither lonely flavor argue grass remind eye tag avocado spot unusual intact"; 7 | const seed = bip39.mnemonicToSeedSync(mnemonic, ""); // (mnemonic, password) 8 | for (let i = 0; i < 10; i++) { 9 | const path = `m/44'/501'/${i}'/0'`; 10 | const keypair = Keypair.fromSeed(derivePath(path, seed.toString("hex")).key); 11 | console.log(`${path} => ${keypair.publicKey.toBase58()}`); 12 | } 13 | /* 14 | m/44'/501'/0'/0' => 5vftMkHL72JaJG6ExQfGAsT2uGVHpRR7oTNUPMs68Y2N 15 | m/44'/501'/1'/0' => GcXbfQ5yY3uxCyBNDPBbR5FjumHf89E7YHXuULfGDBBv 16 | m/44'/501'/2'/0' => 7QPgyQwNLqnoSwHEuK8wKy2Y3Ani6EHoZRihTuWkwxbc 17 | m/44'/501'/3'/0' => 5aE8UprEEWtpVskhxo3f8ETco2kVKiZT9SS3D5Lcg8s2 18 | m/44'/501'/4'/0' => 5n6afo6LZmzH1J4R38ZCaNSwaztLjd48nWwToLQkCHxp 19 | m/44'/501'/5'/0' => 2Gr1hWnbaqGXMghicSTHncqV7GVLLddNFJDC7YJoso8M 20 | m/44'/501'/6'/0' => BNMDY3tCyYbayMzBjZm8RW59unpDWcQRfVmWXCJhLb7D 21 | m/44'/501'/7'/0' => 9CySTpi4iC85gMW6G4BMoYbNBsdyJrfseHoGmViLha63 22 | m/44'/501'/8'/0' => ApteF7PmUWS8Lzm6tJPkWgrxSFW5LwYGWCUJ2ByAec91 23 | m/44'/501'/9'/0' => 6frdqXQAgJMyKwmZxkLYbdGjnYTvUceh6LNhkQt2siQp 24 | */ 25 | })(); 26 | -------------------------------------------------------------------------------- /tour/create-keypair/private-key.ts: -------------------------------------------------------------------------------- 1 | import { Keypair } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | 4 | (async () => { 5 | // 1. create a random 6 | { 7 | const keypair = Keypair.generate(); 8 | console.log(`public key: ${keypair.publicKey.toBase58()}`); 9 | console.log(`private key(raw): ${keypair.secretKey}`); 10 | console.log(`private key(bs58): ${bs58.encode(keypair.secretKey)}`); 11 | } 12 | 13 | // 2. from bs58 string 14 | { 15 | const keypair = Keypair.fromSecretKey( 16 | bs58.decode("5MaiiCavjCmn9Hs1o3eznqDEhRwxo7pXiAYez7keQUviUkauRiTMD8DrESdrNjN8zd9mTmVhRvBJeg5vhyvgrAhG") 17 | ); 18 | } 19 | 20 | // 3. from bytes 21 | { 22 | const keypair = Keypair.fromSecretKey( 23 | Uint8Array.from([ 24 | 174, 47, 154, 16, 202, 193, 206, 113, 199, 190, 53, 133, 169, 175, 31, 56, 222, 53, 138, 189, 224, 216, 117, 25 | 173, 10, 149, 53, 45, 73, 251, 237, 246, 15, 185, 186, 82, 177, 240, 148, 69, 241, 227, 167, 80, 141, 89, 240, 26 | 121, 121, 35, 172, 247, 68, 251, 226, 218, 48, 63, 176, 109, 168, 89, 238, 135, 27 | ]) 28 | ); 29 | console.log(`${keypair.publicKey.toBase58()}`); // 24PNhTaNtomHhoy3fTRaMhAFCRj4uHqhZEEoWrKDbR5p 30 | } 31 | })(); 32 | -------------------------------------------------------------------------------- /tour/create-mint/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, SystemProgram, Connection } from "@solana/web3.js"; 2 | 3 | import { 4 | createInitializeMintInstruction, 5 | getMinimumBalanceForRentExemptMint, 6 | MINT_SIZE, 7 | TOKEN_PROGRAM_ID, 8 | } from "@solana/spl-token"; 9 | 10 | import * as bs58 from "bs58"; 11 | 12 | // connection 13 | const connection = new Connection("https://api.devnet.solana.com"); 14 | 15 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 16 | const feePayer = Keypair.fromSecretKey( 17 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 18 | ); 19 | 20 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 21 | const alice = Keypair.fromSecretKey( 22 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 23 | ); 24 | 25 | // create mint (create your own token) 26 | 27 | // you can treat a mint as a ERC-20's token address in Ethereum 28 | // SRM, RAY, USDC... all of them are mints 29 | 30 | (async () => { 31 | // create a mint account 32 | let mint = Keypair.generate(); 33 | console.log(`mint: ${mint.publicKey.toBase58()}`); 34 | 35 | let tx = new Transaction(); 36 | tx.add( 37 | // create account 38 | SystemProgram.createAccount({ 39 | fromPubkey: feePayer.publicKey, 40 | newAccountPubkey: mint.publicKey, 41 | space: MINT_SIZE, 42 | lamports: await getMinimumBalanceForRentExemptMint(connection), 43 | programId: TOKEN_PROGRAM_ID, 44 | }), 45 | // init mint 46 | createInitializeMintInstruction( 47 | mint.publicKey, // mint pubkey 48 | 0, // decimals 49 | alice.publicKey, // mint authority (an auth to mint token) 50 | null // freeze authority (we use null first, the auth can let you freeze user's token account) 51 | ) 52 | ); 53 | 54 | console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer, mint])}`); 55 | })(); 56 | -------------------------------------------------------------------------------- /tour/create-mint/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, SystemProgram, Connection } from "@solana/web3.js"; 2 | 3 | import { 4 | createInitializeMintInstruction, 5 | getMinimumBalanceForRentExemptMint, 6 | MINT_SIZE, 7 | TOKEN_PROGRAM_ID, 8 | } from "@solana/spl-token"; 9 | 10 | import * as bs58 from "bs58"; 11 | 12 | // connection 13 | const connection = new Connection("https://api.devnet.solana.com"); 14 | 15 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 16 | const feePayer = Keypair.fromSecretKey( 17 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 18 | ); 19 | 20 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 21 | const alice = Keypair.fromSecretKey( 22 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 23 | ); 24 | 25 | // 創建 Mint (發行自己的token) 26 | 27 | // Mint的概念就像是Ethereum上的ERC-20的地址 28 | // 換句話說,USDC、RAY、SRM ... 都是mint 29 | 30 | (async () => { 31 | // create a mint account 32 | let mint = Keypair.generate(); 33 | console.log(`mint: ${mint.publicKey.toBase58()}`); 34 | 35 | let tx = new Transaction(); 36 | tx.add( 37 | // create account 38 | SystemProgram.createAccount({ 39 | fromPubkey: feePayer.publicKey, 40 | newAccountPubkey: mint.publicKey, 41 | space: MINT_SIZE, 42 | lamports: await getMinimumBalanceForRentExemptMint(connection), 43 | programId: TOKEN_PROGRAM_ID, 44 | }), 45 | // init mint 46 | createInitializeMintInstruction( 47 | mint.publicKey, // 幣的地址 48 | 0, // 位數 49 | alice.publicKey, // 增發幣的權限 50 | null // 冷凍帳戶的權限 (我們這邊先留null) 51 | ) 52 | ); 53 | 54 | console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer, mint])}`); 55 | })(); 56 | -------------------------------------------------------------------------------- /tour/create-token-account/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, SystemProgram, Connection, PublicKey } from "@solana/web3.js"; 2 | 3 | import { 4 | ACCOUNT_SIZE, 5 | createAssociatedTokenAccountInstruction, 6 | createInitializeAccountInstruction, 7 | getAssociatedTokenAddress, 8 | getMinimumBalanceForRentExemptAccount, 9 | TOKEN_PROGRAM_ID, 10 | } from "@solana/spl-token"; 11 | 12 | import * as bs58 from "bs58"; 13 | 14 | // connection 15 | const connection = new Connection("https://api.devnet.solana.com"); 16 | 17 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 18 | const feePayer = Keypair.fromSecretKey( 19 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 20 | ); 21 | 22 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 23 | const alice = Keypair.fromSecretKey( 24 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 25 | ); 26 | 27 | const mintPubkey = new PublicKey("AjMpnWhqrbFPJTQps4wEPNnGuQPMKUcfqHUqAeEf1WM4"); 28 | 29 | // create token account 30 | 31 | // you will need a token account to recieve token in Solana 32 | // in the other words, if you want to receive USDC, you will need a USDC token account 33 | // if you want to receive RAY, you will need a RAY token account 34 | // and these account's address are different (because they are not the same account) 35 | 36 | // There are two ways to create token account 37 | 38 | // 1. Random 39 | // the main concept is to create a random keypair and init it as a token account 40 | // but I don't recommend you to use this way, it will let user to store many different account 41 | // make managing token account hard. 42 | 43 | // 2. Associated Token Address (ATA) 44 | // the recommend one 45 | // this way will derive your token address by your SOL address + mint address 46 | // and anytime you get the same result, if you pass the same SOL address and mint address 47 | // it make managing token account easy, because I can know all of your token address just by your SOL address 48 | 49 | (async () => { 50 | // 1. Random 51 | { 52 | let tokenAccount = Keypair.generate(); 53 | console.log(`ramdom token address: ${tokenAccount.publicKey.toBase58()}`); 54 | 55 | let tx = new Transaction(); 56 | tx.add( 57 | // create account 58 | SystemProgram.createAccount({ 59 | fromPubkey: feePayer.publicKey, 60 | newAccountPubkey: tokenAccount.publicKey, 61 | space: ACCOUNT_SIZE, 62 | lamports: await getMinimumBalanceForRentExemptAccount(connection), 63 | programId: TOKEN_PROGRAM_ID, 64 | }), 65 | // init token account 66 | createInitializeAccountInstruction(tokenAccount.publicKey, mintPubkey, alice.publicKey) 67 | ); 68 | 69 | console.log( 70 | `create random token account txhash: ${await connection.sendTransaction(tx, [feePayer, tokenAccount])}` 71 | ); 72 | } 73 | 74 | // 2. ATA 75 | { 76 | let ata = await getAssociatedTokenAddress( 77 | mintPubkey, // mint 78 | alice.publicKey, // owner 79 | false // allow owner off curve 80 | ); 81 | console.log(`ata: ${ata.toBase58()}`); 82 | 83 | let tx = new Transaction(); 84 | tx.add( 85 | createAssociatedTokenAccountInstruction( 86 | feePayer.publicKey, // payer 87 | ata, // ata 88 | alice.publicKey, // owner 89 | mintPubkey // mint 90 | ) 91 | ); 92 | 93 | console.log(`create ata txhash: ${await connection.sendTransaction(tx, [feePayer])}`); 94 | } 95 | })(); 96 | -------------------------------------------------------------------------------- /tour/create-token-account/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, SystemProgram, Connection, PublicKey } from "@solana/web3.js"; 2 | 3 | import { 4 | ACCOUNT_SIZE, 5 | createAssociatedTokenAccountInstruction, 6 | createInitializeAccountInstruction, 7 | getAssociatedTokenAddress, 8 | getMinimumBalanceForRentExemptAccount, 9 | TOKEN_PROGRAM_ID, 10 | } from "@solana/spl-token"; 11 | 12 | import * as bs58 from "bs58"; 13 | 14 | // connection 15 | const connection = new Connection("https://api.devnet.solana.com"); 16 | 17 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 18 | const feePayer = Keypair.fromSecretKey( 19 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 20 | ); 21 | 22 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 23 | const alice = Keypair.fromSecretKey( 24 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 25 | ); 26 | 27 | const mintPubkey = new PublicKey("AjMpnWhqrbFPJTQps4wEPNnGuQPMKUcfqHUqAeEf1WM4"); 28 | 29 | // 創建Token Account 30 | 31 | // 如果要在Solana上收Token的話,會需要創造且初始化一個相對應的Token Account 32 | // 換句話說,如果你要收USDC,你會需要準備一個收USDC的Account,如果想要收RAY,也是一樣的。 33 | // 而這幾個收Token的Account在Solana裡面都會是不一樣的地址 (是不同的Account) 34 | 35 | // 創建Token Account的方法有兩種 36 | 37 | // 1. 隨機產生 38 | // 這種方法的概念就是隨便找一個地址,然後對他做Token Account的初始化 39 | // 目前不建議使用這種方法,這種方法會造成Token Account管理上的麻煩 (因為要記錄許多不同的Account) 40 | // 現在比較推薦能用第二種方式產生Token Account 41 | 42 | // 2. Associated Token Address (ATA) 43 | // 這種方式會依據你的SOL地址推算出你的Token Account地址 44 | // 所以每次算出來的都會是一樣的,在管理上只需要知道SOL的地址就可以知道他的Token地址 45 | 46 | (async () => { 47 | // 1. Random 48 | { 49 | let tokenAccount = Keypair.generate(); 50 | console.log(`ramdom token address: ${tokenAccount.publicKey.toBase58()}`); 51 | 52 | let tx = new Transaction(); 53 | tx.add( 54 | // create account 55 | SystemProgram.createAccount({ 56 | fromPubkey: feePayer.publicKey, 57 | newAccountPubkey: tokenAccount.publicKey, 58 | space: ACCOUNT_SIZE, 59 | lamports: await getMinimumBalanceForRentExemptAccount(connection), 60 | programId: TOKEN_PROGRAM_ID, 61 | }), 62 | // init token account 63 | createInitializeAccountInstruction(tokenAccount.publicKey, mintPubkey, alice.publicKey) 64 | ); 65 | 66 | console.log( 67 | `create random token account txhash: ${await connection.sendTransaction(tx, [feePayer, tokenAccount])}` 68 | ); 69 | } 70 | 71 | // 2. ATA 72 | { 73 | let ata = await getAssociatedTokenAddress( 74 | mintPubkey, // mint 75 | alice.publicKey, // owner 76 | false // allow owner off curve 77 | ); 78 | console.log(`ata: ${ata.toBase58()}`); 79 | 80 | let tx = new Transaction(); 81 | tx.add( 82 | createAssociatedTokenAccountInstruction( 83 | feePayer.publicKey, // payer 84 | ata, // ata 85 | alice.publicKey, // owner 86 | mintPubkey // mint 87 | ) 88 | ); 89 | 90 | console.log(`create ata txhash: ${await connection.sendTransaction(tx, [feePayer])}`); 91 | } 92 | })(); 93 | -------------------------------------------------------------------------------- /tour/get-mint/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, SystemProgram, Connection, PublicKey } from "@solana/web3.js"; 2 | 3 | import { getMint } from "@solana/spl-token"; 4 | 5 | // connection 6 | const connection = new Connection("https://api.devnet.solana.com"); 7 | 8 | const mintPubkey = new PublicKey("2GouGzZ5Z5s8FJmwPkca8Urma64WBFZ8twRCUbLQARkb"); 9 | 10 | // fetch mint info 11 | 12 | // you can get mint informations by a mint address 13 | 14 | (async () => { 15 | let mint = await getMint(connection, mintPubkey); 16 | console.log(mint); 17 | 18 | // you will find that the data not include name, symbol, image... 19 | // because in the begin, solana don't make these data write on chain 20 | // if you want to fetch these info, refer to 21 | // https://github.com/solana-labs/token-list 22 | })(); 23 | -------------------------------------------------------------------------------- /tour/get-mint/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, SystemProgram, Connection, PublicKey } from "@solana/web3.js"; 2 | 3 | import { getMint } from "@solana/spl-token"; 4 | 5 | // connection 6 | const connection = new Connection("https://api.devnet.solana.com"); 7 | 8 | const mintPubkey = new PublicKey("2GouGzZ5Z5s8FJmwPkca8Urma64WBFZ8twRCUbLQARkb"); 9 | 10 | // 拿mint的資訊 11 | 12 | // 你可以透過mint的地址來抓取他裡面的詳細資訊 13 | 14 | (async () => { 15 | let mint = await getMint(connection, mintPubkey); 16 | console.log(mint); 17 | 18 | // 你會發現name, symbol, image並不在回來的資訊上 19 | // 因為當初solana並沒有把這幾個欄位也規劃上鏈 20 | // 如果有需要抓取相關資訊可以參考 21 | // https://github.com/solana-labs/token-list 22 | })(); 23 | -------------------------------------------------------------------------------- /tour/get-sol-balance/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | 4 | // connection 5 | const connection = new Connection("https://api.devnet.solana.com"); 6 | 7 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 8 | const feePayer = Keypair.fromSecretKey( 9 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 10 | ); 11 | 12 | (async () => { 13 | let balance = await connection.getBalance(feePayer.publicKey); 14 | console.log(`${balance / LAMPORTS_PER_SOL} SOL`); 15 | })(); 16 | -------------------------------------------------------------------------------- /tour/get-token-account/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from "@solana/web3.js"; 2 | import { getAccount } from "@solana/spl-token"; 3 | 4 | // connection 5 | const connection = new Connection("https://api.devnet.solana.com"); 6 | 7 | const tokenAccount1Pubkey = new PublicKey("37sAdhEFiYxKnQAm7CPd5GLK1ZxWovqn3p87kKjfD44c"); 8 | 9 | const tokenAccount2Pubkey = new PublicKey("CFEPU5Jd6DNj8gpjPLJ1d9i4xSJDGYNV7n6qw53zE3n1"); 10 | 11 | // get token account info 12 | 13 | (async () => { 14 | let tokenAccount = await getAccount(connection, tokenAccount1Pubkey); 15 | console.log(tokenAccount); 16 | })(); 17 | -------------------------------------------------------------------------------- /tour/get-token-balance/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from "@solana/web3.js"; 2 | 3 | // connection 4 | const connection = new Connection("https://api.devnet.solana.com"); 5 | 6 | const tokenAccount1Pubkey = new PublicKey("37sAdhEFiYxKnQAm7CPd5GLK1ZxWovqn3p87kKjfD44c"); 7 | 8 | const tokenAccount2Pubkey = new PublicKey("CFEPU5Jd6DNj8gpjPLJ1d9i4xSJDGYNV7n6qw53zE3n1"); 9 | 10 | (async () => { 11 | let tokenAccountBalance = await connection.getTokenAccountBalance(tokenAccount1Pubkey); 12 | console.log(`decimals: ${tokenAccountBalance.value.decimals}, amount: ${tokenAccountBalance.value.amount}`); 13 | })(); 14 | -------------------------------------------------------------------------------- /tour/get-token-balance/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection, PublicKey } from "@solana/web3.js"; 2 | 3 | // connection 4 | const connection = new Connection("https://api.devnet.solana.com"); 5 | 6 | const tokenAccount1Pubkey = new PublicKey("37sAdhEFiYxKnQAm7CPd5GLK1ZxWovqn3p87kKjfD44c"); 7 | 8 | const tokenAccount2Pubkey = new PublicKey("CFEPU5Jd6DNj8gpjPLJ1d9i4xSJDGYNV7n6qw53zE3n1"); 9 | 10 | (async () => { 11 | let tokenAccountBalance = await connection.getTokenAccountBalance(tokenAccount1Pubkey); 12 | console.log(`decimals: ${tokenAccountBalance.value.decimals}, amount: ${tokenAccountBalance.value.amount}`); 13 | })(); 14 | -------------------------------------------------------------------------------- /tour/mint-to/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, Connection, PublicKey } from "@solana/web3.js"; 2 | import { createMintToCheckedInstruction } from "@solana/spl-token"; 3 | import * as bs58 from "bs58"; 4 | 5 | // connection 6 | const connection = new Connection("https://api.devnet.solana.com"); 7 | 8 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 9 | const feePayer = Keypair.fromSecretKey( 10 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 11 | ); 12 | 13 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 14 | const alice = Keypair.fromSecretKey( 15 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 16 | ); 17 | 18 | const mintPubkey = new PublicKey("AjMpnWhqrbFPJTQps4wEPNnGuQPMKUcfqHUqAeEf1WM4"); 19 | 20 | const tokenAccount1Pubkey = new PublicKey("37sAdhEFiYxKnQAm7CPd5GLK1ZxWovqn3p87kKjfD44c"); 21 | 22 | const tokenAccount2Pubkey = new PublicKey("CFEPU5Jd6DNj8gpjPLJ1d9i4xSJDGYNV7n6qw53zE3n1"); 23 | 24 | 25 | // mint token 26 | 27 | (async () => { 28 | let tx = new Transaction(); 29 | tx.add( 30 | createMintToCheckedInstruction( 31 | mintPubkey, 32 | tokenAccount1Pubkey, 33 | alice.publicKey, // mint auth 34 | 1, // amount 35 | 0 // decimals 36 | ) 37 | ); 38 | console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer, alice])}`); 39 | })(); 40 | -------------------------------------------------------------------------------- /tour/mint-to/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, Connection, PublicKey } from "@solana/web3.js"; 2 | import { createMintToCheckedInstruction } from "@solana/spl-token"; 3 | import * as bs58 from "bs58"; 4 | 5 | // connection 6 | const connection = new Connection("https://api.devnet.solana.com"); 7 | 8 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 9 | const feePayer = Keypair.fromSecretKey( 10 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 11 | ); 12 | 13 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 14 | const alice = Keypair.fromSecretKey( 15 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 16 | ); 17 | 18 | const mintPubkey = new PublicKey("AjMpnWhqrbFPJTQps4wEPNnGuQPMKUcfqHUqAeEf1WM4"); 19 | 20 | const tokenAccount1Pubkey = new PublicKey("37sAdhEFiYxKnQAm7CPd5GLK1ZxWovqn3p87kKjfD44c"); 21 | 22 | const tokenAccount2Pubkey = new PublicKey("CFEPU5Jd6DNj8gpjPLJ1d9i4xSJDGYNV7n6qw53zE3n1"); 23 | 24 | // mint token 25 | 26 | (async () => { 27 | let tx = new Transaction(); 28 | tx.add( 29 | createMintToCheckedInstruction( 30 | mintPubkey, 31 | tokenAccount1Pubkey, 32 | alice.publicKey, // mint auth 33 | 1, // amount 34 | 0 // decimals 35 | ) 36 | ); 37 | console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer, alice])}`); 38 | })(); 39 | -------------------------------------------------------------------------------- /tour/request-airdrop/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | 4 | // connection 5 | const connection = new Connection("https://api.devnet.solana.com"); 6 | 7 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 8 | const feePayer = Keypair.fromSecretKey( 9 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 10 | ); 11 | 12 | (async () => { 13 | // 1e9 lamports = 10^9 lamports = 1 SOL 14 | let txhash = await connection.requestAirdrop(feePayer.publicKey, 1e9); 15 | console.log(`txhash: ${txhash}`); 16 | })(); 17 | -------------------------------------------------------------------------------- /tour/token-transfer/main.en.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, Connection, PublicKey } from "@solana/web3.js"; 2 | import { createTransferCheckedInstruction } from "@solana/spl-token"; 3 | import * as bs58 from "bs58"; 4 | 5 | // connection 6 | const connection = new Connection("https://api.devnet.solana.com"); 7 | 8 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 9 | const feePayer = Keypair.fromSecretKey( 10 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 11 | ); 12 | 13 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 14 | const alice = Keypair.fromSecretKey( 15 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 16 | ); 17 | 18 | const mintPubkey = new PublicKey("AjMpnWhqrbFPJTQps4wEPNnGuQPMKUcfqHUqAeEf1WM4"); 19 | 20 | const tokenAccount1Pubkey = new PublicKey("37sAdhEFiYxKnQAm7CPd5GLK1ZxWovqn3p87kKjfD44c"); 21 | 22 | const tokenAccount2Pubkey = new PublicKey("CFEPU5Jd6DNj8gpjPLJ1d9i4xSJDGYNV7n6qw53zE3n1"); 23 | 24 | // mint token 25 | 26 | (async () => { 27 | let tx = new Transaction(); 28 | tx.add( 29 | createTransferCheckedInstruction( 30 | tokenAccount1Pubkey, // from 31 | mintPubkey, // mint 32 | tokenAccount2Pubkey, // to 33 | alice.publicKey, // from's owner 34 | 1, // amount 35 | 0 // decimals 36 | ) 37 | ); 38 | console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer, alice])}`); 39 | })(); 40 | -------------------------------------------------------------------------------- /tour/token-transfer/main.ts: -------------------------------------------------------------------------------- 1 | import { Keypair, Transaction, Connection, PublicKey } from "@solana/web3.js"; 2 | import { createTransferCheckedInstruction } from "@solana/spl-token"; 3 | import * as bs58 from "bs58"; 4 | 5 | // connection 6 | const connection = new Connection("https://api.devnet.solana.com"); 7 | 8 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 9 | const feePayer = Keypair.fromSecretKey( 10 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 11 | ); 12 | 13 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 14 | const alice = Keypair.fromSecretKey( 15 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 16 | ); 17 | 18 | const mintPubkey = new PublicKey("AjMpnWhqrbFPJTQps4wEPNnGuQPMKUcfqHUqAeEf1WM4"); 19 | 20 | const tokenAccount1Pubkey = new PublicKey("37sAdhEFiYxKnQAm7CPd5GLK1ZxWovqn3p87kKjfD44c"); 21 | 22 | const tokenAccount2Pubkey = new PublicKey("CFEPU5Jd6DNj8gpjPLJ1d9i4xSJDGYNV7n6qw53zE3n1"); 23 | 24 | // mint token 25 | 26 | (async () => { 27 | let tx = new Transaction(); 28 | tx.add( 29 | createTransferCheckedInstruction( 30 | tokenAccount1Pubkey, // from 31 | mintPubkey, // mint 32 | tokenAccount2Pubkey, // to 33 | alice.publicKey, // from's owner 34 | 1, // amount 35 | 0 // decimals 36 | ) 37 | ); 38 | console.log(`txhash: ${await connection.sendTransaction(tx, [feePayer, alice])}`); 39 | })(); 40 | -------------------------------------------------------------------------------- /tour/transfer/main.ts: -------------------------------------------------------------------------------- 1 | import { Connection, Keypair, Transaction, SystemProgram, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js"; 2 | import * as bs58 from "bs58"; 3 | 4 | // connection 5 | const connection = new Connection("https://api.devnet.solana.com"); 6 | 7 | // 5YNmS1R9nNSCDzb5a7mMJ1dwK9uHeAAF4CmPEwKgVWr8 8 | const feePayer = Keypair.fromSecretKey( 9 | bs58.decode("588FU4PktJWfGfxtzpAAXywSNt74AvtroVzGfKkVN1LwRuvHwKGr851uH8czM5qm4iqLbs1kKoMKtMJG4ATR7Ld2") 10 | ); 11 | 12 | // G2FAbFQPFa5qKXCetoFZQEvF9BVvCKbvUZvodpVidnoY 13 | const alice = Keypair.fromSecretKey( 14 | bs58.decode("4NMwxzmYj2uvHuq8xoqhY8RXg63KSVJM1DXkpbmkUY7YQWuoyQgFnnzn6yo3CMnqZasnNPNuAT2TLwQsCaKkUddp") 15 | ); 16 | 17 | (async () => { 18 | let tx = new Transaction().add( 19 | SystemProgram.transfer({ 20 | fromPubkey: alice.publicKey, 21 | toPubkey: new PublicKey("4MWwxzWsWmHrsbfPFwE6LDq471nqNeNMsD6DS7y8nruw"), 22 | lamports: 1 * LAMPORTS_PER_SOL, 23 | }) 24 | ); 25 | tx.feePayer = feePayer.publicKey; 26 | 27 | let txhash = await connection.sendTransaction(tx, [feePayer, alice]); 28 | console.log(`txhash: ${txhash}`); 29 | })(); 30 | --------------------------------------------------------------------------------