Liang Chen

Software Engineer

0%

前言

希望能以最直觀的方式解釋 Solana 鏈上的代幣,也就是 Token。

注意!這邊特指是 Solana 的,因為會跟乙太坊的代幣細節上有諸多不同,有興趣可以再去研究一下兩者的差別。

本文適合那些看了官方文件還是霧嗄嗄 (bū-sà-sà) 的人,像我自己。

銀行帳戶

以外匯為例,假設阿蟹今天要在銀行存美金、日幣、和歐元,那他勢必要開三個對應的帳戶。美金帳戶只能存美金、日幣帳戶只能存日幣、歐元帳戶只能存歐元。

而 Solana 的代幣其實也是一樣的規則,當我們想要持有某一代幣 (token) 時,我們就必須先創建那個代幣的帳戶 (associated account),然後才能把代幣轉過去。

所以想要持有幾種代幣,就需要幾個代幣帳戶。

鑄幣權

而除了持有各種幣,我們也可以在 Solana 上發行自己的代幣。

以美金做比喻的話,「發行」就是美國央行跟大家宣布他要發行一種貨幣,代號為 USD;在實際鑄造 USD 前,USD 的總發行量會為 0。

接著美國央行可以決定要鑄造多少 USD,而其他人都不可以鑄造 USD。美國央行鑄造出來的 USD ,可以指定給任意對象:可以給央行自己的 USD 帳戶,也可以給到任意對象的 USD 帳戶。

所以鑄幣權的概念其實滿直觀的:只有該代幣「發行人」有權鑄造 (mint) 該代幣,其他人無法鑄造,但可以被指定爲鑄造出來的代幣的「接收者」 (recipient),前提是要擁有該代幣的帳戶。

NFT

其實在 Solana 鏈上,NFT 跟我們一開始談的代幣本質上沒有區別,都是 token。

在 Solana 上發行代幣時,我們可以設定最小單位,預設為 0.000000001;而總量可以有上限,也可以無限。而 NFT 只是最小單位為 1,且發行總量為 1 的代幣!

NFT on Solana:

  • 最小單位為 1
  • 鑄造 1 後即關閉鑄造

而且一樣的,要存一個 NFT,我們也需要開一個對應的帳戶去存他。

實際操作

到上面,其實就已經把 Solana 代幣的概念講完了。

沒錯,就是這麼簡單~

以下是比較技術的部分。我們會實際操作 CLI 實作代幣的發行。

英文不錯的可以直接照著官方文件操作。

發行代幣

1
$ spl-token create-token

執行結果:

1
2
3
4
$ spl-token create-token
Creating token 4o4wYDr7mQGFFawdhdaHCz5CTTeZtsdw4aVdyCApXZHd

Signature: 55e32Jz8qdNxsBHd6TKvGQnuPHvDdKf2vYRU8f1CM4UWNR3FaFtYzmMYCHdzJ15z5CZpeXKu4Qgtcf2Bph136KK82qYQ6JPguUvtktoe6y43BAvgN2C3iyYHWZX1GAB6TbXD3B1nNRHFk4Rxcx7uhnAcB4SUGTXTG4

這樣就發行代幣了!而該代幣的代號為:4o4wYDr7mQGFFawdhdaHCz5CTTeZtsdw4aVdyCApXZHd。每次代號都不同,所以你自己實作時會產生你自己代幣的代號。以下我們都用 <代幣 ID> 表示。

鑄造

發行完後,我們可以查看該代幣總量

1
$ spl-token supply <代幣 ID>

執行結果:

1
2
$ spl-token supply 4o4wYDr7mQGFFawdhdaHCz5CTTeZtsdw4aVdyCApXZHd
0

目前代幣總量應為 0,因為我們還沒鑄造任何代幣。

代幣帳戶

開一個代幣帳戶,此帳戶「只能」存該代幣。

1
$ spl-token create-account <代幣 ID>

執行結果:

1
2
3
4
$ spl-token create-account 4o4wYDr7mQGFFawdhdaHCz5CTTeZtsdw4aVdyCApXZHd
Creating account 9Vz65o3NNNJvhPnheCr6MfFx7HUwxnZt4JhLSXEmmfqz

Signature: smvyxxvZmktfex1dT5umcbqAYZecc2ZFesNUAFQhGfbbLkj2NHM5QJcCZHKrciAVa8gBecMbTFVWhL3k23qSRpM

所以我們的代幣帳號為 9Vz65o3NNNJvhPnheCr6MfFx7HUwxnZt4JhLSXEmmfqz,他是專門用來存代幣 4o4wYDr7mQGFFawdhdaHCz5CTTeZtsdw4aVdyCApXZHd 的。

