forge 的一个重要设计方面是使新手尽可能容易地熟悉 Forge。如果您按照概览的“快速入门”部分操作,应该已经使用了其中很多:forge cli、forge 网页和 forge 模拟器。现在,您应该可以发送第一笔交易了,但在此之前,您需要一个钱包。

关于钱包

如果您熟悉区块链技术,可跳过下个部分。

在区块链世界,钱包其实是一个地址,而地址是被通过公共密钥加密算法生成的密钥对支持。如果您用过比特币或以太坊,可能已经知道ECDSA(椭圆曲线数字签名算法)了——ECDSA 是 DSA(数字签名算法)的一个变体,它使用椭圆曲线密码学。基本来说,如果您拥有一个钱包,您便会拥有密钥,密钥可生成其公共密钥,而公共密钥可被用于生成钱包地址。如果您想从钱包发送交易,需要生成交易数据并用自己的密钥签名。因为您的公共密钥包含在交易内,其他任何人都可以用它验证交易是否真正由您签署。这是区块链技术“信任”的基础。因为每个人都可以验证交易的真实性,它也被称为“公共可验证”。

比特币和以太坊使用 secp256k1 对其钱包进行公共密钥加密,为了生成钱包地址,他们使用固定的哈希算法。在 forge,我们在这方面灵活并可扩展——开发者或用户可选择其最喜欢的算法组合。例如,默认情况下,forge 的公共密钥加密使用 ED25519,公共密钥哈希使用 Sha3,地址使用 base58(实际上是带multibase前缀的 base58)。

创建一个钱包

通过 Forge CLI 创建钱包

您可以通过使用 forge CLI 轻松创建钱包。请注意,在通常情况下,您不应用它创建一个存储贵重资产的钱包,除非您拥有当前的节点且节点位于安全环境。在这个例子中,因为我们在本地运行,便如下所示:

$ forge account:create
? Please input passphrase: helloworld
? Please input moniker: tyrchen
? Please select a account role type? ROLE_ACCOUNT
? Please select a key pair algorithm? ED25519
? Please select a hash algorithm? SHA3
──────────────
✔ account create success!
──────────────
{
  type: {
    pk: 'ED25519',
    hash: 'SHA3',
    address: 'BASE58',
    role: 'ROLE_ACCOUNT'
  },
  sk: '',
  pk: 'waBx5ZSTSO5DOQmvUfKCBkjJFpybm1+Zxk2dO/XgVYU=',
  address: 'z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB'
}
──────────────
✔ account unlocked!
ℹ run forge account z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB to inspect account state

通过您的密码加密(AES256),这个 CLI 会创建一个钱包并将其保存为密钥库(钱包位于:$FORGE_HOME/core/keystore/z1f/rPQRqZbW8wELhAPh1nBMV18c7j1FocbB.key1)。在 forge CLI 中创建钱包后,该钱包会被解锁,即您可以在一段时间内用其发出交易。

1:请注意,在运行上述 CLI 后,您创建的钱包地址会与这个不同。所以,密钥库文件不同。

通过 forge 控制台创建钱包

还记得在运行forge start时,它提出命令连接至 forge 远程控制台吗?

ℹ If you want to access interactive console, please run /Users/tchen/.forge_cli/release/forge/0.18.2/bin/forge remote_console

如果您想自己操作,可以连接至控制台,然后创建您的钱包,包含在forge start上提示的命令:

$ ~/.forge_cli/release/forge/0.18.2/bin/forge remote_console
Erlang/OTP 21 [erts-10.2.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]

