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


tusnagi-functionsはSymbolブロックチェーンのトランザクションを簡単に生成するための関数群です。

#### tsunagi-functions
https://github.com/xembook/tsunagi-sdk

### 目的

Symbolは特殊な言語を習得する必要なしに、また既存リソースと組み合わせることでその真価を発揮します。しかしながら、現状ではあらゆる言語でその開発が容易という段階ではありません。tsunagi-functionsは整備されたSDKがコミュニティから生み出されるまでの「つなぎ」として活用していただくことを目的としています。

現在　JavaScript、PHP、Ruby、Dart、Go、Rustに対応しており、順次対応言語を増やしています。


#### 実装例

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

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

https://qiita.com/nem_takanobu/items/6cf21e7e0b5b5aace4e8

https://qiita.com/nem_takanobu/items/2d625f828c33f7d67a55

https://qiita.com/nem_takanobu/items/419d6a551ca79d2e3b2a

##### その他SDK

###### C#
https://github.com/0x070696E65/symbol_cs_dual_sdk

### 安全なの？

異なるSDKで開発されたウォレットとの**マルチシグ（アグリゲートトランザクション）での利用**をおすすめします。残念ながら開発者（私）は対応する全ての言語についてメモリ管理など各言語のクセや特徴を知り尽くしているわけではありません。また、tsunagi-functionsの応用が期待されるサーバーサイドやIoTなどはクライアントで完結するアプリケーションよりセキュリティ的に危険性の高い環境での用途が多くなります。しかし、Symbolブロックチェーンはそういった環境でも安全に使えるようにマルチシグがプロトコルレベルで実装されています。ぜひ、サーバーサイドやIoTによる自動署名とクライアントサイドの連署で実現するトランザクションに挑戦してみてください。

### 概要

symbol/catbufferで定義されたトランザクションのレイアウトを元に、Symbolブロックチェーンのノードに通知するためのpayloadを生成します。


#### catbuffer / schemas
https://github.com/symbol/symbol/tree/dev/catbuffer/schemas/symbol

catbuffer/schemas は独自DSLで記述されていますが、catbuffer/parserによって yamlに出力することが可能です。
tsunagi-functionsではこの出力されたyamlファイルをさらにjsonファイルにコンバートしたものをcatjsonとして使用します。

#### tsunagi-sdk / catjson
https://github.com/xembook/tsunagi-sdk/tree/main/catjson

tsunagi-functionsではこのcatjsonから取得できたトランザクションのレイアウトに必要なパラメータを注入することでトランザクションの配列を作ります。その後、署名・シリアライズすることでSymbolが解読可能なpayloadを出力します。

### catjsonのレイアウト例

例えば、catjsonで転送トランザクション(transfer transaction)を調べてみると以下のようにレイアウトが定義されています。
- transfer.json
    - factory_type = Transacion
    - name = TransferTransaction

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29187/be4695b4-108a-624c-0eca-0daab4f64527.png)

ここに以下のようなトランザクションとネットワーク情報を注入します。

```js
network = {
	version:1,
	network:'TESTNET',
	generationHash:'7fccd304802016bebbcd342a332f91ff1f3bb5e902988b352697be245f48e836',
	currencyDivisibility:6,
	epochAdjustment:1637848847,
	catjasonBase:"https://xembook.github.io/tsunagi-sdk/catjson/",
}
```
:::note info
2023年7月現在、テストネットの値は以下の通りです。
generationHash
49D6E1CE276A85B70EAFE52349AACCA389302E7A9754BCF1221E79494FC665A4
epochAdjustment
1667250467
:::


```js
tx1 = {
	type:'TRANSFER',
	signer_public_key:"5f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb",
	fee:25000n,
	deadline:this.deadline,
	recipient_address:generateAddressId("TCO7HLVDQUX6V7C737BCM3VYJ3MKP6REE2EKROA"),
	mosaics:[
		{mosaic_id: 0x2A09B7F9097934C2n, amount: 1n},
		{mosaic_id: 0x3A8416DB2D53B6C8n, amount: 100n},
	],
	message:"Hello Tsunagi(Catjson) SDK!",
}
```

`TESTNET`や`TRANSFER`など、使用できる固定値はすべてcatjsonに定義されている必要があります。例えば、transaction typeは以下のように定義されています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29187/fb3fa6d7-eeca-8e45-cb24-65e3994827d2.png)

