getting started - atticplaygroup/prex GitHub Wiki
This document illustrates the use of an example service (think of it as an echo server), to demonstrate Prex functionality. The walkthrough simulates three roles:
- A Prex platform administrator: sets up a Prex instance.
- A service provider: registers a basic service with the Prex instance.
- A user (agent): locates the service, obtains authorization, and uses the service.
Note
Prex will be transparent to end users.
This quickstart guide helps you get familiar with Prex, including what it can do and how to interact with it. In practice the end user will use a user agent to achieve the same rather than dev tools like cURL. All of them will be transparent to the user except setting an initial budget to pay.
Install the prex
binary via the following command:
go install github.com/atticplaygroup/prex
Then start the gRPC service:
cp .env.example .env
prex server start
Several important environment variables are:
SUI_NETWORK=devnet
ENABLE_PREX_QUOTA_LIMITER=0
ENABLE_SERVICE_REGISTRATION_WHITELIST=0
ADMIN_USERNAME=admin
ADMIN_PASSWORD=change_me
The configuration SUI_NETWORK=devnet
indicates that Prex is configured to interact with the Sui network for deposit and withdrawal processing, specifically using the devnet environment to handle tokens. To facilitate demonstration purposes, certain features like quota control and service registration whitelist are disabled in this setup.
Prex offers two API interfaces, RESTful and gRPC. A simple test to verify the Prex instance is running can be performed using the curl
command to query the /v1/ping
endpoint.
curl http://localhost:3000/v1/ping | jq
A successful response will return a JSON object.
{
"pong": "pong"
}
The subsequent step involves simulating a service provider of the echo server, which needs to be registered to enable user access. Prior to registration, it's beneficial to examine the currently registered services within Prex. The listing of available services in a Prex instance can be achieved as follows.
curl http://localhost:3000/v1/services | jq
{
"services": [
{
"name": "services/1",
"productTokenPolicy": {
"unitPrice": "1"
},
"globalId": "00000000-0000-0000-0000-000000000000",
"displayName": "free quota service"
},
{
"name": "services/2",
"productTokenPolicy": {
"unitPrice": "1"
},
"globalId": "00000000-0000-0000-0000-000000000001",
"displayName": "sold quota service"
}
]
}
Upon initial start, there are two pre-existing services, primarily utilized internally by Prex for quota control purposes, which can be disregarded for this demonstration since rate limiting was disabled during deployment.
PAID services are uniquely identified within a Prex instance by a service_global_id
, similar to smart contracts. However, these IDs may not be universally unique across different Prex instances due to the absence of a global consensus mechanism. In cases of ID conflicts, the user agent can select its preferred service. Read more at Global ID and Conflicts.
Note
The distinction between service
and instance
A "service" represents a general, abstract definition of a program's behavior, or theoretically a formal language. An "instance" is a concrete execution of that service, such as a particular pod running the program. This relationship is analogous to a smart contract and the miner's node that executes it.
Both service providers and end users need to pay for PAID services, including Prex. A necessary initial step is setting up an account.
Note
However, wouldn't a truly permissionless system eliminate the need for accounts?
While the intention is to be permissionless, Prex itself operates on a PAID model. In theory, payments could be made with each use via methods like cryptocurrency or Stripe (feature in progress). However, to reduce delays or transaction costs, users can opt for "batch payments," essentially creating a pseudo account. The registration process is designed to be sybil-resistant by linking it to deposit, without requiring personal verification like phone numbers.
Currently, account creation involving payment relies on a cryptocurrency called Sui. The specifics are outlined in the account creation guide. For the time being, the scripts/register-account.sh
script can be used. This script performs the following actions:
- Generates a new Sui private key, storing it in a YAML configuration file at
${PREX_CONFIG_PATH}
. - Requests Sui tokens from a devnet faucet.
- Obtains the deposit address from a Prex instance and transfers Sui tokens to it.
- Retrieves the deposit transaction's digest from a Sui devnet node.
- Uses the Sui private key to sign a personal message of a challenge issued by the Prex instance to verify transaction ownership.
- Tells the Prex instance to check the transaction and signature, completing the new account creation.
Note
Why is Sui used, and is using a blockchain mandatory?
No. Currently, Sui is utilized due to its convenience and the team's familiarity with it, as well as to showcase Prex's capabilities for open use. However, Prex is designed to support various payment options, including conventional methods like Stripe. There are plans to transition to integrated payment solutions such as HyperSwitch, and to incorporate additional onboarding and security methods for regulatory purposes like traditional OAuth registration.
In this example, an account is being created for a service provider, referred to as the "seller". For demonstration purposes, the seller's private key will be stored in a file located at /tmp/seller.yml.
bash scripts/register-account.sh /tmp/seller.yml
Response example:
{
"account": {
"name": "/accounts/4",
"accountId": "4",
"username": "YbV75/qpx0SbYZV8eu8B9u3909Y7nGJfZNF8gjj+GD/fUjaAScf096SZuvFXcnUc",
"expireTime": "2025-04-18T08:48:14.561049Z",
"balance": "100000000"
}
}
Within Prex, every account has an associated expireTime
, reflecting the cost of maintaining its presence within the database. Once this time elapses, the account is removed. It's also important to note that the username
is automatically generated and intended for machine management rather than direct human interaction. It is derived from the mnemonic phrase stored within the seller.yml
file.
Note
Is storing sensitive mnemonics in plain text a security risk?
Yes, storing a private key in plain text where it's accessible to all programs is inherently risky. However, Prex accounts are intentionally designed to be easily replaceable, prioritizing ease of use over absolute security in line with a "permissionless" design philosophy.
Prex instances are treated as semi-trusted entities. While reputable instances may be dependable for a period, they could also cease operations at any time. Engaging in transactions inherently involves a degree of financial risk. This approach aims to improve the economic efficiency of Prex itself, enabling smaller operators to enter the market more easily and build trust through conventional means.
In summary, Prex user agents should avoid storing substantial funds. An amount sufficient for a monthly budget, like around $10, is adequate. Even in the event of theft, the financial loss would be limited.
The next step involves logging in.
bash scripts/login-account.sh /tmp/seller.yml
Response example:
{
"account": {
"name": "/accounts/3",
"accountId": "3",
"username": "cYL6U2thMmrZq1ggtdfxEp+q0oDWZ9QPB4vLT+zGqMcpPjI7pQhkb65R/v3625Bm",
"expireTime": "2025-04-18T06:31:14.032170Z",
"balance": "99999900"
},
"accessToken": "eyJhb..."
}
From this point forward, an accessToken
and accountId
will be required to carry out actions.
To inform users that this Prex exchange offers tokens for a particular service, it must be registered. Prex includes a feature that allows only whitelisted accounts to register services. This feature has been disabled for this example, allowing any user to register a service. However, in a production environment, it's recommended to restrict registration to a limited number of trusted sources to ensure service quality and avoid global ID conflicts.
The first step is to generate a unique global ID for the service. In this case, an arbitrary ID is generated.
SERVICE_GLOBAL_ID=$(cat /proc/sys/kernel/random/uuid)
echo "Service global ID: ${SERVICE_GLOBAL_ID}"
AUTH_TOKEN=$(echo ${LOGIN_RESPONSE} | jq .accessToken)
curl -H "Authorization: Bearer ${AUTH_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"product_token_policy": {
"unit_price": 1
},
"globalId": "'"${SERVICE_GLOBAL_ID}"'",
"display_name": "An echo server"
}' -X POST http://localhost:3000/v1/services:create
Response example:
{
"service": {
"name": "services/4",
"productTokenPolicy": {
"unitPrice": "1"
},
"globalId": "79d36e77-b1d5-49c5-ab13-e86e9c6b4f61",
"displayName": "An echo server"
}
}
Now, on this Prex instance, users can exchange tokens associated with this service. Importantly, anyone, not just the service registrant, can create sell orders using the service's global ID, effectively presenting themselves as a service provider for that service.
In reality, multiple services might offer similar core functionality but differ in quality. For instance, two chatbot services might utilize the same underlying LLM, yet one might have an average response rate of less than 10 tokens per second, while the other's rate is between 10 and 20 tokens per second. Service providers can specify their service quality as they see fit. However, if a provider consistently fails to meet expectations, user agents will likely lose trust and exclude it from future matches. Alternatively, issues can be reported to a central index of services to alert others about the service provider's quality and reputation.
In this instance, service metadata such as the display name and description have been added. Within Prex, this information primarily serves to inform users that trading is possible on this particular instance, rather than facilitating comprehensive service discovery (e.g., "Which service should I use for a specific task?"). For service discovery purposes, a dedicated PAID search engine should be used. Prex is built on Postgres, which is optimized for high-volume OLAP transaction processing rather than complex search queries.
Simply registering a service within Prex doesn't automatically mean that a specific entity can fulfill requests for that service. Placing a sell order serves this purpose. By default, anyone can declare themselves as a provider for any given service. However, user agents tend to favor providers with established reputations.
SERVICE_ID=4 # Get the local request id of your target service address
ASK_PRICE=1000 # In SUI Mist
SELL_QUANTITY=1000000 # Total quantity of this sell order
# I will guarantee the service quality at least until this time
SERVICE_EXPIRE_TIME=$(date -d "1 day" +"%Y-%m-%dT%H:%M:%SZ")
# My order will expire at this time
ORDER_EXPIRE_TIME=$(date -d "1 day" +"%Y-%m-%dT%H:%M:%SZ")
curl -s -H "Authorization: Bearer ${AUTH_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"account_id": '${ACCOUNT_ID}',
"sell_order": {
"seller_id": '${ACCOUNT_ID}',
"service_id": '${SERVICE_ID}',
"ask_price": '${ASK_PRICE}',
"quantity": '${SELL_QUANTITY}',
"order_expire_time": "'"${ORDER_EXPIRE_TIME}"'",
"service_expire_time": "'"${SERVICE_EXPIRE_TIME}"'"
},
"ttl": "3600s"
}' \
-X POST "http://localhost:3000/v1/services/${SERVICE_ID}/sell-orders:create"
Here, the service ID, quantity, and asking price are defined:
- Service ID: This informs users about the service's function. It's specific to the Prex instance and doesn't serve as a global identifier for the service.
- Quantity: This represents the amount of service usage available. It doesn't directly correspond to the number of service queries, as each query may consume different amounts of resources. For example, a request with a 100ms timeout may consume twice the quantity units compared to a 50ms timeout request.
- Ask Price: This is the price the user pays for each unit of quantity.
- Service Expire Time: This guarantees the minimum duration for which the service provider will offer the service.
- Order Expire Time: The order's lifespan within the Prex database is limited to conserve resources. The longer it remains active, the more the seller needs to pay.
Now, we're assuming the perspective of an end-user's user agent, seeking an echo service for their requests. It has learned somewhere of a PAID service with the global ID 79d36e77-b1d5-49c5-ab13-e86e9c6b4f61
that implements an echo service and meets its quality criteria. Additionally, it has somehow found that this specific Prex instance facilitates token trading for that service.
Typically, this information would come from a PAID index of services and Prex instances. However, it could also originate from a non-PAID service, provided the user agent trusts the source.
The account registration process is being repeated to create a second account, named buyer
because the seller and buyer must be distinct when purchasing tokens.
bash scripts/register-account.sh /tmp/buyer.yml && \
bash scripts/login-account.sh /tmp/buyer.yml
To utilize a PAID service through Prex, the initial step is to acquire tokens.
To verify that this Prex instance actually offers a service with the specified global ID, the list of services can be retrieved using the following command:
curl http://localhost:3000/v1/services | jq
{
[
// ...
{
"name": "services/4",
"productTokenPolicy": {
"unitPrice": "1"
},
"globalId": "79d36e77-b1d5-49c5-ab13-e86e9c6b4f61",
"displayName": "An echo server"
}
]
}
Next buy a token:
BID_PRICE=200
SERVICE_ID=4
ACCOUNT_ID=4
AUTH_TOKEN="eyJhb..."
curl -s -H "Authorization: Bearer ${AUTH_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"account_id": '${ACCOUNT_ID}',
"quantity": 10,
"bid_price": '${BID_PRICE}',
"min_expire_time": "'$(date -u +"%Y-%m-%dT%H:%M:%SZ")'"
}' \
-X POST "http://localhost:3000/v1/services/${SERVICE_ID}/sell-orders:match"
{
"fulfilledOrder": {
"name": "services/4/fulfilled-orders/38",
"serviceId": "4",
"sellOrderId": "35",
"buyerId": "4",
"sellerId": "3",
"dealPrice": "10",
"dealQuantity": "10",
"dealTime": "2025-04-18T10:30:27.090133Z",
"remainingQuantity": "10",
"serviceExpireTime": "2025-04-19T10:16:57Z"
}
}
Now we can claim the bought token from the fulfilled order.
Prex supports dynamic token settings for bought tokens via the arg_json
argument.
curl -s -H "Authorization: Bearer ${AUTH_TOKEN}" \
-H 'Content-Type: application/json' -d '{
"name": "services/4/fulfilled-orders/38",
"arg_json": "{\\\"count\\\":1}",
"account_id": 4
}' http://localhost:3000/v1/services/4/fulfilled-orders/38:claim
The token acquired is a standard JWT (JSON Web Token), intended for use with Bearer authentication.