Previous << WalletConnect 2.0 Manual Configuration
Next >> Migration Guide v0.25.0
Overview
このリファレンスでは、SDKで利用可能なすべてのメソッドを紹介し、それらのメソッドがどのように機能するかを詳細に説明しています。SDKはオープンソースであり、ライセンスに従ってご利用いただけます。
ライブラリクライアントの仕様は、こちらでご覧いただけます。
Getting Started
Installing
Go Flow SDKをインストールする推奨の方法は、Goモジュールを使用することです。
Goのプロジェクトをすでに初期化している場合は、ターミナルで次のコマンドを実行することができます。
go get github.com/onflow/flow-go-sdk
通常、dependenciesを特定のバージョンに固定しておくことが推奨されています。最新のバージョンを確認するには、SDK releasesページを参照してください。
Importing the Library
ライブラリのインストールが済んだら、インポートできます。
import "github.com/onflow/flow-go-sdk"
Connect
Go SDKライブラリは、アクセスノードとの通信にHTTPまたはgRPC APIを使用し、正しいアクセスノードAPI URLが設定されていなければなりません。ライブラリは、Flow AN APIに接続するためのデフォルト・ファクトリーを提供しており、クライアントインタフェースを使用すれば、HTTPとgRPCを簡単に切り替えることができます。(クライアントインタフェースも提供されています)
クライアントの作成に関するその他の例を、こちらで参照してください。
Basic Example:
/* common client interface */
var flowClient client.Client
/* initialize an http emulator client */
flowClient, err := http.NewClient(http.EmulatorHost)
/* initialize a gPRC emulator client */
flowClient, err = grpc.NewClient(grpc.EmulatorHost)
HTTP クライアントまたは gRPC クライアントを直接初期化することで、ネットワークのオプションにアクセスすることもできます。しかし、共通のインターフェースが実装されていないため、これらを簡単に切り替えることはできないことには注意してください。これは、実装がそれらの高度なオプションを必要とする場合にのみお勧めします。高度な例:
/* initialize http specific client */
httpClient, err := http.NewHTTPClient(http.EMULATOR_URL)
/* initialize grpc specific client */
grpcClient, err := grpc.NewGRPCClient(
grpc.EMULATOR_URL,
grpcOpts.WithTransportCredentials(insecure.NewCredentials()),
)
Querying the Flow Network
アクセスノードとの接続が確立されたら、Flowネットワークにqueryで問い合わせて、ブロック、アカウント、イベント、トランザクションに関するデータを取得することができます。以下では、これらのデータをそれぞれ取得する方法について説明していきます。
Get Blocks
ネットワークにIDと高さによるブロックをqueryします、または最新のブロックの取得します。
📖 ブロックIDは、ブロックペイロード全体のSHA3-256 hashです。このハッシュは、(GetLatestBlock
からのレスポンスなど)ブロックレスポンスオブジェクトのIDとして保存されています。
📖 ブロック高さは、チェーン上のブロックの高さを表します。最新のブロック高さは、有効なブロックが生成されるごとに1ずつ増加します。
Examples
この例では、最新のブロックの取得方法と、高さまたはIDによるその他のブロックの取得方法を説明しています。
func demo() {
ctx := context.Background()
flowClient := examples.NewFlowClient()
/* get the latest sealed block */
isSealed := true
latestBlock, err := flowClient.GetLatestBlock(ctx, isSealed)
printBlock(latestBlock, err)
/* get the block by ID */
blockID := latestBlock.ID.String()
blockByID, err := flowClient.GetBlockByID(ctx, flow.HexToID(blockID))
printBlock(blockByID, err)
/* get block by height */
blockByHeight, err := flowClient.GetBlockByHeight(ctx, 0)
printBlock(blockByHeight, err)
}
func printBlock(block *flow.Block, err error) {
examples.Handle(err)
fmt.Printf("\nID: %s\n", block.ID)
fmt.Printf("height: %d\n", block.Height)
fmt.Printf("timestamp: %s\n\n", block.Timestamp)
}
出力結果:
ID: 835dc83939141097aa4297aa6cf69fc600863e3b5f9241a0d7feac1868adfa4f
height: 10
timestamp: 2021-10-06 15:06:07.105382 +0000 UTC
ID: 835dc83939141097aa4297aa6cf69fc600863e3b5f9241a0d7feac1868adfa4f
height: 10
timestamp: 2021-10-06 15:06:07.105382 +0000 UTC
ID: 7bc42fe85d32ca513769a74f97f7e1a7bad6c9407f0d934c2aa645ef9cf613c7
height: 0
timestamp: 2018-12-19 22:32:30.000000042 +0000 UTC
Get Account
任意のアカウントを、Flowネットワークの最新ブロックまたは指定したブロック高さから取得します。GetAccount
メソッドは、実際には最新ブロックからアカウントを取得するメソッドの別名です。
📖 Account addressは、アカウントを一意に識別するものです。0x
という接頭辞に注意してください。デフォルトの表現ではこの接頭辞を使うべきですが、ユーザーが入力する接頭辞がない表現については慎重に安全に処理してください。
アカウントには以下のデータが含まれます。
- Address:アカウントのアドレス。
- Balance:アカウントの残高。
- Contracts:アカウントにデプロイされたコントラクトのリスト。
- Keys:アカウントに関連付けられたキーのリスト。
Examples
最新のブロックからと、特定のブロック高さからアカウントを取得する方法を例示します。
func demo() {
ctx := context.Background()
flowClient := examples.NewFlowClient()
/* get account from the latest block */
address := flow.HexToAddress("f8d6e0586b0a20c7")
account, err := flowClient.GetAccount(ctx, address)
printAccount(account, err)
/* get account from the block by height 0 */
account, err = flowClient.GetAccountAtBlockHeight(ctx, address, 0)
printAccount(account, err)
}
func printAccount(account *flow.Account, err error) {
examples.Handle(err)
fmt.Printf("\nAddress: %s", account.Address.String())
fmt.Printf("\nBalance: %d", account.Balance)
fmt.Printf("\nContracts: %d", len(account.Contracts))
fmt.Printf("\nKeys: %d\n", len(account.Keys))
}
出力結果:
Address: f8d6e0586b0a20c7
Balance: 999999999999600000
Contracts: 2
Keys: 1
Address: f8d6e0586b0a20c7
Balance: 999999999999600000
Contracts: 2
Keys: 1
Get Transactions
トランザクションIDを指定して、ネットワークからトランザクションを取得します。 トランザクションを送信した後、ステータスを確認するためにトランザクションの結果を取得することもできます。
📖 トランザクションIDは、エンコードされたトランザクションペイロードのハッシュであり、ネットワークにトランザクションを送信する前に計算することができます。
⚠️ 提供するトランザクションIDは、直近のspork以降のものでなければなりません。
📖 トランザクションステータスは、ブロックチェーン上のトランザクションの状態を表します。ステータスは、sealedされるまでは変更される可能性があります。
Status | Final | Description |
---|---|---|
UNKNOWN | ❌ | The transaction has not yet been seen by the network |
PENDING | ❌ | The transaction has not yet been included in a block |
FINALIZED | ❌ | The transaction has been included in a block |
EXECUTED | ❌ | The transaction has been executed but the result has not yet been sealed |
SEALED | ✅ | The transaction has been executed and the result is sealed in a block |
EXPIRED | ✅ | The transaction reference block is outdated before being executed |
func demo(txID flow.Identifier) {
ctx := context.Background()
flowClient := examples.NewFlowClient()
tx, err := flowClient.GetTransaction(ctx, txID)
printTransaction(tx, err)
txr, err := flowClient.GetTransactionResult(ctx, txID)
printTransactionResult(txr, err)
}
func printTransaction(tx *flow.Transaction, err error) {
examples.Handle(err)
fmt.Printf("\nID: %s", tx.ID().String())
fmt.Printf("\nPayer: %s", tx.Payer.String())
fmt.Printf("\nProposer: %s", tx.ProposalKey.Address.String())
fmt.Printf("\nAuthorizers: %s", tx.Authorizers)
}
func printTransactionResult(txr *flow.TransactionResult, err error) {
examples.Handle(err)
fmt.Printf("\nStatus: %s", txr.Status.String())
fmt.Printf("\nError: %v", txr.Error)
}
Example output:
ID: fb1272c57cdad79acf2fcf37576d82bf760e3008de66aa32a900c8cd16174e1c
Payer: f8d6e0586b0a20c7
Proposer: f8d6e0586b0a20c7
Authorizers: []
Status: SEALED
Error: <nil>
Get Events
指定されたブロックの高さの範囲内にある、またはブロックIDのリストから、指定された型のイベントを取得します。
📖 Event typeは、標準フォーマットに従った文字列です。
A.{contract address}.{contract name}.{event name}
イベントに関する詳細をドキュメントで確認してください。このスタンダードに対する例外はcore eventsであり、このドキュメントで詳細をご確認ください。
📖 Block height rangeは、チェーン内の開始ブロックと終了ブロックの高さを表します。
Examples
ブロックの範囲内および複数のブロックIDでイベントを取得する方法を示します。
func demo(deployedContract *flow.Account, runScriptTx *flow.Transaction) {
ctx := context.Background()
flowClient := examples.NewFlowClient()
/* Query for account creation events by type */
result, err := flowClient.GetEventsForHeightRange(ctx, "flow.AccountCreated", 0, 30)
printEvents(result, err)
/* Query for our custom event by type */
customType := fmt.Sprintf("AC.%s.EventDemo.EventDemo.Add", deployedContract.Address.Hex())
result, err = flowClient.GetEventsForHeightRange(ctx, customType, 0, 10)
printEvents(result, err)
/* Get events directly from transaction result */
txResult, err := flowClient.GetTransactionResult(ctx, runScriptTx.ID())
examples.Handle(err)
printEvent(txResult.Events)
}
func printEvents(result []client.BlockEvents, err error) {
examples.Handle(err)
for _, block := range result {
printEvent(block.Events)
}
}
func printEvent(events []flow.Event) {
for _, event := range events {
fmt.Printf("\n\nType: %s", event.Type)
fmt.Printf("\nValues: %v", event.Value)
fmt.Printf("\nTransaction ID: %s", event.TransactionID)
}
}
Example output:
Type: flow.AccountCreated
Values: flow.AccountCreated(address: 0xfd43f9148d4b725d)
Transaction ID: ba9d53c8dcb0f9c2f854f93da8467a22d053eab0c540bde0b9ca2f7ad95eb78e
Type: flow.AccountCreated
Values: flow.AccountCreated(address: 0xeb179c27144f783c)
Transaction ID: 8ab7bfef3de1cf8b2ffb36559446100bf4129a9aa88d6bc59f72a467acf0c801
...
Type: A.eb179c27144f783c.EventDemo.Add
Values: A.eb179c27144f783c.EventDemo.Add(x: 2, y: 3, sum: 5)
Transaction ID: f3a2e33687ad23b0e02644ebbdcd74a7cd8ea7214065410a8007811d0bcbd353
Get Collections
同じブロックに含まれているトランザクションの塊を取得します。これをcollectionsと呼びます。コレクションは、ブロックあたりのトランザクション数を増やすことで、コンセンサス処理のスループットを向上させるために使用され、それらはブロックとトランザクションの間のリンクとして働きます。
📖 コレクションIDは、collectionペイロードのSHA3-256ハッシュです。
コレクションの取得例:
func demo(exampleCollectionID flow.Identifier) {
ctx := context.Background()
flowClient := examples.NewFlowClient()
/* get collection by ID */
collection, err := flowClient.GetCollection(ctx, exampleCollectionID)
printCollection(collection, err)
}
func printCollection(collection *flow.Collection, err error) {
examples.Handle(err)
fmt.Printf("\nID: %s", collection.ID().String())
fmt.Printf("\nTransactions: %s", collection.TransactionIDs)
}
Example output:
ID: 3d7b8037381f2497d83f2f9e09422c036aae2a59d01a7693fb6003b4d0bc3595
Transactions: [cf1184e3de4bd9a7232ca3d0b9dd2cfbf96c97888298b81a05c086451fa52ec1]
Execute Scripts
スクリプトを使用すると、Flowブロックチェーン上で変更が発生しない任意のCadenceコードを書いて、そのデータを受け取ることができます。Cadenceとスクリプトについてはこちらで詳しく説明されていますが、現時点で私達はスクリプトコードの実行とデータの取得のみに興味を持ちます。
Flowブロックチェーンの最新の状態に対してスクリプトを実行することもできますし、ブロックの高さまたはブロックIDで定義されたヒストリーの特定の時点でスクリプトを実行することもできます。
📖 ブロックIDはブロックのペイロード全体の SHA3-256 ハッシュですが、ブロックのレスポンスのプロパティからその値を取得できます。
func demo() {
ctx := context.Background()
flowClient := examples.NewFlowClient()
script := []byte(`
access(all) fun main(a: Int): Int {
return a + 10
}
`)
args := []cadence.Value{ cadence.NewInt(5) }
value, err := flowClient.ExecuteScriptAtLatestBlock(ctx, script, args)
examples.Handle(err)
fmt.Printf("\nValue: %s", value.String())
complexScript := []byte(`
access(all) struct User {
access(all) var balance: UFix64
access(all) var address: Address
access(all) var name: String
init(name: String, address: Address, balance: UFix64) {
self.name = name
self.address = address
self.balance = balance
}
}
access(all) fun main(name: String): User {
return User(
name: name,
address: 0x1,
balance: 10.0
)
}
`)
args = []cadence.Value{ cadence.NewString("Dete") }
value, err = flowClient.ExecuteScriptAtLatestBlock(ctx, complexScript, args)
printComplexScript(value, err)
}
type User struct {
balance uint64
address flow.Address
name string
}
func printComplexScript(value cadence.Value, err error) {
examples.Handle(err)
fmt.Printf("\nString value: %s", value.String())
s := value.(cadence.Struct)
u := User{
balance: s.Fields[0].ToGoValue().(uint64),
address: s.Fields[1].ToGoValue().([flow.AddressLength]byte),
name: s.Fields[2].ToGoValue().(string),
}
fmt.Printf("\nName: %s", u.name)
fmt.Printf("\nAddress: %s", u.address.String())
fmt.Printf("\nBalance: %d", u.balance)
}
Example output:
Value: 15
String value: s.34a17571e1505cf6770e6ef16ca387e345e9d54d71909f23a7ec0d671cd2faf5.User(balance: 10.00000000, address: 0x1, name: "Dete")
Name: Dete
Address: 0000000000000001
Balance: 1000000000
Mutate Flow Network
Flowは、他の多くのブロックチェーンと同じ様に、共有されたグローバルチェーンの状態を変化させるトランザクションを誰でも送信することができます。トランザクションは、状態の変化を記述したペイロードと、特定アカウントによる所有状態の変化を許可した1つ以上の承認、を含むオブジェクトです。
トランザクションデータは、SDKの助けを借りて、作成/署名されます。署名されたトランザクションのペイロードは、その後、アクセスノードAPIに送信されます。トランザクションが無効であった場合、または正しい数の承認署名が用意されていなかった場合、トランザクションは拒否されます。
トランザクションを実行するには、いくつかの手順が必要です。
Transactions
トランザクションとは署名済みのデータセットでしかなく、どのようにネットワークの状態を変化させるかと、その実行を定義したり制限したりするプロパティを指示したスクリプト・コードを含んでいます。これらのプロパティについては、以下で説明します。
📖 Scriptフィールドは、状態の変化のロジックを記述するトランザクションの一部です。Flowでは、トランザクションロジックはCadenceで書かれます。以下は、トランザクションのスクリプトの例です。
transaction(greeting: String) {
execute {
log(greeting.concat(", World!"))
}
}
📖 Arguments: トランザクションは、Cadenceスクリプトに渡されるゼロ個以上の引数を受け入れることができます。トランザクションの引数は、Cadenceスクリプト内で宣言された数と順序と一致する必要があります。上記のサンプルスクリプトでは、単一のString
引数を受け入れています。
📖 Proposal keyは、シーケンス番号として機能し、リプレイ攻撃やその他の潜在的な攻撃を防止するために、提供されなければなりません。
各アカウントのキーは別個のトランザクションのシーケンス番号を管理しています。トランザクションにそのシーケンス番号を付与するキーは、提案キー(proposal key)と呼ばれます。
提案キーは3つのフィールドを含みます。
- Account address
- Key index
- Sequence number
トランザクションは、宣言されたシーケンス番号がそのキーの現在のチェーン上のシーケンス番号と一致する場合のみ有効です。トランザクションが実行された後、シーケンス番号は1つインクリメントされます。
📖 Payerは、トランザクションの料金を支払うアカウントです。トランザクションは、必ず1つのPayerを指定する必要があります。Payerは、ネットワーク料金とガス料金の支払いの責任を負うのみであり、トランザクションはPayerアカウントに保存されているリソースやコードにアクセスする権限はありません。
📖 Authorizersとは、トランザクションがリソースを読み取り、変更することを承認するアカウントです。トランザクションは、アクセスする必要のあるアカウントの数に応じて、authorizerをゼロまたは複数指定できます。
トランザクションの承認者の数は、Cadenceスクリプトのprepareステートメントで宣言された&Accountパラメータの数と一致する必要があります。
複数の承認者を含むトランザクションの例:
transaction {
prepare(authorizer1: &Account, authorizer2: &Account) { }
}
Gas Limit
📖 Gas limitとは、トランザクションに必要な計算量の制限値であり、ガスリミットを超えると処理が中断されます。Cadenceでは、トランザクションごとの操作数を計測によって測定しています。詳細は、Cadenceのドキュメントを参照してください。
ガスリミットは、トランザクションスクリプトの複雑さによって異なります。専用のガス測定ツールが利用可能になるまでは、エミュレータを使用して複雑なトランザクションをテストし、安全なリミットを決定することをお勧めします。
Reference Block
📖 Reference block(参照ブロック) は、ネットワークによってトランザクションが有効とみなされる、expiration window(有効期限の期間)(ブロック単位)を指定します。 トランザクションがexpiry block(有効期限ブロック)を過ぎて送信された場合、そのトランザクションは拒否されます。 Flowは、トランザクション上のReference block(参照ブロック) フィールドを使用してトランザクションの有効期限を計算します。 トランザクションは、600
ブロックがReference block(参照ブロック)の上にコミットされると有効期限切れとなり、これは平均的なMainnetブロックレートでは約10分かかります。
Build Transactions
トランザクションの組み立てには、上述した必須プロパティの設定とトランザクション・オブジェクトの作成が含まれます。
ここで、ネットワーク上で実行される、学習用の良い例となるシンプルなトランザクション・スクリプトを定義します。
transaction(greeting: String) {
let guest: Address
prepare(authorizer: &Account) {
self.guest = authorizer.address
}
execute {
log(greeting.concat(",").concat(self.guest.toString()))
}
}
import (
"context"
"os"
"github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/client"
)
func main() {
greeting, err := os.ReadFile("Greeting2.cdc")
if err != nil {
panic("failed to load Cadence script")
}
proposerAddress := flow.HexToAddress("9a0766d93b6608b7")
proposerKeyIndex := 3
payerAddress := flow.HexToAddress("631e88ae7f1d7c20")
authorizerAddress := flow.HexToAddress("7aad92e5a0715d21")
var accessAPIHost string
/* Establish a connection with an access node */
flowClient := examples.NewFlowClient()
/* Get the latest sealed block to use as a reference block */
latestBlock, err := flowClient.GetLatestBlockHeader(context.Background(), true)
if err != nil {
panic("failed to fetch latest block")
}
/* Get the latest account info for this address */
proposerAccount, err := flowClient.GetAccountAtLatestBlock(context.Background(), proposerAddress)
if err != nil {
panic("failed to fetch proposer account")
}
/* Get the latest sequence number for this key */
sequenceNumber := proposerAccount.Keys[proposerKeyIndex].SequenceNumber
tx := flow.NewTransaction().
SetScript(greeting).
SetComputeLimit(100).
SetReferenceBlockID(latestBlock.ID).
SetProposalKey(proposerAddress, proposerKeyIndex, sequenceNumber).
SetPayer(payerAddress).
AddAuthorizer(authorizerAddress)
/* Add arguments last */
hello := cadence.NewString("Hello")
err = tx.AddArgument(hello)
if err != nil {
panic("invalid argument")
}
}
トランザクションの作成に成功したら、次に署名を行います。
Sign Transactions
Flowでは、トランザクションの作成や署名を行う際に、より柔軟性を高める新しい概念が導入されています。
トランザクションの作成に成功したら、次に署名を行います。Flowトランザクションにはエンベロープ署名とペイロード署名があり、それぞれについて署名に関するドキュメントで確認してください。
トランザクション構築の手短な例:
import (
"github.com/onflow/flow-go-sdk"
"github.com/onflow/flow-go-sdk/crypto"
)
var (
myAddress flow.Address
myAccountKey flow.AccountKey
myPrivateKey crypto.PrivateKey
)
tx := flow.NewTransaction().
SetScript([]byte("transaction { execute { log(\"Hello, World!\") } }")).
SetComputeLimit(100).
SetProposalKey(myAddress, myAccountKey.Index, myAccountKey.SequenceNumber).
SetPayer(myAddress)
トランザクション署名の処理は、crypto.Signer
インターフェイスを通じて行われます。最も簡単な(かつ最も安全性の低い)crypto.Signer
の実装は、crypto.InMemorySigner
です。
署名をより安全に生成するには、HSM(ハードウェア・セキュリティ・モジュール)などのハードウェアデバイスに保存されたキーを使用することなどが考えられます。crypto.Signer
のインターフェイスはさまざまな署名の実装をサポートする柔軟性を備えており、メモリ内(in-memory)での実装に限定されていません。
Simple signature example:
/* construct a signer from your private key and configured hash algorithm */
mySigner, err := crypto.NewInMemorySigner(myPrivateKey, myAccountKey.HashAlgo)
if err != nil {
panic("failed to create a signer")
}
err = tx.SignEnvelope(myAddress, myAccountKey.Index, mySigner)
if err != nil {
panic("failed to sign transaction")
}
トランザクションの署名においては、Flowは高い柔軟性をサポートしており、複数の承認者(マルチシグ・トランザクション)を定義したり、提案者とは異なる支払者アカウントを持つことができます。以下では、高度な署名シナリオについて説明します。
Single party, single signature
- Proposer, payer and authorizer are the same account (
0x01
). - Only the envelope must be signed.
- Proposal key must have full signing weight.
Account | Key ID | Weight |
---|---|---|
0x01 |
1 | 1000 |
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
key1 := account1.Keys[0]
/* create signer from securely-stored private key */
key1Signer := getSignerForKey1()
referenceBlock, _ := flow.GetLatestBlock(ctx, true)
tx := flow.NewTransaction().
SetScript([]byte(`
transaction {
prepare(signer: &Account) { log(signer.address) }
}
`)).
SetComputeLimit(100).
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
SetReferenceBlockID(referenceBlock.ID).
SetPayer(account1.Address).
AddAuthorizer(account1.Address)
/* account 1 signs the envelope with key 1 */
err := tx.SignEnvelope(account1.Address, key1.Index, key1Signer)
Single party, multiple signatures
- Proposer, payer and authorizer are the same account (
0x01
). - Only the envelope must be signed.
- Each key has weight 500, so two signatures are required.
Account | Key ID | Weight |
---|---|---|
0x01 |
1 | 500 |
0x01 |
2 | 500 |
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
key1 := account1.Keys[0]
key2 := account1.Keys[1]
/* create signers from securely-stored private keys */
key1Signer := getSignerForKey1()
key2Signer := getSignerForKey2()
referenceBlock, _ := flow.GetLatestBlock(ctx, true)
tx := flow.NewTransaction().
SetScript([]byte(`
transaction {
prepare(signer: &Account) { log(signer.address) }
}
`)).
SetComputeLimit(100).
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
SetReferenceBlockID(referenceBlock.ID).
SetPayer(account1.Address).
AddAuthorizer(account1.Address)
/* account 1 signs the envelope with key 1 */
err := tx.SignEnvelope(account1.Address, key1.Index, key1Signer)
/* account 1 signs the envelope with key 2 */
err = tx.SignEnvelope(account1.Address, key2.Index, key2Signer)
Multiple parties
- Proposer and authorizer are the same account (
0x01
). - Payer is a separate account (
0x02
). - Account
0x01
signs the payload. - Account
0x02
signs the envelope.- Account
0x02
must sign last since it is the payer.
- Account
Account | Key ID | Weight |
---|---|---|
0x01 |
1 | 1000 |
0x02 |
3 | 1000 |
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))
key1 := account1.Keys[0]
key3 := account2.Keys[0]
/* create signers from securely-stored private keys */
key1Signer := getSignerForKey1()
key3Signer := getSignerForKey3()
referenceBlock, _ := flow.GetLatestBlock(ctx, true)
tx := flow.NewTransaction().
SetScript([]byte(`
transaction {
prepare(signer: &Account) { log(signer.address) }
}
`)).
SetComputeLimit(100).
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
SetReferenceBlockID(referenceBlock.ID).
SetPayer(account2.Address).
AddAuthorizer(account1.Address)
/* account 1 signs the payload with key 1 */
err := tx.SignPayload(account1.Address, key1.Index, key1Signer)
/* account 2 signs the envelope with key 3
note: payer always signs last */
err = tx.SignEnvelope(account2.Address, key3.Index, key3Signer)
Multiple parties, two authorizers
- Proposer and authorizer are the same account (
0x01
). - Payer is a separate account (
0x02
). - Account
0x01
signs the payload. - Account
0x02
signs the envelope.- Account
0x02
must sign last since it is the payer.
- Account
- Account
0x02
is also an authorizer to show how to include two&Account
objects into an transaction
Account | Key ID | Weight |
---|---|---|
0x01 |
1 | 1000 |
0x02 |
3 | 1000 |
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))
key1 := account1.Keys[0]
key3 := account2.Keys[0]
/* create signers from securely-stored private keys */
key1Signer := getSignerForKey1()
key3Signer := getSignerForKey3()
referenceBlock, _ := flow.GetLatestBlock(ctx, true)
tx := flow.NewTransaction().
SetScript([]byte(`
transaction {
prepare(signer1: &Account, signer2: &Account) {
log(signer.address)
log(signer2.address)
}
}
`)).
SetComputeLimit(100).
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
SetReferenceBlockID(referenceBlock.ID).
SetPayer(account2.Address).
AddAuthorizer(account1.Address).
AddAuthorizer(account2.Address)
/* account 1 signs the payload with key 1 */
err := tx.SignPayload(account1.Address, key1.Index, key1Signer)
/* account 2 signs the envelope with key 3
note: payer always signs last */
err = tx.SignEnvelope(account2.Address, key3.Index, key3Signer)
Multiple parties, multiple signatures
- Proposer and authorizer are the same account (
0x01
). - Payer is a separate account (
0x02
). - Account
0x01
signs the payload. - Account
0x02
signs the envelope.- Account
0x02
must sign last since it is the payer.
- Account
- Both accounts must sign twice (once with each of their keys).
Account | Key ID | Weight |
---|---|---|
0x01 |
1 | 500 |
0x01 |
2 | 500 |
0x02 |
3 | 500 |
0x02 |
4 | 500 |
account1, _ := c.GetAccount(ctx, flow.HexToAddress("01"))
account2, _ := c.GetAccount(ctx, flow.HexToAddress("02"))
key1 := account1.Keys[0]
key2 := account1.Keys[1]
key3 := account2.Keys[0]
key4 := account2.Keys[1]
/* create signers from securely-stored private keys */
key1Signer := getSignerForKey1()
key2Signer := getSignerForKey1()
key3Signer := getSignerForKey3()
key4Signer := getSignerForKey4()
referenceBlock, _ := flow.GetLatestBlock(ctx, true)
tx := flow.NewTransaction().
SetScript([]byte(`
transaction {
prepare(signer: &Account) { log(signer.address) }
}
`)).
SetComputeLimit(100).
SetProposalKey(account1.Address, key1.Index, key1.SequenceNumber).
SetReferenceBlockID(referenceBlock.ID).
SetPayer(account2.Address).
AddAuthorizer(account1.Address)
/* account 1 signs the payload with key 1 */
err := tx.SignPayload(account1.Address, key1.Index, key1Signer)
/* account 1 signs the payload with key 2 */
err = tx.SignPayload(account1.Address, key2.Index, key2Signer)
/* account 2 signs the envelope with key 3
note: payer always signs last */
err = tx.SignEnvelope(account2.Address, key3.Index, key3Signer)
/* account 2 signs the envelope with key 4
note: payer always signs last */
err = tx.SignEnvelope(account2.Address, key4.Index, key4Signer)
Send Transactions
トランザクションが生成され署名されると、Flowブロックチェーンに送信され、そこで実行されます。送信が成功した場合、トランザクションの結果を取得できます。
func demo(tx *flow.Transaction) {
ctx := context.Background()
flowClient := examples.NewFlowClient()
err := flowClient.SendTransaction(ctx, *tx)
if err != nil {
fmt.Println("error sending transaction", err)
}
}
Create Accounts
Flowでは、アカウントの作成はトランザクション内で行われます。なぜなら、このネットワークでは公開鍵(public key)とアカウント(account)間で多対多の関係が許容されているためで、オフラインで公開鍵から新しいアカウントのアドレス(new account address)を導き出すことは不可能だからです。
Flow VMは、決定論的(deterministic)なアドレス生成アルゴリズムを使用してチェーン上のアカウントのアドレスを割り当てます。アドレス生成の詳細については、アカウントとkeysのドキュメントをご覧ください。
Public Key
Flowは、ECDSAキーペアを使用してユーザーアカウントへのアクセスをコントロールします。各キーペアは、SHA2-256またはSHA3-256ハッシュアルゴリズムと組み合わせても使用できます。
⚠️ 新しいアカウントを管理(control)するには、少なくとも1つの公開鍵を認証する必要があります。
Flowは、追加のメタデータなしに、ECDSA公開鍵を生の形式で表示します。各鍵は1バイトのスライスで、ビッグエンディアンバイト形式で、XとYの成分を連結したものを含みます。
Flowアカウントには、ゼロ(制御不可能)またはそれ以上の(アカウント鍵と呼ばれる)公開鍵を含めることができます。アカウントの詳細については、ドキュメントを参照してください。
アカウントキーには以下のデータが含まれます。
- 生の公開鍵(上記参照)
- 署名アルゴリズム
- ハッシュアルゴリズム
- 重み(0~1000の整数)
アカウントの作成はトランザクション内で行われるため、そのトランザクションをネットワークに送信するには誰かが費用を負担する必要しなければなりません。この人物をアカウント作成者と呼びます。まず最初にトランザクションの送信の項を必ずお読みください。
var (
creatorAddress flow.Address
creatorAccountKey *flow.AccountKey
creatorSigner crypto.Signer
)
var accessAPIHost string
/* Establish a connection with an access node */
flowClient := examples.NewFlowClient()
/* Use the templates package to create a new account creation transaction */
tx := templates.CreateAccount([]*flow.AccountKey{accountKey}, nil, creatorAddress)
/* Set the transaction payer and proposal key */
tx.SetPayer(creatorAddress)
tx.SetProposalKey(
creatorAddress,
creatorAccountKey.Index,
creatorAccountKey.SequenceNumber,
)
/* Get the latest sealed block to use as a reference block */
latestBlock, err := flowClient.GetLatestBlockHeader(context.Background(), true)
if err != nil {
panic("failed to fetch latest block")
}
tx.SetReferenceBlockID(latestBlock.ID)
/* Sign and submit the transaction */
err = tx.SignEnvelope(creatorAddress, creatorAccountKey.Index, creatorSigner)
if err != nil {
panic("failed to sign transaction envelope")
}
err = flowClient.SendTransaction(context.Background(), *tx)
if err != nil {
panic("failed to send transaction to network")
}
アカウント作成トランザクションが送信された後、トランザクションの結果を取得することで、新しいアカウントのアドレスを取得できます。
新しいアカウントのアドレスは、システムレベルのflow.AccountCreated
イベントで発行されます。
result, err := flowClient.GetTransactionResult(ctx, tx.ID())
if err != nil {
panic("failed to get transaction result")
}
var newAddress flow.Address
if result.Status != flow.TransactionStatusSealed {
panic("address not known until transaction is sealed")
}
for _, event := range result.Events {
if event.Type == flow.EventAccountCreated {
newAddress = flow.AccountCreatedEvent(event).Address()
break
}
}
Generate Keys
Flowは、ECDSA署名を使用してユーザーアカウントへのアクセスを制御します。各キーペアは、SHA2-256
またはSHA3-256
ハッシュアルゴリズムと組み合わせて使用できます。
P-256(secp256r1)曲線のECDSA秘密鍵を生成する方法は次のとおりです。
import "github.com/onflow/flow-go-sdk/crypto"
/* deterministic seed phrase
note: this is only an example, please use a secure random generator for the key seed */
seed := []byte("elephant ears space cowboy octopus rodeo potato cannon pineapple")
privateKey, err := crypto.GeneratePrivateKey(crypto.ECDSA_P256, seed)
/* the private key can then be encoded as bytes (i.e. for storage) */
encPrivateKey := privateKey.Encode()
/* the private key has an accompanying public key */
publicKey := privateKey.PublicKey()
上記の例では、ECDSA キーペアに P-256 (secp256r1) 楕円曲線を使用しています。Flowは、ビットコインやイーサリアムで使用されているsecp256k1曲線もサポートしています。サポートされているアルゴリズムについてはこちらをご覧ください。
Transfering Flow
これは、Flow Go SDK を使用して FLOW トークンの転送トランザクションを構築する方法の一例です。
Cadence Script
以下のCadenceスクリプトは、送信者から受信者にFLOWトークンを転送します。
注:このトランザクションはFlow Mainnetのみと互換性があります。
/* This transaction is a template for a transaction that
* could be used by anyone to send tokens to another account
* that has been set up to receive tokens.
*
* The withdraw amount and the account from getAccount
* would be the parameters to the transaction
*/
import "FungibleToken"
import "FlowToken"
transaction(amount: UFix64, to: Address) {
/* The Vault resource that holds the tokens that are being transferred */
let sentVault: @{FungibleToken.Vault}
prepare(signer: auth(BorrowValue) &Account) {
/* Get a reference to the signer's stored vault */
let vaultRef = signer.storage.borrow<auth(FungibleToken.Withdraw) &FlowToken.Vault>(from: /storage/flowTokenVault)
?? panic("Could not borrow reference to the owner's Vault!")
/* Withdraw tokens from the signer's stored vault */
self.sentVault <- vaultRef.withdraw(amount: amount)
}
execute {
/* Get a reference to the recipient's Receiver */
let receiverRef = getAccount(to)
.capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)
?? panic("Could not borrow receiver reference to the recipient's Vault")
/* Deposit the withdrawn tokens in the recipient's receiver */
receiverRef.deposit(from: <-self.sentVault)
}
}
Build the Transaction
import (
"github.com/onflow/cadence"
"github.com/onflow/flow-go-sdk"
)
/* Replace with script above */
const transferScript string = TOKEN_TRANSFER_CADENCE_SCRIPT
var (
senderAddress flow.Address
senderAccountKey flow.AccountKey
senderPrivateKey crypto.PrivateKey
)
func main() {
tx := flow.NewTransaction().
SetScript([]byte(transferScript)).
SetComputeLimit(100).
SetPayer(senderAddress).
SetAuthorizer(senderAddress).
SetProposalKey(senderAddress, senderAccountKey.Index, senderAccountKey.SequenceNumber)
amount, err := cadence.NewUFix64("123.4")
if err != nil {
panic(err)
}
recipient := cadence.NewAddress(flow.HexToAddress("0xabc..."))
err = tx.AddArgument(amount)
if err != nil {
panic(err)
}
err = tx.AddArgument(recipient)
if err != nil {
panic(err)
}
}
Last updated on Dec 6, 2024 by Alex Ni
翻訳元
Previous << WalletConnect 2.0 Manual Configuration
Flow BlockchainのCadence version1.0ドキュメント (Flow Go SDK)