鑄造

1
$ spl-token mint <代幣 ID> <欲鑄造數量> <接收的代幣帳戶>

執行結果:

1
2
3
4
5
6
$ spl-token mint 4o4wYDr7mQGFFawdhdaHCz5CTTeZtsdw4aVdyCApXZHd 100
Minting 100 tokens
Token: 4o4wYDr7mQGFFawdhdaHCz5CTTeZtsdw4aVdyCApXZHd
Recipient: 9Vz65o3NNNJvhPnheCr6MfFx7HUwxnZt4JhLSXEmmfqz

Signature: SY6xk2KQNrvizeaCEMvbbQKgPGUYKPZWusjdwWtoAbauqLB8Y3qdKhE6Hp46yhut7z2qcwqSi67uPf6DzA3FfRD

如果 <接收的代幣帳戶> 沒給定,預設會是自己的代幣帳戶;如果之前沒有創建代幣帳戶,這次的鑄造就會失敗。

我們成功的轉了 100 的 4o4wYDr7mQGFFawdhdaHCz5CTTeZtsdw4aVdyCApXZHd 代幣到我 9Vz65o3NNNJvhPnheCr6MfFx7HUwxnZt4JhLSXEmmfqz 的帳戶了。

查看我們的帳戶:

1
$ spl-token accounts

執行結果:

1
2
3
4
5
6
7
8
9
10
11
$ spl-token accounts
Token Balance
---------------------------------------------------------------
2KN5U3jFNC5hf5tCELzg86zi27urr81XMo5MGkC4PiKY 0.100000001
2XkLQPjaXDsRSyF5xV8TBArBwwmUsVmviAyMx675EFDt 1
3M41XViQ3ARAgwEd3Nq3xZEtE7fZEQQd4X25gumMPLQQ 22
4o4wYDr7mQGFFawdhdaHCz5CTTeZtsdw4aVdyCApXZHd 100
6Ta6A8XaohhU1pAaRSKkmuMAKJyVLuYAXizKSyNyaueK 2
95dgoH52qHRapAb8DtW4uQxdcMiyhqG4cS1J4UH8744G 0.11
CmyPi9HBo1yszknenu71vWJaJ9VT5Lnt5Q7SpBXQK4AY 1
GZeBB6EdP4Yrdx9jHed15dCj9ToA9ff8uQiTkfuW6Xc9 1

可以看到第 4 行是我們剛剛新發行的代幣,有 100 枚,沒錯。其他是我所持有的其他代幣,與與其對應的持有數量。

輸入以下指令可以看得更清楚我們是在「哪個」帳戶持有「什麼」代幣:

1
$ spl-token account -v

執行結果:

1
2
3
4
5
6
7
8
9
10
11
$ spl-token account -v
Token Account Balance
----------------------------------------------------------------------------------------------------------
2KN5U3jFNC5hf5tCELzg86zi27urr81XMo5MGkC4PiKY 14si8Shrx9vnVMBbFq5QEgjX1iwG4d7Ffmt6zoHdcA1Q 0.100000001
2XkLQPjaXDsRSyF5xV8TBArBwwmUsVmviAyMx675EFDt 2o6ud26e1bCPYdfMjS1ctHYXXXYPRaEMxLq3WFmC8jyv 1
3M41XViQ3ARAgwEd3Nq3xZEtE7fZEQQd4X25gumMPLQQ CZ7645yM6g5JFcvHPBVXN8xWKdRdBiykXNLdF1t2kbbe 22
4o4wYDr7mQGFFawdhdaHCz5CTTeZtsdw4aVdyCApXZHd 9Vz65o3NNNJvhPnheCr6MfFx7HUwxnZt4JhLSXEmmfqz 100
6Ta6A8XaohhU1pAaRSKkmuMAKJyVLuYAXizKSyNyaueK ypBba7nbYHgNg6T6L37iK2C52DAMJm8vB4Q8Qx66m8y 2
95dgoH52qHRapAb8DtW4uQxdcMiyhqG4cS1J4UH8744G 7xonGX7bnV31XL7BDX1YJSEQUBSrd5mb3FdNyLbkEaNZ 0.11
CmyPi9HBo1yszknenu71vWJaJ9VT5Lnt5Q7SpBXQK4AY HVo4VECQ14Ffq5922E5X5yVbwsSebSESatt6UviZLFtA 1
GZeBB6EdP4Yrdx9jHed15dCj9ToA9ff8uQiTkfuW6Xc9 8uyD4nnnqRmV1mVuLE6hmfAiqVt7Krvc7tAmat2NN5cx 1