このレイアウトにnetworkとtxのデータを流し込むと以下のような感じでvalue値が挿入された配列が取得できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29187/4626a58e-0a9b-e369-89a3-ff30d741d5f0.png)

最後にシリアライズすると、ノードが理解できるデータが取得できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/29187/89984194-c0a3-e99a-9f76-09b779fe536d.png)

あとは好みのhttpクライアントライブラリを使用してノードにPUTしてください。

#### announceTransaction

通常のトランザクションを通知する場合

https://symbol.github.io/symbol-openapi/v1.0.3/#tag/Transaction-routes/operation/announceTransaction

#### announcePartialTransaction

署名が完了していないトランザクションを通知する場合

https://symbol.github.io/symbol-openapi/v1.0.3/#tag/Transaction-routes/operation/announcePartialTransaction

#### announceCosignatureTransaction
追加で署名（連署）する場合

https://symbol.github.io/symbol-openapi/v1.0.3/#tag/Transaction-routes/operation/announceCosignatureTransaction

部分署名と連署は通常のトランザクションのエンドポイントと異なるのでご注意ください。
各言語での通知方法はこちらの情報が参考になるかもしれません。

https://qiita.com/nem_takanobu/items/09b6b687f3cbe10af945


# functions

主なfunctions は ロード系、トランザクション編集系、ID生成系の3系統があります。

### ロード系

catjsonファイルのロード、トランザクションレイアウトの記述されたオブジェクトを読み込みます。




- loadCatjson(tx,network)
    - catjsonを出力
        - 準備したtx,networkを入力
- loadLayout(tx,catjson,isEmbedded)
    - トランザクションレイアウトを出力
        - catjson
            - loadCatjsonで取得したjsonを入力
        - isEmbedded
            - 埋め込みトランザクションのlayoutを取りたい場合はtrue

### トランザクション編集系

- prepareTransaction(tx,layout,network)
    - txの事前処理
- parseTransaction(tx,layout,catjson,network)
    - layoutの解析と加工
- buildTransaction(parsedTx)
    - トランザクションの構築
- hashTransaction(signer,signature,builtTx,network)
    - トランザクションのハッシュ値を計算
- updateTransaction(builtTx,name,type,value)
    - 構築済みトランザクションの更新
- hexlifyTransaction(builtTx,alignment)
    - トランザクションの16進数化（シリアライズ）
- signTransaction(builtTx,priKey,network)
    - トランザクションの署名
- cosignTransaction(txhash,priKey)
    - トランザクションの連署





### ID生成系

- generateAddressId(address)
    - アドレスからIDを生成します。
- generateNamespaceId(name, parentNamespaceId = 0)
    - ネームスペースからIDを生成します
- generateKey(name)
    - 文字列からKey値を生成します。
- generateMosaicId(ownerAddress, nonce)
    - アドレスとノンス値からモザイクIDを生成します。
- convertAddressAliasId(namespaceId)
    - ネームスペースIDをアドレスにリンクされたHEX文字列に変換します。

## 実装例

アグリゲートコンプリートトランザクションを以下のように定義します。

