Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
170 changes: 170 additions & 0 deletions examples/rfq-workflow.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
<?php

declare(strict_types=1);

require_once __DIR__.'/../vendor/autoload.php';

Comment on lines +5 to +6
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor consistency: other example scripts use spaced concatenation for __DIR__ (e.g., examples/bridge-deposit.php:5 uses __DIR__ . '/../vendor/autoload.php'). Consider matching that formatting here for consistency.

Copilot uses AI. Check for mistakes.
use Danielgnh\PolymarketPhp\Client;
use Danielgnh\PolymarketPhp\Enums\OrderSide;
use Danielgnh\PolymarketPhp\Enums\RfqSortBy;
use Danielgnh\PolymarketPhp\Enums\RfqSortDir;
use Danielgnh\PolymarketPhp\Enums\RfqState;

/**
* Example: RFQ (Request for Quote) Workflow.
*
* This example demonstrates the complete RFQ workflow for institutional trading:
* 1. Requester creates an RFQ request
* 2. Market maker creates a quote in response
* 3. Requester accepts the quote
* 4. Market maker approves the order (last look)
*
* RFQ is designed for large orders and negotiated pricing.
*/

// Initialize client with authentication
$client = new Client();

// Setup authentication (required for RFQ operations)
try {
$client->auth(); // Uses private key from .env
} catch (Exception $e) {
echo "Authentication required for RFQ operations\n";
echo "Set POLYMARKET_PRIVATE_KEY in your .env file\n";
exit(1);
}

echo "RFQ Workflow Examples\n";
echo "====================\n\n";

// Example token ID
$tokenId = '21742633143463906290569050155826241533067272736897614950488156847949938836455';

// 1. Create an RFQ Request (Requester wants to buy/sell)
echo "1. Creating RFQ Request...\n";
try {
$request = $client->clob()->rfq()->createRequest([
'token_id' => $tokenId,
'side' => OrderSide::BUY->value,
'size' => '100',
'price' => '0.55',
'expiry' => time() + 3600, // Expires in 1 hour
]);

$requestId = $request['id'];
echo "Created RFQ request: $requestId\n";
echo "Token: $tokenId\n";
echo "Side: BUY\n";
echo "Size: 100 shares\n";
echo "Target Price: \$0.55\n\n";
} catch (Exception $e) {
echo "Error creating request: {$e->getMessage()}\n\n";
$requestId = null;
}

// 2. List all RFQ Requests
echo "2. Listing Active RFQ Requests...\n";
try {
$requests = $client->clob()->rfq()->listRequests([
'state' => RfqState::ACTIVE->value,
'limit' => 10,
'sort_by' => RfqSortBy::CREATED->value,
'sort_dir' => RfqSortDir::DESC->value,
]);

echo 'Found '.count($requests)." active RFQ requests\n\n";
} catch (Exception $e) {
echo "Error listing requests: {$e->getMessage()}\n\n";
}

// 3. Create a Quote (Market Maker responds to request)
if ($requestId !== null) {
echo "3. Creating Quote for Request...\n";
try {
$quote = $client->clob()->rfq()->createQuote([
'request_id' => $requestId,
'price' => '0.54',
'size' => '100',
'expiry' => time() + 1800, // 30 minutes
]);

$quoteId = $quote['id'];
echo "Created quote: $quoteId\n";
echo "Offered Price: \$0.54\n";
echo "Size: 100 shares\n\n";
} catch (Exception $e) {
echo "Error creating quote: {$e->getMessage()}\n\n";
$quoteId = null;
}
} else {
$quoteId = null;
}

// 4. List Quotes for a Request
if ($requestId !== null) {
echo "4. Listing Quotes for Request...\n";
try {
$quotes = $client->clob()->rfq()->listQuotes([
'request_id' => $requestId,
'sort_by' => RfqSortBy::PRICE->value,
]);

echo 'Found '.count($quotes)." quotes\n\n";
} catch (Exception $e) {
echo "Error listing quotes: {$e->getMessage()}\n\n";
}
}

// 5. Accept a Quote (Requester accepts market maker's quote)
if ($quoteId !== null) {
echo "5. Accepting Quote...\n";
try {
$acceptance = $client->clob()->rfq()->acceptQuote([
'quote_id' => $quoteId,
]);

echo "Quote accepted\n";
echo "Orders created\n\n";
} catch (Exception $e) {
echo "Error accepting quote: {$e->getMessage()}\n\n";
}
}