發行 NFT

在觀念說明時說過,NFT 在 Solana 跟一般的 token 其實沒有本質上的區別,只是最小單位、與總發行量都為 1。實作起來會如下:

-- decimals 0 是小數點後有 0 位的意思,所以該代幣只能持有整數,如果嘗試轉 0.1 的話不會噴錯,但帳戶數量不會增加,有興趣可以試試。

1
$ spl-token create-token --decimals 0

執行結果:

1
2
3
4
$ spl-token create-token --decimals 0
Creating token 9J7Bmg8Yx4dPsSiiQAvdHuwLS5dCf9ET6jyamwpaJUKR

Signature: 5aHJuH1eBvzHQZ2NCHPVPcCioKyfJdiPkhtTVtbfCvUcqeUrZ1vohf1i59pcmZWz8jo9pcqJ9ZkBvAxXstJStwGY

接著一樣是要創建對應的帳戶然後鑄造:

1
2
3
$ spl-token create-account 9J7Bmg8Yx4dPsSiiQAvdHuwLS5dCf9ET6jyamwpaJUKR

$ spl-token mint 9J7Bmg8Yx4dPsSiiQAvdHuwLS5dCf9ET6jyamwpaJUKR 1

注意,這裏其實你也可以 mint 1 個以上,也可以成功,但這樣這個 token 總量就會有 1 個以上,就不是所謂的 NFT 了。

成功 mint 1 個代幣後,我們馬上關閉該代幣的鑄造功能。

這個關閉事不能再打開的,如此就達成意義上的 NFT 了,每個 token 都是獨一無二。

命令:

1
$ spl-token authorize <代幣 ID> mint --disable

執行結果:

1
2
3
4
5
6
$ spl-token authorize 9J7Bmg8Yx4dPsSiiQAvdHuwLS5dCf9ET6jyamwpaJUKR mint --disable
Updating 9J7Bmg8Yx4dPsSiiQAvdHuwLS5dCf9ET6jyamwpaJUKR
Current mint authority: 46hytJBhguswo6S8fCcVtR85HEnb9nd1hwMxFWYnSHXc
New mint authority: disabled

Signature: 295NVTrVZYSygVXZbNwG91HE99GaosN2QA6U4fNZ5v976nFQXWRk1suLNqM81smniTuH92QgG6YBLfHQ8eM6bEwW

總結

  • 如果想要持有 1 種代幣,必須擁有 1 個對應代幣的帳戶
  • NFT 是一種最小單位與發行量為 1 的代幣

Reference

Wallet types

  • file
  • paper
  • hardware

Create wallet

create wallet (key-pair)

1
2
3
4
// file wallet
solana-keygen new --outfile YOUR-PRIVATE-KEY-FILE-PATH
// paper wallet
solana-keygen new --no-outfile

the public key will display on the terminal
if you didn’t save the pubkey, it’s fine.
we can get the pubkey from private-key

1
solana-keygen pubkey YOUR-PRIVATE-KEY-FILE-PATH

Verify key-pair

1
solana-keygen verify PUBKEY PRIVATE-KEY-FILE-PATH

Interact with wallet

  • pubkey aka. address aka. wallet address

config

1
solana config get

set URL to dev-net for developing

1
solana config set --url https://api.devnet.solana.com

send 1 SOL to address

1
solana airdrop 1 PUBKEY

get balance of address

1
solana balance PUBKEY

transfer

  • from
  • to
  • amount
  • fee-payer
1
2
3
solana transfer --from <KEYPAIR> \
<RECIPIENT_ACCOUNT_ADDRESS> <AMOUNT> \
--fee-payer <KEYPAIR>

Stake

create stake account

1
2
3
4
5
6
7
// first create a keypair for account address
solana-keygen new --no-passphrase -o stake-account.json

// then create account
solana create-stake-account --from <KEYPAIR> stake-account.json <AMOUNT> \
--stake-authority <KEYPAIR> --withdraw-authority <KEYPAIR> \
--fee-payer <KEYPAIR>

get account

1
solana stake-account <STAKE_ACCOUNT_ADDRESS>

update account auth

1
2
3
solana stake-authorize <STAKE_ACCOUNT_ADDRESS> \
--stake-authority <KEYPAIR> --new-stake-authority <PUBKEY> \
--fee-payer <KEYPAIR>

Delegate Stake (質押)

list available validators

1
solana validators

delegate

1
2
solana delegate-stake --stake-authority <KEYPAIR> <STAKE_ACCOUNT_ADDRESS> <VOTE_ACCOUNT_ADDRESS> \
--fee-payer <KEYPAIR>

