├── .github └── CODEOWNERS ├── .gitignore ├── .yarnrc.yml ├── LICENSE.md ├── README.md ├── examples ├── README.md ├── common.ts ├── deposit_nft.ts ├── deposit_sol.ts ├── get_pool_bid_details.ts ├── init_pool.ts ├── list_edit_delist_nft.ts ├── package-lock.json └── package.json ├── package.json ├── src ├── common.ts ├── index.ts ├── tensor_bid │ ├── constants.ts │ ├── idl │ │ ├── tensor_bid.ts │ │ └── tensor_bid_v0_1_0.ts │ ├── index.ts │ ├── pda.ts │ └── sdk.ts ├── tensor_whitelist │ ├── constants.ts │ ├── idl │ │ ├── tensor_whitelist.ts │ │ └── tensor_whitelist_v0_1_0.ts │ ├── index.ts │ ├── pda.ts │ └── sdk.ts ├── tensorswap │ ├── constants.ts │ ├── idl │ │ ├── tensorswap.ts │ │ ├── tensorswap_v0_1_32.ts │ │ ├── tensorswap_v0_2_0.ts │ │ ├── tensorswap_v0_3_0.ts │ │ ├── tensorswap_v0_3_5.ts │ │ ├── tensorswap_v1_0_0.ts │ │ ├── tensorswap_v1_1_0.ts │ │ ├── tensorswap_v1_3_0.ts │ │ ├── tensorswap_v1_4_0.ts │ │ ├── tensorswap_v1_5_0.ts │ │ ├── tensorswap_v1_6_0.ts │ │ └── tensorswap_v1_7_0.ts │ ├── index.ts │ ├── pda.ts │ ├── prices.ts │ ├── sdk.ts │ └── types.ts ├── token2022.ts └── types.ts ├── tsconfig.base.json ├── tsconfig.cjs.json ├── tsconfig.json ├── tsconfig.tests.json └── yarn.lock /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @richardwu @ilmoi 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | target 3 | **/*.rs.bk 4 | node_modules 5 | .yarn/ 6 | 7 | # Private keys/secrets 8 | *.jks 9 | *.p8 10 | *.p12 11 | *.key 12 | .env 13 | 14 | # macOS 15 | .DS_Store 16 | 17 | # WSL 18 | *:Zone.Identifier 19 | 20 | # Temp files 21 | *.orig.* 22 | *.swp 23 | *.log 24 | 25 | # Node/tsc 26 | node_modules/ 27 | npm-debug.* 28 | dist/ 29 | 30 | # Data 31 | *.jsonl 32 | *.gz 33 | 34 | # IDEs 35 | .idea/ 36 | .vscode/ 37 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | 3 | npmScopes: 4 | tensor-oss: 5 | npmAlwaysAuth: true 6 | tensor-hq: 7 | npmAlwaysAuth: true 8 | npmRegistryServer: "https://registry.npmjs.org" 9 | 10 | yarnPath: .yarn/releases/yarn-3.3.1.cjs 11 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | https://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | Copyright 2013-2018 Docker, Inc. 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | https://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tensorswap SDK 2 | 3 | - [Getting Started](#getting-started) 4 | - [Example Code](#example-code) 5 | - [API Access](#api-access) 6 | 7 | ## Getting Started 8 | 9 | ### From npm/yarn (RECOMMENDED) 10 | 11 | ``` 12 | # yarn 13 | yarn add @tensor-oss/tensorswap-sdk 14 | # npm 15 | npm install @tensor-oss/tensorswap-sdk 16 | ``` 17 | 18 | ### From source 19 | 20 | ```sh 21 | git clone https://github.com/tensor-hq/tensorswap-sdk.git 22 | cd tensorswap-sdk/ 23 | yarn 24 | # Build JS files 25 | yarn tsc 26 | ``` 27 | 28 | ## Example Code 29 | 30 | Working examples can be found under `examples/`. 31 | 32 | ```ts 33 | const { AnchorProvider, Wallet } = require("@project-serum/anchor"); 34 | const { Connection, Keypair, PublicKey } = require("@solana/web3.js"); 35 | const { 36 | TensorSwapSDK, 37 | TensorWhitelistSDK, 38 | computeTakerPrice, 39 | TakerSide, 40 | castPoolConfigAnchor, 41 | findWhitelistPDA, 42 | } = require("@tensor-oss/tensorswap-sdk"); 43 | 44 | const conn = new Connection("https://api.mainnet-beta.solana.com"); 45 | const provider = new AnchorProvider(conn, new Wallet(Keypair.generate())); 46 | const swapSdk = new TensorSwapSDK({ provider }); 47 | const wlSdk = new TensorWhitelistSDK({ provider }); 48 | 49 | // ========= Compute current price (Buy + sell) 50 | 51 | // Fetch the pool PDA for its settings. 52 | const pool = await swapSdk.fetchPool(new PublicKey("
")); 53 | const config = castPoolConfigAnchor(pool.config); 54 | 55 | const price = computeTakerPrice({ 56 | takerSide: TakerSide.Buy, // or TakerSide.Sell for selling 57 | extraNFTsSelected: 0, 58 | 59 | // These fields can be extracted from the pool object above. 60 | config, 61 | takerSellCount: pool.takerSellCount, 62 | takerBuyCount: pool.takerBuyCount, 63 | maxTakerSellCount: pool.maxTakerSellCount, 64 | statsTakerSellCount: pool.stats.takerSellCount, 65 | slippage: , // add optional slippage: in case pool updates on-chain 66 | }); 67 | 68 | 69 | 70 | // ========= Buying 71 | { 72 | const { 73 | tx: { ixs }, 74 | } = await swapSdk.buyNft({ 75 | // Whitelist PDA address where name = tensor slug (see TensorWhitelistSDK.nameToBuffer) 76 | whitelist, 77 | // NFT Mint address 78 | nftMint, 79 | // Buyer ATA account (destination) 80 | nftBuyerAcc, 81 | // owner of NFT (in pool PDA) 82 | owner, 83 | // buyer 84 | buyer, 85 | // PoolConfig object: construct from pool PDA 86 | config, 87 | // max price buyer is willing to pay (add ~0.1% for exponential pools b/c of rounding differences) 88 | // see `computeTakerPrice` above to get the current price 89 | maxPrice 90 | }); 91 | const buyTx = new Transaction(...ixs); 92 | } 93 | 94 | // ========= Selling 95 | 96 | // uuid = Tensor collection ID (see "Collection UUID" API endpoint below) 97 | const uuid = "..." 98 | 99 | // Remove "-" symbols from uuid, so it's within the 32 seed length limit. Additionally convert the uuid to a Uint8Array 100 | const uuidArray = Buffer.from(uuid.replaceAll("-", "")).toJSON().data; 101 | 102 | // Finding the PDA address 103 | const wlAddr = findWhitelistPDA({uuid: uuidArray})[0]; 104 | 105 | // Step 1: Prepare the mint proof PDA (if required). 106 | { 107 | const wl = await wlSdk.fetchWhitelist(wlAddr); 108 | 109 | // Proof is only required if rootHash is NOT a 0 array, o/w not necessary! 110 | if(JSON.stringify(wl.rootHash) !== JSON.stringify(Array(32).fill(0))) { 111 | // Off-chain merkle proof (see "Mint Proof" API endpoint below). 112 | const proof = ...; 113 | 114 | const { 115 | tx: { ixs }, 116 | } = await wlSdk.initUpdateMintProof({ 117 | // User signing the tx (the seller) 118 | user, 119 | whitelist: wlAddr, 120 | // (NFT) Mint address 121 | mint, 122 | proof, 123 | }); 124 | const proofTx = new Transaction(...ixs); 125 | } 126 | } 127 | 128 | // Step 2: Send sell tx. 129 | { 130 | const { 131 | tx: { ixs }, 132 | } = await swapSdk.sellNft({ 133 | type: "token", // or 'trade' for a trade pool 134 | whitelist: wlAddr, 135 | // NFT Mint address 136 | nftMint, 137 | // Token account holding seller's mint 138 | nftSellerAcc, 139 | // owner of NFT (in pool PDA) 140 | owner, 141 | // seller 142 | seller, 143 | // PoolConfig object: construct from pool PDA 144 | config, 145 | // min price seller is willing to receive (sub ~0.1% for exponential pools b/c of rounding differences) 146 | // see `computeTakerPrice` above to get the current price 147 | minPrice, 148 | }); 149 | const sellTx = new Transaction(...ixs); 150 | } 151 | 152 | // ========= TODO: initPool / closePool / editPool / withdrawNft / depositNft / withdrawSol / depositSol 153 | ``` 154 | 155 | ## API Access 156 | 157 | Docs can be [found here](https://tensor-hq.notion.site/PUBLIC-Tensor-Trade-API-alpha-b18e1a196187473bac9b5d6de5b47032). 158 | 159 | Ping us in our [Discord](https://www.discord.com/invite/a8spfqxEpC) for access. 160 | 161 | ### Collection UUID 162 | 163 | You can query all Tensor collections and their metadata, including their `id` which 164 | corresponds to `whitelist.uuid` with [this query](https://www.notion.so/tensor-hq/PUBLIC-Tensor-Trade-API-alpha-b18e1a196187473bac9b5d6de5b47032#56b333bfe0b641f8acad51a963a04f4f). 165 | 166 | Alternative, you can find a collection for a corresponding mint: 167 | 1. [Get the mint's Tensor slug](https://www.notion.so/tensor-hq/PUBLIC-Tensor-Trade-API-alpha-b18e1a196187473bac9b5d6de5b47032#5ae4f2d0499a4c6ba3ceed4f9ee949ad) 168 | 2. [Get the collection's ID](https://www.notion.so/tensor-hq/PUBLIC-Tensor-Trade-API-alpha-b18e1a196187473bac9b5d6de5b47032#59c583754aa2477caacd2b436071d564) 169 | 3. Use the ID as the `uuid` for the whitelist 170 | 171 | The ID for a collection will never change, so feel free to cache this locally. 172 | 173 | A mint's collection will almost always never change (99% of the time), so feel free to cache this as needed and update if necessary. 174 | 175 | ### Mint Proof 176 | 177 | For selling and depositing and for some collections (where `whitelist.rootHash` is not a 0-zero), 178 | you will need to fetch the off-chain [merkle proofs](https://en.wikipedia.org/wiki/Merkle_tree) we use for collection whitelisting from our API. 179 | 180 | Endpoint + example can be [found here](https://www.notion.so/tensor-hq/PUBLIC-Tensor-Trade-API-alpha-b18e1a196187473bac9b5d6de5b47032#9be7fb3fc59f49e08cc10a0d7d1d7ba7). 181 | 182 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Tensor SDK examples 2 | 3 | ## Setup 4 | 5 | ```bash 6 | npm ci 7 | ``` 8 | 9 | ## Running a script 10 | 11 | ```bash 12 | node_modules/.bin/ts-node init_pool.ts 13 | ``` 14 | -------------------------------------------------------------------------------- /examples/common.ts: -------------------------------------------------------------------------------- 1 | import { getAssociatedTokenAddressSync } from "@solana/spl-token"; 2 | import { Connection, Keypair, PublicKey } from "@solana/web3.js"; 3 | 4 | export const conn = new Connection("https://api.mainnet-beta.solana.com"); 5 | export const keypair = Keypair.generate(); 6 | 7 | export const nftMint = new PublicKey( 8 | "AhzUD99Lq9wWXLWQHXF6y3gGZzmxyNU9uMBW7hdtpEg4" 9 | ); 10 | export const nftSource = getAssociatedTokenAddressSync( 11 | nftMint, 12 | keypair.publicKey 13 | ); 14 | export const whitelist = new PublicKey( 15 | "7GLDrSDBVoBkdX1odXQ6WM8qyTrAoje8mx5LeGbRY8PU" 16 | ); 17 | -------------------------------------------------------------------------------- /examples/deposit_nft.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProvider, BN, Wallet } from "@project-serum/anchor"; 2 | import { LAMPORTS_PER_SOL, PublicKey, Transaction } from "@solana/web3.js"; 3 | import { 4 | CurveTypeAnchor, 5 | PoolTypeAnchor, 6 | TensorSwapSDK, 7 | } from "@tensor-hq/tensorswap-sdk"; 8 | import { conn, keypair, nftMint, nftSource, whitelist } from "./common"; 9 | 10 | const provider = new AnchorProvider(conn, new Wallet(keypair), { 11 | commitment: "confirmed", 12 | }); 13 | const swapSdk = new TensorSwapSDK({ provider }); 14 | 15 | (async () => { 16 | const data = await swapSdk.depositNft({ 17 | owner: keypair.publicKey, 18 | // Whitelist used to create the pool. 19 | whitelist, 20 | // NFT to deposit. 21 | nftMint, 22 | nftSource, 23 | // Use the original config of your pool. 24 | config: { 25 | poolType: PoolTypeAnchor.NFT, 26 | curveType: CurveTypeAnchor.Exponential, 27 | startingPrice: new BN(0.1 * LAMPORTS_PER_SOL), 28 | delta: new BN(0), 29 | mmCompoundFees: true, 30 | mmFeeBps: null, 31 | }, 32 | }); 33 | 34 | const tx = new Transaction().add(...data.tx.ixs); 35 | })(); 36 | -------------------------------------------------------------------------------- /examples/deposit_sol.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProvider, Wallet, BN } from "@project-serum/anchor"; 2 | import { PublicKey, LAMPORTS_PER_SOL, Transaction } from "@solana/web3.js"; 3 | import { getAssociatedTokenAddress } from "@solana/spl-token"; 4 | import { 5 | TensorSwapSDK, 6 | CurveTypeAnchor, 7 | PoolTypeAnchor, 8 | } from "@tensor-hq/tensorswap-sdk"; 9 | import { conn, keypair, whitelist } from "./common"; 10 | 11 | const provider = new AnchorProvider(conn, new Wallet(keypair), { 12 | commitment: "confirmed", 13 | }); 14 | const swapSdk = new TensorSwapSDK({ provider }); 15 | 16 | (async () => { 17 | const data = await swapSdk.depositSol({ 18 | owner: keypair.publicKey, 19 | // Whitelist used to create the pool. 20 | whitelist, 21 | // Deposit 55 SOL. 22 | lamports: new BN(55 * LAMPORTS_PER_SOL), 23 | // Use the original config of your pool. 24 | config: { 25 | poolType: PoolTypeAnchor.Token, 26 | curveType: CurveTypeAnchor.Exponential, 27 | startingPrice: new BN(0.1 * LAMPORTS_PER_SOL), 28 | delta: new BN(0), 29 | mmCompoundFees: true, 30 | mmFeeBps: null, 31 | }, 32 | }); 33 | 34 | const tx = new Transaction().add(...data.tx.ixs); 35 | })(); 36 | -------------------------------------------------------------------------------- /examples/get_pool_bid_details.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { AnchorProvider, Wallet, BN } from "@coral-xyz/anchor"; 3 | import { TensorSwapSDK, TakerSide, CurveType, PoolType, castPoolConfigAnchor, computeMakerAmountCount } from "@tensor-oss/tensorswap-sdk"; 4 | import { conn, keypair } from "./common"; 5 | import Big from "big.js"; 6 | 7 | const provider = new AnchorProvider(conn, new Wallet(keypair), { 8 | commitment: "confirmed", 9 | }); 10 | const swapSdk = new TensorSwapSDK({ provider }); 11 | const HUNDRED_PCT_BPS = 100_00; 12 | 13 | async function getPoolBidDetails(poolAddress) { 14 | 15 | // fetch pool 16 | const pool = await swapSdk.fetchPool(new PublicKey(poolAddress)); 17 | const config = castPoolConfigAnchor(pool.config); 18 | 19 | // return early if pool is one-sided listing-only 20 | if (config.poolType == PoolType.NFT) { 21 | console.log("Listing-only pool can not bid."); 22 | return null; 23 | } 24 | 25 | // fetch balance of margin account if pool is attached to escrow, fetch balance of solEscrow instead if pool is not attached to escrow 26 | const solBalanceLamports = pool.margin != null ? new BN(await conn.getBalance(pool.margin)) : new BN(await conn.getBalance(pool.solEscrow)); 27 | 28 | // retrieve amount of possible bids, total lamports needed for that amount of bids and initial price of the pool 29 | const { allowedCount, totalAmount, initialPrice } = computeMakerAmountCount({ 30 | desired: { total: solBalanceLamports }, 31 | maxCountWhenInfinite: 1000, 32 | takerSide: TakerSide.Sell, 33 | extraNFTsSelected: 0, 34 | config, 35 | takerSellCount: pool.takerSellCount, 36 | takerBuyCount: pool.takerBuyCount, 37 | maxTakerSellCount: pool.maxTakerSellCount, 38 | statsTakerSellCount: pool.stats.takerSellCount, 39 | statsTakerBuyCount: pool.stats.takerBuyCount, 40 | marginated: pool.margin !== null, 41 | }); 42 | 43 | // return early if amount of possible bids is 0 44 | if (allowedCount == 0) { 45 | console.log("Pool is out of funds or reached maxTakerSellCount and won't bid anymore."); 46 | return null; 47 | } 48 | 49 | // retrieve initial highest bid price if pool is double sided (config.startingPrice would be the initial lowest list price in that case) 50 | var startingPriceBidSide: Number; 51 | if (config.poolType == PoolType.Trade) { 52 | 53 | // on linear curvetype, subtract delta once and multiply by (1 - mmFee) 54 | if (config.curveType == CurveType.Linear) { 55 | startingPriceBidSide = config.startingPrice 56 | .sub(config.delta) 57 | .mul(Big(1) 58 | .sub(Big(config.mmFeeBps) 59 | .div(Big(HUNDRED_PCT_BPS)) 60 | ) 61 | ); 62 | } 63 | 64 | // on exponential curvetype, divide by (1 + delta) once and multiply by (1 - mmFee) 65 | else if (config.curveType == CurveType.Exponential) { 66 | startingPriceBidSide = config.startingPrice 67 | .div(Big(1) 68 | .add(config.delta 69 | .div(Big(HUNDRED_PCT_BPS)) 70 | ) 71 | ) 72 | .mul(Big(1) 73 | .sub(Big(config.mmFeeBps) 74 | .div(Big(HUNDRED_PCT_BPS)) 75 | ) 76 | ); 77 | } 78 | } 79 | 80 | // else if one-sided bidding-only pool, config.startingPrice matches initial highest bid already 81 | else if (config.poolType == PoolType.Token) { 82 | startingPriceBidSide = config.startingPrice; 83 | } 84 | 85 | // get the current lowest bid price by shifting price down by allowedCountWithLimit - 1 (since arg = 0 would be the initial highest bid) + pool.takerSellCount - pool.takerBuyCount (to do x less steps depending on how many bids already got fulfilled) 86 | const currentLowestBidPrice = shiftPriceByDelta( 87 | config.curveType, 88 | startingPriceBidSide!, 89 | config.delta, 90 | "down", 91 | allowedCount - 1 + pool.takerSellCount - pool.takerBuyCount 92 | ); 93 | 94 | // get the highest bid price by shifting up or down x times depending on how many bids already got fulfilled 95 | var currentHighestBidPrice = pool.takerSellCount - pool.takerBuyCount >= 0 ? 96 | shiftPriceByDelta(config.curveType, 97 | startingPriceBidSide!, 98 | config.delta, 99 | "down", 100 | pool.takerSellCount - pool.takerBuyCount 101 | ) : 102 | shiftPriceByDelta(config.curveType, 103 | startingPriceBidSide!, 104 | config.delta, 105 | "up", 106 | (pool.takerSellCount - pool.takerBuyCount) * -1 107 | ); 108 | 109 | console.log("Pool has " + allowedCount + " bids left, bid range is: " + Number(currentHighestBidPrice / 1_000_000_000) + "-" + Number(currentLowestBidPrice / 1_000_000_000) + " SOL."); 110 | } 111 | 112 | // helper function copied from https://github.com/tensor-hq/tensorswap-sdk/blob/main/src/tensorswap/prices.ts#L145 113 | const shiftPriceByDelta = ( 114 | curveType: CurveType, 115 | startingPrice: Big, 116 | delta: Big, 117 | direction: "up" | "down", 118 | times: number 119 | ): Big => { 120 | switch (curveType) { 121 | case CurveType.Exponential: 122 | switch (direction) { 123 | // price * (1 + delta)^trade_count 124 | case "up": 125 | return startingPrice.mul( 126 | new Big(1).add(delta.div(HUNDRED_PCT_BPS)).pow(times) 127 | ); 128 | case "down": 129 | return startingPrice.div( 130 | new Big(1).add(delta.div(HUNDRED_PCT_BPS)).pow(times) 131 | ); 132 | } 133 | break; 134 | case CurveType.Linear: 135 | switch (direction) { 136 | case "up": 137 | return startingPrice.add(delta.mul(times)); 138 | case "down": 139 | return startingPrice.sub(delta.mul(times)); 140 | } 141 | } 142 | }; 143 | 144 | getPoolBidDetails("G498NY38Jxdab9BbGfaKiHze1pcr94ZLqusDoxUwoWsm"); 145 | -------------------------------------------------------------------------------- /examples/init_pool.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProvider, BN, Wallet } from "@project-serum/anchor"; 2 | import { LAMPORTS_PER_SOL, PublicKey, Transaction } from "@solana/web3.js"; 3 | import { 4 | CurveTypeAnchor, 5 | OrderType, 6 | PoolTypeAnchor, 7 | TensorSwapSDK, 8 | } from "@tensor-hq/tensorswap-sdk"; 9 | import { conn, keypair, whitelist } from "./common"; 10 | 11 | const provider = new AnchorProvider(conn, new Wallet(keypair), { 12 | commitment: "confirmed", 13 | }); 14 | const swapSdk = new TensorSwapSDK({ provider }); 15 | 16 | (async () => { 17 | const initPool = await swapSdk.initPool({ 18 | owner: keypair.publicKey, 19 | whitelist, 20 | config: { 21 | // Token = Collection-wide bid 22 | // NFT = Listing NFTs on a curve. 23 | // Trade = market-making order. 24 | poolType: PoolTypeAnchor.Token, 25 | // Exponential = % change. 26 | // Linear = SOL change. 27 | curveType: CurveTypeAnchor.Exponential, 28 | startingPrice: new BN(0.1 * LAMPORTS_PER_SOL), 29 | // If curveType = Exponential, this is in BPS (delta = 100 = 1%). 30 | // If curveType = Linear, this is in lamports (delta = 1e8 = 0.1 SOL). 31 | delta: new BN(0), 32 | // ===== These are only valid for poolType = Trade ===== 33 | // If true, fees deposited back into pool to buy more NFTs. 34 | mmCompoundFees: true, 35 | // How much fee to collect for each buy + sell (mmFeeBps = 100 = 1%). 36 | mmFeeBps: null, 37 | }, 38 | // Maximum # of NFTs to buy from the pool in its lifetime (important to set for shared escrows!). 39 | maxTakerSellCount: 1, 40 | // keep these as is. 41 | customAuthSeed: undefined, 42 | isCosigned: false, 43 | orderType: OrderType.Standard, 44 | }); 45 | console.log(initPool); 46 | const tx = new Transaction().add(...initPool.tx.ixs); 47 | console.log(tx); 48 | })(); 49 | -------------------------------------------------------------------------------- /examples/list_edit_delist_nft.ts: -------------------------------------------------------------------------------- 1 | import { AnchorProvider, BN, Wallet } from "@project-serum/anchor"; 2 | import { LAMPORTS_PER_SOL, Transaction } from "@solana/web3.js"; 3 | import { TensorSwapSDK } from "@tensor-hq/tensorswap-sdk"; 4 | import { conn, keypair, nftMint, nftSource } from "./common"; 5 | 6 | const provider = new AnchorProvider(conn, new Wallet(keypair), { 7 | commitment: "confirmed", 8 | }); 9 | const swapSdk = new TensorSwapSDK({ provider }); 10 | 11 | (async () => { 12 | // List the NFT. 13 | { 14 | const data = await swapSdk.list({ 15 | owner: keypair.publicKey, 16 | nftMint, 17 | nftSource, 18 | // Create listing to sell for 0.1 SOL. 19 | price: new BN(0.1 * LAMPORTS_PER_SOL), 20 | }); 21 | 22 | const tx = new Transaction().add(...data.tx.ixs); 23 | // Sign + send tx. 24 | } 25 | 26 | // Edit the listing. 27 | { 28 | const data = await swapSdk.editSingleListing({ 29 | owner: keypair.publicKey, 30 | nftMint, 31 | // Change listing to 0.5 SOL. 32 | price: new BN(0.5 * LAMPORTS_PER_SOL), 33 | }); 34 | 35 | const tx = new Transaction().add(...data.tx.ixs); 36 | } 37 | 38 | // Delist. 39 | { 40 | const data = await swapSdk.delist({ 41 | owner: keypair.publicKey, 42 | nftMint, 43 | // (!!) Specify the destination ATA for the NFT. 44 | nftDest: nftSource, 45 | }); 46 | 47 | const tx = new Transaction().add(...data.tx.ixs); 48 | } 49 | })(); 50 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tensorswap-sdk-examples", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@project-serum/anchor": "^0.26.0", 13 | "@solana/web3.js": "^1.75.0", 14 | "@tensor-hq/tensorswap-sdk": "^1.9.3" 15 | }, 16 | "devDependencies": { 17 | "ts-node": "^10.9.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tensor-oss/tensorswap-sdk", 3 | "version": "4.5.0", 4 | "description": "Anchor/JS SDK for interacting with TensorSwap, TensorWhitelist and TensorBid.", 5 | "sideEffects": false, 6 | "module": "./dist/esm/index.js", 7 | "main": "./dist/cjs/index.js", 8 | "types": "dist/cjs/index.d.ts", 9 | "files": [ 10 | "/dist/esm/**/*", 11 | "/dist/cjs/**/*" 12 | ], 13 | "repository": "https://github.com/tensor-hq/tensorswap-sdk.git", 14 | "license": "MIT", 15 | "publishConfig": { 16 | "@tensor-hq:registry": "https://registry.npmjs.org" 17 | }, 18 | "scripts": { 19 | "build": "anchor build && bash scripts/cp_idl.sh", 20 | "build:test": "bash scripts/test_build.sh", 21 | "build:ts": "rm -rf ./dist && yarn tsc && yarn tsc -p tsconfig.cjs.json", 22 | "fast-test": "anchor test --skip-build -- --features testing", 23 | "test": "yarn build:test && yarn fast-test", 24 | "push": "git push --atomic", 25 | "publish:private": "yarn build:ts && yarn npm publish", 26 | "publish:public": "yarn build:ts && npm publish --access public --registry https://registry.npmjs.org", 27 | "upgrade": "yarn add @tensor-hq/tensor-common" 28 | }, 29 | "dependencies": { 30 | "@coral-xyz/anchor": "^0.26.0", 31 | "@msgpack/msgpack": "^2.8.0", 32 | "@saberhq/solana-contrib": "^1.14.11", 33 | "@solana/spl-token": "^0.4.0", 34 | "@solana/web3.js": "^1.73.0", 35 | "@tensor-hq/tensor-common": "^8.0.8", 36 | "@types/bn.js": "^5.1.0", 37 | "big.js": "^6.2.1", 38 | "bn.js": "^5.2.0", 39 | "js-sha256": "^0.9.0", 40 | "keccak256": "^1.0.6", 41 | "math-expression-evaluator": "^2.0.4", 42 | "merkletreejs": "^0.3.11", 43 | "uuid": "^8.3.2" 44 | }, 45 | "devDependencies": { 46 | "@metaplex-foundation/mpl-token-auth-rules": "^2.0.0", 47 | "@metaplex-foundation/mpl-token-metadata": "^2.13.0", 48 | "@types/big.js": "^6.1.5", 49 | "@types/chai": "^4.3.10", 50 | "@types/chai-as-promised": "^7.1.4", 51 | "@types/mocha": "^10.0.4", 52 | "@types/uuid": "^8.3.4", 53 | "@types/yargs": "^17.0.12", 54 | "chai": "^4.3.6", 55 | "chai-as-promised": "^7.1.1", 56 | "chai-bn": "^0.3.1", 57 | "exponential-backoff": "^3.1.0", 58 | "jsbi": "^4.1.0", 59 | "mocha": "^10.0.0", 60 | "prettier": "^2.5.1", 61 | "ts-mocha": "^10.0.0", 62 | "ts-node": "^10.9.1", 63 | "typescript": "^4.3.5", 64 | "yargs": "^17.5.1" 65 | }, 66 | "packageManager": "yarn@3.3.1" 67 | } 68 | -------------------------------------------------------------------------------- /src/common.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import Mexp from "math-expression-evaluator"; 3 | 4 | // pNFTs can be very expensive: just over allocate. 5 | export const DEFAULT_XFER_COMPUTE_UNITS = 800_000; 6 | export const DEFAULT_RULESET_ADDN_COMPUTE_UNITS = 400_000; 7 | export const DEFAULT_MICRO_LAMPORTS = 10_000; 8 | 9 | export type AccountSuffix = 10 | | "Nft Mint" 11 | | "Sol Escrow" 12 | | "Old Sol Escrow" 13 | | "New Sol Escrow" 14 | | "Pool" 15 | | "Old Pool" 16 | | "New Pool" 17 | | "Nft Escrow" 18 | | "Whitelist" 19 | | "Nft Receipt" 20 | | "Buyer" 21 | | "Seller" 22 | | "Owner" 23 | | "Nft Authority" 24 | | "Margin Account" 25 | | "Single Listing" 26 | | "Bid State" 27 | | "Bidder"; 28 | 29 | export const evalMathExpr = (str: string) => { 30 | const mexp = new Mexp(); 31 | return mexp.eval(str, [], {}); 32 | }; 33 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./types"; 2 | export * from "./common"; 3 | export * from "./tensorswap"; 4 | export * from "./tensor_whitelist"; 5 | export * from "./tensor_bid"; 6 | export * from "./token2022"; 7 | -------------------------------------------------------------------------------- /src/tensor_bid/constants.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | export const TBID_ADDR = new PublicKey( 4 | process.env.TBID_ADDR || "TB1Dqt8JeKQh7RLDzfYDJsq8KS4fS2yt87avRjyRxMv" 5 | ); 6 | -------------------------------------------------------------------------------- /src/tensor_bid/idl/tensor_bid.ts: -------------------------------------------------------------------------------- 1 | export type TensorBid = { 2 | "version": "1.0.0", 3 | "name": "tensor_bid", 4 | "constants": [ 5 | { 6 | "name": "CURRENT_TBID_VERSION", 7 | "type": "u8", 8 | "value": "1" 9 | }, 10 | { 11 | "name": "TBID_TAKER_FEE_BPS", 12 | "type": "u16", 13 | "value": "150" 14 | }, 15 | { 16 | "name": "MAX_EXPIRY_SEC", 17 | "type": "i64", 18 | "value": "31536000" 19 | }, 20 | { 21 | "name": "BID_STATE_SIZE", 22 | "type": { 23 | "defined": "usize" 24 | }, 25 | "value": "8 + 1 + 8 + (32 * 2) + 1 + 8 + 33 + 8 + 56" 26 | } 27 | ], 28 | "instructions": [ 29 | { 30 | "name": "bid", 31 | "accounts": [ 32 | { 33 | "name": "nftMint", 34 | "isMut": false, 35 | "isSigner": false 36 | }, 37 | { 38 | "name": "bidState", 39 | "isMut": true, 40 | "isSigner": false 41 | }, 42 | { 43 | "name": "bidder", 44 | "isMut": true, 45 | "isSigner": true 46 | }, 47 | { 48 | "name": "systemProgram", 49 | "isMut": false, 50 | "isSigner": false 51 | }, 52 | { 53 | "name": "rent", 54 | "isMut": false, 55 | "isSigner": false 56 | }, 57 | { 58 | "name": "tswap", 59 | "isMut": false, 60 | "isSigner": false 61 | }, 62 | { 63 | "name": "marginAccount", 64 | "isMut": true, 65 | "isSigner": false 66 | } 67 | ], 68 | "args": [ 69 | { 70 | "name": "lamports", 71 | "type": "u64" 72 | }, 73 | { 74 | "name": "expireInSec", 75 | "type": { 76 | "option": "u64" 77 | } 78 | } 79 | ] 80 | }, 81 | { 82 | "name": "takeBid", 83 | "accounts": [ 84 | { 85 | "name": "tswap", 86 | "isMut": false, 87 | "isSigner": false 88 | }, 89 | { 90 | "name": "feeVault", 91 | "isMut": true, 92 | "isSigner": false 93 | }, 94 | { 95 | "name": "nftMint", 96 | "isMut": false, 97 | "isSigner": false 98 | }, 99 | { 100 | "name": "nftBidderAcc", 101 | "isMut": true, 102 | "isSigner": false 103 | }, 104 | { 105 | "name": "nftSellerAcc", 106 | "isMut": true, 107 | "isSigner": false 108 | }, 109 | { 110 | "name": "nftTempAcc", 111 | "isMut": true, 112 | "isSigner": false 113 | }, 114 | { 115 | "name": "nftMetadata", 116 | "isMut": true, 117 | "isSigner": false 118 | }, 119 | { 120 | "name": "bidState", 121 | "isMut": true, 122 | "isSigner": false 123 | }, 124 | { 125 | "name": "bidder", 126 | "isMut": true, 127 | "isSigner": false 128 | }, 129 | { 130 | "name": "seller", 131 | "isMut": true, 132 | "isSigner": true 133 | }, 134 | { 135 | "name": "tokenProgram", 136 | "isMut": false, 137 | "isSigner": false 138 | }, 139 | { 140 | "name": "associatedTokenProgram", 141 | "isMut": false, 142 | "isSigner": false 143 | }, 144 | { 145 | "name": "systemProgram", 146 | "isMut": false, 147 | "isSigner": false 148 | }, 149 | { 150 | "name": "rent", 151 | "isMut": false, 152 | "isSigner": false 153 | }, 154 | { 155 | "name": "nftEdition", 156 | "isMut": false, 157 | "isSigner": false 158 | }, 159 | { 160 | "name": "sellerTokenRecord", 161 | "isMut": true, 162 | "isSigner": false 163 | }, 164 | { 165 | "name": "bidderTokenRecord", 166 | "isMut": true, 167 | "isSigner": false 168 | }, 169 | { 170 | "name": "tempTokenRecord", 171 | "isMut": true, 172 | "isSigner": false 173 | }, 174 | { 175 | "name": "pnftShared", 176 | "accounts": [ 177 | { 178 | "name": "tokenMetadataProgram", 179 | "isMut": false, 180 | "isSigner": false 181 | }, 182 | { 183 | "name": "instructions", 184 | "isMut": false, 185 | "isSigner": false 186 | }, 187 | { 188 | "name": "authorizationRulesProgram", 189 | "isMut": false, 190 | "isSigner": false 191 | } 192 | ] 193 | }, 194 | { 195 | "name": "tensorswapProgram", 196 | "isMut": false, 197 | "isSigner": false 198 | }, 199 | { 200 | "name": "authRules", 201 | "isMut": false, 202 | "isSigner": false 203 | }, 204 | { 205 | "name": "marginAccount", 206 | "isMut": true, 207 | "isSigner": false 208 | }, 209 | { 210 | "name": "takerBroker", 211 | "isMut": true, 212 | "isSigner": false 213 | } 214 | ], 215 | "args": [ 216 | { 217 | "name": "lamports", 218 | "type": "u64" 219 | }, 220 | { 221 | "name": "rulesAccPresent", 222 | "type": "bool" 223 | }, 224 | { 225 | "name": "authorizationData", 226 | "type": { 227 | "option": { 228 | "defined": "AuthorizationDataLocal" 229 | } 230 | } 231 | }, 232 | { 233 | "name": "optionalRoyaltyPct", 234 | "type": { 235 | "option": "u16" 236 | } 237 | } 238 | ] 239 | }, 240 | { 241 | "name": "takeBidT22", 242 | "accounts": [ 243 | { 244 | "name": "tswap", 245 | "isMut": false, 246 | "isSigner": false 247 | }, 248 | { 249 | "name": "feeVault", 250 | "isMut": true, 251 | "isSigner": false 252 | }, 253 | { 254 | "name": "nftMint", 255 | "isMut": false, 256 | "isSigner": false 257 | }, 258 | { 259 | "name": "nftBidderAcc", 260 | "isMut": true, 261 | "isSigner": false 262 | }, 263 | { 264 | "name": "nftSellerAcc", 265 | "isMut": true, 266 | "isSigner": false 267 | }, 268 | { 269 | "name": "bidState", 270 | "isMut": true, 271 | "isSigner": false 272 | }, 273 | { 274 | "name": "bidder", 275 | "isMut": true, 276 | "isSigner": false 277 | }, 278 | { 279 | "name": "seller", 280 | "isMut": true, 281 | "isSigner": true 282 | }, 283 | { 284 | "name": "tokenProgram", 285 | "isMut": false, 286 | "isSigner": false 287 | }, 288 | { 289 | "name": "associatedTokenProgram", 290 | "isMut": false, 291 | "isSigner": false 292 | }, 293 | { 294 | "name": "systemProgram", 295 | "isMut": false, 296 | "isSigner": false 297 | }, 298 | { 299 | "name": "rent", 300 | "isMut": false, 301 | "isSigner": false 302 | }, 303 | { 304 | "name": "tensorswapProgram", 305 | "isMut": false, 306 | "isSigner": false 307 | }, 308 | { 309 | "name": "marginAccount", 310 | "isMut": true, 311 | "isSigner": false 312 | }, 313 | { 314 | "name": "takerBroker", 315 | "isMut": true, 316 | "isSigner": false 317 | } 318 | ], 319 | "args": [ 320 | { 321 | "name": "lamports", 322 | "type": "u64" 323 | } 324 | ] 325 | }, 326 | { 327 | "name": "wnsTakeBid", 328 | "accounts": [ 329 | { 330 | "name": "tswap", 331 | "isMut": false, 332 | "isSigner": false 333 | }, 334 | { 335 | "name": "feeVault", 336 | "isMut": true, 337 | "isSigner": false 338 | }, 339 | { 340 | "name": "nftMint", 341 | "isMut": false, 342 | "isSigner": false 343 | }, 344 | { 345 | "name": "nftBidderAcc", 346 | "isMut": true, 347 | "isSigner": false 348 | }, 349 | { 350 | "name": "nftSellerAcc", 351 | "isMut": true, 352 | "isSigner": false 353 | }, 354 | { 355 | "name": "bidState", 356 | "isMut": true, 357 | "isSigner": false 358 | }, 359 | { 360 | "name": "bidder", 361 | "isMut": true, 362 | "isSigner": false 363 | }, 364 | { 365 | "name": "seller", 366 | "isMut": true, 367 | "isSigner": true 368 | }, 369 | { 370 | "name": "tokenProgram", 371 | "isMut": false, 372 | "isSigner": false 373 | }, 374 | { 375 | "name": "associatedTokenProgram", 376 | "isMut": false, 377 | "isSigner": false 378 | }, 379 | { 380 | "name": "systemProgram", 381 | "isMut": false, 382 | "isSigner": false 383 | }, 384 | { 385 | "name": "rent", 386 | "isMut": false, 387 | "isSigner": false 388 | }, 389 | { 390 | "name": "tensorswapProgram", 391 | "isMut": false, 392 | "isSigner": false 393 | }, 394 | { 395 | "name": "marginAccount", 396 | "isMut": true, 397 | "isSigner": false 398 | }, 399 | { 400 | "name": "takerBroker", 401 | "isMut": true, 402 | "isSigner": false 403 | }, 404 | { 405 | "name": "approveAccount", 406 | "isMut": true, 407 | "isSigner": false 408 | }, 409 | { 410 | "name": "distribution", 411 | "isMut": true, 412 | "isSigner": false 413 | }, 414 | { 415 | "name": "wnsProgram", 416 | "isMut": false, 417 | "isSigner": false 418 | }, 419 | { 420 | "name": "distributionProgram", 421 | "isMut": false, 422 | "isSigner": false 423 | }, 424 | { 425 | "name": "extraMetas", 426 | "isMut": false, 427 | "isSigner": false 428 | } 429 | ], 430 | "args": [ 431 | { 432 | "name": "lamports", 433 | "type": "u64" 434 | } 435 | ] 436 | }, 437 | { 438 | "name": "cancelBid", 439 | "accounts": [ 440 | { 441 | "name": "nftMint", 442 | "isMut": false, 443 | "isSigner": false 444 | }, 445 | { 446 | "name": "bidState", 447 | "isMut": true, 448 | "isSigner": false 449 | }, 450 | { 451 | "name": "bidder", 452 | "isMut": true, 453 | "isSigner": true 454 | }, 455 | { 456 | "name": "systemProgram", 457 | "isMut": false, 458 | "isSigner": false 459 | }, 460 | { 461 | "name": "rent", 462 | "isMut": false, 463 | "isSigner": false 464 | } 465 | ], 466 | "args": [] 467 | }, 468 | { 469 | "name": "closeExpiredBid", 470 | "accounts": [ 471 | { 472 | "name": "nftMint", 473 | "isMut": false, 474 | "isSigner": false 475 | }, 476 | { 477 | "name": "bidState", 478 | "isMut": true, 479 | "isSigner": false 480 | }, 481 | { 482 | "name": "bidder", 483 | "isMut": true, 484 | "isSigner": false 485 | }, 486 | { 487 | "name": "systemProgram", 488 | "isMut": false, 489 | "isSigner": false 490 | }, 491 | { 492 | "name": "rent", 493 | "isMut": false, 494 | "isSigner": false 495 | } 496 | ], 497 | "args": [] 498 | } 499 | ], 500 | "accounts": [ 501 | { 502 | "name": "bidState", 503 | "type": { 504 | "kind": "struct", 505 | "fields": [ 506 | { 507 | "name": "version", 508 | "type": "u8" 509 | }, 510 | { 511 | "name": "bidAmount", 512 | "type": "u64" 513 | }, 514 | { 515 | "name": "nftMint", 516 | "type": "publicKey" 517 | }, 518 | { 519 | "name": "bidder", 520 | "type": "publicKey" 521 | }, 522 | { 523 | "name": "bump", 524 | "type": { 525 | "array": [ 526 | "u8", 527 | 1 528 | ] 529 | } 530 | }, 531 | { 532 | "name": "expiry", 533 | "type": "i64" 534 | }, 535 | { 536 | "name": "margin", 537 | "type": { 538 | "option": "publicKey" 539 | } 540 | }, 541 | { 542 | "name": "updatedAt", 543 | "type": "i64" 544 | }, 545 | { 546 | "name": "reserved", 547 | "type": { 548 | "array": [ 549 | "u8", 550 | 8 551 | ] 552 | } 553 | }, 554 | { 555 | "name": "reserved1", 556 | "type": { 557 | "array": [ 558 | "u8", 559 | 16 560 | ] 561 | } 562 | }, 563 | { 564 | "name": "reserved2", 565 | "type": { 566 | "array": [ 567 | "u8", 568 | 32 569 | ] 570 | } 571 | } 572 | ] 573 | } 574 | } 575 | ], 576 | "types": [ 577 | { 578 | "name": "AuthorizationDataLocal", 579 | "type": { 580 | "kind": "struct", 581 | "fields": [ 582 | { 583 | "name": "payload", 584 | "type": { 585 | "vec": { 586 | "defined": "TaggedPayload" 587 | } 588 | } 589 | } 590 | ] 591 | } 592 | }, 593 | { 594 | "name": "TaggedPayload", 595 | "type": { 596 | "kind": "struct", 597 | "fields": [ 598 | { 599 | "name": "name", 600 | "type": "string" 601 | }, 602 | { 603 | "name": "payload", 604 | "type": { 605 | "defined": "PayloadTypeLocal" 606 | } 607 | } 608 | ] 609 | } 610 | }, 611 | { 612 | "name": "SeedsVecLocal", 613 | "type": { 614 | "kind": "struct", 615 | "fields": [ 616 | { 617 | "name": "seeds", 618 | "docs": [ 619 | "The vector of derivation seeds." 620 | ], 621 | "type": { 622 | "vec": "bytes" 623 | } 624 | } 625 | ] 626 | } 627 | }, 628 | { 629 | "name": "ProofInfoLocal", 630 | "type": { 631 | "kind": "struct", 632 | "fields": [ 633 | { 634 | "name": "proof", 635 | "docs": [ 636 | "The merkle proof." 637 | ], 638 | "type": { 639 | "vec": { 640 | "array": [ 641 | "u8", 642 | 32 643 | ] 644 | } 645 | } 646 | } 647 | ] 648 | } 649 | }, 650 | { 651 | "name": "PayloadTypeLocal", 652 | "type": { 653 | "kind": "enum", 654 | "variants": [ 655 | { 656 | "name": "Pubkey", 657 | "fields": [ 658 | "publicKey" 659 | ] 660 | }, 661 | { 662 | "name": "Seeds", 663 | "fields": [ 664 | { 665 | "defined": "SeedsVecLocal" 666 | } 667 | ] 668 | }, 669 | { 670 | "name": "MerkleProof", 671 | "fields": [ 672 | { 673 | "defined": "ProofInfoLocal" 674 | } 675 | ] 676 | }, 677 | { 678 | "name": "Number", 679 | "fields": [ 680 | "u64" 681 | ] 682 | } 683 | ] 684 | } 685 | } 686 | ], 687 | "events": [ 688 | { 689 | "name": "BidEvent", 690 | "fields": [ 691 | { 692 | "name": "lamports", 693 | "type": "u64", 694 | "index": false 695 | }, 696 | { 697 | "name": "expiry", 698 | "type": "i64", 699 | "index": false 700 | }, 701 | { 702 | "name": "mint", 703 | "type": "publicKey", 704 | "index": false 705 | }, 706 | { 707 | "name": "bidder", 708 | "type": "publicKey", 709 | "index": false 710 | } 711 | ] 712 | }, 713 | { 714 | "name": "TakeBidEvent", 715 | "fields": [ 716 | { 717 | "name": "lamports", 718 | "type": "u64", 719 | "index": false 720 | }, 721 | { 722 | "name": "tswapFee", 723 | "type": "u64", 724 | "index": false 725 | }, 726 | { 727 | "name": "creatorsFee", 728 | "type": "u64", 729 | "index": false 730 | }, 731 | { 732 | "name": "expiry", 733 | "type": "i64", 734 | "index": false 735 | }, 736 | { 737 | "name": "mint", 738 | "type": "publicKey", 739 | "index": false 740 | }, 741 | { 742 | "name": "bidder", 743 | "type": "publicKey", 744 | "index": false 745 | } 746 | ] 747 | } 748 | ], 749 | "errors": [ 750 | { 751 | "code": 6000, 752 | "name": "BadMargin", 753 | "msg": "bad margin account passed in" 754 | }, 755 | { 756 | "code": 6001, 757 | "name": "ExpiryTooLarge", 758 | "msg": "expiry date too far in the future, max expiry 60d" 759 | }, 760 | { 761 | "code": 6002, 762 | "name": "PriceMismatch", 763 | "msg": "passed in amount doesnt match that stored" 764 | }, 765 | { 766 | "code": 6003, 767 | "name": "BidExpired", 768 | "msg": "bid expired" 769 | }, 770 | { 771 | "code": 6004, 772 | "name": "BidNotYetExpired", 773 | "msg": "bid hasn't reached expiry time yet" 774 | } 775 | ] 776 | }; 777 | 778 | export const IDL: TensorBid = { 779 | "version": "1.0.0", 780 | "name": "tensor_bid", 781 | "constants": [ 782 | { 783 | "name": "CURRENT_TBID_VERSION", 784 | "type": "u8", 785 | "value": "1" 786 | }, 787 | { 788 | "name": "TBID_TAKER_FEE_BPS", 789 | "type": "u16", 790 | "value": "150" 791 | }, 792 | { 793 | "name": "MAX_EXPIRY_SEC", 794 | "type": "i64", 795 | "value": "31536000" 796 | }, 797 | { 798 | "name": "BID_STATE_SIZE", 799 | "type": { 800 | "defined": "usize" 801 | }, 802 | "value": "8 + 1 + 8 + (32 * 2) + 1 + 8 + 33 + 8 + 56" 803 | } 804 | ], 805 | "instructions": [ 806 | { 807 | "name": "bid", 808 | "accounts": [ 809 | { 810 | "name": "nftMint", 811 | "isMut": false, 812 | "isSigner": false 813 | }, 814 | { 815 | "name": "bidState", 816 | "isMut": true, 817 | "isSigner": false 818 | }, 819 | { 820 | "name": "bidder", 821 | "isMut": true, 822 | "isSigner": true 823 | }, 824 | { 825 | "name": "systemProgram", 826 | "isMut": false, 827 | "isSigner": false 828 | }, 829 | { 830 | "name": "rent", 831 | "isMut": false, 832 | "isSigner": false 833 | }, 834 | { 835 | "name": "tswap", 836 | "isMut": false, 837 | "isSigner": false 838 | }, 839 | { 840 | "name": "marginAccount", 841 | "isMut": true, 842 | "isSigner": false 843 | } 844 | ], 845 | "args": [ 846 | { 847 | "name": "lamports", 848 | "type": "u64" 849 | }, 850 | { 851 | "name": "expireInSec", 852 | "type": { 853 | "option": "u64" 854 | } 855 | } 856 | ] 857 | }, 858 | { 859 | "name": "takeBid", 860 | "accounts": [ 861 | { 862 | "name": "tswap", 863 | "isMut": false, 864 | "isSigner": false 865 | }, 866 | { 867 | "name": "feeVault", 868 | "isMut": true, 869 | "isSigner": false 870 | }, 871 | { 872 | "name": "nftMint", 873 | "isMut": false, 874 | "isSigner": false 875 | }, 876 | { 877 | "name": "nftBidderAcc", 878 | "isMut": true, 879 | "isSigner": false 880 | }, 881 | { 882 | "name": "nftSellerAcc", 883 | "isMut": true, 884 | "isSigner": false 885 | }, 886 | { 887 | "name": "nftTempAcc", 888 | "isMut": true, 889 | "isSigner": false 890 | }, 891 | { 892 | "name": "nftMetadata", 893 | "isMut": true, 894 | "isSigner": false 895 | }, 896 | { 897 | "name": "bidState", 898 | "isMut": true, 899 | "isSigner": false 900 | }, 901 | { 902 | "name": "bidder", 903 | "isMut": true, 904 | "isSigner": false 905 | }, 906 | { 907 | "name": "seller", 908 | "isMut": true, 909 | "isSigner": true 910 | }, 911 | { 912 | "name": "tokenProgram", 913 | "isMut": false, 914 | "isSigner": false 915 | }, 916 | { 917 | "name": "associatedTokenProgram", 918 | "isMut": false, 919 | "isSigner": false 920 | }, 921 | { 922 | "name": "systemProgram", 923 | "isMut": false, 924 | "isSigner": false 925 | }, 926 | { 927 | "name": "rent", 928 | "isMut": false, 929 | "isSigner": false 930 | }, 931 | { 932 | "name": "nftEdition", 933 | "isMut": false, 934 | "isSigner": false 935 | }, 936 | { 937 | "name": "sellerTokenRecord", 938 | "isMut": true, 939 | "isSigner": false 940 | }, 941 | { 942 | "name": "bidderTokenRecord", 943 | "isMut": true, 944 | "isSigner": false 945 | }, 946 | { 947 | "name": "tempTokenRecord", 948 | "isMut": true, 949 | "isSigner": false 950 | }, 951 | { 952 | "name": "pnftShared", 953 | "accounts": [ 954 | { 955 | "name": "tokenMetadataProgram", 956 | "isMut": false, 957 | "isSigner": false 958 | }, 959 | { 960 | "name": "instructions", 961 | "isMut": false, 962 | "isSigner": false 963 | }, 964 | { 965 | "name": "authorizationRulesProgram", 966 | "isMut": false, 967 | "isSigner": false 968 | } 969 | ] 970 | }, 971 | { 972 | "name": "tensorswapProgram", 973 | "isMut": false, 974 | "isSigner": false 975 | }, 976 | { 977 | "name": "authRules", 978 | "isMut": false, 979 | "isSigner": false 980 | }, 981 | { 982 | "name": "marginAccount", 983 | "isMut": true, 984 | "isSigner": false 985 | }, 986 | { 987 | "name": "takerBroker", 988 | "isMut": true, 989 | "isSigner": false 990 | } 991 | ], 992 | "args": [ 993 | { 994 | "name": "lamports", 995 | "type": "u64" 996 | }, 997 | { 998 | "name": "rulesAccPresent", 999 | "type": "bool" 1000 | }, 1001 | { 1002 | "name": "authorizationData", 1003 | "type": { 1004 | "option": { 1005 | "defined": "AuthorizationDataLocal" 1006 | } 1007 | } 1008 | }, 1009 | { 1010 | "name": "optionalRoyaltyPct", 1011 | "type": { 1012 | "option": "u16" 1013 | } 1014 | } 1015 | ] 1016 | }, 1017 | { 1018 | "name": "takeBidT22", 1019 | "accounts": [ 1020 | { 1021 | "name": "tswap", 1022 | "isMut": false, 1023 | "isSigner": false 1024 | }, 1025 | { 1026 | "name": "feeVault", 1027 | "isMut": true, 1028 | "isSigner": false 1029 | }, 1030 | { 1031 | "name": "nftMint", 1032 | "isMut": false, 1033 | "isSigner": false 1034 | }, 1035 | { 1036 | "name": "nftBidderAcc", 1037 | "isMut": true, 1038 | "isSigner": false 1039 | }, 1040 | { 1041 | "name": "nftSellerAcc", 1042 | "isMut": true, 1043 | "isSigner": false 1044 | }, 1045 | { 1046 | "name": "bidState", 1047 | "isMut": true, 1048 | "isSigner": false 1049 | }, 1050 | { 1051 | "name": "bidder", 1052 | "isMut": true, 1053 | "isSigner": false 1054 | }, 1055 | { 1056 | "name": "seller", 1057 | "isMut": true, 1058 | "isSigner": true 1059 | }, 1060 | { 1061 | "name": "tokenProgram", 1062 | "isMut": false, 1063 | "isSigner": false 1064 | }, 1065 | { 1066 | "name": "associatedTokenProgram", 1067 | "isMut": false, 1068 | "isSigner": false 1069 | }, 1070 | { 1071 | "name": "systemProgram", 1072 | "isMut": false, 1073 | "isSigner": false 1074 | }, 1075 | { 1076 | "name": "rent", 1077 | "isMut": false, 1078 | "isSigner": false 1079 | }, 1080 | { 1081 | "name": "tensorswapProgram", 1082 | "isMut": false, 1083 | "isSigner": false 1084 | }, 1085 | { 1086 | "name": "marginAccount", 1087 | "isMut": true, 1088 | "isSigner": false 1089 | }, 1090 | { 1091 | "name": "takerBroker", 1092 | "isMut": true, 1093 | "isSigner": false 1094 | } 1095 | ], 1096 | "args": [ 1097 | { 1098 | "name": "lamports", 1099 | "type": "u64" 1100 | } 1101 | ] 1102 | }, 1103 | { 1104 | "name": "wnsTakeBid", 1105 | "accounts": [ 1106 | { 1107 | "name": "tswap", 1108 | "isMut": false, 1109 | "isSigner": false 1110 | }, 1111 | { 1112 | "name": "feeVault", 1113 | "isMut": true, 1114 | "isSigner": false 1115 | }, 1116 | { 1117 | "name": "nftMint", 1118 | "isMut": false, 1119 | "isSigner": false 1120 | }, 1121 | { 1122 | "name": "nftBidderAcc", 1123 | "isMut": true, 1124 | "isSigner": false 1125 | }, 1126 | { 1127 | "name": "nftSellerAcc", 1128 | "isMut": true, 1129 | "isSigner": false 1130 | }, 1131 | { 1132 | "name": "bidState", 1133 | "isMut": true, 1134 | "isSigner": false 1135 | }, 1136 | { 1137 | "name": "bidder", 1138 | "isMut": true, 1139 | "isSigner": false 1140 | }, 1141 | { 1142 | "name": "seller", 1143 | "isMut": true, 1144 | "isSigner": true 1145 | }, 1146 | { 1147 | "name": "tokenProgram", 1148 | "isMut": false, 1149 | "isSigner": false 1150 | }, 1151 | { 1152 | "name": "associatedTokenProgram", 1153 | "isMut": false, 1154 | "isSigner": false 1155 | }, 1156 | { 1157 | "name": "systemProgram", 1158 | "isMut": false, 1159 | "isSigner": false 1160 | }, 1161 | { 1162 | "name": "rent", 1163 | "isMut": false, 1164 | "isSigner": false 1165 | }, 1166 | { 1167 | "name": "tensorswapProgram", 1168 | "isMut": false, 1169 | "isSigner": false 1170 | }, 1171 | { 1172 | "name": "marginAccount", 1173 | "isMut": true, 1174 | "isSigner": false 1175 | }, 1176 | { 1177 | "name": "takerBroker", 1178 | "isMut": true, 1179 | "isSigner": false 1180 | }, 1181 | { 1182 | "name": "approveAccount", 1183 | "isMut": true, 1184 | "isSigner": false 1185 | }, 1186 | { 1187 | "name": "distribution", 1188 | "isMut": true, 1189 | "isSigner": false 1190 | }, 1191 | { 1192 | "name": "wnsProgram", 1193 | "isMut": false, 1194 | "isSigner": false 1195 | }, 1196 | { 1197 | "name": "distributionProgram", 1198 | "isMut": false, 1199 | "isSigner": false 1200 | }, 1201 | { 1202 | "name": "extraMetas", 1203 | "isMut": false, 1204 | "isSigner": false 1205 | } 1206 | ], 1207 | "args": [ 1208 | { 1209 | "name": "lamports", 1210 | "type": "u64" 1211 | } 1212 | ] 1213 | }, 1214 | { 1215 | "name": "cancelBid", 1216 | "accounts": [ 1217 | { 1218 | "name": "nftMint", 1219 | "isMut": false, 1220 | "isSigner": false 1221 | }, 1222 | { 1223 | "name": "bidState", 1224 | "isMut": true, 1225 | "isSigner": false 1226 | }, 1227 | { 1228 | "name": "bidder", 1229 | "isMut": true, 1230 | "isSigner": true 1231 | }, 1232 | { 1233 | "name": "systemProgram", 1234 | "isMut": false, 1235 | "isSigner": false 1236 | }, 1237 | { 1238 | "name": "rent", 1239 | "isMut": false, 1240 | "isSigner": false 1241 | } 1242 | ], 1243 | "args": [] 1244 | }, 1245 | { 1246 | "name": "closeExpiredBid", 1247 | "accounts": [ 1248 | { 1249 | "name": "nftMint", 1250 | "isMut": false, 1251 | "isSigner": false 1252 | }, 1253 | { 1254 | "name": "bidState", 1255 | "isMut": true, 1256 | "isSigner": false 1257 | }, 1258 | { 1259 | "name": "bidder", 1260 | "isMut": true, 1261 | "isSigner": false 1262 | }, 1263 | { 1264 | "name": "systemProgram", 1265 | "isMut": false, 1266 | "isSigner": false 1267 | }, 1268 | { 1269 | "name": "rent", 1270 | "isMut": false, 1271 | "isSigner": false 1272 | } 1273 | ], 1274 | "args": [] 1275 | } 1276 | ], 1277 | "accounts": [ 1278 | { 1279 | "name": "bidState", 1280 | "type": { 1281 | "kind": "struct", 1282 | "fields": [ 1283 | { 1284 | "name": "version", 1285 | "type": "u8" 1286 | }, 1287 | { 1288 | "name": "bidAmount", 1289 | "type": "u64" 1290 | }, 1291 | { 1292 | "name": "nftMint", 1293 | "type": "publicKey" 1294 | }, 1295 | { 1296 | "name": "bidder", 1297 | "type": "publicKey" 1298 | }, 1299 | { 1300 | "name": "bump", 1301 | "type": { 1302 | "array": [ 1303 | "u8", 1304 | 1 1305 | ] 1306 | } 1307 | }, 1308 | { 1309 | "name": "expiry", 1310 | "type": "i64" 1311 | }, 1312 | { 1313 | "name": "margin", 1314 | "type": { 1315 | "option": "publicKey" 1316 | } 1317 | }, 1318 | { 1319 | "name": "updatedAt", 1320 | "type": "i64" 1321 | }, 1322 | { 1323 | "name": "reserved", 1324 | "type": { 1325 | "array": [ 1326 | "u8", 1327 | 8 1328 | ] 1329 | } 1330 | }, 1331 | { 1332 | "name": "reserved1", 1333 | "type": { 1334 | "array": [ 1335 | "u8", 1336 | 16 1337 | ] 1338 | } 1339 | }, 1340 | { 1341 | "name": "reserved2", 1342 | "type": { 1343 | "array": [ 1344 | "u8", 1345 | 32 1346 | ] 1347 | } 1348 | } 1349 | ] 1350 | } 1351 | } 1352 | ], 1353 | "types": [ 1354 | { 1355 | "name": "AuthorizationDataLocal", 1356 | "type": { 1357 | "kind": "struct", 1358 | "fields": [ 1359 | { 1360 | "name": "payload", 1361 | "type": { 1362 | "vec": { 1363 | "defined": "TaggedPayload" 1364 | } 1365 | } 1366 | } 1367 | ] 1368 | } 1369 | }, 1370 | { 1371 | "name": "TaggedPayload", 1372 | "type": { 1373 | "kind": "struct", 1374 | "fields": [ 1375 | { 1376 | "name": "name", 1377 | "type": "string" 1378 | }, 1379 | { 1380 | "name": "payload", 1381 | "type": { 1382 | "defined": "PayloadTypeLocal" 1383 | } 1384 | } 1385 | ] 1386 | } 1387 | }, 1388 | { 1389 | "name": "SeedsVecLocal", 1390 | "type": { 1391 | "kind": "struct", 1392 | "fields": [ 1393 | { 1394 | "name": "seeds", 1395 | "docs": [ 1396 | "The vector of derivation seeds." 1397 | ], 1398 | "type": { 1399 | "vec": "bytes" 1400 | } 1401 | } 1402 | ] 1403 | } 1404 | }, 1405 | { 1406 | "name": "ProofInfoLocal", 1407 | "type": { 1408 | "kind": "struct", 1409 | "fields": [ 1410 | { 1411 | "name": "proof", 1412 | "docs": [ 1413 | "The merkle proof." 1414 | ], 1415 | "type": { 1416 | "vec": { 1417 | "array": [ 1418 | "u8", 1419 | 32 1420 | ] 1421 | } 1422 | } 1423 | } 1424 | ] 1425 | } 1426 | }, 1427 | { 1428 | "name": "PayloadTypeLocal", 1429 | "type": { 1430 | "kind": "enum", 1431 | "variants": [ 1432 | { 1433 | "name": "Pubkey", 1434 | "fields": [ 1435 | "publicKey" 1436 | ] 1437 | }, 1438 | { 1439 | "name": "Seeds", 1440 | "fields": [ 1441 | { 1442 | "defined": "SeedsVecLocal" 1443 | } 1444 | ] 1445 | }, 1446 | { 1447 | "name": "MerkleProof", 1448 | "fields": [ 1449 | { 1450 | "defined": "ProofInfoLocal" 1451 | } 1452 | ] 1453 | }, 1454 | { 1455 | "name": "Number", 1456 | "fields": [ 1457 | "u64" 1458 | ] 1459 | } 1460 | ] 1461 | } 1462 | } 1463 | ], 1464 | "events": [ 1465 | { 1466 | "name": "BidEvent", 1467 | "fields": [ 1468 | { 1469 | "name": "lamports", 1470 | "type": "u64", 1471 | "index": false 1472 | }, 1473 | { 1474 | "name": "expiry", 1475 | "type": "i64", 1476 | "index": false 1477 | }, 1478 | { 1479 | "name": "mint", 1480 | "type": "publicKey", 1481 | "index": false 1482 | }, 1483 | { 1484 | "name": "bidder", 1485 | "type": "publicKey", 1486 | "index": false 1487 | } 1488 | ] 1489 | }, 1490 | { 1491 | "name": "TakeBidEvent", 1492 | "fields": [ 1493 | { 1494 | "name": "lamports", 1495 | "type": "u64", 1496 | "index": false 1497 | }, 1498 | { 1499 | "name": "tswapFee", 1500 | "type": "u64", 1501 | "index": false 1502 | }, 1503 | { 1504 | "name": "creatorsFee", 1505 | "type": "u64", 1506 | "index": false 1507 | }, 1508 | { 1509 | "name": "expiry", 1510 | "type": "i64", 1511 | "index": false 1512 | }, 1513 | { 1514 | "name": "mint", 1515 | "type": "publicKey", 1516 | "index": false 1517 | }, 1518 | { 1519 | "name": "bidder", 1520 | "type": "publicKey", 1521 | "index": false 1522 | } 1523 | ] 1524 | } 1525 | ], 1526 | "errors": [ 1527 | { 1528 | "code": 6000, 1529 | "name": "BadMargin", 1530 | "msg": "bad margin account passed in" 1531 | }, 1532 | { 1533 | "code": 6001, 1534 | "name": "ExpiryTooLarge", 1535 | "msg": "expiry date too far in the future, max expiry 60d" 1536 | }, 1537 | { 1538 | "code": 6002, 1539 | "name": "PriceMismatch", 1540 | "msg": "passed in amount doesnt match that stored" 1541 | }, 1542 | { 1543 | "code": 6003, 1544 | "name": "BidExpired", 1545 | "msg": "bid expired" 1546 | }, 1547 | { 1548 | "code": 6004, 1549 | "name": "BidNotYetExpired", 1550 | "msg": "bid hasn't reached expiry time yet" 1551 | } 1552 | ] 1553 | }; 1554 | -------------------------------------------------------------------------------- /src/tensor_bid/idl/tensor_bid_v0_1_0.ts: -------------------------------------------------------------------------------- 1 | export type TensorBid = { 2 | version: "0.1.0"; 3 | name: "tensor_bid"; 4 | constants: [ 5 | { 6 | name: "CURRENT_TBID_VERSION"; 7 | type: "u8"; 8 | value: "1"; 9 | }, 10 | { 11 | name: "TBID_FEE_BPS"; 12 | type: "u16"; 13 | value: "100"; 14 | }, 15 | { 16 | name: "MAX_EXPIRY_SEC"; 17 | type: "i64"; 18 | value: "5184000"; 19 | }, 20 | { 21 | name: "BID_STATE_SIZE"; 22 | type: { 23 | defined: "usize"; 24 | }; 25 | value: "8 + 1 + 8 + (32 * 2) + 1 + 8 + 33 + 64"; 26 | } 27 | ]; 28 | instructions: [ 29 | { 30 | name: "bid"; 31 | accounts: [ 32 | { 33 | name: "nftMint"; 34 | isMut: false; 35 | isSigner: false; 36 | }, 37 | { 38 | name: "bidState"; 39 | isMut: true; 40 | isSigner: false; 41 | }, 42 | { 43 | name: "bidder"; 44 | isMut: true; 45 | isSigner: true; 46 | }, 47 | { 48 | name: "systemProgram"; 49 | isMut: false; 50 | isSigner: false; 51 | }, 52 | { 53 | name: "rent"; 54 | isMut: false; 55 | isSigner: false; 56 | }, 57 | { 58 | name: "tswap"; 59 | isMut: false; 60 | isSigner: false; 61 | }, 62 | { 63 | name: "marginAccount"; 64 | isMut: true; 65 | isSigner: false; 66 | } 67 | ]; 68 | args: [ 69 | { 70 | name: "lamports"; 71 | type: "u64"; 72 | }, 73 | { 74 | name: "expireInSec"; 75 | type: { 76 | option: "u64"; 77 | }; 78 | }, 79 | { 80 | name: "fundMargin"; 81 | type: "bool"; 82 | } 83 | ]; 84 | }, 85 | { 86 | name: "takeBid"; 87 | accounts: [ 88 | { 89 | name: "tswap"; 90 | isMut: false; 91 | isSigner: false; 92 | }, 93 | { 94 | name: "feeVault"; 95 | isMut: true; 96 | isSigner: false; 97 | }, 98 | { 99 | name: "nftMint"; 100 | isMut: false; 101 | isSigner: false; 102 | }, 103 | { 104 | name: "nftBidderAcc"; 105 | isMut: true; 106 | isSigner: false; 107 | }, 108 | { 109 | name: "nftSellerAcc"; 110 | isMut: true; 111 | isSigner: false; 112 | }, 113 | { 114 | name: "nftTempAcc"; 115 | isMut: true; 116 | isSigner: false; 117 | }, 118 | { 119 | name: "nftMetadata"; 120 | isMut: true; 121 | isSigner: false; 122 | }, 123 | { 124 | name: "bidState"; 125 | isMut: true; 126 | isSigner: false; 127 | }, 128 | { 129 | name: "bidder"; 130 | isMut: true; 131 | isSigner: false; 132 | }, 133 | { 134 | name: "seller"; 135 | isMut: true; 136 | isSigner: true; 137 | }, 138 | { 139 | name: "tokenProgram"; 140 | isMut: false; 141 | isSigner: false; 142 | }, 143 | { 144 | name: "associatedTokenProgram"; 145 | isMut: false; 146 | isSigner: false; 147 | }, 148 | { 149 | name: "systemProgram"; 150 | isMut: false; 151 | isSigner: false; 152 | }, 153 | { 154 | name: "rent"; 155 | isMut: false; 156 | isSigner: false; 157 | }, 158 | { 159 | name: "nftEdition"; 160 | isMut: false; 161 | isSigner: false; 162 | }, 163 | { 164 | name: "sellerTokenRecord"; 165 | isMut: true; 166 | isSigner: false; 167 | }, 168 | { 169 | name: "bidderTokenRecord"; 170 | isMut: true; 171 | isSigner: false; 172 | }, 173 | { 174 | name: "tempTokenRecord"; 175 | isMut: true; 176 | isSigner: false; 177 | }, 178 | { 179 | name: "pnftShared"; 180 | accounts: [ 181 | { 182 | name: "tokenMetadataProgram"; 183 | isMut: false; 184 | isSigner: false; 185 | }, 186 | { 187 | name: "instructions"; 188 | isMut: false; 189 | isSigner: false; 190 | }, 191 | { 192 | name: "authorizationRulesProgram"; 193 | isMut: false; 194 | isSigner: false; 195 | } 196 | ]; 197 | }, 198 | { 199 | name: "tensorswapProgram"; 200 | isMut: false; 201 | isSigner: false; 202 | }, 203 | { 204 | name: "authRules"; 205 | isMut: false; 206 | isSigner: false; 207 | }, 208 | { 209 | name: "marginAccount"; 210 | isMut: true; 211 | isSigner: false; 212 | }, 213 | { 214 | name: "takerBroker"; 215 | isMut: true; 216 | isSigner: false; 217 | } 218 | ]; 219 | args: [ 220 | { 221 | name: "lamports"; 222 | type: "u64"; 223 | }, 224 | { 225 | name: "rulesAccPresent"; 226 | type: "bool"; 227 | }, 228 | { 229 | name: "authorizationData"; 230 | type: { 231 | option: { 232 | defined: "AuthorizationDataLocal"; 233 | }; 234 | }; 235 | }, 236 | { 237 | name: "optionalRoyaltyPct"; 238 | type: { 239 | option: "u16"; 240 | }; 241 | } 242 | ]; 243 | }, 244 | { 245 | name: "cancelBid"; 246 | accounts: [ 247 | { 248 | name: "nftMint"; 249 | isMut: false; 250 | isSigner: false; 251 | }, 252 | { 253 | name: "bidState"; 254 | isMut: true; 255 | isSigner: false; 256 | }, 257 | { 258 | name: "bidder"; 259 | isMut: true; 260 | isSigner: true; 261 | }, 262 | { 263 | name: "systemProgram"; 264 | isMut: false; 265 | isSigner: false; 266 | }, 267 | { 268 | name: "rent"; 269 | isMut: false; 270 | isSigner: false; 271 | } 272 | ]; 273 | args: []; 274 | }, 275 | { 276 | name: "closeExpiredBid"; 277 | accounts: [ 278 | { 279 | name: "nftMint"; 280 | isMut: false; 281 | isSigner: false; 282 | }, 283 | { 284 | name: "bidState"; 285 | isMut: true; 286 | isSigner: false; 287 | }, 288 | { 289 | name: "bidder"; 290 | isMut: true; 291 | isSigner: false; 292 | }, 293 | { 294 | name: "tswap"; 295 | isMut: false; 296 | isSigner: false; 297 | }, 298 | { 299 | name: "cosigner"; 300 | isMut: false; 301 | isSigner: true; 302 | }, 303 | { 304 | name: "systemProgram"; 305 | isMut: false; 306 | isSigner: false; 307 | }, 308 | { 309 | name: "rent"; 310 | isMut: false; 311 | isSigner: false; 312 | } 313 | ]; 314 | args: []; 315 | } 316 | ]; 317 | accounts: [ 318 | { 319 | name: "bidState"; 320 | type: { 321 | kind: "struct"; 322 | fields: [ 323 | { 324 | name: "version"; 325 | type: "u8"; 326 | }, 327 | { 328 | name: "bidAmount"; 329 | type: "u64"; 330 | }, 331 | { 332 | name: "nftMint"; 333 | type: "publicKey"; 334 | }, 335 | { 336 | name: "bidder"; 337 | type: "publicKey"; 338 | }, 339 | { 340 | name: "bump"; 341 | type: { 342 | array: ["u8", 1]; 343 | }; 344 | }, 345 | { 346 | name: "expiry"; 347 | type: "i64"; 348 | }, 349 | { 350 | name: "margin"; 351 | type: { 352 | option: "publicKey"; 353 | }; 354 | }, 355 | { 356 | name: "reserved"; 357 | type: { 358 | array: ["u8", 64]; 359 | }; 360 | } 361 | ]; 362 | }; 363 | } 364 | ]; 365 | types: [ 366 | { 367 | name: "AuthorizationDataLocal"; 368 | type: { 369 | kind: "struct"; 370 | fields: [ 371 | { 372 | name: "payload"; 373 | type: { 374 | vec: { 375 | defined: "TaggedPayload"; 376 | }; 377 | }; 378 | } 379 | ]; 380 | }; 381 | }, 382 | { 383 | name: "TaggedPayload"; 384 | type: { 385 | kind: "struct"; 386 | fields: [ 387 | { 388 | name: "name"; 389 | type: "string"; 390 | }, 391 | { 392 | name: "payload"; 393 | type: { 394 | defined: "PayloadTypeLocal"; 395 | }; 396 | } 397 | ]; 398 | }; 399 | }, 400 | { 401 | name: "SeedsVecLocal"; 402 | type: { 403 | kind: "struct"; 404 | fields: [ 405 | { 406 | name: "seeds"; 407 | docs: ["The vector of derivation seeds."]; 408 | type: { 409 | vec: "bytes"; 410 | }; 411 | } 412 | ]; 413 | }; 414 | }, 415 | { 416 | name: "ProofInfoLocal"; 417 | type: { 418 | kind: "struct"; 419 | fields: [ 420 | { 421 | name: "proof"; 422 | docs: ["The merkle proof."]; 423 | type: { 424 | vec: { 425 | array: ["u8", 32]; 426 | }; 427 | }; 428 | } 429 | ]; 430 | }; 431 | }, 432 | { 433 | name: "PayloadTypeLocal"; 434 | type: { 435 | kind: "enum"; 436 | variants: [ 437 | { 438 | name: "Pubkey"; 439 | fields: ["publicKey"]; 440 | }, 441 | { 442 | name: "Seeds"; 443 | fields: [ 444 | { 445 | defined: "SeedsVecLocal"; 446 | } 447 | ]; 448 | }, 449 | { 450 | name: "MerkleProof"; 451 | fields: [ 452 | { 453 | defined: "ProofInfoLocal"; 454 | } 455 | ]; 456 | }, 457 | { 458 | name: "Number"; 459 | fields: ["u64"]; 460 | } 461 | ]; 462 | }; 463 | } 464 | ]; 465 | events: [ 466 | { 467 | name: "BidEvent"; 468 | fields: [ 469 | { 470 | name: "lamports"; 471 | type: "u64"; 472 | index: false; 473 | }, 474 | { 475 | name: "expiry"; 476 | type: "i64"; 477 | index: false; 478 | }, 479 | { 480 | name: "mint"; 481 | type: "publicKey"; 482 | index: false; 483 | }, 484 | { 485 | name: "bidder"; 486 | type: "publicKey"; 487 | index: false; 488 | } 489 | ]; 490 | }, 491 | { 492 | name: "TakeBidEvent"; 493 | fields: [ 494 | { 495 | name: "lamports"; 496 | type: "u64"; 497 | index: false; 498 | }, 499 | { 500 | name: "tswapFee"; 501 | type: "u64"; 502 | index: false; 503 | }, 504 | { 505 | name: "creatorsFee"; 506 | type: "u64"; 507 | index: false; 508 | }, 509 | { 510 | name: "expiry"; 511 | type: "i64"; 512 | index: false; 513 | }, 514 | { 515 | name: "mint"; 516 | type: "publicKey"; 517 | index: false; 518 | }, 519 | { 520 | name: "bidder"; 521 | type: "publicKey"; 522 | index: false; 523 | } 524 | ]; 525 | } 526 | ]; 527 | errors: [ 528 | { 529 | code: 6000; 530 | name: "BadMargin"; 531 | msg: "bad margin account passed in"; 532 | }, 533 | { 534 | code: 6001; 535 | name: "ExpiryTooLarge"; 536 | msg: "expiry date too far in the future, max expiry 60d"; 537 | }, 538 | { 539 | code: 6002; 540 | name: "PriceMismatch"; 541 | msg: "passed in amount doesnt match that stored"; 542 | }, 543 | { 544 | code: 6003; 545 | name: "BidExpired"; 546 | msg: "bid expired"; 547 | }, 548 | { 549 | code: 6004; 550 | name: "BidNotYetExpired"; 551 | msg: "bid hasn't reached expiry time yet"; 552 | } 553 | ]; 554 | }; 555 | 556 | export const IDL: TensorBid = { 557 | version: "0.1.0", 558 | name: "tensor_bid", 559 | constants: [ 560 | { 561 | name: "CURRENT_TBID_VERSION", 562 | type: "u8", 563 | value: "1", 564 | }, 565 | { 566 | name: "TBID_FEE_BPS", 567 | type: "u16", 568 | value: "100", 569 | }, 570 | { 571 | name: "MAX_EXPIRY_SEC", 572 | type: "i64", 573 | value: "5184000", 574 | }, 575 | { 576 | name: "BID_STATE_SIZE", 577 | type: { 578 | defined: "usize", 579 | }, 580 | value: "8 + 1 + 8 + (32 * 2) + 1 + 8 + 33 + 64", 581 | }, 582 | ], 583 | instructions: [ 584 | { 585 | name: "bid", 586 | accounts: [ 587 | { 588 | name: "nftMint", 589 | isMut: false, 590 | isSigner: false, 591 | }, 592 | { 593 | name: "bidState", 594 | isMut: true, 595 | isSigner: false, 596 | }, 597 | { 598 | name: "bidder", 599 | isMut: true, 600 | isSigner: true, 601 | }, 602 | { 603 | name: "systemProgram", 604 | isMut: false, 605 | isSigner: false, 606 | }, 607 | { 608 | name: "rent", 609 | isMut: false, 610 | isSigner: false, 611 | }, 612 | { 613 | name: "tswap", 614 | isMut: false, 615 | isSigner: false, 616 | }, 617 | { 618 | name: "marginAccount", 619 | isMut: true, 620 | isSigner: false, 621 | }, 622 | ], 623 | args: [ 624 | { 625 | name: "lamports", 626 | type: "u64", 627 | }, 628 | { 629 | name: "expireInSec", 630 | type: { 631 | option: "u64", 632 | }, 633 | }, 634 | { 635 | name: "fundMargin", 636 | type: "bool", 637 | }, 638 | ], 639 | }, 640 | { 641 | name: "takeBid", 642 | accounts: [ 643 | { 644 | name: "tswap", 645 | isMut: false, 646 | isSigner: false, 647 | }, 648 | { 649 | name: "feeVault", 650 | isMut: true, 651 | isSigner: false, 652 | }, 653 | { 654 | name: "nftMint", 655 | isMut: false, 656 | isSigner: false, 657 | }, 658 | { 659 | name: "nftBidderAcc", 660 | isMut: true, 661 | isSigner: false, 662 | }, 663 | { 664 | name: "nftSellerAcc", 665 | isMut: true, 666 | isSigner: false, 667 | }, 668 | { 669 | name: "nftTempAcc", 670 | isMut: true, 671 | isSigner: false, 672 | }, 673 | { 674 | name: "nftMetadata", 675 | isMut: true, 676 | isSigner: false, 677 | }, 678 | { 679 | name: "bidState", 680 | isMut: true, 681 | isSigner: false, 682 | }, 683 | { 684 | name: "bidder", 685 | isMut: true, 686 | isSigner: false, 687 | }, 688 | { 689 | name: "seller", 690 | isMut: true, 691 | isSigner: true, 692 | }, 693 | { 694 | name: "tokenProgram", 695 | isMut: false, 696 | isSigner: false, 697 | }, 698 | { 699 | name: "associatedTokenProgram", 700 | isMut: false, 701 | isSigner: false, 702 | }, 703 | { 704 | name: "systemProgram", 705 | isMut: false, 706 | isSigner: false, 707 | }, 708 | { 709 | name: "rent", 710 | isMut: false, 711 | isSigner: false, 712 | }, 713 | { 714 | name: "nftEdition", 715 | isMut: false, 716 | isSigner: false, 717 | }, 718 | { 719 | name: "sellerTokenRecord", 720 | isMut: true, 721 | isSigner: false, 722 | }, 723 | { 724 | name: "bidderTokenRecord", 725 | isMut: true, 726 | isSigner: false, 727 | }, 728 | { 729 | name: "tempTokenRecord", 730 | isMut: true, 731 | isSigner: false, 732 | }, 733 | { 734 | name: "pnftShared", 735 | accounts: [ 736 | { 737 | name: "tokenMetadataProgram", 738 | isMut: false, 739 | isSigner: false, 740 | }, 741 | { 742 | name: "instructions", 743 | isMut: false, 744 | isSigner: false, 745 | }, 746 | { 747 | name: "authorizationRulesProgram", 748 | isMut: false, 749 | isSigner: false, 750 | }, 751 | ], 752 | }, 753 | { 754 | name: "tensorswapProgram", 755 | isMut: false, 756 | isSigner: false, 757 | }, 758 | { 759 | name: "authRules", 760 | isMut: false, 761 | isSigner: false, 762 | }, 763 | { 764 | name: "marginAccount", 765 | isMut: true, 766 | isSigner: false, 767 | }, 768 | { 769 | name: "takerBroker", 770 | isMut: true, 771 | isSigner: false, 772 | }, 773 | ], 774 | args: [ 775 | { 776 | name: "lamports", 777 | type: "u64", 778 | }, 779 | { 780 | name: "rulesAccPresent", 781 | type: "bool", 782 | }, 783 | { 784 | name: "authorizationData", 785 | type: { 786 | option: { 787 | defined: "AuthorizationDataLocal", 788 | }, 789 | }, 790 | }, 791 | { 792 | name: "optionalRoyaltyPct", 793 | type: { 794 | option: "u16", 795 | }, 796 | }, 797 | ], 798 | }, 799 | { 800 | name: "cancelBid", 801 | accounts: [ 802 | { 803 | name: "nftMint", 804 | isMut: false, 805 | isSigner: false, 806 | }, 807 | { 808 | name: "bidState", 809 | isMut: true, 810 | isSigner: false, 811 | }, 812 | { 813 | name: "bidder", 814 | isMut: true, 815 | isSigner: true, 816 | }, 817 | { 818 | name: "systemProgram", 819 | isMut: false, 820 | isSigner: false, 821 | }, 822 | { 823 | name: "rent", 824 | isMut: false, 825 | isSigner: false, 826 | }, 827 | ], 828 | args: [], 829 | }, 830 | { 831 | name: "closeExpiredBid", 832 | accounts: [ 833 | { 834 | name: "nftMint", 835 | isMut: false, 836 | isSigner: false, 837 | }, 838 | { 839 | name: "bidState", 840 | isMut: true, 841 | isSigner: false, 842 | }, 843 | { 844 | name: "bidder", 845 | isMut: true, 846 | isSigner: false, 847 | }, 848 | { 849 | name: "tswap", 850 | isMut: false, 851 | isSigner: false, 852 | }, 853 | { 854 | name: "cosigner", 855 | isMut: false, 856 | isSigner: true, 857 | }, 858 | { 859 | name: "systemProgram", 860 | isMut: false, 861 | isSigner: false, 862 | }, 863 | { 864 | name: "rent", 865 | isMut: false, 866 | isSigner: false, 867 | }, 868 | ], 869 | args: [], 870 | }, 871 | ], 872 | accounts: [ 873 | { 874 | name: "bidState", 875 | type: { 876 | kind: "struct", 877 | fields: [ 878 | { 879 | name: "version", 880 | type: "u8", 881 | }, 882 | { 883 | name: "bidAmount", 884 | type: "u64", 885 | }, 886 | { 887 | name: "nftMint", 888 | type: "publicKey", 889 | }, 890 | { 891 | name: "bidder", 892 | type: "publicKey", 893 | }, 894 | { 895 | name: "bump", 896 | type: { 897 | array: ["u8", 1], 898 | }, 899 | }, 900 | { 901 | name: "expiry", 902 | type: "i64", 903 | }, 904 | { 905 | name: "margin", 906 | type: { 907 | option: "publicKey", 908 | }, 909 | }, 910 | { 911 | name: "reserved", 912 | type: { 913 | array: ["u8", 64], 914 | }, 915 | }, 916 | ], 917 | }, 918 | }, 919 | ], 920 | types: [ 921 | { 922 | name: "AuthorizationDataLocal", 923 | type: { 924 | kind: "struct", 925 | fields: [ 926 | { 927 | name: "payload", 928 | type: { 929 | vec: { 930 | defined: "TaggedPayload", 931 | }, 932 | }, 933 | }, 934 | ], 935 | }, 936 | }, 937 | { 938 | name: "TaggedPayload", 939 | type: { 940 | kind: "struct", 941 | fields: [ 942 | { 943 | name: "name", 944 | type: "string", 945 | }, 946 | { 947 | name: "payload", 948 | type: { 949 | defined: "PayloadTypeLocal", 950 | }, 951 | }, 952 | ], 953 | }, 954 | }, 955 | { 956 | name: "SeedsVecLocal", 957 | type: { 958 | kind: "struct", 959 | fields: [ 960 | { 961 | name: "seeds", 962 | docs: ["The vector of derivation seeds."], 963 | type: { 964 | vec: "bytes", 965 | }, 966 | }, 967 | ], 968 | }, 969 | }, 970 | { 971 | name: "ProofInfoLocal", 972 | type: { 973 | kind: "struct", 974 | fields: [ 975 | { 976 | name: "proof", 977 | docs: ["The merkle proof."], 978 | type: { 979 | vec: { 980 | array: ["u8", 32], 981 | }, 982 | }, 983 | }, 984 | ], 985 | }, 986 | }, 987 | { 988 | name: "PayloadTypeLocal", 989 | type: { 990 | kind: "enum", 991 | variants: [ 992 | { 993 | name: "Pubkey", 994 | fields: ["publicKey"], 995 | }, 996 | { 997 | name: "Seeds", 998 | fields: [ 999 | { 1000 | defined: "SeedsVecLocal", 1001 | }, 1002 | ], 1003 | }, 1004 | { 1005 | name: "MerkleProof", 1006 | fields: [ 1007 | { 1008 | defined: "ProofInfoLocal", 1009 | }, 1010 | ], 1011 | }, 1012 | { 1013 | name: "Number", 1014 | fields: ["u64"], 1015 | }, 1016 | ], 1017 | }, 1018 | }, 1019 | ], 1020 | events: [ 1021 | { 1022 | name: "BidEvent", 1023 | fields: [ 1024 | { 1025 | name: "lamports", 1026 | type: "u64", 1027 | index: false, 1028 | }, 1029 | { 1030 | name: "expiry", 1031 | type: "i64", 1032 | index: false, 1033 | }, 1034 | { 1035 | name: "mint", 1036 | type: "publicKey", 1037 | index: false, 1038 | }, 1039 | { 1040 | name: "bidder", 1041 | type: "publicKey", 1042 | index: false, 1043 | }, 1044 | ], 1045 | }, 1046 | { 1047 | name: "TakeBidEvent", 1048 | fields: [ 1049 | { 1050 | name: "lamports", 1051 | type: "u64", 1052 | index: false, 1053 | }, 1054 | { 1055 | name: "tswapFee", 1056 | type: "u64", 1057 | index: false, 1058 | }, 1059 | { 1060 | name: "creatorsFee", 1061 | type: "u64", 1062 | index: false, 1063 | }, 1064 | { 1065 | name: "expiry", 1066 | type: "i64", 1067 | index: false, 1068 | }, 1069 | { 1070 | name: "mint", 1071 | type: "publicKey", 1072 | index: false, 1073 | }, 1074 | { 1075 | name: "bidder", 1076 | type: "publicKey", 1077 | index: false, 1078 | }, 1079 | ], 1080 | }, 1081 | ], 1082 | errors: [ 1083 | { 1084 | code: 6000, 1085 | name: "BadMargin", 1086 | msg: "bad margin account passed in", 1087 | }, 1088 | { 1089 | code: 6001, 1090 | name: "ExpiryTooLarge", 1091 | msg: "expiry date too far in the future, max expiry 60d", 1092 | }, 1093 | { 1094 | code: 6002, 1095 | name: "PriceMismatch", 1096 | msg: "passed in amount doesnt match that stored", 1097 | }, 1098 | { 1099 | code: 6003, 1100 | name: "BidExpired", 1101 | msg: "bid expired", 1102 | }, 1103 | { 1104 | code: 6004, 1105 | name: "BidNotYetExpired", 1106 | msg: "bid hasn't reached expiry time yet", 1107 | }, 1108 | ], 1109 | }; 1110 | -------------------------------------------------------------------------------- /src/tensor_bid/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./constants"; 2 | export * from "./sdk"; 3 | export * from "./pda"; 4 | -------------------------------------------------------------------------------- /src/tensor_bid/pda.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { TBID_ADDR } from "./constants"; 3 | import { TENSORSWAP_ADDR } from "../tensorswap"; 4 | 5 | export const findBidStatePda = ({ 6 | program, 7 | mint, 8 | owner, 9 | }: { 10 | program?: PublicKey; 11 | mint: PublicKey; 12 | owner: PublicKey; 13 | }) => { 14 | return PublicKey.findProgramAddressSync( 15 | [Buffer.from("bid_state"), owner.toBytes(), mint.toBytes()], 16 | program ?? TBID_ADDR 17 | ); 18 | }; 19 | 20 | export const findNftTempPDA = ({ 21 | program, 22 | nftMint, 23 | }: { 24 | program?: PublicKey; 25 | nftMint: PublicKey; 26 | }) => { 27 | return PublicKey.findProgramAddressSync( 28 | [Buffer.from("nft_temp_acc"), nftMint.toBytes()], 29 | program ?? TBID_ADDR 30 | ); 31 | }; 32 | -------------------------------------------------------------------------------- /src/tensor_bid/sdk.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AnchorProvider, 3 | BorshCoder, 4 | Coder, 5 | EventParser, 6 | Instruction, 7 | Program, 8 | } from "@coral-xyz/anchor"; 9 | import { 10 | ASSOCIATED_TOKEN_PROGRAM_ID, 11 | getAssociatedTokenAddressSync, 12 | getExtraAccountMetaAddress, 13 | TOKEN_2022_PROGRAM_ID, 14 | } from "@solana/spl-token"; 15 | import { 16 | AccountInfo, 17 | AccountMeta, 18 | Commitment, 19 | PublicKey, 20 | SystemProgram, 21 | SYSVAR_INSTRUCTIONS_PUBKEY, 22 | SYSVAR_RENT_PUBKEY, 23 | TransactionResponse, 24 | } from "@solana/web3.js"; 25 | import { 26 | AcctDiscHexMap, 27 | AUTH_PROGRAM_ID, 28 | Cluster, 29 | decodeAnchorAcct, 30 | genAcctDiscHexMap, 31 | getRent, 32 | getRentSync, 33 | hexCode, 34 | parseAnchorIxs, 35 | ParsedAnchorIx, 36 | PnftArgs, 37 | prependComputeIxs, 38 | prepPnftAccounts, 39 | TMETA_PROGRAM_ID, 40 | } from "@tensor-hq/tensor-common"; 41 | import BN from "bn.js"; 42 | import { 43 | AccountSuffix, 44 | DEFAULT_MICRO_LAMPORTS, 45 | DEFAULT_XFER_COMPUTE_UNITS, 46 | evalMathExpr, 47 | } from "../common"; 48 | import { 49 | WNS_PROGRAM_ID, 50 | WNS_DISTRIBUTION_PROGRAM_ID, 51 | getApprovalAccount, 52 | getDistributionAccount, 53 | } from "../token2022"; 54 | import { findTSwapPDA, TensorSwapSDK, TENSORSWAP_ADDR } from "../tensorswap"; 55 | import { ParsedAccount } from "../types"; 56 | import { TBID_ADDR } from "./constants"; 57 | import { findBidStatePda, findNftTempPDA } from "./pda"; 58 | 59 | // ---------------------------------------- Versioned IDLs for backwards compat when parsing. 60 | import { 61 | IDL as IDL_v0_1_0, 62 | TensorBid as TBid_v0_1_0, 63 | } from "./idl/tensor_bid_v0_1_0"; 64 | 65 | import { IDL as IDL_latest, TensorBid as TBid_latest } from "./idl/tensor_bid"; 66 | 67 | // initial deployment: https://explorer.solana.com/tx/2pLEU4Bvvd6xtRasDMQa9pRjhEsJKzqRoaQ4oDBG38AWadHUPudi8WjNACrB4neR5ap1GAxK6kvgcMuYYRvSVg11 68 | export const TBidIDL_v0_1_0 = IDL_v0_1_0; 69 | export const TBidIDL_v0_1_0_EffSlot_Mainnet = 183865849; 70 | 71 | // remove margin funding during bidding: https://solscan.io/tx/5A7XWvgicH1hDYAPtWhZd2SX7WCvUB2jjKDFqyRr6MwtfnGuTyfPkngTvQ7dFfcSTvjihLuBSETftPo1u5iixpp 72 | export const TBidIDL_latest = IDL_latest; 73 | export const TBidIDL_latest_EffSlot_Mainnet = 184669012; 74 | export const TBidIDL_latest_EffSlot_Devnet = 203536603; 75 | 76 | export type TBidIDL = TBid_v0_1_0 | TBid_latest; 77 | 78 | // Use this function to figure out which IDL to use based on the slot # of historical txs. 79 | export const triageBidIDL = ( 80 | slot: number | bigint, 81 | cluster: Cluster 82 | ): TBidIDL | null => { 83 | switch (cluster) { 84 | case Cluster.Mainnet: 85 | if (slot < TBidIDL_v0_1_0_EffSlot_Mainnet) return null; 86 | if (slot < TBidIDL_latest_EffSlot_Mainnet) return TBidIDL_v0_1_0; 87 | return TBidIDL_latest; 88 | case Cluster.Devnet: 89 | if (slot < TBidIDL_latest_EffSlot_Devnet) return null; 90 | return TBidIDL_latest; 91 | } 92 | }; 93 | 94 | // --------------------------------------- constants 95 | 96 | export const CURRENT_TBID_VERSION: number = +IDL_latest.constants.find( 97 | (c) => c.name === "CURRENT_TBID_VERSION" 98 | )!.value; 99 | export const TBID_TAKER_FEE_BPS: number = +IDL_latest.constants.find( 100 | (c) => c.name === "TBID_TAKER_FEE_BPS" 101 | )!.value; 102 | export const MAX_EXPIRY_SEC: number = +IDL_latest.constants.find( 103 | (c) => c.name === "MAX_EXPIRY_SEC" 104 | )!.value; 105 | export const BID_STATE_SIZE: number = evalMathExpr( 106 | IDL_latest.constants.find((c) => c.name === "BID_STATE_SIZE")!.value 107 | ); 108 | 109 | export const APPROX_BID_STATE_RENT = getRentSync(BID_STATE_SIZE); 110 | 111 | // --------------------------------------- state structs 112 | 113 | export type BidStateAnchor = { 114 | version: number; 115 | bidAmount: BN; 116 | nftMint: PublicKey; 117 | bidder: PublicKey; 118 | bump: number[]; 119 | expiry: BN; 120 | margin: PublicKey | null; 121 | updatedAt: BN; 122 | }; 123 | 124 | export type TensorBidPdaAnchor = BidStateAnchor; 125 | 126 | export type TaggedTensorBidPdaAnchor = { 127 | name: "bidState"; 128 | account: BidStateAnchor; 129 | }; 130 | 131 | // ------------- Types for parsed ixs from raw tx. 132 | 133 | export type TBidIxName = (typeof IDL_latest)["instructions"][number]["name"]; 134 | export type TBidIx = Omit & { name: TBidIxName }; 135 | export type ParsedTBidIx = ParsedAnchorIx; 136 | export type TBidPricedIx = { lamports: BN }; 137 | 138 | // --------------------------------------- sdk 139 | 140 | export class TensorBidSDK { 141 | program: Program; 142 | discMap: AcctDiscHexMap; 143 | coder: BorshCoder; 144 | eventParser: EventParser; 145 | 146 | constructor({ 147 | idl = IDL_latest, 148 | addr = TBID_ADDR, 149 | provider, 150 | coder, 151 | }: { 152 | idl?: any; //todo better typing 153 | addr?: PublicKey; 154 | provider?: AnchorProvider; 155 | coder?: Coder; 156 | }) { 157 | this.program = new Program(idl, addr, provider, coder); 158 | this.discMap = genAcctDiscHexMap(idl); 159 | this.coder = new BorshCoder(idl); 160 | this.eventParser = new EventParser(addr, this.coder); 161 | } 162 | 163 | // --------------------------------------- fetchers 164 | 165 | async fetchBidState(bidState: PublicKey, commitment?: Commitment) { 166 | return (await this.program.account.bidState.fetch( 167 | bidState, 168 | commitment 169 | )) as BidStateAnchor; 170 | } 171 | 172 | // --------------------------------------- account methods 173 | 174 | decode(acct: AccountInfo): TaggedTensorBidPdaAnchor | null { 175 | if (!acct.owner.equals(this.program.programId)) return null; 176 | return decodeAnchorAcct(acct, this.discMap); 177 | } 178 | 179 | // --------------------------------------- ixs 180 | 181 | async bid({ 182 | bidder, 183 | nftMint, 184 | lamports, 185 | margin = null, 186 | expireIn = null, 187 | }: { 188 | bidder: PublicKey; 189 | nftMint: PublicKey; 190 | lamports: BN; 191 | margin?: PublicKey | null; 192 | expireIn?: BN | null; 193 | }) { 194 | const [bidState, bidStateBump] = findBidStatePda({ 195 | mint: nftMint, 196 | owner: bidder, 197 | }); 198 | const [tswapPda, tswapPdaBump] = findTSwapPDA({}); 199 | 200 | const builder = this.program.methods.bid(lamports, expireIn).accounts({ 201 | nftMint, 202 | bidder, 203 | bidState, 204 | rent: SYSVAR_RENT_PUBKEY, 205 | systemProgram: SystemProgram.programId, 206 | tswap: tswapPda, 207 | //optional, as a default pick another mutable account 208 | marginAccount: margin ?? bidder, 209 | }); 210 | 211 | return { 212 | builder, 213 | tx: { ixs: [await builder.instruction()], extraSigners: [] }, 214 | bidState, 215 | bidStateBump, 216 | tswapPda, 217 | tswapPdaBump, 218 | }; 219 | } 220 | 221 | async takeBid({ 222 | bidder, 223 | seller, 224 | nftMint, 225 | lamports, 226 | tokenProgram, 227 | margin = null, 228 | nftSellerAcc, 229 | meta, 230 | authData, 231 | compute = DEFAULT_XFER_COMPUTE_UNITS, 232 | priorityMicroLamports = DEFAULT_MICRO_LAMPORTS, 233 | optionalRoyaltyPct = null, 234 | takerBroker = null, 235 | }: { 236 | bidder: PublicKey; 237 | seller: PublicKey; 238 | nftMint: PublicKey; 239 | lamports: BN; 240 | tokenProgram: PublicKey; 241 | margin?: PublicKey | null; 242 | nftSellerAcc: PublicKey; 243 | //optional % OF full royalty amount, so eg 50% of 10% royalty would be 5% 244 | optionalRoyaltyPct?: number | null; 245 | //optional taker broker account 246 | takerBroker?: PublicKey | null; 247 | } & PnftArgs) { 248 | const [bidState, bidStateBump] = findBidStatePda({ 249 | mint: nftMint, 250 | owner: bidder, 251 | }); 252 | const [tswapPda, tswapPdaBump] = findTSwapPDA({}); 253 | 254 | const swapSdk = new TensorSwapSDK({ 255 | provider: this.program.provider as AnchorProvider, 256 | }); 257 | const tSwapAcc = await swapSdk.fetchTSwap(tswapPda); 258 | const [tempPda, tempPdaBump] = findNftTempPDA({ nftMint }); 259 | 260 | const destAta = getAssociatedTokenAddressSync( 261 | nftMint, 262 | bidder, 263 | true, 264 | tokenProgram 265 | ); 266 | 267 | //prepare 2 pnft account sets 268 | const { 269 | meta: newMeta, 270 | creators, 271 | ownerTokenRecordBump, 272 | ownerTokenRecordPda, 273 | destTokenRecordBump: tempDestTokenRecordBump, 274 | destTokenRecordPda: tempDestTokenRecordPda, 275 | ruleSet, 276 | nftEditionPda, 277 | authDataSerialized, 278 | } = await prepPnftAccounts({ 279 | connection: this.program.provider.connection, 280 | meta, 281 | nftMint, 282 | destAta: tempPda, 283 | authData, 284 | sourceAta: nftSellerAcc, 285 | }); 286 | meta = newMeta; 287 | const { 288 | destTokenRecordBump: destTokenRecordBump, 289 | destTokenRecordPda: destTokenRecordPda, 290 | } = await prepPnftAccounts({ 291 | connection: this.program.provider.connection, 292 | meta, 293 | nftMint, 294 | destAta, 295 | authData, 296 | sourceAta: tempPda, 297 | }); 298 | 299 | const builder = this.program.methods 300 | .takeBid(lamports, !!ruleSet, authDataSerialized, optionalRoyaltyPct) 301 | .accounts({ 302 | nftMint, 303 | tswap: tswapPda, 304 | feeVault: tSwapAcc.feeVault, 305 | bidState, 306 | bidder, 307 | nftSellerAcc, 308 | nftMetadata: meta.address, 309 | nftBidderAcc: destAta, 310 | nftTempAcc: tempPda, 311 | seller, 312 | tensorswapProgram: TENSORSWAP_ADDR, 313 | tokenProgram, 314 | associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 315 | systemProgram: SystemProgram.programId, 316 | rent: SYSVAR_RENT_PUBKEY, 317 | nftEdition: nftEditionPda, 318 | bidderTokenRecord: destTokenRecordPda, 319 | sellerTokenRecord: ownerTokenRecordPda, 320 | tempTokenRecord: tempDestTokenRecordPda, 321 | marginAccount: margin ?? seller, 322 | authRules: ruleSet ?? SystemProgram.programId, 323 | takerBroker: takerBroker ?? tSwapAcc.feeVault, 324 | pnftShared: { 325 | authorizationRulesProgram: AUTH_PROGRAM_ID, 326 | tokenMetadataProgram: TMETA_PROGRAM_ID, 327 | instructions: SYSVAR_INSTRUCTIONS_PUBKEY, 328 | }, 329 | }) 330 | .remainingAccounts( 331 | creators.map((c) => ({ 332 | pubkey: c.address, 333 | isWritable: c.share > 0, //only writable if they have a share 334 | isSigner: false, 335 | })) 336 | ); 337 | 338 | return { 339 | builder, 340 | tx: { 341 | ixs: prependComputeIxs( 342 | [await builder.instruction()], 343 | compute, 344 | priorityMicroLamports 345 | ), 346 | extraSigners: [], 347 | }, 348 | bidState, 349 | bidStateBump, 350 | tswapPda, 351 | tswapPdaBump, 352 | tempPda, 353 | tempPdaBump, 354 | meta, 355 | ownerTokenRecordBump, 356 | ownerTokenRecordPda, 357 | destTokenRecordBump, 358 | destTokenRecordPda, 359 | tempDestTokenRecordBump, 360 | tempDestTokenRecordPda, 361 | ruleSet, 362 | nftEditionPda, 363 | authDataSerialized, 364 | nftbidderAcc: destAta, 365 | }; 366 | } 367 | 368 | async cancelBid({ 369 | bidder, 370 | nftMint, 371 | }: { 372 | bidder: PublicKey; 373 | nftMint: PublicKey; 374 | }) { 375 | const [bidState, bidStateBump] = findBidStatePda({ 376 | mint: nftMint, 377 | owner: bidder, 378 | }); 379 | 380 | const builder = this.program.methods.cancelBid().accounts({ 381 | nftMint, 382 | bidder, 383 | bidState, 384 | rent: SYSVAR_RENT_PUBKEY, 385 | systemProgram: SystemProgram.programId, 386 | }); 387 | 388 | return { 389 | builder, 390 | tx: { ixs: [await builder.instruction()], extraSigners: [] }, 391 | bidState, 392 | bidStateBump, 393 | }; 394 | } 395 | 396 | async closeExpiredBid({ 397 | bidder, 398 | nftMint, 399 | }: { 400 | bidder: PublicKey; 401 | nftMint: PublicKey; 402 | }) { 403 | const [bidState, bidStateBump] = findBidStatePda({ 404 | mint: nftMint, 405 | owner: bidder, 406 | }); 407 | const builder = this.program.methods.closeExpiredBid().accounts({ 408 | nftMint, 409 | bidder, 410 | bidState, 411 | rent: SYSVAR_RENT_PUBKEY, 412 | systemProgram: SystemProgram.programId, 413 | }); 414 | 415 | return { 416 | builder, 417 | tx: { ixs: [await builder.instruction()], extraSigners: [] }, 418 | bidState, 419 | bidStateBump, 420 | }; 421 | } 422 | 423 | // --------------------------------------- T22 424 | 425 | async takeBidT22({ 426 | bidder, 427 | seller, 428 | nftMint, 429 | lamports, 430 | margin = null, 431 | nftSellerAcc, 432 | compute = DEFAULT_XFER_COMPUTE_UNITS, 433 | priorityMicroLamports = DEFAULT_MICRO_LAMPORTS, 434 | takerBroker = null, 435 | }: { 436 | bidder: PublicKey; 437 | seller: PublicKey; 438 | nftMint: PublicKey; 439 | lamports: BN; 440 | margin?: PublicKey | null; 441 | nftSellerAcc: PublicKey; 442 | compute?: number | null | undefined; 443 | priorityMicroLamports?: number | null | undefined; 444 | //optional taker broker account 445 | takerBroker?: PublicKey | null; 446 | }) { 447 | const [bidState, bidStateBump] = findBidStatePda({ 448 | mint: nftMint, 449 | owner: bidder, 450 | }); 451 | const [tswapPda, tswapPdaBump] = findTSwapPDA({}); 452 | 453 | const swapSdk = new TensorSwapSDK({ 454 | provider: this.program.provider as AnchorProvider, 455 | }); 456 | const tSwapAcc = await swapSdk.fetchTSwap(tswapPda); 457 | const [tempPda, tempPdaBump] = findNftTempPDA({ nftMint }); 458 | 459 | const destAta = getAssociatedTokenAddressSync( 460 | nftMint, 461 | bidder, 462 | true, 463 | TOKEN_2022_PROGRAM_ID 464 | ); 465 | 466 | const builder = this.program.methods.takeBidT22(lamports).accounts({ 467 | nftMint, 468 | tswap: tswapPda, 469 | feeVault: tSwapAcc.feeVault, 470 | bidState, 471 | bidder, 472 | nftSellerAcc, 473 | nftBidderAcc: destAta, 474 | seller, 475 | tensorswapProgram: TENSORSWAP_ADDR, 476 | tokenProgram: TOKEN_2022_PROGRAM_ID, 477 | associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 478 | systemProgram: SystemProgram.programId, 479 | rent: SYSVAR_RENT_PUBKEY, 480 | marginAccount: margin ?? seller, 481 | takerBroker: takerBroker ?? tSwapAcc.feeVault, 482 | }); 483 | 484 | return { 485 | builder, 486 | tx: { 487 | ixs: prependComputeIxs( 488 | [await builder.instruction()], 489 | compute, 490 | priorityMicroLamports 491 | ), 492 | extraSigners: [], 493 | }, 494 | bidState, 495 | bidStateBump, 496 | tswapPda, 497 | tswapPdaBump, 498 | tempPda, 499 | tempPdaBump, 500 | nftbidderAcc: destAta, 501 | }; 502 | } 503 | 504 | // --------------------------------------- WNS 505 | 506 | async wnsTakeBid({ 507 | bidder, 508 | seller, 509 | nftMint, 510 | lamports, 511 | margin = null, 512 | nftSellerAcc, 513 | collectionMint, 514 | compute = DEFAULT_XFER_COMPUTE_UNITS, 515 | priorityMicroLamports = DEFAULT_MICRO_LAMPORTS, 516 | takerBroker = null, 517 | }: { 518 | bidder: PublicKey; 519 | seller: PublicKey; 520 | nftMint: PublicKey; 521 | lamports: BN; 522 | margin?: PublicKey | null; 523 | nftSellerAcc: PublicKey; 524 | collectionMint: PublicKey; 525 | compute?: number | null | undefined; 526 | priorityMicroLamports?: number | null | undefined; 527 | //optional taker broker account 528 | takerBroker?: PublicKey | null; 529 | }) { 530 | const [bidState, bidStateBump] = findBidStatePda({ 531 | mint: nftMint, 532 | owner: bidder, 533 | }); 534 | const [tswapPda, tswapPdaBump] = findTSwapPDA({}); 535 | 536 | const swapSdk = new TensorSwapSDK({ 537 | provider: this.program.provider as AnchorProvider, 538 | }); 539 | const tSwapAcc = await swapSdk.fetchTSwap(tswapPda); 540 | const [tempPda, tempPdaBump] = findNftTempPDA({ nftMint }); 541 | 542 | const destAta = getAssociatedTokenAddressSync( 543 | nftMint, 544 | bidder, 545 | true, 546 | TOKEN_2022_PROGRAM_ID 547 | ); 548 | 549 | const approveAccount = getApprovalAccount(nftMint); 550 | const distribution = getDistributionAccount(collectionMint); 551 | const extraMetas = getExtraAccountMetaAddress(nftMint, WNS_PROGRAM_ID); 552 | 553 | const builder = this.program.methods.wnsTakeBid(lamports).accounts({ 554 | nftMint, 555 | tswap: tswapPda, 556 | feeVault: tSwapAcc.feeVault, 557 | bidState, 558 | bidder, 559 | nftSellerAcc, 560 | nftBidderAcc: destAta, 561 | seller, 562 | tensorswapProgram: TENSORSWAP_ADDR, 563 | tokenProgram: TOKEN_2022_PROGRAM_ID, 564 | associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID, 565 | systemProgram: SystemProgram.programId, 566 | rent: SYSVAR_RENT_PUBKEY, 567 | marginAccount: margin ?? seller, 568 | takerBroker: takerBroker ?? tSwapAcc.feeVault, 569 | approveAccount, 570 | distribution, 571 | distributionProgram: WNS_DISTRIBUTION_PROGRAM_ID, 572 | wnsProgram: WNS_PROGRAM_ID, 573 | extraMetas, 574 | }); 575 | 576 | return { 577 | builder, 578 | tx: { 579 | ixs: prependComputeIxs( 580 | [await builder.instruction()], 581 | compute, 582 | priorityMicroLamports 583 | ), 584 | extraSigners: [], 585 | }, 586 | bidState, 587 | bidStateBump, 588 | tswapPda, 589 | tswapPdaBump, 590 | tempPda, 591 | tempPdaBump, 592 | nftbidderAcc: destAta, 593 | }; 594 | } 595 | 596 | // --------------------------------------- helpers 597 | 598 | async getBidStateRent() { 599 | return await getRent( 600 | this.program.provider.connection, 601 | this.program.account.bidState 602 | ); 603 | } 604 | 605 | getError( 606 | name: (typeof IDL_latest)["errors"][number]["name"] 607 | ): (typeof IDL_latest)["errors"][number] { 608 | //@ts-ignore (throwing weird ts errors for me) 609 | return this.program.idl.errors.find((e) => e.name === name)!; 610 | } 611 | 612 | getErrorCodeHex(name: (typeof IDL_latest)["errors"][number]["name"]): string { 613 | return hexCode(this.getError(name).code); 614 | } 615 | 616 | // --------------------------------------- parsing raw txs 617 | 618 | /** This only works for the latest IDL. This is intentional: otherwise we'll need to switch/case all historical deprecated ixs downstream. */ 619 | parseIxs(tx: TransactionResponse): ParsedTBidIx[] { 620 | return parseAnchorIxs({ 621 | tx, 622 | coder: this.coder, 623 | eventParser: this.eventParser, 624 | programId: this.program.programId, 625 | }); 626 | } 627 | 628 | getFeeAmount(ix: ParsedTBidIx): BN | null { 629 | switch (ix.ix.name) { 630 | case "takeBid": 631 | case "takeBidT22": 632 | case "wnsTakeBid": { 633 | const event = ix.events[0].data; 634 | return event.tswapFee.add(event.creatorsFee); 635 | } 636 | case "bid": 637 | case "cancelBid": 638 | case "closeExpiredBid": 639 | return null; 640 | } 641 | } 642 | 643 | getSolAmount(ix: ParsedTBidIx): BN | null { 644 | switch (ix.ix.name) { 645 | case "takeBid": 646 | case "takeBidT22": 647 | case "wnsTakeBid": 648 | case "bid": 649 | return (ix.ix.data as TBidPricedIx).lamports; 650 | case "cancelBid": 651 | case "closeExpiredBid": 652 | return null; 653 | } 654 | } 655 | 656 | // FYI: accounts under InstructioNDisplay is the space-separated capitalized 657 | // version of the fields for the corresponding #[Accounts]. 658 | // eg sol_escrow -> "Sol Escrow', or tswap -> "Tswap" 659 | // shared.sol_escrow -> "Shared > Sol Escrow" 660 | getAccountByName( 661 | ix: ParsedTBidIx, 662 | name: AccountSuffix 663 | ): ParsedAccount | undefined { 664 | // We use endsWith since composite nested accounts (eg shared.sol_escrow) 665 | // will prefix it as "Shared > Sol Escrow" 666 | return ix.formatted?.accounts.find((acc) => acc.name?.endsWith(name)); 667 | } 668 | } 669 | -------------------------------------------------------------------------------- /src/tensor_whitelist/constants.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | export const TLIST_ADDR = new PublicKey( 4 | process.env.TLIST_ADDR || "TL1ST2iRBzuGTqLn1KXnGdSnEow62BzPnGiqyRXhWtW" 5 | ); 6 | 7 | export const TLIST_COSIGNER = new PublicKey( 8 | process.env.TLIST_COSIGNER || "5aB7nyNJTuQZdKnhZXQHNhT16tBNevCuLRp14btvANxu" 9 | ); 10 | 11 | export const TLIST_OWNER = new PublicKey( 12 | process.env.TLIST_OWNER || "99cmWwQMqMFzMPx85rvZYKwusGSjZUDsu6mqYV4iisiz" 13 | ); 14 | 15 | export const MAX_PROOF_LEN = 28; 16 | -------------------------------------------------------------------------------- /src/tensor_whitelist/idl/tensor_whitelist.ts: -------------------------------------------------------------------------------- 1 | export type TensorWhitelist = { 2 | "version": "0.2.1", 3 | "name": "tensor_whitelist", 4 | "constants": [ 5 | { 6 | "name": "AUTHORITY_SIZE", 7 | "type": { 8 | "defined": "usize" 9 | }, 10 | "value": "8 + 1 + (32 * 2) + 64" 11 | }, 12 | { 13 | "name": "WHITELIST_SIZE", 14 | "type": { 15 | "defined": "usize" 16 | }, 17 | "value": "8 + 1 + 1 + 1 + (32 * 3) + 1 + (33 * 2) + 64" 18 | }, 19 | { 20 | "name": "MINT_PROOF_SIZE", 21 | "type": { 22 | "defined": "usize" 23 | }, 24 | "value": "8 + (32 * 28) + 1" 25 | } 26 | ], 27 | "instructions": [ 28 | { 29 | "name": "initUpdateAuthority", 30 | "accounts": [ 31 | { 32 | "name": "whitelistAuthority", 33 | "isMut": true, 34 | "isSigner": false 35 | }, 36 | { 37 | "name": "cosigner", 38 | "isMut": true, 39 | "isSigner": true, 40 | "docs": [ 41 | "both have to sign on any updates" 42 | ] 43 | }, 44 | { 45 | "name": "owner", 46 | "isMut": false, 47 | "isSigner": true 48 | }, 49 | { 50 | "name": "systemProgram", 51 | "isMut": false, 52 | "isSigner": false 53 | } 54 | ], 55 | "args": [ 56 | { 57 | "name": "newCosigner", 58 | "type": { 59 | "option": "publicKey" 60 | } 61 | }, 62 | { 63 | "name": "newOwner", 64 | "type": { 65 | "option": "publicKey" 66 | } 67 | } 68 | ] 69 | }, 70 | { 71 | "name": "initUpdateWhitelist", 72 | "docs": [ 73 | "Store min 1, max 3, check in priority order" 74 | ], 75 | "accounts": [ 76 | { 77 | "name": "whitelist", 78 | "isMut": true, 79 | "isSigner": false 80 | }, 81 | { 82 | "name": "whitelistAuthority", 83 | "isMut": false, 84 | "isSigner": false, 85 | "docs": [ 86 | "there can only be 1 whitelist authority (due to seeds),", 87 | "and we're checking that 1)the correct cosigner is present on it, and 2)is a signer" 88 | ] 89 | }, 90 | { 91 | "name": "cosigner", 92 | "isMut": true, 93 | "isSigner": true, 94 | "docs": [ 95 | "only cosigner has to sign for unfrozen, for frozen owner also has to sign" 96 | ] 97 | }, 98 | { 99 | "name": "systemProgram", 100 | "isMut": false, 101 | "isSigner": false 102 | } 103 | ], 104 | "args": [ 105 | { 106 | "name": "uuid", 107 | "type": { 108 | "array": [ 109 | "u8", 110 | 32 111 | ] 112 | } 113 | }, 114 | { 115 | "name": "rootHash", 116 | "type": { 117 | "option": { 118 | "array": [ 119 | "u8", 120 | 32 121 | ] 122 | } 123 | } 124 | }, 125 | { 126 | "name": "name", 127 | "type": { 128 | "option": { 129 | "array": [ 130 | "u8", 131 | 32 132 | ] 133 | } 134 | } 135 | }, 136 | { 137 | "name": "voc", 138 | "type": { 139 | "option": "publicKey" 140 | } 141 | }, 142 | { 143 | "name": "fvc", 144 | "type": { 145 | "option": "publicKey" 146 | } 147 | } 148 | ] 149 | }, 150 | { 151 | "name": "initUpdateMintProof", 152 | "accounts": [ 153 | { 154 | "name": "whitelist", 155 | "isMut": false, 156 | "isSigner": false 157 | }, 158 | { 159 | "name": "mint", 160 | "isMut": false, 161 | "isSigner": false 162 | }, 163 | { 164 | "name": "mintProof", 165 | "isMut": true, 166 | "isSigner": false 167 | }, 168 | { 169 | "name": "user", 170 | "isMut": true, 171 | "isSigner": true 172 | }, 173 | { 174 | "name": "systemProgram", 175 | "isMut": false, 176 | "isSigner": false 177 | } 178 | ], 179 | "args": [ 180 | { 181 | "name": "proof", 182 | "type": { 183 | "vec": { 184 | "array": [ 185 | "u8", 186 | 32 187 | ] 188 | } 189 | } 190 | } 191 | ] 192 | }, 193 | { 194 | "name": "reallocAuthority", 195 | "accounts": [ 196 | { 197 | "name": "whitelistAuthority", 198 | "isMut": true, 199 | "isSigner": false, 200 | "docs": [ 201 | "there can only be 1 whitelist authority (due to seeds),", 202 | "and we're checking that 1)the correct cosigner is present on it, and 2)is a signer" 203 | ] 204 | }, 205 | { 206 | "name": "cosigner", 207 | "isMut": true, 208 | "isSigner": true 209 | }, 210 | { 211 | "name": "systemProgram", 212 | "isMut": false, 213 | "isSigner": false 214 | } 215 | ], 216 | "args": [] 217 | }, 218 | { 219 | "name": "reallocWhitelist", 220 | "accounts": [ 221 | { 222 | "name": "whitelist", 223 | "isMut": true, 224 | "isSigner": false 225 | }, 226 | { 227 | "name": "whitelistAuthority", 228 | "isMut": false, 229 | "isSigner": false, 230 | "docs": [ 231 | "there can only be 1 whitelist authority (due to seeds),", 232 | "and we're checking that 1)the correct cosigner is present on it, and 2)is a signer" 233 | ] 234 | }, 235 | { 236 | "name": "cosigner", 237 | "isMut": true, 238 | "isSigner": true 239 | }, 240 | { 241 | "name": "systemProgram", 242 | "isMut": false, 243 | "isSigner": false 244 | } 245 | ], 246 | "args": [] 247 | }, 248 | { 249 | "name": "freezeWhitelist", 250 | "accounts": [ 251 | { 252 | "name": "whitelist", 253 | "isMut": true, 254 | "isSigner": false 255 | }, 256 | { 257 | "name": "whitelistAuthority", 258 | "isMut": false, 259 | "isSigner": false, 260 | "docs": [ 261 | "there can only be 1 whitelist authority (due to seeds),", 262 | "and we're checking that 1)the correct cosigner is present on it, and 2)is a signer" 263 | ] 264 | }, 265 | { 266 | "name": "cosigner", 267 | "isMut": true, 268 | "isSigner": true, 269 | "docs": [ 270 | "freezing only requires cosigner" 271 | ] 272 | }, 273 | { 274 | "name": "systemProgram", 275 | "isMut": false, 276 | "isSigner": false 277 | } 278 | ], 279 | "args": [] 280 | }, 281 | { 282 | "name": "unfreezeWhitelist", 283 | "accounts": [ 284 | { 285 | "name": "whitelist", 286 | "isMut": true, 287 | "isSigner": false 288 | }, 289 | { 290 | "name": "whitelistAuthority", 291 | "isMut": false, 292 | "isSigner": false, 293 | "docs": [ 294 | "there can only be 1 whitelist authority (due to seeds),", 295 | "and we're checking that 1)the correct cosigner is present on it, and 2)is a signer" 296 | ] 297 | }, 298 | { 299 | "name": "owner", 300 | "isMut": true, 301 | "isSigner": true, 302 | "docs": [ 303 | "unfreezing requires owner" 304 | ] 305 | }, 306 | { 307 | "name": "systemProgram", 308 | "isMut": false, 309 | "isSigner": false 310 | } 311 | ], 312 | "args": [] 313 | } 314 | ], 315 | "accounts": [ 316 | { 317 | "name": "authority", 318 | "type": { 319 | "kind": "struct", 320 | "fields": [ 321 | { 322 | "name": "bump", 323 | "type": "u8" 324 | }, 325 | { 326 | "name": "cosigner", 327 | "docs": [ 328 | "cosigner of the whitelist - has rights to update it if unfrozen" 329 | ], 330 | "type": "publicKey" 331 | }, 332 | { 333 | "name": "owner", 334 | "docs": [ 335 | "owner of the whitelist (stricter, should be handled more carefully)", 336 | "has rights to 1)freeze, 2)unfreeze, 3)update frozen whitelists" 337 | ], 338 | "type": "publicKey" 339 | }, 340 | { 341 | "name": "reserved", 342 | "type": { 343 | "array": [ 344 | "u8", 345 | 64 346 | ] 347 | } 348 | } 349 | ] 350 | } 351 | }, 352 | { 353 | "name": "whitelist", 354 | "type": { 355 | "kind": "struct", 356 | "fields": [ 357 | { 358 | "name": "version", 359 | "type": "u8" 360 | }, 361 | { 362 | "name": "bump", 363 | "type": "u8" 364 | }, 365 | { 366 | "name": "verified", 367 | "docs": [ 368 | "DEPRECATED, doesn't do anything" 369 | ], 370 | "type": "bool" 371 | }, 372 | { 373 | "name": "rootHash", 374 | "docs": [ 375 | "in the case when not present will be [u8; 32]" 376 | ], 377 | "type": { 378 | "array": [ 379 | "u8", 380 | 32 381 | ] 382 | } 383 | }, 384 | { 385 | "name": "uuid", 386 | "type": { 387 | "array": [ 388 | "u8", 389 | 32 390 | ] 391 | } 392 | }, 393 | { 394 | "name": "name", 395 | "type": { 396 | "array": [ 397 | "u8", 398 | 32 399 | ] 400 | } 401 | }, 402 | { 403 | "name": "frozen", 404 | "type": "bool" 405 | }, 406 | { 407 | "name": "voc", 408 | "type": { 409 | "option": "publicKey" 410 | } 411 | }, 412 | { 413 | "name": "fvc", 414 | "type": { 415 | "option": "publicKey" 416 | } 417 | }, 418 | { 419 | "name": "reserved", 420 | "type": { 421 | "array": [ 422 | "u8", 423 | 64 424 | ] 425 | } 426 | } 427 | ] 428 | } 429 | }, 430 | { 431 | "name": "mintProof", 432 | "type": { 433 | "kind": "struct", 434 | "fields": [ 435 | { 436 | "name": "proofLen", 437 | "type": "u8" 438 | }, 439 | { 440 | "name": "proof", 441 | "type": { 442 | "array": [ 443 | { 444 | "array": [ 445 | "u8", 446 | 32 447 | ] 448 | }, 449 | 28 450 | ] 451 | } 452 | } 453 | ] 454 | } 455 | } 456 | ], 457 | "types": [ 458 | { 459 | "name": "FullMerkleProof", 460 | "type": { 461 | "kind": "struct", 462 | "fields": [ 463 | { 464 | "name": "proof", 465 | "type": { 466 | "vec": { 467 | "array": [ 468 | "u8", 469 | 32 470 | ] 471 | } 472 | } 473 | }, 474 | { 475 | "name": "leaf", 476 | "type": { 477 | "array": [ 478 | "u8", 479 | 32 480 | ] 481 | } 482 | } 483 | ] 484 | } 485 | } 486 | ], 487 | "errors": [ 488 | { 489 | "code": 6000, 490 | "name": "BadCosigner", 491 | "msg": "passed in cosigner doesnt have the rights to do this" 492 | }, 493 | { 494 | "code": 6001, 495 | "name": "MissingVerification", 496 | "msg": "missing all 3 verification methods: at least one must be present" 497 | }, 498 | { 499 | "code": 6002, 500 | "name": "MissingName", 501 | "msg": "missing name" 502 | }, 503 | { 504 | "code": 6003, 505 | "name": "BadWhitelist", 506 | "msg": "bad whitelist" 507 | }, 508 | { 509 | "code": 6004, 510 | "name": "ProofTooLong", 511 | "msg": "proof provided exceeds the limit of 32 hashes" 512 | }, 513 | { 514 | "code": 6005, 515 | "name": "BadOwner", 516 | "msg": "passed in owner doesnt have the rights to do this" 517 | }, 518 | { 519 | "code": 6006, 520 | "name": "FailedVocVerification", 521 | "msg": "failed voc verification" 522 | }, 523 | { 524 | "code": 6007, 525 | "name": "FailedFvcVerification", 526 | "msg": "failed fvc verification" 527 | }, 528 | { 529 | "code": 6008, 530 | "name": "FailedMerkleProofVerification", 531 | "msg": "failed merkle proof verification" 532 | } 533 | ] 534 | }; 535 | 536 | export const IDL: TensorWhitelist = { 537 | "version": "0.2.1", 538 | "name": "tensor_whitelist", 539 | "constants": [ 540 | { 541 | "name": "AUTHORITY_SIZE", 542 | "type": { 543 | "defined": "usize" 544 | }, 545 | "value": "8 + 1 + (32 * 2) + 64" 546 | }, 547 | { 548 | "name": "WHITELIST_SIZE", 549 | "type": { 550 | "defined": "usize" 551 | }, 552 | "value": "8 + 1 + 1 + 1 + (32 * 3) + 1 + (33 * 2) + 64" 553 | }, 554 | { 555 | "name": "MINT_PROOF_SIZE", 556 | "type": { 557 | "defined": "usize" 558 | }, 559 | "value": "8 + (32 * 28) + 1" 560 | } 561 | ], 562 | "instructions": [ 563 | { 564 | "name": "initUpdateAuthority", 565 | "accounts": [ 566 | { 567 | "name": "whitelistAuthority", 568 | "isMut": true, 569 | "isSigner": false 570 | }, 571 | { 572 | "name": "cosigner", 573 | "isMut": true, 574 | "isSigner": true, 575 | "docs": [ 576 | "both have to sign on any updates" 577 | ] 578 | }, 579 | { 580 | "name": "owner", 581 | "isMut": false, 582 | "isSigner": true 583 | }, 584 | { 585 | "name": "systemProgram", 586 | "isMut": false, 587 | "isSigner": false 588 | } 589 | ], 590 | "args": [ 591 | { 592 | "name": "newCosigner", 593 | "type": { 594 | "option": "publicKey" 595 | } 596 | }, 597 | { 598 | "name": "newOwner", 599 | "type": { 600 | "option": "publicKey" 601 | } 602 | } 603 | ] 604 | }, 605 | { 606 | "name": "initUpdateWhitelist", 607 | "docs": [ 608 | "Store min 1, max 3, check in priority order" 609 | ], 610 | "accounts": [ 611 | { 612 | "name": "whitelist", 613 | "isMut": true, 614 | "isSigner": false 615 | }, 616 | { 617 | "name": "whitelistAuthority", 618 | "isMut": false, 619 | "isSigner": false, 620 | "docs": [ 621 | "there can only be 1 whitelist authority (due to seeds),", 622 | "and we're checking that 1)the correct cosigner is present on it, and 2)is a signer" 623 | ] 624 | }, 625 | { 626 | "name": "cosigner", 627 | "isMut": true, 628 | "isSigner": true, 629 | "docs": [ 630 | "only cosigner has to sign for unfrozen, for frozen owner also has to sign" 631 | ] 632 | }, 633 | { 634 | "name": "systemProgram", 635 | "isMut": false, 636 | "isSigner": false 637 | } 638 | ], 639 | "args": [ 640 | { 641 | "name": "uuid", 642 | "type": { 643 | "array": [ 644 | "u8", 645 | 32 646 | ] 647 | } 648 | }, 649 | { 650 | "name": "rootHash", 651 | "type": { 652 | "option": { 653 | "array": [ 654 | "u8", 655 | 32 656 | ] 657 | } 658 | } 659 | }, 660 | { 661 | "name": "name", 662 | "type": { 663 | "option": { 664 | "array": [ 665 | "u8", 666 | 32 667 | ] 668 | } 669 | } 670 | }, 671 | { 672 | "name": "voc", 673 | "type": { 674 | "option": "publicKey" 675 | } 676 | }, 677 | { 678 | "name": "fvc", 679 | "type": { 680 | "option": "publicKey" 681 | } 682 | } 683 | ] 684 | }, 685 | { 686 | "name": "initUpdateMintProof", 687 | "accounts": [ 688 | { 689 | "name": "whitelist", 690 | "isMut": false, 691 | "isSigner": false 692 | }, 693 | { 694 | "name": "mint", 695 | "isMut": false, 696 | "isSigner": false 697 | }, 698 | { 699 | "name": "mintProof", 700 | "isMut": true, 701 | "isSigner": false 702 | }, 703 | { 704 | "name": "user", 705 | "isMut": true, 706 | "isSigner": true 707 | }, 708 | { 709 | "name": "systemProgram", 710 | "isMut": false, 711 | "isSigner": false 712 | } 713 | ], 714 | "args": [ 715 | { 716 | "name": "proof", 717 | "type": { 718 | "vec": { 719 | "array": [ 720 | "u8", 721 | 32 722 | ] 723 | } 724 | } 725 | } 726 | ] 727 | }, 728 | { 729 | "name": "reallocAuthority", 730 | "accounts": [ 731 | { 732 | "name": "whitelistAuthority", 733 | "isMut": true, 734 | "isSigner": false, 735 | "docs": [ 736 | "there can only be 1 whitelist authority (due to seeds),", 737 | "and we're checking that 1)the correct cosigner is present on it, and 2)is a signer" 738 | ] 739 | }, 740 | { 741 | "name": "cosigner", 742 | "isMut": true, 743 | "isSigner": true 744 | }, 745 | { 746 | "name": "systemProgram", 747 | "isMut": false, 748 | "isSigner": false 749 | } 750 | ], 751 | "args": [] 752 | }, 753 | { 754 | "name": "reallocWhitelist", 755 | "accounts": [ 756 | { 757 | "name": "whitelist", 758 | "isMut": true, 759 | "isSigner": false 760 | }, 761 | { 762 | "name": "whitelistAuthority", 763 | "isMut": false, 764 | "isSigner": false, 765 | "docs": [ 766 | "there can only be 1 whitelist authority (due to seeds),", 767 | "and we're checking that 1)the correct cosigner is present on it, and 2)is a signer" 768 | ] 769 | }, 770 | { 771 | "name": "cosigner", 772 | "isMut": true, 773 | "isSigner": true 774 | }, 775 | { 776 | "name": "systemProgram", 777 | "isMut": false, 778 | "isSigner": false 779 | } 780 | ], 781 | "args": [] 782 | }, 783 | { 784 | "name": "freezeWhitelist", 785 | "accounts": [ 786 | { 787 | "name": "whitelist", 788 | "isMut": true, 789 | "isSigner": false 790 | }, 791 | { 792 | "name": "whitelistAuthority", 793 | "isMut": false, 794 | "isSigner": false, 795 | "docs": [ 796 | "there can only be 1 whitelist authority (due to seeds),", 797 | "and we're checking that 1)the correct cosigner is present on it, and 2)is a signer" 798 | ] 799 | }, 800 | { 801 | "name": "cosigner", 802 | "isMut": true, 803 | "isSigner": true, 804 | "docs": [ 805 | "freezing only requires cosigner" 806 | ] 807 | }, 808 | { 809 | "name": "systemProgram", 810 | "isMut": false, 811 | "isSigner": false 812 | } 813 | ], 814 | "args": [] 815 | }, 816 | { 817 | "name": "unfreezeWhitelist", 818 | "accounts": [ 819 | { 820 | "name": "whitelist", 821 | "isMut": true, 822 | "isSigner": false 823 | }, 824 | { 825 | "name": "whitelistAuthority", 826 | "isMut": false, 827 | "isSigner": false, 828 | "docs": [ 829 | "there can only be 1 whitelist authority (due to seeds),", 830 | "and we're checking that 1)the correct cosigner is present on it, and 2)is a signer" 831 | ] 832 | }, 833 | { 834 | "name": "owner", 835 | "isMut": true, 836 | "isSigner": true, 837 | "docs": [ 838 | "unfreezing requires owner" 839 | ] 840 | }, 841 | { 842 | "name": "systemProgram", 843 | "isMut": false, 844 | "isSigner": false 845 | } 846 | ], 847 | "args": [] 848 | } 849 | ], 850 | "accounts": [ 851 | { 852 | "name": "authority", 853 | "type": { 854 | "kind": "struct", 855 | "fields": [ 856 | { 857 | "name": "bump", 858 | "type": "u8" 859 | }, 860 | { 861 | "name": "cosigner", 862 | "docs": [ 863 | "cosigner of the whitelist - has rights to update it if unfrozen" 864 | ], 865 | "type": "publicKey" 866 | }, 867 | { 868 | "name": "owner", 869 | "docs": [ 870 | "owner of the whitelist (stricter, should be handled more carefully)", 871 | "has rights to 1)freeze, 2)unfreeze, 3)update frozen whitelists" 872 | ], 873 | "type": "publicKey" 874 | }, 875 | { 876 | "name": "reserved", 877 | "type": { 878 | "array": [ 879 | "u8", 880 | 64 881 | ] 882 | } 883 | } 884 | ] 885 | } 886 | }, 887 | { 888 | "name": "whitelist", 889 | "type": { 890 | "kind": "struct", 891 | "fields": [ 892 | { 893 | "name": "version", 894 | "type": "u8" 895 | }, 896 | { 897 | "name": "bump", 898 | "type": "u8" 899 | }, 900 | { 901 | "name": "verified", 902 | "docs": [ 903 | "DEPRECATED, doesn't do anything" 904 | ], 905 | "type": "bool" 906 | }, 907 | { 908 | "name": "rootHash", 909 | "docs": [ 910 | "in the case when not present will be [u8; 32]" 911 | ], 912 | "type": { 913 | "array": [ 914 | "u8", 915 | 32 916 | ] 917 | } 918 | }, 919 | { 920 | "name": "uuid", 921 | "type": { 922 | "array": [ 923 | "u8", 924 | 32 925 | ] 926 | } 927 | }, 928 | { 929 | "name": "name", 930 | "type": { 931 | "array": [ 932 | "u8", 933 | 32 934 | ] 935 | } 936 | }, 937 | { 938 | "name": "frozen", 939 | "type": "bool" 940 | }, 941 | { 942 | "name": "voc", 943 | "type": { 944 | "option": "publicKey" 945 | } 946 | }, 947 | { 948 | "name": "fvc", 949 | "type": { 950 | "option": "publicKey" 951 | } 952 | }, 953 | { 954 | "name": "reserved", 955 | "type": { 956 | "array": [ 957 | "u8", 958 | 64 959 | ] 960 | } 961 | } 962 | ] 963 | } 964 | }, 965 | { 966 | "name": "mintProof", 967 | "type": { 968 | "kind": "struct", 969 | "fields": [ 970 | { 971 | "name": "proofLen", 972 | "type": "u8" 973 | }, 974 | { 975 | "name": "proof", 976 | "type": { 977 | "array": [ 978 | { 979 | "array": [ 980 | "u8", 981 | 32 982 | ] 983 | }, 984 | 28 985 | ] 986 | } 987 | } 988 | ] 989 | } 990 | } 991 | ], 992 | "types": [ 993 | { 994 | "name": "FullMerkleProof", 995 | "type": { 996 | "kind": "struct", 997 | "fields": [ 998 | { 999 | "name": "proof", 1000 | "type": { 1001 | "vec": { 1002 | "array": [ 1003 | "u8", 1004 | 32 1005 | ] 1006 | } 1007 | } 1008 | }, 1009 | { 1010 | "name": "leaf", 1011 | "type": { 1012 | "array": [ 1013 | "u8", 1014 | 32 1015 | ] 1016 | } 1017 | } 1018 | ] 1019 | } 1020 | } 1021 | ], 1022 | "errors": [ 1023 | { 1024 | "code": 6000, 1025 | "name": "BadCosigner", 1026 | "msg": "passed in cosigner doesnt have the rights to do this" 1027 | }, 1028 | { 1029 | "code": 6001, 1030 | "name": "MissingVerification", 1031 | "msg": "missing all 3 verification methods: at least one must be present" 1032 | }, 1033 | { 1034 | "code": 6002, 1035 | "name": "MissingName", 1036 | "msg": "missing name" 1037 | }, 1038 | { 1039 | "code": 6003, 1040 | "name": "BadWhitelist", 1041 | "msg": "bad whitelist" 1042 | }, 1043 | { 1044 | "code": 6004, 1045 | "name": "ProofTooLong", 1046 | "msg": "proof provided exceeds the limit of 32 hashes" 1047 | }, 1048 | { 1049 | "code": 6005, 1050 | "name": "BadOwner", 1051 | "msg": "passed in owner doesnt have the rights to do this" 1052 | }, 1053 | { 1054 | "code": 6006, 1055 | "name": "FailedVocVerification", 1056 | "msg": "failed voc verification" 1057 | }, 1058 | { 1059 | "code": 6007, 1060 | "name": "FailedFvcVerification", 1061 | "msg": "failed fvc verification" 1062 | }, 1063 | { 1064 | "code": 6008, 1065 | "name": "FailedMerkleProofVerification", 1066 | "msg": "failed merkle proof verification" 1067 | } 1068 | ] 1069 | }; 1070 | -------------------------------------------------------------------------------- /src/tensor_whitelist/idl/tensor_whitelist_v0_1_0.ts: -------------------------------------------------------------------------------- 1 | export type TensorWhitelist = { 2 | version: "0.1.0"; 3 | name: "tensor_whitelist"; 4 | instructions: [ 5 | { 6 | name: "initUpdateAuthority"; 7 | accounts: [ 8 | { 9 | name: "whitelistAuthority"; 10 | isMut: true; 11 | isSigner: false; 12 | }, 13 | { 14 | name: "owner"; 15 | isMut: true; 16 | isSigner: true; 17 | }, 18 | { 19 | name: "systemProgram"; 20 | isMut: false; 21 | isSigner: false; 22 | } 23 | ]; 24 | args: [ 25 | { 26 | name: "newOwner"; 27 | type: "publicKey"; 28 | } 29 | ]; 30 | }, 31 | { 32 | name: "initUpdateWhitelist"; 33 | accounts: [ 34 | { 35 | name: "whitelist"; 36 | isMut: true; 37 | isSigner: false; 38 | }, 39 | { 40 | name: "whitelistAuthority"; 41 | isMut: false; 42 | isSigner: false; 43 | docs: [ 44 | "there can only be 1 whitelist authority (due to seeds),", 45 | "and we're checking that 1)the correct owner is present on it, and 2)is a signer" 46 | ]; 47 | }, 48 | { 49 | name: "owner"; 50 | isMut: true; 51 | isSigner: true; 52 | }, 53 | { 54 | name: "systemProgram"; 55 | isMut: false; 56 | isSigner: false; 57 | } 58 | ]; 59 | args: [ 60 | { 61 | name: "uuid"; 62 | type: { 63 | array: ["u8", 32]; 64 | }; 65 | }, 66 | { 67 | name: "rootHash"; 68 | type: { 69 | option: { 70 | array: ["u8", 32]; 71 | }; 72 | }; 73 | }, 74 | { 75 | name: "name"; 76 | type: { 77 | option: { 78 | array: ["u8", 32]; 79 | }; 80 | }; 81 | } 82 | ]; 83 | }, 84 | { 85 | name: "initUpdateMintProof"; 86 | accounts: [ 87 | { 88 | name: "whitelist"; 89 | isMut: false; 90 | isSigner: false; 91 | }, 92 | { 93 | name: "mint"; 94 | isMut: false; 95 | isSigner: false; 96 | }, 97 | { 98 | name: "mintProof"; 99 | isMut: true; 100 | isSigner: false; 101 | }, 102 | { 103 | name: "user"; 104 | isMut: true; 105 | isSigner: true; 106 | }, 107 | { 108 | name: "systemProgram"; 109 | isMut: false; 110 | isSigner: false; 111 | } 112 | ]; 113 | args: [ 114 | { 115 | name: "proof"; 116 | type: { 117 | vec: { 118 | array: ["u8", 32]; 119 | }; 120 | }; 121 | } 122 | ]; 123 | } 124 | ]; 125 | accounts: [ 126 | { 127 | name: "authority"; 128 | type: { 129 | kind: "struct"; 130 | fields: [ 131 | { 132 | name: "bump"; 133 | type: "u8"; 134 | }, 135 | { 136 | name: "owner"; 137 | type: "publicKey"; 138 | } 139 | ]; 140 | }; 141 | }, 142 | { 143 | name: "whitelist"; 144 | type: { 145 | kind: "struct"; 146 | fields: [ 147 | { 148 | name: "version"; 149 | type: "u8"; 150 | }, 151 | { 152 | name: "bump"; 153 | type: "u8"; 154 | }, 155 | { 156 | name: "verified"; 157 | type: "bool"; 158 | }, 159 | { 160 | name: "rootHash"; 161 | type: { 162 | array: ["u8", 32]; 163 | }; 164 | }, 165 | { 166 | name: "uuid"; 167 | type: { 168 | array: ["u8", 32]; 169 | }; 170 | }, 171 | { 172 | name: "name"; 173 | type: { 174 | array: ["u8", 32]; 175 | }; 176 | } 177 | ]; 178 | }; 179 | }, 180 | { 181 | name: "mintProof"; 182 | type: { 183 | kind: "struct"; 184 | fields: [ 185 | { 186 | name: "proofLen"; 187 | type: "u8"; 188 | }, 189 | { 190 | name: "proof"; 191 | type: { 192 | array: [ 193 | { 194 | array: ["u8", 32]; 195 | }, 196 | 28 197 | ]; 198 | }; 199 | } 200 | ]; 201 | }; 202 | } 203 | ]; 204 | errors: [ 205 | { 206 | code: 6000; 207 | name: "BadOwner"; 208 | msg: "passed in owner doesnt have the rights to do this"; 209 | }, 210 | { 211 | code: 6001; 212 | name: "MissingRootHash"; 213 | msg: "missing root hash"; 214 | }, 215 | { 216 | code: 6002; 217 | name: "MissingName"; 218 | msg: "missing name"; 219 | }, 220 | { 221 | code: 6003; 222 | name: "InvalidProof"; 223 | msg: "invalid merkle proof, token not whitelisted"; 224 | }, 225 | { 226 | code: 6004; 227 | name: "ProofTooLong"; 228 | msg: "proof provided exceeds the limit of 32 hashes"; 229 | } 230 | ]; 231 | }; 232 | 233 | export const IDL: TensorWhitelist = { 234 | version: "0.1.0", 235 | name: "tensor_whitelist", 236 | instructions: [ 237 | { 238 | name: "initUpdateAuthority", 239 | accounts: [ 240 | { 241 | name: "whitelistAuthority", 242 | isMut: true, 243 | isSigner: false, 244 | }, 245 | { 246 | name: "owner", 247 | isMut: true, 248 | isSigner: true, 249 | }, 250 | { 251 | name: "systemProgram", 252 | isMut: false, 253 | isSigner: false, 254 | }, 255 | ], 256 | args: [ 257 | { 258 | name: "newOwner", 259 | type: "publicKey", 260 | }, 261 | ], 262 | }, 263 | { 264 | name: "initUpdateWhitelist", 265 | accounts: [ 266 | { 267 | name: "whitelist", 268 | isMut: true, 269 | isSigner: false, 270 | }, 271 | { 272 | name: "whitelistAuthority", 273 | isMut: false, 274 | isSigner: false, 275 | docs: [ 276 | "there can only be 1 whitelist authority (due to seeds),", 277 | "and we're checking that 1)the correct owner is present on it, and 2)is a signer", 278 | ], 279 | }, 280 | { 281 | name: "owner", 282 | isMut: true, 283 | isSigner: true, 284 | }, 285 | { 286 | name: "systemProgram", 287 | isMut: false, 288 | isSigner: false, 289 | }, 290 | ], 291 | args: [ 292 | { 293 | name: "uuid", 294 | type: { 295 | array: ["u8", 32], 296 | }, 297 | }, 298 | { 299 | name: "rootHash", 300 | type: { 301 | option: { 302 | array: ["u8", 32], 303 | }, 304 | }, 305 | }, 306 | { 307 | name: "name", 308 | type: { 309 | option: { 310 | array: ["u8", 32], 311 | }, 312 | }, 313 | }, 314 | ], 315 | }, 316 | { 317 | name: "initUpdateMintProof", 318 | accounts: [ 319 | { 320 | name: "whitelist", 321 | isMut: false, 322 | isSigner: false, 323 | }, 324 | { 325 | name: "mint", 326 | isMut: false, 327 | isSigner: false, 328 | }, 329 | { 330 | name: "mintProof", 331 | isMut: true, 332 | isSigner: false, 333 | }, 334 | { 335 | name: "user", 336 | isMut: true, 337 | isSigner: true, 338 | }, 339 | { 340 | name: "systemProgram", 341 | isMut: false, 342 | isSigner: false, 343 | }, 344 | ], 345 | args: [ 346 | { 347 | name: "proof", 348 | type: { 349 | vec: { 350 | array: ["u8", 32], 351 | }, 352 | }, 353 | }, 354 | ], 355 | }, 356 | ], 357 | accounts: [ 358 | { 359 | name: "authority", 360 | type: { 361 | kind: "struct", 362 | fields: [ 363 | { 364 | name: "bump", 365 | type: "u8", 366 | }, 367 | { 368 | name: "owner", 369 | type: "publicKey", 370 | }, 371 | ], 372 | }, 373 | }, 374 | { 375 | name: "whitelist", 376 | type: { 377 | kind: "struct", 378 | fields: [ 379 | { 380 | name: "version", 381 | type: "u8", 382 | }, 383 | { 384 | name: "bump", 385 | type: "u8", 386 | }, 387 | { 388 | name: "verified", 389 | type: "bool", 390 | }, 391 | { 392 | name: "rootHash", 393 | type: { 394 | array: ["u8", 32], 395 | }, 396 | }, 397 | { 398 | name: "uuid", 399 | type: { 400 | array: ["u8", 32], 401 | }, 402 | }, 403 | { 404 | name: "name", 405 | type: { 406 | array: ["u8", 32], 407 | }, 408 | }, 409 | ], 410 | }, 411 | }, 412 | { 413 | name: "mintProof", 414 | type: { 415 | kind: "struct", 416 | fields: [ 417 | { 418 | name: "proofLen", 419 | type: "u8", 420 | }, 421 | { 422 | name: "proof", 423 | type: { 424 | array: [ 425 | { 426 | array: ["u8", 32], 427 | }, 428 | 28, 429 | ], 430 | }, 431 | }, 432 | ], 433 | }, 434 | }, 435 | ], 436 | errors: [ 437 | { 438 | code: 6000, 439 | name: "BadOwner", 440 | msg: "passed in owner doesnt have the rights to do this", 441 | }, 442 | { 443 | code: 6001, 444 | name: "MissingRootHash", 445 | msg: "missing root hash", 446 | }, 447 | { 448 | code: 6002, 449 | name: "MissingName", 450 | msg: "missing name", 451 | }, 452 | { 453 | code: 6003, 454 | name: "InvalidProof", 455 | msg: "invalid merkle proof, token not whitelisted", 456 | }, 457 | { 458 | code: 6004, 459 | name: "ProofTooLong", 460 | msg: "proof provided exceeds the limit of 32 hashes", 461 | }, 462 | ], 463 | }; 464 | -------------------------------------------------------------------------------- /src/tensor_whitelist/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./constants"; 2 | export * from "./sdk"; 3 | export * from "./pda"; 4 | -------------------------------------------------------------------------------- /src/tensor_whitelist/pda.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { TLIST_ADDR } from "./constants"; 3 | 4 | export const findWhitelistAuthPDA = ({ program }: { program?: PublicKey }) => { 5 | return PublicKey.findProgramAddressSync([], program ?? TLIST_ADDR); 6 | }; 7 | 8 | export const findWhitelistPDA = ({ 9 | program, 10 | uuid, 11 | }: { 12 | program?: PublicKey; 13 | uuid: number[]; 14 | }) => { 15 | return PublicKey.findProgramAddressSync( 16 | [Buffer.from(uuid)], 17 | program ?? TLIST_ADDR 18 | ); 19 | }; 20 | 21 | export const findMintProofPDA = ({ 22 | program, 23 | mint, 24 | whitelist, 25 | }: { 26 | program?: PublicKey; 27 | mint: PublicKey; 28 | whitelist: PublicKey; 29 | }) => { 30 | return PublicKey.findProgramAddressSync( 31 | [Buffer.from("mint_proof"), mint.toBytes(), whitelist.toBytes()], 32 | program ?? TLIST_ADDR 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /src/tensor_whitelist/sdk.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AccountInfo, 3 | Commitment, 4 | PublicKey, 5 | SystemProgram, 6 | } from "@solana/web3.js"; 7 | import { Coder, Program, Provider } from "@coral-xyz/anchor"; 8 | import { TLIST_ADDR, TLIST_COSIGNER, TLIST_OWNER } from "./constants"; 9 | import { 10 | findMintProofPDA, 11 | findWhitelistAuthPDA, 12 | findWhitelistPDA, 13 | } from "./pda"; 14 | import { v4 } from "uuid"; 15 | import MerkleTree from "merkletreejs"; 16 | import keccak256 from "keccak256"; 17 | import { 18 | DEFAULT_MICRO_LAMPORTS, 19 | DEFAULT_XFER_COMPUTE_UNITS, 20 | evalMathExpr, 21 | } from "../common"; 22 | 23 | // ---------------------------------------- Versioned IDLs for backwards compat when parsing. 24 | import { 25 | IDL as IDL_v0_1_0, 26 | TensorWhitelist as TensorWhitelist_v0_1_0, 27 | } from "./idl/tensor_whitelist_v0_1_0"; 28 | 29 | import { 30 | IDL as IDL_latest, 31 | TensorWhitelist as TensorWhitelist_latest, 32 | } from "./idl/tensor_whitelist"; 33 | import { 34 | AcctDiscHexMap, 35 | Cluster, 36 | decodeAnchorAcct, 37 | genAcctDiscHexMap, 38 | getRent, 39 | getRentSync, 40 | hexCode, 41 | prependComputeIxs, 42 | removeNullBytes, 43 | } from "@tensor-hq/tensor-common"; 44 | 45 | //a non-breaking update to migrate account space to exportable constants: https://explorer.solana.com/tx/5czMUGttDttcXwhTTGH8QzyTffwcVfeUAQbY2FzSh8WGxRFBQAmdrYeGBQxfEfS1bog4CfTvqPvXmvxdygQ5aJKE 46 | export const TensorWhitelistIDL_v0_1_0 = IDL_v0_1_0; 47 | export const TensorWhitelistIDL_v0_1_0_EffSlot = 0; //todo find slot 48 | 49 | // added 3 types of verification: https://solscan.io/tx/55gtoZSTKf96XL6XDD5e9F4nkoiPqXHtP4mJoYNT6eZVwtHw2FRRhVxfg9jHADMLrVS2FmNRh2VAWVCqnTxrX3Ro 50 | export const TensorWhitelistIDL_latest = IDL_latest; 51 | export const TensorWhitelistIDL_latest_EffSlot_Mainnet = 172170872; 52 | export const TensorWhitelistIDL_latest_EffSlot_Devnet = 203539290; 53 | 54 | export type TensorWhitelistIDL = 55 | | TensorWhitelist_v0_1_0 56 | | TensorWhitelist_latest; 57 | 58 | // Use this function to figure out which IDL to use based on the slot # of historical txs. 59 | export const triageWhitelistIDL = ( 60 | slot: number | bigint, 61 | cluster: Cluster 62 | ): TensorWhitelistIDL | null => { 63 | switch (cluster) { 64 | case Cluster.Mainnet: 65 | //cba to parse really old txs, this was before public launch 66 | if (slot < TensorWhitelistIDL_v0_1_0_EffSlot) return null; 67 | if (slot < TensorWhitelistIDL_latest_EffSlot_Mainnet) 68 | return TensorWhitelistIDL_v0_1_0; 69 | return TensorWhitelistIDL_latest; 70 | case Cluster.Devnet: 71 | if (slot < TensorWhitelistIDL_latest_EffSlot_Devnet) 72 | return TensorWhitelistIDL_v0_1_0; 73 | return TensorWhitelistIDL_latest; 74 | } 75 | }; 76 | 77 | // --------------------------------------- constants 78 | 79 | export const WHITELIST_SIZE: number = evalMathExpr( 80 | IDL_latest.constants.find((c) => c.name === "WHITELIST_SIZE")!.value 81 | ); 82 | export const AUTHORITY_SIZE: number = evalMathExpr( 83 | IDL_latest.constants.find((c) => c.name === "AUTHORITY_SIZE")!.value 84 | ); 85 | export const MINT_PROOF_SIZE: number = evalMathExpr( 86 | IDL_latest.constants.find((c) => c.name === "MINT_PROOF_SIZE")!.value 87 | ); 88 | 89 | export const APPROX_WHITELIST_RENT = getRentSync(WHITELIST_SIZE); 90 | export const APPROX_AUTHORITY_RENT = getRentSync(AUTHORITY_SIZE); 91 | export const APPROX_MINT_PROOF_RENT = getRentSync(MINT_PROOF_SIZE); 92 | 93 | // --------------------------------------- state structs 94 | 95 | export type AuthorityAnchor = { 96 | bump: number; 97 | cosigner: PublicKey; 98 | owner: PublicKey; 99 | }; 100 | 101 | export type WhitelistAnchor = { 102 | version: number; 103 | bump: number; 104 | verified: boolean; 105 | rootHash: number[]; 106 | uuid: number[]; 107 | name: number[]; 108 | frozen: boolean; 109 | voc?: PublicKey; 110 | fvc?: PublicKey; 111 | }; 112 | 113 | export type MintProofAnchor = { 114 | proofLen: number; 115 | proof: number[][]; 116 | }; 117 | 118 | export type TensorWhitelistPdaAnchor = AuthorityAnchor | WhitelistAnchor; 119 | 120 | export type TaggedTensorWhitelistPdaAnchor = 121 | | { 122 | name: "authority"; 123 | account: AuthorityAnchor; 124 | } 125 | | { 126 | name: "whitelist"; 127 | account: WhitelistAnchor; 128 | } 129 | | { 130 | name: "mintProof"; 131 | account: MintProofAnchor; 132 | }; 133 | 134 | // --------------------------------------- sdk 135 | 136 | export class TensorWhitelistSDK { 137 | program: Program; 138 | discMap: AcctDiscHexMap; 139 | 140 | constructor({ 141 | idl = IDL_latest, 142 | addr = TLIST_ADDR, 143 | provider, 144 | coder, 145 | }: { 146 | idl?: any; //todo better typing 147 | addr?: PublicKey; 148 | provider?: Provider; 149 | coder?: Coder; 150 | }) { 151 | this.program = new Program(idl, addr, provider, coder); 152 | this.discMap = genAcctDiscHexMap(idl); 153 | } 154 | 155 | // --------------------------------------- fetchers 156 | 157 | async fetchAuthority(authority: PublicKey, commitment?: Commitment) { 158 | return (await this.program.account.authority.fetch( 159 | authority, 160 | commitment 161 | )) as AuthorityAnchor; 162 | } 163 | 164 | async fetchWhitelist(whitelist: PublicKey, commitment?: Commitment) { 165 | return (await this.program.account.whitelist.fetch( 166 | whitelist, 167 | commitment 168 | )) as WhitelistAnchor; 169 | } 170 | 171 | async fetchMintProof(mintProof: PublicKey, commitment?: Commitment) { 172 | return (await this.program.account.mintProof.fetch( 173 | mintProof, 174 | commitment 175 | )) as MintProofAnchor; 176 | } 177 | 178 | // --------------------------------------- account methods 179 | 180 | decode(acct: AccountInfo): TaggedTensorWhitelistPdaAnchor | null { 181 | if (!acct.owner.equals(this.program.programId)) return null; 182 | return decodeAnchorAcct(acct, this.discMap); 183 | } 184 | 185 | // --------------------------------------- authority methods 186 | 187 | //main signature: cosigner 188 | async initUpdateAuthority({ 189 | cosigner = TLIST_COSIGNER, 190 | owner = TLIST_OWNER, 191 | newCosigner, 192 | newOwner, 193 | }: { 194 | cosigner?: PublicKey; 195 | owner?: PublicKey; 196 | newCosigner: PublicKey | null; 197 | newOwner: PublicKey | null; 198 | }) { 199 | const [authPda] = findWhitelistAuthPDA({}); 200 | 201 | const builder = this.program.methods 202 | .initUpdateAuthority(newCosigner, newOwner) 203 | .accounts({ 204 | whitelistAuthority: authPda, 205 | owner, 206 | cosigner, 207 | systemProgram: SystemProgram.programId, 208 | }); 209 | 210 | return { 211 | builder, 212 | tx: { ixs: [await builder.instruction()], extraSigners: [] }, 213 | authPda, 214 | }; 215 | } 216 | 217 | // --------------------------------------- whitelist methods 218 | 219 | //main signature: cosigner 220 | async initUpdateWhitelist({ 221 | cosigner = TLIST_COSIGNER, 222 | owner, //can't pass default here, coz then it'll be auto-included in rem accs 223 | uuid, 224 | rootHash = null, 225 | name = null, 226 | voc = null, 227 | fvc = null, 228 | compute = null, 229 | priorityMicroLamports = DEFAULT_MICRO_LAMPORTS, 230 | }: { 231 | cosigner?: PublicKey; 232 | owner?: PublicKey; 233 | uuid: number[]; 234 | rootHash?: number[] | null; 235 | name?: number[] | null; 236 | voc?: PublicKey | null; 237 | fvc?: PublicKey | null; 238 | priorityMicroLamports?: number | null; 239 | compute?: number | null; 240 | }) { 241 | const [authPda] = findWhitelistAuthPDA({}); 242 | const [whitelistPda] = findWhitelistPDA({ 243 | uuid, 244 | }); 245 | 246 | //only needed for frozen whitelists 247 | const remAcc = owner 248 | ? [ 249 | { 250 | pubkey: owner, 251 | isWritable: false, 252 | isSigner: true, 253 | }, 254 | ] 255 | : []; 256 | 257 | const builder = this.program.methods 258 | .initUpdateWhitelist(uuid, rootHash, name, voc, fvc) 259 | .accounts({ 260 | whitelist: whitelistPda, 261 | whitelistAuthority: authPda, 262 | cosigner, 263 | systemProgram: SystemProgram.programId, 264 | }) 265 | .remainingAccounts(remAcc); 266 | 267 | return { 268 | builder, 269 | tx: { 270 | ixs: prependComputeIxs( 271 | [await builder.instruction()], 272 | compute, 273 | priorityMicroLamports 274 | ), 275 | extraSigners: [], 276 | }, 277 | authPda, 278 | whitelistPda, 279 | }; 280 | } 281 | 282 | //main signature: cosigner 283 | async freezeWhitelist({ 284 | uuid, 285 | cosigner = TLIST_COSIGNER, 286 | }: { 287 | uuid: number[]; 288 | cosigner?: PublicKey; 289 | }) { 290 | const [authPda] = findWhitelistAuthPDA({}); 291 | const [whitelistPda] = findWhitelistPDA({ 292 | uuid, 293 | }); 294 | 295 | const builder = this.program.methods.freezeWhitelist().accounts({ 296 | whitelist: whitelistPda, 297 | whitelistAuthority: authPda, 298 | cosigner, 299 | systemProgram: SystemProgram.programId, 300 | }); 301 | 302 | return { 303 | builder, 304 | tx: { ixs: [await builder.instruction()], extraSigners: [] }, 305 | authPda, 306 | whitelistPda, 307 | }; 308 | } 309 | 310 | //main signature: owner 311 | async unfreezeWhitelist({ 312 | uuid, 313 | owner = TLIST_OWNER, 314 | }: { 315 | uuid: number[]; 316 | owner?: PublicKey; 317 | }) { 318 | const [authPda] = findWhitelistAuthPDA({}); 319 | const [whitelistPda] = findWhitelistPDA({ 320 | uuid, 321 | }); 322 | 323 | const builder = this.program.methods.unfreezeWhitelist().accounts({ 324 | whitelist: whitelistPda, 325 | whitelistAuthority: authPda, 326 | owner, 327 | systemProgram: SystemProgram.programId, 328 | }); 329 | 330 | return { 331 | builder, 332 | tx: { ixs: [await builder.instruction()], extraSigners: [] }, 333 | authPda, 334 | whitelistPda, 335 | }; 336 | } 337 | 338 | // --------------------------------------- mint proof methods 339 | 340 | //main signature: user 341 | async initUpdateMintProof({ 342 | user, 343 | mint, 344 | whitelist, 345 | proof, 346 | }: { 347 | user: PublicKey; 348 | mint: PublicKey; 349 | whitelist: PublicKey; 350 | proof: Buffer[]; 351 | }) { 352 | const [mintProofPda] = findMintProofPDA({ mint, whitelist }); 353 | 354 | const builder = this.program.methods.initUpdateMintProof(proof).accounts({ 355 | whitelist, 356 | mint, 357 | user, 358 | mintProof: mintProofPda, 359 | systemProgram: SystemProgram.programId, 360 | }); 361 | 362 | return { 363 | builder, 364 | tx: { ixs: [await builder.instruction()], extraSigners: [] }, 365 | mintProofPda, 366 | }; 367 | } 368 | 369 | // --------------------------------------- reallocs 370 | 371 | async reallocAuthority({ 372 | cosigner = TLIST_COSIGNER, 373 | }: { 374 | cosigner?: PublicKey; 375 | }) { 376 | const [authPda] = findWhitelistAuthPDA({}); 377 | 378 | const builder = this.program.methods.reallocAuthority().accounts({ 379 | whitelistAuthority: authPda, 380 | cosigner, 381 | systemProgram: SystemProgram.programId, 382 | }); 383 | 384 | return { 385 | builder, 386 | tx: { ixs: [await builder.instruction()], extraSigners: [] }, 387 | authPda, 388 | }; 389 | } 390 | 391 | async reallocWhitelist({ 392 | uuid, 393 | cosigner = TLIST_COSIGNER, 394 | }: { 395 | uuid: number[]; 396 | cosigner?: PublicKey; 397 | }) { 398 | const [authPda] = findWhitelistAuthPDA({}); 399 | const [whitelistPda] = findWhitelistPDA({ 400 | uuid, 401 | }); 402 | 403 | const builder = this.program.methods.reallocWhitelist().accounts({ 404 | whitelist: whitelistPda, 405 | whitelistAuthority: authPda, 406 | cosigner, 407 | systemProgram: SystemProgram.programId, 408 | }); 409 | 410 | return { 411 | builder, 412 | tx: { ixs: [await builder.instruction()], extraSigners: [] }, 413 | authPda, 414 | whitelistPda, 415 | }; 416 | } 417 | 418 | // --------------------------------------- helper methods 419 | 420 | async getWhitelistRent() { 421 | return await getRent( 422 | this.program.provider.connection, 423 | this.program.account.whitelist 424 | ); 425 | } 426 | 427 | async getAuthorityRent() { 428 | return await getRent( 429 | this.program.provider.connection, 430 | this.program.account.authority 431 | ); 432 | } 433 | 434 | async getMintProofRent() { 435 | return await getRent( 436 | this.program.provider.connection, 437 | this.program.account.mintProof 438 | ); 439 | } 440 | 441 | getError( 442 | name: typeof IDL_latest["errors"][number]["name"] 443 | ): typeof IDL_latest["errors"][number] { 444 | //@ts-ignore (throwing weird ts errors for me) 445 | return this.program.idl.errors.find((e) => e.name === name)!; 446 | } 447 | 448 | getErrorCodeHex(name: typeof IDL_latest["errors"][number]["name"]): string { 449 | return hexCode(this.getError(name).code); 450 | } 451 | 452 | static uuidToBuffer = (uuid: string) => { 453 | return Buffer.from(uuid.replaceAll("-", "")).toJSON().data; 454 | }; 455 | 456 | static bufferToUuid = (buffer: number[]) => { 457 | const raw = String.fromCharCode(...buffer); 458 | return `${raw.slice(0, 8)}-${raw.slice(8, 12)}-${raw.slice( 459 | 12, 460 | 16 461 | )}-${raw.slice(16, 20)}-${raw.slice(20)}`; 462 | }; 463 | 464 | // NB: this truncates names to 32 bytes (32 chars if ascii, < if unicode). 465 | static nameToBuffer = (name: string) => { 466 | return Buffer.from(name.padEnd(32, "\0")).toJSON().data.slice(0, 32); 467 | }; 468 | 469 | static bufferToName = (buffer: number[]) => { 470 | return removeNullBytes(String.fromCharCode(...buffer)); 471 | }; 472 | 473 | // Generates a Merkle tree + root hash + proofs for a set of mints. 474 | static createTreeForMints = (mints: PublicKey[], skipVerify: boolean = false) => { 475 | const buffers = mints.map((m) => m.toBuffer()); 476 | 477 | // Create hashes 478 | const leaves = buffers.map(keccak256); 479 | 480 | // Create an array of { leaf, mint } to preserve mapping 481 | const leafMintPairs = leaves.map((leaf, index) => { 482 | return { leaf: leaf, mint: mints[index] }; 483 | }); 484 | 485 | // Presort the array based on the leaves, so that original leaves remain in the same order after tree construction 486 | const sortedLeafMintPairs = leafMintPairs.slice().sort((a, b) => Buffer.compare(a.leaf, b.leaf)); 487 | 488 | // Extract only the leaves from sortedLeafMintPairs 489 | const sortedLeaves = sortedLeafMintPairs.map(pair => pair.leaf); 490 | 491 | const tree = new MerkleTree(sortedLeaves, keccak256, { 492 | sortPairs: true, 493 | }); 494 | 495 | const rootHash = tree.getRoot(); 496 | 497 | // Get all proofs (order should be same as leaves) 498 | const allProofs = tree.getProofs(); 499 | 500 | // This assumes proofs indices align with mints indices (which appears to be the case). 501 | const proofs: { mint: PublicKey; proof: Buffer[] }[] = sortedLeafMintPairs.map((val, index) => { 502 | const proof = allProofs[index]; 503 | const mint = val.mint; 504 | 505 | if (!skipVerify && !tree.verify(proof, keccak256(mint.toBuffer()), rootHash)){ 506 | throw new Error(`Invalid proof for mint at index ${index}`); 507 | } 508 | 509 | const validProof: Buffer[] = proof.map((p) => p.data); 510 | 511 | return { mint, proof: validProof }; 512 | }); 513 | 514 | return { tree, root: tree.getRoot().toJSON().data, proofs }; 515 | }; 516 | 517 | genWhitelistUUID() { 518 | return v4().toString(); 519 | } 520 | } 521 | -------------------------------------------------------------------------------- /src/tensorswap/constants.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | // constants separated from sdk.ts, used by the frontend 4 | 5 | //(!) Keep in sync with Tensorswap contract and TBID_FEE_BPS 6 | export const TSWAP_TAKER_FEE_BPS: number = 150; 7 | export const MAKER_REBATE_BPS: number = 25; 8 | 9 | export const TENSORSWAP_ADDR = new PublicKey( 10 | process.env.TENSORSWAP_ADDR || "TSWAPaqyCSx2KABk68Shruf4rp7CxcNi8hAsbdwmHbN" 11 | ); 12 | 13 | //@Deprecated, not used inside of Tswap anymore 14 | export const TSWAP_FEE_ACC = new PublicKey( 15 | process.env.TSWAP_FEE_ACC || "4zdNGgAtFsW1cQgHqkiWyRsxaAgxrSRRynnuunxzjxue" 16 | ); 17 | 18 | export const TSWAP_COSIGNER = new PublicKey( 19 | process.env.TSWAP_COSIGNER || "6WQvG9Z6D1NZM76Ljz3WjgR7gGXRBJohHASdQxXyKi8q" 20 | ); 21 | 22 | export const TSWAP_OWNER = new PublicKey( 23 | process.env.TSWAP_OWNER || "99cmWwQMqMFzMPx85rvZYKwusGSjZUDsu6mqYV4iisiz" 24 | ); 25 | -------------------------------------------------------------------------------- /src/tensorswap/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./prices"; 2 | export * from "./types"; 3 | export * from "./constants"; 4 | export * from "./sdk"; 5 | export * from "./pda"; 6 | -------------------------------------------------------------------------------- /src/tensorswap/pda.ts: -------------------------------------------------------------------------------- 1 | import { AccountInfo, Connection, PublicKey } from "@solana/web3.js"; 2 | import BN from "bn.js"; 3 | import { TENSORSWAP_ADDR } from "./constants"; 4 | 5 | export const findPoolPDA = ({ 6 | program, 7 | tswap, 8 | owner, 9 | whitelist, 10 | poolType, 11 | curveType, 12 | startingPrice, 13 | delta, 14 | }: { 15 | program?: PublicKey; 16 | tswap: PublicKey; 17 | owner: PublicKey; 18 | whitelist: PublicKey; 19 | poolType: number; 20 | curveType: number; 21 | startingPrice: BN; 22 | delta: BN; 23 | }): [PublicKey, number] => { 24 | return PublicKey.findProgramAddressSync( 25 | [ 26 | tswap.toBytes(), 27 | owner.toBytes(), 28 | whitelist.toBytes(), 29 | //u8s, hence 1 byte each 30 | new BN(poolType).toArrayLike(Uint8Array as any, "le", 1), 31 | new BN(curveType).toArrayLike(Uint8Array as any, "le", 1), 32 | //u64s, hence 8 bytes each 33 | startingPrice.toArrayLike(Uint8Array as any, "le", 8), 34 | delta.toArrayLike(Uint8Array as any, "le", 8), 35 | ], 36 | program ?? TENSORSWAP_ADDR 37 | ); 38 | }; 39 | 40 | export const findTSwapPDA = ({ program }: { program?: PublicKey }) => { 41 | return PublicKey.findProgramAddressSync([], program ?? TENSORSWAP_ADDR); 42 | }; 43 | 44 | export type FindMarginArgs = { 45 | tswap: PublicKey; 46 | owner: PublicKey; 47 | marginNr: number; 48 | program?: PublicKey; 49 | }; 50 | export const findMarginPDA = ({ 51 | tswap, 52 | owner, 53 | marginNr, 54 | program, 55 | }: FindMarginArgs) => { 56 | return PublicKey.findProgramAddressSync( 57 | [ 58 | Buffer.from("margin"), 59 | tswap.toBytes(), 60 | owner.toBytes(), 61 | //u16, hence 2 bytes 62 | new BN(marginNr).toArrayLike(Uint8Array as any, "le", 2), 63 | ], 64 | program ?? TENSORSWAP_ADDR 65 | ); 66 | }; 67 | 68 | export const findNextFreeMarginNr = async ({ 69 | connection, 70 | startNr, 71 | tswap, 72 | owner, 73 | program, 74 | }: { 75 | connection: Connection; 76 | startNr?: number; 77 | } & Omit) => { 78 | let marginNr = startNr ?? 0; 79 | let marginPda; 80 | let marginBump; 81 | let account: AccountInfo | null = null; 82 | while (marginNr < 2 ** 16) { 83 | [marginPda, marginBump] = findMarginPDA({ 84 | tswap, 85 | owner, 86 | marginNr, 87 | program, 88 | }); 89 | account = await connection.getAccountInfo(marginPda); 90 | if (!account) { 91 | return { marginNr, marginPda, marginBump }; 92 | } 93 | marginNr++; 94 | } 95 | throw new Error("margin number > u16::MAX"); 96 | }; 97 | 98 | export const findNftEscrowPDA = ({ 99 | program, 100 | nftMint, 101 | }: { 102 | program?: PublicKey; 103 | nftMint: PublicKey; 104 | }) => { 105 | return PublicKey.findProgramAddressSync( 106 | [Buffer.from("nft_escrow"), nftMint.toBytes()], 107 | program ?? TENSORSWAP_ADDR 108 | ); 109 | }; 110 | 111 | export const findNftDepositReceiptPDA = ({ 112 | program, 113 | nftMint, 114 | }: { 115 | program?: PublicKey; 116 | nftMint: PublicKey; 117 | }) => { 118 | return PublicKey.findProgramAddressSync( 119 | [Buffer.from("nft_receipt"), nftMint.toBytes()], 120 | program ?? TENSORSWAP_ADDR 121 | ); 122 | }; 123 | 124 | export const findSolEscrowPDA = ({ 125 | program, 126 | pool, 127 | }: { 128 | program?: PublicKey; 129 | pool: PublicKey; 130 | }) => { 131 | return PublicKey.findProgramAddressSync( 132 | [Buffer.from("sol_escrow"), pool.toBytes()], 133 | program ?? TENSORSWAP_ADDR 134 | ); 135 | }; 136 | 137 | export const findNftAuthorityPDA = ({ 138 | program, 139 | authSeed, 140 | }: { 141 | program?: PublicKey; 142 | authSeed: number[]; 143 | }) => { 144 | return PublicKey.findProgramAddressSync( 145 | [Buffer.from("nft_auth"), Buffer.from(authSeed)], 146 | program ?? TENSORSWAP_ADDR 147 | ); 148 | }; 149 | 150 | export const findSingleListingPDA = ({ 151 | program, 152 | nftMint, 153 | }: { 154 | program?: PublicKey; 155 | nftMint: PublicKey; 156 | }) => { 157 | return PublicKey.findProgramAddressSync( 158 | [Buffer.from("single_listing"), nftMint.toBytes()], 159 | program ?? TENSORSWAP_ADDR 160 | ); 161 | }; 162 | -------------------------------------------------------------------------------- /src/tensorswap/prices.ts: -------------------------------------------------------------------------------- 1 | import { CurveType, PoolConfig, PoolType, TakerSide } from "../types"; 2 | import Big from "big.js"; 3 | import BN from "bn.js"; 4 | 5 | export const HUNDRED_PCT_BPS = 100_00; 6 | // 0.1% seems to be enough to deal with truncation divergence b/w off-chain and on-chain. 7 | const EXPO_SLIPPAGE = 0.001; 8 | 9 | export type ComputePriceArgs = { 10 | config: PoolConfig; 11 | takerSellCount: number; 12 | takerBuyCount: number; 13 | takerSide: TakerSide; 14 | 15 | //v1.1: for selling, (statsTakerSellCount - statsTakerBuyCount) < maxTakerSellCount (o/w no taking). 16 | maxTakerSellCount: number; 17 | statsTakerSellCount: number; 18 | //for bids this is 0, only relevant for MM orders 19 | statsTakerBuyCount: number; 20 | 21 | //single "extra" selection field, instead of 2 (nftsSelectedToBuy / nftsSelectedToSell) 22 | //that's because for Trade pools we don't want user's selection in buy tab to influence price in sell tab and vv 23 | //takerSide basically decides which way we add it 24 | extraNFTsSelected: number; 25 | 26 | // In addition to your standard slippage, 27 | // for exponential prices, we MUST add a small tolerance/slippage 28 | // since on-chain and off-chain rounding is not exactly the same. 29 | // 0.01 = 1%. 30 | slippage?: number; 31 | 32 | //indicate if the pool is marginated or not 33 | marginated: boolean; 34 | }; 35 | 36 | // This is what should be displayed to the user ((!) no slippage, since slippage is only used for rounding errors). 37 | // In contrast, computeTakerPrice is what should be passed to the ix itself (has some noise slippage). 38 | export const computeTakerDisplayPrice = (args: ComputePriceArgs) => { 39 | // Explicitly set slippage to 0. 40 | return computeTakerPrice({ ...args, slippage: 0 }); 41 | }; 42 | 43 | // This includes MM fees (when taker is selling into a trade pool). 44 | // This should be used when computing deposit amounts + display (see computeTakerDisplayPrice) and nothing else. 45 | // (doesn't take into account mm fees). 46 | export const computeTakerPrice = (args: ComputePriceArgs): Big | null => { 47 | let currentPrice = computeCurrentPrice(args); 48 | if (currentPrice === null) return null; 49 | 50 | let priceWithMMFees = currentPrice; 51 | if ( 52 | args.config.poolType === PoolType.Trade && 53 | args.takerSide === TakerSide.Sell 54 | ) { 55 | priceWithMMFees = priceWithMMFees.sub( 56 | priceWithMMFees.mul(args.config.mmFeeBps ?? 0).div(HUNDRED_PCT_BPS) 57 | ); 58 | } 59 | 60 | return priceWithMMFees; 61 | }; 62 | 63 | // Computes the current (base) price of a pool (WITHOUT MM FEES), 64 | // optionally with slippage (so minPrice for Sell, maxPrice for Buy). 65 | // Note even w/ 0 slippage this price will differ from the on-chain current price 66 | // for Exponential curves b/c of rounding differences. 67 | // Will return null if price is neagtive (ie cannot sell anymore). 68 | const computeCurrentPrice = ({ 69 | config, 70 | takerSellCount, 71 | takerBuyCount, 72 | takerSide, 73 | maxTakerSellCount, 74 | statsTakerSellCount, 75 | statsTakerBuyCount, 76 | extraNFTsSelected, 77 | // Default small tolerance for exponential curves. 78 | slippage = config.curveType === CurveType.Linear ? 0 : EXPO_SLIPPAGE, 79 | marginated, 80 | }: ComputePriceArgs): Big | null => { 81 | // Cannot sell anymore into capped pool. 82 | if ( 83 | takerSide === TakerSide.Sell && 84 | marginated && 85 | maxTakerSellCount != 0 && 86 | statsTakerSellCount - statsTakerBuyCount >= maxTakerSellCount 87 | ) { 88 | return null; 89 | } 90 | 91 | let basePrice = (() => { 92 | switch (config.poolType) { 93 | case PoolType.Token: 94 | return _shiftPriceByDelta( 95 | config.curveType, 96 | config.startingPrice, 97 | config.delta, 98 | "down", 99 | takerSellCount + extraNFTsSelected 100 | ); 101 | case PoolType.NFT: 102 | return _shiftPriceByDelta( 103 | config.curveType, 104 | config.startingPrice, 105 | config.delta, 106 | "up", 107 | takerBuyCount + extraNFTsSelected 108 | ); 109 | case PoolType.Trade: 110 | const isSelling = takerSide === TakerSide.Sell; 111 | const offset = isSelling ? 1 : 0; 112 | const modSellCount = 113 | takerSellCount + offset + +isSelling * extraNFTsSelected; 114 | const modBuyCount = 115 | takerBuyCount + (1 - +isSelling) * extraNFTsSelected; 116 | if (modBuyCount > modSellCount) { 117 | return _shiftPriceByDelta( 118 | config.curveType, 119 | config.startingPrice, 120 | config.delta, 121 | "up", 122 | modBuyCount - modSellCount 123 | ); 124 | } else { 125 | return _shiftPriceByDelta( 126 | config.curveType, 127 | config.startingPrice, 128 | config.delta, 129 | "down", 130 | modSellCount - modBuyCount 131 | ); 132 | } 133 | } 134 | })(); 135 | 136 | if (basePrice.lt(0)) return null; 137 | 138 | basePrice = basePrice.mul( 139 | 1 + (takerSide === TakerSide.Buy ? 1 : -1) * slippage 140 | ); 141 | 142 | return basePrice; 143 | }; 144 | 145 | const _shiftPriceByDelta = ( 146 | curveType: CurveType, 147 | startingPrice: Big, 148 | delta: Big, 149 | direction: "up" | "down", 150 | times: number 151 | ): Big => { 152 | switch (curveType) { 153 | case CurveType.Exponential: 154 | switch (direction) { 155 | // price * (1 + delta)^trade_count 156 | case "up": 157 | return startingPrice.mul( 158 | new Big(1).add(delta.div(HUNDRED_PCT_BPS)).pow(times) 159 | ); 160 | case "down": 161 | return startingPrice.div( 162 | new Big(1).add(delta.div(HUNDRED_PCT_BPS)).pow(times) 163 | ); 164 | } 165 | break; 166 | case CurveType.Linear: 167 | switch (direction) { 168 | case "up": 169 | return startingPrice.add(delta.mul(times)); 170 | case "down": 171 | return startingPrice.sub(delta.mul(times)); 172 | } 173 | } 174 | }; 175 | 176 | // Use this to figure out (from the maker perspective): 177 | // (1) desired = count - how much SOL lamports (totalAmount) required to sell/buy `count` 178 | // (2) desired = total - how many NFTs (allowedCount) one can sell/buy with `total` 179 | // What's special about this fn is we add a bit of negative slippage exponential `totalAmount`. 180 | // This ensures that the maker deposits more than enough (for rounding issues). 181 | export const computeMakerAmountCount = ({ 182 | desired, 183 | maxCountWhenInfinite = 1000, 184 | ...priceArgs 185 | }: Omit & { 186 | desired: { count: number } | { total: BN }; 187 | // Necessary since when price = 0 or when selling (lin or exp), this can be infinity. 188 | // allowedCount can still exceed this if it can be computed & is finite. 189 | maxCountWhenInfinite?: number; 190 | }): { 191 | totalAmount: BN; 192 | allowedCount: number; 193 | initialPrice: BN | null; 194 | } => { 195 | const currPriceArgs = { 196 | ...priceArgs, 197 | slippage: 0, 198 | }; 199 | const initTakerPrice = computeTakerPrice(currPriceArgs); 200 | if (!initTakerPrice) { 201 | return { totalAmount: new BN(0), allowedCount: 0, initialPrice: null }; 202 | } 203 | 204 | const initialPrice = new BN(initTakerPrice.round().toString()); 205 | 206 | // For calculations, we need to apply MM fees AFTER we sum things up (for sells). 207 | const initTakerPriceNoMM = computeTakerPrice({ 208 | ...currPriceArgs, 209 | config: { 210 | ...currPriceArgs.config, 211 | mmFeeBps: 0, 212 | }, 213 | })!; 214 | 215 | const { takerSide, config } = priceArgs; 216 | 217 | /* 218 | Constants: 219 | p = initial price 220 | d = delta 221 | T = total amount 222 | n = allowed count 223 | +/- = plus (buying) / minus (selling) 224 | 225 | Linear: 226 | Solving for T: T = p +/- n(n-1)d/2 227 | Solving for n: 228 | BUYS (https://www.wolframalpha.com/input?i=solve+for+x+in+T+%3Dxp+%2B+x%28x-1%29d%2F2): 229 | only need positive root 230 | n = ( sqrt((d-2p)^2 + 8dT) + d - 2p ) / (2d) 231 | SELLS, (https://www.wolframalpha.com/input?i=solve+for+x+in+T+%3Dxp+-+x%28x-1%29d%2F2): 232 | only need negative root 233 | if (d-2p)62 - 8dT < 0 (ie we can buy until negative prices): 234 | n = maxCountWhenInfinite (see above) 235 | else: 236 | n = ( - sqrt((d-2p)^2 - 8dT) + d + 2p ) / (2d) 237 | 238 | Exponential: 239 | r = (1 +/- delta) 240 | Solving for T: T = p (1 - r^n) / (1 - r) 241 | Solving for n: 242 | BUYS: (https://www.wolframalpha.com/input?i=solve+for+x+in+T+%3D+p%281-r%5Ex%29+%2F+%281-r%29) 243 | n = log[(r - 1)T/p + 1] / log(r) 244 | SELLS: infinite (can always sell at 0) 245 | */ 246 | 247 | /// Clips allowed count by taking into account maxTakerSellCount cap. 248 | const adjustByMaxTakerCount = (allowedCount: number) => { 249 | if (takerSide !== TakerSide.Sell) return allowedCount; 250 | if (!priceArgs.marginated || priceArgs.maxTakerSellCount === 0) 251 | return allowedCount; 252 | 253 | // Negative should be fine. Eg taker sold 3, bought 5, adjTakerSellCount for pool is -2 254 | // if hook will never trigger, and subtracting a negative gives a positive (capped at allowedCount) 255 | const adjMaxTakerSellCount = 256 | priceArgs.statsTakerSellCount - (priceArgs.statsTakerBuyCount ?? 0); 257 | 258 | if (adjMaxTakerSellCount >= priceArgs.maxTakerSellCount) return 0; 259 | return Math.min( 260 | priceArgs.maxTakerSellCount - adjMaxTakerSellCount, 261 | allowedCount 262 | ); 263 | }; 264 | 265 | /// `undo` = add MM fee back; otherwise subtract MM fee. 266 | const adjustTotalMMFee = (total: Big, undo: boolean = false) => { 267 | if (config.poolType !== PoolType.Trade) return total; 268 | if (takerSide !== TakerSide.Sell) return total; 269 | 270 | const feePct = new Big(HUNDRED_PCT_BPS) 271 | .minus(config.mmFeeBps ?? 0) 272 | .div(HUNDRED_PCT_BPS); 273 | 274 | if (undo) { 275 | // Shouldn't be possible (mmFeeBps < 100_000), but if just return total. 276 | if (feePct.eq(0)) return total; 277 | return total.div(feePct); 278 | } 279 | return total.mul(feePct); 280 | }; 281 | 282 | /// This is how many times to price can decrease when selling before we reach 0. 283 | /// Specifically for sell, 284 | /// k = # of times we decrement delta before hitting 0 price 285 | /// p = initial price 286 | /// d = delta 287 | /// p - kd >= 0 --> k <= p / d 288 | /// So delta count = 1 + k 289 | /// Buying we just take desired count. 290 | const getMaxSellCountLinear = () => { 291 | if (initTakerPriceNoMM.eq(0)) { 292 | if (config.delta.eq(0)) return maxCountWhenInfinite; 293 | return 1; 294 | } 295 | 296 | if (config.delta.eq(0)) return maxCountWhenInfinite; 297 | 298 | return ( 299 | 1 + 300 | initTakerPriceNoMM.div(config.delta).round(0, Big.roundDown).toNumber() 301 | ); 302 | }; 303 | 304 | const getTotalAmountLinear = (desiredCount: number) => { 305 | const allowedCount = adjustByMaxTakerCount( 306 | takerSide === TakerSide.Buy 307 | ? desiredCount 308 | : Math.min(desiredCount, getMaxSellCountLinear()) 309 | ); 310 | 311 | // This is basically an arithmetic series: 312 | // T = p + (p +/- d) + p +/- 2d) + ... + (p +/- (n-1) d) 313 | // = p +/- n(n-1)d/2 314 | const base = initTakerPriceNoMM.mul(allowedCount); 315 | const deltas = config.delta.mul(allowedCount * (allowedCount - 1)).div(2); 316 | const totalAmount = 317 | takerSide === TakerSide.Buy ? base.add(deltas) : base.minus(deltas); 318 | 319 | return { 320 | allowedCount, 321 | totalAmount: new BN(adjustTotalMMFee(totalAmount).round().toString()), 322 | }; 323 | }; 324 | 325 | const getRateExp = () => { 326 | const base = 1 + config.delta.div(HUNDRED_PCT_BPS).toNumber(); 327 | if (takerSide === TakerSide.Buy) return base; 328 | return 1 / base; 329 | }; 330 | 331 | const getTotalAmountExp = (count: number) => { 332 | /* 333 | Exponential = geometric series. 334 | We have: 335 | r = decay (1 + delta for buy, 1 - delta for sell) 336 | Thus: 337 | T = p + pr + pr^2 + ... + pr^(n-1) 338 | = p * geosum(r, n-1) 339 | = p * (1 - r^n) / (1 - r), r != 1 340 | */ 341 | const allowedCount = adjustByMaxTakerCount(count); 342 | const r = getRateExp(); 343 | 344 | const geosum = (1 - Math.pow(r, allowedCount)) / (1 - r); 345 | const totalAmount = initTakerPriceNoMM.mul(geosum); 346 | 347 | return { 348 | allowedCount, 349 | // Negative slippage. 350 | totalAmount: new BN( 351 | adjustTotalMMFee(totalAmount) 352 | .mul(1 + EXPO_SLIPPAGE) 353 | .round() 354 | .toString() 355 | ), 356 | }; 357 | }; 358 | 359 | // delta = 0 when exp -> linear (degenerate) (o/w getRateExp will return 1 rate -> divide by 0s) 360 | const isLinear = config.curveType === CurveType.Linear || config.delta.eq(0); 361 | 362 | // ====================== By count 363 | 364 | if ("count" in desired) { 365 | if (isLinear) { 366 | const { allowedCount, totalAmount } = getTotalAmountLinear(desired.count); 367 | return { totalAmount, allowedCount, initialPrice }; 368 | } else { 369 | const { allowedCount, totalAmount } = getTotalAmountExp(desired.count); 370 | return { totalAmount, allowedCount, initialPrice }; 371 | } 372 | } 373 | 374 | // ====================== By total 375 | 376 | // Since our series are done PRE-MM fee, we need to "undo" the MM fee for our 377 | // desired total first, o/w it won't add up. 378 | const total = adjustTotalMMFee(new Big(desired.total.toString()), true); 379 | if (total.lt(initTakerPriceNoMM)) { 380 | return { totalAmount: new BN(0), allowedCount: 0, initialPrice }; 381 | } 382 | 383 | if (isLinear) { 384 | const twoP = initTakerPriceNoMM.mul(2); 385 | // These are the a, b, c in the quadratic formula. 386 | const fourAC = total.mul(config.delta).mul(8); 387 | const twoA = config.delta.mul(2); 388 | 389 | let tempCount: number; 390 | if (initTakerPriceNoMM.eq(0)) { 391 | // Protects against divide by 0. 392 | tempCount = maxCountWhenInfinite; 393 | } else if (config.delta.eq(0)) { 394 | // Protects against divide by 0. 395 | tempCount = total 396 | .div(initTakerPriceNoMM) 397 | .round(0, Big.roundDown) 398 | .toNumber(); 399 | } else { 400 | if (takerSide === TakerSide.Buy) { 401 | const bSquared = config.delta.minus(twoP).pow(2); 402 | // n = ( sqrt((d-2p)^2 + 8dT) + d - 2p ) / (2d) 403 | tempCount = bSquared 404 | .add(fourAC) 405 | .sqrt() 406 | .add(config.delta) 407 | .minus(twoP) 408 | .div(twoA) 409 | .round(0, Big.roundDown) 410 | .toNumber(); 411 | } else { 412 | // if (d-2p)^2 - 8dT < 0 (ie we can buy until negative prices): 413 | // n = maxCountWhenInfinite (see above) 414 | // else: 415 | // n = ( - sqrt((d+2p)^2 - 8dT) + d + 2p ) / (2d) 416 | const bSquared = config.delta.plus(twoP).pow(2); 417 | const operand = bSquared.minus(fourAC); 418 | if (operand.lt(0)) { 419 | tempCount = getMaxSellCountLinear(); 420 | } else { 421 | tempCount = operand 422 | .sqrt() 423 | .mul(-1) 424 | .add(config.delta) 425 | .add(twoP) 426 | .div(twoA) 427 | .round(0, Big.roundDown) 428 | .toNumber(); 429 | } 430 | } 431 | } 432 | tempCount = Math.max(0, tempCount); 433 | const { allowedCount, totalAmount } = getTotalAmountLinear(tempCount); 434 | return { allowedCount, totalAmount, initialPrice }; 435 | } 436 | 437 | // Exponential. 438 | // n = log[(r - 1)T/p + 1] / log(r), r != 1 439 | const r = getRateExp(); 440 | let tempCount: number; 441 | if (initTakerPriceNoMM.eq(0)) { 442 | // Protects against divide by 0. 443 | tempCount = maxCountWhenInfinite; 444 | } else { 445 | const operand = total 446 | .mul(r - 1) 447 | .div(initTakerPriceNoMM) 448 | .add(1) 449 | .toNumber(); 450 | 451 | if (operand <= 0) { 452 | // Only possible when r < 1 ie selling (since we can sell infinitely). 453 | tempCount = maxCountWhenInfinite; 454 | } else { 455 | tempCount = Math.floor(Math.log(operand) / Math.log(r)); 456 | } 457 | } 458 | tempCount = Math.max(0, tempCount); 459 | 460 | const { allowedCount, totalAmount } = getTotalAmountExp(tempCount); 461 | return { allowedCount, totalAmount, initialPrice }; 462 | }; 463 | -------------------------------------------------------------------------------- /src/tensorswap/types.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import Big from "big.js"; 3 | import BN from "bn.js"; 4 | import { CurveType, PoolConfig, PoolType } from "../types"; 5 | 6 | // --------------------------------------- pool type 7 | 8 | export const PoolTypeAnchor = { 9 | Token: { token: {} }, 10 | NFT: { nft: {} }, 11 | Trade: { trade: {} }, 12 | }; 13 | type PoolTypeAnchor = typeof PoolTypeAnchor[keyof typeof PoolTypeAnchor]; 14 | 15 | export const poolTypeU8 = (poolType: PoolTypeAnchor): 0 | 1 | 2 => { 16 | const order: Record = { 17 | token: 0, 18 | nft: 1, 19 | trade: 2, 20 | }; 21 | return order[Object.keys(poolType)[0]]; 22 | }; 23 | 24 | export const castPoolTypeAnchor = (poolType: PoolTypeAnchor): PoolType => 25 | ({ 26 | 0: PoolType.Token, 27 | 1: PoolType.NFT, 28 | 2: PoolType.Trade, 29 | }[poolTypeU8(poolType)]); 30 | 31 | export const castPoolType = (poolType: PoolType): PoolTypeAnchor => 32 | poolType === PoolType.NFT 33 | ? PoolTypeAnchor.NFT 34 | : poolType === PoolType.Token 35 | ? PoolTypeAnchor.Token 36 | : PoolTypeAnchor.Trade; 37 | 38 | // --------------------------------------- curve type 39 | 40 | export const CurveTypeAnchor = { 41 | Linear: { linear: {} }, 42 | Exponential: { exponential: {} }, 43 | }; 44 | 45 | type CurveTypeAnchor = typeof CurveTypeAnchor[keyof typeof CurveTypeAnchor]; 46 | 47 | export const curveTypeU8 = (curveType: CurveTypeAnchor): 0 | 1 => { 48 | const order: Record = { 49 | linear: 0, 50 | exponential: 1, 51 | }; 52 | return order[Object.keys(curveType)[0]]; 53 | }; 54 | 55 | export const castCurveTypeAnchor = (curveType: CurveTypeAnchor): CurveType => 56 | ({ 57 | 0: CurveType.Linear, 58 | 1: CurveType.Exponential, 59 | }[curveTypeU8(curveType)]); 60 | 61 | export const castCurveType = (curveType: CurveType): CurveTypeAnchor => 62 | curveType === CurveType.Linear 63 | ? CurveTypeAnchor.Linear 64 | : CurveTypeAnchor.Exponential; 65 | 66 | // --------------------------------------- config 67 | 68 | export type TSwapConfigAnchor = { 69 | feeBps: number; 70 | }; 71 | 72 | export type PoolConfigAnchor = { 73 | poolType: PoolTypeAnchor; 74 | curveType: CurveTypeAnchor; 75 | startingPrice: BN; 76 | delta: BN; 77 | mmCompoundFees: boolean; 78 | mmFeeBps: number | null; // null for non-trade pools 79 | }; 80 | 81 | export const castPoolConfigAnchor = (config: PoolConfigAnchor): PoolConfig => ({ 82 | poolType: castPoolTypeAnchor(config.poolType), 83 | curveType: castCurveTypeAnchor(config.curveType), 84 | startingPrice: new Big(config.startingPrice.toString()), 85 | delta: new Big(config.delta.toString()), 86 | mmCompoundFees: config.mmCompoundFees, 87 | mmFeeBps: config.mmFeeBps, 88 | }); 89 | 90 | export const castPoolConfig = (config: PoolConfig): PoolConfigAnchor => ({ 91 | poolType: castPoolType(config.poolType), 92 | curveType: castCurveType(config.curveType), 93 | startingPrice: new BN(config.startingPrice.round().toString()), 94 | delta: new BN(config.delta.round().toString()), 95 | mmCompoundFees: config.mmCompoundFees, 96 | mmFeeBps: config.mmFeeBps, 97 | }); 98 | 99 | // --------------------------------------- rest 100 | 101 | export enum OrderType { 102 | Standard = 0, 103 | Sniping = 1, 104 | } 105 | 106 | export type Frozen = { 107 | amount: BN; 108 | time: BN; 109 | }; 110 | 111 | export type PoolStatsAnchor = { 112 | takerSellCount: number; 113 | takerBuyCount: number; 114 | accumulatedMmProfit: BN; 115 | }; 116 | 117 | export type PoolAnchor = { 118 | version: number; 119 | bump: number[]; 120 | solEscrowBump: number[]; 121 | createdUnixSeconds: BN; 122 | config: PoolConfigAnchor; 123 | tswap: PublicKey; 124 | owner: PublicKey; 125 | whitelist: PublicKey; 126 | solEscrow: PublicKey; 127 | takerSellCount: number; 128 | takerBuyCount: number; 129 | nftsHeld: number; 130 | //v0.3 131 | nftAuthority: PublicKey; 132 | stats: PoolStatsAnchor; 133 | //v1.0 134 | margin: PublicKey | null; 135 | isCosigned: boolean; 136 | orderType: OrderType; 137 | frozen: Frozen | null; 138 | lastTransactedSeconds: BN; 139 | maxTakerSellCount: number; 140 | }; 141 | 142 | export type SolEscrowAnchor = {}; 143 | export type TSwapAnchor = { 144 | version: number; 145 | bump: number[]; 146 | config: TSwapConfigAnchor; 147 | owner: PublicKey; 148 | feeVault: PublicKey; 149 | cosigner: PublicKey; 150 | }; 151 | 152 | export type NftDepositReceiptAnchor = { 153 | bump: number; 154 | nftAuthority: PublicKey; 155 | nftMint: PublicKey; 156 | nftEscrow: PublicKey; 157 | }; 158 | 159 | // --------------------------------------- state accounts 160 | 161 | export type NftAuthorityAnchor = { 162 | randomSeed: number[]; 163 | bump: number[]; 164 | pool: PublicKey; 165 | }; 166 | 167 | export type MarginAccountAnchor = { 168 | owner: PublicKey; 169 | name: number[]; 170 | nr: number; 171 | bump: number[]; 172 | poolsAttached: number; 173 | }; 174 | 175 | export type SingleListingAnchor = { 176 | owner: PublicKey; 177 | nftMint: PublicKey; 178 | price: BN; 179 | bump: number[]; 180 | }; 181 | 182 | // ----------- together 183 | 184 | export type TensorSwapPdaAnchor = 185 | | PoolAnchor 186 | | SolEscrowAnchor 187 | | TSwapAnchor 188 | | NftDepositReceiptAnchor 189 | | NftAuthorityAnchor 190 | | MarginAccountAnchor 191 | | SingleListingAnchor; 192 | 193 | export type TaggedTensorSwapPdaAnchor = 194 | | { 195 | name: "pool"; 196 | account: PoolAnchor; 197 | } 198 | | { 199 | name: "solEscrow"; 200 | account: SolEscrowAnchor; 201 | } 202 | | { 203 | name: "tSwap"; 204 | account: TSwapAnchor; 205 | } 206 | | { 207 | name: "nftDepositReceipt"; 208 | account: NftDepositReceiptAnchor; 209 | } 210 | | { 211 | name: "nftAuthority"; 212 | account: NftAuthorityAnchor; 213 | } 214 | | { 215 | name: "marginAccount"; 216 | account: MarginAccountAnchor; 217 | } 218 | | { 219 | name: "singleListing"; 220 | account: SingleListingAnchor; 221 | }; 222 | -------------------------------------------------------------------------------- /src/token2022.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getExtraAccountMetaAddress, 3 | getExtraAccountMetas, 4 | resolveExtraAccountMeta, 5 | } from "@solana/spl-token"; 6 | import { 7 | AccountMeta, 8 | Commitment, 9 | Connection, 10 | PublicKey, 11 | TransactionInstruction, 12 | } from "@solana/web3.js"; 13 | 14 | export async function getTransferHookExtraAccounts( 15 | connection: Connection, 16 | mint: PublicKey, 17 | instruction: TransactionInstruction, 18 | tansferHookProgramId: PublicKey, 19 | commitment?: Commitment 20 | ) { 21 | const address = getExtraAccountMetaAddress(mint, tansferHookProgramId); 22 | const account = await connection.getAccountInfo(address, commitment); 23 | const extraMetas: AccountMeta[] = [ 24 | { 25 | pubkey: tansferHookProgramId, 26 | isSigner: false, 27 | isWritable: false, 28 | }, 29 | ]; 30 | 31 | // if we don't have the account, no extra accounts to add 32 | if (account == null) { 33 | return extraMetas; 34 | } 35 | 36 | for (const extraAccountMeta of getExtraAccountMetas(account)) { 37 | extraMetas.push( 38 | await resolveExtraAccountMeta( 39 | connection, 40 | extraAccountMeta, 41 | instruction.keys, 42 | instruction.data, 43 | instruction.programId 44 | ) 45 | ); 46 | } 47 | 48 | // add the extra account meta 49 | extraMetas.push({ 50 | pubkey: address, 51 | isSigner: false, 52 | isWritable: false, 53 | }); 54 | 55 | return extraMetas; 56 | } 57 | 58 | // ------------ WNS 59 | 60 | export const WNS_DISTRIBUTION_PROGRAM_ID = new PublicKey( 61 | "diste3nXmK7ddDTs1zb6uday6j4etCa9RChD8fJ1xay" 62 | ); 63 | export const WNS_PROGRAM_ID = new PublicKey( 64 | "wns1gDLt8fgLcGhWi5MqAqgXpwEP1JftKE9eZnXS1HM" 65 | ); 66 | 67 | export const getApprovalAccount = (mint: PublicKey) => { 68 | const [approvalAccount] = PublicKey.findProgramAddressSync( 69 | [Buffer.from("approve-account"), mint.toBuffer()], 70 | WNS_PROGRAM_ID 71 | ); 72 | 73 | return approvalAccount; 74 | }; 75 | 76 | export const getDistributionAccount = ( 77 | collection: PublicKey, 78 | paymentMint: PublicKey = PublicKey.default 79 | ) => { 80 | const [distributionAccount] = PublicKey.findProgramAddressSync( 81 | [collection.toBuffer(), paymentMint.toBuffer()], 82 | WNS_DISTRIBUTION_PROGRAM_ID 83 | ); 84 | 85 | return distributionAccount; 86 | }; 87 | 88 | export const getApproveAccountLen = () => { 89 | // discriminator + slot 90 | return 8 + 8; 91 | }; 92 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import Big from "big.js"; 3 | 4 | //the side of the trade that the trader is taking 5 | export enum TakerSide { 6 | Buy = "Buy", 7 | Sell = "Sell", 8 | } 9 | 10 | export enum PoolType { 11 | NFT = "NFT", 12 | Token = "Token", 13 | Trade = "Trade", 14 | } 15 | 16 | export enum CurveType { 17 | Linear = "Linear", 18 | Exponential = "Exponential", 19 | } 20 | 21 | export type PoolConfig = { 22 | poolType: PoolType; 23 | curveType: CurveType; 24 | // TODO: THESE SHOULD BE BNs, OTHERWISE WE'LL RUN INTO ALL SORTS OF RUN TIME ERRORS. 25 | startingPrice: Big; 26 | delta: Big; 27 | mmCompoundFees: boolean; 28 | mmFeeBps: number | null; // null for non-trade pools 29 | }; 30 | 31 | // Parsed account from a raw tx. 32 | export type ParsedAccount = { 33 | // See "getAccountByName" for name suffixes (these are the capitalized, space-separate names). 34 | name?: string | undefined; 35 | pubkey: PublicKey; 36 | isSigner: boolean; 37 | isWritable: boolean; 38 | }; 39 | 40 | export type InstructionDisplay = { 41 | args: { 42 | name: string; 43 | type: string; 44 | data: string; 45 | }[]; 46 | accounts: { 47 | name?: string; 48 | pubkey: PublicKey; 49 | isSigner: boolean; 50 | isWritable: boolean; 51 | }[]; 52 | }; 53 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src/**/*"], 3 | "compilerOptions": { 4 | "skipLibCheck": true, 5 | "lib": ["esnext", "dom"], 6 | "strict": true, 7 | 8 | "sourceMap": true, 9 | "declaration": true, 10 | "declarationMap": true, 11 | "allowSyntheticDefaultImports": true, 12 | "experimentalDecorators": true, 13 | "emitDecoratorMetadata": true, 14 | "noImplicitAny": false, 15 | "esModuleInterop": true, 16 | "resolveJsonModule": true, 17 | "composite": true, 18 | "baseUrl": ".", 19 | "typeRoots": ["node_modules/@types"] 20 | }, 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "target": "ES2022", 6 | "outDir": "dist/cjs/", 7 | "rootDir": "./src" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "moduleResolution": "node", 5 | "module": "ES2022", 6 | "target": "ES2022", 7 | "outDir": "dist/esm/", 8 | "rootDir": "./src" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "target": "ES2022", 6 | "noEmit": true, 7 | "types": ["mocha"] 8 | }, 9 | "include": ["src/**/*.ts", "tests/**/*.ts", "tests/**/*.json"] 10 | } 11 | --------------------------------------------------------------------------------