pandora-cli-skills 1.1.45 → 1.1.47
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.
- package/README_FOR_SHARING.md +3 -1
- package/SKILL.md +6 -4
- package/cli/lib/schema_command_service.cjs +3 -0
- package/cli/lib/trade_command_service.cjs +3 -0
- package/cli/lib/trade_market_type_service.cjs +203 -0
- package/cli/pandora.cjs +34 -19
- package/package.json +2 -1
- package/tests/unit/trade_market_type_service.test.cjs +109 -0
- package/tsconfig.json +13 -0
package/README_FOR_SHARING.md
CHANGED
|
@@ -262,7 +262,9 @@ Mirror advanced flags (for operator tuning):
|
|
|
262
262
|
- envelope is `ok=true`, `command="trade"`, with tx metadata (`approveTxHash` optional, `buyTxHash` required on success) plus `selectedProbabilityPct` and `riskGuards`.
|
|
263
263
|
|
|
264
264
|
## Phase 2 limitations
|
|
265
|
-
- `trade`
|
|
265
|
+
- `trade` auto-detects market type and uses the correct buy signature:
|
|
266
|
+
- PariMutuel: `buy(bool,uint256,uint256)`
|
|
267
|
+
- AMM: `buy(bool,uint256,uint256,uint256)` (deadline-aware)
|
|
266
268
|
- `minSharesOut` protection defaults to raw `0` unless explicitly set with `--min-shares-out-raw`.
|
|
267
269
|
- If indexer odds are unavailable, `quote` still returns a structured payload with `quoteAvailable=false`.
|
|
268
270
|
- `trade --execute` blocks unquoted execution by default unless `--min-shares-out-raw` or `--allow-unquoted-execute` is provided.
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pandora-cli-skills
|
|
3
3
|
summary: Canonical skill and operator guide for Pandora CLI including mirror, polymarket, resolve, and LP flows.
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.46
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Pandora CLI & Skills
|
|
@@ -359,7 +359,9 @@ pandora --output json schema
|
|
|
359
359
|
- envelope is `ok=true`, `command="trade"`, with tx metadata (`approveTxHash` optional, `buyTxHash` required on success) plus `selectedProbabilityPct` and `riskGuards`.
|
|
360
360
|
|
|
361
361
|
## Phase 2 limitations
|
|
362
|
-
- `trade`
|
|
362
|
+
- `trade` auto-detects market type and uses the correct buy signature:
|
|
363
|
+
- PariMutuel: `buy(bool,uint256,uint256)`
|
|
364
|
+
- AMM: `buy(bool,uint256,uint256,uint256)` (deadline-aware)
|
|
363
365
|
- `--min-shares-out-raw` is the explicit slippage guard input for on-chain execution.
|
|
364
366
|
- If indexer odds are unavailable, `quote` still returns structured output with `quoteAvailable=false`.
|
|
365
367
|
- `trade --execute` blocks unquoted execution by default unless `--min-shares-out-raw` or `--allow-unquoted-execute` is provided.
|
|
@@ -489,8 +491,8 @@ Common structured error codes for automation:
|
|
|
489
491
|
- `ODDS_*`: odds record/history storage and connector failures.
|
|
490
492
|
- `ARB_*`: arb scan parsing/execution output-mode failures.
|
|
491
493
|
- `CONFIG_*`: config read/parse failures (for example `CONFIG_FILE_NOT_FOUND`).
|
|
492
|
-
- `SIMULATE_*`: simulate namespace failures (`SIMULATE_MC_INVALID_INPUT`, `SIMULATE_PARTICLE_FILTER_FAILED`, `SIMULATE_AGENTS_FAILED`).
|
|
493
|
-
- `MODEL_*`: model namespace failures (`MODEL_CALIBRATE_INVALID_INPUT`, `MODEL_CORRELATION_FAILED`, `MODEL_DIAGNOSE_INVALID_INPUT`, `MODEL_SCORE_BRIER_FAILED`).
|
|
494
|
+
- `SIMULATE_*`: simulate namespace failures (`SIMULATE_MC_FAILED`, `SIMULATE_MC_INVALID_INPUT`, `SIMULATE_PARTICLE_FILTER_FAILED`, `SIMULATE_PARTICLE_FILTER_INVALID_INPUT`, `SIMULATE_AGENTS_FAILED`, `SIMULATE_AGENTS_INVALID_INPUT`).
|
|
495
|
+
- `MODEL_*`: model namespace failures (`MODEL_CALIBRATE_FAILED`, `MODEL_CALIBRATE_INVALID_INPUT`, `MODEL_CORRELATION_FAILED`, `MODEL_CORRELATION_INVALID_INPUT`, `MODEL_DIAGNOSE_FAILED`, `MODEL_DIAGNOSE_INVALID_INPUT`, `MODEL_SCORE_BRIER_FAILED`, `MODEL_SCORE_BRIER_INVALID_INPUT`).
|
|
494
496
|
- `FORECAST_*`: forecast ledger read/write/normalization failures (`FORECAST_WRITE_FAILED`, `FORECAST_INVALID_RECORD`, `FORECAST_READ_FAILED`).
|
|
495
497
|
- `BRIER_*`: brier scoring input/grouping failures (`BRIER_INVALID_INPUT`, `BRIER_INVALID_GROUP_BY`, `BRIER_FAILED`).
|
|
496
498
|
- `MCP_FILE_ACCESS_BLOCKED`: MCP-mode file path denied outside workspace root.
|
|
@@ -568,6 +568,9 @@ function buildSchemaPayload() {
|
|
|
568
568
|
mode: { enum: ['dry-run', 'execute'] },
|
|
569
569
|
status: { type: 'string' },
|
|
570
570
|
marketAddress: { type: 'string' },
|
|
571
|
+
marketType: { type: ['string', 'null'] },
|
|
572
|
+
buySignature: { type: ['string', 'null'] },
|
|
573
|
+
ammDeadlineEpoch: { type: ['string', 'null'] },
|
|
571
574
|
side: { enum: ['yes', 'no'] },
|
|
572
575
|
amountUsdc: { type: 'number' },
|
|
573
576
|
quote: { type: 'object' },
|
|
@@ -117,6 +117,9 @@ function createRunTradeCommand(deps) {
|
|
|
117
117
|
},
|
|
118
118
|
chainId: execution.chainId,
|
|
119
119
|
marketAddress: options.marketAddress,
|
|
120
|
+
marketType: execution.marketType || null,
|
|
121
|
+
buySignature: execution.buySignature || null,
|
|
122
|
+
ammDeadlineEpoch: execution.ammDeadlineEpoch || null,
|
|
120
123
|
side: options.side,
|
|
121
124
|
amountUsdc: options.amountUsdc,
|
|
122
125
|
amountRaw: execution.amountRaw,
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
const PARI_MUTUEL_BUY_ABI = [
|
|
2
|
+
{
|
|
3
|
+
name: 'buy',
|
|
4
|
+
type: 'function',
|
|
5
|
+
stateMutability: 'nonpayable',
|
|
6
|
+
inputs: [
|
|
7
|
+
{ name: 'isYes', type: 'bool' },
|
|
8
|
+
{ name: 'collateralAmount', type: 'uint256' },
|
|
9
|
+
{ name: 'minSharesOut', type: 'uint256' },
|
|
10
|
+
],
|
|
11
|
+
outputs: [{ name: 'sharesOut', type: 'uint256' }],
|
|
12
|
+
},
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const PREDICTION_AMM_BUY_ABI = [
|
|
16
|
+
{
|
|
17
|
+
name: 'buy',
|
|
18
|
+
type: 'function',
|
|
19
|
+
stateMutability: 'nonpayable',
|
|
20
|
+
inputs: [
|
|
21
|
+
{ name: 'isYes', type: 'bool' },
|
|
22
|
+
{ name: 'collateralAmount', type: 'uint256' },
|
|
23
|
+
{ name: 'minSharesOut', type: 'uint256' },
|
|
24
|
+
{ name: 'deadline', type: 'uint256' },
|
|
25
|
+
],
|
|
26
|
+
outputs: [{ name: 'sharesOut', type: 'uint256' }],
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
const PARI_MUTUEL_MARKER_ABI = [
|
|
31
|
+
{
|
|
32
|
+
type: 'function',
|
|
33
|
+
name: 'curveFlattener',
|
|
34
|
+
stateMutability: 'view',
|
|
35
|
+
inputs: [],
|
|
36
|
+
outputs: [{ type: 'uint8' }],
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const PREDICTION_AMM_MARKER_ABI = [
|
|
41
|
+
{
|
|
42
|
+
type: 'function',
|
|
43
|
+
name: 'tradingFee',
|
|
44
|
+
stateMutability: 'view',
|
|
45
|
+
inputs: [],
|
|
46
|
+
outputs: [{ type: 'uint24' }],
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const DEFAULT_AMM_TRADE_DEADLINE_OFFSET_SEC = 15 * 60;
|
|
51
|
+
|
|
52
|
+
function createTradeTypeError(code, message, details = undefined) {
|
|
53
|
+
const err = new Error(message);
|
|
54
|
+
err.code = code;
|
|
55
|
+
if (details !== undefined) {
|
|
56
|
+
err.details = details;
|
|
57
|
+
}
|
|
58
|
+
return err;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Detects supported Pandora market type by probing stable view-method markers.
|
|
63
|
+
* @param {{readContract: Function}} publicClient
|
|
64
|
+
* @param {`0x${string}`} marketAddress
|
|
65
|
+
* @returns {Promise<{marketType:'parimutuel'|'amm', detectedBy:string}>}
|
|
66
|
+
*/
|
|
67
|
+
async function detectTradeMarketType(publicClient, marketAddress) {
|
|
68
|
+
let pariError = null;
|
|
69
|
+
try {
|
|
70
|
+
await publicClient.readContract({
|
|
71
|
+
address: marketAddress,
|
|
72
|
+
abi: PARI_MUTUEL_MARKER_ABI,
|
|
73
|
+
functionName: 'curveFlattener',
|
|
74
|
+
});
|
|
75
|
+
return {
|
|
76
|
+
marketType: 'parimutuel',
|
|
77
|
+
detectedBy: 'curveFlattener',
|
|
78
|
+
};
|
|
79
|
+
} catch (err) {
|
|
80
|
+
pariError = err;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let ammError = null;
|
|
84
|
+
try {
|
|
85
|
+
await publicClient.readContract({
|
|
86
|
+
address: marketAddress,
|
|
87
|
+
abi: PREDICTION_AMM_MARKER_ABI,
|
|
88
|
+
functionName: 'tradingFee',
|
|
89
|
+
});
|
|
90
|
+
return {
|
|
91
|
+
marketType: 'amm',
|
|
92
|
+
detectedBy: 'tradingFee',
|
|
93
|
+
};
|
|
94
|
+
} catch (err) {
|
|
95
|
+
ammError = err;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
throw createTradeTypeError(
|
|
99
|
+
'UNSUPPORTED_MARKET_TRADE_INTERFACE',
|
|
100
|
+
'Market does not expose a supported Pandora trade interface.',
|
|
101
|
+
{
|
|
102
|
+
marketAddress,
|
|
103
|
+
attemptedMarkers: ['curveFlattener', 'tradingFee'],
|
|
104
|
+
markerErrors: {
|
|
105
|
+
parimutuel: pariError && pariError.message ? pariError.message : String(pariError),
|
|
106
|
+
amm: ammError && ammError.message ? ammError.message : String(ammError),
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function toEpochSeconds(value, fallback) {
|
|
113
|
+
const numeric = Number(value);
|
|
114
|
+
if (Number.isFinite(numeric)) {
|
|
115
|
+
const truncated = Math.trunc(numeric);
|
|
116
|
+
if (truncated >= 0) return truncated;
|
|
117
|
+
}
|
|
118
|
+
return fallback;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Builds the market-specific buy call shape.
|
|
123
|
+
* @param {{
|
|
124
|
+
* marketType: 'parimutuel'|'amm',
|
|
125
|
+
* side: 'yes'|'no',
|
|
126
|
+
* amountRaw: bigint,
|
|
127
|
+
* minSharesOutRaw: bigint,
|
|
128
|
+
* nowEpochSec?: number,
|
|
129
|
+
* ammDeadlineOffsetSec?: number,
|
|
130
|
+
* }} input
|
|
131
|
+
* @returns {{marketType:'parimutuel'|'amm', abi: object[], functionName: 'buy', args: (boolean|bigint)[], signature: string, ammDeadlineEpoch?: string}}
|
|
132
|
+
*/
|
|
133
|
+
function buildTradeBuyCall(input) {
|
|
134
|
+
const marketType = String(input.marketType || '').toLowerCase();
|
|
135
|
+
const isYes = String(input.side || '').toLowerCase() === 'yes';
|
|
136
|
+
const amountRaw = input.amountRaw;
|
|
137
|
+
const minSharesOutRaw = input.minSharesOutRaw;
|
|
138
|
+
|
|
139
|
+
if (marketType === 'parimutuel') {
|
|
140
|
+
return {
|
|
141
|
+
marketType: 'parimutuel',
|
|
142
|
+
abi: PARI_MUTUEL_BUY_ABI,
|
|
143
|
+
functionName: 'buy',
|
|
144
|
+
args: [isYes, amountRaw, minSharesOutRaw],
|
|
145
|
+
signature: 'buy(bool,uint256,uint256)',
|
|
146
|
+
ammDeadlineEpoch: null,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (marketType === 'amm') {
|
|
151
|
+
const nowEpochSec = toEpochSeconds(input.nowEpochSec, Math.trunc(Date.now() / 1000));
|
|
152
|
+
const offsetSec = toEpochSeconds(input.ammDeadlineOffsetSec, DEFAULT_AMM_TRADE_DEADLINE_OFFSET_SEC);
|
|
153
|
+
const deadlineEpoch = BigInt(nowEpochSec + Math.max(1, offsetSec));
|
|
154
|
+
return {
|
|
155
|
+
marketType: 'amm',
|
|
156
|
+
abi: PREDICTION_AMM_BUY_ABI,
|
|
157
|
+
functionName: 'buy',
|
|
158
|
+
args: [isYes, amountRaw, minSharesOutRaw, deadlineEpoch],
|
|
159
|
+
signature: 'buy(bool,uint256,uint256,uint256)',
|
|
160
|
+
ammDeadlineEpoch: deadlineEpoch.toString(),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
throw createTradeTypeError('UNSUPPORTED_MARKET_TYPE', `Unsupported market type for trade execution: ${marketType}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Resolves market type then constructs the correct buy call descriptor.
|
|
169
|
+
* @param {{
|
|
170
|
+
* publicClient: { readContract: Function },
|
|
171
|
+
* marketAddress: `0x${string}`,
|
|
172
|
+
* side: 'yes'|'no',
|
|
173
|
+
* amountRaw: bigint,
|
|
174
|
+
* minSharesOutRaw: bigint,
|
|
175
|
+
* nowEpochSec?: number,
|
|
176
|
+
* ammDeadlineOffsetSec?: number,
|
|
177
|
+
* }} input
|
|
178
|
+
* @returns {Promise<{marketType:'parimutuel'|'amm', detectedBy:string, abi: object[], functionName: 'buy', args: (boolean|bigint)[], signature: string, ammDeadlineEpoch?: string}>}
|
|
179
|
+
*/
|
|
180
|
+
async function resolveTradeBuyCall(input) {
|
|
181
|
+
const detected = await detectTradeMarketType(input.publicClient, input.marketAddress);
|
|
182
|
+
const call = buildTradeBuyCall({
|
|
183
|
+
marketType: detected.marketType,
|
|
184
|
+
side: input.side,
|
|
185
|
+
amountRaw: input.amountRaw,
|
|
186
|
+
minSharesOutRaw: input.minSharesOutRaw,
|
|
187
|
+
nowEpochSec: input.nowEpochSec,
|
|
188
|
+
ammDeadlineOffsetSec: input.ammDeadlineOffsetSec,
|
|
189
|
+
});
|
|
190
|
+
return {
|
|
191
|
+
...call,
|
|
192
|
+
detectedBy: detected.detectedBy,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = {
|
|
197
|
+
PARI_MUTUEL_BUY_ABI,
|
|
198
|
+
PREDICTION_AMM_BUY_ABI,
|
|
199
|
+
DEFAULT_AMM_TRADE_DEADLINE_OFFSET_SEC,
|
|
200
|
+
detectTradeMarketType,
|
|
201
|
+
buildTradeBuyCall,
|
|
202
|
+
resolveTradeBuyCall,
|
|
203
|
+
};
|
package/cli/pandora.cjs
CHANGED
|
@@ -60,6 +60,7 @@ const { createRunOddsCommand } = require('./lib/odds_command_service.cjs');
|
|
|
60
60
|
const { createRunSportsCommand } = require('./lib/sports_command_service.cjs');
|
|
61
61
|
const { createRunRiskCommand } = require('./lib/risk_command_service.cjs');
|
|
62
62
|
const { createRunModelCommand } = require('./lib/model_command_service.cjs');
|
|
63
|
+
const { resolveTradeBuyCall } = require('./lib/trade_market_type_service.cjs');
|
|
63
64
|
const {
|
|
64
65
|
DEFAULT_INDEXER_URL: SHARED_DEFAULT_INDEXER_URL,
|
|
65
66
|
DEFAULT_RPC_BY_CHAIN_ID,
|
|
@@ -598,20 +599,6 @@ const ERC20_ABI = [
|
|
|
598
599
|
outputs: [{ type: 'uint256' }],
|
|
599
600
|
},
|
|
600
601
|
];
|
|
601
|
-
const PARI_MUTUEL_ABI = [
|
|
602
|
-
{
|
|
603
|
-
name: 'buy',
|
|
604
|
-
type: 'function',
|
|
605
|
-
stateMutability: 'nonpayable',
|
|
606
|
-
inputs: [
|
|
607
|
-
{ name: 'isYes', type: 'bool' },
|
|
608
|
-
{ name: 'collateralAmount', type: 'uint256' },
|
|
609
|
-
{ name: 'minSharesOut', type: 'uint256' },
|
|
610
|
-
],
|
|
611
|
-
outputs: [{ name: 'sharesOut', type: 'uint256' }],
|
|
612
|
-
},
|
|
613
|
-
];
|
|
614
|
-
|
|
615
602
|
const MARKET_DIRECT_ODDS_FIELDS = [
|
|
616
603
|
{ yesField: 'yesPct', noField: 'noPct', source: 'direct:yesPct/noPct' },
|
|
617
604
|
{ yesField: 'yesOdds', noField: 'noOdds', source: 'direct:yesOdds/noOdds' },
|
|
@@ -897,10 +884,10 @@ Usage:
|
|
|
897
884
|
|
|
898
885
|
Notes:
|
|
899
886
|
- --dry-run prints the execution plan and quote without sending transactions.
|
|
900
|
-
- --execute performs allowance check, optional USDC approve, then calls buy(
|
|
887
|
+
- --execute performs allowance check, optional USDC approve, then calls market buy() using the detected market ABI.
|
|
901
888
|
- --max-amount-usdc and probability guard flags fail fast before execution.
|
|
902
889
|
- --execute requires a quote by default unless --min-shares-out-raw or --allow-unquoted-execute is set.
|
|
903
|
-
-
|
|
890
|
+
- Supports both PariMutuel and AMM market buy signatures.
|
|
904
891
|
`);
|
|
905
892
|
}
|
|
906
893
|
|
|
@@ -3604,6 +3591,28 @@ async function executeTradeOnchain(options) {
|
|
|
3604
3591
|
});
|
|
3605
3592
|
};
|
|
3606
3593
|
|
|
3594
|
+
let buyCall;
|
|
3595
|
+
try {
|
|
3596
|
+
buyCall = await resolveTradeBuyCall({
|
|
3597
|
+
publicClient,
|
|
3598
|
+
marketAddress: options.marketAddress,
|
|
3599
|
+
side: options.side,
|
|
3600
|
+
amountRaw,
|
|
3601
|
+
minSharesOutRaw,
|
|
3602
|
+
});
|
|
3603
|
+
} catch (error) {
|
|
3604
|
+
if (error && error.code) {
|
|
3605
|
+
throw new CliError(
|
|
3606
|
+
error.code,
|
|
3607
|
+
error.message || 'Unsupported market trade interface.',
|
|
3608
|
+
error.details,
|
|
3609
|
+
);
|
|
3610
|
+
}
|
|
3611
|
+
await decodeTradeError(error, 'TRADE_MARKET_TYPE_RESOLUTION_FAILED', 'Unable to resolve market trade interface.', {
|
|
3612
|
+
stage: 'market-type-resolve',
|
|
3613
|
+
});
|
|
3614
|
+
}
|
|
3615
|
+
|
|
3607
3616
|
let allowance;
|
|
3608
3617
|
try {
|
|
3609
3618
|
allowance = await publicClient.readContract({
|
|
@@ -3660,9 +3669,9 @@ async function executeTradeOnchain(options) {
|
|
|
3660
3669
|
const buySimulation = await publicClient.simulateContract({
|
|
3661
3670
|
account,
|
|
3662
3671
|
address: options.marketAddress,
|
|
3663
|
-
abi:
|
|
3664
|
-
functionName:
|
|
3665
|
-
args:
|
|
3672
|
+
abi: buyCall.abi,
|
|
3673
|
+
functionName: buyCall.functionName,
|
|
3674
|
+
args: buyCall.args,
|
|
3666
3675
|
});
|
|
3667
3676
|
buyGasEstimate =
|
|
3668
3677
|
buySimulation && buySimulation.request && buySimulation.request.gas
|
|
@@ -3675,6 +3684,9 @@ async function executeTradeOnchain(options) {
|
|
|
3675
3684
|
await decodeTradeError(error, 'TRADE_EXECUTION_FAILED', 'Buy transaction failed.', {
|
|
3676
3685
|
stage: 'buy',
|
|
3677
3686
|
buyTxHash,
|
|
3687
|
+
marketType: buyCall ? buyCall.marketType : null,
|
|
3688
|
+
buySignature: buyCall ? buyCall.signature : null,
|
|
3689
|
+
ammDeadlineEpoch: buyCall && buyCall.ammDeadlineEpoch ? buyCall.ammDeadlineEpoch : null,
|
|
3678
3690
|
});
|
|
3679
3691
|
}
|
|
3680
3692
|
|
|
@@ -3684,6 +3696,9 @@ async function executeTradeOnchain(options) {
|
|
|
3684
3696
|
rpcUrl: runtime.rpcUrl,
|
|
3685
3697
|
account: account.address,
|
|
3686
3698
|
usdc: runtime.usdcAddress,
|
|
3699
|
+
marketType: buyCall.marketType,
|
|
3700
|
+
buySignature: buyCall.signature,
|
|
3701
|
+
ammDeadlineEpoch: buyCall.ammDeadlineEpoch,
|
|
3687
3702
|
amountRaw: amountRaw.toString(),
|
|
3688
3703
|
minSharesOutRaw: minSharesOutRaw.toString(),
|
|
3689
3704
|
approveTxHash,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pandora-cli-skills",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.47",
|
|
4
4
|
"description": "Pandora CLI & Skills",
|
|
5
5
|
"main": "cli/pandora.cjs",
|
|
6
6
|
"bin": {
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"tests/cli/**",
|
|
20
20
|
"tests/helpers/**",
|
|
21
21
|
"tests/smoke/**",
|
|
22
|
+
"tsconfig.json",
|
|
22
23
|
"SKILL.md",
|
|
23
24
|
"README.md",
|
|
24
25
|
"README_FOR_SHARING.md"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const test = require('node:test');
|
|
2
|
+
const assert = require('node:assert/strict');
|
|
3
|
+
|
|
4
|
+
const {
|
|
5
|
+
DEFAULT_AMM_TRADE_DEADLINE_OFFSET_SEC,
|
|
6
|
+
detectTradeMarketType,
|
|
7
|
+
buildTradeBuyCall,
|
|
8
|
+
resolveTradeBuyCall,
|
|
9
|
+
} = require('../../cli/lib/trade_market_type_service.cjs');
|
|
10
|
+
|
|
11
|
+
const MARKET = '0x1111111111111111111111111111111111111111';
|
|
12
|
+
|
|
13
|
+
test('detectTradeMarketType resolves parimutuel marker first', async () => {
|
|
14
|
+
const publicClient = {
|
|
15
|
+
async readContract(request) {
|
|
16
|
+
if (request.functionName === 'curveFlattener') return 7n;
|
|
17
|
+
throw new Error('unexpected');
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const detected = await detectTradeMarketType(publicClient, MARKET);
|
|
22
|
+
assert.equal(detected.marketType, 'parimutuel');
|
|
23
|
+
assert.equal(detected.detectedBy, 'curveFlattener');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('detectTradeMarketType falls back to amm marker', async () => {
|
|
27
|
+
const publicClient = {
|
|
28
|
+
async readContract(request) {
|
|
29
|
+
if (request.functionName === 'curveFlattener') throw new Error('function selector was not recognized');
|
|
30
|
+
if (request.functionName === 'tradingFee') return 3000n;
|
|
31
|
+
throw new Error('unexpected');
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const detected = await detectTradeMarketType(publicClient, MARKET);
|
|
36
|
+
assert.equal(detected.marketType, 'amm');
|
|
37
|
+
assert.equal(detected.detectedBy, 'tradingFee');
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('detectTradeMarketType throws unsupported interface when neither marker exists', async () => {
|
|
41
|
+
const publicClient = {
|
|
42
|
+
async readContract() {
|
|
43
|
+
throw new Error('reverted');
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
await assert.rejects(
|
|
48
|
+
() => detectTradeMarketType(publicClient, MARKET),
|
|
49
|
+
(error) => {
|
|
50
|
+
assert.equal(error.code, 'UNSUPPORTED_MARKET_TRADE_INTERFACE');
|
|
51
|
+
assert.equal(error.details.marketAddress, MARKET);
|
|
52
|
+
return true;
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('buildTradeBuyCall uses 3-arg buy for parimutuel', () => {
|
|
58
|
+
const call = buildTradeBuyCall({
|
|
59
|
+
marketType: 'parimutuel',
|
|
60
|
+
side: 'yes',
|
|
61
|
+
amountRaw: 1_000_000n,
|
|
62
|
+
minSharesOutRaw: 0n,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
assert.equal(call.signature, 'buy(bool,uint256,uint256)');
|
|
66
|
+
assert.deepEqual(call.args, [true, 1_000_000n, 0n]);
|
|
67
|
+
assert.equal(call.ammDeadlineEpoch, null);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('buildTradeBuyCall uses 4-arg deadline buy for amm', () => {
|
|
71
|
+
const nowEpochSec = 1_700_000_000;
|
|
72
|
+
const call = buildTradeBuyCall({
|
|
73
|
+
marketType: 'amm',
|
|
74
|
+
side: 'no',
|
|
75
|
+
amountRaw: 2_500_000n,
|
|
76
|
+
minSharesOutRaw: 12n,
|
|
77
|
+
nowEpochSec,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
assert.equal(call.signature, 'buy(bool,uint256,uint256,uint256)');
|
|
81
|
+
assert.equal(call.args.length, 4);
|
|
82
|
+
assert.deepEqual(call.args.slice(0, 3), [false, 2_500_000n, 12n]);
|
|
83
|
+
assert.equal(call.ammDeadlineEpoch, String(nowEpochSec + DEFAULT_AMM_TRADE_DEADLINE_OFFSET_SEC));
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('resolveTradeBuyCall composes detection and call creation', async () => {
|
|
87
|
+
const publicClient = {
|
|
88
|
+
async readContract(request) {
|
|
89
|
+
if (request.functionName === 'curveFlattener') throw new Error('not pari');
|
|
90
|
+
if (request.functionName === 'tradingFee') return 500n;
|
|
91
|
+
throw new Error('unexpected');
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const call = await resolveTradeBuyCall({
|
|
96
|
+
publicClient,
|
|
97
|
+
marketAddress: MARKET,
|
|
98
|
+
side: 'yes',
|
|
99
|
+
amountRaw: 9n,
|
|
100
|
+
minSharesOutRaw: 1n,
|
|
101
|
+
nowEpochSec: 1000,
|
|
102
|
+
ammDeadlineOffsetSec: 5,
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
assert.equal(call.marketType, 'amm');
|
|
106
|
+
assert.equal(call.detectedBy, 'tradingFee');
|
|
107
|
+
assert.equal(call.signature, 'buy(bool,uint256,uint256,uint256)');
|
|
108
|
+
assert.deepEqual(call.args, [true, 9n, 1n, 1005n]);
|
|
109
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"strict": false,
|
|
9
|
+
"noEmit": true,
|
|
10
|
+
"types": ["node"]
|
|
11
|
+
},
|
|
12
|
+
"include": ["scripts/**/*.ts"]
|
|
13
|
+
}
|