zilliqa 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.circleci/config.yml +30 -0
- data/.gitignore +8 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +50 -0
- data/LICENSE.txt +674 -0
- data/README.md +175 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/zilliqa.rb +21 -0
- data/lib/zilliqa/account/account.rb +34 -0
- data/lib/zilliqa/account/transaction.rb +158 -0
- data/lib/zilliqa/account/transaction_factory.rb +16 -0
- data/lib/zilliqa/account/wallet.rb +146 -0
- data/lib/zilliqa/contract/contract.rb +150 -0
- data/lib/zilliqa/contract/contract_factory.rb +47 -0
- data/lib/zilliqa/crypto/key_store.rb +113 -0
- data/lib/zilliqa/crypto/key_tool.rb +61 -0
- data/lib/zilliqa/crypto/schnorr.rb +147 -0
- data/lib/zilliqa/jsonrpc/provider.rb +31 -0
- data/lib/zilliqa/proto/message.proto +44 -0
- data/lib/zilliqa/proto/message_pb.rb +46 -0
- data/lib/zilliqa/util/bech32.rb +28 -0
- data/lib/zilliqa/util/unit.rb +37 -0
- data/lib/zilliqa/util/util.rb +17 -0
- data/lib/zilliqa/util/validator.rb +41 -0
- data/lib/zilliqa/version.rb +3 -0
- data/zilliqa.gemspec +46 -0
- metadata +204 -0
data/README.md
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
# Zilliqa - Zilliqa Blockchain Ruby Library
|
2
|
+
|
3
|
+
- [Zilliqa API doc](https://apidocs.zilliqa.com/)
|
4
|
+
- The project is still under development.
|
5
|
+
|
6
|
+
|
7
|
+
|
8
|
+
## Table of Contents
|
9
|
+
|
10
|
+
<!-- TOC depthFrom:1 depthTo:6 withLinks:1 orderedList:0 -->
|
11
|
+
|
12
|
+
#### [Requirements](#requirement)
|
13
|
+
#### [Installation](#installation)
|
14
|
+
#### [Zilliqa KeyTool](#zilliqa-keytool)
|
15
|
+
#### [Transaction](#transaction-z)
|
16
|
+
#### [Wallet](#wallet-z)
|
17
|
+
- [Smart Contract](#contract)
|
18
|
+
- [Create](#contract-create)
|
19
|
+
- [Deploy](#contract-deploy)
|
20
|
+
|
21
|
+
<!-- /TOC -->
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
## <a name="requirement"></a>Requirement
|
26
|
+
|
27
|
+
- Ruby(2.5.3)
|
28
|
+
|
29
|
+
|
30
|
+
|
31
|
+
## <a name="installation"></a>Installation
|
32
|
+
|
33
|
+
Add this line to your application's Gemfile:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
gem 'zilliqa'
|
37
|
+
```
|
38
|
+
|
39
|
+
And then execute:
|
40
|
+
|
41
|
+
$ bundle
|
42
|
+
|
43
|
+
Or install it yourself as:
|
44
|
+
|
45
|
+
$ gem install zilliqa
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
## <a name="zilliqa-keytool"></a>Zilliqa KeyTool
|
50
|
+
|
51
|
+
### Generate A new address
|
52
|
+
```ruby
|
53
|
+
private_key = Zilliqa::Crypto::KeyTool.generate_private_key
|
54
|
+
public_key = Zilliqa::Crypto::KeyTool.get_public_key_from_private_key(private_key)
|
55
|
+
address = Zilliqa::Crypto::KeyTool.get_address_from_private_key(private_key)
|
56
|
+
```
|
57
|
+
|
58
|
+
### Validate an address
|
59
|
+
```ruby
|
60
|
+
address = '2624B9EA4B1CD740630F6BF2FEA82AAC0067070B'
|
61
|
+
Zilliqa::Util::Validator.address?(address)
|
62
|
+
```
|
63
|
+
|
64
|
+
### Validate checksum address
|
65
|
+
```ruby
|
66
|
+
checksum_address = '0x4BAF5faDA8e5Db92C3d3242618c5B47133AE003C'
|
67
|
+
Zilliqa::Util::Validator.checksum_address?(checksum_address)
|
68
|
+
```
|
69
|
+
|
70
|
+
|
71
|
+
## <a name="transaction-z"></a>Transaction
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
provider = Zilliqa::Jsonrpc::Provider.new('https://dev-api.zilliqa.com')
|
75
|
+
to_addr = 'zil1lpw9fc8p4tse55r85fa37gscnkxf6xq5ahe8uj'
|
76
|
+
pub_key = '032cfec301c57acc2a4b18f47247687a1ec51e61336a7d5936e455b7dab3ae712e'
|
77
|
+
testnet = 21_823_489
|
78
|
+
|
79
|
+
tx_params = {
|
80
|
+
version: testnet,
|
81
|
+
amount: '0',
|
82
|
+
to_addr: to_addr,
|
83
|
+
gas_price: '1000',
|
84
|
+
gas_limit: 1,
|
85
|
+
sender_pub_key: pub_key
|
86
|
+
}
|
87
|
+
|
88
|
+
wallet = Zilliqa::Account::Wallet.new(provider)
|
89
|
+
transaction = Zilliqa::Account::Transaction.new(tx_params, provider)
|
90
|
+
|
91
|
+
wallet.add_by_private_key(private_key)
|
92
|
+
|
93
|
+
tx = wallet.sign(transaction)
|
94
|
+
tx.submit!
|
95
|
+
```
|
96
|
+
|
97
|
+
## <a name="wallet-z"></a>Wallet
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
provider = Zilliqa::Jsonrpc::Provider.new('https://dev-api.zilliqa.com')
|
101
|
+
wallet = Zilliqa::Account::Wallet.new(provider)
|
102
|
+
wallet.add_by_private_key(private_key)
|
103
|
+
wallet.transfer('zil1lpw9fc8p4tse55r85fa37gscnkxf6xq5ahe8uj', 10 ** 12)
|
104
|
+
```
|
105
|
+
|
106
|
+
```
|
107
|
+
Successfull output
|
108
|
+
{
|
109
|
+
"ContractAddress"=>"1e366b36e5a17dec83c46f19d8d6b43434bd1dbb",
|
110
|
+
"Info"=>"Contract Creation txn, sent to shard",
|
111
|
+
"TranID"=>"411c1108800ac85118fcd9a44568d208276dcbdd5287c99119c69167912f344a"
|
112
|
+
}
|
113
|
+
```
|
114
|
+
|
115
|
+
## <a name="contract"> </a>Smart Contract
|
116
|
+
|
117
|
+
|
118
|
+
### <a name="contract-create"> </a>Create Smart contract
|
119
|
+
```ruby
|
120
|
+
private_key = "e19d05c5452598..."
|
121
|
+
provider = Zilliqa::Jsonrpc::Provider.new('https://dev-api.zilliqa.com')
|
122
|
+
wallet = Zilliqa::Account::Wallet.new(provider)
|
123
|
+
address = wallet.add_by_private_key(private_key)
|
124
|
+
|
125
|
+
factory = Zilliqa::Contract::ContractFactory.new(provider, wallet)
|
126
|
+
|
127
|
+
contract = factory.new_contract(TEST_CONTRACT, [
|
128
|
+
{
|
129
|
+
vname: 'owner',
|
130
|
+
type: 'ByStr20',
|
131
|
+
value: '0x124567890124567890124567890124567890',
|
132
|
+
},
|
133
|
+
],
|
134
|
+
ABI,
|
135
|
+
)
|
136
|
+
```
|
137
|
+
|
138
|
+
### <a name="contract-deploy"> </a>Deploy contract
|
139
|
+
|
140
|
+
##### [How to calculate gas limit for smart contract transaction](https://drive.google.com/file/d/1c0EJXELVe_MxhULPuJgwGvxFGenG7fmK/view?usp=sharing)
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
gas_limit = TEST_CONTRACT.bytes.size + ABI.to_s.bytes.size
|
144
|
+
```
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
gas_price = 10 ** 12 # 1 zil
|
148
|
+
testnet_ver = 21_823_489
|
149
|
+
pub_key = '032cfec301...'
|
150
|
+
|
151
|
+
deploy_params = Zilliqa::Contract::DeployParams.new(nil, testnet_ver, nil, gas_price, gas_limit, pub_key)
|
152
|
+
tx, deployed = contract.deploy(deploy_params)
|
153
|
+
|
154
|
+
assert tx.confirmed?
|
155
|
+
assert deployed.deployed?
|
156
|
+
assert_equal Zilliqa::Contract::CONTRACT_STATUSES[:deployed], deployed.status
|
157
|
+
|
158
|
+
assert /[A-F0-9]+/ =~ contract.address
|
159
|
+
|
160
|
+
# call a deployed contract
|
161
|
+
call_tx = deployed.call(
|
162
|
+
'setHello',
|
163
|
+
[
|
164
|
+
{ vname: 'msg', type: 'String', value: 'Hello World!' },
|
165
|
+
],
|
166
|
+
{
|
167
|
+
version: Zilliqa::Util.pack(8, 8),
|
168
|
+
amount: 0,
|
169
|
+
gasPrice: 1000,
|
170
|
+
gasLimit: 1000
|
171
|
+
})
|
172
|
+
|
173
|
+
|
174
|
+
receipt = call_tx.receipt
|
175
|
+
```
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "zilliqa"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/lib/zilliqa.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "zilliqa/version"
|
2
|
+
require 'zilliqa/crypto/key_tool'
|
3
|
+
require 'zilliqa/crypto/key_store'
|
4
|
+
require 'zilliqa/crypto/schnorr'
|
5
|
+
require 'zilliqa/jsonrpc/provider'
|
6
|
+
require 'zilliqa/account/account'
|
7
|
+
require 'zilliqa/account/wallet'
|
8
|
+
require 'zilliqa/account/transaction_factory'
|
9
|
+
require 'zilliqa/account/transaction'
|
10
|
+
require 'zilliqa/proto/message_pb'
|
11
|
+
require 'zilliqa/contract/contract_factory'
|
12
|
+
require 'zilliqa/contract/contract'
|
13
|
+
require 'zilliqa/util/validator'
|
14
|
+
require 'zilliqa/util/util'
|
15
|
+
require 'zilliqa/util/unit'
|
16
|
+
require 'zilliqa/util/bech32'
|
17
|
+
|
18
|
+
|
19
|
+
module Zilliqa
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Zilliqa
|
2
|
+
module Account
|
3
|
+
class Account
|
4
|
+
attr_reader :private_key, :public_key, :address
|
5
|
+
def initialize(private_key)
|
6
|
+
@private_key = private_key
|
7
|
+
@public_key = Zilliqa::Crypto::KeyTool.get_public_key_from_private_key(private_key, true)
|
8
|
+
@address = Zilliqa::Crypto::KeyTool.get_address_from_public_key(@public_key)
|
9
|
+
end
|
10
|
+
|
11
|
+
# Takes a JSON-encoded keystore and passphrase, returning a fully
|
12
|
+
# instantiated Account instance.
|
13
|
+
def self.from_file(file, passphrase)
|
14
|
+
key_store = Zilliqa::Crypto::KeyStore.new
|
15
|
+
private_key = key_store.decrypt_private_key(file, passphrase)
|
16
|
+
Account.new(private_key)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Convert an Account instance to a JSON-encoded keystore.
|
20
|
+
def to_file(passphrase, type)
|
21
|
+
key_store = Zilliqa::Crypto::KeyStore.new
|
22
|
+
json = key_store.encrypt_private_key(@private_key, passphrase, type);
|
23
|
+
end
|
24
|
+
|
25
|
+
# sign the passed in transaction with the account's private and public key
|
26
|
+
def sign_transaction(tx)
|
27
|
+
message = tx.bytes
|
28
|
+
message_hex = Util.encode_hex(message)
|
29
|
+
|
30
|
+
Zilliqa::Crypto::Schnorr.sign(message_hex, @private_key, @public_key)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Zilliqa
|
4
|
+
module Account
|
5
|
+
#
|
6
|
+
# Transaction
|
7
|
+
#
|
8
|
+
# Transaction is a functor. Its purpose is to encode the possible states a
|
9
|
+
# Transaction can be in: Confirmed, Rejected, Pending, or Initialised (i.e., not broadcasted).
|
10
|
+
class Transaction
|
11
|
+
class StandardLengthError < StandardError
|
12
|
+
def initialize(msg = 'standard length exceeded for value')
|
13
|
+
super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class TrackTxError < StandardError; end
|
18
|
+
class TransactionError < JSONRPC::Error::ServerError; end
|
19
|
+
|
20
|
+
ATTRIBUTES = %i[id version nonce amount gas_price gas_limit signature receipt sender_pub_key to_addr code data to_ds].freeze
|
21
|
+
attr_accessor(*ATTRIBUTES)
|
22
|
+
attr_accessor :provider, :status
|
23
|
+
|
24
|
+
GET_TX_ATTEMPTS = 33
|
25
|
+
MAX_BIGINT_BYTES = 2**128 - 1
|
26
|
+
TX_STATUSES = {
|
27
|
+
initialized: 0,
|
28
|
+
pending: 1,
|
29
|
+
confirmed: 2,
|
30
|
+
rejected: 3
|
31
|
+
}.freeze
|
32
|
+
|
33
|
+
def initialize(tx_params, provider, status = TX_STATUSES[:initialized], to_ds = false)
|
34
|
+
unless tx_params.nil?
|
35
|
+
tx_params.each do |key, value|
|
36
|
+
next unless ATTRIBUTES.include?(key)
|
37
|
+
instance_variable_set("@#{key}", value)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
@provider = provider
|
42
|
+
@status = status
|
43
|
+
@to_ds = to_ds
|
44
|
+
end
|
45
|
+
|
46
|
+
# constructs an already-confirmed transaction.
|
47
|
+
def self.confirm(tx_params, provider)
|
48
|
+
Transaction.new(tx_params, provider, TX_STATUSES[:confirmed])
|
49
|
+
end
|
50
|
+
|
51
|
+
# constructs an already-rejected transaction.
|
52
|
+
def self.reject(tx_params, provider)
|
53
|
+
Transaction.new(tx_params, provider, TX_STATUSES[:rejected])
|
54
|
+
end
|
55
|
+
|
56
|
+
def bytes
|
57
|
+
protocol = Zilliqa::Proto::ProtoTransactionCoreInfo.new
|
58
|
+
protocol.version = version.to_i
|
59
|
+
protocol.nonce = nonce.to_i
|
60
|
+
protocol.toaddr = Util.decode_hex(Wallet.to_checksum_address(to_addr).downcase.sub('0x', ''))
|
61
|
+
protocol.senderpubkey = Zilliqa::Proto::ByteArray.new(data: Util.decode_hex(sender_pub_key))
|
62
|
+
|
63
|
+
raise StandardLengthError if amount.to_i > MAX_BIGINT_BYTES
|
64
|
+
|
65
|
+
protocol.amount = Zilliqa::Proto::ByteArray.new(data: bigint_to_bytes(amount.to_i))
|
66
|
+
protocol.gasprice = Zilliqa::Proto::ByteArray.new(data: bigint_to_bytes(gas_price.to_i))
|
67
|
+
protocol.gaslimit = gas_limit.to_i
|
68
|
+
protocol.code = code if code
|
69
|
+
protocol.data = data if data
|
70
|
+
|
71
|
+
Zilliqa::Proto::ProtoTransactionCoreInfo.encode(protocol)
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_payload
|
75
|
+
{
|
76
|
+
version: version.to_i,
|
77
|
+
nonce: nonce.to_i,
|
78
|
+
toAddr: Wallet.to_checksum_address(to_addr),
|
79
|
+
amount: amount.to_s,
|
80
|
+
pubKey: sender_pub_key,
|
81
|
+
gasPrice: gas_price.to_s,
|
82
|
+
gasLimit: gas_limit.to_i,
|
83
|
+
code: code,
|
84
|
+
data: data,
|
85
|
+
signature: signature
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def pending?
|
90
|
+
@status == TX_STATUSES[:pending]
|
91
|
+
end
|
92
|
+
|
93
|
+
def initialised?
|
94
|
+
@status == TX_STATUSES[:initialized]
|
95
|
+
end
|
96
|
+
|
97
|
+
def confirmed?
|
98
|
+
@status == TX_STATUSES[:confirmed]
|
99
|
+
end
|
100
|
+
|
101
|
+
def rejected?
|
102
|
+
@status == TX_STATUSES[:rejected]
|
103
|
+
end
|
104
|
+
|
105
|
+
# This sets the Transaction instance to a state
|
106
|
+
# of pending. Calling this function kicks off a passive loop that polls the
|
107
|
+
# lookup node for confirmation on the txHash.
|
108
|
+
#
|
109
|
+
# The polls are performed with a linear backoff:
|
110
|
+
#
|
111
|
+
# This is a low-level method that you should generally not have to use
|
112
|
+
# directly.
|
113
|
+
def confirm(tx_hash, max_attempts = GET_TX_ATTEMPTS, interval = 1)
|
114
|
+
@status = TX_STATUSES[:pending]
|
115
|
+
1.upto(max_attempts) do
|
116
|
+
return self if track_tx(tx_hash)
|
117
|
+
|
118
|
+
sleep(interval)
|
119
|
+
end
|
120
|
+
|
121
|
+
self.status = TX_STATUSES[:rejected]
|
122
|
+
throw 'The transaction is still not confirmed after ${maxAttempts} attempts.'
|
123
|
+
end
|
124
|
+
|
125
|
+
def track_tx(tx_hash)
|
126
|
+
begin
|
127
|
+
response = @provider.GetTransaction(tx_hash)
|
128
|
+
rescue TrackTxError
|
129
|
+
end
|
130
|
+
|
131
|
+
if response['error']
|
132
|
+
return false
|
133
|
+
end
|
134
|
+
|
135
|
+
self.id = response['result']['ID']
|
136
|
+
self.receipt = response['result']['receipt']
|
137
|
+
receipt['cumulative_gas'] = response['result']['receipt']['cumulative_gas'].to_i
|
138
|
+
self.status = receipt && receipt['success'] ? TX_STATUSES[:confirmed] : TX_STATUSES[:rejected]
|
139
|
+
|
140
|
+
true
|
141
|
+
end
|
142
|
+
|
143
|
+
def submit!
|
144
|
+
provider.CreateTransaction(to_payload)
|
145
|
+
rescue TransactionError => e
|
146
|
+
{ error: e }
|
147
|
+
end
|
148
|
+
|
149
|
+
private
|
150
|
+
|
151
|
+
def bigint_to_bytes(value)
|
152
|
+
raise StandardLengthError if value > MAX_BIGINT_BYTES
|
153
|
+
|
154
|
+
bs = [value / (2**64), value % (2**64)].pack('Q>*')
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Zilliqa
|
2
|
+
module Account
|
3
|
+
class TransactionFactory
|
4
|
+
attr_reader :provider, :signer
|
5
|
+
|
6
|
+
def initialize(provider, signer)
|
7
|
+
@provider = provider
|
8
|
+
@signer = signer
|
9
|
+
end
|
10
|
+
|
11
|
+
def new(tx_params, to_ds = false)
|
12
|
+
Transaction.new(tx_params, @provider, Zilliqa::Account::Transaction::TX_STATUSES[:initialized], to_ds)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|