// 6. Approve Order (Market Maker's last look)
echo "6. Approving Order (Last Look)...\n";
try {
$approval = $client->clob()->rfq()->approveOrder([
'order_id' => 'example-order-id',
]);

echo "Order approved\n";
echo "Trade will execute\n\n";
} catch (Exception $e) {
echo "Error approving order: {$e->getMessage()}\n\n";
Comment on lines +133 to +143
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example claims to demonstrate the complete workflow, but the approval step uses a hard-coded order_id (example-order-id), so it will always error in real runs. Consider extracting the created order ID(s) from the acceptQuote() response (if present) and only calling approveOrder() when you have a real order_id, otherwise skip the step with a clear message.

Copilot uses AI. Check for mistakes.
}

// 7. Cancel a Quote (if needed)
if ($quoteId !== null) {
echo "7. Canceling Quote (Demo)...\n";
try {
$cancellation = $client->clob()->rfq()->cancelQuote($quoteId);
echo "Quote canceled: $quoteId\n\n";
} catch (Exception $e) {
echo "Error canceling quote: {$e->getMessage()}\n\n";
}
}

// 8. Cancel a Request (if needed)
if ($requestId !== null) {
echo "8. Canceling Request (Demo)...\n";
try {
$cancellation = $client->clob()->rfq()->cancelRequest($requestId);
echo "Request canceled: $requestId\n";
} catch (Exception $e) {
echo "Error canceling request: {$e->getMessage()}\n";
}
}

echo "\nRFQ Workflow Complete!\n";
echo "\nNote: This example demonstrates the full workflow.\n";
echo "In production, different parties (requesters and quoters) would perform different steps.\n";
7 changes: 7 additions & 0 deletions src/Clob.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use Danielgnh\PolymarketPhp\Resources\Clob\OrderScoring;
use Danielgnh\PolymarketPhp\Resources\Clob\Pricing;
use Danielgnh\PolymarketPhp\Resources\Clob\Rewards;
use Danielgnh\PolymarketPhp\Resources\Clob\Rfq;
use Danielgnh\PolymarketPhp\Resources\Clob\Server;
use Danielgnh\PolymarketPhp\Resources\Clob\Spreads;
use Danielgnh\PolymarketPhp\Resources\Clob\Trades;
Expand All @@ -35,6 +36,7 @@
* - Authentication: API key management
* - Account: Balance, allowances, and notifications
* - Rewards: Earnings and reward percentages
* - Rfq: Request for Quote (institutional trading)
* - OrderScoring: Order scoring checks
* - Server: Health checks and server info
*
Expand Down Expand Up @@ -121,4 +123,9 @@ public function server(): Server
{
return new Server($this->httpClient);
}

