Liang Chen

Software Engineer

0%

Write a custom Maya command that print “Hello world!” in Maya console.
There are Python API 1.0 and 2.0. The syntax would be different.
We will integrate with API 2.0 in this example.

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
38
39
40
import sys
import maya.api.OpenMaya as api

# Must include to use Maya API 2.0
def maya_useNewAPI():
pass

class HelloWorldCmd(api.MPxCommand):
kCmdName = 'helloWorld'

def __init__(self):
api.MPxCommand.__init__(self)

@staticmethod
def creator():
return HelloWorldCmd()

def doIt(self, arg_list):
print('Hello world!')

# initialize and uninitialize functions are
# needed to be loaded by Maya Plug-in Manager
def initializePlugin(mobject):
fn_plugin = api.MFnPlugin(mobject)
try:
fn_plugin.registerCommand(
HelloWorldCmd.kCmdName,
HelloWorldCmd.creator
)
except:
sys.stderr.write("Fail to register plugin: " + HelloWorldCmd.kCmdName)

def uninitializePlugin(mobject):
fn_plugin = api.MFnPlugin(mobject)
try:
fn_plugin.deregisterCommand(
HelloWorldCmd.kCmdName
)
except:
sys.stderr.write("Fail to deregister plugin: " + HelloWorldCmd.kCmdName)

Open Maya Plug-in Manager, and hit “Browse” to load our command.

Then we can run our command

1
helloworld;

We can also call it in Python:

1
2
from maya import cmds
cmds.helloWorld

Doc:
https://developers.google.com/protocol-buffers/docs/gotutorial

Proto Buffer

Download Proto Buffer:
https://developers.google.com/protocol-buffers/docs/downloads

Check if protoc installed

1
$ protoc --version

Install protoc-gen-go

1
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

Add these text to ~/.bashrc or ~/.zshrc (depends on what shell you use)

1
2
export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

This step is important to avoid Error "protoc-gen-go: program not found or is not executable"

.proto File

.proto example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
syntax = "proto3";

package proto;

option go_package ="../proto"; // avoid "unable to determine go import path"

message Request {
int64 a = 1;
int64 b = 2;
}

message Response {
int64 result = 1;
}

service AddService {
rpc Add(Request) returns (Response);
rpc Multiply(Request) returns (Response);
}

Scaffolding:

1
2
3
4
project/
└── proto/
└── package.proto

Compile

1
2
$ cd proto
$ protoc --go_out=plugins=grpc:. service.proto

plugins=grpc is needed or the output file will lack some functions

Install grpc

1
$ go get -u google.golang.org/grpc

Reference

Study how to use Borsh Serialize. When we write vanila Solana program, we need to serialize and deserialize accounts’ data by ourselves.

1
2
3
4
5
// Cargo.toml
// ...
[dependencies]
borsh = "0.9.3"
borsh-derive = "0.9.1"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// main.rs
use borsh::{BorshDeserialize, BorshSerialize};

#[derive(BorshSerialize, BorshDeserialize, Debug)]
struct Node {
num: i32,
str: String
}

fn main() {
let uint:i32 = 256; // 2^8 -> 1 byte
let node = Node{
num: uint.pow(0) + uint.pow(1)*2 + uint.pow(2)*3 + uint.pow(3)*4, // [1, 2, 3, 4]
str: String::from("好") // first 4 bytes represents how many bytes of this string
};

if let Ok(buf) = node.try_to_vec() {
println!("serialized: {:?}", buf);

if let Ok(deserz_node) = Node::try_from_slice(&buf) {
println!("deserialized: {:?}", deserz_node);
}
}
}

Run the code:

1
2
3
$ cargo run
serialized: [1, 2, 3, 4, 3, 0, 0, 0, 229, 165, 189]
deserialized: Node { num: 67305985, str: "好" }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// rest.js
const row = {
name: "Amy",
age: 32,
email: "amy@gamil.com",
job: "cook"
};

const { job: a, none: b, ...rest_props } = row;

console.log(a); // maps to property "job"
console.log("---")
console.log(b); // property "none" doesn't exist so b is undefined
console.log("---")
console.log(rest_props); // row but without "job" property

run the code:

1
2
3
4
5
6
$ node rest.js                                                                                           
cook
---
undefined
---
{ name: 'Amy', age: 32, email: 'amy@gamil.com' }

More

Problem

Problem: Implement strStr()

Given two strings needle and haystack, return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack. If needle is empty, return 0.

Thoughts

strategies:

  • two pointers to compare each characters of needle and haystack (2 while loops)
  • iterate through haystack to get slices to compare with needle
  • iterate through haystack to get slices. Hash each slice and compare the hashed with the hashed needle
  • use find method

Code