deactive

1
2
solana deactivate-stake --stake-authority <KEYPAIR> <STAKE_ACCOUNT_ADDRESS> \
--fee-payer <KEYPAIR>

withdraw sol

only work when the stake-account deactived

1
2
solana withdraw-stake --withdraw-authority <KEYPAIR> <STAKE_ACCOUNT_ADDRESS> <RECIPIENT_ADDRESS> <AMOUNT> \
--fee-payer <KEYPAIR>

split

You may want to delegate stake to additional validators while your existing stake is not eligible for withdrawal. It might not be eligible because it is currently staked, cooling down, or locked up. To transfer tokens from an existing stake account to a new one

1
2
solana split-stake --stake-authority <KEYPAIR> <STAKE_ACCOUNT_ADDRESS> <NEW_STAKE_ACCOUNT_KEYPAIR> <AMOUNT> \
--fee-payer <KEYPAIR>

Reference

前言

找工作時,個人的 GitHub 常常會是一個審核的重點,可以看到這個人有做過哪些專案、code 寫的乾不乾淨、是否有參與什麼貢獻等。而 profile 頁會是別人進來最先會看到的,所以得好好整理才行,好給人好的第一印象!

跟面試一樣,簡短俐落的內容效果最好!除此之外,因應平台屬性不同,調性也可以有所不同。像是在 Linkedin 可以寫得專業點,給人可信任的感覺,因為主要是給 HR 看的;而在 GitHub 則多數是工程師才會進來看,也就是可能的未來同事或合作夥伴,所以可以弄得活潑點,展現個人魅力、或創意,如果能讓人看了有「哇!我會想跟他工作」或「哈哈,這個人好幽默」的感覺,我想就是不錯的 profile 了。

內容

沒有標準答案。但如果完全沒有方向的話,建議以「簡潔、俐落」為目標。可以適時的加點 emoji 可以讓畫面顏色比較豐富、也可以給人不那麼嚴肅的印象。

如果還是不知該如何起手,這邊分享一個別人做好的 GitHub Profile Generator 😂,可以快速的弄出一個版本,之後有想法再慢慢調整。

連結:https://rahuldkjain.github.io/gh-profile-readme-generator/

Badges

然後 FB、twitter、個人部落格等連結,我推薦使用 badge,簡潔又美觀!效果如下

Medium

也附上我找到線上別人做好的 badge 資源,可以找需要的使用:
https://github.com/Ileriayo/markdown-badges#usage

Gif

最後,因為 GitHub 有支援可以上傳 gif,所以如果有想法,放上一張吸睛的動圖會是很讚的選擇~

最近剛入坑,雖然知道風險很高,但還是抱持著體驗的心情衝進來了😅

相信很多人進場後都會面臨這個問題:該跑步就好,還是生鞋子來賣?
因為鑄造鞋子會花費 GST,但賣鞋時的獲利是 SOL。
所以當 GST 價格偏低時,比較適合鑄造新鞋子,因為可以用較便宜的 GST 去套利 SOL。

以下就分享一個超讚的計算機,幫我們以當前幣價估算,建議你目前該跑步還是生鞋!
workitwallet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
fn main() {
let mut str = String::from("Hello ");
// let mut str = "Hello "; <- this won't work
// let mut str = "Hello ".to_string(); <- this works
println!("Now str: {}", str);

// Get length & capacity of a string
println!("Length: {} Capacity: {}", str.len(), str.capacity());

// Push char
str.push('R');
println!("Now str: {}", str);

// Push string
str.push_str("ust"); // capacity is doubled to 12
println!("Now str: {}", str);

// Check if empty
println!("Is empty: {}", str.is_empty());

// Check if contains
println!("Contains 'PHP'? {}", str.contains("PHP"));

// Replace substring with
println!("Replace output: {}", str.replace("Rust", "Solana"));
// replace is not a in-place operation
// so value of str is still "Hello Rust"

// Iterate a string by word (whitespace)
for word in str.split_whitespace() {
println!("{}", word);
}

// Create a string with capacity
let str_with_cap_10 = String::with_capacity(10);
println!("Length: {} Capacity: {}", str_with_cap_10.len(), str_with_cap_10.capacity());
}

The above outputs the following:

1
2
3
4
5
6
7
8
9
10
Now str: Hello 
Length: 6 Capacity: 6
Now str: Hello R
Now str: Hello Rust
Is empty: false
Contains 'PHP'? false
Replace output: Hello Solana
Hello
Rust
Length: 0 Capacity: 10

Use “+” to append string

