Skip to content
/ rosetta Public

Rosetta implementation for the Flow blockchain

License

Notifications You must be signed in to change notification settings

onflow/rosetta

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

This repo implements the Rosetta API for the Flow blockchain.

Architecture

                   _____ _                 ____                _   _
                  |  ___| | _____      __ |  _ \ ___  ___  ___| |_| |_ __ _
                  | |_  | |/ _ \ \ /\ / / | |_) / _ \/ __|/ _ \ __| __/ _` |
                  |  _| | | (_) \ V  V /  |  _ < (_) \__ \  __/ |_| || (_| |
                  |_|   |_|\___/ \_/\_/   |_| \_\___/|___/\___|\__|\__\__,_|



     .───────────.
    (   Client    )
     `────┬▲─────'
          ││
          ││
          ││
          ││ Rosetta
          ││ API
          ││ Calls
          ││
          ││                                                                    /cmd/server
  ┏━━━━━━━╋╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
  ┃       ││                                                                              ┃
  ┃ ┌─────▼┴─────────────────────────────┐  ┌───────────────────────────────────────────┐ ┃
  ┃ │                                    │  │                                           │ ┃
  ┃ │         Rosetta API Server         │  │               State Indexer               │ ┃
  ┃ │                                    │  │                                           │ ┃
  ┃ └────┬────────────┬──────────────────┘  └─────────┬▲──────────────────────▲─────────┘ ┃
  ┃      │            │                               ││                      │           ┃
  ┃      │            │                        Processes Blocks        Validates Seals    ┃
  ┃      │            │                       and Stores Data in           Against        ┃
  ┃      │            │                         the Local Index         Consensus Data    ┃
  ┃      │            │                               ││                      │           ┃
  ┃      │            │                               ││                      │           ┃
  ┃      │            │                               ││           ┌──────────┴─────────┐ ┃
  ┃      │            │                               ││           │                    │ ┃
  ┃      │   ┌────────┴─────┐          .─────────.    ││           │ Consensus Follower │ ┃
  ┃      │   │              │         ╱           ╲   ││           │                    │ ┃
  ┃      │   │   Data API   │◀───────(  Index DB   )◀─┘│           └───────────────────▲┘ ┃
  ┃      │   │              │         `.         ,'    │                               │  ┃
  ┃      │   └────────────▲─┘           `───┬───'      │       .─────────────────.     │  ┃
  ┃      │                │                 │          │      ( Prefetch Workers  )    │  ┃
  ┃      │                │                 │          │       `─────────────────'     │  ┃
  ┃      │                │                 │          │                       │       │  ┃
  ┃ ┌────┴──────────────┐ │                 ▼          │.─────────.            │       │  ┃
  ┃ │                   │ │     .─────────────────.    ╱           ╲           │       │  ┃
  ┃ │ Construction API  │ │    ( Balance Validator )  (  API Cache  )  Warms Up Cache  │  ┃
  ┃ │                   │ │     `─────────────────'    `.         ,'           │       │  ┃
  ┃ └─┬─────────────────┘ │       ▲                      `────▲──'◀────────────┘       │  ┃
  ┃   │                   │       │                           │                        │  ┃
  ┗━━━╋━━━━━━━━━━━━━━━━━━━╋━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━╋━━┛
      │                   │       │                           │                        │
      │                   │       │                           │                        │
      │                   │       │                           │                        │
      │                   │       │ Fetches                   │                        │
      │                   │       │ On-Chain                  │                        │
      │                   │       │ Balance                   │                        │
      │    Fetches Recent │       │                           │                        │
      │             State │       │                           │                        │
      │                   │       │             Fetches Block │           Follows Live │
      │                   │       │           Data and Events │        Spork Consensus │
      │                   │       │             for Past/Live │         as an Unstaked │
      │ Submits           │       │                    Sporks │            Access Node │
      │ Transactions      │       │                           │                        │
      │                   │       │                           │                        │
      │                   │       │                           │                        │
  ┏━━━▼━━━━━━━━━━━━━━━━━━━┻━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━┻━━━━┓ ┏━━━━━━━━━━━━━━━━━┻━━┓
  ┃                                                                ┃ ┃                    ┃
  ┃                    Flow Access API Servers                     ┃ ┃ Flow Access Nodes  ┃
  ┃                                                                ┃ ┃                    ┃
  ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ┗━━━━━━━━━━━━━━━━━━━━┛

Overview

Flow Rosetta consists of two primary systems:

  • The State Indexer:

    • Uses the Access API servers to get the relevant block data and events for each of the configured sporks.

    • After establishing a data integrity chain from a trusted starting point, processes the events for sealed blocks, and stores the inferred state changes and block metadata in a local Index DB.

    • Processes past sporks by starting with the root block data from the protocol state snapshot of the live spork, and works backwards to the "genesis" block, i.e. the configured "root" block of the first spork in the JSON config.

    • Processes the live spork by starting with the root block of that spork and works forward to the most recently sealed block.

    • Spins up a Consensus Follower, which is used to validate the seals for blocks within a live spork.

    • Spins up concurrent prefetch workers who cache various Access API calls in advance of the main block processing loop.

  • The Rosetta API Server:

    • This runs an HTTP server that implements various endpoints of the Rosetta API.

    • For the Rosetta Data API, the data primarily comes from the local Index DB. For certain secondary bits of state, e.g. account sequence numbers, the information is fetched from the Access API servers for the latest spork.

    • For the Rosetta Construction API, the configured construction_access_nodes are used to help get relevant metadata during transaction construction, and for submitting the transactions.

    • Runs a background balance validation loop that compares indexed account balances against on-chain state whenever the indexer is synced close to tip.

Code Layout

The code is structured into the following top-level directories:

  • access — Provides a client for the Flow Access API that acts as a wrapper around the gRPC Access API client.

  • api — The core Rosetta API server implementation.

  • cache — Provides a storage-backed cache layer for automatically caching Access API calls.

  • cli-conf — Provides the default configuration for validating the implemented Rosetta Data API against rosetta-cli check:data.

  • cmd — Provides the main command line binaries. Primary amongst these is the server command.

  • config — Handles the JSON config file for Flow Rosetta.

  • crypto — Provides utility functions for converting between Rosetta and Flow public key formats for secp256k1 keys.

  • deque — Provides a double-ended, auto-scaling queue for storing finalized block IDs.

  • environ — Contains some minor build/test scripts.

  • fees — Defines the constants used to calculate fees during transaction construction.

  • indexdb — Provides a storage-backed index for persisting the processed block data.

  • log — Provides a thin wrapper for logging.

  • model — Defines the core protobuf-based data models used during transaction construction and for persisting complex data within the indexdb.

  • process — Provides support for signal handlers and for automatically running registered functions on process exit.

  • script — Contains the code for the Cadence transaction scripts and the code for the Proxy contract.

  • state — Implements the State Indexer.

  • timeout — Provides some utilities for auto-extending timeouts on successful I/O operations.

  • trace — Initializes the OpenTelemetry tracing/metrics infrastructure and provides some thin wrappers for using them.

  • version — Defines the constants for the Flow and Flow Rosetta versions.

Shortcomings

This implementation has various shortcomings:

  • We use the events generated from Flow transactions to infer changes in account balances. Since the events on Flow do not fully track the movement of resources on-chain, these events will not 100% match the on-chain balance changes.

  • In order to support balance reconciliation by rosetta-cli check:data, we only track balance changes for accounts created by the configured originators or any of its "descendants".

  • For our balance tracking of these accounts to remain valid, they must never be used in any "non-standard" transactions, e.g. moving FLOW to non-default Vault resources, splitting a withdrawal across multiple deposits, etc.

  • Since Flow effectively hard forks every spork, the Access API may serve block data from an old spork that is then not part of the next spork. If the Rosetta Node isn't shutdown in time, then it's possible for invalid data to be processed and indexed.

  • We can partially mitigate this by restarting the Rosetta Node with an appropriate block height to resync_from. However, if other services have consumed the previously indexed data from the Rosetta Node, these will also need to be reset.

  • Since the last few blocks in a spork don't have corresponding seals with execution results, the events we receive for those blocks cannot be verified through the Access API. It is therefore possible for us to process invalid data for those blocks.

  • If the Access API servers aren't properly maintained and configured for individual sporks, then it could result in data availability issues.

  • It needs to be actively reconfigured and restarted with every new spork. Some of this data can be derived from the sporks.json data that is maintained by Dapper Labs. If there are any delays or inaccuracies in those updates, this would result in downtime.

  • Rosetta assumes that a transaction hash uniquely identifies a transaction within a block. Unfortunately, in Flow, the same transaction hash can be included multiple times within the same block by being included in different collections.

    • In responses to the /block endpoint, we default to using just the first such transaction in a block, and will override it if any subsequent transaction within the same block has any operations.

Installation

# download and install dependencies
make deps

# build
make build

Running interactively in Docker

docker build -t flow .

docker run -it -v $(pwd):/app/src/rosetta-flow flow bash

# first time
make

# when making changes
make go-build

./server <path/to/some-config.json>

Note: Since flow-go can't be built like a normal Go module, you need to first compile a relic build by running: ./environ/build-relic.py. This is covered by the make build target.

And then use -tags relic when building or running cmd/server.

Environment Variables

The Flow Rosetta cmd/server supports configuration of certain aspects via the following environment variables:

  • JSON_LOGS

    • Set this to true to enable JSON-formatted log output. If this value is not set or set to a false value, it will default to "human-readable" log output.
  • OTEL_EXPORTER_OTLP_ENDPOINT

    • Use this to define a gRPC endpoint, e.g. http://localhost:4318, that can be used to export OpenTelemetry traces and metrics using the OpenTelemetry Protocol (OTLP).

    • If this value is not set, all trace and metric data will simply be dropped on the floor.

  • DISABLE_BALANCE_VALIDATION

    • Set this to true to turn off the background balance validation loop. If this value is not set or set to a false value, it will default to running the balance validation loop.

    • This should never be turned on in production.

JSON Config File

The Flow Rosetta cmd/server supports the following fields within the JSON config file:

  • balance_validation_interval: string

    • The interval between every account check within the balance validation loop. This accepts time.Duration values like "1s", "10ms", etc. If no value has been set, this will default to 1 second.
  • cache: bool

    • This can be used to enable the caching of idempotent Access API calls. This should generally be set to true in production systems as it makes a massive difference in a node's ability to be synced to tip.
  • construction_access_nodes: []AccessAPIServerConfig

    • This defines a list of Access API servers that can be used during transaction construction via the Rosetta Construction API.

    • This must have at least one value for the Construction API to work, and the given servers are used to submit transactions and to fetch data relevant for transaction construction, e.g. reference block hash, fee calculations, etc.

  • contracts

    • This defines the addresses for the key contracts on the specific Flow network, e.g.
{
    "flow_cold_storage_proxy": "0000000000000000",
    "flow_fees": "f919ee77447b7497",
    "flow_token": "1654653399040a61",
    "fungible_token": "f233dcee88fe0abe"
}
  • Note: The addresses do not have a 0x prefix, and if you do not have the FlowColdStorageProxy contract deployed on the particular network, you must use the zero address "0000000000000000" to disable support for proxy accounts.

  • data_dir: string

    • This defines the path to the data directory where the server stores data, e.g. indexdb, cache store, etc.
  • disable_consensus_follower: bool

    • This can be used to disable the use of Flow's Consensus Follower to validate the block hashes and seals for blocks within live sporks.
  • drop_cache: bool

    • This can be used to instruct Flow Rosetta to wipe the cache used for storing idempotent Access API calls on startup. This primarily exists as an escape hatch in case any of the existing protection against cache poisoning fails for some reason.
  • max_backoff_interval: string

    • When encountering certain API errors, we exponentially back off from two seconds up to the given max backoff interval. This accepts time.Duration values like "1s", "30s", etc. If no value has been set, this will default to 10 seconds.
  • mode: "online" | "offline"

    • This specifies whether the Flow Rosetta should work in online or offline mode. During offline mode, only certain Rosetta API endpoints are functional.
  • network: "mainnet" | "testnet" | "previewnet" | "localnet"

    • This defines the specific Flow chain that is being used.
  • originators: []string

    • This defines the set of root originator addresses. The addresses must be in the standard hex-encoded format that Flow uses, but without the 0x prefix.

    • All root originator accounts must have been created after the configured root_block of the first spork in the sporks definition. Otherwise, Flow Rosetta will fail to index balance changes properly.

  • port: uint16

    • This defines the port for the Flow Rosetta API server to listen on.
  • purge_proxy_accounts: bool

    • This can be used to delete all proxy accounts from the indexed data on startup. This is useful if you enabled support for proxy accounts at one point, but would later like to remove it.

    • Note: This still leaves behind the IndexedBlock data that might contain operations relating to proxy accounts, which are used to generate responses to the /block endpoint.

  • resync_from: uint64

    • This can be used to tell Flow Rosetta to resync from a particular block height on startup. This can be useful in dropping indexed data from any blocks in the previous spork that are no longer part of Flow after a spork upgrade.
  • skip_blocks: []string

    • This can be used to instruct Flow Rosetta to skip certain data integrity checks when processing any of the given block hashes.

    • Note: The block hashes must be in the standard hex-encoded format that Flow uses, and not use a 0x prefix.

  • spork_seal_tolerance: uint64

    • This defines the number of blocks at the end of a spork for which we expect there to be no explicit seals. This is unfortunately required as Flow self-seals the root block of a new spork, and doesn't provide seals for the trailing blocks of the previous spork.

    • This is a security risk as a compromised Access API server could send us malicious event data that we will not be able to identify until after the fact.

  • sporks: map[string]SporkConfig

    • This defines a mapping of spork IDs to the spork config for the particular set of sporks that we want to index.

    • The ordering of the sporks is automatically inferred from the root_block of each spork, so every spork after the "first" spork we define must have a corresponding definition, and the "last" spork is inferred to be the current live spork.

  • workers: uint

    • This can be used to define the number of prefetch workers to use for warming up the Access API cache. If unspecified, this will default to using twice the number of CPUs available on the system.

The AccessAPIServerConfig supports the following fields:

  • address: string

    • This defines the host:port for the Access API gRPC server.
  • public_key: string

    • This should be set to a hex-encoded public key if the Access API gRPC server is being served over a TLS connection where the server is using a libP2P cert with the given public key.
  • tls: bool

    • This should be set to true if the Access API gRPC server is being served over a TLS connection where the server is using a DNS hostname and the cert has been signed using one of the root Certificate Authorities on the host system.

The SporkConfig supports the following fields:

  • access_nodes: []AccessAPIServerConfig

    • This defines the list of Access API servers that will be used to fetch block data for blocks within a spork.

    • Flow Rosetta will randomly choose one of the given Access API servers for each distinct set of requests.

  • consensus: ConsensusConfig

    • This defines the config for the Consensus Follower. This must be defined within the config for the current spork unless disable_consensus_follower is specified in the top-level configuration.
  • root_block: uint64

    • This defines the block height of the "root" block within a given spork.

    • For the very "first" spork in sporks, this can point to any block within that spork. This is so as to enable spinning up a new Flow Rosetta node without having to sync from the start of a spork.

    • The root_block of this "first" spork will be used as the "genesis" block within that Flow Rosetta instance.

    • For all subsequent sporks, the root_block must refer to the block height of the actual root block within that spork.

  • version: int

    • This defines the Flow Rosetta-specific version number that is used to distinguish between the different data types and hashing mechanisms that are used for block data integrity within sporks.

    • As Flow doesn't define a versioning system for breaking changes in its data integrity mechanisms, we define our own. This enables us to support multiple sporks within the same code base and Flow Rosetta process.

The ConsensusConfig supports the following fields:

  • disable_signature_check: bool

    • This can be used to disable the check of the PGP signature of the root protocol state snapshot data.

    • This should never be set in production.

  • root_protocol_state_url: string

    • This defines the HTTPS URL for the file containing the JSON root protocol state snapshot data for the live spork.
  • root_protocol_state_signature_url: string

    • This defines the HTTPS URL for the file contained the armored PGP signature of the root protocol state snapshot data.

    • The signature needs to have been made by the key defined as the signing_key value.

  • seed_nodes: []SeedNodeConfig

    • This defines the Access Nodes that should be used to bootstrap the Consensus Follower.
  • signing_key: string

    • This defines the raw contents of the armored PGP key used to signed the root protocol state snapshot data.

The SeedNodeConfig supports the following fields:

  • host: string

    • This defines the host for the Access Node.
  • port: uint16

    • This defines the port for the Consensus Follower to use for connecting to the Access Node, usually 3570.
  • public_key: string

    • This defines the hex-encoded ECDSA P-256 public key for the Access Node.

FlowColdStorageProxy Contract

Transactions in Flow currently expire after 600 blocks — roughly about 10 minutes. This can be problematic for offline signing of transactions, e.g. for cold storage wallets, which can often take longer than the expiry window.

In order to support such cold storage transactions, we have created a FlowColdStorageProxy contract, script/FlowColdStorageProxy.cdc that allows for the secure transfer of funds from cold wallets without being limited by Flow's expiry window.

It works by using a "meta transaction":

  1. Cold keys sign an "inner transaction" payload which authorizes the transfer of funds. This payload specifies the receiver address, amount, and inner transaction nonce.

  2. Cold keys can take however long they need to sign this inner transaction.

  3. The payload and signature for the inner transaction are then passed in as parameters of a FlowColdStorageProxy.Vault#transfer call within an "outer transaction".

  4. The outer transaction can then be constructed and submitted by any account on the network.

The FlowColdStorageProxy contract implements a Vault resource that wraps an underlying FlowToken.Vault. We refer to accounts that have been set up with such a resource as a proxy account.

The following template provides the transaction script to create such a proxy account:

import FlowColdStorageProxy from 0x{{.Contracts.FlowColdStorageProxy}}

transaction(publicKey: String) {
    prepare(payer: AuthAccount) {
        // Create a new account with a FlowColdStorageProxy Vault.
        FlowColdStorageProxy.setup(payer: payer, publicKey: publicKey.decodeHex())
    }
}

Behind the scenes, FlowColdStorageProxy.setup will:

  1. Create a new Flow account. These accounts are created without any keys so as to make their storage immutable except through exposed capabilities.

  2. Initialize a FlowColdStorageProxy.Vault resource with the given public key.

  3. Override the default /public/flowTokenReceiver with the FlowColdStorageProxy Vault so that all deposits will be made directly into it.

  4. Expose the default FlowToken Vault as /public/defaultFlowTokenReceiver so that if the minimum account balance ever increases, this can be used to top up the proxy account's default FLOW balance.

  5. Expose the FlowColdStorageProxy Vault as /public/flowColdStorageProxyVault.

While calls to the deposit method are proxied by the FlowColdStorageProxy Vault to the underlying FlowToken Vault, there is no corresponding withdraw method.

Instead, funds must be transferred via the transfer method:

fun transfer(receiver: Address, amount: UFix64, nonce: Int64, sig: [UInt8])

This transaction script gives an example of how it can be used:

import FlowColdStorageProxy from 0x{{.Contracts.FlowColdStorageProxy}}

transaction(sender: Address, receiver: Address, amount: UFix64, nonce: Int64, sig: String) {
    execute {
        // Get a reference to the sender's FlowColdStorageProxy Vault.
        let acct = getAccount(sender)
        let vault = acct.capabilities.borrow<&{FlowColdStorageProxy.Vault}>(FlowColdStorageProxy.VaultCapabilityPublicPath)!


        // Transfer tokens to the receiver.
        vault.transfer(receiver: receiver, amount: amount, nonce: nonce, sig: sig.decodeHex())
    }
}

The intent of the code is that:

  • Funds held within a FlowColdStorageProxy Vault resource should only be transferrable if the FlowColdStorageProxy.Vault#transfer "inner transaction" has been signed by the publicKey specified during the FlowColdStorageProxy.setup call.

  • Funds are protected against any replay attacks.

  • Funds must not be transferrable in any other way.

  • It should not be possible to move or destroy a FlowColdStorageProxy Vault resource, and thus the underlying funds.

  • If the required minimum account balance requirement is ever increased, it should be possible to deposit funds to the default FlowToken Vault by using /public/defaultFlowTokenReceiver and ensure continued operations of an account owning a particular FlowColdStorageProxy Vault instance.

Enabling Support for Proxy Accounts

To configure Flow Rosetta for proxy accounts, you need to first update script/FlowColdStorageProxy.cdc with the right network addresses for the import statements, e.g.

import FlowToken from 0x0ae53cb6e3f42a79
import FungibleToken from 0xee82856bf20e2aa6

Then, you need to deploy the contract, e.g. using Flow CLI:

$ flow accounts add-contract \
  --network <network> \
  --signer <contract-account>
  FlowColdStorageProxy ./FlowColdStorageProxy.cdc
Contract 'FlowColdStorageProxy' deployed to the account '01cf0e2f2f715450'.

And, finally, you need to update the contracts.flow_cold_storage_proxy value of the Flow Rosetta JSON config file with the address of the account to which the contract has been deployed, e.g.

"contracts": {
    "flow_cold_storage_proxy": "01cf0e2f2f715450",
    ...
}

If contracts.flow_cold_storage_proxy is set to 0000000000000000, then proxy account support will be disabled.

If you accidentally, enabled support for proxy accounts and want to later remove it, you need to first re-run Flow Rosetta with purge_proxy_accounts set to true in the JSON config file.

Supported Rosetta Operation Types

We support the following operation types as part of the Rosetta Construction API flow:

  • create_account

  • create_proxy_account

  • deploy_contract

  • proxy_transfer

  • proxy_transfer_inner

  • transfer

  • update_contract

All operations except for proxy_transfer_inner must specify the payer address in the top-level request metadata field, i.e. outside of the operations.

The nonce/sequence number for a transaction can be specified as a string value in the top-level metadata.sequence_number field:

  • For proxy_transfer_inner operations, this refers to the nonce of the FlowColdStorageProxy contract. For all other operations, it refers to the sequence number of the payer's proposal key within the Flow transaction.

  • If this field isn't specified, then the latest on-chain value is looked up during the /construction/metadata call.

Create Account(s)

Use create_account to create a new account on Flow:

[
  {
    "type": "create_account",
    "operation_identifier": {
      "index": 0
    },
    "metadata": {
      "public_key": "0395028a149fe0c961ff9d3623fc83e2ecd1f35071c6725f7a4f495c9e13f23d23"
    }
  }
]

The provided metadata.public_key needs to be a secp256k1 key in the SEC compressed format used by Rosetta. Use the provided cmd/formatkey tool to convert keys between Flow and Rosetta formats, e.g.

$ go run cmd/formatkey/formatkey.go from:flow 95028a149fe0c961ff9d3623fc83e2ecd1f35071c6725f7a4f495c9e13f23d238f31c84c026a9ee2204176b77ccf77b999bc1a924c9ec2bd53bfd2db6cbe60c3
0395028a149fe0c961ff9d3623fc83e2ecd1f35071c6725f7a4f495c9e13f23d23

$ go run cmd/formatkey/formatkey.go from:rosetta 0395028a149fe0c961ff9d3623fc83e2ecd1f35071c6725f7a4f495c9e13f23d23
95028a149fe0c961ff9d3623fc83e2ecd1f35071c6725f7a4f495c9e13f23d238f31c84c026a9ee2204176b77ccf77b999bc1a924c9ec2bd53bfd2db6cbe60c3

Multiple create_account operations can be constructed within the same transaction. The exact upper bound will depend on changes to Flow, but for now, up to 50 accounts can be comfortably created within the same transaction.

Create Proxy Account

Use create_proxy_account to create a new proxy account, i.e. a new account set up with a FlowColdStorageProxy Vault:

[
  {
    "type": "create_proxy_account",
    "operation_identifier": {
      "index": 0
    },
    "metadata": {
      "public_key": "0395028a149fe0c961ff9d3623fc83e2ecd1f35071c6725f7a4f495c9e13f23d23"
    }
  }
]

Only one proxy account can be created within a single transaction, and the metadata.public_key refers to the public key for the FlowColdStorageProxy Vault itself, and not the account.

Deploy Contract

Use deploy_contract to deploy a contract to a payer account:

[
  {
    "type": "deploy_contract",
    "operation_identifier": {
      "index": 0
    },
    "metadata": {
      "contract_code": "70756220636f6e74726163742048656c6c6f576f726c64207b0a202020207075622066756e2068656c6c6f28293a20537472696e67207b0a202020202020202072657475726e202248656c6c6f20776f726c6421220a202020207d0a7d0a",
      "contract_name": "HelloWorld",
      "key_message": "some message to sign",
      "key_metadata": "key_id: 1234",
      "key_signature": "3044022034d188e2aa3c5e6c65b323c400b536b268b2326b49f1d69467753ffc0a5ad538022051e8e9552deddea020b174dfc3d4c91d1c435867010f73c38d1c760817069e47",
      "new_key": "025a7a5a204e7207c02121f5444a47ec29cc34495926b7136ea1e2169dd5445dcc",
      "prev_key_index": 0
    }
  }
]

The contract code needs to be hex-encoded. You can do this on the command-line by piping the source code into something like:

$ cat file.cdc | hexdump -v -e '/1 "%02x"'

The key_signature needs to be a valid ECDSA signature with the given new_key over the SHA2-256 hash of the specified key_message. The key_metadata field can be any arbitrary string. It's only passed in as a parameter to leave an on-chain audit trail, and is not stored anywhere else.

The prev_key_index must be the key index of the most recently set key. This key will be revoked as part of the deploy transaction.

Note: Once a deploy_contract transaction has been submitted and lands on chain, there will be no corresponding operation within the transaction's /block response except for the deducted fee from the payer.

Transfer

Use transfer to send funds from a normal account to any other account, e.g. to send 0.2 FLOW from 0xddac9f33298b6e79 to 0xb73aff1e77717543:

[
  {
    "type": "transfer",
    "operation_identifier": {
      "index": 0
    },
    "account": {
      "address": "0xddac9f33298b6e79"
    },
    "amount": {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "-20000001"
    }
  },
  {
    "type": "transfer",
    "operation_identifier": {
      "index": 1
    },
    "related_operations": [
      {
        "index": 0
      }
    ],
    "account": {
      "address": "0xb73aff1e77717543"
    },
    "amount": {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "20000001"
    }
  }
]

Note: We currently expect the "sender" of a transfer to be the exact same account as the payer.

Proxy Transfer

Use proxy_transfer to send funds from a proxy account to any other account, e.g. to send 0.2 FLOW from 0xddac9f33298b6e79 to 0xb73aff1e77717543:

[
  {
    "type": "proxy_transfer",
    "operation_identifier": {
      "index": 0
    },
    "account": {
      "address": "0xddac9f33298b6e79"
    },
    "amount": {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "-20000001"
    }
  },
  {
    "type": "proxy_transfer",
    "operation_identifier": {
      "index": 1
    },
    "related_operations": [
      {
        "index": 0
      }
    ],
    "account": {
      "address": "0xb73aff1e77717543"
    },
    "amount": {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "20000001"
    }
  }
]

Update Contract

Use update_contract to update an existing contract on a payer account:

[
  {
    "type": "update_contract",
    "operation_identifier": {
      "index": 0
    },
    "metadata": {
      "contract_code": "70756220636f6e74726163742048656c6c6f576f726c64207b0a202020207075622066756e2068656c6c6f28293a20537472696e67207b0a202020202020202072657475726e202248656c6c6f20746865726521220a202020207d0a7d0a",
      "contract_name": "HelloWorld",
      "key_message": "some message to sign",
      "key_metadata": "key_id: 1234",
      "key_signature": "3044022034d188e2aa3c5e6c65b323c400b536b268b2326b49f1d69467753ffc0a5ad538022051e8e9552deddea020b174dfc3d4c91d1c435867010f73c38d1c760817069e47",
      "new_key": "025a7a5a204e7207c02121f5444a47ec29cc34495926b7136ea1e2169dd5445dcc",
      "prev_key_index": 1
    }
  }
]

Note: Once an update_contract transaction has been submitted and lands on chain, there will be no corresponding operation within the transaction's /block response except for the deducted fee from the payer.

Example Originator

For the below examples, we will first generate a key for our root originator:

$ go run cmd/genkey/genkey.go
Public Key (Flow Format): 7509387372ee7ded90281fe21dc5b250b609bacc516da473756d7f190ec2bce73aa5ad760610da452142f61fd6714f871fbd261840a4907ee3253d33d6aa9186
Public Key (Rosetta Format): 027509387372ee7ded90281fe21dc5b250b609bacc516da473756d7f190ec2bce7
Private Key: 914bf493aacd199c1f4f2f19d3f80ef69066307d39d7f4ccfe298ab76fbee0b5

This key will be registered with a new account created at address 0xd99b1eba9b561cfa.

For the purposes of this example, we created this originator via the Flow Testnet Faucet, but this will probably be done by some kind of key management systems in production systems.

Creating Accounts via the Rosetta Construction API

Flow requires accounts to be created with specific public keys on-chain before they can be used. Our implementation provides a create_account operation type to support this via the Rosetta Construction API.

The following provides a high-level walkthrough of the request/response flows for creating accounts via the API.

First, generate a key:

$ go run cmd/genkey/genkey.go
Public Key (Flow Format): bd7bfdb6297207619eb9907a39f512ac2861f45e5ba52cfaa777b6e7fa9aab6c45522faa013c1491627bb534fdbcae3f0545982f6bf77b665bc162ebf2592484
Public Key (Rosetta Format): 02bd7bfdb6297207619eb9907a39f512ac2861f45e5ba52cfaa777b6e7fa9aab6c
Private Key: dc915efc3992d2c59b69de4948d565ac953818d9ccee78b65df590764786ada0

Use the Rosetta format of the public key as the metadata.public_key within the create_account operation, and our originator as the top-level metadata.payer in the call to /construction/preprocess:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "operations": [
    {
      "type": "create_account",
      "operation_identifier": {
        "index": 0
      },
      "metadata": {
        "public_key": "02bd7bfdb6297207619eb9907a39f512ac2861f45e5ba52cfaa777b6e7fa9aab6c"
      }
    }
  ],
  "metadata": {
    "payer": "0xd99b1eba9b561cfa"
  }
}

This will respond with something like:

{
  "options": {
    "protobuf": "1a08d99b1eba9b561cfa28ffffffffffffffffff0148465080c2d72f5801"
  }
}

Pass the options through to the /construction/metadata call:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "options": {
    "protobuf": "1a08d99b1eba9b561cfa28ffffffffffffffffff0148465080c2d72f5801"
  }
}

This will respond with something like:

{
  "metadata": {
    "height": "69453800",
    "protobuf": "0a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb91a08d99b1eba9b561cfa28883f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040e1900648465080c2d72f5801"
  },
  "suggested_fee": [
    {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "100449"
    }
  ]
}

Then pass that through to the /construction/payloads call using the same public_key as in the /construction/preprocess call:

{
  "payloads": [
    {
      "account_identifier": {
        "address": "0xd99b1eba9b561cfa"
      },
      "address": "0xd99b1eba9b561cfa",
      "hex_bytes": "e089ac76de893e4aca1d61b2c340f01c6b4de4c724a968c43e4eeda482c9b00f",
      "signature_type": "ecdsa"
    }
  ],
  "unsigned_transaction": "010ab7047472616e73616374696f6e287075626c69634b6579733a205b537472696e675d29207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a2020202020202020666f72206b657920696e207075626c69634b657973207b0a2020202020202020202020202f2f2043726561746520616e206163636f756e7420616e642073657420746865206163636f756e74207075626c6963206b65792e0a2020202020202020202020206c65742061636374203d20417574684163636f756e742870617965723a207061796572290a2020202020202020202020206c6574207075626c69634b6579203d205075626c69634b6579280a202020202020202020202020202020207075626c69634b65793a206b65792e6465636f646548657828292c0a202020202020202020202020202020207369676e6174757265416c676f726974686d3a205369676e6174757265416c676f726974686d2e45434453415f736563703235366b310a202020202020202020202020290a202020202020202020202020616363742e6b6579732e616464280a202020202020202020202020202020207075626c69634b65793a207075626c69634b65792c0a2020202020202020202020202020202068617368416c676f726974686d3a2048617368416c676f726974686d2e534841335f3235362c0a202020202020202020202020202020207765696768743a20313030302e300a202020202020202020202020290a20202020202020207d0a202020207d0a7d0a12b8017b2274797065223a224172726179222c2276616c7565223a5b7b2274797065223a22537472696e67222c2276616c7565223a226264376266646236323937323037363139656239393037613339663531326163323836316634356535626135326366616137373762366537666139616162366334353532326661613031336331343931363237626235333466646263616533663035343539383266366266373762363635626331363265626632353932343834227d5d7d0a1a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb9208f4e2a0d0a08d99b1eba9b561cfa18883f3208d99b1eba9b561cfa3a08d99b1eba9b561cfa:0a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb91a08d99b1eba9b561cfa28883f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040e1900648465080c2d72f5801"
}

This will respond with something like:

{
  "payloads": [
    {
      "account_identifier": {
        "address": "0xd99b1eba9b561cfa"
      },
      "address": "0xd99b1eba9b561cfa",
      "hex_bytes": "e089ac76de893e4aca1d61b2c340f01c6b4de4c724a968c43e4eeda482c9b00f",
      "signature_type": "ecdsa"
    }
  ],
  "unsigned_transaction": "010ab7047472616e73616374696f6e287075626c69634b6579733a205b537472696e675d29207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a2020202020202020666f72206b657920696e207075626c69634b657973207b0a2020202020202020202020202f2f2043726561746520616e206163636f756e7420616e642073657420746865206163636f756e74207075626c6963206b65792e0a2020202020202020202020206c65742061636374203d20417574684163636f756e742870617965723a207061796572290a2020202020202020202020206c6574207075626c69634b6579203d205075626c69634b6579280a202020202020202020202020202020207075626c69634b65793a206b65792e6465636f646548657828292c0a202020202020202020202020202020207369676e6174757265416c676f726974686d3a205369676e6174757265416c676f726974686d2e45434453415f736563703235366b310a202020202020202020202020290a202020202020202020202020616363742e6b6579732e616464280a202020202020202020202020202020207075626c69634b65793a207075626c69634b65792c0a2020202020202020202020202020202068617368416c676f726974686d3a2048617368416c676f726974686d2e534841335f3235362c0a202020202020202020202020202020207765696768743a20313030302e300a202020202020202020202020290a20202020202020207d0a202020207d0a7d0a12b8017b2274797065223a224172726179222c2276616c7565223a5b7b2274797065223a22537472696e67222c2276616c7565223a226264376266646236323937323037363139656239393037613339663531326163323836316634356535626135326366616137373762366537666139616162366334353532326661613031336331343931363237626235333466646263616533663035343539383266366266373762363635626331363265626632353932343834227d5d7d0a1a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb9208f4e2a0d0a08d99b1eba9b561cfa18883f3208d99b1eba9b561cfa3a08d99b1eba9b561cfa:0a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb91a08d99b1eba9b561cfa28883f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040e1900648465080c2d72f5801"
}

Take the payloads[0].hex_bytes and sign it using the payer's private key, i.e. our root originator. You can use the helper cmd/sign tool for this:

$ go run cmd/sign/sign.go 914bf493aacd199c1f4f2f19d3f80ef69066307d39d7f4ccfe298ab76fbee0b5 e089ac76de893e4aca1d61b2c340f01c6b4de4c724a968c43e4eeda482c9b00f
bc688b21f76f2d3704d5c174464bd428be68704413c9f6401c0bf51e9a80e28a6c117dc9b10b9c7818426bcfb5d62919a3b242c8d4c47d10824fc661d64f886a

It takes two parameters, first is the hex-encoded private key, and the second is the payload.hex_bytes value.

Now make a call to /construction/combine:

  • Copy the unsigned_transaction from the /construction/payloads response

  • Copy payloads[0] from the /construction/payloads response, and set it as signatures[0].signing_payload

  • Set the signer address's public key in signatures[0].public_key.hex_bytes.

  • Copy the hex-encoded signature output from cmd/sign output and set it as signatures[0].hex_bytes

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "unsigned_transaction": "010ab7047472616e73616374696f6e287075626c69634b6579733a205b537472696e675d29207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a2020202020202020666f72206b657920696e207075626c69634b657973207b0a2020202020202020202020202f2f2043726561746520616e206163636f756e7420616e642073657420746865206163636f756e74207075626c6963206b65792e0a2020202020202020202020206c65742061636374203d20417574684163636f756e742870617965723a207061796572290a2020202020202020202020206c6574207075626c69634b6579203d205075626c69634b6579280a202020202020202020202020202020207075626c69634b65793a206b65792e6465636f646548657828292c0a202020202020202020202020202020207369676e6174757265416c676f726974686d3a205369676e6174757265416c676f726974686d2e45434453415f736563703235366b310a202020202020202020202020290a202020202020202020202020616363742e6b6579732e616464280a202020202020202020202020202020207075626c69634b65793a207075626c69634b65792c0a2020202020202020202020202020202068617368416c676f726974686d3a2048617368416c676f726974686d2e534841335f3235362c0a202020202020202020202020202020207765696768743a20313030302e300a202020202020202020202020290a20202020202020207d0a202020207d0a7d0a12b8017b2274797065223a224172726179222c2276616c7565223a5b7b2274797065223a22537472696e67222c2276616c7565223a226264376266646236323937323037363139656239393037613339663531326163323836316634356535626135326366616137373762366537666139616162366334353532326661613031336331343931363237626235333466646263616533663035343539383266366266373762363635626331363265626632353932343834227d5d7d0a1a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb9208f4e2a0d0a08d99b1eba9b561cfa18883f3208d99b1eba9b561cfa3a08d99b1eba9b561cfa:0a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb91a08d99b1eba9b561cfa28883f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040e1900648465080c2d72f5801",
  "signatures": [
    {
      "signing_payload": {
        "account_identifier": {
          "address": "0xd99b1eba9b561cfa"
        },
        "address": "0xd99b1eba9b561cfa",
        "hex_bytes": "e089ac76de893e4aca1d61b2c340f01c6b4de4c724a968c43e4eeda482c9b00f",
        "signature_type": "ecdsa"
      },
      "public_key": {
        "hex_bytes": "027509387372ee7ded90281fe21dc5b250b609bacc516da473756d7f190ec2bce7",
        "curve_type": "secp256k1"
      },
      "signature_type": "ecdsa",
      "hex_bytes": "bc688b21f76f2d3704d5c174464bd428be68704413c9f6401c0bf51e9a80e28a6c117dc9b10b9c7818426bcfb5d62919a3b242c8d4c47d10824fc661d64f886a"
    }
  ]
}

This will return a response like:

{
  "signed_transaction": "010ab7047472616e73616374696f6e287075626c69634b6579733a205b537472696e675d29207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a2020202020202020666f72206b657920696e207075626c69634b657973207b0a2020202020202020202020202f2f2043726561746520616e206163636f756e7420616e642073657420746865206163636f756e74207075626c6963206b65792e0a2020202020202020202020206c65742061636374203d20417574684163636f756e742870617965723a207061796572290a2020202020202020202020206c6574207075626c69634b6579203d205075626c69634b6579280a202020202020202020202020202020207075626c69634b65793a206b65792e6465636f646548657828292c0a202020202020202020202020202020207369676e6174757265416c676f726974686d3a205369676e6174757265416c676f726974686d2e45434453415f736563703235366b310a202020202020202020202020290a202020202020202020202020616363742e6b6579732e616464280a202020202020202020202020202020207075626c69634b65793a207075626c69634b65792c0a2020202020202020202020202020202068617368416c676f726974686d3a2048617368416c676f726974686d2e534841335f3235362c0a202020202020202020202020202020207765696768743a20313030302e300a202020202020202020202020290a20202020202020207d0a202020207d0a7d0a12b8017b2274797065223a224172726179222c2276616c7565223a5b7b2274797065223a22537472696e67222c2276616c7565223a226264376266646236323937323037363139656239393037613339663531326163323836316634356535626135326366616137373762366537666139616162366334353532326661613031336331343931363237626235333466646263616533663035343539383266366266373762363635626331363265626632353932343834227d5d7d0a1a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb9208f4e2a0d0a08d99b1eba9b561cfa18883f3208d99b1eba9b561cfa3a08d99b1eba9b561cfa4a4c0a08d99b1eba9b561cfa1a40bc688b21f76f2d3704d5c174464bd428be68704413c9f6401c0bf51e9a80e28a6c117dc9b10b9c7818426bcfb5d62919a3b242c8d4c47d10824fc661d64f886a:0a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb91a08d99b1eba9b561cfa28883f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040e1900648465080c2d72f5801"
}

Optionally validate that the signed transaction matches the original intent by calling /construction/parse:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "signed": true,
  "transaction": "010ab7047472616e73616374696f6e287075626c69634b6579733a205b537472696e675d29207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a2020202020202020666f72206b657920696e207075626c69634b657973207b0a2020202020202020202020202f2f2043726561746520616e206163636f756e7420616e642073657420746865206163636f756e74207075626c6963206b65792e0a2020202020202020202020206c65742061636374203d20417574684163636f756e742870617965723a207061796572290a2020202020202020202020206c6574207075626c69634b6579203d205075626c69634b6579280a202020202020202020202020202020207075626c69634b65793a206b65792e6465636f646548657828292c0a202020202020202020202020202020207369676e6174757265416c676f726974686d3a205369676e6174757265416c676f726974686d2e45434453415f736563703235366b310a202020202020202020202020290a202020202020202020202020616363742e6b6579732e616464280a202020202020202020202020202020207075626c69634b65793a207075626c69634b65792c0a2020202020202020202020202020202068617368416c676f726974686d3a2048617368416c676f726974686d2e534841335f3235362c0a202020202020202020202020202020207765696768743a20313030302e300a202020202020202020202020290a20202020202020207d0a202020207d0a7d0a12b8017b2274797065223a224172726179222c2276616c7565223a5b7b2274797065223a22537472696e67222c2276616c7565223a226264376266646236323937323037363139656239393037613339663531326163323836316634356535626135326366616137373762366537666139616162366334353532326661613031336331343931363237626235333466646263616533663035343539383266366266373762363635626331363265626632353932343834227d5d7d0a1a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb9208f4e2a0d0a08d99b1eba9b561cfa18883f3208d99b1eba9b561cfa3a08d99b1eba9b561cfa4a4c0a08d99b1eba9b561cfa1a40bc688b21f76f2d3704d5c174464bd428be68704413c9f6401c0bf51e9a80e28a6c117dc9b10b9c7818426bcfb5d62919a3b242c8d4c47d10824fc661d64f886a:0a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb91a08d99b1eba9b561cfa28883f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040e1900648465080c2d72f5801"
}

This returns something like:

{
  "account_identifier_signers": [
    {
      "address": "0xd99b1eba9b561cfa"
    }
  ],
  "operations": [
    {
      "metadata": {
        "public_key": "02bd7bfdb6297207619eb9907a39f512ac2861f45e5ba52cfaa777b6e7fa9aab6c"
      },
      "operation_identifier": {
        "index": 0
      },
      "type": "create_account"
    }
  ],
  "signers": [
    "0xd99b1eba9b561cfa"
  ]
}

As we can see, the operations match what we specified.

We can now submit the transaction by calling /construction/submit:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "signed_transaction": "010ab7047472616e73616374696f6e287075626c69634b6579733a205b537472696e675d29207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a2020202020202020666f72206b657920696e207075626c69634b657973207b0a2020202020202020202020202f2f2043726561746520616e206163636f756e7420616e642073657420746865206163636f756e74207075626c6963206b65792e0a2020202020202020202020206c65742061636374203d20417574684163636f756e742870617965723a207061796572290a2020202020202020202020206c6574207075626c69634b6579203d205075626c69634b6579280a202020202020202020202020202020207075626c69634b65793a206b65792e6465636f646548657828292c0a202020202020202020202020202020207369676e6174757265416c676f726974686d3a205369676e6174757265416c676f726974686d2e45434453415f736563703235366b310a202020202020202020202020290a202020202020202020202020616363742e6b6579732e616464280a202020202020202020202020202020207075626c69634b65793a207075626c69634b65792c0a2020202020202020202020202020202068617368416c676f726974686d3a2048617368416c676f726974686d2e534841335f3235362c0a202020202020202020202020202020207765696768743a20313030302e300a202020202020202020202020290a20202020202020207d0a202020207d0a7d0a12b8017b2274797065223a224172726179222c2276616c7565223a5b7b2274797065223a22537472696e67222c2276616c7565223a226264376266646236323937323037363139656239393037613339663531326163323836316634356535626135326366616137373762366537666139616162366334353532326661613031336331343931363237626235333466646263616533663035343539383266366266373762363635626331363265626632353932343834227d5d7d0a1a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb9208f4e2a0d0a08d99b1eba9b561cfa18883f3208d99b1eba9b561cfa3a08d99b1eba9b561cfa4a4c0a08d99b1eba9b561cfa1a40bc688b21f76f2d3704d5c174464bd428be68704413c9f6401c0bf51e9a80e28a6c117dc9b10b9c7818426bcfb5d62919a3b242c8d4c47d10824fc661d64f886a:0a206d71c7a5a0aee59bf54899ed2622cc0ef724baabb20d2f71f4e851937e9a0cb91a08d99b1eba9b561cfa28883f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040e1900648465080c2d72f5801"
}

This will return something like:

{
  "transaction_identifier": {
    "hash": "f0a9f4f2ff38d9049e12312ff6ee2087e0c0e53ea0cf2849579ed72e245ed9ad"
  }
}

You can then look up the transaction on the Flow Testnet Explorer, e.g.

This tells us that account 0x94b6cb63cb81177a was successfully created by the transaction in block 69454076.

We can confirm that Flow Rosetta has indexed the corresponding operation by calling /block:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "block_identifier": {
    "index": 69454076
  }
}

Which shows us the corresponding create_account operation and its address:

{
  "block": {
    "block_identifier": {
      "hash": "858eb2a4e96b7a01f0d6430022c4f5b26a59dc87ce60647e6ef1a74dbfddd234",
      "index": 69454076
    },
    "parent_block_identifier": {
      "hash": "366794281fac18111f84f5a04da44c380e3220d8c1e7fdc070b059d854b955d4",
      "index": 69454075
    },
    "timestamp": 1653961701995,
    "transactions": [
      {
        "metadata": {
          "error_message": "",
          "events": [
            {
              "amount": "100000",
              "sender": "0xd99b1eba9b561cfa",
              "type": "WITHDRAWAL"
            },
            {
              "amount": "100000",
              "type": "WITHDRAWAL"
            },
            {
              "amount": "0",
              "receiver": "0x912d5440f7e3769e",
              "type": "DEPOSIT"
            },
            {
              "amount": "100000",
              "receiver": "0x94b6cb63cb81177a",
              "type": "DEPOSIT"
            },
            {
              "amount": "314",
              "sender": "0xd99b1eba9b561cfa",
              "type": "WITHDRAWAL"
            },
            {
              "amount": "314",
              "receiver": "0x912d5440f7e3769e",
              "type": "DEPOSIT"
            }
          ],
          "failed": false
        },
        "operations": [
          {
            "operation_identifier": {
              "index": 0
            },
            "type": "create_account",
            "status": "SUCCESS",
            "account": {
              "address": "0x94b6cb63cb81177a"
            }
          },
          {
            "operation_identifier": {
              "index": 1
            },
            "type": "fee",
            "status": "SUCCESS",
            "account": {
              "address": "0xd99b1eba9b561cfa"
            },
            "amount": {
              "value": "-314",
              "currency": {
                "symbol": "FLOW",
                "decimals": 8
              }
            }
          },
          {
            "operation_identifier": {
              "index": 2
            },
            "type": "transfer",
            "status": "SUCCESS",
            "account": {
              "address": "0xd99b1eba9b561cfa"
            },
            "amount": {
              "value": "-100000",
              "currency": {
                "symbol": "FLOW",
                "decimals": 8
              }
            }
          },
          {
            "operation_identifier": {
              "index": 3
            },
            "related_operations": [
              {
                "index": 2
              }
            ],
            "type": "transfer",
            "status": "SUCCESS",
            "account": {
              "address": "0x94b6cb63cb81177a"
            },
            "amount": {
              "value": "100000",
              "currency": {
                "symbol": "FLOW",
                "decimals": 8
              }
            }
          }
        ],
        "transaction_identifier": {
          "hash": "f0a9f4f2ff38d9049e12312ff6ee2087e0c0e53ea0cf2849579ed72e245ed9ad"
        }
      }
    ]
  }
}

Tada!

Creating Proxy Accounts via the Rosetta Construction API

Proxy accounts can be created in the exact same way as normal accounts, just use the operation type of create_proxy_account instead of create_account.

First, generate a key:

$ go run cmd/genkey/genkey.go
Public Key (Flow Format): ac8ffec9c3b5869eb3188865334c703cfbd2eaeebcf2b33b6f3fcbd69886811b7c1529f9e0e79f7060d09b6ea2bc9d804521406ce0f88996071344a10081ea53
Public Key (Rosetta Format): 03ac8ffec9c3b5869eb3188865334c703cfbd2eaeebcf2b33b6f3fcbd69886811b
Private Key: 52c354a66d683929847126a27cb7a8b3b63f8d2c69d60bd4ad7867b7c3b95709

If you then send the following /construction/preprocess request:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "operations": [
    {
      "type": "create_proxy_account",
      "operation_identifier": {
        "index": 0
      },
      "metadata": {
        "public_key": "03ac8ffec9c3b5869eb3188865334c703cfbd2eaeebcf2b33b6f3fcbd69886811b"
      }
    }
  ],
  "metadata": {
    "payer": "0xd99b1eba9b561cfa"
  }
}

Will respond with something like:

{
  "options": {
    "protobuf": "1a08d99b1eba9b561cfa28ffffffffffffffffff01486e5080c2d72f5801"
  }
}

If we pass it along to /construction/metadata:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "options": {
    "protobuf": "1a08d99b1eba9b561cfa28ffffffffffffffffff01486e5080c2d72f5801"
  }
}

It will respond with something like:

{
  "metadata": {
    "height": "69243533",
    "protobuf": "0a209eb08c3774b4f422c6b191658b60cafdf69bceb0f519100169f33a49411b7f8a1a08d99b1eba9b561cfa28cc3530013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040a89206486e5080c2d72f5801"
  },
  "suggested_fee": [
    {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "100648"
    }
  ]
}

If we follow-up with a request to /construction/payloads:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "operations": [
    {
      "type": "create_proxy_account",
      "operation_identifier": {
        "index": 0
      },
      "metadata": {
        "public_key": "03ac8ffec9c3b5869eb3188865334c703cfbd2eaeebcf2b33b6f3fcbd69886811b"
      }
    }
  ],
  "metadata": {
    "protobuf": "0a209eb08c3774b4f422c6b191658b60cafdf69bceb0f519100169f33a49411b7f8a1a08d99b1eba9b561cfa28cc3530013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040a89206486e5080c2d72f5801"
  }
}

It will respond with something like:

{
  "payloads": [
    {
      "account_identifier": {
        "address": "0xd99b1eba9b561cfa"
      },
      "address": "0xd99b1eba9b561cfa",
      "hex_bytes": "ab3ef346874808566379f0d1104c79192adbcbaaba50f44d3a8ce794f9535215",
      "signature_type": "ecdsa"
    }
  ],
  "unsigned_transaction": "010a9602696d706f727420466c6f77436f6c6453746f7261676550726f78792066726f6d203078303263316461333833363930356362340a0a7472616e73616374696f6e287075626c69634b65793a20537472696e6729207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a20202020202020202f2f204372656174652061206e6577206163636f756e742077697468206120466c6f77436f6c6453746f7261676550726f7879205661756c742e0a2020202020202020466c6f77436f6c6453746f7261676550726f78792e73657475702870617965723a2070617965722c207075626c69634b65793a207075626c69634b65792e6465636f64654865782829290a202020207d0a7d0a129d017b2274797065223a22537472696e67222c2276616c7565223a226163386666656339633362353836396562333138383836353333346337303363666264326561656562636632623333623666336663626436393838363831316237633135323966396530653739663730363064303962366561326263396438303435323134303663653066383839393630373133343461313030383165613533227d0a1a209eb08c3774b4f422c6b191658b60cafdf69bceb0f519100169f33a49411b7f8a208f4e2a0d0a08d99b1eba9b561cfa18cc353208d99b1eba9b561cfa3a08d99b1eba9b561cfa:0a209eb08c3774b4f422c6b191658b60cafdf69bceb0f519100169f33a49411b7f8a1a08d99b1eba9b561cfa28cc3530013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040a89206486e5080c2d72f5801"
}

We can then sign the payload for the unsigned transaction with the private key of our root originator:

$ go run cmd/sign/sign.go 914bf493aacd199c1f4f2f19d3f80ef69066307d39d7f4ccfe298ab76fbee0b5 ab3ef346874808566379f0d1104c79192adbcbaaba50f44d3a8ce794f9535215
cddb3e61ce93a094deec2fd3f8d090620836eb3a431b5efef49ac7e1744d03f31c991114f14cc9592686f6a97d18bdb1afd27b2c0697cc3e0b8baad5c2e7b979

And pass it along to /construction/combine:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "unsigned_transaction": "010a9602696d706f727420466c6f77436f6c6453746f7261676550726f78792066726f6d203078303263316461333833363930356362340a0a7472616e73616374696f6e287075626c69634b65793a20537472696e6729207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a20202020202020202f2f204372656174652061206e6577206163636f756e742077697468206120466c6f77436f6c6453746f7261676550726f7879205661756c742e0a2020202020202020466c6f77436f6c6453746f7261676550726f78792e73657475702870617965723a2070617965722c207075626c69634b65793a207075626c69634b65792e6465636f64654865782829290a202020207d0a7d0a129d017b2274797065223a22537472696e67222c2276616c7565223a226163386666656339633362353836396562333138383836353333346337303363666264326561656562636632623333623666336663626436393838363831316237633135323966396530653739663730363064303962366561326263396438303435323134303663653066383839393630373133343461313030383165613533227d0a1a209eb08c3774b4f422c6b191658b60cafdf69bceb0f519100169f33a49411b7f8a208f4e2a0d0a08d99b1eba9b561cfa18cc353208d99b1eba9b561cfa3a08d99b1eba9b561cfa:0a209eb08c3774b4f422c6b191658b60cafdf69bceb0f519100169f33a49411b7f8a1a08d99b1eba9b561cfa28cc3530013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040a89206486e5080c2d72f5801",
  "signatures": [
    {
      "signing_payload": {
        "account_identifier": {
          "address": "0xd99b1eba9b561cfa"
        },
        "address": "0xd99b1eba9b561cfa",
        "hex_bytes": "ab3ef346874808566379f0d1104c79192adbcbaaba50f44d3a8ce794f9535215",
        "signature_type": "ecdsa"
      },
      "public_key": {
        "hex_bytes": "027509387372ee7ded90281fe21dc5b250b609bacc516da473756d7f190ec2bce7",
        "curve_type": "secp256k1"
      },
      "signature_type": "ecdsa",
      "hex_bytes": "cddb3e61ce93a094deec2fd3f8d090620836eb3a431b5efef49ac7e1744d03f31c991114f14cc9592686f6a97d18bdb1afd27b2c0697cc3e0b8baad5c2e7b979"
    }
  ]
}

This will respond with something like:

{
  "signed_transaction": "010a9602696d706f727420466c6f77436f6c6453746f7261676550726f78792066726f6d203078303263316461333833363930356362340a0a7472616e73616374696f6e287075626c69634b65793a20537472696e6729207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a20202020202020202f2f204372656174652061206e6577206163636f756e742077697468206120466c6f77436f6c6453746f7261676550726f7879205661756c742e0a2020202020202020466c6f77436f6c6453746f7261676550726f78792e73657475702870617965723a2070617965722c207075626c69634b65793a207075626c69634b65792e6465636f64654865782829290a202020207d0a7d0a129d017b2274797065223a22537472696e67222c2276616c7565223a226163386666656339633362353836396562333138383836353333346337303363666264326561656562636632623333623666336663626436393838363831316237633135323966396530653739663730363064303962366561326263396438303435323134303663653066383839393630373133343461313030383165613533227d0a1a209eb08c3774b4f422c6b191658b60cafdf69bceb0f519100169f33a49411b7f8a208f4e2a0d0a08d99b1eba9b561cfa18cc353208d99b1eba9b561cfa3a08d99b1eba9b561cfa4a4c0a08d99b1eba9b561cfa1a40cddb3e61ce93a094deec2fd3f8d090620836eb3a431b5efef49ac7e1744d03f31c991114f14cc9592686f6a97d18bdb1afd27b2c0697cc3e0b8baad5c2e7b979:0a209eb08c3774b4f422c6b191658b60cafdf69bceb0f519100169f33a49411b7f8a1a08d99b1eba9b561cfa28cc3530013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040a89206486e5080c2d72f5801"
}

Which we can submit via /construction/submit:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "signed_transaction": "010a9602696d706f727420466c6f77436f6c6453746f7261676550726f78792066726f6d203078303263316461333833363930356362340a0a7472616e73616374696f6e287075626c69634b65793a20537472696e6729207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a20202020202020202f2f204372656174652061206e6577206163636f756e742077697468206120466c6f77436f6c6453746f7261676550726f7879205661756c742e0a2020202020202020466c6f77436f6c6453746f7261676550726f78792e73657475702870617965723a2070617965722c207075626c69634b65793a207075626c69634b65792e6465636f64654865782829290a202020207d0a7d0a129d017b2274797065223a22537472696e67222c2276616c7565223a226163386666656339633362353836396562333138383836353333346337303363666264326561656562636632623333623666336663626436393838363831316237633135323966396530653739663730363064303962366561326263396438303435323134303663653066383839393630373133343461313030383165613533227d0a1a209eb08c3774b4f422c6b191658b60cafdf69bceb0f519100169f33a49411b7f8a208f4e2a0d0a08d99b1eba9b561cfa18cc353208d99b1eba9b561cfa3a08d99b1eba9b561cfa4a4c0a08d99b1eba9b561cfa1a40cddb3e61ce93a094deec2fd3f8d090620836eb3a431b5efef49ac7e1744d03f31c991114f14cc9592686f6a97d18bdb1afd27b2c0697cc3e0b8baad5c2e7b979:0a209eb08c3774b4f422c6b191658b60cafdf69bceb0f519100169f33a49411b7f8a1a08d99b1eba9b561cfa28cc3530013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040a89206486e5080c2d72f5801"
}

Resulting in something like:

{
  "transaction_identifier": {
    "hash": "ac07e337090e0ae33aa06ece96988cba49ade0294fc459aa5bb5beaf7b221b32"
  }
}

We can then view the transaction on the Flow Testnet Explorer:

This tells us that the transaction in block 69243540 has successfully created a new account at 0xf5d5bcb3c4e0b071 with a FlowColdStorageProxy.Vault.

We can check that Flow Rosetta has also seen this with a request to /block:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "block_identifier": {
    "index": 69243540
  }
}

This confirms the newly created proxy account within the corresponding transaction's operations:

{
  "operation_identifier": {
    "index": 0
  },
  "type": "create_proxy_account",
  "status": "SUCCESS",
  "account": {
    "address": "0xf5d5bcb3c4e0b071"
  },
  "metadata": {
    "proxy_public_key": "03ac8ffec9c3b5869eb3188865334c703cfbd2eaeebcf2b33b6f3fcbd69886811b"
  }
}

Creating Transfers via the Rosetta Construction API

For the purposes of this example, we will be transferring from the account we created earlier, 0x94b6cb63cb81177a, to the proxy account we created, 0xf5d5bcb3c4e0b071.

We need to first deposit some FLOW into the sender's account so that it actually has some funds to transfer. We will do this using an external account we have, e.g.

$ flow transactions send  --network testnet --signer test-account \
    deposit.cdc 0x94b6cb63cb81177a 2.0

Where deposit.cdc looks like:

import FlowToken from 0x7e60df042a9c0868
import FungibleToken from 0x9a0766d93b6608b7

transaction(receiver: Address, amount: UFix64) {
    let xfer: @FungibleToken.Vault
    prepare(sender: AuthAccount) {
        let vault = sender.borrow<&FlowToken.Vault>(from: /storage/flowTokenVault)!
        self.xfer <- vault.withdraw(amount: amount)
    }
    execute {
        let receiver = getAccount(receiver)
            .capabilities.borrow<&{FungibleToken.Receiver}>(/public/flowTokenReceiver)!
        receiver.deposit(from: <-self.xfer)
    }
}

We can confirm the updated account balance with a call to /account/balance:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "account_identifier": {
    "address": "0x94b6cb63cb81177a"
  }
}

That shows us the expected balance of 2.001 FLOW (the 2 FLOW that were deposited and the minimum account balance of 0.001 FLOW):

{
  "balances": [
    {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "200100000"
    }
  ],
  "block_identifier": {
    "hash": "9b40d58cc50c5a940034c8084dc6554b936a8febc3abc8c4641a843b88278cf4",
    "index": 69456516
  },
  "metadata": {
    "sequence_number": "0"
  }
}

We can now make the call to /construction/preprocess with the appropriate values:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "operations": [
    {
      "type": "transfer",
      "operation_identifier": {
        "index": 0
      },
      "account": {
        "address": "0x94b6cb63cb81177a"
      },
      "amount": {
        "currency": {
          "decimals": 8,
          "symbol": "FLOW"
        },
        "value": "-50000000"
      }
    },
    {
      "type": "transfer",
      "operation_identifier": {
        "index": 1
      },
      "related_operations": [
        {
          "index": 0
        }
      ],
      "account": {
        "address": "0xf5d5bcb3c4e0b071"
      },
      "amount": {
        "currency": {
          "decimals": 8,
          "symbol": "FLOW"
        },
        "value": "50000000"
      }
    }
  ],
  "metadata": {
    "payer": "0x94b6cb63cb81177a"
  }
}

It will respond with something like:

{
  "options": {
    "protobuf": "1a0894b6cb63cb81177a28ffffffffffffffffff0148505080c2d72f"
  }
}

We can then make the /construction/metadata call:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "options": {
    "protobuf": "1a0894b6cb63cb81177a28ffffffffffffffffff0148505080c2d72f"
  }
}

Which will respond with something like:

{
  "metadata": {
    "height": "69456819",
    "protobuf": "0a206649ceb829aa82d72133888d190633d1200fb2abbdb2476b46542b9aee152a4f1a0894b6cb63cb81177a30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040f30348505080c2d72f"
  },
  "suggested_fee": [
    {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "499"
    }
  ]
}

This will give us the info needed to make the /construction/payloads call:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "operations": [
    {
      "type": "transfer",
      "operation_identifier": {
        "index": 0
      },
      "account": {
        "address": "0x94b6cb63cb81177a"
      },
      "amount": {
        "currency": {
          "decimals": 8,
          "symbol": "FLOW"
        },
        "value": "-50000000"
      }
    },
    {
      "type": "transfer",
      "operation_identifier": {
        "index": 1
      },
      "related_operations": [
        {
          "index": 0
        }
      ],
      "account": {
        "address": "0xf5d5bcb3c4e0b071"
      },
      "amount": {
        "currency": {
          "decimals": 8,
          "symbol": "FLOW"
        },
        "value": "50000000"
      }
    }
  ],
  "metadata": {
    "protobuf": "0a206649ceb829aa82d72133888d190633d1200fb2abbdb2476b46542b9aee152a4f1a0894b6cb63cb81177a30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040f30348505080c2d72f"
  }
}

It will respond with something like:

{
  "payloads": [
    {
      "account_identifier": {
        "address": "0x94b6cb63cb81177a"
      },
      "address": "0x94b6cb63cb81177a",
      "hex_bytes": "087ea79d95670e78eba5f5d48ccedb4c716fe7dcd1ae71e06e498430be589b82",
      "signature_type": "ecdsa"
    }
  ],
  "unsigned_transaction": "010abc08696d706f727420466c6f77546f6b656e2066726f6d203078376536306466303432613963303836380a696d706f72742046756e6769626c65546f6b656e2066726f6d203078396130373636643933623636303862370a0a7472616e73616374696f6e2872656365697665723a20416464726573732c20616d6f756e743a2055466978363429207b0a0a202020202f2f20546865205661756c74207265736f75726365207468617420686f6c64732074686520746f6b656e73207468617420617265206265696e67207472616e736665727265642e0a202020206c657420786665723a204046756e6769626c65546f6b656e2e5661756c740a0a20202020707265706172652873656e6465723a20417574684163636f756e7429207b0a20202020202020202f2f204765742061207265666572656e636520746f207468652073656e646572277320466c6f77546f6b656e2e5661756c742e0a20202020202020206c6574207661756c74203d2073656e6465722e626f72726f773c26466c6f77546f6b656e2e5661756c743e2866726f6d3a202f73746f726167652f666c6f77546f6b656e5661756c74290a2020202020202020202020203f3f2070616e69632822436f756c64206e6f7420626f72726f772061207265666572656e636520746f207468652073656e6465722773207661756c7422290a0a20202020202020202f2f20576974686472617720746f6b656e732066726f6d207468652073656e646572277320466c6f77546f6b656e2e5661756c742e0a202020202020202073656c662e78666572203c2d207661756c742e776974686472617728616d6f756e743a20616d6f756e74290a202020207d0a0a2020202065786563757465207b0a20202020202020202f2f204765742061207265666572656e636520746f2074686520726563656976657227732064656661756c742046756e6769626c65546f6b656e2e52656365697665720a20202020202020202f2f20666f7220464c4f5720746f6b656e732e0a20202020202020206c6574207265636569766572203d206765744163636f756e74287265636569766572290a2020202020202020202020202e6765744361706162696c697479282f7075626c69632f666c6f77546f6b656e5265636569766572290a2020202020202020202020202e626f72726f773c267b46756e6769626c65546f6b656e2e52656365697665727d3e28290a2020202020202020202020203f3f2070616e69632822436f756c64206e6f7420626f72726f772061207265666572656e636520746f207468652072656365697665722773207661756c7422290a0a20202020202020202f2f204465706f736974207468652077697468647261776e20746f6b656e7320696e207468652072656365697665722773207661756c742e0a202020202020202072656365697665722e6465706f7369742866726f6d3a203c2d73656c662e78666572290a202020207d0a7d0a12307b2274797065223a2241646472657373222c2276616c7565223a22307866356435626362336334653062303731227d0a12277b2274797065223a22554669783634222c2276616c7565223a22302e3530303030303030227d0a1a206649ceb829aa82d72133888d190633d1200fb2abbdb2476b46542b9aee152a4f208f4e2a0a0a0894b6cb63cb81177a320894b6cb63cb81177a3a0894b6cb63cb81177a:0a206649ceb829aa82d72133888d190633d1200fb2abbdb2476b46542b9aee152a4f1a0894b6cb63cb81177a30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040f30348505080c2d72f"
}

We can then sign the unsigned transaction with the sender's private key:

$ go run cmd/sign/sign.go dc915efc3992d2c59b69de4948d565ac953818d9ccee78b65df590764786ada0 087ea79d95670e78eba5f5d48ccedb4c716fe7dcd1ae71e06e498430be589b82
403412f6b7ca95b6c5500b23cc2119c9083da7094446c24d80337f538913310a7dd1730c58f44587f24bfedc3f60c91e248d3b759097d2f76acb73ba2bfe6e71

And make the call to /construction/combine:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "unsigned_transaction": "010abc08696d706f727420466c6f77546f6b656e2066726f6d203078376536306466303432613963303836380a696d706f72742046756e6769626c65546f6b656e2066726f6d203078396130373636643933623636303862370a0a7472616e73616374696f6e2872656365697665723a20416464726573732c20616d6f756e743a2055466978363429207b0a0a202020202f2f20546865205661756c74207265736f75726365207468617420686f6c64732074686520746f6b656e73207468617420617265206265696e67207472616e736665727265642e0a202020206c657420786665723a204046756e6769626c65546f6b656e2e5661756c740a0a20202020707265706172652873656e6465723a20417574684163636f756e7429207b0a20202020202020202f2f204765742061207265666572656e636520746f207468652073656e646572277320466c6f77546f6b656e2e5661756c742e0a20202020202020206c6574207661756c74203d2073656e6465722e626f72726f773c26466c6f77546f6b656e2e5661756c743e2866726f6d3a202f73746f726167652f666c6f77546f6b656e5661756c74290a2020202020202020202020203f3f2070616e69632822436f756c64206e6f7420626f72726f772061207265666572656e636520746f207468652073656e6465722773207661756c7422290a0a20202020202020202f2f20576974686472617720746f6b656e732066726f6d207468652073656e646572277320466c6f77546f6b656e2e5661756c742e0a202020202020202073656c662e78666572203c2d207661756c742e776974686472617728616d6f756e743a20616d6f756e74290a202020207d0a0a2020202065786563757465207b0a20202020202020202f2f204765742061207265666572656e636520746f2074686520726563656976657227732064656661756c742046756e6769626c65546f6b656e2e52656365697665720a20202020202020202f2f20666f7220464c4f5720746f6b656e732e0a20202020202020206c6574207265636569766572203d206765744163636f756e74287265636569766572290a2020202020202020202020202e6765744361706162696c697479282f7075626c69632f666c6f77546f6b656e5265636569766572290a2020202020202020202020202e626f72726f773c267b46756e6769626c65546f6b656e2e52656365697665727d3e28290a2020202020202020202020203f3f2070616e69632822436f756c64206e6f7420626f72726f772061207265666572656e636520746f207468652072656365697665722773207661756c7422290a0a20202020202020202f2f204465706f736974207468652077697468647261776e20746f6b656e7320696e207468652072656365697665722773207661756c742e0a202020202020202072656365697665722e6465706f7369742866726f6d3a203c2d73656c662e78666572290a202020207d0a7d0a12307b2274797065223a2241646472657373222c2276616c7565223a22307866356435626362336334653062303731227d0a12277b2274797065223a22554669783634222c2276616c7565223a22302e3530303030303030227d0a1a206649ceb829aa82d72133888d190633d1200fb2abbdb2476b46542b9aee152a4f208f4e2a0a0a0894b6cb63cb81177a320894b6cb63cb81177a3a0894b6cb63cb81177a:0a206649ceb829aa82d72133888d190633d1200fb2abbdb2476b46542b9aee152a4f1a0894b6cb63cb81177a30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040f30348505080c2d72f",
  "signatures": [
    {
      "signing_payload": {
        "account_identifier": {
          "address": "0xd99b1eba9b561cfa"
        },
        "address": "0xd99b1eba9b561cfa",
        "hex_bytes": "087ea79d95670e78eba5f5d48ccedb4c716fe7dcd1ae71e06e498430be589b82",
        "signature_type": "ecdsa"
      },
      "public_key": {
        "hex_bytes": "02bd7bfdb6297207619eb9907a39f512ac2861f45e5ba52cfaa777b6e7fa9aab6c",
        "curve_type": "secp256k1"
      },
      "signature_type": "ecdsa",
      "hex_bytes": "403412f6b7ca95b6c5500b23cc2119c9083da7094446c24d80337f538913310a7dd1730c58f44587f24bfedc3f60c91e248d3b759097d2f76acb73ba2bfe6e71"
    }
  ]
}

Which will respond will something like:

{
  "signed_transaction": "010abc08696d706f727420466c6f77546f6b656e2066726f6d203078376536306466303432613963303836380a696d706f72742046756e6769626c65546f6b656e2066726f6d203078396130373636643933623636303862370a0a7472616e73616374696f6e2872656365697665723a20416464726573732c20616d6f756e743a2055466978363429207b0a0a202020202f2f20546865205661756c74207265736f75726365207468617420686f6c64732074686520746f6b656e73207468617420617265206265696e67207472616e736665727265642e0a202020206c657420786665723a204046756e6769626c65546f6b656e2e5661756c740a0a20202020707265706172652873656e6465723a20417574684163636f756e7429207b0a20202020202020202f2f204765742061207265666572656e636520746f207468652073656e646572277320466c6f77546f6b656e2e5661756c742e0a20202020202020206c6574207661756c74203d2073656e6465722e626f72726f773c26466c6f77546f6b656e2e5661756c743e2866726f6d3a202f73746f726167652f666c6f77546f6b656e5661756c74290a2020202020202020202020203f3f2070616e69632822436f756c64206e6f7420626f72726f772061207265666572656e636520746f207468652073656e6465722773207661756c7422290a0a20202020202020202f2f20576974686472617720746f6b656e732066726f6d207468652073656e646572277320466c6f77546f6b656e2e5661756c742e0a202020202020202073656c662e78666572203c2d207661756c742e776974686472617728616d6f756e743a20616d6f756e74290a202020207d0a0a2020202065786563757465207b0a20202020202020202f2f204765742061207265666572656e636520746f2074686520726563656976657227732064656661756c742046756e6769626c65546f6b656e2e52656365697665720a20202020202020202f2f20666f7220464c4f5720746f6b656e732e0a20202020202020206c6574207265636569766572203d206765744163636f756e74287265636569766572290a2020202020202020202020202e6765744361706162696c697479282f7075626c69632f666c6f77546f6b656e5265636569766572290a2020202020202020202020202e626f72726f773c267b46756e6769626c65546f6b656e2e52656365697665727d3e28290a2020202020202020202020203f3f2070616e69632822436f756c64206e6f7420626f72726f772061207265666572656e636520746f207468652072656365697665722773207661756c7422290a0a20202020202020202f2f204465706f736974207468652077697468647261776e20746f6b656e7320696e207468652072656365697665722773207661756c742e0a202020202020202072656365697665722e6465706f7369742866726f6d3a203c2d73656c662e78666572290a202020207d0a7d0a12307b2274797065223a2241646472657373222c2276616c7565223a22307866356435626362336334653062303731227d0a12277b2274797065223a22554669783634222c2276616c7565223a22302e3530303030303030227d0a1a206649ceb829aa82d72133888d190633d1200fb2abbdb2476b46542b9aee152a4f208f4e2a0a0a0894b6cb63cb81177a320894b6cb63cb81177a3a0894b6cb63cb81177a4a4c0a0894b6cb63cb81177a1a40403412f6b7ca95b6c5500b23cc2119c9083da7094446c24d80337f538913310a7dd1730c58f44587f24bfedc3f60c91e248d3b759097d2f76acb73ba2bfe6e71:0a206649ceb829aa82d72133888d190633d1200fb2abbdb2476b46542b9aee152a4f1a0894b6cb63cb81177a30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040f30348505080c2d72f"
}

And, finally, submit the transaction via /construction/submit:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "signed_transaction": "010abc08696d706f727420466c6f77546f6b656e2066726f6d203078376536306466303432613963303836380a696d706f72742046756e6769626c65546f6b656e2066726f6d203078396130373636643933623636303862370a0a7472616e73616374696f6e2872656365697665723a20416464726573732c20616d6f756e743a2055466978363429207b0a0a202020202f2f20546865205661756c74207265736f75726365207468617420686f6c64732074686520746f6b656e73207468617420617265206265696e67207472616e736665727265642e0a202020206c657420786665723a204046756e6769626c65546f6b656e2e5661756c740a0a20202020707265706172652873656e6465723a20417574684163636f756e7429207b0a20202020202020202f2f204765742061207265666572656e636520746f207468652073656e646572277320466c6f77546f6b656e2e5661756c742e0a20202020202020206c6574207661756c74203d2073656e6465722e626f72726f773c26466c6f77546f6b656e2e5661756c743e2866726f6d3a202f73746f726167652f666c6f77546f6b656e5661756c74290a2020202020202020202020203f3f2070616e69632822436f756c64206e6f7420626f72726f772061207265666572656e636520746f207468652073656e6465722773207661756c7422290a0a20202020202020202f2f20576974686472617720746f6b656e732066726f6d207468652073656e646572277320466c6f77546f6b656e2e5661756c742e0a202020202020202073656c662e78666572203c2d207661756c742e776974686472617728616d6f756e743a20616d6f756e74290a202020207d0a0a2020202065786563757465207b0a20202020202020202f2f204765742061207265666572656e636520746f2074686520726563656976657227732064656661756c742046756e6769626c65546f6b656e2e52656365697665720a20202020202020202f2f20666f7220464c4f5720746f6b656e732e0a20202020202020206c6574207265636569766572203d206765744163636f756e74287265636569766572290a2020202020202020202020202e6765744361706162696c697479282f7075626c69632f666c6f77546f6b656e5265636569766572290a2020202020202020202020202e626f72726f773c267b46756e6769626c65546f6b656e2e52656365697665727d3e28290a2020202020202020202020203f3f2070616e69632822436f756c64206e6f7420626f72726f772061207265666572656e636520746f207468652072656365697665722773207661756c7422290a0a20202020202020202f2f204465706f736974207468652077697468647261776e20746f6b656e7320696e207468652072656365697665722773207661756c742e0a202020202020202072656365697665722e6465706f7369742866726f6d3a203c2d73656c662e78666572290a202020207d0a7d0a12307b2274797065223a2241646472657373222c2276616c7565223a22307866356435626362336334653062303731227d0a12277b2274797065223a22554669783634222c2276616c7565223a22302e3530303030303030227d0a1a206649ceb829aa82d72133888d190633d1200fb2abbdb2476b46542b9aee152a4f208f4e2a0a0a0894b6cb63cb81177a320894b6cb63cb81177a3a0894b6cb63cb81177a4a4c0a0894b6cb63cb81177a1a40403412f6b7ca95b6c5500b23cc2119c9083da7094446c24d80337f538913310a7dd1730c58f44587f24bfedc3f60c91e248d3b759097d2f76acb73ba2bfe6e71:0a206649ceb829aa82d72133888d190633d1200fb2abbdb2476b46542b9aee152a4f1a0894b6cb63cb81177a30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040f30348505080c2d72f"
}

This will respond with something like:

{
  "transaction_identifier": {
    "hash": "5d1979d943fa364b23d11380ab72a2cc49177c3985cc36ded98e26a179b38c80"
  }
}

We can then view the transaction on the Flow Testnet Explorer:

This tells us that the transaction in block 69456946 has successfully transferred 0.5 FLOW from 0x94b6cb63cb81177a to 0xf5d5bcb3c4e0b071.

We can check that Flow Rosetta has also seen this with a request to /block:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "block_identifier": {
    "index": 69456946
  }
}

This lets us confirm the transfer operations:

{
  "block": {
    "block_identifier": {
      "hash": "1c0c0cb257ee7751f1d86302ec33f2ce8f7d7185881e9c69e768abae11a59e76",
      "index": 69456946
    },
    "parent_block_identifier": {
      "hash": "f40bc5cc1f603647fea4139305a4f81a522a13631e25ef804da2f4b82528867c",
      "index": 69456945
    },
    "timestamp": 1653965548661,
    "transactions": [
      {
        "metadata": {
          "error_message": "",
          "events": [
            {
              "amount": "50000000",
              "receiver": "0xf5d5bcb3c4e0b071",
              "type": "PROXY_DEPOSIT"
            },
            {
              "amount": "50000000",
              "sender": "0x94b6cb63cb81177a",
              "type": "WITHDRAWAL"
            },
            {
              "amount": "50000000",
              "receiver": "0xf5d5bcb3c4e0b071",
              "type": "DEPOSIT"
            },
            {
              "amount": "209",
              "sender": "0x94b6cb63cb81177a",
              "type": "WITHDRAWAL"
            },
            {
              "amount": "209",
              "receiver": "0x912d5440f7e3769e",
              "type": "DEPOSIT"
            }
          ],
          "failed": false
        },
        "operations": [
          {
            "operation_identifier": {
              "index": 0
            },
            "type": "fee",
            "status": "SUCCESS",
            "account": {
              "address": "0x94b6cb63cb81177a"
            },
            "amount": {
              "value": "-209",
              "currency": {
                "symbol": "FLOW",
                "decimals": 8
              }
            }
          },
          {
            "operation_identifier": {
              "index": 1
            },
            "type": "transfer",
            "status": "SUCCESS",
            "account": {
              "address": "0x94b6cb63cb81177a"
            },
            "amount": {
              "value": "-50000000",
              "currency": {
                "symbol": "FLOW",
                "decimals": 8
              }
            }
          },
          {
            "operation_identifier": {
              "index": 2
            },
            "related_operations": [
              {
                "index": 1
              }
            ],
            "type": "transfer",
            "status": "SUCCESS",
            "account": {
              "address": "0xf5d5bcb3c4e0b071"
            },
            "amount": {
              "value": "50000000",
              "currency": {
                "symbol": "FLOW",
                "decimals": 8
              }
            }
          }
        ],
        "transaction_identifier": {
          "hash": "5d1979d943fa364b23d11380ab72a2cc49177c3985cc36ded98e26a179b38c80"
        }
      }
    ]
  }
}

We can also make a call to /account/balance for the sender account:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "account_identifier": {
    "address": "0x94b6cb63cb81177a"
  }
}

This confirms that its balance has gone down by 0.5 FLOW + fees:

{
  "balances": [
    {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "150099791"
    }
  ],
  "block_identifier": {
    "hash": "51140d4ffa5da144b6348f964a7506baf2132f94b067f0b669f3328e7cb0e5f2",
    "index": 69457118
  },
  "metadata": {
    "sequence_number": "1"
  }
}

And a similar /account/balance call for the receiver account 0xf5d5bcb3c4e0b071 shows that it now has a balance of 0.5 FLOW:

{
  "balances": [
    {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "50000000"
    }
  ],
  "block_identifier": {
    "hash": "65690d19f1d4986cba735193bec25ed709c0b6016b3fcb0d7b500f5ac547d7c3",
    "index": 69457180
  },
  "metadata": {
    "sequence_number": "0"
  }
}

Creating Proxy Transfers via the Rosetta Construction API

For the purposes of this example, we will be transferring 0.1 FLOW from the proxy account 0xf5d5bcb3c4e0b071 back to 0x94b6cb63cb81177a, and have the transaction submitted and paid for by our root originator 0xd99b1eba9b561cfa.

Note: We could have the transaction submitted and paid for by any account. We're just using the root originator in this example as it's an account we control which has some funds.

To do this, we first call /construction/preprocess with an operation type of proxy_transfer_inner in order to construct the "inner transaction":

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "operations": [
    {
      "type": "proxy_transfer_inner",
      "operation_identifier": {
        "index": 0
      },
      "account": {
        "address": "0xf5d5bcb3c4e0b071"
      },
      "amount": {
        "currency": {
          "decimals": 8,
          "symbol": "FLOW"
        },
        "value": "-10000000"
      }
    },
    {
      "type": "proxy_transfer_inner",
      "operation_identifier": {
        "index": 1
      },
      "related_operations": [
        {
          "index": 0
        }
      ],
      "account": {
        "address": "0x94b6cb63cb81177a"
      },
      "amount": {
        "currency": {
          "decimals": 8,
          "symbol": "FLOW"
        },
        "value": "10000000"
      }
    }
  ]
}

This will return with something like:

{
  "options": {
    "protobuf": "10011a08f5d5bcb3c4e0b07128ffffffffffffffffff0148505080c2d72f"
  }
}

We can then pass that along to /construction/metadata:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "options": {
    "protobuf": "10011a08f5d5bcb3c4e0b07128ffffffffffffffffff0148505080c2d72f"
  }
}

Which will return something like:

{
  "metadata": {
    "height": "0",
    "protobuf": "10011a08f5d5bcb3c4e0b07148505080c2d72f"
  },
  "suggested_fee": [
    {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "0"
    }
  ]
}

We can use that to have /construction/payloads generate the unsigned transaction:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "operations": [
    {
      "type": "proxy_transfer_inner",
      "operation_identifier": {
        "index": 0
      },
      "account": {
        "address": "0xf5d5bcb3c4e0b071"
      },
      "amount": {
        "currency": {
          "decimals": 8,
          "symbol": "FLOW"
        },
        "value": "-10000000"
      }
    },
    {
      "type": "proxy_transfer_inner",
      "operation_identifier": {
        "index": 1
      },
      "related_operations": [
        {
          "index": 0
        }
      ],
      "account": {
        "address": "0x94b6cb63cb81177a"
      },
      "amount": {
        "currency": {
          "decimals": 8,
          "symbol": "FLOW"
        },
        "value": "10000000"
      }
    }
  ],
  "metadata": {
    "protobuf": "10011a08f5d5bcb3c4e0b07148505080c2d72f"
  }
}

This will respond with something like:

{
  "payloads": [
    {
      "account_identifier": {
        "address": "0xf5d5bcb3c4e0b071"
      },
      "address": "0xf5d5bcb3c4e0b071",
      "hex_bytes": "6639db7fe430364dc85ebd0ed5cd0d2ab7ee2b0e2ed3aa54a409e140ad79abad",
      "signature_type": "ecdsa"
    }
  ],
  "unsigned_transaction": "020000000000989680000000000000000094b6cb63cb81177af5d5bcb3c4e0b071"
}

We can then sign this unsigned "inner transaction" with the private key associated with the proxy account:

$ go run cmd/sign/sign.go 52c354a66d683929847126a27cb7a8b3b63f8d2c69d60bd4ad7867b7c3b95709 6639db7fe430364dc85ebd0ed5cd0d2ab7ee2b0e2ed3aa54a409e140ad79abad
7ef5c6f4454c3a2fa1501a7ab6609e5df6f4d3111cf80a14090df86f2d574b212cdb324a3db194b7237110339e3b2edd4a95dd704e9fa63ae15fd29b90e7f8d4

We then make the /construction/combine call:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "unsigned_transaction": "020000000000989680000000000000000094b6cb63cb81177af5d5bcb3c4e0b071",
  "signatures": [
    {
      "signing_payload": {
        "account_identifier": {
          "address": "0xf5d5bcb3c4e0b071"
        },
        "address": "0xf5d5bcb3c4e0b071",
        "hex_bytes": "6639db7fe430364dc85ebd0ed5cd0d2ab7ee2b0e2ed3aa54a409e140ad79abad",
        "signature_type": "ecdsa"
      },
      "public_key": {
        "hex_bytes": "03ac8ffec9c3b5869eb3188865334c703cfbd2eaeebcf2b33b6f3fcbd69886811b",
        "curve_type": "secp256k1"
      },
      "signature_type": "ecdsa",
      "hex_bytes": "7ef5c6f4454c3a2fa1501a7ab6609e5df6f4d3111cf80a14090df86f2d574b212cdb324a3db194b7237110339e3b2edd4a95dd704e9fa63ae15fd29b90e7f8d4"
    }
  ]
}

Which will respond with something like:

{
  "signed_transaction": "020000000000989680000000000000000094b6cb63cb81177af5d5bcb3c4e0b0717ef5c6f4454c3a2fa1501a7ab6609e5df6f4d3111cf80a14090df86f2d574b212cdb324a3db194b7237110339e3b2edd4a95dd704e9fa63ae15fd29b90e7f8d4"
}

At this point, we have a signed "inner transaction". Unlike the standard Rosetta transaction construction flow, we do not submit this transaction, but instead use the signed transaction as metadata.proxy_transfer_payload in the /construction/preprocess call for the "outer transaction" construction.

We use the proxy_transfer operation type for the /construction/preprocess call for the "outer transaction", and set the payer to the address of the account that will simply be paying for the transaction — in this case, our root originator:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "operations": [
    {
      "type": "proxy_transfer",
      "operation_identifier": {
        "index": 0
      },
      "account": {
        "address": "0xf5d5bcb3c4e0b071"
      },
      "amount": {
        "currency": {
          "decimals": 8,
          "symbol": "FLOW"
        },
        "value": "-10000000"
      }
    },
    {
      "type": "proxy_transfer",
      "operation_identifier": {
        "index": 1
      },
      "related_operations": [
        {
          "index": 0
        }
      ],
      "account": {
        "address": "0x94b6cb63cb81177a"
      },
      "amount": {
        "currency": {
          "decimals": 8,
          "symbol": "FLOW"
        },
        "value": "10000000"
      }
    }
  ],
  "metadata": {
    "payer": "0xd99b1eba9b561cfa",
    "proxy_transfer_payload": "020000000000989680000000000000000094b6cb63cb81177af5d5bcb3c4e0b0717ef5c6f4454c3a2fa1501a7ab6609e5df6f4d3111cf80a14090df86f2d574b212cdb324a3db194b7237110339e3b2edd4a95dd704e9fa63ae15fd29b90e7f8d4"
  }
}

This will respond with something like:

{
  "options": {
    "protobuf": "1a08d99b1eba9b561cfa22c201303230303030303030303030393839363830303030303030303030303030303030303934623663623633636238313137376166356435626362336334653062303731376566356336663434353463336132666131353031613761623636303965356466366634643331313163663830613134303930646638366632643537346232313263646233323461336462313934623732333731313033333965336232656464346139356464373034653966613633616531356664323962393065376638643428ffffffffffffffffff01481e5080c2d72f"
  }
}

We pass that along to /construction/metadata:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "options": {
    "protobuf": "1a08d99b1eba9b561cfa22c201303230303030303030303030393839363830303030303030303030303030303030303934623663623633636238313137376166356435626362336334653062303731376566356336663434353463336132666131353031613761623636303965356466366634643331313163663830613134303930646638366632643537346232313263646233323461336462313934623732333731313033333965336232656464346139356464373034653966613633616531356664323962393065376638643428ffffffffffffffffff01481e5080c2d72f"
  }
}

Which will respond with something like:

{
  "metadata": {
    "height": "69490255",
    "protobuf": "0a205ed29244aace03b49013da0e763d5c7187ca0cfd7799b6d2d2bae3085935af331a08d99b1eba9b561cfa22c201303230303030303030303030393839363830303030303030303030303030303030303934623663623633636238313137376166356435626362336334653062303731376566356336663434353463336132666131353031613761623636303965356466366634643331313163663830613134303930646638366632643537346232313263646233323461336462313934623732333731313033333965336232656464346139356464373034653966613633616531356664323962393065376638643428893f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040f901481e5080c2d72f"
  },
  "suggested_fee": [
    {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "249"
    }
  ]
}

We use that to construct the unsigned transaction via /construction/payloads:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "operations": [
    {
      "type": "proxy_transfer",
      "operation_identifier": {
        "index": 0
      },
      "account": {
        "address": "0xf5d5bcb3c4e0b071"
      },
      "amount": {
        "currency": {
          "decimals": 8,
          "symbol": "FLOW"
        },
        "value": "-10000000"
      }
    },
    {
      "type": "proxy_transfer",
      "operation_identifier": {
        "index": 1
      },
      "related_operations": [
        {
          "index": 0
        }
      ],
      "account": {
        "address": "0x94b6cb63cb81177a"
      },
      "amount": {
        "currency": {
          "decimals": 8,
          "symbol": "FLOW"
        },
        "value": "10000000"
      }
    }
  ],
  "metadata": {
    "protobuf": "0a205ed29244aace03b49013da0e763d5c7187ca0cfd7799b6d2d2bae3085935af331a08d99b1eba9b561cfa22c201303230303030303030303030393839363830303030303030303030303030303030303934623663623633636238313137376166356435626362336334653062303731376566356336663434353463336132666131353031613761623636303965356466366634643331313163663830613134303930646638366632643537346232313263646233323461336462313934623732333731313033333965336232656464346139356464373034653966613633616531356664323962393065376638643428893f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040f901481e5080c2d72f"
  }
}

This will return something like:

{
  "payloads": [
    {
      "account_identifier": {
        "address": "0xd99b1eba9b561cfa"
      },
      "address": "0xd99b1eba9b561cfa",
      "hex_bytes": "df7d6cc3b9a286813a83a4bad639b7af52b77a935028bed42f5725a350ed1aad",
      "signature_type": "ecdsa"
    }
  ],
  "unsigned_transaction": "010ac704696d706f727420466c6f77436f6c6453746f7261676550726f78792066726f6d203078303263316461333833363930356362340a0a7472616e73616374696f6e2873656e6465723a20416464726573732c2072656365697665723a20416464726573732c20616d6f756e743a205546697836342c206e6f6e63653a20496e7436342c207369673a20537472696e6729207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a202020207d0a2020202065786563757465207b0a20202020202020202f2f204765742061207265666572656e636520746f207468652073656e646572277320466c6f77436f6c6453746f7261676550726f78792e5661756c742e0a20202020202020206c65742061636374203d206765744163636f756e742873656e646572290a20202020202020206c6574207661756c74203d20616363742e6765744361706162696c69747928466c6f77436f6c6453746f7261676550726f78792e5661756c744361706162696c6974795075626c696350617468292e626f72726f773c26466c6f77436f6c6453746f7261676550726f78792e5661756c743e2829210a0a20202020202020202f2f205472616e7366657220746f6b656e7320746f207468652072656365697665722e0a20202020202020207661756c742e7472616e736665722872656365697665723a2072656365697665722c20616d6f756e743a20616d6f756e742c206e6f6e63653a206e6f6e63652c207369673a207369672e6465636f64654865782829290a202020207d0a7d0a12307b2274797065223a2241646472657373222c2276616c7565223a22307866356435626362336334653062303731227d0a12307b2274797065223a2241646472657373222c2276616c7565223a22307839346236636236336362383131373761227d0a12277b2274797065223a22554669783634222c2276616c7565223a22302e3130303030303030227d0a121d7b2274797065223a22496e743634222c2276616c7565223a2230227d0a129d017b2274797065223a22537472696e67222c2276616c7565223a223765663563366634343534633361326661313530316137616236363039653564663666346433313131636638306131343039306466383666326435373462323132636462333234613364623139346237323337313130333339653362326564643461393564643730346539666136336165313566643239623930653766386434227d0a1a205ed29244aace03b49013da0e763d5c7187ca0cfd7799b6d2d2bae3085935af33208f4e2a0d0a08d99b1eba9b561cfa18893f3208d99b1eba9b561cfa3a08d99b1eba9b561cfa:0a205ed29244aace03b49013da0e763d5c7187ca0cfd7799b6d2d2bae3085935af331a08d99b1eba9b561cfa22c201303230303030303030303030393839363830303030303030303030303030303030303934623663623633636238313137376166356435626362336334653062303731376566356336663434353463336132666131353031613761623636303965356466366634643331313163663830613134303930646638366632643537346232313263646233323461336462313934623732333731313033333965336232656464346139356464373034653966613633616531356664323962393065376638643428893f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040f901481e5080c2d72f"
}

We can sign this unsigned transaction using the key for our payer, the root originator:

$ go run cmd/sign/sign.go 914bf493aacd199c1f4f2f19d3f80ef69066307d39d7f4ccfe298ab76fbee0b5 df7d6cc3b9a286813a83a4bad639b7af52b77a935028bed42f5725a350ed1aad
ed02af244d06778ee1cd69fbc6020f5e75de4b28bafd22c03a64cadde9e8c1ca2301f5da317d4cee5dc1c3dced0b725dd2d512afaec001af8218a69728dc35cb

We can then use this to make the /construction/combine call:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "unsigned_transaction": "010ac704696d706f727420466c6f77436f6c6453746f7261676550726f78792066726f6d203078303263316461333833363930356362340a0a7472616e73616374696f6e2873656e6465723a20416464726573732c2072656365697665723a20416464726573732c20616d6f756e743a205546697836342c206e6f6e63653a20496e7436342c207369673a20537472696e6729207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a202020207d0a2020202065786563757465207b0a20202020202020202f2f204765742061207265666572656e636520746f207468652073656e646572277320466c6f77436f6c6453746f7261676550726f78792e5661756c742e0a20202020202020206c65742061636374203d206765744163636f756e742873656e646572290a20202020202020206c6574207661756c74203d20616363742e6765744361706162696c69747928466c6f77436f6c6453746f7261676550726f78792e5661756c744361706162696c6974795075626c696350617468292e626f72726f773c26466c6f77436f6c6453746f7261676550726f78792e5661756c743e2829210a0a20202020202020202f2f205472616e7366657220746f6b656e7320746f207468652072656365697665722e0a20202020202020207661756c742e7472616e736665722872656365697665723a2072656365697665722c20616d6f756e743a20616d6f756e742c206e6f6e63653a206e6f6e63652c207369673a207369672e6465636f64654865782829290a202020207d0a7d0a12307b2274797065223a2241646472657373222c2276616c7565223a22307866356435626362336334653062303731227d0a12307b2274797065223a2241646472657373222c2276616c7565223a22307839346236636236336362383131373761227d0a12277b2274797065223a22554669783634222c2276616c7565223a22302e3130303030303030227d0a121d7b2274797065223a22496e743634222c2276616c7565223a2230227d0a129d017b2274797065223a22537472696e67222c2276616c7565223a223765663563366634343534633361326661313530316137616236363039653564663666346433313131636638306131343039306466383666326435373462323132636462333234613364623139346237323337313130333339653362326564643461393564643730346539666136336165313566643239623930653766386434227d0a1a205ed29244aace03b49013da0e763d5c7187ca0cfd7799b6d2d2bae3085935af33208f4e2a0d0a08d99b1eba9b561cfa18893f3208d99b1eba9b561cfa3a08d99b1eba9b561cfa:0a205ed29244aace03b49013da0e763d5c7187ca0cfd7799b6d2d2bae3085935af331a08d99b1eba9b561cfa22c201303230303030303030303030393839363830303030303030303030303030303030303934623663623633636238313137376166356435626362336334653062303731376566356336663434353463336132666131353031613761623636303965356466366634643331313163663830613134303930646638366632643537346232313263646233323461336462313934623732333731313033333965336232656464346139356464373034653966613633616531356664323962393065376638643428893f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040f901481e5080c2d72f",
  "signatures": [
    {
      "signing_payload": {
        "account_identifier": {
          "address": "0xd99b1eba9b561cfa"
        },
        "address": "0xd99b1eba9b561cfa",
        "hex_bytes": "df7d6cc3b9a286813a83a4bad639b7af52b77a935028bed42f5725a350ed1aad",
        "signature_type": "ecdsa"
      },
      "public_key": {
        "hex_bytes": "027509387372ee7ded90281fe21dc5b250b609bacc516da473756d7f190ec2bce7",
        "curve_type": "secp256k1"
      },
      "signature_type": "ecdsa",
      "hex_bytes": "ed02af244d06778ee1cd69fbc6020f5e75de4b28bafd22c03a64cadde9e8c1ca2301f5da317d4cee5dc1c3dced0b725dd2d512afaec001af8218a69728dc35cb"
    }
  ]
}

Which will respond with something like:

{
  "signed_transaction": "010ac704696d706f727420466c6f77436f6c6453746f7261676550726f78792066726f6d203078303263316461333833363930356362340a0a7472616e73616374696f6e2873656e6465723a20416464726573732c2072656365697665723a20416464726573732c20616d6f756e743a205546697836342c206e6f6e63653a20496e7436342c207369673a20537472696e6729207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a202020207d0a2020202065786563757465207b0a20202020202020202f2f204765742061207265666572656e636520746f207468652073656e646572277320466c6f77436f6c6453746f7261676550726f78792e5661756c742e0a20202020202020206c65742061636374203d206765744163636f756e742873656e646572290a20202020202020206c6574207661756c74203d20616363742e6765744361706162696c69747928466c6f77436f6c6453746f7261676550726f78792e5661756c744361706162696c6974795075626c696350617468292e626f72726f773c26466c6f77436f6c6453746f7261676550726f78792e5661756c743e2829210a0a20202020202020202f2f205472616e7366657220746f6b656e7320746f207468652072656365697665722e0a20202020202020207661756c742e7472616e736665722872656365697665723a2072656365697665722c20616d6f756e743a20616d6f756e742c206e6f6e63653a206e6f6e63652c207369673a207369672e6465636f64654865782829290a202020207d0a7d0a12307b2274797065223a2241646472657373222c2276616c7565223a22307866356435626362336334653062303731227d0a12307b2274797065223a2241646472657373222c2276616c7565223a22307839346236636236336362383131373761227d0a12277b2274797065223a22554669783634222c2276616c7565223a22302e3130303030303030227d0a121d7b2274797065223a22496e743634222c2276616c7565223a2230227d0a129d017b2274797065223a22537472696e67222c2276616c7565223a223765663563366634343534633361326661313530316137616236363039653564663666346433313131636638306131343039306466383666326435373462323132636462333234613364623139346237323337313130333339653362326564643461393564643730346539666136336165313566643239623930653766386434227d0a1a205ed29244aace03b49013da0e763d5c7187ca0cfd7799b6d2d2bae3085935af33208f4e2a0d0a08d99b1eba9b561cfa18893f3208d99b1eba9b561cfa3a08d99b1eba9b561cfa4a4c0a08d99b1eba9b561cfa1a40ed02af244d06778ee1cd69fbc6020f5e75de4b28bafd22c03a64cadde9e8c1ca2301f5da317d4cee5dc1c3dced0b725dd2d512afaec001af8218a69728dc35cb:0a205ed29244aace03b49013da0e763d5c7187ca0cfd7799b6d2d2bae3085935af331a08d99b1eba9b561cfa22c201303230303030303030303030393839363830303030303030303030303030303030303934623663623633636238313137376166356435626362336334653062303731376566356336663434353463336132666131353031613761623636303965356466366634643331313163663830613134303930646638366632643537346232313263646233323461336462313934623732333731313033333965336232656464346139356464373034653966613633616531356664323962393065376638643428893f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040f901481e5080c2d72f"
}

We can then take the signed transaction and submit it via /construction/submit:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "signed_transaction": "010ac704696d706f727420466c6f77436f6c6453746f7261676550726f78792066726f6d203078303263316461333833363930356362340a0a7472616e73616374696f6e2873656e6465723a20416464726573732c2072656365697665723a20416464726573732c20616d6f756e743a205546697836342c206e6f6e63653a20496e7436342c207369673a20537472696e6729207b0a20202020707265706172652870617965723a20417574684163636f756e7429207b0a202020207d0a2020202065786563757465207b0a20202020202020202f2f204765742061207265666572656e636520746f207468652073656e646572277320466c6f77436f6c6453746f7261676550726f78792e5661756c742e0a20202020202020206c65742061636374203d206765744163636f756e742873656e646572290a20202020202020206c6574207661756c74203d20616363742e6765744361706162696c69747928466c6f77436f6c6453746f7261676550726f78792e5661756c744361706162696c6974795075626c696350617468292e626f72726f773c26466c6f77436f6c6453746f7261676550726f78792e5661756c743e2829210a0a20202020202020202f2f205472616e7366657220746f6b656e7320746f207468652072656365697665722e0a20202020202020207661756c742e7472616e736665722872656365697665723a2072656365697665722c20616d6f756e743a20616d6f756e742c206e6f6e63653a206e6f6e63652c207369673a207369672e6465636f64654865782829290a202020207d0a7d0a12307b2274797065223a2241646472657373222c2276616c7565223a22307866356435626362336334653062303731227d0a12307b2274797065223a2241646472657373222c2276616c7565223a22307839346236636236336362383131373761227d0a12277b2274797065223a22554669783634222c2276616c7565223a22302e3130303030303030227d0a121d7b2274797065223a22496e743634222c2276616c7565223a2230227d0a129d017b2274797065223a22537472696e67222c2276616c7565223a223765663563366634343534633361326661313530316137616236363039653564663666346433313131636638306131343039306466383666326435373462323132636462333234613364623139346237323337313130333339653362326564643461393564643730346539666136336165313566643239623930653766386434227d0a1a205ed29244aace03b49013da0e763d5c7187ca0cfd7799b6d2d2bae3085935af33208f4e2a0d0a08d99b1eba9b561cfa18893f3208d99b1eba9b561cfa3a08d99b1eba9b561cfa4a4c0a08d99b1eba9b561cfa1a40ed02af244d06778ee1cd69fbc6020f5e75de4b28bafd22c03a64cadde9e8c1ca2301f5da317d4cee5dc1c3dced0b725dd2d512afaec001af8218a69728dc35cb:0a205ed29244aace03b49013da0e763d5c7187ca0cfd7799b6d2d2bae3085935af331a08d99b1eba9b561cfa22c201303230303030303030303030393839363830303030303030303030303030303030303934623663623633636238313137376166356435626362336334653062303731376566356336663434353463336132666131353031613761623636303965356466366634643331313163663830613134303930646638366632643537346232313263646233323461336462313934623732333731313033333965336232656464346139356464373034653966613633616531356664323962393065376638643428893f30013a236163636573732e6465766e65742e6e6f6465732e6f6e666c6f772e6f72673a3930303040f901481e5080c2d72f"
}

This will respond with something like:

{
  "transaction_identifier": {
    "hash": "0f082932185a789a227cac3230c0285f0eecf89d7ed9e79f5272eca9136d422c"
  }
}

We can then view the transaction on the Flow Testnet Explorer:

This tells us that the transaction in block 69490379 has successfully transferred 0.1 FLOW from 0xf5d5bcb3c4e0b071 to 0x94b6cb63cb81177a.

We can check that Flow Rosetta has also seen this with a request to /block:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "block_identifier": {
    "index": 69490379
  }
}

This lets us confirm the transfer operations:

{
  "block": {
    "block_identifier": {
      "hash": "0ee60a9efbcd69264c4c074389188c009e2276e69b19b4889de0fc5ae054a712",
      "index": 69490379
    },
    "parent_block_identifier": {
      "hash": "c09d5484669a56be5e8dc073a84e8add9588e4a11b82facccbc634a35ec0ce8b",
      "index": 69490378
    },
    "timestamp": 1654011053872,
    "transactions": [
      {
        "metadata": {
          "error_message": "",
          "events": [
            {
              "amount": "10000000",
              "receiver": "0x94b6cb63cb81177a",
              "sender": "0xf5d5bcb3c4e0b071",
              "type": "PROXY_WITHDRAWAL"
            },
            {
              "amount": "10000000",
              "sender": "0xf5d5bcb3c4e0b071",
              "type": "WITHDRAWAL"
            },
            {
              "amount": "10000000",
              "receiver": "0x94b6cb63cb81177a",
              "type": "DEPOSIT"
            },
            {
              "amount": "284",
              "sender": "0xd99b1eba9b561cfa",
              "type": "WITHDRAWAL"
            },
            {
              "amount": "284",
              "receiver": "0x912d5440f7e3769e",
              "type": "DEPOSIT"
            }
          ],
          "failed": false
        },
        "operations": [
          {
            "operation_identifier": {
              "index": 0
            },
            "type": "fee",
            "status": "SUCCESS",
            "account": {
              "address": "0xd99b1eba9b561cfa"
            },
            "amount": {
              "value": "-284",
              "currency": {
                "symbol": "FLOW",
                "decimals": 8
              }
            }
          },
          {
            "operation_identifier": {
              "index": 1
            },
            "type": "proxy_transfer",
            "status": "SUCCESS",
            "account": {
              "address": "0xf5d5bcb3c4e0b071"
            },
            "amount": {
              "value": "-10000000",
              "currency": {
                "symbol": "FLOW",
                "decimals": 8
              }
            }
          },
          {
            "operation_identifier": {
              "index": 2
            },
            "related_operations": [
              {
                "index": 1
              }
            ],
            "type": "proxy_transfer",
            "status": "SUCCESS",
            "account": {
              "address": "0x94b6cb63cb81177a"
            },
            "amount": {
              "value": "10000000",
              "currency": {
                "symbol": "FLOW",
                "decimals": 8
              }
            }
          }
        ],
        "transaction_identifier": {
          "hash": "0f082932185a789a227cac3230c0285f0eecf89d7ed9e79f5272eca9136d422c"
        }
      }
    ]
  }
}

We can also make a call to /account/balance for the sender account:

{
  "network_identifier": {
    "blockchain": "flow",
    "network": "testnet"
  },
  "account_identifier": {
    "address": "0xf5d5bcb3c4e0b071"
  }
}

The response confirms that its balance has gone down by 0.1 FLOW exactly:

{
  "balances": [
    {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "40000000"
    }
  ],
  "block_identifier": {
    "hash": "06c752602cec6892571949152444253693cf74a90d220af1bbd0b66729bfa946",
    "index": 69490510
  },
  "metadata": {
    "sequence_number": "1"
  }
}

And a similar /account/balance call for the receiver account 0x94b6cb63cb81177a shows that its balance has gone up by 0.1 FLOW:

{
  "balances": [
    {
      "currency": {
        "decimals": 8,
        "symbol": "FLOW"
      },
      "value": "160099791"
    }
  ],
  "block_identifier": {
    "hash": "779c37006a38e67b5ec299ded2f8f2d4b3dddb22d7d36413ead8ac29735a8c26",
    "index": 69490551
  },
  "metadata": {
    "sequence_number": "1"
  }
}

Tada!