Two While Loops

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
impl Solution {
pub fn str_str(haystack: String, needle: String) -> i32 {
let hay_vec: Vec<char> = haystack.chars().collect();
let needle_vec: Vec<char> = needle.chars().collect();

let mut i_hay: usize = 0;
let mut i_needle: usize = 0;
while i_hay < hay_vec.len() {
while i_needle < needle_vec.len() {

let offset_hay = i_hay + i_needle;
if offset_hay >= hay_vec.len() {
break;
}

if hay_vec[offset_hay] != needle_vec[i_needle] {
i_needle = 0;
break;
} else if hay_vec[offset_hay] == needle_vec[i_needle] && i_needle == needle_vec.len() - 1 {
return i_hay as i32;
}

i_needle += 1;
}

i_hay += 1;
}

return -1;
}
}

Compare String Slice

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
impl Solution {
pub fn str_str(haystack: String, needle: String) -> i32 {
let (hay_len, needle_len) = (haystack.len(), needle.len());

if needle_len == 0 { return 0 }
else if needle_len > hay_len { return -1 } // avoid hay: "aa", needle: "aaaa"

for i in 0..=hay_len - needle_len {
if haystack[i..i + needle_len] == needle {
return i as i32;
}
}

-1
}
}

Use Manual Hash

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
38
39
40
41
42
43
44
const MAGIC_NUM: u64 = 31; // can be any number but should not too big

pub fn str_str(haystack: String, needle: String) -> i32 {
let (hay_len, needle_len) = (haystack.len(), needle.len());

if needle_len == 0 { return 0 }
else if needle_len > hay_len { return -1 } // avoid hay: "aa", needle: "aaaa"

let hash_needle = hash(&needle);
let mut hash_hay:u64 = 0;
let highest_pow = MAGIC_NUM.pow(needle_len as u32 - 1);

for i in 0..=hay_len - needle_len {
if hash_hay == 0 {
hash_hay = hash(&haystack[i..i + needle_len]);
} else {
/* rolling hash */
// abc -> _bc
let first_c = haystack.chars().nth(i-1).unwrap();
hash_hay -= first_c as u64 * highest_pow;
// _bc -> bc_
hash_hay *= MAGIC_NUM;
// bc_ -> bcd
hash_hay += haystack.chars().nth(i + needle_len - 1).unwrap() as u64;
}

if hash_hay == hash_needle {
return i as i32;
}
}

-1
}

fn hash(str: &str) -> u64 {
let mut buf:u64 = 0;

for c in str.chars() {
buf *= MAGIC_NUM;
buf += c as u64;
}

buf
}

Use Find

1
2
3
4
5
6
7
8
9
10
impl Solution {
pub fn str_str(haystack: String, needle: String) -> i32 {
if needle.is_empty() { return 0 }

match &haystack.find(&needle) {
Some(i) => *i as i32,
None => -1
}
}
}

If An Unicode String Given

1
2
let str = String::from("好");
println!("{}", str.len()); // This will print out 3

Since the len() returns the length of bytes not characters, we need to change our code if unicode strings given.

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
impl Solution {
pub fn str_str(haystack: String, needle: String) -> i32 {
// Get length of chars instead of bytes
let (hay_len, needle_len) = (
haystack.chars().count(),
needle.chars().count()
);

// Collect string into `Vec<char>`
let (hay_chars, needle_chars) = (
haystack.chars().collect::<Vec<char>>(),
needle.chars().collect::<Vec<char>>()
);

// The rest are same
if needle_len == 0 { return 0 }
else if needle_len > hay_len { return -1 }

for i in 0..=hay_len - needle_len {
if hay_chars[i..i + needle_len] == needle_chars {
return i as i32;
}
}

-1
}
}

Conculsion

  • utilize string slice or find looks easier.
  • in some situations, hash may be useful. However, for this case, it may not be the best practise.

Problem

Given a inorder-traversal result and a preorder-traversal result of a tree, Figure out the tree structure.

For example:

  • inorder: 1,2,3,4,5,6,7
  • preorder: 5,2,1,4,3,6,7

What is the structure of this tree? Can we build a tree based on these two clues?

Thoughts

The first element of preorder-traversal must be the Root of a tree. On the other hand, we can determine left and right from inorder-traversal after we get the root. As the result, we can again get inorder and preorder result of left-sub-tree and right-sub-tree, then both left and right redo above process until they get no or only one element.

Overview the tree for now

So we can expect we can get the whole tree if we repeat the process.

Code

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
pub struct Node {
pub val: i32,
pub left: Option<Box<Node>>,
pub right: Option<Box<Node>>
}

