diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3b39518f7..0f505a0ba 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: node: ["18.x"] - solana: ["1.14.11"] + solana: ["1.14.18"] steps: - name: Git checkout uses: actions/checkout@v3 @@ -52,4 +52,4 @@ jobs: run: yarn validator & sleep 3 - name: Test - run: yarn test \ No newline at end of file + run: yarn test diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..52e45ddfa --- /dev/null +++ b/LICENSE @@ -0,0 +1,94 @@ +Convergence General Source License + +Introduction +----------------------------------------------------------------------------- + +This license defines the terms and conditions under which all Convergence software can be used. Anyone that intends to use, modify, or distribute any Convergence software, in whole or in part, is subject to the terms set forth herein. +For example, this license allows for the free non-commercial use of Convergence software, provided that this license still remains applicable to the software. Any commercial use is only permissible with advanced written permission from Convergence. Modifications are likewise only permitted without commercial purpose, unless such modifications are pre-approved in advance in writing by Convergence. +Additional terms and conditions may apply to any Convergence software. + +Summary of Parameters +----------------------------------------------------------------------------- + +Licensor: Convergence Foundation +Licensed Work: Any code originally released by Convergence where this license appears, or any copy or derivative of such code +Date of License: December 4, 2023 +Contact to seek permission: admin@tradersguild.so + +Definitions +----------------------------------------------------------------------------- + +To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. + +A "Licensed Work" includes any means that is protectable under applicable copyright law, including unmodified code or a work based on the code. + +To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. + +Basic Permissions +----------------------------------------------------------------------------- + +The Licensor hereby grants you the right to copy, redistribute, and use the Licensed Work so long as the foregoing is done on a non-commercial and non-production basis. This License also requires that such uses provide notice of the original source of the Licensed Work as well as the continuing applicability of this License. The Licensor may make additional use grants for certain commercial and/or production use of the Licensed Work on a case-by-case basis by providing or publishing such permission in writing. +The Licensor also grants you the right to contribute to the Licensed Work by modifying it so long as such modifications are non-commercial and on a non-production basis, so long as the modifications are made publicly available for audit, with notice provided of the original source as well as the continuing applicability of this License. Except as specifically stated herein, the Licensor does not grant you the right to modify or create derivative works for any purpose without written permission from the Licensor. +Any other usage or propagation of the Licensed Work is prohibited and subject to copyright laws. If your actual or intended use or propagation of the Licensed Work does not comply with the permissible uses of the Licensed Work, you must refrain from such use unless and until permission is provided in writing by the Licensor. Such permission can be sought at the contact address provided above. + +Seeking Additional Permissions +----------------------------------------------------------------------------- + +To seek permission to propagate or use in any way the Licensed Work beyond the permissions granted herein, please contact the Licensor at the contact address provided above and specify the proposed additional permission sought. To the extent any such use is permitted, this License must continue to apply to any of the Licensed Work that is utilized. For example, where a portion of code is permitted to be utilized and modified to become part of a larger work, this License shall still apply to the portion of the code that has been modified. This license must be made available with the code, and the larger work must include a notice stating the original source of the code, and that it is released under and still subject to this license. + +Applicability +----------------------------------------------------------------------------- + +All copies of the original and modified Licensed Works, and any derivatives thereof, are subject to this License. This License applies separately to each version of a Licensed Work. This License must be conspicuously displayed on each original or modified copy of a Licensed Work. If you receive a Licensed Work in original or modified form from a third party, the terms and conditions set forth in this License apply to your use of that work. + +Termination +----------------------------------------------------------------------------- + +Any use of a Licensed Work in violation of this License will automatically terminate your rights under this License for the current and all other versions of the Licensed Work. +You may not propagate or modify a Licensed Work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License. +The Licensor also retains the right to cancel this License at any time in its sole discretion for any individual or entity that it believes has not acted in compliance with the License, or is utilizing the Licensed Work for an improper purpose. + +Disclaimer of Warranty +----------------------------------------------------------------------------- + +THERE IS NO WARRANTY FOR THE LICENSED WORK, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE LICENSOR, COPYRIGHT HOLDER OR OTHER PARTIES PROVIDE THE LICENSED WORK "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LICENSED WORK IS WITH YOU. SHOULD THE LICENSED WORK PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +Limitation of Liability +----------------------------------------------------------------------------- + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY LICENSOR, COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE LICENSED WORK AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LICENSED WORK (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LICENSED WORK TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +No Ownership Rights +----------------------------------------------------------------------------- + +Use or propagation of the Licensed Work, regardless of whether permissible under this License, does not give you any ownership rights in any of the Licensed Works. Except as explicitly stated herein, all other right, title, and interest in the Licensed Works is exclusively the property of the Licensor. + +Licensor’s Trademark +----------------------------------------------------------------------------- + +This License does not grant anyone any right in the name, logo, or other trademark of Licensor or any affiliate, except as needed to reprint this License. + +Modifications +----------------------------------------------------------------------------- + +The Licensor reserves the right to make changes to this License from time to time without specifically notifying you. The Licensor will make the latest version publicly available. If you continue to use or propagate a Licensed Work, this means that you have accepted any changes to the License. + +Your Responsibility to Comply with Local Law +----------------------------------------------------------------------------- + +It is the responsibility of you and each user to inform yourself of and to observe all applicable laws and regulations of any relevant jurisdictions when using or propagating any of the Licensed Works. Prospective users should inform themselves as to the legal requirements and tax consequence within the jurisdictions of their citizenship, residence, domicile, and place of business, and seek professional advise where appropriate. + +Survivability +----------------------------------------------------------------------------- + +The terms of this License shall survive even if any particular term is deemed not to be enforceable. Any remaining terms shall remain enforceable to the maximum extent permitted by applicable law. To the extent permissible, any terms deemed not enforceable shall be rewritten so as to be enforceable to the maximum extent permissible by applicable law. + +No Waiver +----------------------------------------------------------------------------- + +Any failure by Licensor to exercise or enforce any legal right or to take any action concerning any breach of this License shall not constitute a waiver of any rights or remedies otherwise available to Licensor. + +Choice of Law and Venue +----------------------------------------------------------------------------- + +If there is any dispute arising out of this License or any of the Licensed Works, including any dispute concerning any of the terms of this License, you expressly agree that any such dispute shall be governed by the laws of the State of Delaware in the United States, without regard to the conflict of law provisions of any jurisdiction, and you expressly agree and consent to the exclusive jurisdiction and venue of the state and federal courts of the State of New York, and waive any personal jurisdictional challenge thereto, for the resolution of any such dispute. diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index f564032db..42bd28e70 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,95 @@ # @convergence-rfq/cli +## 4.5.20 + +### Patch Changes + +- Updated dependencies + - @convergence-rfq/sdk@4.5.20 + +## 4.5.19 + +### Patch Changes + +- Updated dependencies + - @convergence-rfq/sdk@4.5.19 + +## 4.5.18 + +### Patch Changes + +- Updated dependencies + - @convergence-rfq/sdk@4.5.18 + +## 4.5.17 + +### Patch Changes + +- Updated dependencies + - @convergence-rfq/sdk@4.5.17 + +## 4.5.16 + +### Patch Changes + +- Updated dependencies + - @convergence-rfq/sdk@4.5.16 + +## 4.5.15 + +### Patch Changes + +- Updated dependencies + - @convergence-rfq/sdk@4.5.15 + +## 4.5.14 + +### Patch Changes + +- Updated dependencies + - @convergence-rfq/sdk@4.5.14 + +## 4.5.13 + +### Patch Changes + +- Updated dependencies + - @convergence-rfq/sdk@4.5.13 + +## 4.5.12 + +### Patch Changes + +- Updated dependencies + - @convergence-rfq/sdk@4.5.12 + +## 4.5.11 + +### Patch Changes + +- Updated dependencies [072f2ca1] + - @convergence-rfq/sdk@4.5.11 + +## 4.5.10 + +### Patch Changes + +- refactor prepareSettlement logic to handle creation of atas and minting option tokens if required + add getTokenBalance operation to token module + add getRfqStateAndAction + add getResponseStateAndAction + fix cli-tests +- Updated dependencies + - @convergence-rfq/sdk@4.5.10 + +## 4.5.9 + +### Patch Changes + +- CLI get-registered-mints now logs mint address and decimals +- Updated dependencies + - @convergence-rfq/sdk@4.5.9 + ## 4.5.8 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index fb1657f03..676fad50e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@convergence-rfq/cli", "description": "Official Convergence CLI", - "version": "4.5.8", + "version": "4.5.20", "license": "MIT", "publishConfig": { "access": "public" @@ -47,7 +47,7 @@ "cli": "ts-node src/index.ts" }, "dependencies": { - "@convergence-rfq/sdk": "4.5.8", + "@convergence-rfq/sdk": "4.5.20", "@solana/web3.js": "^1.73.0", "@types/cookie": "^0.5.1", "commander": "^10.0.0" diff --git a/packages/cli/scripts/.env b/packages/cli/scripts/.env deleted file mode 100644 index ec7fbce1d..000000000 --- a/packages/cli/scripts/.env +++ /dev/null @@ -1,19 +0,0 @@ -export OWNER="HGm8jGLSazATztBSUxXfU62oRyVsmwPKnUsjvviRYbRG" - -export SPOT_INSTRUMENT="HxWk421wJzJPzzRrAa4tPatRtMqY1hqPKV7XenBxsBpH" -export PSYOPTIONS_EUROPEAN_INSTRUMENT="4cEEKadSe3p73rCQmbLaAgzBtdnzWaBJtjr2RBaQ5DgB" -export PSYOPTIONS_AMERICAN_INSTRUMENT="8jc8D8H1jkzJZvHmR6xXvxKwCCo4L2fg6gxoBYam1em9" - -# Same for devnet and mainnet -export BTC_ORACLE_ADDRESS="8SXvChNYFhRq4EZuZvnhjrB3jJRQCv4k3P4W6hesH3Ee" -export SOL_ORACLE_ADDRESS="GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR" - -export USDC_MINT="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" -export BTC_MINT="3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh" -export SOL_MINT="So11111111111111111111111111111111111111112" - -export USDC_MINT_DEVNET="BREWDGvXEQKx9FkZrSCajzjy4cpm9hofzze3b41Z3V4p" -export BTC_MINT_DEVNET="A3c9ThQZTUruMm56Eu4fxVwRosg4nBTpJe2B1pxBMYK7" -export SOL_MINT_DEVNET="FYQ5MgByxnkfGAUzNcbaD734VK8CdEUX49ioTkokypRc" - -export RPC_ENDPOINT="https://muddy-white-morning.solana-devnet.quiknode.pro/637131a6924513d7c83c65efc75e55a9ba2517e9/" \ No newline at end of file diff --git a/packages/cli/scripts/bootstrap.sh b/packages/cli/scripts/bootstrap.sh index f83aaabf3..be9e1d569 100755 --- a/packages/cli/scripts/bootstrap.sh +++ b/packages/cli/scripts/bootstrap.sh @@ -1,30 +1,53 @@ #!env bash set -e -source .env -export RPC_ENDPOINT="https://muddy-white-morning.solana-devnet.quiknode.pro/637131a6924513d7c83c65efc75e55a9ba2517e9/" +export DEVNET="true" -convergence protocol initialize --taker-fee=1 --maker-fee=0 --collateral-mint=$USDC_MINT_DEVNET -convergence risk-engine initialize +export SPOT_INSTRUMENT="CjQCEjXtG3QNBuT5Z1sctaAYCo5Mt6edftqHQetEPo9w" +export PSYOPTIONS_EUROPEAN_INSTRUMENT="A86fhhdNVDdXV8pB48WXtPeM3EBkcBeJEdrx9xrUo9nF" +export PSYOPTIONS_AMERICAN_INSTRUMENT="6JG1tWK4w6LmjeXbmDZJsmUsPSjgnp74j2XPsTvjjTX8" -convergence protocol add-instrument --instrument-program=$SPOT_INSTRUMENT --can-be-used-as-quote=true --validate-data-account-amount=1 --prepare-to-settle-account-amount=7 --settle-account-amount=3 --revert-preparation-account-amount=3 --clean-up-account-amount=4 -convergence protocol add-instrument --instrument-program=$PSYOPTIONS_EUROPEAN_INSTRUMENT --can-be-used-as-quote=true --validate-data-account-amount=2 --prepare-to-settle-account-amount=7 --settle-account-amount=3 --revert-preparation-account-amount=3 --clean-up-account-amount=4 -convergence protocol add-instrument --instrument-program=$PSYOPTIONS_AMERICAN_INSTRUMENT --can-be-used-as-quote=true --validate-data-account-amount=3 --prepare-to-settle-account-amount=7 --settle-account-amount=3 --revert-preparation-account-amount=3 --clean-up-account-amount=4 +# Same for devnet and mainnet +export BTC_ORACLE_ADDRESS="8SXvChNYFhRq4EZuZvnhjrB3jJRQCv4k3P4W6hesH3Ee" +export SOL_ORACLE_ADDRESS="GvDMxPzN1sCj7L26YDK2HnMRXEQmQ2aemov8YBtPS7vR" -convergence risk-engine set-instrument-type --program=$SPOT_INSTRUMENT --type=spot -convergence risk-engine set-instrument-type --program=$PSYOPTIONS_AMERICAN_INSTRUMENT --type=option -convergence risk-engine set-instrument-type --program=$PSYOPTIONS_EUROPEAN_INSTRUMENT --type=option +# Mainnet +if [ $DEVNET = "true" ]; then + export RPC_ENDPOINT="https://api.devnet.solana.com/" -convergence risk-engine set-risk-categories-info --new-value="0.05,0.5,0.02,0.2,0.04,0.3,0.08,0.4,0.12,0.5,0.2,0.6,0.3,0.7" --category=very-low -convergence risk-engine set-risk-categories-info --new-value="0.05,0.8,0.04,0.4,0.08,0.6,0.16,0.8,0.24,1.0,0.4,1.2,0.6,1.4" --category=low -convergence risk-engine set-risk-categories-info --new-value="0.05,1.2,0.06,0.6,0.12,0.9,0.24,1.2,0.36,1.5,0.6,1.8,0.9,2.1" --category=medium -convergence risk-engine set-risk-categories-info --new-value="0.05,2.4,0.08,0.8,0.16,1.2,0.32,1.6,0.48,2.0,0.8,2.4,1.2,2.8" --category=high -convergence risk-engine set-risk-categories-info --new-value="0.05,5.0,0.10,1.0,0.20,1.5,0.40,2.0,0.60,2.5,1.0,3.0,1.5,3.5" --category=very-high + export USDC_MINT="BREWDGvXEQKx9FkZrSCajzjy4cpm9hofzze3b41Z3V4p" + export BTC_MINT="A3c9ThQZTUruMm56Eu4fxVwRosg4nBTpJe2B1pxBMYK7" + export SOL_MINT="FYQ5MgByxnkfGAUzNcbaD734VK8CdEUX49ioTkokypRc" +else + export RPC_ENDPOINT="https://api.mainnet-beta.solana.com/" -convergence protocol add-base-asset --ticker=BTC --oracle-address=$BTC_ORACLE_ADDRESS --oracle-source=switchboard -convergence protocol add-base-asset --ticker=SOL --oracle-address=$SOL_ORACLE_ADDRESS --oracle-source=switchboard + export USDC_MINT="EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + export BTC_MINT="3NZ9JMVBmGAqocybic2c7LQCJScmgsAZ6vQqTDzcqmJh" + export SOL_MINT="So11111111111111111111111111111111111111112" +fi -convergence protocol register-mint --mint=$BTC_MINT --base-asset-index=0 -convergence protocol register-mint --mint=$SOL_MINT --base-asset-index=1 -convergence protocol register-mint --mint=$USDC_MINT +convergence protocol initialize --collateral-mint=$USDC_MINT --rpc-endpoint=$RPC_ENDPOINT + +convergence risk-engine initialize --rpc-endpoint=$RPC_ENDPOINT + +convergence protocol add-instrument --instrument-program=$SPOT_INSTRUMENT --can-be-used-as-quote=true --validate-data-account-amount=1 --prepare-to-settle-account-amount=7 --settle-account-amount=3 --revert-preparation-account-amount=3 --clean-up-account-amount=4 --rpc-endpoint=$RPC_ENDPOINT +convergence protocol add-instrument --instrument-program=$PSYOPTIONS_EUROPEAN_INSTRUMENT --can-be-used-as-quote=false --validate-data-account-amount=2 --prepare-to-settle-account-amount=7 --settle-account-amount=3 --revert-preparation-account-amount=3 --clean-up-account-amount=4 --rpc-endpoint=$RPC_ENDPOINT +convergence protocol add-instrument --instrument-program=$PSYOPTIONS_AMERICAN_INSTRUMENT --can-be-used-as-quote=false --validate-data-account-amount=3 --prepare-to-settle-account-amount=7 --settle-account-amount=3 --revert-preparation-account-amount=3 --clean-up-account-amount=4 --rpc-endpoint=$RPC_ENDPOINT + +convergence risk-engine set-instrument-type --program=$SPOT_INSTRUMENT --type=spot --rpc-endpoint=$RPC_ENDPOINT +convergence risk-engine set-instrument-type --program=$PSYOPTIONS_AMERICAN_INSTRUMENT --type=option --rpc-endpoint=$RPC_ENDPOINT +convergence risk-engine set-instrument-type --program=$PSYOPTIONS_EUROPEAN_INSTRUMENT --type=option --rpc-endpoint=$RPC_ENDPOINT + +convergence risk-engine set-risk-categories-info --new-value="0.05,0.5,0.02,0.2,0.04,0.3,0.08,0.4,0.12,0.5,0.2,0.6,0.3,0.7" --category=very-low --rpc-endpoint=$RPC_ENDPOINT +convergence risk-engine set-risk-categories-info --new-value="0.05,0.8,0.04,0.4,0.08,0.6,0.16,0.8,0.24,1.0,0.4,1.2,0.6,1.4" --category=low --rpc-endpoint=$RPC_ENDPOINT +convergence risk-engine set-risk-categories-info --new-value="0.05,1.2,0.06,0.6,0.12,0.9,0.24,1.2,0.36,1.5,0.6,1.8,0.9,2.1" --category=medium --rpc-endpoint=$RPC_ENDPOINT +convergence risk-engine set-risk-categories-info --new-value="0.05,2.4,0.08,0.8,0.16,1.2,0.32,1.6,0.48,2.0,0.8,2.4,1.2,2.8" --category=high --rpc-endpoint=$RPC_ENDPOINT +convergence risk-engine set-risk-categories-info --new-value="0.05,5.0,0.10,1.0,0.20,1.5,0.40,2.0,0.60,2.5,1.0,3.0,1.5,3.5" --category=very-high --rpc-endpoint=$RPC_ENDPOINT + +convergence protocol add-base-asset --ticker=BTC --oracle-address=$BTC_ORACLE_ADDRESS --oracle-source=switchboard --rpc-endpoint=$RPC_ENDPOINT +convergence protocol add-base-asset --ticker=SOL --oracle-address=$SOL_ORACLE_ADDRESS --oracle-source=switchboard --rpc-endpoint=$RPC_ENDPOINT + +convergence protocol register-mint --mint=$BTC_MINT --base-asset-index=0 --rpc-endpoint=$RPC_ENDPOINT +convergence protocol register-mint --mint=$SOL_MINT --base-asset-index=1 --rpc-endpoint=$RPC_ENDPOINT +convergence protocol register-mint --mint=$USDC_MINT --rpc-endpoint=$RPC_ENDPOINT diff --git a/packages/cli/src/logger.ts b/packages/cli/src/logger.ts index f6ae4a44b..9b398b165 100644 --- a/packages/cli/src/logger.ts +++ b/packages/cli/src/logger.ts @@ -61,6 +61,8 @@ export const logBaseAsset = (b: BaseAsset): void => { export const logRegisteredMint = (r: RegisteredMint): void => { l('Address:', r.address.toString()); + l('Mint:', r.mintAddress.toString()); + l('Decimals:', r.decimals.toString()); }; export const logCollateral = (c: Collateral): void => { diff --git a/packages/cli/tests/unit/protocol.spec.ts b/packages/cli/tests/unit/protocol.spec.ts index 39ebb7e8a..5fbe4d0cc 100644 --- a/packages/cli/tests/unit/protocol.spec.ts +++ b/packages/cli/tests/unit/protocol.spec.ts @@ -37,7 +37,7 @@ describe('unit.protocol', () => { it('get-registered-mints', async () => { await runCli(['protocol', 'get-registered-mints']); - expect(stub.args[1][0]).toEqual(ADDRESS_LABEL); + expect(stub.args[0][0]).toEqual(ADDRESS_LABEL); }); it('close', async () => { @@ -166,7 +166,7 @@ describe('unit.protocol', () => { it('get-registered-mints', async () => { await runCli(['protocol', 'get-registered-mints']); - expect(stub.args[1][0]).toEqual(ADDRESS_LABEL); + expect(stub.args[0][0]).toEqual(ADDRESS_LABEL); }); it('register-mint [collateral]', async () => { diff --git a/packages/js/CHANGELOG.md b/packages/js/CHANGELOG.md index e19e61d65..fbe4bced8 100644 --- a/packages/js/CHANGELOG.md +++ b/packages/js/CHANGELOG.md @@ -1,5 +1,85 @@ # @convergence-rfq/sdk +## 4.5.20 + +### Patch Changes + +- Update CPL Solita packages to version - 2.3.0 + +## 4.5.19 + +### Patch Changes + +- Fresh Deployment with new program Ids + +## 4.5.18 + +### Patch Changes + +- Update getResponseStateAndAction to account for responseExpiration + +## 4.5.17 + +### Patch Changes + +- Add expirationTimestamp to Response Model + Update CPL solita packages to version 2.2.14 + +## 4.5.16 + +### Patch Changes + +- Update CPL solita packages to version 2.2.13 + +## 4.5.15 + +### Patch Changes + +- fix technical debt + handle american put options + handle duplicate atas creation + +## 4.5.14 + +### Patch Changes + +- fix prepareSettlementLogic to handle transaction already processed error + +## 4.5.13 + +### Patch Changes + +- handle blockheight issue popping in browser + +## 4.5.12 + +### Patch Changes + +- fix BN error import + +## 4.5.11 + +### Patch Changes + +- handle option market creation in createRfq logic + optimize prepareSettlement logic for single tx confirmation + +## 4.5.10 + +### Patch Changes + +- refactor prepareSettlement logic to handle creation of atas and minting option tokens if required + add getTokenBalance operation to token module + add getRfqStateAndAction + add getResponseStateAndAction + fix cli-tests + +## 4.5.9 + +### Patch Changes + +- CLI get-registered-mints now logs mint address and decimals + ## 4.5.8 ### Patch Changes diff --git a/packages/js/package.json b/packages/js/package.json index d09989d8b..6cd86a84b 100644 --- a/packages/js/package.json +++ b/packages/js/package.json @@ -1,7 +1,7 @@ { "name": "@convergence-rfq/sdk", "description": "Official Convergence RFQ SDK", - "version": "4.5.8", + "version": "4.5.20", "license": "MIT", "publishConfig": { "access": "public" @@ -51,11 +51,11 @@ "@bundlr-network/client": "^0.8.8", "@convergence-rfq/beet": "0.7.10", "@convergence-rfq/beet-solana": "0.4.11", - "@convergence-rfq/psyoptions-american-instrument": "2.2.12", - "@convergence-rfq/psyoptions-european-instrument": "2.2.12", - "@convergence-rfq/rfq": "2.2.12", - "@convergence-rfq/risk-engine": "2.2.12", - "@convergence-rfq/spot-instrument": "2.2.12", + "@convergence-rfq/psyoptions-american-instrument": "2.3.0", + "@convergence-rfq/psyoptions-european-instrument": "2.3.0", + "@convergence-rfq/rfq": "2.3.0", + "@convergence-rfq/risk-engine": "2.3.0", + "@convergence-rfq/spot-instrument": "2.3.0", "@coral-xyz/borsh": "^0.26.0", "@mithraic-labs/psy-american": "^0.2.3", "@mithraic-labs/tokenized-euros": "^0.2.3", diff --git a/packages/js/src/plugins/identityModule/IdentityClient.ts b/packages/js/src/plugins/identityModule/IdentityClient.ts index 1f580f6d5..70fe64e68 100644 --- a/packages/js/src/plugins/identityModule/IdentityClient.ts +++ b/packages/js/src/plugins/identityModule/IdentityClient.ts @@ -51,6 +51,21 @@ export class IdentityClient return this.driver().signAllTransactions(transactions); } + async signTransactionMatrix( + ...transactionMatrix: Transaction[][] + ): Promise { + const txLengths = transactionMatrix.map((txs) => txs.length); + const flattendedTransactions = transactionMatrix.flat(); + const flattendedSignedTransactions = await this.signAllTransactions( + flattendedTransactions + ); + const constructedTxMatrix = txLengths.map((len) => + flattendedSignedTransactions.splice(0, len) + ); + + return constructedTxMatrix; + } + verifyMessage(message: Uint8Array, signature: Uint8Array): boolean { return ed25519.sync.verify(message, signature, this.publicKey.toBytes()); } diff --git a/packages/js/src/plugins/instrumentModule/methods.ts b/packages/js/src/plugins/instrumentModule/methods.ts index bc43319a0..78fe794c2 100644 --- a/packages/js/src/plugins/instrumentModule/methods.ts +++ b/packages/js/src/plugins/instrumentModule/methods.ts @@ -1,4 +1,4 @@ -import { Leg, QuoteAsset, legBeet } from '@convergence-rfq/rfq'; +import { ApiLeg, QuoteAsset, legBeet } from '@convergence-rfq/rfq'; import { AccountMeta } from '@solana/web3.js'; import { createSerializerFromFixableBeetArgsStruct } from '../../types'; @@ -10,7 +10,7 @@ import { SpotLegInstrument } from '../spotInstrumentModule'; import { LegInstrument, QuoteInstrument } from './types'; import { Convergence } from '@/Convergence'; -export function toLeg(legInstrument: LegInstrument): Leg { +export function toLeg(legInstrument: LegInstrument): ApiLeg { return { instrumentProgram: legInstrument.getProgramId(), baseAssetIndex: legInstrument.getBaseAssetIndex(), @@ -26,7 +26,10 @@ export function toLeg(legInstrument: LegInstrument): Leg { export function serializeAsLeg(legInstrument: LegInstrument) { const legSerializer = createSerializerFromFixableBeetArgsStruct(legBeet); - return legSerializer.serialize(toLeg(legInstrument)); + return legSerializer.serialize({ + ...toLeg(legInstrument), + reserved: new Array(64).fill(0), + }); } export function getSerializedLegLength(legInstrument: LegInstrument) { @@ -41,12 +44,11 @@ export function getProgramAccount(legInstrument: LegInstrument): AccountMeta { }; } -// TODO remove async part after option instruments refactoring -export async function getValidationAccounts( +export function getValidationAccounts( legInstrument: LegInstrument -): Promise { +): AccountMeta[] { return [getProgramAccount(legInstrument)].concat( - await legInstrument.getValidationAccounts() + legInstrument.getValidationAccounts() ); } diff --git a/packages/js/src/plugins/instrumentModule/types.ts b/packages/js/src/plugins/instrumentModule/types.ts index 600ec3a28..e1ee536f5 100644 --- a/packages/js/src/plugins/instrumentModule/types.ts +++ b/packages/js/src/plugins/instrumentModule/types.ts @@ -1,4 +1,4 @@ -import { AccountMeta } from '@solana/web3.js'; +import { AccountMeta, TransactionInstruction } from '@solana/web3.js'; import { BaseAssetIndex, Leg } from '@convergence-rfq/rfq'; import { PublicKey } from '../../types'; @@ -9,6 +9,8 @@ export interface LegInstrumentParser { parseFromLeg(convergence: Convergence, leg: Leg): LegInstrument; } +export type CreateOptionInstrumentsResult = TransactionInstruction[]; + export interface LegInstrument { getProgramId: () => PublicKey; getBaseAssetIndex: () => BaseAssetIndex; @@ -16,7 +18,8 @@ export interface LegInstrument { getDecimals: () => number; getSide: () => LegSide; serializeInstrumentData: () => Buffer; - getValidationAccounts(): Promise; + getValidationAccounts(): AccountMeta[]; + getPreparationsBeforeRfqCreation(): Promise; } // TODO add registration of quote instruments diff --git a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/accounts.ts b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/accounts.ts index d5ea6e0cc..93722b58e 100644 --- a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/accounts.ts +++ b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/accounts.ts @@ -1,17 +1,5 @@ import { OptionMarket } from '@mithraic-labs/psy-american'; -import { - Account, - //getAccountParsingAndAssertingFunction, - //getAccountParsingFunction, -} from '@/types'; +import { Account } from '@/types'; /** @group Accounts */ export type PsyoptionsAmericanInstrumentAccount = Account; - -/** @group Account Helpers */ -//export const parsePsyoptionsEuropeanInstrumentAccount = -// getAccountParsingFunction(Account); - -/** @group Account Helpers */ -//export const toPsyoptionsEuropeanInstrumentAccount = -// getAccountParsingAndAssertingFunction(Instrument); diff --git a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/helpers.ts b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/helpers.ts index 3e6499227..f216f356c 100644 --- a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/helpers.ts +++ b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/helpers.ts @@ -1,25 +1,28 @@ import * as psyoptionsAmerican from '@mithraic-labs/psy-american'; - import { BN } from 'bn.js'; import { PublicKey } from '@solana/web3.js'; import { Convergence } from '../../Convergence'; -import { ATAExistence, getOrCreateATA } from '../../utils/ata'; -import { Mint } from '../tokenModule/models'; +import { getOrCreateATAtxBuilder } from '../../utils/ata'; import { CvgWallet } from '../../utils/Wallets'; -import { - InstructionWithSigners, - TransactionBuilder, -} from '../../utils/TransactionBuilder'; +import { InstructionUniquenessTracker } from '../../utils/classes'; import { PsyoptionsAmericanInstrument } from './types'; import { createAmericanProgram } from './instrument'; +import { TransactionBuilder } from '@/utils/TransactionBuilder'; -export const mintAmericanOptions = async ( +export type PrepareAmericanOptionsResult = { + ataTxBuilders: TransactionBuilder[]; + mintTxBuilders: TransactionBuilder[]; +}; +//create American Options ATAs and mint Options +export const prepareAmericanOptions = async ( convergence: Convergence, responseAddress: PublicKey, - caller: PublicKey, - americanProgram: any -) => { + caller: PublicKey +): Promise => { + const ixTracker = new InstructionUniquenessTracker([]); + const cvgWallet = new CvgWallet(convergence); + const americanProgram = createAmericanProgram(convergence, cvgWallet); const response = await convergence .rfqs() .findResponseByAddress({ address: responseAddress }); @@ -28,160 +31,84 @@ export const mintAmericanOptions = async ( .findRfqByAddress({ address: response.rfq }); const callerSide = caller.equals(rfq.taker) ? 'taker' : 'maker'; - const instructionWithSigners: InstructionWithSigners[] = []; - const { legs } = await convergence.rfqs().getSettlementResult({ + + const { legs: legExchangeResult } = convergence.rfqs().getSettlementResult({ response, rfq, }); - for (const [index, leg] of rfq.legs.entries()) { - if (leg instanceof PsyoptionsAmericanInstrument) { - const { receiver } = legs[index]; - if (receiver !== callerSide) { - const { amount } = legs[index]; - - const optionMarket = await psyoptionsAmerican.getOptionByKey( - americanProgram, - leg.optionMetaPubKey - ); - if (optionMarket) { - const optionToken = await getOrCreateATA( - convergence, - optionMarket.optionMint, - caller - ); - - const writerToken = await getOrCreateATA( - convergence, - optionMarket!.writerTokenMint, - caller - ); - const underlyingToken = await getOrCreateATA( - convergence, - optionMarket!.underlyingAssetMint, - caller - ); - - const ixWithSigners = - await psyoptionsAmerican.instructions.mintOptionV2Instruction( - americanProgram, - optionToken, - writerToken, - underlyingToken, - new BN(amount!), - optionMarket as psyoptionsAmerican.OptionMarketWithKey - ); - ixWithSigners.ix.keys[0] = { - pubkey: caller, - isSigner: true, - isWritable: false, - }; - instructionWithSigners.push({ - instruction: ixWithSigners.ix, - signers: ixWithSigners.signers, - }); - } - } + const ataTxBuilderArray: TransactionBuilder[] = []; + const mintTxBuilderArray: TransactionBuilder[] = []; + for (const [index, leg] of rfq.legs.entries()) { + const { receiver, amount } = legExchangeResult[index]; + if ( + !(leg instanceof PsyoptionsAmericanInstrument) || + receiver === callerSide + ) { + continue; } - } - if (instructionWithSigners.length > 0) { - const payer = convergence.rpc().getDefaultFeePayer(); - const txBuilder = TransactionBuilder.make().setFeePayer(payer); - - txBuilder.add(...instructionWithSigners); - const sig = await txBuilder.sendAndConfirm(convergence); - return sig; - } - return null; -}; - -export const initializeNewAmericanOption = async ( - convergence: Convergence, - underlyingMint: Mint, - quoteMint: Mint, - quoteAmountPerContract: number, - underlyingAmountPerContract: number, - expiration: number -) => { - const expirationUnixTimestamp = new BN(Date.now() / 1_000 + expiration); - const quoteAmountPerContractBN = new BN( - Number(quoteAmountPerContract) * Math.pow(10, quoteMint.decimals) - ); - const underlyingAmountPerContractBN = new BN( - Number(underlyingAmountPerContract) * Math.pow(10, underlyingMint.decimals) - ); - - const americanProgram = createAmericanProgram( - convergence, - new CvgWallet(convergence) - ); - - const { optionMarketKey, optionMintKey, writerMintKey } = - await psyoptionsAmerican.instructions.initializeMarket(americanProgram, { - expirationUnixTimestamp, - quoteAmountPerContract: quoteAmountPerContractBN, - quoteMint: quoteMint.address, - underlyingAmountPerContract: underlyingAmountPerContractBN, - underlyingMint: underlyingMint.address, + const optionMarket = await leg.getOptionMeta(); + const optionToken = await getOrCreateATAtxBuilder( + convergence, + optionMarket.optionMint, + caller + ); + if (optionToken.txBuilder && ixTracker.checkedAdd(optionToken.txBuilder)) { + ataTxBuilderArray.push(optionToken.txBuilder); + } + const writerToken = await getOrCreateATAtxBuilder( + convergence, + optionMarket!.writerTokenMint, + caller + ); + if (writerToken.txBuilder && ixTracker.checkedAdd(writerToken.txBuilder)) { + ataTxBuilderArray.push(writerToken.txBuilder); + } + const underlyingToken = await getOrCreateATAtxBuilder( + convergence, + optionMarket!.underlyingAssetMint, + caller + ); + if ( + underlyingToken.txBuilder && + ixTracker.checkedAdd(underlyingToken.txBuilder) + ) { + ataTxBuilderArray.push(underlyingToken.txBuilder); + } + const { tokenBalance } = await convergence.tokens().getTokenBalance({ + mintAddress: optionMarket.optionMint, + owner: caller, + mintDecimals: PsyoptionsAmericanInstrument.decimals, }); - const optionMarket = (await psyoptionsAmerican.getOptionByKey( - americanProgram, - optionMarketKey - )) as psyoptionsAmerican.OptionMarketWithKey; - - const optionMint = await convergence - .tokens() - .findMintByAddress({ address: optionMintKey }); - + const tokensToMint = amount - tokenBalance; + if (tokensToMint <= 0) continue; + const ixWithSigners = + await psyoptionsAmerican.instructions.mintOptionV2Instruction( + americanProgram, + optionToken.ataPubKey, + writerToken.ataPubKey, + underlyingToken.ataPubKey, + new BN(tokensToMint), + optionMarket as psyoptionsAmerican.OptionMarketWithKey + ); + ixWithSigners.ix.keys[0] = { + pubkey: caller, + isSigner: true, + isWritable: false, + }; + const mintTxBuilder = TransactionBuilder.make().setFeePayer( + convergence.rpc().getDefaultFeePayer() + ); + mintTxBuilder.add({ + instruction: ixWithSigners.ix, + signers: [convergence.identity()], + }); + mintTxBuilderArray.push(mintTxBuilder); + } return { - optionMarketKey, - optionMarket, - optionMintKey, - writerMintKey, - optionMint, + ataTxBuilders: ataTxBuilderArray, + mintTxBuilders: mintTxBuilderArray, }; }; - -// used in UI -export const getOrCreateAmericanOptionATAs = async ( - convergence: Convergence, - responseAddress: PublicKey, - caller: PublicKey, - americanProgram: any -): Promise => { - let flag = false; - const response = await convergence - .rfqs() - .findResponseByAddress({ address: responseAddress }); - const rfq = await convergence - .rfqs() - .findRfqByAddress({ address: response.rfq }); - - const callerSide = caller.equals(rfq.taker) ? 'taker' : 'maker'; - const { legs } = await convergence.rfqs().getSettlementResult({ - response, - rfq, - }); - for (const [index, leg] of rfq.legs.entries()) { - if (leg instanceof PsyoptionsAmericanInstrument) { - const { receiver } = legs[index]; - if (receiver !== callerSide) { - flag = true; - - const optionMarket = await psyoptionsAmerican.getOptionByKey( - americanProgram, - leg.optionMetaPubKey - ); - if (optionMarket) { - await getOrCreateATA(convergence, optionMarket.optionMint, caller); - } - } - } - } - if (flag === true) { - return ATAExistence.EXISTS; - } - return ATAExistence.NOTEXISTS; -}; diff --git a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/index.ts b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/index.ts index cbe4783ae..53d92badd 100644 --- a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/index.ts +++ b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/index.ts @@ -1,6 +1,5 @@ export * from './plugin'; export * from './types'; -export * from './models'; export * from './programs'; export * from './instrument'; export * from './helpers'; diff --git a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/instrument.ts b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/instrument.ts index ad226fc03..e6c7b4d70 100644 --- a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/instrument.ts +++ b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/instrument.ts @@ -1,4 +1,4 @@ -import { Keypair, PublicKey } from '@solana/web3.js'; +import { Keypair, PublicKey, TransactionInstruction } from '@solana/web3.js'; import { Leg, BaseAssetIndex } from '@convergence-rfq/rfq'; import { OptionMarketWithKey } from '@mithraic-labs/psy-american'; import { OptionType } from '@mithraic-labs/tokenized-euros'; @@ -8,12 +8,19 @@ import * as anchor from '@project-serum/anchor'; import * as psyoptionsAmerican from '@mithraic-labs/psy-american'; import BN from 'bn.js'; import { Mint } from '../tokenModule'; -import { LegInstrument } from '../instrumentModule'; +import { + CreateOptionInstrumentsResult, + LegInstrument, +} from '../instrumentModule'; import { addDecimals, removeDecimals } from '../../utils/conversions'; import { Convergence } from '../../Convergence'; import { createSerializerFromFixableBeetArgsStruct } from '../../types'; import { LegSide, fromSolitaLegSide } from '../rfqModule/models/LegSide'; import { CvgWallet, NoopWallet } from '../../utils/Wallets'; +import { + GetOrCreateATAtxBuilderReturnType, + getOrCreateATAtxBuilder, +} from '@/utils'; type PsyoptionsAmericanInstrumentData = { optionType: OptionType; @@ -53,29 +60,52 @@ export class PsyoptionsAmericanInstrument implements LegInstrument { private readonly underlyingAmountPerContractDecimals: number, readonly strikePrice: number, // without decimals private readonly strikePriceDecimals: number, - readonly expiration: number, // timestamp in seconds + readonly expirationTimestamp: number, // timestamp in seconds readonly optionMint: PublicKey, readonly optionMetaPubKey: PublicKey, readonly baseAssetIndex: BaseAssetIndex, readonly amount: number, readonly side: LegSide, - private optionMeta?: OptionMarketWithKey + readonly underlyingAssetMint?: PublicKey, + readonly stableAssetMint?: PublicKey ) {} getBaseAssetIndex = () => this.baseAssetIndex; getAmount = () => this.amount; getDecimals = () => PsyoptionsAmericanInstrument.decimals; getSide = () => this.side; + async getPreparationsBeforeRfqCreation(): Promise { + if (!this.underlyingAssetMint) { + throw new Error('Missing underlying asset mint'); + } + if (!this.stableAssetMint) { + throw new Error('Missing stable asset mint'); + } + + const optionMarketIxs = await getPsyAmericanMarketIxs( + this.convergence, + this.underlyingAssetMint, + this.underlyingAmountPerContractDecimals, + this.underlyingAmountPerContract, + this.stableAssetMint, + this.strikePriceDecimals, + this.strikePrice, + this.expirationTimestamp, + this.optionType + ); + return optionMarketIxs; + } static async create( convergence: Convergence, underlyingMint: Mint, - quoteMint: Mint, + stableMint: Mint, optionType: OptionType, - optionMeta: OptionMarketWithKey, - optionMetaPubkey: PublicKey, amount: number, - side: LegSide + side: LegSide, + underlyingAmountPerContract: number, + strike: number, + expirationTimestamp: number ) { const mintInfoAddress = convergence .rfqs() @@ -89,23 +119,32 @@ export class PsyoptionsAmericanInstrument implements LegInstrument { throw Error('Stablecoin mint cannot be used in a leg!'); } + const cvgWallet = new CvgWallet(convergence); + const americanProgram = await createAmericanProgram(convergence, cvgWallet); + const { optionMint, metaKey } = await getAmericanOptionkeys( + americanProgram, + underlyingMint, + stableMint, + expirationTimestamp, + strike, + underlyingAmountPerContract, + optionType + ); return new PsyoptionsAmericanInstrument( convergence, optionType, - removeDecimals( - optionMeta.underlyingAmountPerContract, - underlyingMint.decimals - ), + underlyingAmountPerContract, underlyingMint.decimals, - removeDecimals(optionMeta.quoteAmountPerContract, quoteMint.decimals), - quoteMint.decimals, - Number(optionMeta.expirationUnixTimestamp), - optionMeta.optionMint, - optionMetaPubkey, + strike, + stableMint.decimals, + expirationTimestamp, + optionMint, + metaKey, mintInfo.mintType.baseAssetIndex, amount, side, - optionMeta + underlyingMint.address, + stableMint.address ); } @@ -113,7 +152,8 @@ export class PsyoptionsAmericanInstrument implements LegInstrument { convergence: Convergence, metaKey: PublicKey ): Promise { - const americanProgram = createAmericanProgram(convergence); + const cvgWallet = new CvgWallet(convergence); + const americanProgram = createAmericanProgram(convergence, cvgWallet); const optionMarket = (await psyoptionsAmerican.getOptionByKey( americanProgram, metaKey @@ -123,27 +163,29 @@ export class PsyoptionsAmericanInstrument implements LegInstrument { } async getOptionMeta() { - if (this.optionMeta === undefined) { - this.optionMeta = await PsyoptionsAmericanInstrument.fetchMeta( - this.convergence, - this.optionMetaPubKey - ); - } + const optionMeta = await PsyoptionsAmericanInstrument.fetchMeta( + this.convergence, + this.optionMetaPubKey + ); - return this.optionMeta; + return optionMeta; } - async getValidationAccounts() { - const optionMeta = await this.getOptionMeta(); - + getValidationAccounts() { + if (!this.underlyingAssetMint) { + throw new Error('Missing underlying asset mint'); + } + if (!this.stableAssetMint) { + throw new Error('Missing stable asset mint'); + } const mintInfoPda = this.convergence .rfqs() .pdas() - .mintInfo({ mint: optionMeta.underlyingAssetMint }); + .mintInfo({ mint: this.underlyingAssetMint }); const quoteAssetMintPda = this.convergence .rfqs() .pdas() - .mintInfo({ mint: optionMeta.quoteAssetMint }); + .mintInfo({ mint: this.stableAssetMint }); return [ { pubkey: this.optionMetaPubKey, isSigner: false, isWritable: false }, { @@ -178,7 +220,7 @@ export class PsyoptionsAmericanInstrument implements LegInstrument { this.underlyingAmountPerContractDecimals, strikePrice: addDecimals(this.strikePrice, this.strikePriceDecimals), strikePriceDecimals: this.strikePriceDecimals, - expiration: new BN(this.expiration), + expiration: new BN(this.expirationTimestamp), optionMint: this.optionMint, metaKey: this.optionMetaPubKey, }; @@ -253,3 +295,188 @@ export const createAmericanProgram = ( return americanProgram; }; + +export const getPsyAmericanMarketIxs = async ( + cvg: Convergence, + underlyingMint: PublicKey, + underlyingMintDecimals: number, + underlyingAmountPerContract: number, + stableMint: PublicKey, + stableMintDecimals: number, + strike: number, + expirationTimestamp: number, + optionType: OptionType +): Promise => { + const cvgWallet = new CvgWallet(cvg); + const americanProgram = createAmericanProgram(cvg, cvgWallet); + + const expirationTimestampBN = new BN(expirationTimestamp); + const quoteAmountPerContractBN = new BN( + addDecimals(strike, stableMintDecimals) + ); + const underlyingAmountPerContractBN = new BN( + addDecimals(underlyingAmountPerContract, underlyingMintDecimals) + ); + + let quoteAmountPerContractToPass = quoteAmountPerContractBN; + let underlyingAmountPerContractToPass = underlyingAmountPerContractBN; + let stableMintToPass = stableMint; + let underlyingMintToPass = underlyingMint; + if (optionType === OptionType.PUT) { + quoteAmountPerContractToPass = underlyingAmountPerContractBN; + underlyingAmountPerContractToPass = quoteAmountPerContractBN; + stableMintToPass = underlyingMint; + underlyingMintToPass = stableMint; + } + const [optionMarketKey] = await psyoptionsAmerican.deriveOptionKeyFromParams({ + expirationUnixTimestamp: expirationTimestampBN, + programId: americanProgram.programId, + quoteAmountPerContract: quoteAmountPerContractToPass, + quoteMint: stableMintToPass, + underlyingAmountPerContract: underlyingAmountPerContractToPass, + underlyingMint: underlyingMintToPass, + }); + const optionMarket = await psyoptionsAmerican.getOptionByKey( + americanProgram, + optionMarketKey + ); + if (optionMarket) { + return []; + } + // If there is no existing market, derive the optionMarket from inputs + const optionMarketIxs: TransactionInstruction[] = []; + const { optionMarketIx, mintFeeAccount, exerciseFeeAccount } = + await getPsyAmericanOptionMarketAccounts( + cvg, + americanProgram, + expirationTimestampBN, + quoteAmountPerContractBN, + stableMint, + underlyingAmountPerContractBN, + underlyingMint, + optionType + ); + if (mintFeeAccount.txBuilder) { + optionMarketIxs.push(...mintFeeAccount.txBuilder.getInstructions()); + } + + if (exerciseFeeAccount.txBuilder) { + optionMarketIxs.push(...exerciseFeeAccount.txBuilder.getInstructions()); + } + + optionMarketIxs.push(optionMarketIx.tx); + return optionMarketIxs; +}; + +export type GetAmericanOptionMetaResult = { + optionMint: PublicKey; + metaKey: PublicKey; +}; +export const getAmericanOptionkeys = async ( + americanProgram: any, + underlyingMint: Mint, + stableMint: Mint, + expirationUnixTimestamp: number, + strike: number, + underlyingAmountPerContract: number, + optionType: OptionType +): Promise => { + const quoteAmountPerContractBN = new BN( + addDecimals(strike, stableMint.decimals) + ); + const underlyingAmountPerContractBN = new BN( + addDecimals(underlyingAmountPerContract, underlyingMint.decimals) + ); + + let quoteAmountPerContractToPass = quoteAmountPerContractBN; + let underlyingAmountPerContractToPass = underlyingAmountPerContractBN; + let stableMintToPass = stableMint; + let underlyingMintToPass = underlyingMint; + if (optionType === OptionType.PUT) { + quoteAmountPerContractToPass = underlyingAmountPerContractBN; + underlyingAmountPerContractToPass = quoteAmountPerContractBN; + stableMintToPass = underlyingMint; + underlyingMintToPass = stableMint; + } + + const [metaKey] = await psyoptionsAmerican.deriveOptionKeyFromParams({ + expirationUnixTimestamp: new BN(expirationUnixTimestamp), + programId: americanProgram.programId, + quoteAmountPerContract: quoteAmountPerContractToPass, + quoteMint: stableMintToPass.address, + underlyingAmountPerContract: underlyingAmountPerContractToPass, + underlyingMint: underlyingMintToPass.address, + }); + + const [optionMint] = PublicKey.findProgramAddressSync( + [metaKey.toBuffer(), Buffer.from('optionToken')], + americanProgram.programId + ); + + return { optionMint, metaKey }; +}; + +export type GetPsyAmericanOptionMarketAccounts = { + optionMarketIx: { + optionMarketKey: PublicKey; + optionMintKey: PublicKey; + quoteAssetPoolKey: PublicKey; + tx: TransactionInstruction; + underlyingAssetPoolKey: PublicKey; + writerMintKey: PublicKey; + }; + mintFeeAccount: GetOrCreateATAtxBuilderReturnType; + exerciseFeeAccount: GetOrCreateATAtxBuilderReturnType; +}; + +const getPsyAmericanOptionMarketAccounts = async ( + cvg: Convergence, + americanProgram: any, + expirationUnixTimestamp: BN, + quoteAmountPerContract: BN, + stableMint: PublicKey, + underlyingAmountPerContract: BN, + underlyingMint: PublicKey, + optionType: OptionType +): Promise => { + let quoteAmountPerContractToPass = quoteAmountPerContract; + let underlyingAmountPerContractToPass = underlyingAmountPerContract; + let stableMintToPass = stableMint; + let underlyingMintToPass = underlyingMint; + if (optionType === OptionType.PUT) { + quoteAmountPerContractToPass = underlyingAmountPerContract; + underlyingAmountPerContractToPass = quoteAmountPerContract; + stableMintToPass = underlyingMint; + underlyingMintToPass = stableMint; + } + const optionMarketIx = + await psyoptionsAmerican.instructions.initializeOptionInstruction( + americanProgram, + { + /** The option market expiration timestamp in seconds */ + expirationUnixTimestamp, + quoteAmountPerContract: quoteAmountPerContractToPass, + quoteMint: stableMintToPass, + underlyingAmountPerContract: underlyingAmountPerContractToPass, + underlyingMint: underlyingMintToPass, + } + ); + const feeOwner = psyoptionsAmerican.FEE_OWNER_KEY; + const mintFeeAccount = await getOrCreateATAtxBuilder( + cvg, + underlyingMintToPass, + feeOwner + ); + + const exerciseFeeAccount = await getOrCreateATAtxBuilder( + cvg, + stableMintToPass, + feeOwner + ); + + return { + optionMarketIx, + mintFeeAccount, + exerciseFeeAccount, + }; +}; diff --git a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/models/index.ts b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/models/index.ts deleted file mode 100644 index 47ffefff2..000000000 --- a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/models/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from '../instrument'; diff --git a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/plugin.ts b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/plugin.ts index af8db0217..54e73f71c 100644 --- a/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/plugin.ts +++ b/packages/js/src/plugins/psyoptionsAmericanInstrumentModule/plugin.ts @@ -1,6 +1,6 @@ import { ProgramClient } from '../programModule'; import { PSYOPTIONS_AMERICAN_INSTRUMENT_PROGRAM_ID } from './types'; -import { psyoptionsAmericanInstrumentParser } from './models'; +import { psyoptionsAmericanInstrumentParser } from './instrument'; import { ConvergencePlugin, Program } from '@/types'; import type { Convergence } from '@/Convergence'; diff --git a/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/accounts.ts b/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/accounts.ts index 6412d169d..194206c7c 100644 --- a/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/accounts.ts +++ b/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/accounts.ts @@ -1,18 +1,5 @@ import { EuroMeta } from '@convergence-rfq/psyoptions-european-instrument'; -//import { Instrument } from '@convergence-rfq/rfq'; -import { - Account, - //getAccountParsingAndAssertingFunction, - //getAccountParsingFunction, -} from '@/types'; +import { Account } from '@/types'; /** @group Accounts */ export type PsyoptionsEuropeanInstrumentAccount = Account; - -/** @group Account Helpers */ -//export const parsePsyoptionsEuropeanInstrumentAccount = -// getAccountParsingFunction(Account); - -/** @group Account Helpers */ -//export const toPsyoptionsEuropeanInstrumentAccount = -// getAccountParsingAndAssertingFunction(Instrument); diff --git a/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/helpers.ts b/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/helpers.ts index d3c021a2a..6d14ea69d 100644 --- a/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/helpers.ts +++ b/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/helpers.ts @@ -1,128 +1,27 @@ import * as psyoptionsEuropean from '@mithraic-labs/tokenized-euros'; -import * as anchor from '@project-serum/anchor'; -import { Keypair, PublicKey } from '@solana/web3.js'; -import { BN } from 'bn.js'; -import { Mint } from '../tokenModule'; -import { ATAExistence, getOrCreateATA } from '../../utils/ata'; +import { PublicKey } from '@solana/web3.js'; +import { getOrCreateATAtxBuilder } from '../../utils/ata'; import { addDecimals } from '../../utils/conversions'; import { TransactionBuilder } from '../../utils/TransactionBuilder'; import { Convergence } from '../../Convergence'; -import { PsyoptionsEuropeanInstrument } from './instrument'; -import { Pda } from '@/types/Pda'; -import { makeConfirmOptionsFinalizedOnMainnet } from '@/types/Operation'; -import { toBigNumber } from '@/types/BigNumber'; - -export const initializeNewEuropeanOption = async ( - convergence: Convergence, - oracle: PublicKey, - europeanProgram: anchor.Program, - underlyingMint: Mint, - stableMint: Mint, - strikePrice: number, - underlyingAmountPerContract: number, - expiration: number, - oracleProviderId = 1 -) => { - const expirationTimestamp = new BN(Date.now() / 1_000 + expiration); - - let { instructions: initializeIxs } = - await psyoptionsEuropean.instructions.initializeAllAccountsInstructions( - europeanProgram, - underlyingMint.address, - stableMint.address, - oracle, - expirationTimestamp, - stableMint.decimals, - oracleProviderId - ); - - const tx = TransactionBuilder.make(); - - const underlyingPoolKey = Pda.find(europeanProgram.programId, [ - underlyingMint.address.toBuffer(), - Buffer.from('underlyingPool', 'utf-8'), - ]); - // TODO: Use retry method - const underlyingPoolAccount = await convergence.connection.getAccountInfo( - underlyingPoolKey - ); - if (underlyingPoolAccount && initializeIxs.length === 3) { - initializeIxs = initializeIxs.slice(1); - } - const stablePoolKey = Pda.find(europeanProgram.programId, [ - stableMint.address.toBuffer(), - Buffer.from('stablePool', 'utf-8'), - ]); - // TODO: Use retry method - const stablePoolAccount = await convergence.connection.getAccountInfo( - stablePoolKey - ); - if (stablePoolAccount && initializeIxs.length === 2) { - initializeIxs = initializeIxs.slice(1); - } else if (stablePoolAccount && initializeIxs.length === 3) { - initializeIxs.splice(1, 1); - } - - initializeIxs.forEach((ix) => { - tx.add({ instruction: ix, signers: [] }); - }); - - const confirmOptions = makeConfirmOptionsFinalizedOnMainnet(convergence); - - if (initializeIxs.length > 0) { - await tx.sendAndConfirm(convergence, confirmOptions); - } - - const strikePriceSize = addDecimals(strikePrice, stableMint.decimals); - const underlyingAmountPerContractSize = addDecimals( - underlyingAmountPerContract, - underlyingMint.decimals - ); - - const { - instruction: createIx, - euroMeta, - euroMetaKey, - expirationData, - } = await psyoptionsEuropean.instructions.createEuroMetaInstruction( - europeanProgram, - underlyingMint.address, - underlyingMint.decimals, - stableMint.address, - stableMint.decimals, - expirationTimestamp, - toBigNumber(underlyingAmountPerContractSize), - toBigNumber(strikePriceSize), - stableMint.decimals, - oracle, - oracleProviderId - ); - - await TransactionBuilder.make() - .add({ instruction: createIx, signers: [] }) - .sendAndConfirm(convergence); - - return { - euroMeta, - euroMetaKey, - expirationData, - }; -}; - -export const createEuropeanProgram = async (convergence: Convergence) => { - return psyoptionsEuropean.createProgram( - convergence.rpc().getDefaultFeePayer() as Keypair, - convergence.connection.rpcEndpoint, - new PublicKey(psyoptionsEuropean.programId) - ); +import { InstructionUniquenessTracker } from '../../utils/classes'; +import { + PsyoptionsEuropeanInstrument, + createEuropeanProgram, +} from './instrument'; + +export type PrepareEuropeanOptionsResult = { + ataTxBuilders: TransactionBuilder[]; + mintTxBuilders: TransactionBuilder[]; }; - -export const mintEuropeanOptions = async ( +// create European Option ATAs and mint options +export const prepareEuropeanOptions = async ( convergence: Convergence, responseAddress: PublicKey, - caller: PublicKey, - europeanProgram: any + caller: PublicKey ) => { + const ixTracker = new InstructionUniquenessTracker([]); + const europeanProgram = await createEuropeanProgram(convergence); const response = await convergence .rfqs() .findResponseByAddress({ address: responseAddress }); @@ -130,137 +29,102 @@ export const mintEuropeanOptions = async ( .rfqs() .findRfqByAddress({ address: response.rfq }); - const callerIsTaker = caller.toBase58() === rfq.taker.toBase58(); - const callerSide = callerIsTaker ? 'taker' : 'maker'; - const instructions: anchor.web3.TransactionInstruction[] = []; - const { legs } = await convergence.rfqs().getSettlementResult({ + const callerSide = caller.equals(rfq.taker) ? 'taker' : 'maker'; + + const { legs: legExchangeResult } = convergence.rfqs().getSettlementResult({ response, rfq, }); + const mintTxBuilderArray: TransactionBuilder[] = []; + const ataTxBuilderArray: TransactionBuilder[] = []; for (const [index, leg] of rfq.legs.entries()) { - if (leg instanceof PsyoptionsEuropeanInstrument) { - const { receiver } = legs[index]; + const { receiver, amount } = legExchangeResult[index]; + if ( + !(leg instanceof PsyoptionsEuropeanInstrument) || + receiver === callerSide + ) { + continue; + } + const euroMeta = await leg.getOptionMeta(); + const { stableMint, underlyingMint } = euroMeta; + const stableMintToken = convergence.tokens().pdas().associatedTokenAccount({ + mint: stableMint, + owner: caller, + }); + const underlyingMintToken = convergence + .tokens() + .pdas() + .associatedTokenAccount({ + mint: underlyingMint, + owner: caller, + }); + const minterCollateralKey = + leg.optionType == psyoptionsEuropean.OptionType.PUT + ? stableMintToken + : underlyingMintToken; + + const optionToken = await getOrCreateATAtxBuilder( + convergence, + leg.optionType == psyoptionsEuropean.OptionType.PUT + ? euroMeta.putOptionMint + : euroMeta.callOptionMint, + caller + ); - if (receiver !== callerSide) { - const { amount } = legs[index]; + if (optionToken.txBuilder && ixTracker.checkedAdd(optionToken.txBuilder)) { + ataTxBuilderArray.push(optionToken.txBuilder); + } + const writerToken = await getOrCreateATAtxBuilder( + convergence, + leg.optionType == psyoptionsEuropean.OptionType.PUT + ? euroMeta.putWriterMint + : euroMeta.callWriterMint, + caller + ); + if (writerToken.txBuilder && ixTracker.checkedAdd(writerToken.txBuilder)) { + ataTxBuilderArray.push(writerToken.txBuilder); + } - const euroMeta = await leg.getOptionMeta(); - const { stableMint } = euroMeta; - const { underlyingMint } = euroMeta; - const stableMintToken = convergence - .tokens() - .pdas() - .associatedTokenAccount({ - mint: stableMint, - owner: caller, - }); - const underlyingMintToken = convergence - .tokens() - .pdas() - .associatedTokenAccount({ - mint: underlyingMint, - owner: caller, - }); - const minterCollateralKey = - leg.optionType == psyoptionsEuropean.OptionType.PUT - ? stableMintToken - : underlyingMintToken; + const { tokenBalance } = await convergence.tokens().getTokenBalance({ + mintAddress: + leg.optionType == psyoptionsEuropean.OptionType.PUT + ? euroMeta.putOptionMint + : euroMeta.callOptionMint, + owner: caller, + mintDecimals: PsyoptionsEuropeanInstrument.decimals, + }); - const optionDestination = await getOrCreateATA( - convergence, - leg.optionType == psyoptionsEuropean.OptionType.PUT - ? euroMeta.putOptionMint - : euroMeta.callOptionMint, - caller - ); - const writerDestination = await getOrCreateATA( - convergence, - leg.optionType == psyoptionsEuropean.OptionType.PUT - ? euroMeta.putWriterMint - : euroMeta.callWriterMint, - caller - ); - const { instruction: ix } = psyoptionsEuropean.instructions.mintOptions( - europeanProgram, - leg.optionMetaPubKey, - euroMeta as psyoptionsEuropean.EuroMeta, - minterCollateralKey, - optionDestination, - writerDestination, - new BN(addDecimals(amount, PsyoptionsEuropeanInstrument.decimals)), - leg.optionType - ); + const tokensToMint = amount - tokenBalance; + if (tokensToMint! <= 0) continue; + const { instruction: ix } = psyoptionsEuropean.instructions.mintOptions( + europeanProgram, + leg.optionMetaPubKey, + euroMeta as psyoptionsEuropean.EuroMeta, + minterCollateralKey, + optionToken.ataPubKey, + writerToken.ataPubKey, + addDecimals(tokensToMint, PsyoptionsEuropeanInstrument.decimals), + leg.optionType + ); - ix.keys[0] = { - pubkey: caller, - isSigner: true, - isWritable: false, - }; + ix.keys[0] = { + pubkey: caller, + isSigner: true, + isWritable: false, + }; - instructions.push(ix); - } - } - } - if (instructions.length > 0) { - const txBuilder = TransactionBuilder.make().setFeePayer( + const mintTxBuilder = TransactionBuilder.make().setFeePayer( convergence.rpc().getDefaultFeePayer() ); - - instructions.forEach((ins) => { - txBuilder.add({ - instruction: ins, - signers: [convergence.identity()], - }); + mintTxBuilder.add({ + instruction: ix, + signers: [convergence.identity()], }); - - const confirmOptions = makeConfirmOptionsFinalizedOnMainnet(convergence, { - skipPreflight: true, - }); - - const sig = await txBuilder.sendAndConfirm(convergence, confirmOptions); - return sig; + mintTxBuilderArray.push(mintTxBuilder); } - return null; -}; -export const getOrCreateEuropeanOptionATAs = async ( - convergence: Convergence, - responseAddress: PublicKey, - caller: PublicKey -): Promise => { - let flag = false; - const response = await convergence - .rfqs() - .findResponseByAddress({ address: responseAddress }); - const rfq = await convergence - .rfqs() - .findRfqByAddress({ address: response.rfq }); - - const callerIsTaker = caller.toBase58() === rfq.taker.toBase58(); - const callerSide = callerIsTaker ? 'taker' : 'maker'; - const { legs } = await convergence.rfqs().getSettlementResult({ - response, - rfq, - }); - for (const [index, leg] of rfq.legs.entries()) { - if (leg instanceof PsyoptionsEuropeanInstrument) { - const { receiver } = legs[index]; - if (receiver !== callerSide) { - flag = true; - const euroMeta = await leg.getOptionMeta(); - const { optionType } = leg; - await getOrCreateATA( - convergence, - optionType === psyoptionsEuropean.OptionType.PUT - ? euroMeta.putOptionMint - : euroMeta.callOptionMint, - caller - ); - } - } - } - if (flag === true) { - return ATAExistence.EXISTS; - } - return ATAExistence.NOTEXISTS; + return { + ataTxBuilders: ataTxBuilderArray, + mintTxBuilders: mintTxBuilderArray, + }; }; diff --git a/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/instrument.ts b/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/instrument.ts index 18ac0a928..5f04d8148 100644 --- a/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/instrument.ts +++ b/packages/js/src/plugins/psyoptionsEuropeanInstrumentModule/instrument.ts @@ -10,14 +10,32 @@ import { bignum, } from '@convergence-rfq/beet'; import { publicKey } from '@convergence-rfq/beet-solana'; - +import BN from 'bn.js'; +import { Program } from '@project-serum/anchor'; +import * as psyoptionsEuropean from '@mithraic-labs/tokenized-euros'; +import * as anchor from '@project-serum/anchor'; import { Mint } from '../tokenModule'; -import { LegInstrument } from '../instrumentModule'; +import { + CreateOptionInstrumentsResult, + LegInstrument, +} from '../instrumentModule'; import { addDecimals, removeDecimals } from '../../utils/conversions'; import { assert } from '../../utils/assert'; import { Convergence } from '../../Convergence'; import { createSerializerFromFixableBeetArgsStruct } from '../../types'; import { LegSide, fromSolitaLegSide } from '../rfqModule/models/LegSide'; +import { CvgWallet } from '@/utils'; +export const createEuropeanProgram = async (convergence: Convergence) => { + const cvgWallet = new CvgWallet(convergence); + return psyoptionsEuropean.createProgramFromProvider( + new anchor.AnchorProvider( + convergence.connection, + cvgWallet, + anchor.AnchorProvider.defaultOptions() + ), + new PublicKey(psyoptionsEuropean.programId) + ); +}; type PsyoptionsEuropeanInstrumentData = { optionType: OptionType; @@ -89,28 +107,63 @@ export class PsyoptionsEuropeanInstrument implements LegInstrument { private readonly underlyingAmountPerContractDecimals: number, readonly strikePrice: number, // without decimals private readonly strikePriceDecimals: number, - readonly expiration: number, // timestamp in seconds + readonly expirationTimestamp: number, // timestamp in seconds readonly optionMint: PublicKey, readonly optionMetaPubKey: PublicKey, readonly baseAssetIndex: BaseAssetIndex, readonly amount: number, readonly side: LegSide, - private optionMeta?: EuroMeta + readonly underlyingAssetMint?: PublicKey, + readonly stableAssetMint?: PublicKey, + readonly oracleAddress?: PublicKey, + readonly oracleProviderId?: number ) {} getBaseAssetIndex = () => this.baseAssetIndex; getAmount = () => this.amount; getDecimals = () => PsyoptionsEuropeanInstrument.decimals; getSide = () => this.side; + async getPreparationsBeforeRfqCreation(): Promise { + if (!this.underlyingAssetMint) { + throw new Error('Missing underlying asset mint'); + } + if (!this.stableAssetMint) { + throw new Error('Missing stable asset mint'); + } + if (!this.oracleAddress) { + throw new Error('Missing oracle address'); + } + if (this.oracleProviderId === undefined) { + throw new Error('Missing oracle provider id'); + } + const optionMarketIxs = await getPsyEuropeanMarketIxs( + this.convergence, + this.underlyingAssetMint, + this.underlyingAmountPerContractDecimals, + this.underlyingAmountPerContract, + this.stableAssetMint, + this.strikePriceDecimals, + this.strikePrice, + this.expirationTimestamp, + this.oracleAddress, + this.oracleProviderId + ); + + return optionMarketIxs; + } static async create( convergence: Convergence, underlyingMint: Mint, + stableMint: Mint, optionType: OptionType, - meta: EuroMeta, - metaKey: PublicKey, amount: number, - side: LegSide + side: LegSide, + strike: number, + underlyingAmountPerContract: number, + oracleAddress: PublicKey, + oracleProviderId: number, + expirationTimestamp: number ) { const mintInfoAddress = convergence .rfqs() @@ -124,45 +177,58 @@ export class PsyoptionsEuropeanInstrument implements LegInstrument { throw Error('Stablecoin mint cannot be used in a leg!'); } + const europeanProgram = await createEuropeanProgram(convergence); + const { metaKey, optionMint } = await getEuropeanOptionKeys( + europeanProgram, + underlyingMint, + stableMint, + expirationTimestamp, + strike, + underlyingAmountPerContract, + optionType + ); + return new PsyoptionsEuropeanInstrument( convergence, optionType, - removeDecimals(meta.underlyingAmountPerContract, meta.underlyingDecimals), - meta.underlyingDecimals, - removeDecimals(meta.strikePrice, meta.priceDecimals), - meta.priceDecimals, - Number(meta.expiration), - optionType == OptionType.CALL ? meta.callOptionMint : meta.putOptionMint, + underlyingAmountPerContract, + underlyingMint.decimals, + strike, + stableMint.decimals, + expirationTimestamp, + optionMint, metaKey, mintInfo.mintType.baseAssetIndex, amount, side, - meta + underlyingMint.address, + stableMint.address, + oracleAddress, + oracleProviderId ); } async getOptionMeta() { - if (this.optionMeta === undefined) { - this.optionMeta = await PsyoptionsEuropeanInstrument.fetchMeta( - this.convergence, - this.optionMetaPubKey - ); - } + const optionMeta = await PsyoptionsEuropeanInstrument.fetchMeta( + this.convergence, + this.optionMetaPubKey + ); - return this.optionMeta; + return optionMeta; } /** Helper method to get validation accounts for a Psyoptions European instrument. */ - async getValidationAccounts() { - const optionMeta = await this.getOptionMeta(); - + getValidationAccounts() { + if (!this.underlyingAssetMint) { + throw new Error('Missing underlying asset mint'); + } return [ { pubkey: this.optionMetaPubKey, isSigner: false, isWritable: false }, { pubkey: this.convergence .rfqs() .pdas() - .mintInfo({ mint: optionMeta.underlyingMint }), + .mintInfo({ mint: this.underlyingAssetMint }), isSigner: false, isWritable: false, }, @@ -197,7 +263,7 @@ export class PsyoptionsEuropeanInstrument implements LegInstrument { this.underlyingAmountPerContractDecimals, strikePrice: addDecimals(this.strikePrice, this.strikePriceDecimals), strikePriceDecimals: this.strikePriceDecimals, - expiration: this.expiration, + expiration: this.expirationTimestamp, optionMint: this.optionMint, metaKey: this.optionMetaPubKey, }; @@ -253,3 +319,105 @@ export const psyoptionsEuropeanInstrumentParser = { ); }, }; + +export const getPsyEuropeanMarketIxs = async ( + cvg: Convergence, + underlyingMint: PublicKey, + underlyingMintDecimals: number, + underlyingAmountPerContract: number, + stableMint: PublicKey, + stableMintDecimals: number, + strike: number, + expirationTimeStamp: number, + oracleAddress: PublicKey, + oracleProviderId: number // Switchboard = 1, Pyth = 0 +): Promise => { + const europeanProgram = await createEuropeanProgram(cvg); + const expirationTimestamp = new BN(expirationTimeStamp); + const quoteAmountPerContractBN = new BN( + addDecimals(strike, stableMintDecimals) + ); + const underlyingAmountPerContractBN = new BN( + addDecimals(underlyingAmountPerContract, underlyingMintDecimals) + ); + + // Retrieve the euro meta account and a creation instruction (may or may not be required) + const { instruction: createIx, euroMetaKey } = + await psyoptionsEuropean.instructions.createEuroMetaInstruction( + europeanProgram, + underlyingMint, + underlyingMintDecimals, + stableMint, + stableMintDecimals, + expirationTimestamp, + underlyingAmountPerContractBN, + quoteAmountPerContractBN, + stableMintDecimals, + oracleAddress, + oracleProviderId + ); + + const euroMetaKeyAccount = await cvg.rpc().getAccount(euroMetaKey); + if (euroMetaKeyAccount.exists) { + return []; + } + + // Initialize all accounts for European program + const { instructions: initializeIxs } = + await psyoptionsEuropean.instructions.initializeAllAccountsInstructions( + europeanProgram, + underlyingMint, + stableMint, + oracleAddress, + expirationTimestamp, + stableMintDecimals, + oracleProviderId + ); + + return [...initializeIxs, createIx]; +}; + +export type GetEuropeanOptionMetaResult = { + optionMint: PublicKey; + metaKey: PublicKey; +}; + +export const getEuropeanOptionKeys = async ( + europeanProgram: Program, + underlyingMint: Mint, + stableMint: Mint, + expirationTimestamp: number, + strike: number, + underlyingAmountPerContract: number, + optionType: OptionType +): Promise => { + const quoteAmountPerContractBN = new BN( + addDecimals(strike, stableMint.decimals) + ); + const underlyingAmountPerContractBN = new BN( + addDecimals(underlyingAmountPerContract, underlyingMint.decimals) + ); + + const [metaKey] = await psyoptionsEuropean.pdas.deriveEuroMeta( + europeanProgram, + underlyingMint.address, + stableMint.address, + new BN(expirationTimestamp), + underlyingAmountPerContractBN, + quoteAmountPerContractBN, + stableMint.decimals + ); + + if (optionType == OptionType.CALL) { + const [optionMint] = await psyoptionsEuropean.pdas.deriveCallOptionMint( + europeanProgram, + metaKey + ); + return { optionMint, metaKey }; + } + const [optionMint] = await psyoptionsEuropean.pdas.derivePutOptionMint( + europeanProgram, + metaKey + ); + return { optionMint, metaKey }; +}; diff --git a/packages/js/src/plugins/rfqModule/RfqClient.ts b/packages/js/src/plugins/rfqModule/RfqClient.ts index 1205569cd..d5e6385d5 100644 --- a/packages/js/src/plugins/rfqModule/RfqClient.ts +++ b/packages/js/src/plugins/rfqModule/RfqClient.ts @@ -79,8 +79,16 @@ import { GetSettlementResultInput, getSettlementResultOperation, getSettlementResultHandler, + GetResponseStateAndActionInput, + getResponseStateAndActionOperation, + getResponseStateAndActionHandler, } from './operations'; import { Response } from './models/Response'; +import { + GetRfqStateAndActionInput, + getRfqStateAndActionHandler, + getRfqStateAndActionOperation, +} from './operations/getRfqStateAndAction'; /** * This is a client for the Rfq module. @@ -488,4 +496,20 @@ export class RfqClient { this.convergence ); } + + /** {@inheritDoc getResponseStateAndAction} */ + getResponseStateAndAction(input: GetResponseStateAndActionInput) { + return getResponseStateAndActionHandler.handle( + getResponseStateAndActionOperation(input), + this.convergence + ); + } + + /** {@inheritDoc getRFqStateAndAction} */ + getRfqStateAndAction(input: GetRfqStateAndActionInput) { + return getRfqStateAndActionHandler.handle( + getRfqStateAndActionOperation(input), + this.convergence + ); + } } diff --git a/packages/js/src/plugins/rfqModule/helpers.ts b/packages/js/src/plugins/rfqModule/helpers.ts index 885b98ec0..0fa5c3608 100644 --- a/packages/js/src/plugins/rfqModule/helpers.ts +++ b/packages/js/src/plugins/rfqModule/helpers.ts @@ -1,6 +1,6 @@ import { AccountMeta } from '@solana/web3.js'; import { Sha256 } from '@aws-crypto/sha256-js'; -import { Leg } from '@convergence-rfq/rfq'; +import { ApiLeg } from '@convergence-rfq/rfq'; import { Confirmation, Quote, @@ -17,8 +17,8 @@ import { serializeAsLeg, toLeg, } from '../instrumentModule'; -import { Rfq, Response, isFixedSizeOpen } from './models'; import { LEG_MULTIPLIER_DECIMALS } from './constants'; +import { Rfq, Response, isFixedSizeOpen } from './models'; export function getPages( accounts: T[], @@ -84,21 +84,21 @@ export const calculateExpectedLegsSize = ( // TODO remove export const instrumentsToLegsAndLegsSize = ( instruments: LegInstrument[] -): [Leg[], number] => { +): [ApiLeg[], number] => { return [ instrumentsToLegs(instruments), calculateExpectedLegsSize(instruments), ]; }; -export const instrumentsToLegs = (instruments: LegInstrument[]): Leg[] => { +export const instrumentsToLegs = (instruments: LegInstrument[]): ApiLeg[] => { return instruments.map((i) => toLeg(i)); }; // TODO remove export const instrumentsToLegsAndExpectedLegsHash = ( instruments: LegInstrument[] -): [Leg[], Uint8Array] => { +): [ApiLeg[], Uint8Array] => { return [ instrumentsToLegs(instruments), calculateExpectedLegsHash(instruments), @@ -107,7 +107,7 @@ export const instrumentsToLegsAndExpectedLegsHash = ( export const legsToBaseAssetAccounts = ( convergence: Convergence, - legs: Leg[] + legs: ApiLeg[] ): AccountMeta[] => { const baseAssetAccounts: AccountMeta[] = []; diff --git a/packages/js/src/plugins/rfqModule/models/Response.ts b/packages/js/src/plugins/rfqModule/models/Response.ts index 11f9114fb..463e802f8 100644 --- a/packages/js/src/plugins/rfqModule/models/Response.ts +++ b/packages/js/src/plugins/rfqModule/models/Response.ts @@ -2,7 +2,10 @@ import { PublicKey } from '@solana/web3.js'; import { DefaultingParty as SolitaDefaultingParty } from '@convergence-rfq/rfq'; import { ResponseAccount } from '../accounts'; -import { removeDecimals } from '../../../utils/conversions'; +import { + convertTimestampToMilliSeconds, + removeDecimals, +} from '../../../utils/conversions'; import { assert } from '../../../utils/assert'; import { Confirmation, fromSolitaConfirmation } from './Confirmation'; import { AuthoritySide, fromSolitaAuthoritySide } from './AuthoritySide'; @@ -34,6 +37,9 @@ export type Response = { /** The timestamp at which this response was created. */ readonly creationTimestamp: number; + /** The timestamp at which this response will expire. */ + readonly expirationTimestamp: number; + /** The bid required for sell and optionally two-way order types. */ readonly bid: Quote | null; @@ -89,7 +95,12 @@ export const toResponse = ( address: account.publicKey, maker: account.data.maker, rfq: account.data.rfq, - creationTimestamp: Number(account.data.creationTimestamp) * 1_000, + creationTimestamp: convertTimestampToMilliSeconds( + account.data.creationTimestamp + ), + expirationTimestamp: convertTimestampToMilliSeconds( + account.data.expirationTimestamp + ), makerCollateralLocked: removeDecimals( account.data.makerCollateralLocked, collateralDecimals diff --git a/packages/js/src/plugins/rfqModule/models/ResponseSide.ts b/packages/js/src/plugins/rfqModule/models/ResponseSide.ts index 670953c16..40f7c99e2 100644 --- a/packages/js/src/plugins/rfqModule/models/ResponseSide.ts +++ b/packages/js/src/plugins/rfqModule/models/ResponseSide.ts @@ -28,3 +28,10 @@ export function toSolitaQuoteSide(responseSide: ResponseSide): SolitaQuoteSide { } } } + +export const inverseResponseSide = (side: ResponseSide): ResponseSide => { + if (side === Bid) { + return Ask; + } + return Bid; +}; diff --git a/packages/js/src/plugins/rfqModule/models/Rfq.ts b/packages/js/src/plugins/rfqModule/models/Rfq.ts index d53e04e06..1db4e9c47 100644 --- a/packages/js/src/plugins/rfqModule/models/Rfq.ts +++ b/packages/js/src/plugins/rfqModule/models/Rfq.ts @@ -1,7 +1,10 @@ import { PublicKey } from '@solana/web3.js'; import { RfqAccount } from '../accounts'; import { assert } from '../../../utils/assert'; -import { convertTimestamp, removeDecimals } from '../../../utils/conversions'; +import { + convertTimestampToMilliSeconds, + removeDecimals, +} from '../../../utils/conversions'; import { SpotLegInstrument, SpotQuoteInstrument, @@ -114,7 +117,9 @@ export const toRfq = async ( quoteMint: SpotLegInstrument.deserializeInstrumentData( Buffer.from(account.data.quoteAsset.instrumentData) ).mintAddress, - creationTimestamp: convertTimestamp(account.data.creationTimestamp), + creationTimestamp: convertTimestampToMilliSeconds( + account.data.creationTimestamp + ), activeWindow: account.data.activeWindow, settlingWindow: account.data.settlingWindow, expectedLegsSize: account.data.expectedLegsSize, diff --git a/packages/js/src/plugins/rfqModule/operations/addLegsToRfq.ts b/packages/js/src/plugins/rfqModule/operations/addLegsToRfq.ts index d2ce3338c..78bf2d36e 100644 --- a/packages/js/src/plugins/rfqModule/operations/addLegsToRfq.ts +++ b/packages/js/src/plugins/rfqModule/operations/addLegsToRfq.ts @@ -138,7 +138,6 @@ export const addLegsToRfqBuilder = async ( const baseAssetAccounts: AccountMeta[] = []; const baseAssetIndexValues = []; - for (const leg of legs) { baseAssetIndexValues.push(leg.baseAssetIndex.value); } diff --git a/packages/js/src/plugins/rfqModule/operations/confirmResponse.ts b/packages/js/src/plugins/rfqModule/operations/confirmResponse.ts index 81bb25098..e0ec2d5db 100644 --- a/packages/js/src/plugins/rfqModule/operations/confirmResponse.ts +++ b/packages/js/src/plugins/rfqModule/operations/confirmResponse.ts @@ -14,6 +14,7 @@ import { TransactionBuilder, TransactionBuilderOptions, } from '../../../utils/TransactionBuilder'; +import { Response } from '../models'; import { ResponseSide, toSolitaQuoteSide } from '../models/ResponseSide'; import { toSolitaOverrideLegMultiplierBps } from '../models/Confirmation'; @@ -181,13 +182,19 @@ export const confirmResponseBuilder = async ( }), } = params; + const responseModel = await convergence + .rfqs() + .findResponseByAddress({ address: response }); + + if (isResponseExpired(responseModel)) { + throw new Error('Response is expired'); + } + const { overrideLegMultiplier = null } = params; const overrideLegMultiplierBps = overrideLegMultiplier && toSolitaOverrideLegMultiplierBps(overrideLegMultiplier); - const responseModel = await convergence - .rfqs() - .findResponseByAddress({ address: response }); + const makerCollateralInfo = convergence.collateral().pdas().collateralInfo({ user: responseModel.maker, programs, @@ -264,3 +271,7 @@ export const confirmResponseBuilder = async ( } ); }; + +const isResponseExpired = (response: Response): boolean => { + return Date.now() > response.expirationTimestamp; +}; diff --git a/packages/js/src/plugins/rfqModule/operations/createAndFinalizeRfqConstruction.ts b/packages/js/src/plugins/rfqModule/operations/createAndFinalizeRfqConstruction.ts index 166622517..80010a988 100644 --- a/packages/js/src/plugins/rfqModule/operations/createAndFinalizeRfqConstruction.ts +++ b/packages/js/src/plugins/rfqModule/operations/createAndFinalizeRfqConstruction.ts @@ -27,6 +27,8 @@ import { import { OrderType } from '../models/OrderType'; import { createRfqBuilder } from './createRfq'; import { finalizeRfqConstructionBuilder } from './finalizeRfqConstruction'; +import { addLegsToRfqBuilder } from './addLegsToRfq'; +import { InstructionUniquenessTracker } from '@/utils/classes'; const Key = 'CreateAndFinalizeRfqConstructionOperation' as const; @@ -156,16 +158,33 @@ export const createAndFinalizeRfqConstructionOperationHandler: OperationHandler< ): Promise => { const { taker = convergence.identity(), - instruments, orderType, + instruments, fixedSize, quoteAsset, activeWindow = 5_000, settlingWindow = 1_000, } = operation.input; - + const payer = convergence.rpc().getDefaultFeePayer(); const recentTimestamp = new BN(Math.floor(Date.now() / 1_000)); - + const rfqPreparationTxBuilderArray: TransactionBuilder[] = []; + const ixTracker = new InstructionUniquenessTracker([]); + for (const ins of instruments) { + const rfqPreparationIxs = await ins.getPreparationsBeforeRfqCreation(); + if (rfqPreparationIxs.length === 0) continue; + const rfqPreparationTxBuilder = + TransactionBuilder.make().setFeePayer(payer); + rfqPreparationIxs.forEach((ix) => { + if (ixTracker.checkedAdd(ix)) { + rfqPreparationTxBuilder.add({ + instruction: ix, + signers: [convergence.identity()], + }); + } + }); + if (rfqPreparationTxBuilder.getInstructionCount() > 0) + rfqPreparationTxBuilderArray.push(rfqPreparationTxBuilder); + } const expectedLegsHash = calculateExpectedLegsHash(instruments); const rfqPda = convergence @@ -182,14 +201,17 @@ export const createAndFinalizeRfqConstructionOperationHandler: OperationHandler< recentTimestamp, }); - const builder = await createAndFinalizeRfqConstructionBuilder( + const { + createRfqTxBuilder, + addLegsTxBuilderArray, + finalizeConstructionTxBuilder, + } = await createAndFinalizeRfqConstructionBuilder( convergence, { ...operation.input, taker, rfq: rfqPda, fixedSize, - instruments, expectedLegsHash, recentTimestamp, }, @@ -201,7 +223,69 @@ export const createAndFinalizeRfqConstructionOperationHandler: OperationHandler< convergence, scope.confirmOptions ); - const output = await builder.sendAndConfirm(convergence, confirmOptions); + const lastValidBlockHeight = await convergence.rpc().getLatestBlockhash(); + const rfqPreparationTxs = rfqPreparationTxBuilderArray.map((b) => + b.toTransaction(lastValidBlockHeight) + ); + + const createRfqTx = + createRfqTxBuilder.toTransaction(lastValidBlockHeight); + + const addLegsTxs = addLegsTxBuilderArray.map((b) => + b.toTransaction(lastValidBlockHeight) + ); + + const finalizeRfqTxs = + finalizeConstructionTxBuilder.toTransaction(lastValidBlockHeight); + const [ + rfqPreparationSignedTxs, + [createRfqSignedTx], + addLegsSignedTxs, + [finalizeRfqSignedTx], + ] = await convergence + .identity() + .signTransactionMatrix(rfqPreparationTxs, [createRfqTx], addLegsTxs, [ + finalizeRfqTxs, + ]); + + for (const signedTx of rfqPreparationSignedTxs) { + await convergence + .rpc() + .serializeAndSendTransaction( + signedTx, + lastValidBlockHeight, + confirmOptions + ); + } + + await convergence + .rpc() + .serializeAndSendTransaction( + createRfqSignedTx, + lastValidBlockHeight, + confirmOptions + ); + + await Promise.all( + addLegsSignedTxs.map((signedTx) => + convergence + .rpc() + .serializeAndSendTransaction( + signedTx, + lastValidBlockHeight, + confirmOptions + ) + ) + ); + + const response = await convergence + .rpc() + .serializeAndSendTransaction( + finalizeRfqSignedTx, + lastValidBlockHeight, + confirmOptions + ); + scope.throwIfCanceled(); const rfq = await convergence @@ -209,7 +293,7 @@ export const createAndFinalizeRfqConstructionOperationHandler: OperationHandler< .findRfqByAddress({ address: rfqPda }); assertRfq(rfq); - return { ...output, rfq }; + return { response, rfq }; }, }; @@ -224,32 +308,58 @@ export type CreateAndFinalizeRfqConstructionBuilderParams = rfq: PublicKey; }; +export type CreateAndFinalizeRfqConstructionBuilderResult = { + createRfqTxBuilder: TransactionBuilder; + addLegsTxBuilderArray: TransactionBuilder[]; + finalizeConstructionTxBuilder: TransactionBuilder; +}; + export const createAndFinalizeRfqConstructionBuilder = async ( convergence: Convergence, params: CreateAndFinalizeRfqConstructionBuilderParams, options: TransactionBuilderOptions = {} -): Promise => { - const { payer = convergence.rpc().getDefaultFeePayer() } = options; - const { rfq } = params; +): Promise => { + const { rfq, instruments } = params; - const rfqBuilder = await createRfqBuilder( + const { createRfqTxBuilder, remainingLegsToAdd } = await createRfqBuilder( convergence, { ...params }, options ); - const finalizeConstructionBuilder = await finalizeRfqConstructionBuilder( + + const addLegsTxBuilderArray: TransactionBuilder[] = []; + if (remainingLegsToAdd.length > 0) { + let legsToAddCount = remainingLegsToAdd.length; + let legsAddedCount = 0; + while (legsAddedCount !== remainingLegsToAdd.length) { + const addLegsTxBuilder = await addLegsToRfqBuilder( + convergence, + { + rfq, + taker: params.taker, + instruments: remainingLegsToAdd.slice(legsAddedCount, legsToAddCount), + }, + options + ); + if (addLegsTxBuilder.checkTransactionFits()) { + legsAddedCount = legsToAddCount; + legsToAddCount = remainingLegsToAdd.length; + addLegsTxBuilderArray.push(addLegsTxBuilder); + } else { + legsToAddCount--; + } + } + } + + const finalizeConstructionTxBuilder = await finalizeRfqConstructionBuilder( convergence, - { ...params, legs: params.instruments }, + { ...params, legs: instruments }, options ); - return TransactionBuilder.make() - .setContext({ - rfq, - }) - .setFeePayer(payer) - .add( - ...rfqBuilder.getInstructionsWithSigners(), - ...finalizeConstructionBuilder.getInstructionsWithSigners() - ); + return { + createRfqTxBuilder, + addLegsTxBuilderArray, + finalizeConstructionTxBuilder, + }; }; diff --git a/packages/js/src/plugins/rfqModule/operations/createRfq.ts b/packages/js/src/plugins/rfqModule/operations/createRfq.ts index 0f25ea236..77d97f576 100644 --- a/packages/js/src/plugins/rfqModule/operations/createRfq.ts +++ b/packages/js/src/plugins/rfqModule/operations/createRfq.ts @@ -7,9 +7,10 @@ import { SendAndConfirmTransactionResponse } from '../../rpcModule'; import { assertRfq, FixedSize, Rfq, toSolitaFixedSize } from '../models'; import { calculateExpectedLegsHash, - instrumentsToLegsAndLegsSize, instrumentsToLegAccounts, legsToBaseAssetAccounts, + instrumentsToLegs, + calculateExpectedLegsSize, } from '../helpers'; import { TransactionBuilder, @@ -26,10 +27,12 @@ import { import { Convergence } from '../../../Convergence'; import { LegInstrument, + // LegInstrumentInputData, QuoteInstrument, toQuote, } from '../../../plugins/instrumentModule'; import { OrderType, toSolitaOrderType } from '../models/OrderType'; +import { InstructionUniquenessTracker } from '@/utils/classes'; const Key = 'CreateRfqOperation' as const; @@ -157,9 +160,26 @@ export const createRfqOperationHandler: OperationHandler = { settlingWindow = 1_000, } = operation.input; let { expectedLegsHash } = operation.input; - + const payer = convergence.rpc().getDefaultFeePayer(); const recentTimestamp = new BN(Math.floor(Date.now() / 1_000)); - + const rfqPreparationTxBuilderArray: TransactionBuilder[] = []; + const ixTracker = new InstructionUniquenessTracker([]); + for (const ins of instruments) { + const rfqPreparationIxs = await ins.getPreparationsBeforeRfqCreation(); + if (rfqPreparationIxs.length === 0) continue; + const rfqPreparationTxBuilder = + TransactionBuilder.make().setFeePayer(payer); + rfqPreparationIxs.forEach((ix) => { + if (ixTracker.checkedAdd(ix)) { + rfqPreparationTxBuilder.add({ + instruction: ix, + signers: [convergence.identity()], + }); + } + }); + if (rfqPreparationTxBuilder.getInstructionCount() > 0) + rfqPreparationTxBuilderArray.push(rfqPreparationTxBuilder); + } expectedLegsHash = expectedLegsHash ?? calculateExpectedLegsHash(instruments); @@ -177,13 +197,13 @@ export const createRfqOperationHandler: OperationHandler = { recentTimestamp, }); - const builder = await createRfqBuilder( + const { createRfqTxBuilder } = await createRfqBuilder( convergence, { ...operation.input, + instruments, rfq: rfqPda, fixedSize, - instruments, activeWindow, settlingWindow, expectedLegsHash, @@ -198,13 +218,41 @@ export const createRfqOperationHandler: OperationHandler = { scope.confirmOptions ); - const output = await builder.sendAndConfirm(convergence, confirmOptions); + const lastValidBlockHeight = await convergence.rpc().getLatestBlockhash(); + + const rfqPreparationTxs = rfqPreparationTxBuilderArray.map((b) => + b.toTransaction(lastValidBlockHeight) + ); + + const createRfqTx = createRfqTxBuilder.toTransaction(lastValidBlockHeight); + + const [rfqPreparationSignedTxs, [createRfqSignedTx]] = await convergence + .identity() + .signTransactionMatrix(rfqPreparationTxs, [createRfqTx]); + for (const signedTx of rfqPreparationSignedTxs) { + await convergence + .rpc() + .serializeAndSendTransaction( + signedTx, + lastValidBlockHeight, + confirmOptions + ); + } + + const response = await convergence + .rpc() + .serializeAndSendTransaction( + createRfqSignedTx, + lastValidBlockHeight, + confirmOptions + ); + scope.throwIfCanceled(); const rfq = await convergence.rfqs().findRfqByAddress({ address: rfqPda }); assertRfq(rfq); - return { ...output, rfq }; + return { response, rfq }; }, }; @@ -233,17 +281,22 @@ export type CreateRfqBuilderParams = CreateRfqInput & { * @group Transaction Builders * @category Constructors */ + +export type CreateRfqBuilderResult = { + createRfqTxBuilder: TransactionBuilder; + remainingLegsToAdd: LegInstrument[]; +}; + export const createRfqBuilder = async ( convergence: Convergence, params: CreateRfqBuilderParams, options: TransactionBuilderOptions = {} -): Promise => { +): Promise => { const { programs, payer = convergence.rpc().getDefaultFeePayer() } = options; const { taker = convergence.identity(), quoteAsset, - instruments, rfq, orderType, fixedSize, @@ -251,12 +304,13 @@ export const createRfqBuilder = async ( settlingWindow = 1_000, recentTimestamp, expectedLegsHash, + instruments, } = params; let { expectedLegsSize } = params; - const [legs, expectedLegsSizeValue] = await instrumentsToLegsAndLegsSize( - instruments - ); + const legs = instrumentsToLegs(instruments); + + const expectedLegsSizeValue = calculateExpectedLegsSize(instruments); expectedLegsSize = expectedLegsSize ?? expectedLegsSizeValue; const systemProgram = convergence.programs().getSystem(programs); @@ -281,10 +335,10 @@ export const createRfqBuilder = async ( }, ]; - const baseAssetAccounts = legsToBaseAssetAccounts(convergence, legs); - const legAccounts = await instrumentsToLegAccounts(instruments); + let baseAssetAccounts = legsToBaseAssetAccounts(convergence, legs); + let legAccounts = await instrumentsToLegAccounts(instruments); - return TransactionBuilder.make() + let rfqBuilder = TransactionBuilder.make() .setFeePayer(payer) .setContext({ rfq, @@ -318,4 +372,55 @@ export const createRfqBuilder = async ( signers: [taker], key: 'createRfq', }); + + let legsToAdd = [...legs]; + let instrumentsToAdd = [...instruments]; + + while (!rfqBuilder.checkTransactionFits()) { + instrumentsToAdd = instrumentsToAdd.slice(0, instrumentsToAdd.length - 1); + legsToAdd = legsToAdd.slice(0, instrumentsToAdd.length); + legAccounts = await instrumentsToLegAccounts(instrumentsToAdd); + baseAssetAccounts = legsToBaseAssetAccounts(convergence, legsToAdd); + rfqBuilder = TransactionBuilder.make() + .setFeePayer(payer) + .setContext({ + rfq, + }) + .add({ + instruction: createCreateRfqInstruction( + { + taker: taker.publicKey, + protocol: convergence.protocol().pdas().protocol(), + rfq, + systemProgram: systemProgram.address, + anchorRemainingAccounts: [ + ...quoteAccounts, + ...baseAssetAccounts, + ...legAccounts, + ], + }, + { + expectedLegsSize, + expectedLegsHash: Array.from(expectedLegsHash), + legs: legsToAdd, + orderType: toSolitaOrderType(orderType), + quoteAsset: toQuote(quoteAsset), + fixedSize: toSolitaFixedSize(fixedSize, quoteAsset.getDecimals()), + activeWindow, + settlingWindow, + recentTimestamp, + }, + rfqProgram.address + ), + signers: [taker], + key: 'createRfq', + }); + } + + const remainingLegsToAdd = instruments.slice(legsToAdd.length, legs.length); + + return { + createRfqTxBuilder: rfqBuilder, + remainingLegsToAdd, + }; }; diff --git a/packages/js/src/plugins/rfqModule/operations/getResponseStateAndAction.ts b/packages/js/src/plugins/rfqModule/operations/getResponseStateAndAction.ts new file mode 100644 index 000000000..def15307c --- /dev/null +++ b/packages/js/src/plugins/rfqModule/operations/getResponseStateAndAction.ts @@ -0,0 +1,278 @@ +import { DefaultingParty } from '@convergence-rfq/rfq'; +import { + Rfq, + Response, + ResponseSide, + AuthoritySide, + inverseResponseSide, +} from '../models'; +import { Operation, SyncOperationHandler, useOperation } from '../../../types'; +import { ResponseTimers, RfqTimers } from '@/utils/classes'; +const Key = 'GetResponseStateAndAction' as const; +export type ResponseState = + | 'Active' + | 'Cancelled' + | 'Expired' + | 'MakerDefaulted' + | 'TakerDefaulted' + | 'BothDefaulted' + | 'Settled' + | 'SettlingPreparations' + | 'ReadyForSettling' + | 'WaitingForLastLook' + | 'OnlyMakerPrepared' + | 'OnlyTakerPrepared' + | 'Rejected'; + +export type ResponseAction = + | 'Cancel' + | 'UnlockCollateral' + | 'Cleanup' + | 'Settle' + | 'Approve' + | 'Settle One Party Defaulted' + | 'Settle Both Party Defaulted' + | null; + +/** + * getResponseStateAndAction. + * + * ```ts + * const result = await convergence.rfqs().getResponseStateAndAction({ + * response, + * rfq, + * caller: 'taker'| 'maker, + * responseSide: 'ask' | 'bid' + * }) + * ``` + * + * @group Operations + * @category Constructors + */ +export const getResponseStateAndActionOperation = + useOperation(Key); + +/** + * @group Operations + * @category Types + */ +export type GetResponseStateAndAction = Operation< + typeof Key, + GetResponseStateAndActionInput, + GetResponseStateAndActionOutput +>; + +/** + * @group Operations + * @category Inputs + */ +export type GetResponseStateAndActionInput = { + response: Response; + rfq: Rfq; + caller: 'taker' | 'maker'; + responseSide: 'ask' | 'bid'; +}; + +/** + * @group Operations + * @category Outputs + */ +export type GetResponseStateAndActionOutput = { + responseState: ResponseState; + responseAction: ResponseAction; +}; + +/** + * @group Operations + * @category Handlers + */ +export const getResponseStateAndActionHandler: SyncOperationHandler = + { + handle: ( + operation: GetResponseStateAndAction + ): GetResponseStateAndActionOutput => { + const { response, caller, rfq, responseSide } = operation.input; + if (!response.rfq.equals(rfq.address)) { + throw new Error('Response does not match RFQ'); + } + const responseTimers = new ResponseTimers(response); + const rfqTimers = new RfqTimers(rfq); + const responseState = getResponseState( + response, + rfq, + rfqTimers, + responseTimers, + responseSide + ); + const responseAction = getResponseAction(response, responseState, caller); + return { responseState, responseAction }; + }, + }; + +const getResponseState = ( + response: Response, + rfq: Rfq, + rfqTimers: RfqTimers, + responseTimers: ResponseTimers, + responseSide: ResponseSide +): ResponseState => { + const responseExpired = responseTimers.isResponseExpired(); + const settlementWindowElapsed = rfqTimers.isRfqSettlementWindowElapsed(); + const confirmedInverseResponseSide = + response?.confirmed?.side === inverseResponseSide(responseSide); + const responseConfirmed = response?.confirmed !== null; + const makerPrepared = hasMakerPrepared(response, rfq); + const takerPrepared = hasTakerPrepared(response, rfq); + const defaultingParty = getDefautingParty( + response, + settlementWindowElapsed, + makerPrepared, + takerPrepared + ); + + if (responseConfirmed && confirmedInverseResponseSide) return 'Rejected'; + switch (response.state) { + case 'active': + if (!responseExpired) return 'Active'; + return 'Expired'; + case 'canceled': + return 'Cancelled'; + case 'waiting-for-last-look': + if (!responseExpired) return 'WaitingForLastLook'; + return 'Expired'; + case 'settling-preparations': + if (!settlementWindowElapsed) { + if (makerPrepared) return 'OnlyMakerPrepared'; + if (takerPrepared) return 'OnlyTakerPrepared'; + return 'SettlingPreparations'; + } + switch (defaultingParty) { + case DefaultingParty.Maker: + return 'MakerDefaulted'; + case DefaultingParty.Taker: + return 'TakerDefaulted'; + case DefaultingParty.Both: + return 'BothDefaulted'; + } + case 'ready-for-settling': + return 'ReadyForSettling'; + case 'settled': + return 'Settled'; + case 'defaulted': + switch (defaultingParty) { + case DefaultingParty.Maker: + return 'MakerDefaulted'; + case DefaultingParty.Taker: + return 'TakerDefaulted'; + case DefaultingParty.Both: + return 'BothDefaulted'; + } + default: + throw new Error('Invalid Response state'); + } +}; + +const getResponseAction = ( + response: Response, + responseState: ResponseState, + caller: AuthoritySide +): ResponseAction => { + const responseConfirmed = response?.confirmed !== null; + switch (caller) { + case 'maker': + switch (responseState) { + case 'Active': + if (!responseConfirmed) return 'Cancel'; + break; + case 'Expired': + case 'Cancelled': + case 'Settled': + if ( + response.makerCollateralLocked > 0 || + response.takerCollateralLocked > 0 + ) + return 'UnlockCollateral'; + if ( + response.makerCollateralLocked === 0 && + response.takerCollateralLocked === 0 + ) + return 'Cleanup'; + case 'SettlingPreparations': + case 'OnlyMakerPrepared': + case 'OnlyTakerPrepared': + case 'ReadyForSettling': + return 'Settle'; + case 'MakerDefaulted': + case 'TakerDefaulted': + return 'Settle One Party Defaulted'; + case 'BothDefaulted': + return 'Settle Both Party Defaulted'; + case 'Rejected': + return null; + } + break; + + case 'taker': + switch (responseState) { + case 'Active': + if (!responseConfirmed) return 'Approve'; + break; + case 'SettlingPreparations': + case 'OnlyMakerPrepared': + case 'OnlyTakerPrepared': + case 'ReadyForSettling': + return 'Settle'; + case 'MakerDefaulted': + return 'Settle One Party Defaulted'; + case 'TakerDefaulted': + return 'Settle One Party Defaulted'; + case 'BothDefaulted': + return 'Settle Both Party Defaulted'; + case 'Settled': + case 'Expired': + case 'Cancelled': + if ( + response.takerCollateralLocked > 0 || + response.makerCollateralLocked > 0 + ) + return 'UnlockCollateral'; + if ( + response.takerCollateralLocked === 0 && + response.makerCollateralLocked === 0 + ) + return 'Cleanup'; + case 'Rejected': + return null; + } + break; + } + return null; +}; + +const getDefautingParty = ( + response: Response, + settlementWindowElapsed: boolean, + makerPrepared: boolean, + takerPrepared: boolean +): DefaultingParty | null => { + const { defaultingParty } = response; + if (defaultingParty) return defaultingParty; + const defaulted = + settlementWindowElapsed && (!makerPrepared || !takerPrepared); + + if (defaulted && defaultingParty === null) { + if (!makerPrepared && !takerPrepared) return DefaultingParty.Both; + if (!makerPrepared) return DefaultingParty.Maker; + if (!takerPrepared) return DefaultingParty.Taker; + } + + return null; +}; + +const hasMakerPrepared = (response: Response, rfq: Rfq) => { + return response.makerPreparedLegs === rfq.legs.length; +}; + +const hasTakerPrepared = (response: Response, rfq: Rfq) => { + return response.takerPreparedLegs === rfq.legs.length; +}; diff --git a/packages/js/src/plugins/rfqModule/operations/getRfqStateAndAction.ts b/packages/js/src/plugins/rfqModule/operations/getRfqStateAndAction.ts new file mode 100644 index 000000000..154916c03 --- /dev/null +++ b/packages/js/src/plugins/rfqModule/operations/getRfqStateAndAction.ts @@ -0,0 +1,182 @@ +import { Rfq } from '../models'; +import { Operation, SyncOperationHandler, useOperation } from '../../../types'; +const Key = 'GetRfqStateAndAction' as const; + +export type RfqState = + | 'Active' + | 'Cancelled' + | 'Constructed' + | 'Expired' + | 'Settling' + | 'SettlingEnded'; + +export type RfqAction = + | 'Cancel' + | 'UnlockCollateral' + | 'Cleanup' + | 'FinalizeConstruction' + | 'Respond' + | 'ViewResponses' + | null; +/** + * getRfqStateAndAction. + * + * ```ts + * const result = await convergence.rfqs().getRfqStateAndAction({ + * rfq, + * caller: 'maker' | 'taker', + * }) + * ``` + * + * @group Operations + * @category Constructors + */ +export const getRfqStateAndActionOperation = + useOperation(Key); + +/** + * @group Operations + * @category Types + */ +export type GetRfqStateAndAction = Operation< + typeof Key, + GetRfqStateAndActionInput, + GetRfqStateAndActionOutput +>; + +/** + * @group Operations + * @category Inputs + */ +export type GetRfqStateAndActionInput = { + rfq: Rfq; + caller: 'maker' | 'taker'; +}; + +/** + * @group Operations + * @category Outputs + */ +export type GetRfqStateAndActionOutput = { + rfqState: RfqState; + rfqAction: RfqAction; + rfqStateValidUntil: Date | null; +}; + +/** + * @group Operations + * @category Handlers + */ +export const getRfqStateAndActionHandler: SyncOperationHandler = + { + handle: (operation: GetRfqStateAndAction): GetRfqStateAndActionOutput => { + const { rfq, caller } = operation.input; + const timestampStart = new Date(Number(rfq.creationTimestamp)); + const timestampExpiry = new Date( + timestampStart.getTime() + Number(rfq.activeWindow) * 1000 + ); + const timestampSettlement = new Date( + timestampExpiry.getTime() + Number(rfq.settlingWindow) * 1000 + ); + const rfqState = getRfqState(rfq, timestampExpiry, timestampSettlement); + const rfqStateValidUntil = getRfqStateValidity( + rfqState, + rfq, + timestampExpiry, + timestampSettlement + ); + const rfqAction = getRfqAction(rfq, rfqState, caller); + return { rfqState, rfqStateValidUntil, rfqAction }; + }, + }; + +const getRfqStateValidity = ( + rfqState: RfqState, + rfq: Rfq, + timestampExpiry: Date, + timestampSettlement: Date +) => { + if (rfqState === 'Active') { + return timestampExpiry; + } + if (rfqState === 'Settling') { + return timestampSettlement; + } + return null; +}; + +const getRfqState = ( + rfq: Rfq, + timestampExpiry: Date, + timestampSettlement: Date +): RfqState => { + const rfqExpired = timestampExpiry.getTime() <= Date.now(); + const settlementWindowElapsed = timestampSettlement.getTime() <= Date.now(); + switch (rfq.state) { + case 'constructed': + return 'Constructed'; + case 'canceled': + return 'Cancelled'; + case 'active': { + if (!rfqExpired) return 'Active'; + if (rfqExpired) return 'Expired'; + if (!settlementWindowElapsed) return 'Settling'; + if (settlementWindowElapsed) return 'SettlingEnded'; + } + } + throw new Error('Invalid Rfq state'); +}; + +const getRfqAction = ( + rfq: Rfq, + rfqState: RfqState, + caller: 'maker' | 'taker' +): RfqAction => { + const pendingResponses = rfq.totalResponses - rfq.clearedResponses; + + switch (rfqState) { + case 'Active': + if (caller === 'maker') return 'Respond'; + if (caller === 'taker' && pendingResponses > 0) return 'ViewResponses'; + if (caller === 'taker' && pendingResponses === 0) return 'Cancel'; + break; + case 'Constructed': + if (caller === 'maker') return null; + if (caller === 'taker') return 'FinalizeConstruction'; + break; + case 'Expired': + if (caller === 'maker') return null; + if (caller === 'taker' && pendingResponses > 0) return 'ViewResponses'; + if ( + caller === 'taker' && + pendingResponses === 0 && + rfq.totalTakerCollateralLocked > 0 + ) + return 'UnlockCollateral'; + if ( + caller === 'taker' && + pendingResponses === 0 && + rfq.totalTakerCollateralLocked == 0 + ) + return 'Cleanup'; + break; + case 'Cancelled': + if (caller === 'maker') return null; + if ( + caller === 'taker' && + pendingResponses === 0 && + rfq.totalTakerCollateralLocked > 0 + ) + return 'UnlockCollateral'; + if ( + caller === 'taker' && + pendingResponses === 0 && + rfq.totalTakerCollateralLocked == 0 + ) + return 'Cleanup'; + break; + default: + return null; + } + return null; +}; diff --git a/packages/js/src/plugins/rfqModule/operations/index.ts b/packages/js/src/plugins/rfqModule/operations/index.ts index 018dfd5a4..d82acb2ea 100644 --- a/packages/js/src/plugins/rfqModule/operations/index.ts +++ b/packages/js/src/plugins/rfqModule/operations/index.ts @@ -35,3 +35,4 @@ export * from './unlockResponseCollateral'; export * from './unlockResponsesCollateral'; export * from './unlockRfqsCollateral'; export * from './getSettlementResult'; +export * from './getResponseStateAndAction'; diff --git a/packages/js/src/plugins/rfqModule/operations/partiallySettleLegsAndSettle.ts b/packages/js/src/plugins/rfqModule/operations/partiallySettleLegsAndSettle.ts index e0359e486..f46770c71 100644 --- a/packages/js/src/plugins/rfqModule/operations/partiallySettleLegsAndSettle.ts +++ b/packages/js/src/plugins/rfqModule/operations/partiallySettleLegsAndSettle.ts @@ -100,7 +100,6 @@ export const partiallySettleLegsAndSettleOperationHandler: OperationHandler => { const { rfq } = operation.input; - const MAX_TX_SIZE = 1232; const confirmOptions = makeConfirmOptionsFinalizedOnMainnet( convergence, @@ -116,17 +115,13 @@ export const partiallySettleLegsAndSettleOperationHandler: OperationHandler MAX_TX_SIZE) { + while (settleRfqBuilder.checkTransactionFits()) { const index = Math.trunc(slicedIndex / 2); // const startIndex = rfqModel.legs.length - index; const startIndex = rfqModel.legs.length - index + 3; @@ -140,10 +135,6 @@ export const partiallySettleLegsAndSettleOperationHandler: OperationHandler MAX_TX_SIZE - ) { + while (partiallySettleBuilder.checkTransactionFits()) { const halvedLegAmount = Math.trunc( partiallySettleSlicedLegAmount / 2 ); @@ -180,10 +164,6 @@ export const partiallySettleLegsAndSettleOperationHandler: OperationHandler => { - const builder = await prepareSettlementBuilder( + const ixTracker = new InstructionUniquenessTracker([]); + const { + response, + rfq, + caller = convergence.identity(), + } = operation.input; + + const rfqModel = await convergence + .rfqs() + .findRfqByAddress({ address: rfq }); + + const { + ataTxBuilderArray: remainingAtaTxBuilders, + prepareSettlementTxBuilder, + } = await prepareSettlementBuilder( convergence, + rfqModel, { ...operation.input, }, scope ); + let ataTxs: Transaction[] = []; + let mintTxs: Transaction[] = []; + let prepareOptionsResult: PrepareAmericanOptionsResult = { + ataTxBuilders: [], + mintTxBuilders: [], + }; + + if (doesRfqLegContainsPsyoptionsAmerican(rfqModel)) { + prepareOptionsResult = await prepareAmericanOptions( + convergence, + response, + caller?.publicKey + ); + } + if (doesRfqLegContainsPsyoptionsEuropean(rfqModel)) { + prepareOptionsResult = await prepareEuropeanOptions( + convergence, + response, + caller?.publicKey + ); + } + + prepareOptionsResult.ataTxBuilders.forEach((txBuilder) => { + ixTracker.checkedAdd(txBuilder); + }); + const lastValidBlockHeight = await convergence.rpc().getLatestBlockhash(); + ataTxs = prepareOptionsResult.ataTxBuilders.map((txBuilder) => + txBuilder.toTransaction(lastValidBlockHeight) + ); + mintTxs = prepareOptionsResult.mintTxBuilders.map((txBuilder) => + txBuilder.toTransaction(lastValidBlockHeight) + ); + const uniqueRemainingAtaTxBuilders: TransactionBuilder[] = []; + remainingAtaTxBuilders.forEach((txBuilder) => { + if (ixTracker.checkedAdd(txBuilder)) { + uniqueRemainingAtaTxBuilders.push(txBuilder); + } + }); + + const remainingAtaTxs = uniqueRemainingAtaTxBuilders.map((txBuilder) => + txBuilder.toTransaction(lastValidBlockHeight) + ); + + const prepareSettlementTx = + prepareSettlementTxBuilder.toTransaction(lastValidBlockHeight); const confirmOptions = makeConfirmOptionsFinalizedOnMainnet( convergence, scope.confirmOptions ); + const [ataSignedTxs, mintSignedTxs, signedRemainingAtaTxs] = + await convergence + .identity() + .signTransactionMatrix(ataTxs, mintTxs, remainingAtaTxs); + + if (ataSignedTxs.length > 0) { + await Promise.all( + ataSignedTxs.map((signedTx) => + convergence + .rpc() + .serializeAndSendTransaction(signedTx, lastValidBlockHeight) + ) + ); + } + + if (signedRemainingAtaTxs.length > 0) { + await Promise.all( + signedRemainingAtaTxs.map((signedTx) => + convergence + .rpc() + .serializeAndSendTransaction(signedTx, lastValidBlockHeight) + ) + ); + } - const output = await builder.sendAndConfirm(convergence, confirmOptions); - scope.throwIfCanceled(); + if (mintSignedTxs.length > 0) { + await Promise.all( + mintSignedTxs.map((signedTx) => + convergence + .rpc() + .serializeAndSendTransaction(signedTx, lastValidBlockHeight) + ) + ); + } - return { ...output }; + const signedPrepareSettlementTx = await convergence + .identity() + .signTransaction(prepareSettlementTx); + + const output = await convergence + .rpc() + .serializeAndSendTransaction( + signedPrepareSettlementTx, + lastValidBlockHeight, + confirmOptions + ); + + return { + response: output, + }; }, }; @@ -151,11 +268,17 @@ export type PrepareSettlementBuilderParams = PrepareSettlementInput; * @group Transaction Builders * @category Constructors */ + +export type PrepareSettlementBuilderResult = { + ataTxBuilderArray: TransactionBuilder[]; + prepareSettlementTxBuilder: TransactionBuilder; +}; export const prepareSettlementBuilder = async ( convergence: Convergence, + rfqModel: Rfq, params: PrepareSettlementBuilderParams, options: TransactionBuilderOptions = {} -): Promise => { +): Promise => { const { programs, payer = convergence.rpc().getDefaultFeePayer() } = options; const { caller = convergence.identity(), @@ -166,7 +289,6 @@ export const prepareSettlementBuilder = async ( const rfqProgram = convergence.programs().getRfq(programs); - const rfqModel = await convergence.rfqs().findRfqByAddress({ address: rfq }); const responseModel = await convergence .rfqs() .findResponseByAddress({ address: response }); @@ -227,6 +349,7 @@ export const prepareSettlementBuilder = async ( anchorRemainingAccounts.push(spotInstrumentProgramAccount, ...quoteAccounts); + const ataTxBuilderArray: TransactionBuilder[] = []; for (let legIndex = 0; legIndex < legAmountToPrepare; legIndex++) { const instrumentProgramAccount: AccountMeta = { pubkey: rfqModel.legs[legIndex].getProgramId(), @@ -242,6 +365,17 @@ export const prepareSettlementBuilder = async ( rfqModel, }); + const { ataPubKey, txBuilder } = await getOrCreateATAtxBuilder( + convergence, + baseAssetMints[legIndex].address, + caller.publicKey, + programs + ); + + if (txBuilder) { + ataTxBuilderArray.push(txBuilder); + } + const legAccounts: AccountMeta[] = [ // `caller` { @@ -251,12 +385,7 @@ export const prepareSettlementBuilder = async ( }, // `caller_token_account` { - pubkey: await getOrCreateATA( - convergence, - baseAssetMints[legIndex].address, - caller.publicKey, - programs - ), + pubkey: ataPubKey, isSigner: false, isWritable: true, }, @@ -279,7 +408,7 @@ export const prepareSettlementBuilder = async ( anchorRemainingAccounts.push(instrumentProgramAccount, ...legAccounts); } - return TransactionBuilder.make() + const prepareSettlementTxBuilder = TransactionBuilder.make() .setFeePayer(payer) .add( { @@ -307,4 +436,20 @@ export const prepareSettlementBuilder = async ( key: 'prepareSettlement', } ); + return { + ataTxBuilderArray, + prepareSettlementTxBuilder, + }; +}; + +const doesRfqLegContainsPsyoptionsAmerican = (rfq: Rfq) => { + return rfq.legs.some((leg) => + leg.getProgramId().equals(psyoptionsAmericanInstrumentProgram.address) + ); +}; + +const doesRfqLegContainsPsyoptionsEuropean = (rfq: Rfq) => { + return rfq.legs.some((leg) => + leg.getProgramId().equals(psyoptionsEuropeanInstrumentProgram.address) + ); }; diff --git a/packages/js/src/plugins/rfqModule/operations/prepareSettlementAndPrepareMoreLegs.ts b/packages/js/src/plugins/rfqModule/operations/prepareSettlementAndPrepareMoreLegs.ts index e3a3c89fd..ba489d84d 100644 --- a/packages/js/src/plugins/rfqModule/operations/prepareSettlementAndPrepareMoreLegs.ts +++ b/packages/js/src/plugins/rfqModule/operations/prepareSettlementAndPrepareMoreLegs.ts @@ -96,13 +96,14 @@ export const prepareSettlementAndPrepareMoreLegsOperationHandler: OperationHandl convergence: Convergence, scope: OperationScope ): Promise => { - const { caller = convergence.identity(), legAmountToPrepare } = - operation.input; + const { legAmountToPrepare, rfq } = operation.input; + const rfqModel = await convergence.rfqs().findRfqByAddress({ + address: rfq, + }); - const MAX_TX_SIZE = 1232; - - let prepareBuilder = await prepareSettlementBuilder( + const result = await prepareSettlementBuilder( convergence, + rfqModel, { ...operation.input, }, @@ -110,30 +111,23 @@ export const prepareSettlementAndPrepareMoreLegsOperationHandler: OperationHandl ); scope.throwIfCanceled(); - let prepareSettlementTxSize = await convergence - .rpc() - .getTransactionSize(prepareBuilder, [caller]); - let slicedLegAmount = legAmountToPrepare; + const { ataTxBuilderArray } = result; + let prepareBuilder = result.prepareSettlementTxBuilder; - while ( - prepareSettlementTxSize == -1 || - prepareSettlementTxSize + 193 > MAX_TX_SIZE - ) { + while (!prepareBuilder.checkTransactionFits()) { const halvedLegAmount = Math.trunc(slicedLegAmount / 2); - prepareBuilder = await prepareSettlementBuilder( + const result = await prepareSettlementBuilder( convergence, + rfqModel, { ...operation.input, legAmountToPrepare: halvedLegAmount, }, scope ); - - prepareSettlementTxSize = await convergence - .rpc() - .getTransactionSize(prepareBuilder, [caller]); + prepareBuilder = result.prepareSettlementTxBuilder; slicedLegAmount = halvedLegAmount; } @@ -143,13 +137,36 @@ export const prepareSettlementAndPrepareMoreLegsOperationHandler: OperationHandl scope.confirmOptions ); - const output = await prepareBuilder.sendAndConfirm( - convergence, - confirmOptions + const latestValidBlockHeight = await convergence + .rpc() + .getLatestBlockhash(); + const ataTxs = ataTxBuilderArray.map((builder) => + builder.toTransaction(latestValidBlockHeight) ); - scope.throwIfCanceled(); - let prepareMoreTxSize = 0; + const prepareSettlementTx = prepareBuilder.toTransaction( + latestValidBlockHeight + ); + const [signedAtaTxs, [signedPrepareSettlementTx]] = await convergence + .identity() + .signTransactionMatrix(ataTxs, [prepareSettlementTx]); + + await Promise.all( + signedAtaTxs.map((signedTx) => + convergence + .rpc() + .serializeAndSendTransaction(signedTx, latestValidBlockHeight) + ) + ); + + const output = await convergence + .rpc() + .serializeAndSendTransaction( + signedPrepareSettlementTx, + latestValidBlockHeight + ); + + scope.throwIfCanceled(); if (slicedLegAmount < legAmountToPrepare) { let prepareMoreLegsSlicedLegAmount = @@ -165,14 +182,7 @@ export const prepareSettlementAndPrepareMoreLegsOperationHandler: OperationHandl scope ); - prepareMoreTxSize = await convergence - .rpc() - .getTransactionSize(prepareMoreBuilder, [caller]); - - while ( - prepareMoreTxSize == -1 || - prepareMoreTxSize + 193 > MAX_TX_SIZE - ) { + while (!prepareMoreBuilder.checkTransactionFits()) { const halvedLegAmount = Math.trunc( prepareMoreLegsSlicedLegAmount / 2 ); @@ -186,11 +196,6 @@ export const prepareSettlementAndPrepareMoreLegsOperationHandler: OperationHandl }, scope ); - - prepareMoreTxSize = await convergence - .rpc() - .getTransactionSize(prepareMoreBuilder, [caller]); - prepareMoreLegsSlicedLegAmount = halvedLegAmount; } @@ -242,6 +247,6 @@ export const prepareSettlementAndPrepareMoreLegsOperationHandler: OperationHandl } } - return { ...output }; + return { response: output }; }, }; diff --git a/packages/js/src/plugins/rfqModule/operations/respondToRfq.ts b/packages/js/src/plugins/rfqModule/operations/respondToRfq.ts index 529505697..258f56295 100644 --- a/packages/js/src/plugins/rfqModule/operations/respondToRfq.ts +++ b/packages/js/src/plugins/rfqModule/operations/respondToRfq.ts @@ -1,6 +1,7 @@ import { createRespondToRfqInstruction } from '@convergence-rfq/rfq'; import { PublicKey, AccountMeta, ComputeBudgetProgram } from '@solana/web3.js'; +import BN from 'bn.js'; import { SendAndConfirmTransactionResponse } from '../../rpcModule'; import { assertResponse, Response } from '../models/Response'; import { Convergence } from '../../../Convergence'; @@ -17,6 +18,7 @@ import { } from '../../../utils/TransactionBuilder'; import { Quote, Rfq } from '../models'; import { toSolitaQuote } from '../models/Quote'; +import { convertTimestampToSeconds } from '@/utils'; const getNextResponsePdaAndDistinguisher = async ( cvg: Convergence, @@ -99,6 +101,11 @@ export type RespondToRfqInput = { */ ask?: Quote; + /** + * The optional response expirationTimestamp in seconds. + */ + expirationTimestamp?: number; + /** * The address of the RFQ account. */ @@ -239,6 +246,7 @@ export const respondToRfqBuilder = async ( user: maker.publicKey, programs, }), + expirationTimestamp, } = params; if (!bid && !ask) { @@ -247,6 +255,27 @@ export const respondToRfqBuilder = async ( const rfqModel = await convergence.rfqs().findRfqByAddress({ address: rfq }); + const rfqExpirationTimestampSeconds = + convertTimestampToSeconds(rfqModel.creationTimestamp) + + rfqModel.activeWindow; + + const currentTimestampSeconds = convertTimestampToSeconds(Date.now()); + + let expirationTimestampBn: BN; + + if (!expirationTimestamp) { + expirationTimestampBn = new BN(rfqExpirationTimestampSeconds); + } else { + if (expirationTimestamp < currentTimestampSeconds) { + throw new Error('Expiration timestamp must be in the future'); + } + if (expirationTimestamp > rfqExpirationTimestampSeconds) { + throw new Error('Response expiration must be less than RFQ expiration'); + } + + expirationTimestampBn = new BN(expirationTimestamp); + } + const { response, pdaDistinguisher } = await getNextResponsePdaAndDistinguisher( convergence, @@ -323,6 +352,7 @@ export const respondToRfqBuilder = async ( bid: bid && toSolitaQuote(bid, rfqModel.quoteAsset.getDecimals()), ask: ask && toSolitaQuote(ask, rfqModel.quoteAsset.getDecimals()), pdaDistinguisher, + expirationTimestamp: expirationTimestampBn, } ), signers: [maker], diff --git a/packages/js/src/plugins/rfqModule/plugin.ts b/packages/js/src/plugins/rfqModule/plugin.ts index a77e84727..ef9d2b5a0 100644 --- a/packages/js/src/plugins/rfqModule/plugin.ts +++ b/packages/js/src/plugins/rfqModule/plugin.ts @@ -73,8 +73,14 @@ import { cleanUpRfqsOperationHandler, getSettlementResultOperation, getSettlementResultHandler, + getResponseStateAndActionOperation, + getResponseStateAndActionHandler, } from './operations'; import { rfqProgram } from './program'; +import { + getRfqStateAndActionHandler, + getRfqStateAndActionOperation, +} from './operations/getRfqStateAndAction'; /** @group Plugins */ export const rfqModule = (): ConvergencePlugin => ({ @@ -182,6 +188,11 @@ export const rfqModule = (): ConvergencePlugin => ({ unlockRfqsCollateralOperationHandler ); op.register(getSettlementResultOperation, getSettlementResultHandler); + op.register( + getResponseStateAndActionOperation, + getResponseStateAndActionHandler + ); + op.register(getRfqStateAndActionOperation, getRfqStateAndActionHandler); convergence.rfqs = function () { return new RfqClient(this); diff --git a/packages/js/src/plugins/rpcModule/RpcClient.ts b/packages/js/src/plugins/rpcModule/RpcClient.ts index a89551491..2e1fa5290 100644 --- a/packages/js/src/plugins/rpcModule/RpcClient.ts +++ b/packages/js/src/plugins/rpcModule/RpcClient.ts @@ -283,8 +283,8 @@ export class RpcClient { async sendAndConfirmTransaction( transaction: Transaction | TransactionBuilder, - confirmOptions?: ConfirmOptions, - signers: Signer[] = [] + signers: Signer[] = [], + confirmOptions?: ConfirmOptions ): Promise { const prepared = await this.prepareTransaction(transaction, signers); const { blockhashWithExpiryBlockHeight } = prepared; @@ -442,25 +442,6 @@ export class RpcClient { }; } - async signAllTransactions( - transactions: Transaction[], - signers: Signer[] - ): Promise { - const { keypairs, identities } = getSignerHistogram(signers); - - for (let transaction of transactions) { - if (keypairs.length > 0) { - transaction.partialSign(...keypairs); - } - - for (let i = 0; i < identities.length; i++) { - transaction = await identities[i].signTransaction(transaction); - } - } - - return transactions; - } - protected parseProgramError( error: unknown, transaction: Transaction diff --git a/packages/js/src/plugins/spotInstrumentModule/instruments.ts b/packages/js/src/plugins/spotInstrumentModule/instruments.ts index b4f0070ad..11ac793a3 100644 --- a/packages/js/src/plugins/spotInstrumentModule/instruments.ts +++ b/packages/js/src/plugins/spotInstrumentModule/instruments.ts @@ -4,7 +4,11 @@ import { FixableBeetArgsStruct } from '@convergence-rfq/beet'; import { publicKey } from '@convergence-rfq/beet-solana'; import { Mint } from '../tokenModule'; -import { LegInstrument, QuoteInstrument } from '../instrumentModule'; +import { + CreateOptionInstrumentsResult, + LegInstrument, + QuoteInstrument, +} from '../instrumentModule'; import { Convergence } from '../../Convergence'; import { createSerializerFromFixableBeetArgsStruct } from '../../types'; import { removeDecimals } from '../../utils/conversions'; @@ -42,6 +46,9 @@ export class SpotLegInstrument implements LegInstrument { getDecimals = () => this.decimals; getAmount = () => this.amount; getBaseAssetIndex = () => this.baseAssetIndex; + async getPreparationsBeforeRfqCreation(): Promise { + return []; + } static async create( convergence: Convergence, @@ -77,7 +84,7 @@ export class SpotLegInstrument implements LegInstrument { } /** Helper method to get validation accounts for a spot instrument. */ - async getValidationAccounts() { + getValidationAccounts() { const mintInfo = this.convergence .rfqs() .pdas() diff --git a/packages/js/src/plugins/tokenModule/TokenClient.ts b/packages/js/src/plugins/tokenModule/TokenClient.ts index 9f4aacc0c..ad0587bf4 100644 --- a/packages/js/src/plugins/tokenModule/TokenClient.ts +++ b/packages/js/src/plugins/tokenModule/TokenClient.ts @@ -17,6 +17,8 @@ import { findTokenWithMintByMintOperation, FreezeTokensInput, freezeTokensOperation, + GetTokenBalanceInput, + getTokenBalanceOperation, MintTokensInput, mintTokensOperation, RevokeTokenDelegateAuthorityInput, @@ -233,4 +235,11 @@ export class TokenClient { .operations() .execute(revokeTokenDelegateAuthorityOperation(input), options); } + + /** {@inheritDoc getTokenBalanceOperation } */ + getTokenBalance(input: GetTokenBalanceInput, options?: OperationOptions) { + return this.convergence + .operations() + .execute(getTokenBalanceOperation(input), options); + } } diff --git a/packages/js/src/plugins/tokenModule/operations/getTokenBalance.ts b/packages/js/src/plugins/tokenModule/operations/getTokenBalance.ts new file mode 100644 index 000000000..4dda8d08a --- /dev/null +++ b/packages/js/src/plugins/tokenModule/operations/getTokenBalance.ts @@ -0,0 +1,91 @@ +import { PublicKey } from '@solana/web3.js'; + +import BN from 'bn.js'; +import type { Convergence } from '../../../Convergence'; +import { Operation, OperationHandler, useOperation } from '../../../types'; + +const Key = 'GetTokenBalance' as const; + +/** + * Get token Balance. + * + * ```ts + * await convergence + * .tokens() + * .getTokenBalance({ + * mintAddress, + * owner, + * mintDecimals + * }; + * ``` + * + * @group Operations + * @category Constructors + */ +export const getTokenBalanceOperation = + useOperation(Key); + +/** + * @group Operations + * @category Types + */ +export type GetTokenBalanceOperation = Operation< + typeof Key, + GetTokenBalanceInput, + GetTokenBalanceOutput +>; + +/** + * @group Operations + * @category Inputs + */ +export type GetTokenBalanceInput = { + /** The address of the mint account. */ + mintAddress: PublicKey; + /** The address of ATA owner */ + owner: PublicKey; + /** mint decimals*/ + mintDecimals: number; +}; + +/** + * @group Operations + * @category Outputs + */ +export type GetTokenBalanceOutput = { + /** The blockchain response from sending and confirming the transaction. */ + tokenBalance: number; +}; + +/** + * @group Operations + * @category Handlers + */ +export const getTokenBalanceOperationHandler: OperationHandler = + { + async handle( + operation: GetTokenBalanceOperation, + convergence: Convergence + ): Promise { + const { mintAddress, owner, mintDecimals } = operation.input; + + const ataAddress = convergence.tokens().pdas().associatedTokenAccount({ + mint: mintAddress, + owner, + }); + try { + const ata = await convergence + .tokens() + .findTokenByAddress({ address: ataAddress }); + return { + tokenBalance: ata.amount.basisPoints + .div(new BN(Math.pow(10, mintDecimals))) + .toNumber(), + }; + } catch (e) { + return { + tokenBalance: 0, + }; + } + }, + }; diff --git a/packages/js/src/plugins/tokenModule/operations/index.ts b/packages/js/src/plugins/tokenModule/operations/index.ts index 126c7f4a1..2a3ea8a8f 100644 --- a/packages/js/src/plugins/tokenModule/operations/index.ts +++ b/packages/js/src/plugins/tokenModule/operations/index.ts @@ -11,3 +11,4 @@ export * from './mintTokens'; export * from './revokeTokenDelegateAuthority'; export * from './sendTokens'; export * from './thawTokens'; +export * from './getTokenBalance'; diff --git a/packages/js/src/plugins/tokenModule/plugin.ts b/packages/js/src/plugins/tokenModule/plugin.ts index 7f7555483..77544a77c 100644 --- a/packages/js/src/plugins/tokenModule/plugin.ts +++ b/packages/js/src/plugins/tokenModule/plugin.ts @@ -18,6 +18,8 @@ import { findTokenWithMintByMintOperationHandler, freezeTokensOperation, freezeTokensOperationHandler, + getTokenBalanceOperationHandler, + getTokenBalanceOperation, mintTokensOperation, mintTokensOperationHandler, revokeTokenDelegateAuthorityOperation, @@ -94,6 +96,8 @@ export const tokenModule = (): ConvergencePlugin => ({ op.register(sendTokensOperation, sendTokensOperationHandler); op.register(thawTokensOperation, thawTokensOperationHandler); + op.register(getTokenBalanceOperation, getTokenBalanceOperationHandler); + convergence.tokens = function () { return new TokenClient(this); }; diff --git a/packages/js/src/utils/TransactionBuilder.ts b/packages/js/src/utils/TransactionBuilder.ts index 04be4161e..2502cd9b3 100644 --- a/packages/js/src/utils/TransactionBuilder.ts +++ b/packages/js/src/utils/TransactionBuilder.ts @@ -1,6 +1,7 @@ import { BlockhashWithExpiryBlockHeight, ConfirmOptions, + PACKET_DATA_SIZE, SignaturePubkeyPair, Transaction, TransactionInstruction, @@ -9,6 +10,8 @@ import { SendAndConfirmTransactionResponse } from '../plugins/rpcModule'; import type { Convergence } from '../Convergence'; import type { OperationOptions, Signer } from '../types'; +export const DUMMY_BLOCKHASH = 'H9cCgV1suCbdxMGDGUecdgJPZzdCe4CbNYa6ijP1uBLS'; + export type InstructionWithSigners = { instruction: TransactionInstruction; signers: Signer[]; @@ -42,6 +45,23 @@ export class TransactionBuilder { this.transactionOptions = transactionOptions; } + checkTransactionFits = () => { + const transaction = this.toTransaction({ + blockhash: DUMMY_BLOCKHASH, + lastValidBlockHeight: 0, + }); + const message = transaction.compileMessage(); + + const serializedMessage = message.serialize(); + const txSize = + 1 + 64 * message.header.numRequiredSignatures + serializedMessage.length; + if (txSize > PACKET_DATA_SIZE) { + return false; + } + + return true; + }; + static make( transactionOptions?: TransactionOptions ): TransactionBuilder { @@ -196,7 +216,7 @@ export class TransactionBuilder { ): Promise<{ response: SendAndConfirmTransactionResponse } & C> { const response = await convergence .rpc() - .sendAndConfirmTransaction(this, confirmOptions); + .sendAndConfirmTransaction(this, [], confirmOptions); return { response, diff --git a/packages/js/src/utils/Wallets.ts b/packages/js/src/utils/Wallets.ts index 23bfe096b..6b4500aa1 100644 --- a/packages/js/src/utils/Wallets.ts +++ b/packages/js/src/utils/Wallets.ts @@ -1,5 +1,5 @@ import { Keypair, PublicKey, Transaction } from '@solana/web3.js'; -import { Convergence, Signer } from '..'; +import { Convergence } from '..'; interface Wallet { signTransaction(tx: Transaction): Promise; @@ -15,17 +15,15 @@ export class CvgWallet implements Wallet { constructor(convergence: Convergence) { this.convergence = convergence; this.payer = convergence.rpc().getDefaultFeePayer() as Keypair; - this.publicKey = convergence.rpc().getDefaultFeePayer().publicKey; + this.publicKey = convergence.identity().publicKey; } signTransaction = (tx: Transaction): Promise => { - return this.convergence.rpc().signTransaction(tx, [this.payer as Signer]); + return this.convergence.identity().signTransaction(tx); }; signAllTransactions = (txs: Transaction[]): Promise => { - return this.convergence - .rpc() - .signAllTransactions(txs, [this.payer as Signer]); + return this.convergence.identity().signAllTransactions(txs); }; } diff --git a/packages/js/src/utils/ata.ts b/packages/js/src/utils/ata.ts index 7478b4fce..2b01c6e59 100644 --- a/packages/js/src/utils/ata.ts +++ b/packages/js/src/utils/ata.ts @@ -1,14 +1,10 @@ -import * as Spl from '@solana/spl-token'; -import { Keypair, PublicKey, TransactionInstruction } from '@solana/web3.js'; +import { Keypair, PublicKey } from '@solana/web3.js'; import { Convergence } from '../Convergence'; import { token } from '../types/Amount'; import { Program } from '../types'; +import { TransactionBuilder } from '../utils/TransactionBuilder'; import { collateralMintCache } from '@/plugins/collateralModule/cache'; - -export enum ATAExistence { - EXISTS, - NOTEXISTS, -} +import { createTokenBuilder } from '@/plugins/tokenModule/operations/createToken'; export const getOrCreateATA = async ( convergence: Convergence, @@ -35,31 +31,31 @@ export const getOrCreateATA = async ( return ata; }; -export const getOrCreateATAInx = async ( +export interface GetOrCreateATAtxBuilderReturnType { + ataPubKey: PublicKey; + txBuilder?: TransactionBuilder; +} + +export const getOrCreateATAtxBuilder = async ( convergence: Convergence, mint: PublicKey, owner: PublicKey, programs?: Program[] -): Promise => { +): Promise => { const pda = convergence.tokens().pdas().associatedTokenAccount({ mint, owner, programs, }); const account = await convergence.rpc().getAccount(pda); - let ix: TransactionInstruction; if (!account.exists) { - ix = Spl.createAssociatedTokenAccountInstruction( - owner, - pda, - owner, + const txBuilder = await createTokenBuilder(convergence, { mint, - Spl.TOKEN_PROGRAM_ID, - Spl.ASSOCIATED_TOKEN_PROGRAM_ID - ); - return ix; + owner, + }); + return { ataPubKey: pda, txBuilder }; } - return pda; + return { ataPubKey: pda }; }; export const devnetAirdrops = async ( diff --git a/packages/js/src/utils/classes.ts b/packages/js/src/utils/classes.ts new file mode 100644 index 000000000..d7b972f52 --- /dev/null +++ b/packages/js/src/utils/classes.ts @@ -0,0 +1,90 @@ +import { AccountMeta, TransactionInstruction } from '@solana/web3.js'; +import { TransactionBuilder } from './TransactionBuilder'; +import { Response, Rfq } from '@/plugins/rfqModule'; + +export class InstructionUniquenessTracker { + constructor(public readonly IxArray: TransactionInstruction[]) { + this.IxArray = IxArray; + } + + private matchKeys = ( + keys: AccountMeta[], + keysToMatch: AccountMeta[] + ): boolean => { + if (keys.length !== keysToMatch.length) { + return false; + } + return keys.every( + (key, index) => + key.isSigner === keysToMatch[index].isSigner && + key.isWritable === keysToMatch[index].isWritable && + key.pubkey.equals(keysToMatch[index].pubkey) + ); + }; + private matchInstruction = (ixToBeAdded: TransactionInstruction): boolean => { + return !this.IxArray.every( + (ix) => + !( + this.matchKeys(ix.keys, ixToBeAdded.keys) && + ix.programId.equals(ixToBeAdded.programId) && + ix.data.equals(ixToBeAdded.data) + ) + ); + }; + checkedAdd(ix: TransactionInstruction | TransactionBuilder): boolean { + if (ix instanceof TransactionBuilder) { + const instructions = ix.getInstructions(); + const areAllUnique = instructions.every( + (ix) => !this.matchInstruction(ix) + ); + if (areAllUnique) { + this.IxArray.push(...instructions); + return true; + } + return false; + } else if (ix instanceof TransactionInstruction) { + if (!this.matchInstruction(ix)) { + this.IxArray.push(ix); + return true; + } + return false; + } + throw new Error('Invalid Instruction type'); + } +} + +export class RfqTimers { + public timestampExpiry: Date; + public timestampStart: Date; + public timeStampSettlement: Date; + + constructor(rfq: Rfq) { + this.timestampStart = new Date(Number(rfq.creationTimestamp)); + this.timestampExpiry = new Date( + this.timestampStart.getTime() + Number(rfq.activeWindow) * 1000 + ); + this.timeStampSettlement = new Date( + this.timestampExpiry.getTime() + Number(rfq.settlingWindow) * 1000 + ); + } + + isRfqExpired(): boolean { + return Date.now() >= this.timestampExpiry.getTime(); + } + + isRfqSettlementWindowElapsed(): boolean { + return Date.now() >= this.timeStampSettlement.getTime(); + } +} + +export class ResponseTimers { + public timestampExpiry: Date; + + constructor(response: Response) { + this.timestampExpiry = new Date(response.expirationTimestamp); + } + + isResponseExpired(): boolean { + return Date.now() >= this.timestampExpiry.getTime(); + } +} diff --git a/packages/js/src/utils/conversions.ts b/packages/js/src/utils/conversions.ts index 4755437bc..265af495e 100644 --- a/packages/js/src/utils/conversions.ts +++ b/packages/js/src/utils/conversions.ts @@ -50,10 +50,22 @@ export const addDecimals = (value: number, decimals: number = 0): BN => { * @param timestamp {bignum} Solita timestamp * @returns {number} timestamp in milliseconds */ -export function convertTimestamp(timestamp: bignum): number { +export function convertTimestampToMilliSeconds( + timestamp: bignum | number +): number { + if (typeof timestamp === 'number') { + return timestamp * 1_000; + } return Number(timestamp) * 1_000; } +export function convertTimestampToSeconds(timestamp: bignum | number): number { + if (typeof timestamp === 'number') { + return Math.floor(timestamp / 1_000); + } + return Math.floor(Number(timestamp) / 1_000); +} + /** * Used to roundUp values to a certain amount of decimals. * diff --git a/packages/js/src/utils/index.ts b/packages/js/src/utils/index.ts index c6caca0ba..47ca604ba 100644 --- a/packages/js/src/utils/index.ts +++ b/packages/js/src/utils/index.ts @@ -12,3 +12,4 @@ export * from './Wallets'; export * from './cache'; export * from './conversions'; export * from './ata'; +export * from './classes'; diff --git a/packages/js/tests/helpers.ts b/packages/js/tests/helpers.ts index 2800eed64..28b5345df 100644 --- a/packages/js/tests/helpers.ts +++ b/packages/js/tests/helpers.ts @@ -2,34 +2,25 @@ import { Commitment, Connection } from '@solana/web3.js'; import { PROGRAM_ID } from '@convergence-rfq/rfq'; import { v4 as uuidv4 } from 'uuid'; import { Program, web3 } from '@project-serum/anchor'; -import * as anchor from '@project-serum/anchor'; +import { OptionType } from '@mithraic-labs/tokenized-euros'; import { Convergence, OrderType, - OptionType, - PsyoptionsAmericanInstrument, - initializeNewAmericanOption, toBigNumber, - createAmericanProgram, Rfq, Response, - getOrCreateAmericanOptionATAs, - mintAmericanOptions, + prepareAmericanOptions, SpotQuoteInstrument, - SpotLegInstrument, keypairIdentity, PublicKey, removeDecimals, - useCache, - createEuropeanProgram, - CvgWallet, PsyoptionsEuropeanInstrument, - initializeNewEuropeanOption, - getOrCreateEuropeanOptionATAs, - mintEuropeanOptions, + prepareEuropeanOptions, + PsyoptionsAmericanInstrument, + SpotLegInstrument, + Mint, } from '../src'; import { getUserKp, RPC_ENDPOINT } from '../../validator'; -import { IDL as PseudoPythIdl } from '../../validator/fixtures/programs/pseudo_pyth_idl'; import { BASE_MINT_BTC_PK, QUOTE_MINT_PK } from './constants'; const DEFAULT_COMMITMENT = 'confirmed'; const DEFAULT_SKIP_PREFLIGHT = true; @@ -113,18 +104,11 @@ export function generateTicker(): string { export const createAmericanCoveredCallRfq = async ( cvg: Convergence, orderType: OrderType, - baseMint: any, - quoteMint: any + baseMint: Mint, + quoteMint: Mint ) => { - const { optionMarketKey, optionMarket } = await initializeNewAmericanOption( - cvg, - baseMint, - quoteMint, - 27_000, - 1, - 3_600 + Math.random() - ); - + const randomExpiry = 3_600 + Math.random() * 1000; + const expirationTimestamp = Date.now() / 1000 + randomExpiry; const { rfq, response } = await cvg.rfqs().createAndFinalize({ instruments: [ await SpotLegInstrument.create(cvg, baseMint, 1.0, 'long'), @@ -133,10 +117,11 @@ export const createAmericanCoveredCallRfq = async ( baseMint, quoteMint, OptionType.CALL, - optionMarket, - optionMarketKey, 1, - 'long' + 'long', + 1, + 24_534, + expirationTimestamp ), ], orderType, @@ -144,147 +129,97 @@ export const createAmericanCoveredCallRfq = async ( quoteAsset: await SpotQuoteInstrument.create(cvg, quoteMint), }); - return { rfq, response, optionMarket }; + return { rfq, response }; }; export const createEuropeanCoveredCallRfq = async ( cvg: Convergence, orderType: OrderType, - baseMint: any, - quoteMint: any + baseMint: Mint, + quoteMint: Mint, + oracle: PublicKey ) => { - const europeanProgram = await createEuropeanProgram(cvg); - const oracle = await createPythPriceFeed( - new anchor.Program( - PseudoPythIdl, - new PublicKey('FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH'), - new anchor.AnchorProvider(cvg.connection, new CvgWallet(cvg), {}) - ), - 17_000, - quoteMint.decimals * -1 - ); const min = 3_600; const randomExpiry = min + Math.random(); - - const { euroMeta, euroMetaKey } = await initializeNewEuropeanOption( - cvg, - oracle, - europeanProgram, - baseMint, - quoteMint, - 23_354, - 1, - randomExpiry, - 0 - ); - + const expirationTimestamp = Date.now() / 1000 + randomExpiry; const { rfq, response } = await cvg.rfqs().createAndFinalize({ instruments: [ await SpotLegInstrument.create(cvg, baseMint, 1.0, 'long'), await PsyoptionsEuropeanInstrument.create( cvg, baseMint, + quoteMint, OptionType.CALL, - euroMeta, - euroMetaKey, 1, - 'long' + 'long', + 24_534, + 1, + oracle, + 0, + expirationTimestamp ), ], orderType, fixedSize: { type: 'fixed-base', amount: 1 }, quoteAsset: await SpotQuoteInstrument.create(cvg, quoteMint), }); - return { rfq, response, euroMeta }; + return { rfq, response }; }; export const createEuropeanOpenSizeCallSpdOptionRfq = async ( cvg: Convergence, orderType: OrderType, - baseMint: any, - quoteMint: any + baseMint: Mint, + quoteMint: Mint, + oracle: PublicKey ) => { - const europeanProgram = await createEuropeanProgram(cvg); - const oracle = await createPythPriceFeed( - new anchor.Program( - PseudoPythIdl, - new PublicKey('FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH'), - new anchor.AnchorProvider(cvg.connection, new CvgWallet(cvg), {}) - ), - 17_000, - quoteMint.decimals * -1 - ); const min = 3_600; - const randomExpiry = min + Math.random(); - const { euroMeta: euroMeta1, euroMetaKey: euroMetaKey1 } = - await initializeNewEuropeanOption( - cvg, - oracle, - europeanProgram, - baseMint, - quoteMint, - 23_354, - 1, - randomExpiry, - 0 - ); - const { euroMeta: euroMeta2, euroMetaKey: euroMetaKey2 } = - await initializeNewEuropeanOption( - cvg, - oracle, - europeanProgram, - baseMint, - quoteMint, - 25_354, - 1, - randomExpiry, - 0 - ); - + const randomExpiry = min + Math.random() * 1000; + const expirationTimestamp = Date.now() / 1000 + randomExpiry; const { rfq, response } = await cvg.rfqs().createAndFinalize({ instruments: [ await PsyoptionsEuropeanInstrument.create( cvg, baseMint, + quoteMint, OptionType.CALL, - euroMeta1, - euroMetaKey1, 1, - 'long' + 'long', + 29_000, + 1, + oracle, + 0, + expirationTimestamp ), await PsyoptionsEuropeanInstrument.create( cvg, baseMint, + quoteMint, OptionType.CALL, - euroMeta2, - euroMetaKey2, 1, - 'short' + 'long', + 31_000, + 1, + oracle, + 0, + expirationTimestamp ), ], orderType, fixedSize: { type: 'open' }, quoteAsset: await SpotQuoteInstrument.create(cvg, quoteMint), }); - return { rfq, response, euroMeta1, euroMeta2 }; + return { rfq, response }; }; export const createAmericanFixedBaseStraddle = async ( cvg: Convergence, orderType: OrderType, - baseMint: any, - quoteMint: any + baseMint: Mint, + quoteMint: Mint ) => { - const expiration = 3_600 + Math.random(); - const { optionMarketKey, optionMarket } = await initializeNewAmericanOption( - cvg, - baseMint, - quoteMint, - 27_000, - 1, - expiration - ); - + const randomExpiry = 3_600 + Math.random() * 1000; + const expirationTimestamp = Date.now() / 1000 + randomExpiry; const { rfq, response } = await cvg.rfqs().createAndFinalize({ instruments: [ await PsyoptionsAmericanInstrument.create( @@ -292,20 +227,22 @@ export const createAmericanFixedBaseStraddle = async ( baseMint, quoteMint, OptionType.CALL, - optionMarket, - optionMarketKey, 1, - 'long' + 'long', + 1, + 27_000, + expirationTimestamp ), await PsyoptionsAmericanInstrument.create( cvg, baseMint, quoteMint, OptionType.PUT, - optionMarket, - optionMarketKey, 1, - 'long' + 'long', + 1, + 27_000, + expirationTimestamp ), ], orderType, @@ -313,59 +250,46 @@ export const createAmericanFixedBaseStraddle = async ( quoteAsset: await SpotQuoteInstrument.create(cvg, quoteMint), }); - return { rfq, response, optionMarket }; + return { rfq, response }; }; export const createEuropeanFixedBaseStraddle = async ( cvg: Convergence, orderType: OrderType, - baseMint: any, - quoteMint: any + baseMint: Mint, + quoteMint: Mint, + oracle: PublicKey ) => { - const europeanProgram = await createEuropeanProgram(cvg); - const oracle = await createPythPriceFeed( - new anchor.Program( - PseudoPythIdl, - new PublicKey('FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH'), - new anchor.AnchorProvider(cvg.connection, new CvgWallet(cvg), {}) - ), - 17_000, - quoteMint.decimals * -1 - ); const min = 3_600; - const randomExpiry = min + Math.random(); - const { euroMeta: euroMeta, euroMetaKey: euroMetaKey } = - await initializeNewEuropeanOption( - cvg, - oracle, - europeanProgram, - baseMint, - quoteMint, - 23_354, - 1, - randomExpiry, - 0 - ); - + const randomExpiry = min + Math.random() * 1000; + const expirationTimestamp = Date.now() / 1000 + randomExpiry; const { rfq, response } = await cvg.rfqs().createAndFinalize({ instruments: [ await PsyoptionsEuropeanInstrument.create( cvg, baseMint, + quoteMint, OptionType.CALL, - euroMeta, - euroMetaKey, 1, - 'long' + 'long', + 26_334, + 1, + oracle, + 0, + expirationTimestamp ), await PsyoptionsEuropeanInstrument.create( cvg, baseMint, + quoteMint, OptionType.PUT, - euroMeta, - euroMetaKey, 1, - 'long' + 'long', + 26_334, + 1, + oracle, + 0, + expirationTimestamp ), ], orderType, @@ -373,35 +297,17 @@ export const createEuropeanFixedBaseStraddle = async ( quoteAsset: await SpotQuoteInstrument.create(cvg, quoteMint), }); - return { rfq, response, euroMeta }; + return { rfq, response }; }; export const createAmericanOpenSizeCallSpdOptionRfq = async ( cvg: Convergence, orderType: OrderType, - baseMint: any, - quoteMint: any + baseMint: Mint, + quoteMint: Mint ) => { - const expiration = 3_600 + Math.random(); - const { optionMarketKey: optionMarketKey1, optionMarket: optionMarket1 } = - await initializeNewAmericanOption( - cvg, - baseMint, - quoteMint, - 27_000, - 1, - expiration - ); - const { optionMarketKey: optionMarketKey2, optionMarket: optionMarket2 } = - await initializeNewAmericanOption( - cvg, - baseMint, - quoteMint, - 33_000, - 1, - expiration - ); - + const randomExpiry = 3_600 + Math.random() * 1000; + const expirationTimestamp = Date.now() / 1000 + randomExpiry; const { rfq, response } = await cvg.rfqs().createAndFinalize({ instruments: [ await PsyoptionsAmericanInstrument.create( @@ -409,20 +315,22 @@ export const createAmericanOpenSizeCallSpdOptionRfq = async ( baseMint, quoteMint, OptionType.CALL, - optionMarket1, - optionMarketKey1, 1, - 'long' + 'long', + 1, + 33_000, + expirationTimestamp ), await PsyoptionsAmericanInstrument.create( cvg, baseMint, quoteMint, OptionType.CALL, - optionMarket2, - optionMarketKey2, 1, - 'short' + 'short', + 1, + 31_000, + expirationTimestamp ), ], orderType, @@ -430,53 +338,9 @@ export const createAmericanOpenSizeCallSpdOptionRfq = async ( quoteAsset: await SpotQuoteInstrument.create(cvg, quoteMint), }); - return { rfq, response, optionMarket1, optionMarket2 }; + return { rfq, response }; }; -const cFlyMarketsCache = useCache(async (cvg, baseMint, quoteMint) => { - const [ - { optionMarket: low, optionMarketKey: lowKey }, - { optionMarket: medium, optionMarketKey: mediumKey }, - { optionMarket: high, optionMarketKey: highKey }, - ] = await Promise.all([ - initializeNewAmericanOption( - cvg, - baseMint, - quoteMint, - 18_000, - 1, - 90 * 24 * 60 * 60 // 90 days - ), - initializeNewAmericanOption( - cvg, - baseMint, - quoteMint, - 20_000, - 1, - 90 * 24 * 60 * 60 // 90 days - ), - initializeNewAmericanOption( - cvg, - baseMint, - quoteMint, - 22_000, - 1, - 90 * 24 * 60 * 60 // 90 days - ), - ]); - - return { - low, - lowKey, - - medium, - mediumKey, - - high, - highKey, - }; -}, 300); - export const createCFlyRfq = async ( cvg: Convergence, orderType: OrderType, @@ -489,41 +353,42 @@ export const createCFlyRfq = async ( .tokens() .findMintByAddress({ address: QUOTE_MINT_PK }); - const optionMarkets = await cFlyMarketsCache.get(cvg, baseMint, quoteMint); - - const { - rfq: { address: rfqAddress }, - } = await cvg.rfqs().create({ + const randomExpiry = 3_600 + Math.random() * 1000; + const expirationTimestamp = Date.now() / 1000 + randomExpiry; + const { rfq } = await cvg.rfqs().createAndFinalize({ instruments: [ await PsyoptionsAmericanInstrument.create( cvg, baseMint, quoteMint, OptionType.CALL, - optionMarkets.low, - optionMarkets.lowKey, 1, - reversed ? 'short' : 'long' + reversed ? 'short' : 'long', + 1, + 33_000, + expirationTimestamp ), await PsyoptionsAmericanInstrument.create( cvg, baseMint, quoteMint, OptionType.CALL, - optionMarkets.medium, - optionMarkets.mediumKey, 2, - reversed ? 'long' : 'short' + reversed ? 'long' : 'short', + 1, + 35_000, + expirationTimestamp ), await PsyoptionsAmericanInstrument.create( cvg, baseMint, quoteMint, OptionType.CALL, - optionMarkets.high, - optionMarkets.highKey, 1, - reversed ? 'short' : 'long' + reversed ? 'short' : 'long', + 1, + 37_000, + expirationTimestamp ), ], orderType, @@ -531,7 +396,6 @@ export const createCFlyRfq = async ( quoteAsset: await SpotQuoteInstrument.create(cvg, quoteMint), settlingWindow: 90 * 24 * 60 * 60, // 90 days }); - const { rfq } = await cvg.rfqs().finalizeRfqConstruction({ rfq: rfqAddress }); return rfq; }; @@ -540,9 +404,11 @@ export const createRfq = async ( cvg: Convergence, amount: number, orderType: OrderType, + activeWindow?: number, rfqType: 'open' | 'fixed-base' | 'fixed-quote' = 'fixed-base', quoteMintPk = QUOTE_MINT_PK, baseMintPk = BASE_MINT_BTC_PK + // 10 minutes ) => { let instrumentAmount = 1; let fixedSizeAmount = 1; @@ -565,6 +431,7 @@ export const createRfq = async ( orderType, fixedSize: { type: rfqType, amount: fixedSizeAmount }, quoteAsset: await SpotQuoteInstrument.create(cvg, quoteMint), + activeWindow, }); return { rfq, response }; }; @@ -574,6 +441,7 @@ export const respondToRfq = async ( rfq: Rfq, bid?: number, ask?: number, + responseExpirationTimestamp?: number, legsMultiplier?: number ) => { if (!bid && !ask) { @@ -584,6 +452,7 @@ export const respondToRfq = async ( rfq: rfq.address, bid: bid ? { price: bid, legsMultiplier } : undefined, ask: ask ? { price: ask, legsMultiplier } : undefined, + expirationTimestamp: responseExpirationTimestamp, }); }; @@ -593,7 +462,7 @@ export const prepareRfqSettlement = async ( response: Response ) => { return await cvg.rfqs().prepareSettlement({ - caller: cvg.rpc().getDefaultFeePayer(), + caller: cvg.identity(), rfq: rfq.address, response: response.address, legAmountToPrepare: rfq.legs.length, @@ -652,33 +521,164 @@ export const createPythPriceFeed = async ( }; export const setupAmerican = async (cvg: Convergence, response: Response) => { - const americanProgram = createAmericanProgram(cvg); - await getOrCreateAmericanOptionATAs( - cvg, - response.address, - cvg.identity().publicKey, - americanProgram - ); - await mintAmericanOptions( - cvg, - response.address, - cvg.identity().publicKey, - americanProgram - ); + await prepareAmericanOptions(cvg, response.address, cvg.identity().publicKey); }; export const setupEuropean = async (cvg: Convergence, response: Response) => { - const europeanProgram = await createEuropeanProgram(cvg); - - await getOrCreateEuropeanOptionATAs( + await prepareEuropeanOptions( cvg, response.address, cvg.rpc().getDefaultFeePayer().publicKey ); - await mintEuropeanOptions( - cvg, - response.address, - cvg.rpc().getDefaultFeePayer().publicKey, - europeanProgram - ); +}; + +export const createAmericanIronCondor = async ( + cvg: Convergence, + orderType: OrderType, + baseMint: Mint, + quoteMint: Mint +) => { + const randomExpiry = 3_600 + Math.random() * 1000; + const expirationTimestamp = Date.now() / 1000 + randomExpiry; + const { rfq } = await cvg.rfqs().createAndFinalize({ + instruments: [ + await PsyoptionsAmericanInstrument.create( + cvg, + baseMint, + quoteMint, + OptionType.CALL, + 4, + 'long', + 1, + 34_000, + expirationTimestamp + ), + await PsyoptionsAmericanInstrument.create( + cvg, + baseMint, + quoteMint, + OptionType.CALL, + 4, + 'short', + 1, + 35_000, + expirationTimestamp + ), + await PsyoptionsAmericanInstrument.create( + cvg, + baseMint, + quoteMint, + OptionType.CALL, + 4, + 'long', + 1, + 37_000, + expirationTimestamp + ), + await PsyoptionsAmericanInstrument.create( + cvg, + baseMint, + quoteMint, + OptionType.CALL, + 4, + 'short', + 1, + 36_000, + expirationTimestamp + ), + ], + orderType, + fixedSize: { type: 'fixed-base', amount: 1 }, + quoteAsset: await SpotQuoteInstrument.create(cvg, quoteMint), + settlingWindow: 90 * 24 * 60 * 60, // 90 days + }); + + return { rfq }; +}; + +export const createEuropeanIronCondor = async ( + cvg: Convergence, + orderType: OrderType, + baseMint: Mint, + quoteMint: Mint, + oracle: PublicKey +) => { + const randomExpiry = 3_600 + Math.random() * 1000; + const expirationTimestamp = Date.now() / 1000 + randomExpiry; + const { rfq } = await cvg.rfqs().createAndFinalize({ + instruments: [ + await PsyoptionsEuropeanInstrument.create( + cvg, + baseMint, + quoteMint, + OptionType.CALL, + 3, + 'long', + 27_000, + 1, + oracle, + 0, + expirationTimestamp + ), + await PsyoptionsEuropeanInstrument.create( + cvg, + baseMint, + quoteMint, + OptionType.PUT, + 3, + 'short', + 28_000, + 1, + oracle, + 0, + expirationTimestamp + ), + await PsyoptionsEuropeanInstrument.create( + cvg, + baseMint, + quoteMint, + OptionType.CALL, + 3, + 'long', + 30_000, + 1, + oracle, + 0, + expirationTimestamp + ), + await PsyoptionsEuropeanInstrument.create( + cvg, + baseMint, + quoteMint, + OptionType.PUT, + 3, + 'short', + 29_000, + 1, + oracle, + 0, + expirationTimestamp + ), + ], + orderType, + fixedSize: { type: 'fixed-base', amount: 1 }, + quoteAsset: await SpotQuoteInstrument.create(cvg, quoteMint), + settlingWindow: 90 * 24 * 60 * 60, // 90 days + }); + + return { rfq }; +}; + +export const expectError = async (promise: Promise, errorText: string) => { + try { + await promise; + throw new Error('No error thrown!'); + } catch (e) { + if ( + !e?.message.includes(errorText) && + !e?.logs?.some((e: string) => e.includes(errorText)) + ) { + throw e; + } + } }; diff --git a/packages/js/tests/integration/psyoptionsAmerican.spec.ts b/packages/js/tests/integration/psyoptionsAmerican.spec.ts index 0e32836a9..abd60fc7d 100644 --- a/packages/js/tests/integration/psyoptionsAmerican.spec.ts +++ b/packages/js/tests/integration/psyoptionsAmerican.spec.ts @@ -1,15 +1,15 @@ import { expect } from 'expect'; -import { Mint } from '@solana/spl-token'; +import { Mint } from '../../src/plugins/tokenModule'; import { createAmericanCoveredCallRfq, respondToRfq, prepareRfqSettlement, settleRfq, createUserCvg, - setupAmerican, createAmericanOpenSizeCallSpdOptionRfq, createAmericanFixedBaseStraddle, + createAmericanIronCondor, } from '../helpers'; import { BASE_MINT_BTC_PK, QUOTE_MINT_PK } from '../constants'; @@ -28,7 +28,7 @@ describe('integration.psyoptionsAmerican', () => { .findMintByAddress({ address: QUOTE_MINT_PK }); }); - it('covered call [sell]', async () => { + it('american covered call [sell]', async () => { const { rfq } = await createAmericanCoveredCallRfq( takerCvg, 'sell', @@ -47,8 +47,8 @@ describe('integration.psyoptionsAmerican', () => { response: rfqResponse.address, side: 'bid', }); + expect(confirmResponse).toHaveProperty('signature'); - await setupAmerican(takerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, @@ -93,15 +93,13 @@ describe('integration.psyoptionsAmerican', () => { side: 'ask', }); expect(confirmResponse).toHaveProperty('signature'); - await setupAmerican(takerCvg, rfqResponse); - await setupAmerican(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, rfqResponse ); - expect(takerResponse.response).toHaveProperty('signature'); + expect(takerResponse.response).toHaveProperty('signature'); const makerResponse = await prepareRfqSettlement( makerCvg, rfq, @@ -113,10 +111,10 @@ describe('integration.psyoptionsAmerican', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('fixed-size american straddle [sell]', async () => { - const { rfq } = await createAmericanFixedBaseStraddle( + it('open size american call Spread [buy]', async () => { + const { rfq } = await createAmericanOpenSizeCallSpdOptionRfq( takerCvg, - 'sell', + 'buy', baseMint, quoteMint ); @@ -124,20 +122,22 @@ describe('integration.psyoptionsAmerican', () => { const { rfqResponse } = await respondToRfq( makerCvg, rfq, - 55_133, - undefined + undefined, + 150_123, + undefined, + 5 ); expect(rfqResponse).toHaveProperty('address'); + const { response: confirmResponse } = await takerCvg .rfqs() .confirmResponse({ rfq: rfq.address, response: rfqResponse.address, - side: 'bid', + side: 'ask', + overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupAmerican(takerCvg, rfqResponse); - await setupAmerican(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -156,33 +156,36 @@ describe('integration.psyoptionsAmerican', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('fixed-size american straddle [2-way]', async () => { + it('fixed-size american straddle [sell]', async () => { const { rfq } = await createAmericanFixedBaseStraddle( takerCvg, - 'two-way', + 'sell', baseMint, quoteMint ); expect(rfq).toHaveProperty('address'); - const { rfqResponse } = await respondToRfq(makerCvg, rfq, 61_222, 60_123); + const { rfqResponse } = await respondToRfq( + makerCvg, + rfq, + 55_133, + undefined + ); expect(rfqResponse).toHaveProperty('address'); const { response: confirmResponse } = await takerCvg .rfqs() .confirmResponse({ rfq: rfq.address, response: rfqResponse.address, - side: 'ask', + side: 'bid', }); expect(confirmResponse).toHaveProperty('signature'); - await setupAmerican(takerCvg, rfqResponse); - await setupAmerican(makerCvg, rfqResponse); + const takerResponse = await prepareRfqSettlement( takerCvg, rfq, rfqResponse ); expect(takerResponse.response).toHaveProperty('signature'); - const makerResponse = await prepareRfqSettlement( makerCvg, rfq, @@ -194,10 +197,10 @@ describe('integration.psyoptionsAmerican', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('open size american call Spread [buy]', async () => { + it('open size american call Spread [2-way]', async () => { const { rfq } = await createAmericanOpenSizeCallSpdOptionRfq( takerCvg, - 'buy', + 'two-way', baseMint, quoteMint ); @@ -205,8 +208,9 @@ describe('integration.psyoptionsAmerican', () => { const { rfqResponse } = await respondToRfq( makerCvg, rfq, - undefined, + 220_111, 150_123, + undefined, 5 ); expect(rfqResponse).toHaveProperty('address'); @@ -220,8 +224,6 @@ describe('integration.psyoptionsAmerican', () => { overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupAmerican(takerCvg, rfqResponse); - await setupAmerican(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -240,34 +242,60 @@ describe('integration.psyoptionsAmerican', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('open size american call Spread [2-way]', async () => { - const { rfq } = await createAmericanOpenSizeCallSpdOptionRfq( + it('fixed-size american straddle [2-way]', async () => { + const { rfq } = await createAmericanFixedBaseStraddle( takerCvg, 'two-way', baseMint, quoteMint ); expect(rfq).toHaveProperty('address'); - const { rfqResponse } = await respondToRfq( + const { rfqResponse } = await respondToRfq(makerCvg, rfq, 61_222, 60_123); + expect(rfqResponse).toHaveProperty('address'); + const { response: confirmResponse } = await takerCvg + .rfqs() + .confirmResponse({ + rfq: rfq.address, + response: rfqResponse.address, + side: 'ask', + }); + expect(confirmResponse).toHaveProperty('signature'); + const takerResponse = await prepareRfqSettlement( + takerCvg, + rfq, + rfqResponse + ); + expect(takerResponse.response).toHaveProperty('signature'); + + const makerResponse = await prepareRfqSettlement( makerCvg, rfq, - 220_111, - 150_123, - 5 + rfqResponse ); - expect(rfqResponse).toHaveProperty('address'); + expect(makerResponse.response).toHaveProperty('signature'); + + const settlementResponse = await settleRfq(takerCvg, rfq, rfqResponse); + expect(settlementResponse.response).toHaveProperty('signature'); + }); + it('fixed-size american iron Condor [buy]', async () => { + const { rfq } = await createAmericanIronCondor( + takerCvg, + 'sell', + baseMint, + quoteMint + ); + expect(rfq).toHaveProperty('address'); + const { rfqResponse } = await respondToRfq(makerCvg, rfq, 61_222); + expect(rfqResponse).toHaveProperty('address'); const { response: confirmResponse } = await takerCvg .rfqs() .confirmResponse({ rfq: rfq.address, response: rfqResponse.address, - side: 'ask', - overrideLegMultiplier: 4, + side: 'bid', }); expect(confirmResponse).toHaveProperty('signature'); - await setupAmerican(takerCvg, rfqResponse); - await setupAmerican(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, diff --git a/packages/js/tests/integration/psyoptionsEuropean.spec.ts b/packages/js/tests/integration/psyoptionsEuropean.spec.ts index 54ee6ea70..e038ae827 100644 --- a/packages/js/tests/integration/psyoptionsEuropean.spec.ts +++ b/packages/js/tests/integration/psyoptionsEuropean.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'expect'; - -import { Mint } from '@solana/spl-token'; +import { Program, AnchorProvider } from '@project-serum/anchor'; +import { Mint } from '../../src/plugins/tokenModule'; import { prepareRfqSettlement, respondToRfq, @@ -8,17 +8,21 @@ import { createUserCvg, createEuropeanCoveredCallRfq, createEuropeanOpenSizeCallSpdOptionRfq, - setupEuropean, createEuropeanFixedBaseStraddle, + createEuropeanIronCondor, + createPythPriceFeed, } from '../helpers'; import { BASE_MINT_BTC_PK, QUOTE_MINT_PK } from '../constants'; +import { PublicKey } from '../../src'; +import { CvgWallet } from '../../src/utils/Wallets'; +import { IDL as PseudoPythIdl } from '../../../validator/fixtures/programs/pseudo_pyth_idl'; describe('integration.psyoptionsEuropean', () => { const takerCvg = createUserCvg('taker'); const makerCvg = createUserCvg('maker'); let baseMint: Mint; let quoteMint: Mint; - + let oracle: PublicKey; before(async () => { baseMint = await takerCvg .tokens() @@ -26,14 +30,24 @@ describe('integration.psyoptionsEuropean', () => { quoteMint = await takerCvg .tokens() .findMintByAddress({ address: QUOTE_MINT_PK }); + oracle = await createPythPriceFeed( + new Program( + PseudoPythIdl, + new PublicKey('FsJ3A3u2vn5cTVofAjvy6y5kwABJAqYWpe4975bi2epH'), + new AnchorProvider(takerCvg.connection, new CvgWallet(takerCvg), {}) + ), + 17_000, + quoteMint.decimals * -1 + ); }); - it('covered call [sell]', async () => { + it('european covered call [sell]', async () => { const { rfq, response } = await createEuropeanCoveredCallRfq( takerCvg, 'sell', baseMint, - quoteMint + quoteMint, + oracle ); expect(rfq).toHaveProperty('address'); @@ -45,20 +59,19 @@ describe('integration.psyoptionsEuropean', () => { response: rfqResponse.address, side: 'bid', }); - - await setupEuropean(takerCvg, rfqResponse); - await prepareRfqSettlement(makerCvg, rfq, rfqResponse); await prepareRfqSettlement(takerCvg, rfq, rfqResponse); await settleRfq(takerCvg, rfq, rfqResponse); }); + it('fixed-size european straddle [buy]', async () => { const { rfq } = await createEuropeanFixedBaseStraddle( takerCvg, 'buy', baseMint, - quoteMint + quoteMint, + oracle ); expect(rfq).toHaveProperty('address'); const { rfqResponse } = await respondToRfq( @@ -76,8 +89,6 @@ describe('integration.psyoptionsEuropean', () => { side: 'ask', }); expect(confirmResponse).toHaveProperty('signature'); - await setupEuropean(takerCvg, rfqResponse); - await setupEuropean(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -96,31 +107,34 @@ describe('integration.psyoptionsEuropean', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('fixed-size european straddle [sell]', async () => { - const { rfq } = await createEuropeanFixedBaseStraddle( + it('open size european call Spread [buy]', async () => { + const { rfq } = await createEuropeanOpenSizeCallSpdOptionRfq( takerCvg, - 'sell', + 'buy', baseMint, - quoteMint + quoteMint, + oracle ); expect(rfq).toHaveProperty('address'); const { rfqResponse } = await respondToRfq( makerCvg, rfq, - 55_133, - undefined + undefined, + 150_123, + undefined, + 5 ); expect(rfqResponse).toHaveProperty('address'); + const { response: confirmResponse } = await takerCvg .rfqs() .confirmResponse({ rfq: rfq.address, response: rfqResponse.address, - side: 'bid', + side: 'ask', + overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupEuropean(takerCvg, rfqResponse); - await setupEuropean(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -139,26 +153,30 @@ describe('integration.psyoptionsEuropean', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('fixed-size european straddle [2-way]', async () => { + it('fixed-size european straddle [sell]', async () => { const { rfq } = await createEuropeanFixedBaseStraddle( takerCvg, - 'two-way', + 'sell', baseMint, - quoteMint + quoteMint, + oracle ); expect(rfq).toHaveProperty('address'); - const { rfqResponse } = await respondToRfq(makerCvg, rfq, 61_222, 60_123); + const { rfqResponse } = await respondToRfq( + makerCvg, + rfq, + 55_133, + undefined + ); expect(rfqResponse).toHaveProperty('address'); const { response: confirmResponse } = await takerCvg .rfqs() .confirmResponse({ rfq: rfq.address, response: rfqResponse.address, - side: 'ask', + side: 'bid', }); expect(confirmResponse).toHaveProperty('signature'); - await setupEuropean(takerCvg, rfqResponse); - await setupEuropean(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -177,19 +195,21 @@ describe('integration.psyoptionsEuropean', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('open size european call Spread [buy]', async () => { + it('open size european call Spread [2-way]', async () => { const { rfq } = await createEuropeanOpenSizeCallSpdOptionRfq( takerCvg, - 'buy', + 'two-way', baseMint, - quoteMint + quoteMint, + oracle ); expect(rfq).toHaveProperty('address'); const { rfqResponse } = await respondToRfq( makerCvg, rfq, - undefined, + 220_111, 150_123, + undefined, 5 ); expect(rfqResponse).toHaveProperty('address'); @@ -203,8 +223,6 @@ describe('integration.psyoptionsEuropean', () => { overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupEuropean(takerCvg, rfqResponse); - await setupEuropean(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -223,34 +241,25 @@ describe('integration.psyoptionsEuropean', () => { expect(settlementResponse.response).toHaveProperty('signature'); }); - it('open size european call Spread [2-way]', async () => { - const { rfq } = await createEuropeanOpenSizeCallSpdOptionRfq( + it('fixed-size european straddle [2-way]', async () => { + const { rfq } = await createEuropeanFixedBaseStraddle( takerCvg, 'two-way', baseMint, - quoteMint + quoteMint, + oracle ); expect(rfq).toHaveProperty('address'); - const { rfqResponse } = await respondToRfq( - makerCvg, - rfq, - 220_111, - 150_123, - 5 - ); + const { rfqResponse } = await respondToRfq(makerCvg, rfq, 61_222, 60_123); expect(rfqResponse).toHaveProperty('address'); - const { response: confirmResponse } = await takerCvg .rfqs() .confirmResponse({ rfq: rfq.address, response: rfqResponse.address, side: 'ask', - overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupEuropean(takerCvg, rfqResponse); - await setupEuropean(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, rfq, @@ -274,7 +283,8 @@ describe('integration.psyoptionsEuropean', () => { takerCvg, 'sell', baseMint, - quoteMint + quoteMint, + oracle ); expect(rfq).toHaveProperty('address'); const { rfqResponse } = await respondToRfq( @@ -282,6 +292,7 @@ describe('integration.psyoptionsEuropean', () => { rfq, 150_123, undefined, + undefined, 5 ); expect(rfqResponse).toHaveProperty('address'); @@ -295,8 +306,6 @@ describe('integration.psyoptionsEuropean', () => { overrideLegMultiplier: 4, }); expect(confirmResponse).toHaveProperty('signature'); - await setupEuropean(takerCvg, rfqResponse); - await setupEuropean(makerCvg, rfqResponse); const takerResponse = await prepareRfqSettlement( takerCvg, @@ -315,4 +324,41 @@ describe('integration.psyoptionsEuropean', () => { const settlementResponse = await settleRfq(takerCvg, rfq, rfqResponse); expect(settlementResponse.response).toHaveProperty('signature'); }); + + it('fixed-size european iron Condor [buy]', async () => { + const { rfq } = await createEuropeanIronCondor( + takerCvg, + 'sell', + baseMint, + quoteMint, + oracle + ); + expect(rfq).toHaveProperty('address'); + const { rfqResponse } = await respondToRfq(makerCvg, rfq, 61_222); + expect(rfqResponse).toHaveProperty('address'); + const { response: confirmResponse } = await takerCvg + .rfqs() + .confirmResponse({ + rfq: rfq.address, + response: rfqResponse.address, + side: 'bid', + }); + expect(confirmResponse).toHaveProperty('signature'); + const takerResponse = await prepareRfqSettlement( + takerCvg, + rfq, + rfqResponse + ); + expect(takerResponse.response).toHaveProperty('signature'); + + const makerResponse = await prepareRfqSettlement( + makerCvg, + rfq, + rfqResponse + ); + expect(makerResponse.response).toHaveProperty('signature'); + + const settlementResponse = await settleRfq(takerCvg, rfq, rfqResponse); + expect(settlementResponse.response).toHaveProperty('signature'); + }); }); diff --git a/packages/js/tests/unit/response.spec.ts b/packages/js/tests/unit/response.spec.ts index 08085665e..adbda7e88 100644 --- a/packages/js/tests/unit/response.spec.ts +++ b/packages/js/tests/unit/response.spec.ts @@ -1,7 +1,13 @@ import { expect } from 'expect'; -import { Rfq } from '../../src'; -import { createUserCvg, createRfq, respondToRfq } from '../helpers'; +import { Rfq, convertTimestampToSeconds } from '../../src'; +import { + createUserCvg, + createRfq, + respondToRfq, + sleep, + expectError, +} from '../helpers'; describe('unit.response', () => { const makerCvg = createUserCvg('maker'); @@ -219,4 +225,36 @@ describe('unit.response', () => { expect(r.rfq.toBase58()).toEqual(rfq0.address.toBase58()) ); }); + + it('Cannot confirm Response if response is expired', async () => { + const rfq = await createRfq(takerCvg, amount0, 'buy', 10); + + const res = await respondToRfq( + makerCvg, + rfq.rfq, + undefined, + amount1, + convertTimestampToSeconds(Date.now()) + 2 + ); + + await sleep(2); + + await expectError( + takerCvg.rfqs().confirmResponse({ + response: res.rfqResponse.address, + rfq: rfq.rfq.address, + side: 'ask', + }), + 'Response is expired' + ); + + await makerCvg.rfqs().unlockResponseCollateral({ + response: res.rfqResponse.address, + }); + + await makerCvg.rfqs().cleanUpResponse({ + response: res.rfqResponse.address, + maker: makerCvg.identity().publicKey, + }); + }); }); diff --git a/packages/js/tests/unit/responseStateAndAction.spec.ts b/packages/js/tests/unit/responseStateAndAction.spec.ts new file mode 100644 index 000000000..41ca950aa --- /dev/null +++ b/packages/js/tests/unit/responseStateAndAction.spec.ts @@ -0,0 +1,326 @@ +import { expect } from 'expect'; +import { Response } from '../../src/plugins/rfqModule/models/Response'; +import { + Mint, + SpotLegInstrument, + SpotQuoteInstrument, + FixedSize, +} from '../../src'; +import { createUserCvg, sleep } from '../helpers'; +import { BASE_MINT_BTC_PK, QUOTE_MINT_PK } from '../constants'; + +describe('unit.responseStateAndAction', () => { + const takerCvg = createUserCvg('taker'); + const makerCvg = createUserCvg('maker'); + + let baseMintBTC: Mint; + let quoteMint: Mint; + + before(async () => { + baseMintBTC = await takerCvg + .tokens() + .findMintByAddress({ address: BASE_MINT_BTC_PK }); + quoteMint = await takerCvg + .tokens() + .findMintByAddress({ address: QUOTE_MINT_PK }); + }); + + it('[Cancel, UnlockCollateral, Cleanup, Cancelled]', async () => { + let refreshedResponse: Response; + const fixedSize: FixedSize = { + type: 'fixed-base', + amount: 19.653_038_331, + }; + + const { rfq } = await takerCvg.rfqs().createAndFinalize({ + instruments: [ + await SpotLegInstrument.create(takerCvg, baseMintBTC, 5.12, 'long'), + ], + orderType: 'sell', + quoteAsset: await SpotQuoteInstrument.create(takerCvg, quoteMint), + fixedSize, + }); + const { rfqResponse } = await makerCvg.rfqs().respond({ + rfq: rfq.address, + bid: { + price: 12000, + }, + }); + + //Cancel for maker + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: rfqResponse, + rfq, + caller: 'maker', + responseSide: 'bid', + }).responseAction + ).toBe('Cancel'); + + await makerCvg.rfqs().cancelResponse({ response: rfqResponse.address }); + refreshedResponse = await makerCvg.rfqs().findResponseByAddress({ + address: rfqResponse.address, + }); + + //Cancelled for taker + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: refreshedResponse, + rfq, + caller: 'taker', + responseSide: 'bid', + }).responseState + ).toBe('Cancelled'); + + //Unlock Response Collateral for maker + refreshedResponse = await makerCvg.rfqs().findResponseByAddress({ + address: rfqResponse.address, + }); + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: refreshedResponse, + rfq, + caller: 'maker', + responseSide: 'bid', + }).responseAction + ).toBe('UnlockCollateral'); + await makerCvg.rfqs().unlockResponseCollateral({ + response: refreshedResponse.address, + }); + + //Cleanup for maker + refreshedResponse = await makerCvg.rfqs().findResponseByAddress({ + address: rfqResponse.address, + }); + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: refreshedResponse, + rfq, + caller: 'maker', + responseSide: 'bid', + }).responseAction + ).toBe('Cleanup'); + + await makerCvg.rfqs().cleanUpResponse({ + response: rfqResponse.address, + maker: makerCvg.identity().publicKey, + }); + }); + + it('[Approve, Settle, Settled]', async () => { + let refreshedResponse: Response; + const fixedSize: FixedSize = { + type: 'fixed-base', + amount: 19.653_038_331, + }; + + const { rfq } = await takerCvg.rfqs().createAndFinalize({ + instruments: [ + await SpotLegInstrument.create(takerCvg, baseMintBTC, 5.12, 'long'), + ], + orderType: 'sell', + quoteAsset: await SpotQuoteInstrument.create(takerCvg, quoteMint), + fixedSize, + }); + const { rfqResponse } = await makerCvg.rfqs().respond({ + rfq: rfq.address, + bid: { + price: 12000, + }, + }); + + //Approve for taker + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: rfqResponse, + rfq, + caller: 'taker', + responseSide: 'bid', + }).responseAction + ).toBe('Approve'); + + await takerCvg.rfqs().confirmResponse({ + response: rfqResponse.address, + side: 'bid', + rfq: rfq.address, + }); + + //Settle for maker + refreshedResponse = await makerCvg.rfqs().findResponseByAddress({ + address: rfqResponse.address, + }); + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: refreshedResponse, + rfq, + caller: 'maker', + responseSide: 'bid', + }).responseAction + ).toBe('Settle'); + + //Settle for taker + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: refreshedResponse, + rfq, + caller: 'taker', + responseSide: 'bid', + }).responseAction + ).toBe('Settle'); + + await takerCvg.rfqs().prepareSettlement({ + response: rfqResponse.address, + rfq: rfq.address, + legAmountToPrepare: rfq.legs.length, + }); + + await makerCvg.rfqs().prepareSettlement({ + response: rfqResponse.address, + rfq: rfq.address, + legAmountToPrepare: rfq.legs.length, + }); + + await takerCvg.rfqs().settle({ + response: rfqResponse.address, + rfq: rfq.address, + maker: makerCvg.identity().publicKey, + taker: takerCvg.identity().publicKey, + }); + + //Settled for maker + refreshedResponse = await makerCvg.rfqs().findResponseByAddress({ + address: rfqResponse.address, + }); + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: refreshedResponse, + rfq, + caller: 'maker', + responseSide: 'bid', + }).responseState + ).toBe('Settled'); + }); + + it('[Expired]', async () => { + const fixedSize: FixedSize = { + type: 'fixed-base', + amount: 19.653_038_331, + }; + + const { rfq } = await takerCvg.rfqs().createAndFinalize({ + instruments: [ + await SpotLegInstrument.create(takerCvg, baseMintBTC, 5.12, 'long'), + ], + orderType: 'sell', + quoteAsset: await SpotQuoteInstrument.create(takerCvg, quoteMint), + fixedSize, + activeWindow: 2, + settlingWindow: 1, + }); + const { rfqResponse } = await makerCvg.rfqs().respond({ + rfq: rfq.address, + bid: { + price: 12000, + }, + }); + await sleep(3); + + //Expired for taker + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: rfqResponse, + rfq, + caller: 'taker', + responseSide: 'bid', + }).responseState + ).toBe('Expired'); + }); + + it('[Rejected, Defaulted]', async () => { + let refreshedResponse: Response; + const fixedSize: FixedSize = { + type: 'fixed-base', + amount: 19.653_038_331, + }; + + const { rfq } = await takerCvg.rfqs().createAndFinalize({ + instruments: [ + await SpotLegInstrument.create(takerCvg, baseMintBTC, 5.12, 'long'), + ], + orderType: 'two-way', + quoteAsset: await SpotQuoteInstrument.create(takerCvg, quoteMint), + activeWindow: 2, + settlingWindow: 1, + fixedSize, + }); + const { rfqResponse } = await makerCvg.rfqs().respond({ + rfq: rfq.address, + bid: { + price: 12000, + }, + ask: { + price: 21000, + }, + }); + + await takerCvg.rfqs().confirmResponse({ + response: rfqResponse.address, + side: 'bid', + rfq: rfq.address, + }); + + refreshedResponse = await makerCvg.rfqs().findResponseByAddress({ + address: rfqResponse.address, + }); + + //Settle for taker + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: refreshedResponse, + rfq, + caller: 'taker', + responseSide: 'bid', + }).responseAction + ).toBe('Settle'); + + //Rejected for maker + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: refreshedResponse, + rfq, + caller: 'taker', + responseSide: 'ask', + }).responseState + ).toBe('Rejected'); + + await takerCvg.rfqs().prepareSettlement({ + response: rfqResponse.address, + rfq: rfq.address, + legAmountToPrepare: rfq.legs.length, + }); + + await sleep(3); + + refreshedResponse = await makerCvg.rfqs().findResponseByAddress({ + address: rfqResponse.address, + }); + //MakerDefaulted for taker + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: refreshedResponse, + rfq, + caller: 'taker', + responseSide: 'bid', + }).responseState + ).toBe('MakerDefaulted'); + //MakerDefaulted for maker + expect( + takerCvg.rfqs().getResponseStateAndAction({ + response: refreshedResponse, + rfq, + caller: 'maker', + responseSide: 'bid', + }).responseState + ).toBe('MakerDefaulted'); + }); +}); diff --git a/packages/js/tests/unit/rfq.spec.ts b/packages/js/tests/unit/rfq.spec.ts index fb7b3d919..9fd4b571f 100644 --- a/packages/js/tests/unit/rfq.spec.ts +++ b/packages/js/tests/unit/rfq.spec.ts @@ -55,12 +55,15 @@ describe('unit.rfq', () => { expect(rfqs.length).toBeGreaterThan(0); }); - // TODO ADD getRfqState function + // TODO ADD getRfqStateAndAction function it('cancel', async () => { // Error Number: 6016. Error Message: Rfq is not in required state. const iterator: any = takerCvg.rfqs().findRfqs({}); const rfqs = (await getAll(iterator)).flat().filter((rfq: any) => { - return rfq.state === 'active' && rfq.totalResponses === 0; + return ( + takerCvg.rfqs().getRfqStateAndAction({ rfq, caller: 'taker' }) + .rfqAction === 'Cancel' + ); }); expect(rfqs.length).toBeGreaterThan(0); const { responses } = await takerCvg @@ -72,7 +75,10 @@ describe('unit.rfq', () => { it('unlock', async () => { const iterator: any = takerCvg.rfqs().findRfqs({}); const rfqsBefore = (await getAll(iterator)).flat().filter((rfq: any) => { - return rfq.state === 'canceled' && rfq.totalResponses === 0; + return ( + takerCvg.rfqs().getRfqStateAndAction({ rfq, caller: 'taker' }) + .rfqAction === 'UnlockCollateral' + ); }); expect(rfqsBefore.length).toBeGreaterThan(0); const { responses } = await takerCvg.rfqs().unlockRfqsCollateral({ @@ -80,7 +86,10 @@ describe('unit.rfq', () => { }); expect(responses.length).toBe(rfqsBefore.length); const rfqsAfter = (await getAll(iterator)).flat().filter((rfq: any) => { - return rfq.state === 'canceled' && rfq.totalResponses === 0; + return ( + takerCvg.rfqs().getRfqStateAndAction({ rfq, caller: 'taker' }) + .rfqAction === 'Cleanup' + ); }); rfqsAfter.map((rfq: any) => { expect(rfq.totalTakerCollateralLocked).toBe(0); @@ -90,7 +99,10 @@ describe('unit.rfq', () => { it('clean up', async () => { const iterator: any = takerCvg.rfqs().findRfqs({}); const rfqs = (await getAll(iterator)).flat().filter((rfq: any) => { - return rfq.state === 'canceled'; + return ( + takerCvg.rfqs().getRfqStateAndAction({ rfq, caller: 'taker' }) + .rfqAction === 'Cleanup' + ); }); expect(rfqs.length).toBeGreaterThan(0); await takerCvg.rfqs().cleanUpRfqs({ diff --git a/packages/js/tests/unit/rfqStateAndAction.spec.ts b/packages/js/tests/unit/rfqStateAndAction.spec.ts new file mode 100644 index 000000000..a65806413 --- /dev/null +++ b/packages/js/tests/unit/rfqStateAndAction.spec.ts @@ -0,0 +1,204 @@ +import { expect } from 'expect'; +import { + Mint, + SpotLegInstrument, + SpotQuoteInstrument, + FixedSize, + Rfq, +} from '../../src'; +import { createUserCvg, sleep } from '../helpers'; +import { BASE_MINT_BTC_PK, QUOTE_MINT_PK } from '../constants'; + +describe('unit.rfqStateAndAction', () => { + const takerCvg = createUserCvg('taker'); + const makerCvg = createUserCvg('maker'); + + let baseMintBTC: Mint; + let quoteMint: Mint; + + before(async () => { + baseMintBTC = await takerCvg + .tokens() + .findMintByAddress({ address: BASE_MINT_BTC_PK }); + quoteMint = await takerCvg + .tokens() + .findMintByAddress({ address: QUOTE_MINT_PK }); + }); + + it('[Cancel, UnlockCollateral, Cleanup, Respond, Null]', async () => { + let refreshedRfq: Rfq; + const fixedSize: FixedSize = { + type: 'fixed-base', + amount: 19.65, + }; + const { rfq } = await takerCvg.rfqs().createAndFinalize({ + instruments: [ + await SpotLegInstrument.create(takerCvg, baseMintBTC, 5.12, 'long'), + ], + orderType: 'sell', + quoteAsset: await SpotQuoteInstrument.create(takerCvg, quoteMint), + fixedSize, + }); + + //Cancel for taker + expect( + takerCvg.rfqs().getRfqStateAndAction({ + rfq, + caller: 'taker', + }).rfqAction + ).toBe('Cancel'); + + // Respond for maker + expect( + takerCvg.rfqs().getRfqStateAndAction({ + rfq, + caller: 'maker', + }).rfqAction + ).toBe('Respond'); + + await takerCvg.rfqs().cancelRfq({ rfq: rfq.address }); + refreshedRfq = await makerCvg.rfqs().findRfqByAddress({ + address: rfq.address, + }); + + //UnlockCollateral for taker + expect( + takerCvg.rfqs().getRfqStateAndAction({ + rfq: refreshedRfq, + caller: 'taker', + }).rfqAction + ).toBe('UnlockCollateral'); + + //null for maker + expect( + takerCvg.rfqs().getRfqStateAndAction({ + rfq: refreshedRfq, + caller: 'maker', + }).rfqAction + ).toBe(null); + + await takerCvg.rfqs().unlockRfqCollateral({ + rfq: rfq.address, + }); + refreshedRfq = await makerCvg.rfqs().findRfqByAddress({ + address: rfq.address, + }); + + //Cleanup for taker + expect( + takerCvg.rfqs().getRfqStateAndAction({ + rfq: refreshedRfq, + caller: 'taker', + }).rfqAction + ).toBe('Cleanup'); + }); + + it('[FinalizeConstruction, Responses]', async () => { + let refreshedRfq: Rfq; + const fixedSize: FixedSize = { + type: 'fixed-base', + amount: 12.122, + }; + const { rfq } = await takerCvg.rfqs().create({ + instruments: [ + await SpotLegInstrument.create(takerCvg, baseMintBTC, 5.12, 'long'), + ], + orderType: 'sell', + quoteAsset: await SpotQuoteInstrument.create(takerCvg, quoteMint), + fixedSize, + }); + + //FinalizeConstruction for taker + expect( + takerCvg.rfqs().getRfqStateAndAction({ + rfq, + caller: 'taker', + }).rfqAction + ).toBe('FinalizeConstruction'); + + await takerCvg.rfqs().finalizeRfqConstruction({ + rfq: rfq.address, + }); + refreshedRfq = await takerCvg + .rfqs() + .findRfqByAddress({ address: rfq.address }); + + //Cancel for taker + expect( + takerCvg.rfqs().getRfqStateAndAction({ + rfq: refreshedRfq, + caller: 'taker', + }).rfqAction + ).toBe('Cancel'); + + await makerCvg.rfqs().respond({ + rfq: rfq.address, + bid: { + price: 12000, + }, + }); + + refreshedRfq = await takerCvg.rfqs().findRfqByAddress({ + address: rfq.address, + }); + + //Responses for taker + expect( + takerCvg.rfqs().getRfqStateAndAction({ + rfq: refreshedRfq, + caller: 'taker', + }).rfqAction + ).toBe('ViewResponses'); + }); + + it('[UnlockCollateral,Cleanup] when rfq is expired', async () => { + const fixedSize: FixedSize = { + type: 'fixed-base', + amount: 1, + }; + const { rfq } = await takerCvg.rfqs().createAndFinalize({ + instruments: [ + await SpotLegInstrument.create(takerCvg, baseMintBTC, 12.1, 'long'), + ], + orderType: 'sell', + quoteAsset: await SpotQuoteInstrument.create(takerCvg, quoteMint), + activeWindow: 3, + settlingWindow: 3, + fixedSize, + }); + + await sleep(4); + + //UnlockCollateral for taker + expect( + takerCvg.rfqs().getRfqStateAndAction({ + rfq, + caller: 'taker', + }).rfqAction + ).toBe('UnlockCollateral'); + + //null for maker + expect( + takerCvg.rfqs().getRfqStateAndAction({ + rfq, + caller: 'maker', + }).rfqAction + ).toBe(null); + + await takerCvg.rfqs().unlockRfqCollateral({ + rfq: rfq.address, + }); + + const refreshedRfq = await makerCvg.rfqs().findRfqByAddress({ + address: rfq.address, + }); + + //Cleanup for taker + expect( + takerCvg.rfqs().getRfqStateAndAction({ + rfq: refreshedRfq, + caller: 'taker', + }).rfqAction + ).toBe('Cleanup'); + }); +}); diff --git a/packages/js/tests/unit/settlementResult.spec.ts b/packages/js/tests/unit/settlementResult.spec.ts index d784c36bc..2a87526cd 100644 --- a/packages/js/tests/unit/settlementResult.spec.ts +++ b/packages/js/tests/unit/settlementResult.spec.ts @@ -1,12 +1,11 @@ import expect from 'expect'; -import { Mint } from '@solana/spl-token'; import { createAmericanCoveredCallRfq, - createEuropeanCoveredCallRfq, createRfq, createUserCvg, respondToRfq, } from '../helpers'; +import { Mint } from '../../src'; import { BASE_MINT_BTC_PK, QUOTE_MINT_DECIMALS, @@ -90,6 +89,7 @@ describe('unit.settlementResult', () => { takerCvg, quoteAmount, 'buy', + undefined, 'fixed-quote' ); expect(rfq).toHaveProperty('address'); @@ -136,13 +136,20 @@ describe('unit.settlementResult', () => { const baseAmount = 1; const quoteAmount = 12_300.9783; - const { rfq } = await createRfq(takerCvg, baseAmount, 'buy', 'open'); + const { rfq } = await createRfq( + takerCvg, + baseAmount, + 'buy', + undefined, + 'open' + ); expect(rfq).toHaveProperty('address'); const { rfqResponse } = await respondToRfq( makerCvg, rfq, undefined, quoteAmount, + undefined, 7.456 ); expect(rfqResponse).toHaveProperty('address'); @@ -182,13 +189,20 @@ describe('unit.settlementResult', () => { const baseAmount = 1; const quoteAmount = 70_999.97; - const { rfq } = await createRfq(takerCvg, baseAmount, 'sell', 'open'); + const { rfq } = await createRfq( + takerCvg, + baseAmount, + 'sell', + undefined, + 'open' + ); expect(rfq).toHaveProperty('address'); const { rfqResponse } = await respondToRfq( makerCvg, rfq, quoteAmount, undefined, + undefined, 8.456123456 ); const responseResult = takerCvg.rfqs().getSettlementResult({ @@ -366,8 +380,8 @@ describe('unit.settlementResult', () => { ], }); }); - it('fixed-base european covered call', async () => { - const { rfq, response } = await createEuropeanCoveredCallRfq( + it('fixed-base american covered call', async () => { + const { response, rfq } = await createAmericanCoveredCallRfq( takerCvg, 'sell', baseMint, diff --git a/packages/validator/fixtures/accounts/rfq-base-asset-btc.json b/packages/validator/fixtures/accounts/rfq-base-asset-btc.json index b05dd7bd4..693a2d8b9 100644 --- a/packages/validator/fixtures/accounts/rfq-base-asset-btc.json +++ b/packages/validator/fixtures/accounts/rfq-base-asset-btc.json @@ -1,12 +1,12 @@ { - "pubkey": "DcP8g86f7WxKVmFNUm1o7bPDbmj3hkNoWhkwRpVyZV11", + "pubkey": "D3AV2d2Dpf54BPqvJ7pNfuzWboFFY4tNH8U6Me5Jz44y", "account": { "lamports": 2804880, "data": [ "A/5kN3P/Bxf/AAABAABujEl3g94AWWT2ktFbxNUQIrF1jMwAhCmERj8Lf9/+nwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAEJUQwAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "base64" ], - "owner": "CeYwCe6YwBvRE9CpRU2Zgc5oQP7r2ThNqicyKN37Unn4", + "owner": "J7WkE9mTzwTo3pjxENdrH7sekZPrN2VYNpk1pgfZxVr9", "executable": false, "rentEpoch": 0 } diff --git a/packages/validator/fixtures/accounts/rfq-base-asset-eth.json b/packages/validator/fixtures/accounts/rfq-base-asset-eth.json index 9baeabe9a..d9fdd78e4 100644 --- a/packages/validator/fixtures/accounts/rfq-base-asset-eth.json +++ b/packages/validator/fixtures/accounts/rfq-base-asset-eth.json @@ -1,12 +1,12 @@ { - "pubkey": "439cjNap8aRJ3BgRc5G3eHEtEJMzbDhRQSZvjpnGbcFV", + "pubkey": "CjN93gP6pPLNyEmKaSbkikSQuw9TY5Xy8MoqE5UCwDPR", "account": { "lamports": 2804880, "data": [ - "A/5kN3P/Bxf9AgABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAn0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAEVUSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "A/5kN3P/Bxf/AgABAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAn0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAEVUSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "base64" ], - "owner": "CeYwCe6YwBvRE9CpRU2Zgc5oQP7r2ThNqicyKN37Unn4", + "owner": "J7WkE9mTzwTo3pjxENdrH7sekZPrN2VYNpk1pgfZxVr9", "executable": false, "rentEpoch": 0 } diff --git a/packages/validator/fixtures/accounts/rfq-base-asset-sol.json b/packages/validator/fixtures/accounts/rfq-base-asset-sol.json index 57f65c090..363e2a2c4 100644 --- a/packages/validator/fixtures/accounts/rfq-base-asset-sol.json +++ b/packages/validator/fixtures/accounts/rfq-base-asset-sol.json @@ -1,12 +1,12 @@ { - "pubkey": "8x7hjf2mnwTeayV86caKAQBzEXc6zMC3z17XjDWEzffD", + "pubkey": "FkyRgBpQZNuxgYZpnrg6bg1s3TTSN7fmyoELcFQwmacD", "account": { "lamports": 2804880, "data": [ - "A/5kN3P/Bxf4AQABAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO8Ni2/aLOukHaFdQJXR2jkqDS+O0MbHvA9M+sjCgLVtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAFNPTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "A/5kN3P/Bxf8AQABAgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAO8Ni2/aLOukHaFdQJXR2jkqDS+O0MbHvA9M+sjCgLVtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAFNPTAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "base64" ], - "owner": "CeYwCe6YwBvRE9CpRU2Zgc5oQP7r2ThNqicyKN37Unn4", + "owner": "J7WkE9mTzwTo3pjxENdrH7sekZPrN2VYNpk1pgfZxVr9", "executable": false, "rentEpoch": 0 } diff --git a/packages/validator/fixtures/accounts/rfq-collateral-info-dao.json b/packages/validator/fixtures/accounts/rfq-collateral-info-dao.json index 9da58e3bd..9563912e3 100644 --- a/packages/validator/fixtures/accounts/rfq-collateral-info-dao.json +++ b/packages/validator/fixtures/accounts/rfq-collateral-info-dao.json @@ -1,12 +1,12 @@ { - "pubkey": "EES2vhXowsyNzCo7iRABtb2pjTC2CzRU3KoR4iNzWaNM", + "pubkey": "GwzpnXhzMNSgLdNJXKtyLB9ZbSiwFTDdomBh4UnVB5Q2", "account": { - "lamports": 1280640, + "lamports": 3062400, "data": [ - "o0RSJdyyEpn/ZJJsMbdIfexWonT9yyqMxu1HHDD0uX9BX7OXjafeIJn9AAAAAAAAAAAAAAAAAAA=", + "o0RSJdyyEpn7ZJJsMbdIfexWonT9yyqMxu1HHDD0uX9BX7OXjafeIJn/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "base64" ], - "owner": "CeYwCe6YwBvRE9CpRU2Zgc5oQP7r2ThNqicyKN37Unn4", + "owner": "J7WkE9mTzwTo3pjxENdrH7sekZPrN2VYNpk1pgfZxVr9", "executable": false, "rentEpoch": 0 } diff --git a/packages/validator/fixtures/accounts/rfq-collateral-info-maker.json b/packages/validator/fixtures/accounts/rfq-collateral-info-maker.json index 42a887a6a..8f133aac6 100644 --- a/packages/validator/fixtures/accounts/rfq-collateral-info-maker.json +++ b/packages/validator/fixtures/accounts/rfq-collateral-info-maker.json @@ -1,12 +1,12 @@ { - "pubkey": "Db2vmyiWT8DJReEG8AZHd6DiK3kTRw22NLY3BQWKiWqS", + "pubkey": "B8cMG9jrUgcUS2FdZfJLEnjFhL828WvhE8xTqfM1xDSK", "account": { - "lamports": 1280640, + "lamports": 3062400, "data": [ - "o0RSJdyyEpn+RGcv6WZ5nR0SQFCdXFvcEqmvImWuWJkw9t9ogVLKIbf6AAAAAAAAAAAAAAAAAAA=", + "o0RSJdyyEpn/RGcv6WZ5nR0SQFCdXFvcEqmvImWuWJkw9t9ogVLKIbf/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "base64" ], - "owner": "CeYwCe6YwBvRE9CpRU2Zgc5oQP7r2ThNqicyKN37Unn4", + "owner": "J7WkE9mTzwTo3pjxENdrH7sekZPrN2VYNpk1pgfZxVr9", "executable": false, "rentEpoch": 0 } diff --git a/packages/validator/fixtures/accounts/rfq-collateral-info-taker.json b/packages/validator/fixtures/accounts/rfq-collateral-info-taker.json index f96f3aa98..4a7f4a297 100644 --- a/packages/validator/fixtures/accounts/rfq-collateral-info-taker.json +++ b/packages/validator/fixtures/accounts/rfq-collateral-info-taker.json @@ -1,12 +1,12 @@ { - "pubkey": "ABtYxAFisHFdrwL5P1nLhjP6KrtdX7Q1RPRWfNPAcoZX", + "pubkey": "5Xpck9sewCtS1KBECyHonaLErZT9C1Cnve11W5TWLRCH", "account": { - "lamports": 1280640, + "lamports": 3062400, "data": [ - "o0RSJdyyEpn+yxkDb26bL0vjVHOsyuI/i4PC7QTivY42lYqpvanXNlf9AAAAAAAAAAAAAAAAAAA=", + "o0RSJdyyEpn/yxkDb26bL0vjVHOsyuI/i4PC7QTivY42lYqpvanXNlf7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "base64" ], - "owner": "CeYwCe6YwBvRE9CpRU2Zgc5oQP7r2ThNqicyKN37Unn4", + "owner": "J7WkE9mTzwTo3pjxENdrH7sekZPrN2VYNpk1pgfZxVr9", "executable": false, "rentEpoch": 0 } diff --git a/packages/validator/fixtures/accounts/rfq-collateral-token-dao.json b/packages/validator/fixtures/accounts/rfq-collateral-token-dao.json index ce2e43bbc..21f9e1664 100644 --- a/packages/validator/fixtures/accounts/rfq-collateral-token-dao.json +++ b/packages/validator/fixtures/accounts/rfq-collateral-token-dao.json @@ -1,9 +1,9 @@ { - "pubkey": "B82kXR6yBP55btHCkmovhHRNYeRgJ4ApUrwGnzatieYR", + "pubkey": "6A4Zb6yNp8g7nfSY6kJPtrozDfKjJo1dAL1JQuoKpwHd", "account": { "lamports": 2039280, "data": [ - "aRq5Tm95/2xFlF3S5PgBarK9se95mD6qo+5+Z1sAZunEmMkjSnIdNss4oAE750laIRW48CVpsCkoDdP4hNFncgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "aRq5Tm95/2xFlF3S5PgBarK9se95mD6qo+5+Z1sAZuns9gyb560QSE+xCnX/2taeg+WJKxx/syFzd8sOLafudwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "base64" ], "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", diff --git a/packages/validator/fixtures/accounts/rfq-collateral-token-maker.json b/packages/validator/fixtures/accounts/rfq-collateral-token-maker.json index ff81b060e..66c05b7cd 100644 --- a/packages/validator/fixtures/accounts/rfq-collateral-token-maker.json +++ b/packages/validator/fixtures/accounts/rfq-collateral-token-maker.json @@ -1,9 +1,9 @@ { - "pubkey": "9RTWX9gkMtq8rHcTDiKDNyYDH2UUN9hjfYaEkL88ok8A", + "pubkey": "EK4QgzVVh3xCwhUSA7rYQuXXEw55jSnjhk6WRoqqKV2s", "account": { "lamports": 2039280, "data": [ - "aRq5Tm95/2xFlF3S5PgBarK9se95mD6qo+5+Z1sAZum7BCtv2Y9yiEGebEGabk9RlENLR7VJubTl09sUKyKbdQCAxqR+jQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "aRq5Tm95/2xFlF3S5PgBarK9se95mD6qo+5+Z1sAZumWh+eQKJUVQ3K7T57fvQBRB5ibV6Eu3TGmP3WqFZmsBACAxqR+jQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "base64" ], "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", diff --git a/packages/validator/fixtures/accounts/rfq-collateral-token-taker.json b/packages/validator/fixtures/accounts/rfq-collateral-token-taker.json index 2f86c4287..9d21e3ad6 100644 --- a/packages/validator/fixtures/accounts/rfq-collateral-token-taker.json +++ b/packages/validator/fixtures/accounts/rfq-collateral-token-taker.json @@ -1,9 +1,9 @@ { - "pubkey": "5GKbWheozXu5UdDWR7rh7NXCHtRmnhvqmwY6REsRDshS", + "pubkey": "2ChcGSXg2kB2UaPbicxyraTTTuPzaUppioSYiUungodw", "account": { "lamports": 2039280, "data": [ - "aRq5Tm95/2xFlF3S5PgBarK9se95mD6qo+5+Z1sAZumIg0d5OcJEuXgDkyXlsUy/Abr+OukNdDiQtWtxR00f3gCAxqR+jQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "aRq5Tm95/2xFlF3S5PgBarK9se95mD6qo+5+Z1sAZulDVAMKF8UfyoBiSzIX9GhRItPnJYfDvOYXpImeivHxdgCAxqR+jQMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "base64" ], "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", diff --git a/packages/validator/fixtures/accounts/rfq-mint-info-btc.json b/packages/validator/fixtures/accounts/rfq-mint-info-btc.json index 95302c85a..ce930aaa8 100644 --- a/packages/validator/fixtures/accounts/rfq-mint-info-btc.json +++ b/packages/validator/fixtures/accounts/rfq-mint-info-btc.json @@ -1,12 +1,12 @@ { - "pubkey": "2eoTJHVP1UuW9fSRaN7AjvBYyHRLcCqebJhHFzid6Ltd", + "pubkey": "46iUXCqyuVhoN9hpdETa6eyhbMDLQh3HjbpKS2FmygE7", "account": { "lamports": 2324640, "data": [ - "x3PV3dsdh67/1QdAbyji9GYRbBJEIDGa7a7Ri6BXDdP2m5O+Zgs5buYJAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "x3PV3dsdh6791QdAbyji9GYRbBJEIDGa7a7Ri6BXDdP2m5O+Zgs5buYJAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "base64" ], - "owner": "CeYwCe6YwBvRE9CpRU2Zgc5oQP7r2ThNqicyKN37Unn4", + "owner": "J7WkE9mTzwTo3pjxENdrH7sekZPrN2VYNpk1pgfZxVr9", "executable": false, "rentEpoch": 0 } diff --git a/packages/validator/fixtures/accounts/rfq-mint-info-eth.json b/packages/validator/fixtures/accounts/rfq-mint-info-eth.json index 8bddf5a5f..b31642f1f 100644 --- a/packages/validator/fixtures/accounts/rfq-mint-info-eth.json +++ b/packages/validator/fixtures/accounts/rfq-mint-info-eth.json @@ -1,12 +1,12 @@ { - "pubkey": "BfEQtEoXdnQyedHjjU1DAidDz9Qy3DwLA2jxE1oGaoM7", + "pubkey": "CzMVe1ZbzCp1BdRYezghVzBsXoiZgBAodbR33gu8WTFC", "account": { "lamports": 2324640, "data": [ - "x3PV3dsdh67+KOeZ7XiJHyjoRFaYKs+85ejjnJrjfLSWJ2Uf3N+pjqwJAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "x3PV3dsdh67/KOeZ7XiJHyjoRFaYKs+85ejjnJrjfLSWJ2Uf3N+pjqwJAQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "base64" ], - "owner": "CeYwCe6YwBvRE9CpRU2Zgc5oQP7r2ThNqicyKN37Unn4", + "owner": "J7WkE9mTzwTo3pjxENdrH7sekZPrN2VYNpk1pgfZxVr9", "executable": false, "rentEpoch": 0 } diff --git a/packages/validator/fixtures/accounts/rfq-mint-info-sol.json b/packages/validator/fixtures/accounts/rfq-mint-info-sol.json index fdb95519d..66c3e8262 100644 --- a/packages/validator/fixtures/accounts/rfq-mint-info-sol.json +++ b/packages/validator/fixtures/accounts/rfq-mint-info-sol.json @@ -1,12 +1,12 @@ { - "pubkey": "DZ8AjrQ8LZyrj8sMoDpCMDdxvnfDkPov3hesnahyx8i6", + "pubkey": "H75NkwkmBuom83nuqQwyuqQZQQuyue59frfmgc5QALXv", "account": { "lamports": 2324640, "data": [ "x3PV3dsdh67/KOWejuQHeNYncgTtjfJmQZMafgdeEmJ0ZWn5c9DFHpsJAQEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "base64" ], - "owner": "CeYwCe6YwBvRE9CpRU2Zgc5oQP7r2ThNqicyKN37Unn4", + "owner": "J7WkE9mTzwTo3pjxENdrH7sekZPrN2VYNpk1pgfZxVr9", "executable": false, "rentEpoch": 0 } diff --git a/packages/validator/fixtures/accounts/rfq-mint-info-usd-quote.json b/packages/validator/fixtures/accounts/rfq-mint-info-usd-quote.json index f91410f46..f9c21fd34 100644 --- a/packages/validator/fixtures/accounts/rfq-mint-info-usd-quote.json +++ b/packages/validator/fixtures/accounts/rfq-mint-info-usd-quote.json @@ -1,12 +1,12 @@ { - "pubkey": "8ras5A6ciiJCzB73KEU8zH6W2aFjh7mPdApZKGdjNjfp", + "pubkey": "2eDUTTPjPs8CusiikNDG4rWSZvaVPbodkJrU9bNDYiSX", "account": { "lamports": 2324640, "data": [ - "x3PV3dsdh678e2UTGuRioXUH1thtTs+0Qj7hdyxRnoHwm55RQpy8WkYJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "x3PV3dsdh67/e2UTGuRioXUH1thtTs+0Qj7hdyxRnoHwm55RQpy8WkYJAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "base64" ], - "owner": "CeYwCe6YwBvRE9CpRU2Zgc5oQP7r2ThNqicyKN37Unn4", + "owner": "J7WkE9mTzwTo3pjxENdrH7sekZPrN2VYNpk1pgfZxVr9", "executable": false, "rentEpoch": 0 } diff --git a/packages/validator/fixtures/accounts/rfq-protocol.json b/packages/validator/fixtures/accounts/rfq-protocol.json index 548124e37..9aefb2b96 100644 --- a/packages/validator/fixtures/accounts/rfq-protocol.json +++ b/packages/validator/fixtures/accounts/rfq-protocol.json @@ -1,12 +1,12 @@ { - "pubkey": "BRLhhsBrEwXeJD5w1rcrXT3PytZYvC11bmC7jz3rkf3q", + "pubkey": "9xcYm9iSMH5aw7dQQBb711wpy3fYLEPfsNhbiJM4yqkU", "account": { - "lamports": 15632160, + "lamports": 33895200, "data": [ - "ITOthiOMw/hkkmwxt0h97FaidP3LKozG7UccMPS5f0Ffs5eNp94gmf0BAC0xAQAAAACAlpgAAAAAAADh9QUAAAAAAGXNHQAAAABgoCyAOYQ76ND6k2SiZxBSfVfid13p2Qo1wMx4n7C9+GkauU5vef9sRZRd0uT4AWqyvbHveZg+qqPufmdbAGbpAwAAAPvzkKUjVr2RJ/MiwC5oaNwkPBdNtco8b6iIQ4az3uheAQEBBwMDBDWZWRFsvs8NEep+dyvZRK+4ZuKZjxi+fIj+DOdPvWrAAQACBwMDBHLr6unnoLJNB/EnW9DcdIB55M2IvZt72lrCYvRJQbtUAQADBwMDBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + "ITOthiOMw/hkkmwxt0h97FaidP3LKozG7UccMPS5f0Ffs5eNp94gmf4BAC0xAQAAAACAlpgAAAAAAADh9QUAAAAAAGXNHQAAAABXDOQgzydMH+ZRgmUiu2l19Vnyq1wVR35OLdD4mObdU2kauU5vef9sRZRd0uT4AWqyvbHveZg+qqPufmdbAGbpAwAAAK5NLOfqYWrHRlUywKtjfd8qcXIG2SdJ6CJsOcSdStteAQEBBwMDBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAh4qn7+zq+3cWnlFlgi9vA6gec0len/0P5uUkxxsJr6ABAAIHAwMEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABOtklH0CoWMHYr5p2cci65zXOxIf9IrHFBBI3NCo6bSwEAAwcDAwQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", "base64" ], - "owner": "CeYwCe6YwBvRE9CpRU2Zgc5oQP7r2ThNqicyKN37Unn4", + "owner": "J7WkE9mTzwTo3pjxENdrH7sekZPrN2VYNpk1pgfZxVr9", "executable": false, "rentEpoch": 0 } diff --git a/packages/validator/fixtures/accounts/risk-engine-config.json b/packages/validator/fixtures/accounts/risk-engine-config.json index a8bda25fb..9ac0d1cf9 100644 --- a/packages/validator/fixtures/accounts/risk-engine-config.json +++ b/packages/validator/fixtures/accounts/risk-engine-config.json @@ -1,12 +1,12 @@ { - "pubkey": "Hcu5LZCmxDEE3uYvs28F1VEW1NS29yGB7Av8rWmGF5Mu", + "pubkey": "5tpJhxJTEh49PH3Q1hhDG3nNjoeJ1vW1iCqVe8rqe83D", "account": { - "lamports": 21492480, + "lamports": 23274240, "data": [ - "mwyq4B76zIIAypo7AAAAAACUNXcAAAAACQAAAAAAAAB7FK5H4XqEP5qZmZmZmbk/AAPMEgAAAAB7FK5H4XqEP5qZmZmZmak/AAAAAAAA4D97FK5H4XqUP5qZmZmZmck/exSuR+F6pD8zMzMzMzPTP3sUrkfherQ/mpmZmZmZ2T+4HoXrUbi+PwAAAAAAAOA/mpmZmZmZyT8zMzMzMzPjPzMzMzMzM9M/ZmZmZmZm5j+amZmZmZmpP5qZmZmZmek/exSuR+F6pD+amZmZmZnZP3sUrkfherQ/MzMzMzMz4z97FK5H4XrEP5qZmZmZmek/uB6F61G4zj8AAAAAAADwP5qZmZmZmdk/MzMzMzMz8z8zMzMzMzPjP2ZmZmZmZvY/mpmZmZmZqT8zMzMzMzPzP7gehetRuK4/MzMzMzMz4z+4HoXrUbi+P83MzMzMzOw/uB6F61G4zj8zMzMzMzPzPwrXo3A9Ctc/AAAAAAAA+D8zMzMzMzPjP83MzMzMzPw/zczMzMzM7D/NzMzMzMwAQJqZmZmZmak/MzMzMzMzA0B7FK5H4Xq0P5qZmZmZmek/exSuR+F6xD8zMzMzMzPzP3sUrkfhetQ/mpmZmZmZ+T+4HoXrUbjePwAAAAAAAABAmpmZmZmZ6T8zMzMzMzMDQDMzMzMzM/M/ZmZmZmZmBkCamZmZmZmpPwAAAAAAABRAmpmZmZmZuT8AAAAAAADwP5qZmZmZmck/AAAAAAAA+D+amZmZmZnZPwAAAAAAAABAMzMzMzMz4z8AAAAAAAAEQAAAAAAAAPA/AAAAAAAACEAAAAAAAAD4PwAAAAAAAAxAmpmZmZmZqT8AAAAAAAAkQJqZmZmZmck/AAAAAAAA4D8zMzMzMzPTPwAAAAAAAOA/mpmZmZmZ2T8AAAAAAADgPwAAAAAAAOA/AAAAAAAA4D8zMzMzMzPjPwAAAAAAAOA/ZmZmZmZm5j8AAAAAAADgP5qZmZmZmak/AAAAAAAALkCamZmZmZnJPwAAAAAAAOA/MzMzMzMz0z8AAAAAAADgP5qZmZmZmdk/AAAAAAAA4D8AAAAAAADgPwAAAAAAAOA/MzMzMzMz4z8AAAAAAADgP2ZmZmZmZuY/AAAAAAAA4D+amZmZmZmpPwAAAAAAADRAmpmZmZmZyT8AAAAAAADgPzMzMzMzM9M/AAAAAAAA4D+amZmZmZnZPwAAAAAAAOA/AAAAAAAA4D8AAAAAAADgPzMzMzMzM+M/AAAAAAAA4D9mZmZmZmbmPwAAAAAAAOA/NZlZEWy+zw0R6n53K9lEr7hm4pmPGL58iP4M50+9asABAAAAAAAAAPvzkKUjVr2RJ/MiwC5oaNwkPBdNtco8b6iIQ4az3uheAAAAAAAAAABy6+rp56CyTQfxJ1vQ3HSAeeTNiL2be9pawmL0SUG7VAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "mwyq4B76zIIAypo7AAAAAACUNXcAAAAACQAAAAAAAAB7FK5H4XqEP5qZmZmZmbk/AAPMEgAAAAB7FK5H4XqEPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACamZmZmZmpPwAAAAAAAOA/exSuR+F6lD+amZmZmZnJP3sUrkfheqQ/MzMzMzMz0z97FK5H4Xq0P5qZmZmZmdk/uB6F61G4vj8AAAAAAADgP5qZmZmZmck/MzMzMzMz4z8zMzMzMzPTP2ZmZmZmZuY/mpmZmZmZqT+amZmZmZnpP3sUrkfheqQ/mpmZmZmZ2T97FK5H4Xq0PzMzMzMzM+M/exSuR+F6xD+amZmZmZnpP7gehetRuM4/AAAAAAAA8D+amZmZmZnZPzMzMzMzM/M/MzMzMzMz4z9mZmZmZmb2P5qZmZmZmak/MzMzMzMz8z+4HoXrUbiuPzMzMzMzM+M/uB6F61G4vj/NzMzMzMzsP7gehetRuM4/MzMzMzMz8z8K16NwPQrXPwAAAAAAAPg/MzMzMzMz4z/NzMzMzMz8P83MzMzMzOw/zczMzMzMAECamZmZmZmpPzMzMzMzMwNAexSuR+F6tD+amZmZmZnpP3sUrkfhesQ/MzMzMzMz8z97FK5H4XrUP5qZmZmZmfk/uB6F61G43j8AAAAAAAAAQJqZmZmZmek/MzMzMzMzA0AzMzMzMzPzP2ZmZmZmZgZAmpmZmZmZqT8AAAAAAAAUQJqZmZmZmbk/AAAAAAAA8D+amZmZmZnJPwAAAAAAAPg/mpmZmZmZ2T8AAAAAAAAAQDMzMzMzM+M/AAAAAAAABEAAAAAAAADwPwAAAAAAAAhAAAAAAAAA+D8AAAAAAAAMQJqZmZmZmak/AAAAAAAAJECamZmZmZnJPwAAAAAAAOA/MzMzMzMz0z8AAAAAAADgP5qZmZmZmdk/AAAAAAAA4D8AAAAAAADgPwAAAAAAAOA/MzMzMzMz4z8AAAAAAADgP2ZmZmZmZuY/AAAAAAAA4D+amZmZmZmpPwAAAAAAAC5AmpmZmZmZyT8AAAAAAADgPzMzMzMzM9M/AAAAAAAA4D+amZmZmZnZPwAAAAAAAOA/AAAAAAAA4D8AAAAAAADgPzMzMzMzM+M/AAAAAAAA4D9mZmZmZmbmPwAAAAAAAOA/mpmZmZmZqT8AAAAAAAA0QJqZmZmZmck/AAAAAAAA4D8zMzMzMzPTPwAAAAAAAOA/mpmZmZmZ2T8AAAAAAADgPwAAAAAAAOA/AAAAAAAA4D8zMzMzMzPjPwAAAAAAAOA/ZmZmZmZm5j8AAAAAAADgP65NLOfqYWrHRlUywKtjfd8qcXIG2SdJ6CJsOcSdStteAAAAAAAAAACHiqfv7Or7dxaeUWWCL28DqB5zSV6f/Q/m5STHGwmvoAEAAAAAAAAATrZJR9AqFjB2K+adnHIuuc1zsSH/SKxxQQSNzQqOm0sBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", "base64" ], - "owner": "7WBoFuPnWttNyxzL9tPtQJrNaAZTG6VquYy4dZZkj84F", + "owner": "6rosVyXKwj9tiHubXUBTguaCELsci4pBEuiRy36Lbz1p", "executable": false, "rentEpoch": 0 } diff --git a/packages/validator/fixtures/programs/psyoptions_american_instrument.so b/packages/validator/fixtures/programs/psyoptions_american_instrument.so index 1af845b28..998dedccb 100755 Binary files a/packages/validator/fixtures/programs/psyoptions_american_instrument.so and b/packages/validator/fixtures/programs/psyoptions_american_instrument.so differ diff --git a/packages/validator/fixtures/programs/psyoptions_european_instrument.so b/packages/validator/fixtures/programs/psyoptions_european_instrument.so index 13dc09288..078d4919c 100755 Binary files a/packages/validator/fixtures/programs/psyoptions_european_instrument.so and b/packages/validator/fixtures/programs/psyoptions_european_instrument.so differ diff --git a/packages/validator/fixtures/programs/rfq.so b/packages/validator/fixtures/programs/rfq.so index c19590396..aac77bd7b 100755 Binary files a/packages/validator/fixtures/programs/rfq.so and b/packages/validator/fixtures/programs/rfq.so differ diff --git a/packages/validator/fixtures/programs/risk_engine.so b/packages/validator/fixtures/programs/risk_engine.so index 3a27cf9ac..831b0484b 100755 Binary files a/packages/validator/fixtures/programs/risk_engine.so and b/packages/validator/fixtures/programs/risk_engine.so differ diff --git a/packages/validator/fixtures/programs/spot_instrument.so b/packages/validator/fixtures/programs/spot_instrument.so index 8abba2e6a..ad36a0ed6 100755 Binary files a/packages/validator/fixtures/programs/spot_instrument.so and b/packages/validator/fixtures/programs/spot_instrument.so differ diff --git a/yarn.lock b/yarn.lock index bdbb8837f..2a96b3ec3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1240,10 +1240,10 @@ bn.js "^5.2.0" debug "^4.3.3" -"@convergence-rfq/psyoptions-american-instrument@2.2.12": - version "2.2.12" - resolved "https://registry.yarnpkg.com/@convergence-rfq/psyoptions-american-instrument/-/psyoptions-american-instrument-2.2.12.tgz#a4882edb24b2a5ef355548cfaf85b498bdd46c2a" - integrity sha512-x2a2pAtb0KMcgNLkc1HwHOIpF303Pfk2CceJL/eqGTLh2kvAPBlwE1XgPmziGPcxImEp07n76tQFbZ6G+R9z1g== +"@convergence-rfq/psyoptions-american-instrument@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@convergence-rfq/psyoptions-american-instrument/-/psyoptions-american-instrument-2.3.0.tgz#b97934de1ca09b481c097e494f60c2827546cdfa" + integrity sha512-twUtMt8CJw8IV2uFkGGffElmsr25Jw5nlEfCPl+68oH5jKzN2BLq6ovp31KKPwFySfOeQPNH9uDp56fxUJMJ/g== dependencies: "@convergence-rfq/beet" "^0.7.10" "@convergence-rfq/beet-solana" "^0.4.11" @@ -1251,10 +1251,10 @@ "@solana/web3.js" "^1.56.2" bn.js "^5.2.0" -"@convergence-rfq/psyoptions-european-instrument@2.2.12": - version "2.2.12" - resolved "https://registry.yarnpkg.com/@convergence-rfq/psyoptions-european-instrument/-/psyoptions-european-instrument-2.2.12.tgz#9e2bb2b0dc269a4f33c6cab191b4625f00baa1b4" - integrity sha512-V51vcnDX6EHORQFEbwiVCgNDfCHDz21REYu7MGuU24efTl6M+PGbqLZHhN0WU5HrjMFAZSizllqdx+VW23clcQ== +"@convergence-rfq/psyoptions-european-instrument@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@convergence-rfq/psyoptions-european-instrument/-/psyoptions-european-instrument-2.3.0.tgz#4ec96e3e62652fe9afd627164b6efeae36d8e579" + integrity sha512-bdMP1I+hLzmxbCBvrCSmDMmE9gtyH7LvS3jvk/vvuAPwe2XKDWKvdX8uKrC8ageleBNOHGojxh65zXRaNGe8bw== dependencies: "@convergence-rfq/beet" "^0.7.10" "@convergence-rfq/beet-solana" "^0.4.11" @@ -1262,10 +1262,10 @@ "@solana/web3.js" "^1.56.2" bn.js "^5.2.0" -"@convergence-rfq/rfq@2.2.12": - version "2.2.12" - resolved "https://registry.yarnpkg.com/@convergence-rfq/rfq/-/rfq-2.2.12.tgz#359f8cc0ce7a2e5449874d971c207b8b6d1f57bf" - integrity sha512-KzmaAbiaW1zSpWkf1xDbHDPqtEKmZthEjSbxaU2wKhY6WMfrLYvHKPUQZyXWOTiDRlhkvLgElNeqBqt3OcyVpw== +"@convergence-rfq/rfq@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@convergence-rfq/rfq/-/rfq-2.3.0.tgz#5908d382a822cf57813db54a470f5b2f530007cd" + integrity sha512-yHiOPrypEeueDPPt0ephgP3Nb86M84c9JlszXm+RNMyvGO6kpRvZgGtnqfs7+5bGa/BxrOdzdfC/uJSXSNcysw== dependencies: "@convergence-rfq/beet" "^0.7.10" "@convergence-rfq/beet-solana" "^0.4.11" @@ -1273,10 +1273,10 @@ "@solana/web3.js" "^1.56.2" bn.js "^5.2.0" -"@convergence-rfq/risk-engine@2.2.12": - version "2.2.12" - resolved "https://registry.yarnpkg.com/@convergence-rfq/risk-engine/-/risk-engine-2.2.12.tgz#75f77681488d54cec5722cfc448901455fff94f1" - integrity sha512-ZcBX8FLY/wHiqYacX8n1NXhvsarY2JEskZOuRjSoIdi71qIArGe5DrK/wyHfda6D19R9Q7VH531L64qnYl32oA== +"@convergence-rfq/risk-engine@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@convergence-rfq/risk-engine/-/risk-engine-2.3.0.tgz#30c17478944a8d13475bee10dd4477ae465a299a" + integrity sha512-XiMGG/4t8yy8WArnBst+xaBWQFhyDKrhIFX0UlttyV/mDledbT3l4P7mEjAG4nbbYySEdgNxLM7/suwgCmMsGQ== dependencies: "@convergence-rfq/beet" "^0.7.10" "@convergence-rfq/beet-solana" "^0.4.11" @@ -1284,10 +1284,10 @@ "@solana/web3.js" "^1.56.2" bn.js "^5.2.0" -"@convergence-rfq/spot-instrument@2.2.12": - version "2.2.12" - resolved "https://registry.yarnpkg.com/@convergence-rfq/spot-instrument/-/spot-instrument-2.2.12.tgz#395c74a7ba19878f22afbabab7d354ea094ce3aa" - integrity sha512-MmChERJTJ4BG/Al7ZXbJeLCC0IKWpVIn7miw0B97k9ANY7iXaaEcuh969jV62K8zSioPH5H+55sfOrFf06Zgig== +"@convergence-rfq/spot-instrument@2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@convergence-rfq/spot-instrument/-/spot-instrument-2.3.0.tgz#b9320243cade7564c9ab743ec88b32a32fc1991b" + integrity sha512-g0fxe623jWHTGNJjnINqT7Mcg7YHiiFAFUaUYv6sOn96JV82fsM6U0DmQOA3D6lgJIxtSbYzXMS64HpyN+8I8A== dependencies: "@convergence-rfq/beet" "^0.7.10" "@convergence-rfq/beet-solana" "^0.4.11"