public function rfq(): Rfq
{
return new Rfq($this->httpClient);
}
Comment on lines +127 to +130
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A new public CLOB resource (rfq()) is introduced, but there are no corresponding Feature tests (the repo has per-resource coverage under tests/Feature/Clob/*Test.php). Please add tests for the new RFQ methods/endpoints (at least happy paths for create/list/cancel request/quote, accept, approve) similar to existing resources’ tests.

Copilot uses AI. Check for mistakes.
}
13 changes: 13 additions & 0 deletions src/Enums/RfqSortBy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

declare(strict_types=1);

namespace Danielgnh\PolymarketPhp\Enums;

enum RfqSortBy: string
{
case PRICE = 'price';
case EXPIRY = 'expiry';
case SIZE = 'size';
case CREATED = 'created';
}
11 changes: 11 additions & 0 deletions src/Enums/RfqSortDir.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace Danielgnh\PolymarketPhp\Enums;

enum RfqSortDir: string
{
case ASC = 'asc';
case DESC = 'desc';
}
12 changes: 12 additions & 0 deletions src/Enums/RfqState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

declare(strict_types=1);

namespace Danielgnh\PolymarketPhp\Enums;

enum RfqState: string
{
case ACTIVE = 'active';
case INACTIVE = 'inactive';
case UNKNOWN = 'unknown';
}
147 changes: 147 additions & 0 deletions src/Resources/Clob/Rfq.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php

declare(strict_types=1);

namespace Danielgnh\PolymarketPhp\Resources\Clob;

use Danielgnh\PolymarketPhp\Exceptions\PolymarketException;
use Danielgnh\PolymarketPhp\Resources\Resource;

/**
* RFQ (Request for Quote) Resource.
*
* Handles institutional/large order workflow for negotiated pricing.
* Enables users to request quotes from market makers and vice versa.
*/
class Rfq extends Resource
{
/**
* Create an RFQ request for buying/selling outcome tokens.
*
* @param array<string, mixed> $requestData RFQ request data (tokenId, side, size, price, etc.)
*
Comment on lines +19 to +22
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docblocks describe request payload keys using camelCase (e.g., tokenId, requestId), but existing CLOB resources and the README use snake_case keys (e.g., token_id in src/Resources/Clob/Book.php:19). Please update these docblocks to reflect the actual expected parameter names so IDE hints don’t mislead users.

Copilot uses AI. Check for mistakes.
* @return array<string, mixed>
*
* @throws PolymarketException
*/
public function createRequest(array $requestData): array
{
$response = $this->httpClient->post('/rfq/requests', $requestData);

return $response->json();
}

/**
* Cancel an RFQ request.
*
* @param string $requestId RFQ request ID
*
* @return array<string, mixed>
*
* @throws PolymarketException
*/
public function cancelRequest(string $requestId): array
{
$response = $this->httpClient->delete("/rfq/requests/$requestId");

Comment on lines +45 to +46
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String interpolation for the request/quote IDs deviates from the convention used elsewhere in CLOB resources (e.g., src/Resources/Clob/Orders.php:36 uses "/orders/{$orderId}"). Prefer "/rfq/requests/{$requestId}" (and similarly for quotes) to avoid ambiguity and keep style consistent.

Copilot uses AI. Check for mistakes.
return $response->json();
}

/**
* List RFQ requests with pagination and filters.
*
* @param array<string, mixed> $filters Filters (state, tokenId, limit, offset, sortBy, sortDir, etc.)
*
* @return array<int, array<string, mixed>>
*
* @throws PolymarketException
*/
public function listRequests(array $filters = []): array
{
$response = $this->httpClient->get('/rfq/requests', $filters);

/** @var array<int, array<string, mixed>> */
return $response->json();
}

/**
* Create a quote responding to an RFQ request.
*
* @param array<string, mixed> $quoteData Quote data (requestId, price, size, expiry, etc.)
*
* @return array<string, mixed>
*
* @throws PolymarketException
*/
public function createQuote(array $quoteData): array
{
$response = $this->httpClient->post('/rfq/quotes', $quoteData);

return $response->json();
}

/**
* Cancel an RFQ quote.
*
* @param string $quoteId RFQ quote ID
*
* @return array<string, mixed>
*
* @throws PolymarketException
*/
public function cancelQuote(string $quoteId): array
{
$response = $this->httpClient->delete("/rfq/quotes/$quoteId");

Comment on lines +94 to +95
Copy link

Copilot AI Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The quote cancellation endpoint uses string interpolation without braces, which is inconsistent with the rest of the codebase’s path construction (e.g., src/Resources/Clob/Orders.php:97). Use "/rfq/quotes/{$quoteId}" for consistency/readability.

Copilot uses AI. Check for mistakes.
return $response->json();
}

/**
* List RFQ quotes with pagination and filters.
*
* @param array<string, mixed> $filters Filters (state, requestId, limit, offset, sortBy, sortDir, etc.)
*
* @return array<int, array<string, mixed>>
*
* @throws PolymarketException
*/
public function listQuotes(array $filters = []): array
{
$response = $this->httpClient->get('/rfq/quotes', $filters);

/** @var array<int, array<string, mixed>> */
return $response->json();
}

/**
* Accept a quote and create orders (requester action).
*
* @param array<string, mixed> $acceptData Acceptance data (quoteId, etc.)
*
* @return array<string, mixed>
*
* @throws PolymarketException
*/
public function acceptQuote(array $acceptData): array
{
$response = $this->httpClient->post('/rfq/accept', $acceptData);

return $response->json();
}

/**
* Approve an order during last look window (quoter action).
*
* @param array<string, mixed> $approveData Approval data (orderId, etc.)
*
* @return array<string, mixed>
*
* @throws PolymarketException
*/
public function approveOrder(array $approveData): array
{
$response = $this->httpClient->post('/rfq/approve', $approveData);

return $response->json();
}
}