impl Node {
pub fn new(val: i32) -> Self {
Node {
val,
left: None,
right: None
}
}

pub fn inorder_traversal(&self) {
if let Some(left) = &self.left {
left.inorder_traversal();
}

println!("{}", &self.val);

if let Some(right) = &self.right {
right.inorder_traversal();
}
}

pub fn preorder_traversal(&self) {

println!("{}", &self.val);

if let Some(left) = &self.left {
left.preorder_traversal();
}

if let Some(right) = &self.right {
right.preorder_traversal();
}
}
}

pub fn rebuild_tree(inord_arr: &Vec<i32>, preord_arr: &Vec<i32>) -> Node {

let root_val = preord_arr[0];
let mut root = Node::new(root_val);

// Find the index of root from inorder result
let inord_arr_root_i = inord_arr
.iter()
.position(|&x| x == root_val)
.unwrap();

let inord_left_sub_len = inord_arr_root_i;

if inord_left_sub_len == 1 {
root.left = Some(Box::new(Node::new(inord_arr[0])));
} else if inord_left_sub_len > 1 {
let preord_left_sub_vec = preord_arr[1..inord_left_sub_len+1].to_vec();
let inord_left_sub_vec = inord_arr[..inord_arr_root_i].to_vec();
root.left = Some(
Box::new(rebuild_tree(
&inord_left_sub_vec,
&preord_left_sub_vec
))
);
}

let inord_right_sub_len = inord_arr.len() - inord_left_sub_len - 1;
if inord_right_sub_len == 1 {
root.right = Some(Box::new(Node::new(inord_arr[inord_arr_root_i + 1])));
} else if inord_right_sub_len > 1 {
let preord_right_sub_vec = preord_arr[1+inord_left_sub_len..].to_vec();
let inord_right_sub_vec = inord_arr[inord_arr_root_i+1..].to_vec();
root.right = Some(
Box::new(rebuild_tree(
&inord_right_sub_vec,
&preord_right_sub_vec
))
);
}

root
}

fn main() {
let inord_vec = vec![1,2,3,4,5,6,7];
let preord_vec = vec![5,2,1,4,3,6,7];
let rebuild_tree_n = rebuild_tree(&inord_vec, &preord_vec);

rebuild_tree_n.inorder_traversal(); // 1,2,3,4,5,6,7
println!("--------");
rebuild_tree_n.preorder_traversal(); // 5,2,1,4,3,6,7
}

Problem

Problem: Koko Eating Bananas

Let’s draw some pictures to understand this quesiton. If there are 4 piles of bananas like below, the given array would be [3, 2, 4, 1].

Then if Koko can eat 3 bananas per hour, it will takes her 5 hours to eat all the bananas like blow.

Now, if the limit hours h is 5 then 3 bananas per hour satisfies. However, if h, for example, is 3, then Koko need to SPEEDS UP to finish all the bananas. On the other hand, if h is 10 which means we still have time right? So Koko should SLOW DOWN to certain speed to make herself chill but still can finish all the bananas in time.

So what is the best eating speed is the question!

Thoughts

  • The maximum of speed should equals to the number of the bananas of the tallest pile
  • The minimun of speed should equals to 1

Which means we are trying to find the best from 1 ~ piles.max(). Searching a value from a sorted array… maybe binary search is a soslution.

Code

Try #1

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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
use std::cmp::Ordering;

impl Solution {
pub fn min_eating_speed(piles: Vec<i32>, h: i32) -> i32 {
let mut sorted_piles = piles.clone();
sorted_piles.sort();
Solution::min_eating_speed_inner(
&sorted_piles,
h,
1,
*sorted_piles.last().unwrap()
)
}

fn min_eating_speed_inner(piles: &Vec<i32>, h: i32, min: i32, max: i32) -> i32 {
// return codition
if piles.len() == 0 || min > max {
return -1;
}

// speeds arr is [min..max]
// so the mid speed is (min + max) / 2
let mid = min + (max - min) / 2;

// piles divides speed return spend time
let time = Solution::count_eating_time(&piles, mid);

// find the matched speed or continue go down to sub tree
match time.cmp(&h) {
// spend too much time to eat, speed up
Ordering::Greater => Solution::min_eating_speed_inner(piles, h, mid + 1, max),
// match, try to find a better (slower) speed
_ => {
let new_speed = Solution::min_eating_speed_inner(piles, h, min, mid - 1);

if new_speed > 0 {
return new_speed;
}
mid
}
}
}

fn count_eating_time(piles: &Vec<i32>, speed: i32) -> i32 {
// count the time to eat each pile and return sum
let sum = piles
.into_iter()
.map(|pile| {
if pile % speed > 0 {
return pile / speed + 1
}
return pile / speed
})
.sum();

sum
}
}

Thoughts

The core concept is to find the target from a sorted array which is quite suitable to use binary serach I think.

