glueby 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +35 -0
- data/Gemfile +1 -0
- data/README.md +111 -6
- data/glueby.gemspec +1 -1
- data/lib/generators/glueby/contract/reissuable_token_generator.rb +26 -0
- data/lib/generators/glueby/contract/templates/key_table.rb.erb +3 -3
- data/lib/generators/glueby/contract/templates/reissuable_token_table.rb.erb +10 -0
- data/lib/generators/glueby/contract/templates/system_information_table.rb.erb +2 -2
- data/lib/generators/glueby/contract/templates/timestamp_table.rb.erb +1 -1
- data/lib/generators/glueby/contract/templates/utxo_table.rb.erb +2 -2
- data/lib/generators/glueby/contract/templates/wallet_table.rb.erb +2 -2
- data/lib/glueby.rb +25 -0
- data/lib/glueby/configuration.rb +62 -0
- data/lib/glueby/contract.rb +2 -2
- data/lib/glueby/contract/active_record.rb +1 -0
- data/lib/glueby/contract/active_record/reissuable_token.rb +26 -0
- data/lib/glueby/contract/fee_estimator.rb +38 -0
- data/lib/glueby/contract/payment.rb +4 -4
- data/lib/glueby/contract/timestamp.rb +6 -6
- data/lib/glueby/contract/token.rb +69 -22
- data/lib/glueby/contract/tx_builder.rb +22 -19
- data/lib/glueby/fee_provider.rb +73 -0
- data/lib/glueby/fee_provider/tasks.rb +136 -0
- data/lib/glueby/generator/migrate_generator.rb +1 -1
- data/lib/glueby/internal/wallet.rb +28 -4
- data/lib/glueby/internal/wallet/abstract_wallet_adapter.rb +18 -3
- data/lib/glueby/internal/wallet/active_record/wallet.rb +15 -5
- data/lib/glueby/internal/wallet/active_record_wallet_adapter.rb +15 -5
- data/lib/glueby/internal/wallet/errors.rb +3 -0
- data/lib/glueby/internal/wallet/tapyrus_core_wallet_adapter.rb +36 -11
- data/lib/glueby/version.rb +1 -1
- data/lib/glueby/wallet.rb +3 -2
- data/lib/tasks/glueby/contract/timestamp.rake +1 -1
- data/lib/tasks/glueby/fee_provider.rake +13 -0
- metadata +16 -9
- data/.travis.yml +0 -7
- data/lib/glueby/contract/fee_provider.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 572e6f36cf1b1df86b93fcd264ce1a537769588a43cbe8bac5ad3d6d9df21f44
|
4
|
+
data.tar.gz: 4516f08781b324f76846057cfe4722552bc413e7f9518d68b038c7bc16f03f79
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b04613b40d622b24e14fb45440ea974905daeea6726c4a44c9e458eabe9ea552e1b8a5ad581a7ea19ab8824216f001b95c49101fdefaf2bc424a39c2fdf23d07
|
7
|
+
data.tar.gz: 1e6a50bf51dd39249c5b2746e27831ca2b63da7bb02090ba2f04b978bfa172dc0372885774986cbbf94f07ccca11d82194b1d104c467d232e0916adab50c2308
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: Ruby
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [master]
|
13
|
+
pull_request:
|
14
|
+
branches: [master]
|
15
|
+
|
16
|
+
jobs:
|
17
|
+
test:
|
18
|
+
runs-on: ubuntu-latest
|
19
|
+
strategy:
|
20
|
+
matrix:
|
21
|
+
ruby-version: ["2.6", "2.7", "3.0"]
|
22
|
+
|
23
|
+
steps:
|
24
|
+
- run: docker pull tapyrus/tapyrusd:edge
|
25
|
+
- uses: actions/checkout@v2
|
26
|
+
- name: Set up Ruby
|
27
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
28
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
29
|
+
# uses: ruby/setup-ruby@v1
|
30
|
+
uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
|
31
|
+
with:
|
32
|
+
ruby-version: ${{ matrix.ruby-version }}
|
33
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
34
|
+
- name: Run tests
|
35
|
+
run: bundle exec rake
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
# Glueby [![
|
1
|
+
# Glueby [![Ruby](https://github.com/chaintope/glueby/actions/workflows/ruby.yml/badge.svg)](https://github.com/chaintope/glueby/actions/workflows/ruby.yml) [![Gem Version](https://badge.fury.io/rb/glueby.svg)](https://badge.fury.io/rb/glueby) [![MIT License](http://img.shields.io/badge/license-MIT-blue.svg?style=flat)](LICENSE)
|
2
|
+
|
2
3
|
|
3
4
|
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/glueby`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
5
|
|
@@ -30,8 +31,10 @@ Glueby has below features.
|
|
30
31
|
|
31
32
|
```ruby
|
32
33
|
|
33
|
-
|
34
|
-
|
34
|
+
Glurby.configure do |config|
|
35
|
+
config.wallet_adapter = :activerecord
|
36
|
+
config.rpc_config = { schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass' }
|
37
|
+
end
|
35
38
|
|
36
39
|
wallet = Glueby::Wallet.create
|
37
40
|
timestamp = Glueby::Contract::Timestamp.new(wallet: wallet, content: "\x01\x02\x03")
|
@@ -110,10 +113,12 @@ bin/rails glueby:contract:install
|
|
110
113
|
|
111
114
|
Install task creates a file `glueby.rb` in `config/initializers` directory like this.
|
112
115
|
|
113
|
-
```
|
116
|
+
```ruby
|
114
117
|
# Edit configuration for connection to tapyrus core
|
115
|
-
|
116
|
-
|
118
|
+
Glueby.configure do |config|
|
119
|
+
config.wallet_adapter = :activerecord
|
120
|
+
config.rpc_config = { schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass' }
|
121
|
+
end
|
117
122
|
```
|
118
123
|
|
119
124
|
If you use timestamp feature, use `glueby:contract:timestamp` generator.
|
@@ -157,6 +162,106 @@ bin/rails glueby:contract:timestamp:confirm
|
|
157
162
|
confirmed (id=1, txid=8d602ca8ebdd50fa70b5ee6bc6351965b614d0a4843adacf9f43fedd7112fbf4)
|
158
163
|
```
|
159
164
|
|
165
|
+
## Use fee provider mode
|
166
|
+
|
167
|
+
Glueby contracts have two different way of fee provisions.
|
168
|
+
|
169
|
+
1. `:sender_pays_itself`
|
170
|
+
2. `:fee_provider_bears`
|
171
|
+
|
172
|
+
The first one: `:sender_pays_itself`, is the default behavior.
|
173
|
+
In the second Fee Provider mode, the Fee Provider module pays a fee instead of the transaction's sender.
|
174
|
+
|
175
|
+
### Fee Provider Specification
|
176
|
+
|
177
|
+
* Fee Provider pays fixed amount fee, and it is configurable.
|
178
|
+
* Fee Provider needs to have enough funds into their wallet.
|
179
|
+
* Fee Provider is managed to keep some number of UTXOs that have fixed fee value by rake tasks.
|
180
|
+
|
181
|
+
### Setting up Fee Provider
|
182
|
+
|
183
|
+
1. Set like below
|
184
|
+
|
185
|
+
```ruby
|
186
|
+
Glurby.configure do |config|
|
187
|
+
# Use FeeProvider to supply inputs for fees on each transaction that is created on Glueby.
|
188
|
+
config.fee_provider_bears!
|
189
|
+
config.fee_provider_config = {
|
190
|
+
# The fee that Fee Provider pays on each transaction.
|
191
|
+
fixed_fee: 1000,
|
192
|
+
# Fee Provider tries to keep the number of utxo in utxo pool as this size using `glueby:fee_provider:manage_utxo_pool` rake task
|
193
|
+
utxo_pool_size: 20
|
194
|
+
}
|
195
|
+
end
|
196
|
+
```
|
197
|
+
|
198
|
+
2. Deposit TPC into Fee Provider's wallet
|
199
|
+
|
200
|
+
Get an address from the wallet.
|
201
|
+
|
202
|
+
```
|
203
|
+
$ bundle exec rake glueby:fee_provider:address
|
204
|
+
mqYTLdLCUCCZkTkcpbVx1GqpvV1gK4euRD
|
205
|
+
```
|
206
|
+
|
207
|
+
Send TPC to the address.
|
208
|
+
|
209
|
+
If you use `Glueby::Contract::Payment` to the sending, you can do like this:
|
210
|
+
|
211
|
+
```ruby
|
212
|
+
Glueby::Contract::Payment.transfer(sender: sender, receiver_address: 'mqYTLdLCUCCZkTkcpbVx1GqpvV1gK4euRD', amount: 1_000_000)
|
213
|
+
```
|
214
|
+
|
215
|
+
3. Manage UTXO pool
|
216
|
+
|
217
|
+
The Fee Provider's wallet has to keep some UTXOs with `fixed_fee` amount for paying fees using `manage_utxo_pool` rake task below.
|
218
|
+
This rake task tries to split UTOXs up to `utxo_pool_size`. If the pool has more than `utxo_pool_size` UTXOs, it does nothing.
|
219
|
+
|
220
|
+
```
|
221
|
+
$ bundle exec rake glueby:fee_provider:manage_utxo_pool
|
222
|
+
Status: Ready
|
223
|
+
TPC amount: 999_000
|
224
|
+
UTXO pool size: 20
|
225
|
+
|
226
|
+
Configuration:
|
227
|
+
fixed_fee = 1_000
|
228
|
+
utxo_pool_size = 20
|
229
|
+
```
|
230
|
+
|
231
|
+
This shows that the UTXO pool has 20 UTXOs with `fixed_fee` amount for paying fees and has other UTXOs that never use for paying fees.
|
232
|
+
The sum of all the UTXOs that includes both kinds of UTXO is 999_000 tapyrus.
|
233
|
+
|
234
|
+
If the wallet doesn't have enough amount, the rake task shows an error like:
|
235
|
+
|
236
|
+
```
|
237
|
+
$ bundle exec rake glueby:fee_provider:manage_utxo_pool
|
238
|
+
Status: Insufficient Amount
|
239
|
+
TPC amount: 15_000
|
240
|
+
UTXO pool size: 15
|
241
|
+
|
242
|
+
1. Please replenishment TPC which is for paying fee to FeeProvider.
|
243
|
+
FeeProvider needs 21000 tapyrus at least for paying 20 transaction fees.
|
244
|
+
FeeProvider wallet's address is '1DBgMCNBdjQ1Ntz1vpwx2HMYJmc9kw88iT'
|
245
|
+
2. Then create UTXOs for paying in UTXO pool with 'rake glueby:fee_provider:manage_utxo_pool'
|
246
|
+
|
247
|
+
Configuration:
|
248
|
+
fixed_fee = 1_000
|
249
|
+
utxo_pool_size = 20
|
250
|
+
```
|
251
|
+
|
252
|
+
If you want to get the status information, you can use the `status` task.
|
253
|
+
|
254
|
+
```
|
255
|
+
$ bundle exec rake glueby:fee_provider:status
|
256
|
+
Status: Ready
|
257
|
+
TPC amount: 999_000
|
258
|
+
UTXO pool size: 20
|
259
|
+
|
260
|
+
Configuration:
|
261
|
+
fixed_fee = 1_000
|
262
|
+
utxo_pool_size = 20
|
263
|
+
```
|
264
|
+
|
160
265
|
## Development
|
161
266
|
|
162
267
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/glueby.gemspec
CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
27
|
spec.require_paths = ["lib"]
|
28
28
|
|
29
|
-
spec.add_runtime_dependency 'tapyrus', '>= 0.2.
|
29
|
+
spec.add_runtime_dependency 'tapyrus', '>= 0.2.9'
|
30
30
|
spec.add_development_dependency 'activerecord'
|
31
31
|
spec.add_development_dependency 'sqlite3'
|
32
32
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Glueby
|
2
|
+
module Contract
|
3
|
+
class ReissuableTokenGenerator < Rails::Generators::Base
|
4
|
+
include ::Rails::Generators::Migration
|
5
|
+
include Glueby::Generator::MigrateGenerator
|
6
|
+
extend Glueby::Generator::MigrateGenerator::ClassMethod
|
7
|
+
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
9
|
+
|
10
|
+
def create_migration_file
|
11
|
+
migration_dir = File.expand_path("db/migrate")
|
12
|
+
|
13
|
+
if self.class.migration_exists?(migration_dir, "create_reissuable_token")
|
14
|
+
::Kernel.warn "Migration already exists: create_reissuable_token"
|
15
|
+
else
|
16
|
+
migration_template(
|
17
|
+
"reissuable_token_table.rb.erb",
|
18
|
+
"db/migrate/create_reissuable_token.rb",
|
19
|
+
migration_version: migration_version,
|
20
|
+
table_options: table_options,
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class CreateKey < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
|
-
create_table :
|
3
|
+
create_table :glueby_keys<%= table_options %> do |t|
|
4
4
|
t.string :private_key
|
5
5
|
t.string :public_key
|
6
6
|
t.string :script_pubkey
|
@@ -9,7 +9,7 @@ class CreateKey < ActiveRecord::Migration<%= migration_version %>
|
|
9
9
|
t.timestamps
|
10
10
|
end
|
11
11
|
|
12
|
-
add_index :
|
13
|
-
add_index :
|
12
|
+
add_index :glueby_keys, [:script_pubkey], unique: true
|
13
|
+
add_index :glueby_keys, [:private_key], unique: true
|
14
14
|
end
|
15
15
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class CreateReissuableToken < ActiveRecord::Migration<%= migration_version %>
|
2
|
+
def change
|
3
|
+
create_table :glueby_reissuable_tokens<%= table_options %> do |t|
|
4
|
+
t.string :color_id, null: false
|
5
|
+
t.string :script_pubkey, null: false
|
6
|
+
t.timestamps
|
7
|
+
end
|
8
|
+
add_index :glueby_reissuable_tokens, [:color_id], unique: true
|
9
|
+
end
|
10
|
+
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
class CreateSystemInformation < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
|
-
create_table :
|
3
|
+
create_table :glueby_system_informations<%= table_options %> do |t|
|
4
4
|
t.string :info_key
|
5
5
|
t.string :info_value
|
6
6
|
t.timestamps
|
7
7
|
end
|
8
|
-
add_index :
|
8
|
+
add_index :glueby_system_informations, [:info_key], unique: true
|
9
9
|
|
10
10
|
Glueby::AR::SystemInformation.create(info_key: "synced_block_number", info_value: "0")
|
11
11
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
class CreateUtxo < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
|
-
create_table :
|
3
|
+
create_table :glueby_utxos<%= table_options %> do |t|
|
4
4
|
t.string :txid
|
5
5
|
t.integer :index
|
6
6
|
t.bigint :value
|
@@ -10,6 +10,6 @@ class CreateUtxo < ActiveRecord::Migration<%= migration_version %>
|
|
10
10
|
t.timestamps
|
11
11
|
end
|
12
12
|
|
13
|
-
add_index :
|
13
|
+
add_index :glueby_utxos, [:txid, :index], unique: true
|
14
14
|
end
|
15
15
|
end
|
@@ -1,10 +1,10 @@
|
|
1
1
|
class CreateWallet < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
|
-
create_table :
|
3
|
+
create_table :glueby_wallets<%= table_options %> do |t|
|
4
4
|
t.string :wallet_id
|
5
5
|
t.timestamps
|
6
6
|
end
|
7
7
|
|
8
|
-
add_index :
|
8
|
+
add_index :glueby_wallets, [:wallet_id], unique: true
|
9
9
|
end
|
10
10
|
end
|
data/lib/glueby.rb
CHANGED
@@ -7,6 +7,13 @@ module Glueby
|
|
7
7
|
autoload :Wallet, 'glueby/wallet'
|
8
8
|
autoload :Internal, 'glueby/internal'
|
9
9
|
autoload :AR, 'glueby/active_record'
|
10
|
+
autoload :FeeProvider, 'glueby/fee_provider'
|
11
|
+
autoload :Configuration, 'glueby/configuration'
|
12
|
+
|
13
|
+
# Add prefix to activerecord table names
|
14
|
+
def self.table_name_prefix
|
15
|
+
'glueby_'
|
16
|
+
end
|
10
17
|
|
11
18
|
begin
|
12
19
|
class Railtie < ::Rails::Railtie
|
@@ -15,10 +22,28 @@ module Glueby
|
|
15
22
|
load "tasks/glueby/contract/timestamp.rake"
|
16
23
|
load "tasks/glueby/contract/wallet_adapter.rake"
|
17
24
|
load "tasks/glueby/contract/block_syncer.rake"
|
25
|
+
load "tasks/glueby/fee_provider.rake"
|
18
26
|
end
|
19
27
|
end
|
20
28
|
rescue
|
21
29
|
# Rake task is unavailable
|
22
30
|
puts "Rake task is unavailable"
|
23
31
|
end
|
32
|
+
|
33
|
+
# Returns the global [Configuration](RSpec/Core/Configuration) object.
|
34
|
+
def self.configuration
|
35
|
+
@configuration ||= Glueby::Configuration.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# Yields the global configuration to a block.
|
39
|
+
# @yield [Configuration] global configuration
|
40
|
+
#
|
41
|
+
# @example
|
42
|
+
# Glueby.configure do |config|
|
43
|
+
# config.wallet_adapter = :activerecord
|
44
|
+
# config.rpc_config = { schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass' }
|
45
|
+
# end
|
46
|
+
def self.configure
|
47
|
+
yield configuration if block_given?
|
48
|
+
end
|
24
49
|
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Glueby
|
2
|
+
# Global configuration on runtime
|
3
|
+
#
|
4
|
+
# The global configuration treats configurations for all modules in Glueby.
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# Glueby.configure do |config|
|
8
|
+
# config.wallet_adapter = :activerecord
|
9
|
+
# config.rpc_config = { schema: 'http', host: '127.0.0.1', port: 12381, user: 'user', password: 'pass' }
|
10
|
+
# end
|
11
|
+
class Configuration
|
12
|
+
|
13
|
+
attr_reader :fee_provider_bears
|
14
|
+
alias_method :fee_provider_bears?, :fee_provider_bears
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@fee_provider_bears = false
|
18
|
+
end
|
19
|
+
|
20
|
+
# Specify wallet adapter.
|
21
|
+
# @param [Symbol] adapter - The adapter type :activerecord or :core is currently supported.
|
22
|
+
def wallet_adapter=(adapter)
|
23
|
+
case adapter
|
24
|
+
when :core
|
25
|
+
Glueby::Internal::Wallet.wallet_adapter = Glueby::Internal::Wallet::TapyrusCoreWalletAdapter.new
|
26
|
+
when :activerecord
|
27
|
+
Glueby::Internal::Wallet.wallet_adapter = Glueby::Internal::Wallet::ActiveRecordWalletAdapter.new
|
28
|
+
else
|
29
|
+
raise 'Not implemented'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Specify connection information to Tapyrus Core RPC.
|
34
|
+
# @param [Hash] config
|
35
|
+
# @option config [String] :schema - http or https
|
36
|
+
# @option config [String] :host - The host of the RPC endpoint
|
37
|
+
# @option config [Integer] :port - The port of the RPC endpoint
|
38
|
+
# @Option config [String] :user - The user for Basic Authorization of the RPC endpoint
|
39
|
+
# @Option config [String] :password - The password for Basic Authorization of the RPC endpoint
|
40
|
+
def rpc_config=(config)
|
41
|
+
Glueby::Internal::RPC.configure(config)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Use This to enable to use FeeProvider to supply inputs for fees on each transaction that is created on Glueby.
|
45
|
+
def fee_provider_bears!
|
46
|
+
@fee_provider_bears = true
|
47
|
+
end
|
48
|
+
|
49
|
+
# Use This to disable to use FeeProvider
|
50
|
+
def disable_fee_provider_bears!
|
51
|
+
@fee_provider_bears = false
|
52
|
+
end
|
53
|
+
|
54
|
+
# Specify FeeProvider configuration.
|
55
|
+
# @param [Hash] config
|
56
|
+
# @option config [Integer] :fixed_fee - The fee that Fee Provider pays on each transaction.
|
57
|
+
# @option config [Integer] :utxo_pool_size - Fee Provider tries to keep the number of utxo in utxo pool as this size using `glueby:fee_provider:manage_utxo_pool` rake task
|
58
|
+
def fee_provider_config=(config)
|
59
|
+
FeeProvider.configure(config)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/glueby/contract.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
module Glueby
|
2
2
|
module Contract
|
3
3
|
autoload :Errors, 'glueby/contract/errors'
|
4
|
-
autoload :
|
5
|
-
autoload :
|
4
|
+
autoload :FeeEstimator, 'glueby/contract/fee_estimator'
|
5
|
+
autoload :FixedFeeEstimator, 'glueby/contract/fee_estimator'
|
6
6
|
autoload :Payment, 'glueby/contract/payment'
|
7
7
|
autoload :Timestamp, 'glueby/contract/timestamp'
|
8
8
|
autoload :Token, 'glueby/contract/token'
|