tsunagi-functionsでSymbolブロックチェーンのトランザクションを自由自在に作ろう！（Go編）

この記事は、以下に紹介したtsunagi-functionsのGo実装版です。

https://qiita.com/nem_takanobu/items/4b5d67fe9eb80ae35433

トランザクション構築の内部処理やノードへの通知方法については以下の記事もご参考ください。

https://qiita.com/nem_takanobu/items/a8c46d67008e72b713e0

Symbolブロックチェーンをよく知らないという方はこちらで速習することができます。

https://github.com/xembook/quick_learning_symbol

# リポジトリ

##### tsunagi-functions for Go
https://github.com/xembook/tsunagi-sdk/tree/main/go

関数群の提供のため、sdkという名称は今後functionsに変更予定です。


# テスト

テストスクリプトを以下に置いています。実行することができればそのロジックをそのまま実装に利用することができます。

#### test_0_1.go

https://github.com/xembook/tsunagi-sdk/blob/main/go/0_1_test.go

```mod:mod.go
module hello
go 1.18

require (
	golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e
	golang.org/x/exp v0.0.0-20221004215720-b9f4876ce741
	golang.org/x/text v0.3.6
	golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/pmezard/go-difflib v1.0.0 // indirect
	github.com/stretchr/testify v1.8.0 // indirect
	gopkg.in/yaml.v3 v3.0.1 // indirect
)

```

```sh:テスト実行
go test
```

# 実装例

まずは簡単な実装例を紹介します。

### 転送トランザクション

```go
func getNetworkInfo() map[string]any {
	return map[string]any{
		"version":1,
		"network":"TESTNET",
		"generationHash":"7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836",
		"epochAdjustment":1637848847,
		"catjasonBase":"https://xembook.github.io/tsunagi-sdk/catjson/",
	}
}

func getDeadline(network map[string]any) int {
	//	now := time.Now()
	now := network["epochAdjustment"].(int)
	deadline := ((now + 7200) - network["epochAdjustment"].(int)) * 1000
	return deadline
}

func getPayload(tx map[string]any,network map[string]any) string {
	catjson := loadCatjson(tx,network)
	layout := loadLayout(tx,catjson,false)
	preparedTx := prepareTransaction(tx,layout,network) //TX事前準備
	parsedTx := parseTransaction(preparedTx,layout,catjson,network)
	builtTx := buildTransaction(parsedTx) 
	signature := signTransaction(builtTx,"94ee0f4d7fe388ac4b04a6a6ae2ba969617879b83616e4d25710d688a89d80c7",network)
	builtTx = updateTransaction(builtTx,"signature","value",signature)
	//	txHash := hashTransaction(tx1["signer_public_key"].(string),signature,builtTx,network);
	payload := hexlifyTransaction(builtTx,0)
	return payload
}

network  := getNetworkInfo()
deadline := getDeadline(network)

tx1 := map[string]any{
	"type" : "TRANSFER",
	"signer_public_key" : "5f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb",
	"fee" : 25000,
	"deadline" : deadline,
	"recipient_address" : generateAddressId("TCO7HLVDQUX6V7C737BCM3VYJ3MKP6REE2EKROA"),
	"mosaics" : []any{
		map[string]any{"mosaic_id" :  0x2A09B7F9097934C2, "amount" : 1},
		map[string]any{"mosaic_id" :  0x3A8416DB2D53B6C8, "amount" : 100},
	},
	"message" : "Hello Tsunagi(Catjson) SDK!",
}

payload := getPayload(tx1,network)


```

### 通知

```go
	client := &http.Client{}
	json, _ := json.Marshal(payload)
	req , _ := http.NewRequest(http.MethodPut, "https://sym-test-02.opening-line.jp:3001/transactions", bytes.NewBuffer(json))
	req.Header.Set("Content-Type", "application/json; charset=utf-8")
	resp, _ := client.Do(req)
	fmt.Println(resp.StatusCode)

```

### 連署

連署を必要とするトランザクションの場合は、tx1 に加えて以下のような定義と署名の追加が必要になります。

```go

cosignature1 := map[string]any{
	"version":0,
	"signer_public_key":"6199bae3b241df60418e258d046c22c8c1a5de2f4f325753554e7fd9c650afec",
	"signature":"",
}

cosignature2 := map[string]any{
	"version":0,
	"signer_public_key":"886adfbd4213576d63ea7e7a4bece61c6933c27cd2ff36f85155c8febfb6eb4e",
	"signature":"",
}

aggTx := map[string]any{
	"type" : "AGGREGATE_COMPLETE",
	"signer_public_key" : "5f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb",
	"fee" : 1000000,
	"deadline" : deadline,
	"transactions": []any{tx1,tx2,tx3},
	"cosignatures": []any{cosignature1,cosignature2},
}

catjson := loadCatjson(aggTx,network)
layout := loadLayout(aggTx,catjson,false)
preparedTx := prepareTransaction(aggTx,layout,network) //TX事前準備
parsedTx := parseTransaction(preparedTx,layout,catjson,network)
builtTx := buildTransaction(parsedTx) 
signature := signTransaction(builtTx,"94ee0f4d7fe388ac4b04a6a6ae2ba969617879b83616e4d25710d688a89d80c7",network)
builtTx = updateTransaction(builtTx,"signature","value",signature)
txHash := hashTransaction(aggTx["signer_public_key"].(string),signature,builtTx,network);

preparedTx["cosignatures"].([]any)[0].(map[string]any)["signature"] = cosignTransaction(txHash,"fa6373f4f497773c5cc55c103e348b139461d61fd4b45387e69d08a68000e06b")
preparedTx["cosignatures"].([]any)[1].(map[string]any)["signature"] = cosignTransaction(txHash,"1e090b2a266877a9f88a510af2eb0945a63dc69dbce674ccd83272717d4175cf")

idx := slices.IndexFunc(layout, func(item any) bool {return item.(map[string]any)["name"] == "cosignatures"})
if idx >= 0 {
	cosignaturesLayout := layout[idx].(map[string]any)
	parsedCosignatures := parseTransaction(preparedTx,[]any{cosignaturesLayout},catjson,network)
	builtTx = updateTransaction(builtTx,"cosignatures","layout",parsedCosignatures[0].(map[string]any)["layout"])
}
payload := hexlifyTransaction(builtTx,0);

```

他にもテストスクリプトに様々なトランザクションを記述しているのでぜひお試しください！