Here are my instructions:

  1. Get the middle element, mid
  2. Compare the mid with the target. there will be 3 branches:
    a. target < mid: continue to find the target from the LEFT side of the mid
    b. target > mid: continue to find the target from the RIGHT side of the mid
    c. target = mid: In this case, [index_of_mid, index_of_mid] will be our base answer so keep it. Then based on this answer, we need to go further to the both left and right to seek if there is other elements match to the search target.

For example, if the given array is [1, 2, 3, 4, 4, 4, 4, 6, 9], and the search target is 4.

Then we are trying to get this green range.

When we go through the instructions, we will get the middle element, and hit the c case.

Then base on this position, we seek towar left and right to get the final range.

So the answer should be [3, 6]

Code

Try #1

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
38
impl Solution {
pub fn search_range(nums: Vec<i32>, target: i32) -> Vec<i32> {
if nums.len() == 0 {return vec![-1,-1]}
bst_search_index_rec(&nums, target, 0, (nums.len()-1) as i32)
}
}

fn bst_search_index_rec(arr: &Vec<i32>, val: i32, start_i: i32, end_i: i32) -> Vec<i32> {
if start_i > end_i {
return vec![-1, -1];
}

let mut result: Vec<i32> = vec![-1, -1];

let mid_i = (start_i + end_i)/2;

if val == arr[mid_i as usize] {
result[0] = mid_i as i32;
result[1] = mid_i as i32;

let tmp_left_result = bst_search_index_rec(&arr, val, start_i, (mid_i-1) as i32);
if tmp_left_result[0] != -1 {
result[0] = tmp_left_result[0];
}

let tmp_right_result = bst_search_index_rec(&arr, val, (mid_i+1) as i32, end_i);
if tmp_right_result[1] != -1 {
result[1] = tmp_right_result[1];
}

} else if val < arr[mid_i as usize] {
result = bst_search_index_rec(&arr, val, start_i, (mid_i - 1) as i32);
} else if val > arr[mid_i as usize] {
result = bst_search_index_rec(&arr, val, (mid_i + 1) as i32, end_i);
}

return result;
}

Try #2

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
use std::cmp::Ordering; // Don't forget to import this

impl Solution {
pub fn search_range(nums: Vec<i32>, target: i32) -> Vec<i32> {

Solution::bst_search_index_inner(&nums, target, 0, nums.len() as i32 - 1)
}

fn bst_search_index_inner(arr: &Vec<i32>, val: i32, start_i: i32, end_i: i32) -> Vec<i32> {
// return condition
if start_i > end_i || arr.len() == 0 {
return vec![-1, -1];
}

// avoid overflows rather than simply (start_i + end_i) / 2
let mid_i = start_i + (end_i - start_i) / 2;

match val.cmp(&arr[mid_i as usize]) {
Ordering::Less => Solution::bst_search_index_inner(&arr, val, start_i, mid_i - 1),
Ordering::Greater => Solution::bst_search_index_inner(&arr, val, mid_i + 1, end_i),
Ordering::Equal => {
let l = Solution::bst_search_index_inner(arr, val, start_i, mid_i - 1);
let r = Solution::bst_search_index_inner(arr, val, mid_i + 1, end_i);

match (l[0], r[1]) {
(-1, -1) => vec![mid_i, mid_i],
(-1, new_r) => vec![mid_i, new_r],
(new_l, -1) => vec![new_l, mid_i],
(new_l, new_r) => vec![new_l, new_r]
}
}
}
}

}

Conclusions

Improvements

  • avoid overflows in some edge cases
  • move the recursion function into Solution struct
  • more rusty coding style

Learned

  • Combo of cmp & match
  • How to use the function itself in a implementation
    -> Solution::bst_search_index_inner

Just a quick note for these two functions I learned today.

checked_sub is used to check if the abstraction of an integer overflows. For example:

1
2
3
4
5
let mut num_u32: u32 = 0;
num_u32 -= 1; // overflow

let mut num_i32 = i32::MIN; // -2147483648
num_i32 -= 1; // overflow

To prevent it, we can use the combo of checked_sub and unwrap_or. checked_sub will return Option<self> which means if there is no overflow occurs, we get Some(substraction_result), else we will get None. unwarp_or can then give the result a default value if we get None or just unwrap Some(substraction_result) to return substraction_result. Let’s look at the code.

1
2
3
4
5
6
7
let mut num_i32 = i32::MIN + 1; // -2147483648 + 1

num_i32 = num_i32.checked_sub(1).unwrap_or(i32::MIN); // -2147483648

num_i32 = num_i32.checked_sub(1).unwrap_or(100); // 100
// Since it overflows, it will return the default value
// that we give to unwrap_or which is 100 for this example

That’s it for today!

前言

希望能以最直觀的方式解釋 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