```js
//Alice->Bob
let tx1 = {
	type:'TRANSFER',
	signer_public_key:"5f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb",
	recipient_address:generateAddressId("TCO7HLVDQUX6V7C737BCM3VYJ3MKP6REE2EKROA"),
	mosaics:[
		{mosaic_id: 0x2A09B7F9097934C2n, amount: 1n},
		{mosaic_id: 0x3A8416DB2D53B6C8n, amount: 100n},
	],
	message:"Hello Tsunagi(Catjson) SDK!",
}
//Bob->Caroll
let tx2 = {
	type:'TRANSFER',
	signer_public_key:"6199BAE3B241DF60418E258D046C22C8C1A5DE2F4F325753554E7FD9C650AFEC",
	recipient_address:generateAddressId("TDZBCWHAVA62R4JFZJJUXQWXLIRTUK5KZHFR5AQ"),
	mosaics:[
		{mosaic_id: 0x3A8416DB2D53B6C8n, amount: 100n},
		{mosaic_id: 0x2A09B7F9097934C2n, amount: 1n},
	],
	message:"Hello Carol! This is Bob.",
}
//Caroll->Alice
let tx3 = {
	type:'TRANSFER',
	signer_public_key:"886ADFBD4213576D63EA7E7A4BECE61C6933C27CD2FF36F85155C8FEBFB6EB4E",
	recipient_address:generateAddressId("TBUXMJAYYW3EH3XHBZXSBVGVKXKZS4EH26TINKI"),
	mosaics:[
		{mosaic_id: 0x3A8416DB2D53B6C8n, amount: 100n},
		{mosaic_id: 0x2A09B7F9097934C2n, amount: 1n},
	],
	message:"Hello Alice, This is Carol.",
}

let cosignature1 = {
	version:0n,
	signer_public_key:"6199BAE3B241DF60418E258D046C22C8C1A5DE2F4F325753554E7FD9C650AFEC",
	signature:"",
}

let cosignature2 = {
	version:0n,
	signer_public_key:"886ADFBD4213576D63EA7E7A4BECE61C6933C27CD2FF36F85155C8FEBFB6EB4E",
	signature:"",
}

let aggTx = {
	type:'AGGREGATE_COMPLETE',
	signer_public_key:"5f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb",
	fee:1000000n,
	deadline:this.deadline,
	transactions:[tx1,tx2,tx3],
	cosignatures:[cosignature1,cosignature2]
}

```

ネームスペースを以下のように使用することができます。
```js
let tx1 = {
	type:'TRANSFER',
	signer_public_key:"5f594dfc018578662e0b5a2f5f83ecfb1cda2b32e29ff1d9b2c5e7325c4cf7cb",
	fee:25000n,
	deadline:this.deadline,
	recipient_address:convertAddressAliasId(generateNamespaceId("xembook")),

	mosaics:[
		{mosaic_id: generateNamespaceId("xym",generateNamespaceId("symbol")), amount: 100n},
		{mosaic_id: generateNamespaceId("tomato",generateNamespaceId("xembook")), amount: 1n},
	],
	message:"Hello Tsunagi(Catjson) SDK!",
}
```

アドレスの代わりに使用する場合は generateNamespaceIdをconvertAddressAliasIdでさらにラップしてください。サブネームスペースは第二引数にその親となるネームスペースを指定してください。

```js
//catjsonの取得
let catjson = await loadCatjson(aggTx,network);
//トランザクションレイアウトの取得
let layout = await loadLayout(aggTx,catjson,false); //isEmbedded false
//トランザクションの事前準備
let preparedTx = await prepareTransaction(aggTx,layout,this.network);
//レイアウトの解析とトランザクションデータの注入
let parsedTx   = await parseTransaction(preparedTx,layout,catjson,this.network);
//トランザクションの構築
let builtTx    = buildTransaction(parsedTx);
//署名
let signature = signTransaction(builtTx,this.privateKey,this.network);
//トランザクションの更新
builtTx = updateTransaction(builtTx,"signature","value",signature);
//トランザクションハッシュ作成
let txHash = hashTransaction(aggTx.signer_public_key,signature,builtTx,this.network);

//連署
preparedTx.cosignatures[0].signature = cosignTransaction(txHash,this.bobPrivateKey);
preparedTx.cosignatures[1].signature = cosignTransaction(txHash,this.carolPrivateKey);
let cosignaturesLayout = layout.find(lf=>lf.name === "cosignatures");
let parsedCosignatures = await parseTransaction(preparedTx,[cosignaturesLayout],catjson,network); //構築
//連署の埋め込み
builtTx = updateTransaction(builtTx,"cosignatures","layout",parsedCosignatures[0].layout);
//ペイロード出力
let payload = hexlifyTransaction(builtTx);

```
手続きが少し長いと感じられるかもしれませんが、言語の仕様に依存しないことを心がけたため、基本的な関数の使い方のみで記述できるようにしています。
ペイロードが取得出来たらノードに通知するとトランザクションが実行されます。

```js
payload = `{"payload": "${pyload}"}`;
response = await fetch(`https://sym-test-01.opening-line.jp:3001/transactions`, {
	method: 'put',
	body: payload,
	headers: {'Content-Type': 'application/json'}
})
await response.text();
```

概要編の解説は以上となります。
今後、各言語に対応した具体的なトランザクションの書き方を例示した詳細編に続きます。
ご期待ください。