1
2
3
4
let s1 = String::from("Hello");
let s2 = String::from(" Rust");
let s3 = s1 + &s2; // Note: s1 is no longer valid since it's ownership is moved to s3
// s3 = "Hello Rust"

Use format! macro to combine strings

imagine we need to append strings by dashes

1
2
3
4
5
6
let s1 = "rust";
let s2 = "ruby";
let s3 = "ray";

let combined_str = format!("{}-{}-{}", s1, s2, s3);
// combined_str = "rust-rude-ray"

List some ways to format the value to be printable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
fn main() {
// Print to console
println!("Hello Rust");

// Basic Formatting
println!("{} is {}", "Rust", "rusty");

// Positional arguments
println!(
"{0} is a {1}, and {0} is {2}",
"Rust", "programming language", "efficient"
);

// Named arguments
println!(
"{target} supports {noun}",
target = "Rust",
noun = "concurrent programming"
);

// Placeholder traits
println!("Binary: {:b} Hex: {:x} Octal: {:o}", 10, 10, 10);

// Placeholder for debug trait
println!("{:?}", (100, false, "Rust"));
}

The above outputs the following:

1
2
3
4
5
6
Hello Rust
Rust is rusty
Rust is a programming language, and Rust is efficient
Rust supports concurrent programming
Binary: 1010 Hex: a Octal: 12
(100, false, "Rust")

What Is Pesudo Element

  • can be created before or after the target element
  • must has content attribute
  • each target element can only has one before and one after pseudo element

In short, pesudo elements will be rendered before or after the target element.

Below is an example, we use a label element as target element,
and use CSS to render pesudo elements next to our target element.

See the Pen Demo Pseudo Element by Leon (@n795113) on CodePen.

What Can Pseudo Elements Do

We can use pseudo elements to create a simple hover effect

See the Pen Untitled by Leon (@n795113) on CodePen.

If you know other great usages, please share with me X)

Reference

Use array!

1
2
3
4
const collect = []
items.map(item => {
collect.push(<li>{item}</li>)
})

React example

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from "react";
import ReactDOM from "react-dom";

class App extends React.Component {
render() {
const items = [1,2,3]
const collect = [<li>first row</li>];
items.map(item => collect.push(<li>{item}</li>))
return <div>{collect}</div>;
}
}

ReactDOM.render(<App />, document.getElementById("container"));

Will print out this:

  • first row
  • 1
  • 2
  • 3

將分為兩部分討論:

  1. 解釋性

什麼是真知識

認知的局限性

我們無法否認我們的思想、身體限制了我們對於世界的認知。身體上,我們的眼睛能看到的光的波段是有限的;思想上,我們的語言所能描述的範圍,就是我們能理解的範圍。

所以我們可以初步把知識分為:可理解的、不可理解的。

能驗證的就是真?

既然認知範圍外的我們碰不到、也確認不了,那我們就不管啦!關注我們能認知的就好(?)
只要在我們能理解的範圍內,驗證為真的知識就是真知識!

問題是,我們如何驗證呢?

例如,我們如何驗證天氣播報說:「今天是晴天」呢?

「很簡單啊,打開窗戶往外看不就知道了?」

「問題是我們怎麼驗證自己的感官器官運作正常呢?」

「那我們相信氣象數據總不會錯吧?」

「一樣的問題,我們怎麼驗證做氣象儀器的人、和解讀數據的人說的是真的呢?」

相信他人相信他人的相信

最後我們會發現,要理性的去驗證每件事成本太高,不可能做到去親證每件事。
所以我們選擇採信他人的說法,既然大家都說那是對的,那他是對的機率應該夠高足以讓我們相信。

但他人何嘗不是如此?

解釋性

「解釋性」高的知識是指更好解釋當前現象的知識。

豐收之神

讓稻作生長的是陽光、水?還是豐收之神?

平心而論,其實兩者都說得通,都具備解釋性。

我們今天之所以會傾向前者,只是因為前者更符合我們知道的其他物理「知識」。想像一下,如果我們是中世紀的普通人,擁有的是中世紀普通人的「知識體系」,那後者的解釋性可能的就強,因為他跟我們的其他知識擺在一起不矛盾!

我們可以發現,我們相信的其實是那與我們「知識體係」不矛盾、前後邏輯一致的知識,而整件事又受我們「經驗」限制。


小結

  • 理解之外無法觸及,理解之內的無法逐一驗證,所以我們不得不人云亦云
  • 現在普遍被認為是真的,也可能只是暫時的真,所以我們必須時時謹慎。

但只有我們如此意識到認知上的諸多限制,才能有機會更靠近那個真實。

Reference