mirror of
https://github.com/logos-blockchain/lez-programs.git
synced 2026-08-25 06:01:11 +00:00
feat(amm-client): freeze wire contract
This commit is contained in:
@@ -71,9 +71,9 @@ Every call returns an owned JSON envelope. Release it exactly once with `amm_cli
|
||||
`NULL` to the free function is allowed. See [`include/amm_client.h`](include/amm_client.h) and
|
||||
[`docs/wire-api.md`](docs/wire-api.md) for the complete transport contract.
|
||||
|
||||
Raw `u128` and `u64` values cross JSON as decimal strings. Account IDs use their canonical base58
|
||||
display form, program IDs use eight JSON `u32` words, account data uses hexadecimal, and encoded
|
||||
instruction words remain JSON `u32` numbers. No JavaScript `Number` conversion is required for
|
||||
chain amounts or deadlines. Plan JSON also includes typed `instructionArgs`, derived directly from
|
||||
the same `amm_core::Instruction` encoded in `instructionWords`. Both C entrypoints accept the five
|
||||
snapshot-bound `prepare_*_transaction` operations.
|
||||
Raw `u128` and `u64` values cross JSON as decimal strings. Account IDs use canonical base58.
|
||||
Program IDs use 64-character lowercase hexadecimal strings. Account data uses hexadecimal, and
|
||||
encoded instruction words remain JSON `u32` numbers. No JavaScript `Number` conversion is required
|
||||
for chain amounts or deadlines. Plan JSON also includes typed `instructionArgs`, derived directly
|
||||
from the same `amm_core::Instruction` encoded in `instructionWords`. Only `amm_client_plan` accepts
|
||||
the five snapshot-bound `prepare_*_transaction` operations.
|
||||
|
||||
@@ -14,9 +14,10 @@ Requests may include `"schema":"amm-client.v1"`. Schema-less requests remain acc
|
||||
compatibility. Every successful wire value and every C envelope identifies the response schema.
|
||||
|
||||
All `u128` amounts, reserves, supplies, fees, nonces, and balances are unsigned decimal strings.
|
||||
All `u64` windows and deadlines are also decimal strings. Program IDs are arrays of eight `u32`
|
||||
words. Signed ticks are decimal strings. Account IDs are base58 strings. Account `data` is an
|
||||
even-length hexadecimal string.
|
||||
All `u64` windows and deadlines are also decimal strings. Program IDs are exactly 64 lowercase
|
||||
hexadecimal characters: the 32 bytes formed by concatenating the eight `u32` words in
|
||||
little-endian byte order. Signed ticks are decimal strings. Account IDs use canonical base58.
|
||||
Account `data` is an even-length hexadecimal string.
|
||||
|
||||
## Shared inputs
|
||||
|
||||
@@ -24,9 +25,9 @@ Plan context:
|
||||
|
||||
```json
|
||||
{
|
||||
"ammProgramId": [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"tokenProgramId": [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"twapOracleProgramId": [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"ammProgramId": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"tokenProgramId": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"twapOracleProgramId": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"authority": "base58-account-id"
|
||||
}
|
||||
```
|
||||
@@ -53,7 +54,7 @@ Fetched account snapshot used by quotes:
|
||||
```json
|
||||
{
|
||||
"id": "base58-account-id",
|
||||
"programOwner": [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"programOwner": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"balance": "0",
|
||||
"nonce": "0",
|
||||
"data": "00ff"
|
||||
@@ -64,7 +65,7 @@ Existing-pool quote operations include these top-level state fields:
|
||||
|
||||
```json
|
||||
{
|
||||
"ammProgramId": [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"ammProgramId": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"config": { "...": "account snapshot" },
|
||||
"snapshot": {
|
||||
"pool": { "...": "account snapshot" },
|
||||
@@ -128,7 +129,7 @@ A successful plan value contains the following fields (`instructionWords` is abb
|
||||
"maxAmountToAddTokenB": "100",
|
||||
"deadline": "1900000000000"
|
||||
},
|
||||
"programId": [0, 0, 0, 0, 0, 0, 0, 0],
|
||||
"programId": "0000000000000000000000000000000000000000000000000000000000000000",
|
||||
"accounts": [
|
||||
{
|
||||
"id": "base58-account-id",
|
||||
@@ -152,8 +153,8 @@ strings. Account rows follow guest/IDL order.
|
||||
## Quote operations
|
||||
|
||||
Send requests to `amm_client_quote` or `wire::quote_json`. Pool economic operations use the
|
||||
existing-pool quote state described above. Discovery, opening intent, and task-transaction
|
||||
operations use the fields shown in this table and the sections below.
|
||||
existing-pool quote state described above. Discovery and opening-intent operations use the fields
|
||||
shown in this table and the sections below.
|
||||
|
||||
| `operation` | Additional fields |
|
||||
|---|---|
|
||||
@@ -185,11 +186,6 @@ operations use the fields shown in this table and the sections below.
|
||||
| `swap_exact_output` | `userInputHolding`, `userOutputHolding`, `inputTokenDefinitionId`, `exactAmountOut`, `maximumAmountIn` |
|
||||
| `sync_reserves` | no additional fields |
|
||||
| `create_oracle_price_account` | `windowDuration` |
|
||||
| `prepare_create_pool_transaction` | task-transaction fields below |
|
||||
| `prepare_add_liquidity_transaction` | task-transaction fields below |
|
||||
| `prepare_remove_liquidity_transaction` | task-transaction fields below |
|
||||
| `prepare_swap_exact_input_transaction` | task-transaction fields below |
|
||||
| `prepare_swap_exact_output_transaction` | task-transaction fields below |
|
||||
|
||||
Quote values use these result shapes:
|
||||
|
||||
@@ -234,9 +230,10 @@ amounts.
|
||||
|
||||
## Task transactions
|
||||
|
||||
The five snapshot-bound task operations are accepted by both `amm_client_plan`/`wire::plan_json`
|
||||
and `amm_client_quote`/`wire::quote_json`. Every request includes `ammProgramId`, raw `config`, the
|
||||
complete caller-ordered `snapshots`, and decimal-string `deadline`.
|
||||
The five snapshot-bound task operations are accepted only by
|
||||
`amm_client_plan`/`wire::plan_json`. Every request includes `ammProgramId`, raw `config`, the
|
||||
complete caller-ordered `snapshots`, and decimal-string `deadline`. The quote endpoint rejects
|
||||
these operation tags with `invalid_request`.
|
||||
|
||||
| `operation` | Additional fields |
|
||||
|---|---|
|
||||
@@ -314,5 +311,11 @@ The client validates account decoding, configured owners, canonical PDAs, pool/v
|
||||
relationships, swap input/output pairing, and required input balances. Quote arithmetic failures
|
||||
retain the stable `amm_program::quote::QuoteError` code.
|
||||
|
||||
Every failure uses `{ "code": "...", "message": "..." }`. `code` is the stable
|
||||
machine-readable contract; `message` is diagnostic text. JSON adapter failures return
|
||||
`invalid_request` or `unsupported_schema`. The C envelope additionally returns `null_request`,
|
||||
`invalid_utf8`, `invalid_json`, `response_serialization_failed`, or `response_contains_nul` for
|
||||
boundary failures.
|
||||
|
||||
No request performs network I/O or checks an ImageID, release version, compatibility manifest, or
|
||||
program allowlist. Deployment configuration is expected to select the corresponding AMM build.
|
||||
|
||||
@@ -19,18 +19,18 @@ char *amm_client_plan(const char *request_json);
|
||||
* Accepts a tagged UTF-8 JSON request and returns an owned UTF-8 JSON envelope.
|
||||
* Supported operation tags include protocol constants; config and pair discovery;
|
||||
* pair inspection; caller-order opening preparation; economic quote/preparation
|
||||
* operations; and prepare_create_pool_transaction,
|
||||
* prepare_add_liquidity_transaction, prepare_remove_liquidity_transaction,
|
||||
* prepare_swap_exact_input_transaction, and
|
||||
* prepare_swap_exact_output_transaction. See docs/wire-api.md for fields.
|
||||
* operations; reserve synchronization; and oracle-price initialization.
|
||||
* Snapshot-bound prepare_*_transaction operations belong to amm_client_plan.
|
||||
* See docs/wire-api.md for fields.
|
||||
* Release the result with amm_client_free.
|
||||
*/
|
||||
char *amm_client_quote(const char *request_json);
|
||||
|
||||
/*
|
||||
* Raw u128, u64, and signed tick values are decimal JSON strings. Program IDs
|
||||
* and instruction words are JSON u32 arrays. Account IDs are base58 strings and
|
||||
* account data is hexadecimal. Requests may carry schema "amm-client.v1";
|
||||
* are 64-character lowercase hexadecimal strings. Instruction words are JSON
|
||||
* u32 arrays. Account IDs use canonical base58 and account data is hexadecimal.
|
||||
* Requests may carry schema "amm-client.v1";
|
||||
* schema-less legacy requests remain accepted. Responses use
|
||||
* {"schema":"amm-client.v1","ok":true,"value":...} or the same envelope
|
||||
* with ok=false and error={"code":...,"message":...}. Plan values contain
|
||||
|
||||
+334
-295
@@ -90,24 +90,42 @@ impl From<TransactionError> for WireError {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Deserialize)]
|
||||
#[serde(try_from = "String")]
|
||||
struct ProgramIdInput(ProgramId);
|
||||
|
||||
impl From<ProgramIdInput> for ProgramId {
|
||||
fn from(value: ProgramIdInput) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ProgramIdInput {
|
||||
type Error = String;
|
||||
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
parse_program_id(&value).map(Self)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(tag = "operation", rename_all = "snake_case")]
|
||||
enum PlanRequest {
|
||||
Initialize {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramId,
|
||||
amm_program_id: ProgramIdInput,
|
||||
#[serde(rename = "tokenProgramId")]
|
||||
token_program_id: ProgramId,
|
||||
token_program_id: ProgramIdInput,
|
||||
#[serde(rename = "twapOracleProgramId")]
|
||||
twap_oracle_program_id: ProgramId,
|
||||
twap_oracle_program_id: ProgramIdInput,
|
||||
authority: String,
|
||||
},
|
||||
UpdateConfig {
|
||||
context: ContextInput,
|
||||
#[serde(rename = "tokenProgramId")]
|
||||
token_program_id: Option<ProgramId>,
|
||||
token_program_id: Option<ProgramIdInput>,
|
||||
#[serde(rename = "twapOracleProgramId")]
|
||||
twap_oracle_program_id: Option<ProgramId>,
|
||||
twap_oracle_program_id: Option<ProgramIdInput>,
|
||||
#[serde(rename = "newAuthority")]
|
||||
new_authority: Option<String>,
|
||||
},
|
||||
@@ -208,24 +226,137 @@ enum PlanRequest {
|
||||
context: ContextInput,
|
||||
pool: PoolInput,
|
||||
},
|
||||
PrepareCreatePoolTransaction {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramIdInput,
|
||||
config: AccountSnapshotInput,
|
||||
snapshots: Box<PairReadSnapshotsInput>,
|
||||
#[serde(rename = "firstTokenDefinitionId")]
|
||||
first_token_definition_id: String,
|
||||
#[serde(rename = "secondTokenDefinitionId")]
|
||||
second_token_definition_id: String,
|
||||
#[serde(rename = "firstTokenHolding")]
|
||||
first_token_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "secondTokenHolding")]
|
||||
second_token_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "liquidityHolding")]
|
||||
liquidity_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "firstAmount")]
|
||||
first_amount: String,
|
||||
#[serde(rename = "secondAmount")]
|
||||
second_amount: String,
|
||||
#[serde(rename = "feeBps")]
|
||||
fee_bps: String,
|
||||
deadline: String,
|
||||
},
|
||||
PrepareAddLiquidityTransaction {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramIdInput,
|
||||
config: AccountSnapshotInput,
|
||||
snapshots: Box<PairReadSnapshotsInput>,
|
||||
#[serde(rename = "firstTokenDefinitionId")]
|
||||
first_token_definition_id: String,
|
||||
#[serde(rename = "secondTokenDefinitionId")]
|
||||
second_token_definition_id: String,
|
||||
#[serde(rename = "firstTokenHolding")]
|
||||
first_token_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "secondTokenHolding")]
|
||||
second_token_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "liquidityHolding")]
|
||||
liquidity_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "maxFirstAmount")]
|
||||
max_first_amount: String,
|
||||
#[serde(rename = "maxSecondAmount")]
|
||||
max_second_amount: String,
|
||||
#[serde(rename = "slippageBps")]
|
||||
slippage_bps: String,
|
||||
#[serde(rename = "expectedFeeBps")]
|
||||
expected_fee_bps: Option<String>,
|
||||
deadline: String,
|
||||
},
|
||||
PrepareRemoveLiquidityTransaction {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramIdInput,
|
||||
config: AccountSnapshotInput,
|
||||
snapshots: Box<PairReadSnapshotsInput>,
|
||||
#[serde(rename = "firstTokenDefinitionId")]
|
||||
first_token_definition_id: String,
|
||||
#[serde(rename = "secondTokenDefinitionId")]
|
||||
second_token_definition_id: String,
|
||||
#[serde(rename = "firstTokenHolding")]
|
||||
first_token_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "secondTokenHolding")]
|
||||
second_token_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "liquidityHolding")]
|
||||
liquidity_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "removeLiquidityAmount")]
|
||||
remove_liquidity_amount: String,
|
||||
#[serde(rename = "slippageBps")]
|
||||
slippage_bps: String,
|
||||
#[serde(rename = "expectedFeeBps")]
|
||||
expected_fee_bps: Option<String>,
|
||||
deadline: String,
|
||||
},
|
||||
PrepareSwapExactInputTransaction {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramIdInput,
|
||||
config: AccountSnapshotInput,
|
||||
snapshots: Box<PairReadSnapshotsInput>,
|
||||
#[serde(rename = "inputTokenDefinitionId")]
|
||||
input_token_definition_id: String,
|
||||
#[serde(rename = "outputTokenDefinitionId")]
|
||||
output_token_definition_id: String,
|
||||
#[serde(rename = "inputHolding")]
|
||||
input_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "outputHolding")]
|
||||
output_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "amountIn")]
|
||||
amount_in: String,
|
||||
#[serde(rename = "slippageBps")]
|
||||
slippage_bps: String,
|
||||
#[serde(rename = "expectedFeeBps")]
|
||||
expected_fee_bps: Option<String>,
|
||||
deadline: String,
|
||||
},
|
||||
PrepareSwapExactOutputTransaction {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramIdInput,
|
||||
config: AccountSnapshotInput,
|
||||
snapshots: Box<PairReadSnapshotsInput>,
|
||||
#[serde(rename = "inputTokenDefinitionId")]
|
||||
input_token_definition_id: String,
|
||||
#[serde(rename = "outputTokenDefinitionId")]
|
||||
output_token_definition_id: String,
|
||||
#[serde(rename = "inputHolding")]
|
||||
input_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "outputHolding")]
|
||||
output_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "exactAmountOut")]
|
||||
exact_amount_out: String,
|
||||
#[serde(rename = "slippageBps")]
|
||||
slippage_bps: String,
|
||||
#[serde(rename = "expectedFeeBps")]
|
||||
expected_fee_bps: Option<String>,
|
||||
deadline: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ContextInput {
|
||||
amm_program_id: ProgramId,
|
||||
token_program_id: ProgramId,
|
||||
twap_oracle_program_id: ProgramId,
|
||||
amm_program_id: ProgramIdInput,
|
||||
token_program_id: ProgramIdInput,
|
||||
twap_oracle_program_id: ProgramIdInput,
|
||||
authority: String,
|
||||
}
|
||||
|
||||
impl ContextInput {
|
||||
fn into_context(self) -> Result<AmmContext, WireError> {
|
||||
Ok(AmmContext::new(
|
||||
self.amm_program_id,
|
||||
self.amm_program_id.into(),
|
||||
AmmConfig {
|
||||
token_program_id: self.token_program_id,
|
||||
twap_oracle_program_id: self.twap_oracle_program_id,
|
||||
token_program_id: self.token_program_id.into(),
|
||||
twap_oracle_program_id: self.twap_oracle_program_id.into(),
|
||||
authority: account_id(&self.authority, "context.authority")?,
|
||||
},
|
||||
))
|
||||
@@ -281,11 +412,11 @@ enum QuoteRequest {
|
||||
ProtocolConstants,
|
||||
DeriveConfigId {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramId,
|
||||
amm_program_id: ProgramIdInput,
|
||||
},
|
||||
InspectConfig {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramId,
|
||||
amm_program_id: ProgramIdInput,
|
||||
config: AccountSnapshotInput,
|
||||
},
|
||||
CanonicalPair {
|
||||
@@ -296,7 +427,7 @@ enum QuoteRequest {
|
||||
},
|
||||
DerivePairReadManifest {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramId,
|
||||
amm_program_id: ProgramIdInput,
|
||||
config: AccountSnapshotInput,
|
||||
#[serde(rename = "firstTokenDefinitionId")]
|
||||
first_token_definition_id: String,
|
||||
@@ -305,7 +436,7 @@ enum QuoteRequest {
|
||||
},
|
||||
InspectPair {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramId,
|
||||
amm_program_id: ProgramIdInput,
|
||||
config: AccountSnapshotInput,
|
||||
#[serde(rename = "firstTokenDefinitionId")]
|
||||
first_token_definition_id: String,
|
||||
@@ -324,119 +455,6 @@ enum QuoteRequest {
|
||||
fee_bps: String,
|
||||
intent: OpeningLiquidityIntentInput,
|
||||
},
|
||||
PrepareCreatePoolTransaction {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramId,
|
||||
config: AccountSnapshotInput,
|
||||
snapshots: Box<PairReadSnapshotsInput>,
|
||||
#[serde(rename = "firstTokenDefinitionId")]
|
||||
first_token_definition_id: String,
|
||||
#[serde(rename = "secondTokenDefinitionId")]
|
||||
second_token_definition_id: String,
|
||||
#[serde(rename = "firstTokenHolding")]
|
||||
first_token_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "secondTokenHolding")]
|
||||
second_token_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "liquidityHolding")]
|
||||
liquidity_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "firstAmount")]
|
||||
first_amount: String,
|
||||
#[serde(rename = "secondAmount")]
|
||||
second_amount: String,
|
||||
#[serde(rename = "feeBps")]
|
||||
fee_bps: String,
|
||||
deadline: String,
|
||||
},
|
||||
PrepareAddLiquidityTransaction {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramId,
|
||||
config: AccountSnapshotInput,
|
||||
snapshots: Box<PairReadSnapshotsInput>,
|
||||
#[serde(rename = "firstTokenDefinitionId")]
|
||||
first_token_definition_id: String,
|
||||
#[serde(rename = "secondTokenDefinitionId")]
|
||||
second_token_definition_id: String,
|
||||
#[serde(rename = "firstTokenHolding")]
|
||||
first_token_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "secondTokenHolding")]
|
||||
second_token_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "liquidityHolding")]
|
||||
liquidity_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "maxFirstAmount")]
|
||||
max_first_amount: String,
|
||||
#[serde(rename = "maxSecondAmount")]
|
||||
max_second_amount: String,
|
||||
#[serde(rename = "slippageBps")]
|
||||
slippage_bps: String,
|
||||
#[serde(rename = "expectedFeeBps")]
|
||||
expected_fee_bps: Option<String>,
|
||||
deadline: String,
|
||||
},
|
||||
PrepareRemoveLiquidityTransaction {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramId,
|
||||
config: AccountSnapshotInput,
|
||||
snapshots: Box<PairReadSnapshotsInput>,
|
||||
#[serde(rename = "firstTokenDefinitionId")]
|
||||
first_token_definition_id: String,
|
||||
#[serde(rename = "secondTokenDefinitionId")]
|
||||
second_token_definition_id: String,
|
||||
#[serde(rename = "firstTokenHolding")]
|
||||
first_token_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "secondTokenHolding")]
|
||||
second_token_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "liquidityHolding")]
|
||||
liquidity_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "removeLiquidityAmount")]
|
||||
remove_liquidity_amount: String,
|
||||
#[serde(rename = "slippageBps")]
|
||||
slippage_bps: String,
|
||||
#[serde(rename = "expectedFeeBps")]
|
||||
expected_fee_bps: Option<String>,
|
||||
deadline: String,
|
||||
},
|
||||
PrepareSwapExactInputTransaction {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramId,
|
||||
config: AccountSnapshotInput,
|
||||
snapshots: Box<PairReadSnapshotsInput>,
|
||||
#[serde(rename = "inputTokenDefinitionId")]
|
||||
input_token_definition_id: String,
|
||||
#[serde(rename = "outputTokenDefinitionId")]
|
||||
output_token_definition_id: String,
|
||||
#[serde(rename = "inputHolding")]
|
||||
input_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "outputHolding")]
|
||||
output_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "amountIn")]
|
||||
amount_in: String,
|
||||
#[serde(rename = "slippageBps")]
|
||||
slippage_bps: String,
|
||||
#[serde(rename = "expectedFeeBps")]
|
||||
expected_fee_bps: Option<String>,
|
||||
deadline: String,
|
||||
},
|
||||
PrepareSwapExactOutputTransaction {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramId,
|
||||
config: AccountSnapshotInput,
|
||||
snapshots: Box<PairReadSnapshotsInput>,
|
||||
#[serde(rename = "inputTokenDefinitionId")]
|
||||
input_token_definition_id: String,
|
||||
#[serde(rename = "outputTokenDefinitionId")]
|
||||
output_token_definition_id: String,
|
||||
#[serde(rename = "inputHolding")]
|
||||
input_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "outputHolding")]
|
||||
output_holding: AccountSnapshotInput,
|
||||
#[serde(rename = "exactAmountOut")]
|
||||
exact_amount_out: String,
|
||||
#[serde(rename = "slippageBps")]
|
||||
slippage_bps: String,
|
||||
#[serde(rename = "expectedFeeBps")]
|
||||
expected_fee_bps: Option<String>,
|
||||
deadline: String,
|
||||
},
|
||||
PrepareMinimumOpeningPair {
|
||||
#[serde(rename = "desiredPriceQ64_64")]
|
||||
desired_price_q64_64: String,
|
||||
@@ -479,7 +497,7 @@ enum QuoteRequest {
|
||||
},
|
||||
CreatePool {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramId,
|
||||
amm_program_id: ProgramIdInput,
|
||||
config: AccountSnapshotInput,
|
||||
#[serde(rename = "tokenADefinition")]
|
||||
token_a_definition: AccountSnapshotInput,
|
||||
@@ -494,7 +512,7 @@ enum QuoteRequest {
|
||||
},
|
||||
PrepareCreatePool {
|
||||
#[serde(rename = "ammProgramId")]
|
||||
amm_program_id: ProgramId,
|
||||
amm_program_id: ProgramIdInput,
|
||||
config: AccountSnapshotInput,
|
||||
#[serde(rename = "tokenADefinition")]
|
||||
token_a_definition: AccountSnapshotInput,
|
||||
@@ -660,7 +678,7 @@ enum QuoteRequest {
|
||||
#[derive(Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct PoolStateInput {
|
||||
amm_program_id: ProgramId,
|
||||
amm_program_id: ProgramIdInput,
|
||||
config: AccountSnapshotInput,
|
||||
snapshot: PoolSnapshotInput,
|
||||
}
|
||||
@@ -668,7 +686,7 @@ struct PoolStateInput {
|
||||
impl PoolStateInput {
|
||||
fn validate(self) -> Result<(AmmContext, ValidatedPoolSnapshot), WireError> {
|
||||
let config = self.config.into_snapshot()?;
|
||||
let context = AmmContext::from_config_account(self.amm_program_id, &config)?;
|
||||
let context = AmmContext::from_config_account(self.amm_program_id.into(), &config)?;
|
||||
let snapshot = self.snapshot.validate(&context)?;
|
||||
Ok((context, snapshot))
|
||||
}
|
||||
@@ -807,7 +825,7 @@ impl PoolSnapshotInput {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct AccountSnapshotInput {
|
||||
id: String,
|
||||
program_owner: ProgramId,
|
||||
program_owner: ProgramIdInput,
|
||||
balance: String,
|
||||
nonce: String,
|
||||
data: String,
|
||||
@@ -821,7 +839,7 @@ impl AccountSnapshotInput {
|
||||
Ok(AccountSnapshot::new(
|
||||
account_id(&self.id, "account.id")?,
|
||||
Account {
|
||||
program_owner: self.program_owner,
|
||||
program_owner: self.program_owner.into(),
|
||||
balance: decimal_u128(&self.balance, "account.balance")?,
|
||||
data,
|
||||
nonce: Nonce(decimal_u128(&self.nonce, "account.nonce")?),
|
||||
@@ -833,27 +851,20 @@ impl AccountSnapshotInput {
|
||||
/// Builds a canonical low-level plan or prepares a snapshot-bound task transaction from JSON.
|
||||
pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
validate_wire_schema(&value)?;
|
||||
if value
|
||||
.get("operation")
|
||||
.and_then(Value::as_str)
|
||||
.is_some_and(is_prepared_transaction_operation)
|
||||
{
|
||||
return quote_json(value);
|
||||
}
|
||||
let request: PlanRequest = serde_json::from_value(value)
|
||||
.map_err(|error| invalid_request(format!("invalid plan request: {error}")))?;
|
||||
let plan = match request {
|
||||
versioned(match request {
|
||||
PlanRequest::Initialize {
|
||||
amm_program_id,
|
||||
token_program_id,
|
||||
twap_oracle_program_id,
|
||||
authority,
|
||||
} => plan_initialize(InitializePlanInput {
|
||||
amm_program_id,
|
||||
token_program_id,
|
||||
twap_oracle_program_id,
|
||||
} => transaction_plan_json(&plan_initialize(InitializePlanInput {
|
||||
amm_program_id: amm_program_id.into(),
|
||||
token_program_id: token_program_id.into(),
|
||||
twap_oracle_program_id: twap_oracle_program_id.into(),
|
||||
authority: account_id(&authority, "authority")?,
|
||||
}),
|
||||
})),
|
||||
PlanRequest::UpdateConfig {
|
||||
context,
|
||||
token_program_id,
|
||||
@@ -865,12 +876,12 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
.as_deref()
|
||||
.map(|value| account_id(value, "newAuthority"))
|
||||
.transpose()?;
|
||||
plan_update_config(UpdateConfigPlanInput {
|
||||
transaction_plan_json(&plan_update_config(UpdateConfigPlanInput {
|
||||
context: &context,
|
||||
token_program_id,
|
||||
twap_oracle_program_id,
|
||||
token_program_id: token_program_id.map(Into::into),
|
||||
twap_oracle_program_id: twap_oracle_program_id.map(Into::into),
|
||||
new_authority,
|
||||
})
|
||||
}))
|
||||
}
|
||||
PlanRequest::CreatePriceObservations {
|
||||
context,
|
||||
@@ -878,11 +889,13 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
window_duration,
|
||||
} => {
|
||||
let context = context.into_context()?;
|
||||
plan_create_price_observations(CreatePriceObservationsPlanInput {
|
||||
context: &context,
|
||||
pool_id: account_id(&pool_id, "poolId")?,
|
||||
window_duration: decimal_u64(&window_duration, "windowDuration")?,
|
||||
})
|
||||
transaction_plan_json(&plan_create_price_observations(
|
||||
CreatePriceObservationsPlanInput {
|
||||
context: &context,
|
||||
pool_id: account_id(&pool_id, "poolId")?,
|
||||
window_duration: decimal_u64(&window_duration, "windowDuration")?,
|
||||
},
|
||||
))
|
||||
}
|
||||
PlanRequest::CreateOraclePriceAccount {
|
||||
context,
|
||||
@@ -890,11 +903,13 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
window_duration,
|
||||
} => {
|
||||
let context = context.into_context()?;
|
||||
plan_create_oracle_price_account(CreateOraclePriceAccountPlanInput {
|
||||
context: &context,
|
||||
pool_id: account_id(&pool_id, "poolId")?,
|
||||
window_duration: decimal_u64(&window_duration, "windowDuration")?,
|
||||
})
|
||||
transaction_plan_json(&plan_create_oracle_price_account(
|
||||
CreateOraclePriceAccountPlanInput {
|
||||
context: &context,
|
||||
pool_id: account_id(&pool_id, "poolId")?,
|
||||
window_duration: decimal_u64(&window_duration, "windowDuration")?,
|
||||
},
|
||||
))
|
||||
}
|
||||
PlanRequest::CreatePool {
|
||||
context,
|
||||
@@ -909,7 +924,7 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
deadline,
|
||||
} => {
|
||||
let context = context.into_context()?;
|
||||
plan_create_pool(CreatePoolPlanInput {
|
||||
transaction_plan_json(&plan_create_pool(CreatePoolPlanInput {
|
||||
context: &context,
|
||||
token_a_definition_id: account_id(&token_a_definition_id, "tokenADefinitionId")?,
|
||||
token_b_definition_id: account_id(&token_b_definition_id, "tokenBDefinitionId")?,
|
||||
@@ -920,7 +935,7 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
token_b_amount: decimal_u128(&token_b_amount, "tokenBAmount")?,
|
||||
fees: decimal_u128(&fees, "fees")?,
|
||||
deadline: decimal_u64(&deadline, "deadline")?,
|
||||
})?
|
||||
})?)
|
||||
}
|
||||
PlanRequest::AddLiquidity {
|
||||
context,
|
||||
@@ -935,7 +950,7 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
} => {
|
||||
let context = context.into_context()?;
|
||||
let (pool_id, pool) = pool.into_pool()?;
|
||||
plan_add_liquidity(AddLiquidityPlanInput {
|
||||
transaction_plan_json(&plan_add_liquidity(AddLiquidityPlanInput {
|
||||
context: &context,
|
||||
pool: PoolContext::new(&context, pool_id, &pool)?,
|
||||
user_holding_a: account_id(&user_holding_a, "userHoldingA")?,
|
||||
@@ -951,7 +966,7 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
"maxAmountToAddTokenB",
|
||||
)?,
|
||||
deadline: decimal_u64(&deadline, "deadline")?,
|
||||
})
|
||||
}))
|
||||
}
|
||||
PlanRequest::RemoveLiquidity {
|
||||
context,
|
||||
@@ -966,7 +981,7 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
} => {
|
||||
let context = context.into_context()?;
|
||||
let (pool_id, pool) = pool.into_pool()?;
|
||||
plan_remove_liquidity(RemoveLiquidityPlanInput {
|
||||
transaction_plan_json(&plan_remove_liquidity(RemoveLiquidityPlanInput {
|
||||
context: &context,
|
||||
pool: PoolContext::new(&context, pool_id, &pool)?,
|
||||
user_holding_a: account_id(&user_holding_a, "userHoldingA")?,
|
||||
@@ -985,7 +1000,7 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
"minAmountToRemoveTokenB",
|
||||
)?,
|
||||
deadline: decimal_u64(&deadline, "deadline")?,
|
||||
})
|
||||
}))
|
||||
}
|
||||
PlanRequest::SwapExactInput {
|
||||
context,
|
||||
@@ -998,7 +1013,7 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
} => {
|
||||
let context = context.into_context()?;
|
||||
let (pool_id, pool) = pool.into_pool()?;
|
||||
plan_swap_exact_input(SwapExactInputPlanInput {
|
||||
transaction_plan_json(&plan_swap_exact_input(SwapExactInputPlanInput {
|
||||
context: &context,
|
||||
pool: PoolContext::new(&context, pool_id, &pool)?,
|
||||
user_input_holding: account_id(&user_input_holding, "userInputHolding")?,
|
||||
@@ -1006,7 +1021,7 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
swap_amount_in: decimal_u128(&swap_amount_in, "swapAmountIn")?,
|
||||
min_amount_out: decimal_u128(&min_amount_out, "minAmountOut")?,
|
||||
deadline: decimal_u64(&deadline, "deadline")?,
|
||||
})
|
||||
}))
|
||||
}
|
||||
PlanRequest::SwapExactOutput {
|
||||
context,
|
||||
@@ -1019,7 +1034,7 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
} => {
|
||||
let context = context.into_context()?;
|
||||
let (pool_id, pool) = pool.into_pool()?;
|
||||
plan_swap_exact_output(SwapExactOutputPlanInput {
|
||||
transaction_plan_json(&plan_swap_exact_output(SwapExactOutputPlanInput {
|
||||
context: &context,
|
||||
pool: PoolContext::new(&context, pool_id, &pool)?,
|
||||
user_input_holding: account_id(&user_input_holding, "userInputHolding")?,
|
||||
@@ -1027,117 +1042,17 @@ pub fn plan_json(value: Value) -> Result<Value, WireError> {
|
||||
exact_amount_out: decimal_u128(&exact_amount_out, "exactAmountOut")?,
|
||||
max_amount_in: decimal_u128(&max_amount_in, "maxAmountIn")?,
|
||||
deadline: decimal_u64(&deadline, "deadline")?,
|
||||
})
|
||||
}))
|
||||
}
|
||||
PlanRequest::SyncReserves { context, pool } => {
|
||||
let context = context.into_context()?;
|
||||
let (pool_id, pool) = pool.into_pool()?;
|
||||
plan_sync_reserves(SyncReservesPlanInput {
|
||||
transaction_plan_json(&plan_sync_reserves(SyncReservesPlanInput {
|
||||
context: &context,
|
||||
pool: PoolContext::new(&context, pool_id, &pool)?,
|
||||
})
|
||||
}))
|
||||
}
|
||||
};
|
||||
|
||||
versioned(transaction_plan_json(&plan))
|
||||
}
|
||||
|
||||
fn is_prepared_transaction_operation(operation: &str) -> bool {
|
||||
matches!(
|
||||
operation,
|
||||
"prepare_create_pool_transaction"
|
||||
| "prepare_add_liquidity_transaction"
|
||||
| "prepare_remove_liquidity_transaction"
|
||||
| "prepare_swap_exact_input_transaction"
|
||||
| "prepare_swap_exact_output_transaction"
|
||||
)
|
||||
}
|
||||
|
||||
/// Evaluates one reusable AMM economic quote from tagged JSON.
|
||||
pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
validate_wire_schema(&value)?;
|
||||
let request: QuoteRequest = serde_json::from_value(value)
|
||||
.map_err(|error| invalid_request(format!("invalid quote request: {error}")))?;
|
||||
versioned(match request {
|
||||
QuoteRequest::ProtocolConstants => Ok(json!({
|
||||
"minimumLiquidity": MINIMUM_LIQUIDITY.to_string(),
|
||||
"feeBpsDenominator": FEE_BPS_DENOMINATOR.to_string(),
|
||||
"slippageBpsDenominator": SLIPPAGE_BPS_DENOMINATOR.to_string(),
|
||||
"supportedFeeTiers": SUPPORTED_FEE_TIERS
|
||||
.iter()
|
||||
.map(u128::to_string)
|
||||
.collect::<Vec<_>>(),
|
||||
})),
|
||||
QuoteRequest::DeriveConfigId { amm_program_id } => Ok(json!({
|
||||
"configId": discovery::derive_config_id(amm_program_id).to_string(),
|
||||
})),
|
||||
QuoteRequest::InspectConfig {
|
||||
amm_program_id,
|
||||
config,
|
||||
} => {
|
||||
let config = config.into_snapshot()?;
|
||||
let context = discovery::inspect_config(amm_program_id, &config)?;
|
||||
Ok(amm_context_json(&context))
|
||||
}
|
||||
QuoteRequest::CanonicalPair {
|
||||
first_token_definition_id,
|
||||
second_token_definition_id,
|
||||
} => {
|
||||
let pair = discovery::canonical_pair(
|
||||
account_id(&first_token_definition_id, "firstTokenDefinitionId")?,
|
||||
account_id(&second_token_definition_id, "secondTokenDefinitionId")?,
|
||||
)?;
|
||||
Ok(canonical_pair_json(pair))
|
||||
}
|
||||
QuoteRequest::DerivePairReadManifest {
|
||||
amm_program_id,
|
||||
config,
|
||||
first_token_definition_id,
|
||||
second_token_definition_id,
|
||||
} => {
|
||||
let config = config.into_snapshot()?;
|
||||
let context = discovery::inspect_config(amm_program_id, &config)?;
|
||||
let manifest = discovery::derive_pair_read_manifest(
|
||||
&context,
|
||||
account_id(&first_token_definition_id, "firstTokenDefinitionId")?,
|
||||
account_id(&second_token_definition_id, "secondTokenDefinitionId")?,
|
||||
)?;
|
||||
Ok(pair_read_manifest_json(manifest))
|
||||
}
|
||||
QuoteRequest::InspectPair {
|
||||
amm_program_id,
|
||||
config,
|
||||
first_token_definition_id,
|
||||
second_token_definition_id,
|
||||
snapshots,
|
||||
} => {
|
||||
let config = config.into_snapshot()?;
|
||||
let context = discovery::inspect_config(amm_program_id, &config)?;
|
||||
let snapshots = snapshots.into_snapshots()?;
|
||||
let inspected = discovery::inspect_pair(
|
||||
&context,
|
||||
account_id(&first_token_definition_id, "firstTokenDefinitionId")?,
|
||||
account_id(&second_token_definition_id, "secondTokenDefinitionId")?,
|
||||
snapshots.as_borrowed(),
|
||||
)?;
|
||||
Ok(pair_inspection_json(inspected))
|
||||
}
|
||||
QuoteRequest::PrepareCallerOpeningPair {
|
||||
first_token_definition_id,
|
||||
second_token_definition_id,
|
||||
desired_price_q64_64,
|
||||
fee_bps,
|
||||
intent,
|
||||
} => Ok(prepared_caller_opening_pair_json(
|
||||
crate::prepare_caller_opening_pair(
|
||||
account_id(&first_token_definition_id, "firstTokenDefinitionId")?,
|
||||
account_id(&second_token_definition_id, "secondTokenDefinitionId")?,
|
||||
decimal_u128(&desired_price_q64_64, "desiredPriceQ64_64")?,
|
||||
decimal_u128(&fee_bps, "feeBps")?,
|
||||
intent.into_intent()?,
|
||||
)?,
|
||||
)),
|
||||
QuoteRequest::PrepareCreatePoolTransaction {
|
||||
PlanRequest::PrepareCreatePoolTransaction {
|
||||
amm_program_id,
|
||||
config,
|
||||
snapshots,
|
||||
@@ -1158,7 +1073,7 @@ pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
let liquidity_holding = liquidity_holding.into_snapshot()?;
|
||||
let prepared =
|
||||
crate::prepare_create_pool_transaction(crate::CreatePoolTransactionInput {
|
||||
amm_program_id,
|
||||
amm_program_id: amm_program_id.into(),
|
||||
config: &config,
|
||||
pair: snapshots.as_borrowed(),
|
||||
first_token_definition_id: account_id(
|
||||
@@ -1179,7 +1094,7 @@ pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
})?;
|
||||
prepared_transaction_json(&prepared, create_pool_quote_json(*prepared.quote()))
|
||||
}
|
||||
QuoteRequest::PrepareAddLiquidityTransaction {
|
||||
PlanRequest::PrepareAddLiquidityTransaction {
|
||||
amm_program_id,
|
||||
config,
|
||||
snapshots,
|
||||
@@ -1201,7 +1116,7 @@ pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
let liquidity_holding = liquidity_holding.into_snapshot()?;
|
||||
let prepared =
|
||||
crate::prepare_add_liquidity_transaction(crate::AddLiquidityTransactionInput {
|
||||
amm_program_id,
|
||||
amm_program_id: amm_program_id.into(),
|
||||
pool_accounts: crate::PoolAccountSnapshots {
|
||||
config: &config,
|
||||
pair: snapshots.as_borrowed(),
|
||||
@@ -1225,7 +1140,7 @@ pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
})?;
|
||||
prepared_transaction_json(&prepared, add_liquidity_quote_json(*prepared.quote()))
|
||||
}
|
||||
QuoteRequest::PrepareRemoveLiquidityTransaction {
|
||||
PlanRequest::PrepareRemoveLiquidityTransaction {
|
||||
amm_program_id,
|
||||
config,
|
||||
snapshots,
|
||||
@@ -1246,7 +1161,7 @@ pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
let liquidity_holding = liquidity_holding.into_snapshot()?;
|
||||
let prepared = crate::prepare_remove_liquidity_transaction(
|
||||
crate::RemoveLiquidityTransactionInput {
|
||||
amm_program_id,
|
||||
amm_program_id: amm_program_id.into(),
|
||||
pool_accounts: crate::PoolAccountSnapshots {
|
||||
config: &config,
|
||||
pair: snapshots.as_borrowed(),
|
||||
@@ -1273,7 +1188,7 @@ pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
)?;
|
||||
prepared_transaction_json(&prepared, remove_liquidity_quote_json(*prepared.quote()))
|
||||
}
|
||||
QuoteRequest::PrepareSwapExactInputTransaction {
|
||||
PlanRequest::PrepareSwapExactInputTransaction {
|
||||
amm_program_id,
|
||||
config,
|
||||
snapshots,
|
||||
@@ -1292,7 +1207,7 @@ pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
let output_holding = output_holding.into_snapshot()?;
|
||||
let prepared = crate::prepare_swap_exact_input_transaction(
|
||||
crate::SwapExactInputTransactionInput {
|
||||
amm_program_id,
|
||||
amm_program_id: amm_program_id.into(),
|
||||
pool_accounts: crate::PoolAccountSnapshots {
|
||||
config: &config,
|
||||
pair: snapshots.as_borrowed(),
|
||||
@@ -1315,7 +1230,7 @@ pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
)?;
|
||||
prepared_transaction_json(&prepared, swap_quote_json(*prepared.quote()))
|
||||
}
|
||||
QuoteRequest::PrepareSwapExactOutputTransaction {
|
||||
PlanRequest::PrepareSwapExactOutputTransaction {
|
||||
amm_program_id,
|
||||
config,
|
||||
snapshots,
|
||||
@@ -1334,7 +1249,7 @@ pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
let output_holding = output_holding.into_snapshot()?;
|
||||
let prepared = crate::prepare_swap_exact_output_transaction(
|
||||
crate::SwapExactOutputTransactionInput {
|
||||
amm_program_id,
|
||||
amm_program_id: amm_program_id.into(),
|
||||
pool_accounts: crate::PoolAccountSnapshots {
|
||||
config: &config,
|
||||
pair: snapshots.as_borrowed(),
|
||||
@@ -1357,6 +1272,93 @@ pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
)?;
|
||||
prepared_transaction_json(&prepared, swap_quote_json(*prepared.quote()))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Evaluates one reusable AMM economic quote from tagged JSON.
|
||||
pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
validate_wire_schema(&value)?;
|
||||
let request: QuoteRequest = serde_json::from_value(value)
|
||||
.map_err(|error| invalid_request(format!("invalid quote request: {error}")))?;
|
||||
versioned(match request {
|
||||
QuoteRequest::ProtocolConstants => Ok(json!({
|
||||
"minimumLiquidity": MINIMUM_LIQUIDITY.to_string(),
|
||||
"feeBpsDenominator": FEE_BPS_DENOMINATOR.to_string(),
|
||||
"slippageBpsDenominator": SLIPPAGE_BPS_DENOMINATOR.to_string(),
|
||||
"supportedFeeTiers": SUPPORTED_FEE_TIERS
|
||||
.iter()
|
||||
.map(u128::to_string)
|
||||
.collect::<Vec<_>>(),
|
||||
})),
|
||||
QuoteRequest::DeriveConfigId { amm_program_id } => Ok(json!({
|
||||
"configId": discovery::derive_config_id(amm_program_id.into()).to_string(),
|
||||
})),
|
||||
QuoteRequest::InspectConfig {
|
||||
amm_program_id,
|
||||
config,
|
||||
} => {
|
||||
let config = config.into_snapshot()?;
|
||||
let context = discovery::inspect_config(amm_program_id.into(), &config)?;
|
||||
Ok(amm_context_json(&context))
|
||||
}
|
||||
QuoteRequest::CanonicalPair {
|
||||
first_token_definition_id,
|
||||
second_token_definition_id,
|
||||
} => {
|
||||
let pair = discovery::canonical_pair(
|
||||
account_id(&first_token_definition_id, "firstTokenDefinitionId")?,
|
||||
account_id(&second_token_definition_id, "secondTokenDefinitionId")?,
|
||||
)?;
|
||||
Ok(canonical_pair_json(pair))
|
||||
}
|
||||
QuoteRequest::DerivePairReadManifest {
|
||||
amm_program_id,
|
||||
config,
|
||||
first_token_definition_id,
|
||||
second_token_definition_id,
|
||||
} => {
|
||||
let config = config.into_snapshot()?;
|
||||
let context = discovery::inspect_config(amm_program_id.into(), &config)?;
|
||||
let manifest = discovery::derive_pair_read_manifest(
|
||||
&context,
|
||||
account_id(&first_token_definition_id, "firstTokenDefinitionId")?,
|
||||
account_id(&second_token_definition_id, "secondTokenDefinitionId")?,
|
||||
)?;
|
||||
Ok(pair_read_manifest_json(manifest))
|
||||
}
|
||||
QuoteRequest::InspectPair {
|
||||
amm_program_id,
|
||||
config,
|
||||
first_token_definition_id,
|
||||
second_token_definition_id,
|
||||
snapshots,
|
||||
} => {
|
||||
let config = config.into_snapshot()?;
|
||||
let context = discovery::inspect_config(amm_program_id.into(), &config)?;
|
||||
let snapshots = snapshots.into_snapshots()?;
|
||||
let inspected = discovery::inspect_pair(
|
||||
&context,
|
||||
account_id(&first_token_definition_id, "firstTokenDefinitionId")?,
|
||||
account_id(&second_token_definition_id, "secondTokenDefinitionId")?,
|
||||
snapshots.as_borrowed(),
|
||||
)?;
|
||||
Ok(pair_inspection_json(inspected))
|
||||
}
|
||||
QuoteRequest::PrepareCallerOpeningPair {
|
||||
first_token_definition_id,
|
||||
second_token_definition_id,
|
||||
desired_price_q64_64,
|
||||
fee_bps,
|
||||
intent,
|
||||
} => Ok(prepared_caller_opening_pair_json(
|
||||
crate::prepare_caller_opening_pair(
|
||||
account_id(&first_token_definition_id, "firstTokenDefinitionId")?,
|
||||
account_id(&second_token_definition_id, "secondTokenDefinitionId")?,
|
||||
decimal_u128(&desired_price_q64_64, "desiredPriceQ64_64")?,
|
||||
decimal_u128(&fee_bps, "feeBps")?,
|
||||
intent.into_intent()?,
|
||||
)?,
|
||||
)),
|
||||
QuoteRequest::PrepareMinimumOpeningPair {
|
||||
desired_price_q64_64,
|
||||
fee_bps,
|
||||
@@ -1429,7 +1431,7 @@ pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
fee_bps,
|
||||
} => {
|
||||
let config = config.into_snapshot()?;
|
||||
let context = AmmContext::from_config_account(amm_program_id, &config)?;
|
||||
let context = AmmContext::from_config_account(amm_program_id.into(), &config)?;
|
||||
let token_a_definition = token_a_definition.into_snapshot()?;
|
||||
let token_b_definition = token_b_definition.into_snapshot()?;
|
||||
let token_a = ValidatedFungibleDefinition::new(&context, &token_a_definition)?;
|
||||
@@ -1454,7 +1456,7 @@ pub fn quote_json(value: Value) -> Result<Value, WireError> {
|
||||
fee_bps,
|
||||
} => {
|
||||
let config = config.into_snapshot()?;
|
||||
let context = AmmContext::from_config_account(amm_program_id, &config)?;
|
||||
let context = AmmContext::from_config_account(amm_program_id.into(), &config)?;
|
||||
let token_a_definition = token_a_definition.into_snapshot()?;
|
||||
let token_b_definition = token_b_definition.into_snapshot()?;
|
||||
let token_a = ValidatedFungibleDefinition::new(&context, &token_a_definition)?;
|
||||
@@ -1763,7 +1765,7 @@ fn transaction_plan_json(plan: &TransactionPlan) -> Result<Value, WireError> {
|
||||
Ok(json!({
|
||||
"instruction": plan.instruction_name(),
|
||||
"instructionArgs": instruction_args_json(plan.instruction()),
|
||||
"programId": plan.program_id(),
|
||||
"programId": program_id_hex(plan.program_id()),
|
||||
"accounts": accounts,
|
||||
"affectedAccountIds": plan
|
||||
.affected_account_ids()
|
||||
@@ -1781,8 +1783,8 @@ fn instruction_args_json(instruction: &Instruction) -> Value {
|
||||
twap_oracle_program_id,
|
||||
authority,
|
||||
} => json!({
|
||||
"tokenProgramId": token_program_id,
|
||||
"twapOracleProgramId": twap_oracle_program_id,
|
||||
"tokenProgramId": program_id_hex(*token_program_id),
|
||||
"twapOracleProgramId": program_id_hex(*twap_oracle_program_id),
|
||||
"authority": authority.to_string(),
|
||||
}),
|
||||
Instruction::UpdateConfig {
|
||||
@@ -1790,8 +1792,8 @@ fn instruction_args_json(instruction: &Instruction) -> Value {
|
||||
twap_oracle_program_id,
|
||||
new_authority,
|
||||
} => json!({
|
||||
"tokenProgramId": token_program_id,
|
||||
"twapOracleProgramId": twap_oracle_program_id,
|
||||
"tokenProgramId": token_program_id.map(program_id_hex),
|
||||
"twapOracleProgramId": twap_oracle_program_id.map(program_id_hex),
|
||||
"newAuthority": new_authority.map(|authority| authority.to_string()),
|
||||
}),
|
||||
Instruction::CreatePriceObservations { window_duration }
|
||||
@@ -1944,10 +1946,10 @@ fn pool_update_json(pool: PoolUpdate) -> Value {
|
||||
|
||||
fn amm_context_json(context: &AmmContext) -> Value {
|
||||
json!({
|
||||
"ammProgramId": context.amm_program_id,
|
||||
"ammProgramId": program_id_hex(context.amm_program_id),
|
||||
"configId": context.config_id().to_string(),
|
||||
"tokenProgramId": context.token_program_id(),
|
||||
"twapOracleProgramId": context.twap_oracle_program_id(),
|
||||
"tokenProgramId": program_id_hex(context.token_program_id()),
|
||||
"twapOracleProgramId": program_id_hex(context.twap_oracle_program_id()),
|
||||
"authority": context.config.authority.to_string(),
|
||||
})
|
||||
}
|
||||
@@ -2255,9 +2257,46 @@ fn oracle_price_quote_json(quote: OraclePriceAccountQuote) -> Value {
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_program_id(value: &str) -> Result<ProgramId, String> {
|
||||
if value.len() != 64
|
||||
|| !value
|
||||
.bytes()
|
||||
.all(|byte| byte.is_ascii_digit() || matches!(byte, b'a'..=b'f'))
|
||||
{
|
||||
return Err(String::from(
|
||||
"program ID must be exactly 64 lowercase hexadecimal characters",
|
||||
));
|
||||
}
|
||||
|
||||
let bytes = hex_bytes(value, "program ID").map_err(|error| error.to_string())?;
|
||||
let mut program_id = [0_u32; 8];
|
||||
for (word, bytes) in program_id.iter_mut().zip(bytes.chunks_exact(4)) {
|
||||
let mut word_bytes = [0_u8; 4];
|
||||
word_bytes.copy_from_slice(bytes);
|
||||
*word = u32::from_le_bytes(word_bytes);
|
||||
}
|
||||
Ok(program_id)
|
||||
}
|
||||
|
||||
fn program_id_hex(program_id: ProgramId) -> String {
|
||||
let mut output = String::with_capacity(64);
|
||||
for word in program_id {
|
||||
for byte in word.to_le_bytes() {
|
||||
output.push_str(&format!("{byte:02x}"));
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
fn account_id(value: &str, field: &str) -> Result<AccountId, WireError> {
|
||||
AccountId::from_str(value)
|
||||
.map_err(|error| invalid_request(format!("{field} is not a valid account ID: {error}")))
|
||||
let account_id = AccountId::from_str(value)
|
||||
.map_err(|error| invalid_request(format!("{field} is not a valid account ID: {error}")))?;
|
||||
if account_id.to_string() != value {
|
||||
return Err(invalid_request(format!(
|
||||
"{field} must use canonical base58 encoding"
|
||||
)));
|
||||
}
|
||||
Ok(account_id)
|
||||
}
|
||||
|
||||
fn decimal_u128(value: &str, field: &str) -> Result<u128, WireError> {
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
use nssa_core::program::ProgramId;
|
||||
|
||||
pub fn program_id_hex(program_id: ProgramId) -> String {
|
||||
let mut output = String::with_capacity(64);
|
||||
for word in program_id {
|
||||
for byte in word.to_le_bytes() {
|
||||
output.push_str(&format!("{byte:02x}"));
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
reason = "contract tests call the exported C ABI and release its owned pointers"
|
||||
)]
|
||||
|
||||
mod common;
|
||||
|
||||
use std::ffi::{c_char, CStr, CString};
|
||||
|
||||
use amm_client::{amm_client_free, amm_client_plan, amm_client_quote, wire::WIRE_SCHEMA};
|
||||
@@ -10,6 +12,7 @@ use amm_core::{
|
||||
compute_config_pda, compute_liquidity_token_pda, compute_pool_pda, compute_vault_pda,
|
||||
AmmConfig, Instruction, PoolDefinition, FEE_TIER_BPS_30, MINIMUM_LIQUIDITY,
|
||||
};
|
||||
use common::program_id_hex;
|
||||
use nssa_core::{
|
||||
account::{Account, AccountId, Data, Nonce},
|
||||
program::ProgramId,
|
||||
@@ -45,7 +48,7 @@ fn call_json(operation: Operation, request: &Value) -> Value {
|
||||
fn snapshot(id: AccountId, account: &Account) -> Value {
|
||||
json!({
|
||||
"id": id.to_string(),
|
||||
"programOwner": account.program_owner,
|
||||
"programOwner": program_id_hex(account.program_owner),
|
||||
"balance": account.balance.to_string(),
|
||||
"nonce": account.nonce.0.to_string(),
|
||||
"data": hex(account.data.as_ref()),
|
||||
@@ -147,6 +150,31 @@ fn protocol_constants_are_exposed_without_numeric_json_values() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn program_ids_require_canonical_lowercase_hex_strings() {
|
||||
let canonical = program_id_hex([0xabcdef01; 8]);
|
||||
let authority = AccountId::new([44; 32]).to_string();
|
||||
|
||||
for invalid in [
|
||||
json!([1, 1, 1, 1, 1, 1, 1, 1]),
|
||||
json!(canonical.to_uppercase()),
|
||||
] {
|
||||
let response = call_json(
|
||||
amm_client_plan,
|
||||
&json!({
|
||||
"operation": "initialize",
|
||||
"ammProgramId": invalid,
|
||||
"tokenProgramId": canonical,
|
||||
"twapOracleProgramId": canonical,
|
||||
"authority": authority,
|
||||
}),
|
||||
);
|
||||
|
||||
assert_eq!(response["ok"], false);
|
||||
assert_eq!(response["error"]["code"], "invalid_request");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successful_plan_preserves_u64_above_javascript_range_in_guest_words() {
|
||||
let amm_program_id: ProgramId = [11; 8];
|
||||
@@ -160,9 +188,9 @@ fn successful_plan_preserves_u64_above_javascript_range_in_guest_words() {
|
||||
&json!({
|
||||
"operation": "create_price_observations",
|
||||
"context": {
|
||||
"ammProgramId": amm_program_id,
|
||||
"tokenProgramId": token_program_id,
|
||||
"twapOracleProgramId": twap_oracle_program_id,
|
||||
"ammProgramId": program_id_hex(amm_program_id),
|
||||
"tokenProgramId": program_id_hex(token_program_id),
|
||||
"twapOracleProgramId": program_id_hex(twap_oracle_program_id),
|
||||
"authority": authority.to_string(),
|
||||
},
|
||||
"poolId": pool_id.to_string(),
|
||||
@@ -175,7 +203,10 @@ fn successful_plan_preserves_u64_above_javascript_range_in_guest_words() {
|
||||
response["value"]["instruction"],
|
||||
"create_price_observations"
|
||||
);
|
||||
assert_eq!(response["value"]["programId"], json!(amm_program_id));
|
||||
assert_eq!(
|
||||
response["value"]["programId"],
|
||||
program_id_hex(amm_program_id)
|
||||
);
|
||||
assert!(response["value"]["accounts"].is_array());
|
||||
let words: Vec<u32> = serde_json::from_value(response["value"]["instructionWords"].clone())
|
||||
.expect("instruction words must be u32 JSON numbers");
|
||||
@@ -232,7 +263,7 @@ fn successful_quote_preserves_u128_above_javascript_range_as_decimal() {
|
||||
amm_client_quote,
|
||||
&json!({
|
||||
"operation": "create_pool",
|
||||
"ammProgramId": amm_program_id,
|
||||
"ammProgramId": program_id_hex(amm_program_id),
|
||||
"config": snapshot(compute_config_pda(amm_program_id), &config_account),
|
||||
"tokenADefinition": snapshot(token_a_id, &definition("A")),
|
||||
"tokenBDefinition": snapshot(token_b_id, &definition("B")),
|
||||
@@ -258,7 +289,7 @@ fn successful_quote_preserves_u128_above_javascript_range_as_decimal() {
|
||||
amm_client_quote,
|
||||
&json!({
|
||||
"operation": "prepare_create_pool",
|
||||
"ammProgramId": amm_program_id,
|
||||
"ammProgramId": program_id_hex(amm_program_id),
|
||||
"config": snapshot(compute_config_pda(amm_program_id), &config_account),
|
||||
"tokenADefinition": snapshot(token_a_id, &definition("A")),
|
||||
"tokenBDefinition": snapshot(token_b_id, &definition("B")),
|
||||
@@ -311,7 +342,7 @@ fn swap_quote_rejects_unrelated_output_holding() {
|
||||
amm_client_quote,
|
||||
&json!({
|
||||
"operation": "preview_swap_exact_input",
|
||||
"ammProgramId": amm_program_id,
|
||||
"ammProgramId": program_id_hex(amm_program_id),
|
||||
"config": snapshot(
|
||||
compute_config_pda(amm_program_id),
|
||||
&account(amm_program_id, Data::from(&config)),
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
mod common;
|
||||
|
||||
use amm_client::wire::{quote_json, WIRE_SCHEMA};
|
||||
use amm_core::{
|
||||
compute_config_pda, compute_liquidity_token_pda, compute_lp_lock_holding_pda, compute_pool_pda,
|
||||
compute_vault_pda, AmmConfig, PoolDefinition, FEE_TIER_BPS_30, MINIMUM_LIQUIDITY,
|
||||
};
|
||||
use clock_core::{ClockAccountData, CLOCK_01_PROGRAM_ACCOUNT_ID};
|
||||
use common::program_id_hex;
|
||||
use nssa_core::{
|
||||
account::{Account, AccountId, Data, Nonce},
|
||||
program::ProgramId,
|
||||
@@ -108,7 +111,7 @@ impl PairIds {
|
||||
fn inspect_request(&self, snapshots: Value) -> Value {
|
||||
json!({
|
||||
"operation": "inspect_pair",
|
||||
"ammProgramId": AMM_PROGRAM_ID,
|
||||
"ammProgramId": program_id_hex(AMM_PROGRAM_ID),
|
||||
"config": config_snapshot(),
|
||||
"firstTokenDefinitionId": self.first_token_id.to_string(),
|
||||
"secondTokenDefinitionId": self.second_token_id.to_string(),
|
||||
@@ -202,7 +205,7 @@ impl PairIds {
|
||||
fn snapshot(id: AccountId, account: &Account) -> Value {
|
||||
json!({
|
||||
"id": id.to_string(),
|
||||
"programOwner": account.program_owner,
|
||||
"programOwner": program_id_hex(account.program_owner),
|
||||
"balance": account.balance.to_string(),
|
||||
"nonce": account.nonce.0.to_string(),
|
||||
"data": account
|
||||
@@ -229,7 +232,7 @@ fn discovery_operations_return_exact_string_account_ids() {
|
||||
|
||||
let config_id = quote_json(json!({
|
||||
"operation": "derive_config_id",
|
||||
"ammProgramId": AMM_PROGRAM_ID,
|
||||
"ammProgramId": program_id_hex(AMM_PROGRAM_ID),
|
||||
}))
|
||||
.expect("legacy schema-less request remains accepted");
|
||||
assert_eq!(config_id["schema"], WIRE_SCHEMA);
|
||||
@@ -242,16 +245,19 @@ fn discovery_operations_return_exact_string_account_ids() {
|
||||
let inspected = quote_json(json!({
|
||||
"schema": WIRE_SCHEMA,
|
||||
"operation": "inspect_config",
|
||||
"ammProgramId": AMM_PROGRAM_ID,
|
||||
"ammProgramId": program_id_hex(AMM_PROGRAM_ID),
|
||||
"config": config.clone(),
|
||||
}))
|
||||
.expect("config must inspect");
|
||||
assert_eq!(inspected["schema"], WIRE_SCHEMA);
|
||||
assert_eq!(inspected["ammProgramId"], json!(AMM_PROGRAM_ID));
|
||||
assert_eq!(inspected["tokenProgramId"], json!(TOKEN_PROGRAM_ID));
|
||||
assert_eq!(inspected["ammProgramId"], program_id_hex(AMM_PROGRAM_ID));
|
||||
assert_eq!(
|
||||
inspected["tokenProgramId"],
|
||||
program_id_hex(TOKEN_PROGRAM_ID)
|
||||
);
|
||||
assert_eq!(
|
||||
inspected["twapOracleProgramId"],
|
||||
json!(TWAP_ORACLE_PROGRAM_ID)
|
||||
program_id_hex(TWAP_ORACLE_PROGRAM_ID)
|
||||
);
|
||||
assert_eq!(inspected["authority"], AccountId::new([9; 32]).to_string());
|
||||
|
||||
@@ -266,7 +272,7 @@ fn discovery_operations_return_exact_string_account_ids() {
|
||||
|
||||
let manifest = quote_json(json!({
|
||||
"operation": "derive_pair_read_manifest",
|
||||
"ammProgramId": AMM_PROGRAM_ID,
|
||||
"ammProgramId": program_id_hex(AMM_PROGRAM_ID),
|
||||
"config": config,
|
||||
"firstTokenDefinitionId": first_token_id.to_string(),
|
||||
"secondTokenDefinitionId": second_token_id.to_string(),
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
mod common;
|
||||
|
||||
use amm_client::{maximum_guard_amount, minimum_guard_amount, wire::quote_json, SlippageTolerance};
|
||||
use amm_core::{
|
||||
compute_config_pda, compute_liquidity_token_pda, compute_pool_pda, compute_vault_pda,
|
||||
AmmConfig, PoolDefinition, FEE_TIER_BPS_30,
|
||||
};
|
||||
use common::program_id_hex;
|
||||
use nssa_core::{
|
||||
account::{Account, AccountId, Data, Nonce},
|
||||
program::ProgramId,
|
||||
@@ -26,7 +29,7 @@ fn account(program_owner: ProgramId, data: Data) -> Account {
|
||||
fn snapshot(id: AccountId, account: &Account) -> Value {
|
||||
json!({
|
||||
"id": id.to_string(),
|
||||
"programOwner": account.program_owner,
|
||||
"programOwner": program_id_hex(account.program_owner),
|
||||
"balance": account.balance.to_string(),
|
||||
"nonce": account.nonce.0.to_string(),
|
||||
"data": account
|
||||
@@ -99,7 +102,7 @@ impl WireFixture {
|
||||
fees: FEE_TIER_BPS_30,
|
||||
};
|
||||
let state = json!({
|
||||
"ammProgramId": AMM_PROGRAM_ID,
|
||||
"ammProgramId": program_id_hex(AMM_PROGRAM_ID),
|
||||
"config": config,
|
||||
"snapshot": {
|
||||
"pool": snapshot(pool_id, &account(AMM_PROGRAM_ID, Data::from(&pool))),
|
||||
@@ -161,7 +164,7 @@ fn prepare_wire_operations_return_lossless_instruction_args() {
|
||||
|
||||
let create = quote_json(json!({
|
||||
"operation": "prepare_create_pool",
|
||||
"ammProgramId": AMM_PROGRAM_ID,
|
||||
"ammProgramId": program_id_hex(AMM_PROGRAM_ID),
|
||||
"config": fixture.config.clone(),
|
||||
"tokenADefinition": snapshot(fixture.token_a_id, &definition(100_000, None)),
|
||||
"tokenBDefinition": snapshot(fixture.token_b_id, &definition(100_000, None)),
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
mod common;
|
||||
|
||||
use amm_client::wire::{plan_json, quote_json};
|
||||
use amm_core::{
|
||||
compute_config_pda, compute_liquidity_token_pda, compute_lp_lock_holding_pda, compute_pool_pda,
|
||||
compute_vault_pda, AmmConfig, Instruction, PoolDefinition, FEE_TIER_BPS_30, MINIMUM_LIQUIDITY,
|
||||
};
|
||||
use clock_core::{ClockAccountData, CLOCK_01_PROGRAM_ACCOUNT_ID};
|
||||
use common::program_id_hex;
|
||||
use nssa_core::{
|
||||
account::{Account, AccountId, Data, Nonce},
|
||||
program::ProgramId,
|
||||
@@ -52,7 +55,7 @@ fn holding(definition_id: AccountId, balance: u128) -> Account {
|
||||
fn snapshot(id: AccountId, account: &Account) -> Value {
|
||||
json!({
|
||||
"id": id.to_string(),
|
||||
"programOwner": account.program_owner,
|
||||
"programOwner": program_id_hex(account.program_owner),
|
||||
"balance": account.balance.to_string(),
|
||||
"nonce": account.nonce.0.to_string(),
|
||||
"data": account
|
||||
@@ -209,7 +212,7 @@ impl TransactionFixture {
|
||||
fn active_common(&self, operation: &str) -> Value {
|
||||
json!({
|
||||
"operation": operation,
|
||||
"ammProgramId": AMM_PROGRAM_ID,
|
||||
"ammProgramId": program_id_hex(AMM_PROGRAM_ID),
|
||||
"config": self.config.clone(),
|
||||
"snapshots": self.active_snapshots.clone(),
|
||||
"firstTokenDefinitionId": self.first_token_id.to_string(),
|
||||
@@ -226,7 +229,7 @@ impl TransactionFixture {
|
||||
fn swap_common(&self, operation: &str) -> Value {
|
||||
json!({
|
||||
"operation": operation,
|
||||
"ammProgramId": AMM_PROGRAM_ID,
|
||||
"ammProgramId": program_id_hex(AMM_PROGRAM_ID),
|
||||
"config": self.config.clone(),
|
||||
"snapshots": self.active_snapshots.clone(),
|
||||
"inputTokenDefinitionId": self.first_token_id.to_string(),
|
||||
@@ -321,9 +324,9 @@ fn five_transaction_operations_emit_exact_plans_and_task_artifacts() {
|
||||
let fixture = TransactionFixture::new();
|
||||
|
||||
let second_amount = LARGE.checked_mul(2).expect("test amount fits");
|
||||
let create = quote_json(json!({
|
||||
let create = plan_json(json!({
|
||||
"operation": "prepare_create_pool_transaction",
|
||||
"ammProgramId": AMM_PROGRAM_ID,
|
||||
"ammProgramId": program_id_hex(AMM_PROGRAM_ID),
|
||||
"config": fixture.config.clone(),
|
||||
"snapshots": fixture.missing_snapshots.clone(),
|
||||
"firstTokenDefinitionId": fixture.first_token_id.to_string(),
|
||||
@@ -379,11 +382,10 @@ fn five_transaction_operations_emit_exact_plans_and_task_artifacts() {
|
||||
let mut add_request = fixture.active_common("prepare_add_liquidity_transaction");
|
||||
insert(&mut add_request, "maxFirstAmount", json!("100"));
|
||||
insert(&mut add_request, "maxSecondAmount", json!("400"));
|
||||
let add = quote_json(add_request.clone()).expect("add transaction must prepare");
|
||||
assert_eq!(
|
||||
plan_json(add_request).expect("plan entrypoint must prepare task transactions"),
|
||||
add
|
||||
);
|
||||
let quote_error = quote_json(add_request.clone())
|
||||
.expect_err("quote endpoint must reject transaction preparation");
|
||||
assert_eq!(quote_error.code(), "invalid_request");
|
||||
let add = plan_json(add_request).expect("add transaction must prepare");
|
||||
assert_common_contract(&add, "add_liquidity", false);
|
||||
assert_eq!(add["callerAmounts"]["first"], "100");
|
||||
assert_eq!(add["callerAmounts"]["second"], "200");
|
||||
@@ -428,7 +430,7 @@ fn five_transaction_operations_emit_exact_plans_and_task_artifacts() {
|
||||
|
||||
let mut remove_request = fixture.active_common("prepare_remove_liquidity_transaction");
|
||||
insert(&mut remove_request, "removeLiquidityAmount", json!("500"));
|
||||
let remove = quote_json(remove_request).expect("remove transaction must prepare");
|
||||
let remove = plan_json(remove_request).expect("remove transaction must prepare");
|
||||
assert_common_contract(&remove, "remove_liquidity", false);
|
||||
assert_eq!(remove["callerAmounts"]["first"], "125");
|
||||
assert_eq!(remove["callerAmounts"]["second"], "250");
|
||||
@@ -469,8 +471,7 @@ fn five_transaction_operations_emit_exact_plans_and_task_artifacts() {
|
||||
|
||||
let mut exact_input_request = fixture.swap_common("prepare_swap_exact_input_transaction");
|
||||
insert(&mut exact_input_request, "amountIn", json!("100"));
|
||||
let exact_input =
|
||||
quote_json(exact_input_request).expect("exact-input transaction must prepare");
|
||||
let exact_input = plan_json(exact_input_request).expect("exact-input transaction must prepare");
|
||||
assert_common_contract(&exact_input, "swap_exact_input", true);
|
||||
assert_eq!(exact_input["callerAmounts"]["first"], "100");
|
||||
assert_eq!(
|
||||
@@ -503,7 +504,7 @@ fn five_transaction_operations_emit_exact_plans_and_task_artifacts() {
|
||||
let mut exact_output_request = fixture.swap_common("prepare_swap_exact_output_transaction");
|
||||
insert(&mut exact_output_request, "exactAmountOut", json!("100"));
|
||||
let exact_output =
|
||||
quote_json(exact_output_request).expect("exact-output transaction must prepare");
|
||||
plan_json(exact_output_request).expect("exact-output transaction must prepare");
|
||||
assert_common_contract(&exact_output, "swap_exact_output", true);
|
||||
assert_eq!(exact_output["callerAmounts"]["second"], "100");
|
||||
match decode_instruction(&exact_output) {
|
||||
@@ -560,6 +561,6 @@ fn transaction_wire_rejects_expected_fee_mismatch() {
|
||||
insert(&mut request, "maxSecondAmount", json!("400"));
|
||||
insert(&mut request, "expectedFeeBps", json!("100"));
|
||||
|
||||
let error = quote_json(request).expect_err("wrong expected fee must fail");
|
||||
let error = plan_json(request).expect_err("wrong expected fee must fail");
|
||||
assert_eq!(error.code(), "fee_mismatch");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user