Interactive Elixir (1.8.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(forge@127.0.0.1)1> ForgeSdk.create_wallet(moniker: "cynthia", passphrase: "helloworld")
{%ForgeAbi.WalletInfo{
   address: "z115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis",
   pk: <<250, 96, 58, 155, 134, 18, 94, 46, 31, 170, 134, 4, 54, 129, 30, 147,
     120, 139, 208, 168, 87, 140, 12, 83, 110, 179, 248, 141, 69, 90, 9, 193>>,
   sk: "",
   type: %ForgeAbi.WalletType{address: 0, hash: 0, pk: 0, role: 0}
 }, "9981b5debd415bf85e94f8136a026d89"}

因为 Forge 的构建包含 erlang/elixir,这是 erlang 的默认控制台,即您可以在其中运行多种类型的 erlang/elixir 代码。 ForgeSdk.create_wallet(moniker: "cynthia", passphrase: "helloworld")是我们创建钱包时执行的函数。钱包创建后,返回的钱包里没有密钥(sk),而是有一个凭证。之后您可以通过这个凭证来解锁钱包并签署交易。

如需退出 forge 控制台,请按下"CTRL+c"两次。

现在,如果您查看密钥库文件夹,会看到两个密钥库:

$ tree ~/.forge_release/core/keystore/
/Users/tchen/.forge_release/core/keystore/
├── z11
│   └── 5hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis.key
└── z1f
    └── rPQRqZbW8wELhAPh1nBMV18c7j1FocbB.key

尽管我们使用相同的名字和密码,每次创建钱包时,都会生成一个新的密钥对,和一个新的钱包(地址)。

通过 Wallet 应用程序创建钱包

对普通大众来说,创建钱包最安全的方法便是使用钱包应用程序——一个 forge 兼容的钱包应用程序。目前,您可以使用 ArcBlock 钱包应用程序进行操作。

关于钱包的更多信息

我们使用第一个钱包地址 z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB 来看看我们在 forge 区块搜索器中可以找到什么。将地址粘贴(在这里请粘贴您通过 Forge CLI 创建的地址)到搜索器的搜索栏,然后点击回车键:

Search wallet

然后您会看到:

Search result

我们只是通过 Forge CLI 创建了一个钱包,为什么已经有已发出的交易?

您看到的交易是声明账户交易,如果您点击交易哈希,会看到:

Declare Account transaction

在 forge,若不声明 tx,则钱包不能被使用。TX 的目的有几个:

  • 为地址创建默认状态
  • 在链上登记地址/钱包类型/公共密钥

在比特币/以太坊,其他人可在没有任何前提条件的情况下向您的钱包发送硬币/代币。您可以有一个只接收代币的钱包,尽管该钱包的状态不存在2。在 Forge,因为它的主要目的是允许用户创建丰富的应用程序,提前将钱包状态放入链可解锁并简化很多用例。

2:事实上,比特币是一个 UTXO 系统,它没有钱包状态;对于以太坊,如果钱包曾经发出过交易,则有钱包状态。

发出交易

现在,您有一个钱包,发出交易就很简单了。我们还是会使用钱包z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB 发出转移 tx。接收者是我们在 forge 控制台创建的那一个:z115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis。在发出交易前,您可以需要看看声明 tx 创建的钱包账户的状态。您可以使用 forge cli 这样做:

$ forge account z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB
{
  balance: '10000 TOKEN',
  nonce: 2,
  numTxs: 1,
  address: 'z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB',
  pk: 'waBx5ZSTSO5DOQmvUfKCBkjJFpybm1+Zxk2dO/XgVYU=',
  type: {
    pk: 'ED25519',
    hash: 'SHA3',
    address: 'BASE58',
    role: 'ROLE_ACCOUNT'
  },
  moniker: 'tyrchen',
  context: {
    genesisTx: 'D6377BF8D115649BAF2312F9056D43E5820533B067B84F8FE2AD4810024D63E2',
    renaissanceTx: 'D6377BF8D115649BAF2312F9056D43E5820533B067B84F8FE2AD4810024D63E2',
    genesisTime: '2019-03-13T18:49:54.707Z',
    renaissanceTime: '2019-03-13T18:49:54.707Z'
  },
  issuer: '',
  migratedTo: [],
  migratedFrom: [],
  numAssets: 0,
  stake: {
    totalStakes: '0',
    totalUnstakes: '0',
    totalReceivedStakes: '0',
    recentStakes: {
      items: [],
      typeUrl: 'fg:x:address',
      maxItems: 128,
      circular: true,
      fifo: false
    },
    recentReceivedStakes: {
      items: [],
      typeUrl: 'fg:x:address',
      maxItems: 128,
      circular: true,
      fifo: false
    }
  }
}

 钱包创建的账户状态非常大。目前,请不要担心您不理解的数据。为了开发方便,所有新声明的钱包的状态中都会有 10,000 个代币。我们可以将其用户互相转移代币。

通过 forge CLI 转移

我们可以使用forge tx:send CLI 发出交易。如果您选择发送 tx 的钱包未被解锁,Forge CLI 会提醒您输入密码,以便其解锁钱包并使用密钥签署交易:

$ forge tx:send
? Use cached wallet <z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB>? Yes
? Please enter passphrase of the wallet: helloworld
? Select transaction type you want to send: TransferTx
? Please enter the itx data object (js supported): Received
──────────────
{
  to: 'z115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis',
  value: '1000',
  assets: [],
  data: null
}
──────────────
✔ tx send success! EAF6A091136A7D95AFA704993F43CA175844EFFAA8908A7A2F8F5BF1EE08F4A4

请注意,当您在Please enter the itx data object后点击回车键时,会弹出一个含默认值的编辑器,您可以复制粘贴上面显示的 json 数据:

{
  "to": "z115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis",
  "value": "1000",
  "assets": [],
  "data": null
}

这个TransferTx会将 1000 单位发送至z115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis。请注意,默认情况下,$1 个代币 = 10^{16}单位$,当我们转移时,最小转移单位是 1 单位。

我们看看发送到链上的整个 tx:

$ forge tx EAF6A091136A7D95AFA704993F43CA175844EFFAA8908A7A2F8F5BF1EE08F4A4
{
  from: 'z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB',
  nonce: 3,
  signature: 'OjxrrpPr+6Xx76TTzX+u7m73Dvci8gWyhCqG58v8utjNceqSnEhN6hywrnOs6uk0XBwPUfqWdfzhPxK1D9uSCQ==',
  chainId: 'forge',
  signatures: [],
  itx: {
    type: 'TransferTx',
    value: {
      to: 'z115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis',
      value: '1000',
      assets: []
    }
  }
}

Forge CLI 会为交易为您填写几个值:

  • from:发出者地址。
  • nounce:此 tx 的随机值。
  • signature:发出者此 tx 的签名。
  • chain_id:这个 tx 所属的链。如果您没有改变forge_release.toml,默认链 id 是forge

在链上执行 tx 后(您可能需要等待 5 秒),您可以得到两个地址的更新账户地址z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbBz115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis

$ forge account z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB
{
  balance: '9999.9999999999999 TOKEN',
  nonce: 3,
  numTxs: 2,
  ...
}

发出者的余额减少 100 单位,接收者的余额则增加 100 单位。

$ forge account z115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis
{
  balance: '10000.0000000000001 TOKEN',
  nonce: 2,
  numTxs: 2,
  ...
}

祝贺您!您以发出自己的第一笔转移交易了!如果您想知道魔法是怎么发生的,请阅读Forge 核心的交易章节

通过 Forge 控制台转移(可选)

这个部分为勇敢或 elixir/erlang 用户提供。如果您知道 elixir/erlang 或不怕进行有点奇怪的操作,那就请继续读下去。

现在,再次输入 forge 控制台(~/.forge_cli/release/forge/0.18.2/bin/forge remote_console)。

首先,让我们创建一个TransferTx,我们想给z115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis 100 个代币,所以我们将地址放在to,值放在ForgeAbi.token_to_arc(100)

iex(forge@127.0.0.1)1> itx = ForgeAbi.TransferTx.new(to: "z115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis", value: ForgeAbi.token_to_arc(100))
%ForgeAbi.TransferTx{
  assets: [],
  data: nil,
  to: "z115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis",
  value: %ForgeAbi.BigUint{value: <<13, 224, 182, 179, 167, 100, 0, 0>>}
}

您可能在此看到BigUint,它的值很奇怪。我们之前提到,默认情况下,$1 个代币 = 10^{16}单位$,我们转移时,最低的转移值为 1 单位。因此,如果我们想转移 100 个代币,即为$100*10^{16}$。这个数字很大。因此,我们需要一个方式,在所有语言间解码大整数(尽管 elixir、python 之类的语言支持任何数位的大数字,但很多语言只支持固定大小的整数)。如需有效编码大数据(字符串格式是最低效的方式),我们用其最小的二进制格式。如需了解BigUint的更多信息,请参考:Forge 中的大整数处理

在我们发出交易前,和在 Forge CLI 中的操作一样,需要解锁钱包:

iex(forge@127.0.0.1)2> {w, t} = ForgeSdk.load_wallet(address: "z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB", passphrase: "helloworld")
{%ForgeAbi.WalletInfo{
   address: "z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB",
   pk: <<193, 160, 113, 229, 148, 147, 72, 238, 67, 57, 9, 175, 81, 242, 130, 6,
     72, 201, 22, 156, 155, 155, 95, 153, 198, 77, 157, 59, 245, 224, 85, 133>>,
   sk: "",
   type: %ForgeAbi.WalletType{address: 1, hash: 1, pk: 0, role: 0}
 }, "bedd6df719d6e142eef856f88fd7c847"}

在此,我们有钱包数据和凭证,可用其告诉 Forge 加载密钥,以签署 tx 并发出。ForgeSdk.transfer是一个辅助函数,会为您提供以下服务:

  • TransferTx包裹在Transaction
  • 用钱包和凭证签署交易。
  • 将交易发出到链。
iex(forge@127.0.0.1)3> ForgeSdk.transfer(itx, wallet: w, token: t)
"9C2CD01B5EA739AEB430F300757DA74AB5D17F2F660DD090BD0BB80CA4D6F95C"

您可以使用ForgeSdk.get_tx找回交易单据:

iex(forge@127.0.0.1)4> ForgeSdk.get_tx(hash: "9C2CD01B5EA739AEB430F300757DA74AB5D17F2F660DD090BD0BB80CA4D6F95C")
%ForgeAbi.TransactionInfo{
  code: 0,
  extra_meta: nil,
  hash: "9C2CD01B5EA739AEB430F300757DA74AB5D17F2F660DD090BD0BB80CA4D6F95C",
  height: 118,
  index: 0,
  tags: [],
  time: %Google.Protobuf.Timestamp{nanos: 348370000, seconds: 1552503566},
  tx: %ForgeAbi.Transaction{
    chain_id: "forge",
    from: "z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB",
    itx: %Google.Protobuf.Any{
      type_url: "fg:t:transfer",
      value: <<10, 36, 122, 49, 49, 53, 104, 74, 116, 110, 114, 89, 103, 121,
        70, 84, 101, 112, 88, 81, 98, 66, 69, 87, 72, 82, 80, 98, 49, 90, 80,
        121, 100, 77, 118, 107, 105, ...>>
    },
    nonce: 2,
    signature: <<153, 85, 120, 117, 178, 235, 91, 29, 57, 81, 114, 67, 14, 163,
      250, 131, 82, 250, 161, 75, 244, 20, 240, 169, 66, 228, 135, 242, 3, 179,
      149, 14, 240, 25, 89, 185, 209, ...>>,
    signatures: []
  }
}

请注意,code在此为 0,即 tx 成功执行。不是 0 的代码意味着 tx 执行失败。如需了解状态代码的更多信息,请查看:Forge 状态代码

现在,我们看看余额的格式:

iex(forge@127.0.0.1)5> ForgeSdk.get_account_state(address: "z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB")
%ForgeAbi.AccountState{
  address: "z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB",
  balance: %ForgeAbi.BigUint{value: <<5, 93, 230, 167, 121, 187, 171, 252, 24>>},
  ...
}

这个读起来比较难,因此我们提供了一个ForgeSdk.display,显示更加人类可读的结果:

iex(forge@127.0.0.1)6> ForgeSdk.get_account_state(address: "z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB") |> ForgeSdk.display()
%{
  address: "z1frPQRqZbW8wELhAPh1nBMV18c7j1FocbB",
  balance: 98999999999999999000,
  ...
}

我们可以看到,余额进一步减少了 100 个代币($100*10^{16}$)。当然,接收者的余额增加了 100 个代币:

iex(forge@127.0.0.1)7> ForgeSdk.get_account_state(address: "z115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis") |> ForgeSdk.display()
%{
  address: "z115hJtnrYgyFTepXQbBEWHRPb1ZPydMvkis",
  balance: 101000000000000001000,
  ...
}

希望您看得懂我在说什么。如果您没有完全理解一切,也不必担心。再次说明,您可以随意访问Forge 核心交易章节获取详情。