├── .github └── workflows │ └── release.yml ├── .gitignore ├── LICENSE ├── README.md ├── babel.config.js ├── jest.config.ts ├── package-lock.json ├── package.json ├── src ├── api │ ├── callback-client.ts │ ├── candy-machine-client.ts │ ├── collection-client.ts │ ├── compressed-nft-client.ts │ ├── index.ts │ ├── marketplace-client.ts │ ├── mp-bidding-client.ts │ ├── mp-listing-client.ts │ ├── nft-client.ts │ ├── rpc-client.ts │ ├── semi-custodial-wallet-client.ts │ ├── storage-client.ts │ ├── token-client.ts │ ├── transaction-client.ts │ ├── txn-relayer-client.ts │ └── wallet-client.ts ├── index.ts ├── types │ ├── Callback.ts │ ├── CandyMachine.ts │ ├── CompressedNft.ts │ ├── DAS.ts │ ├── Enums.ts │ ├── Error.ts │ ├── Marketplace.ts │ ├── MpBidding.ts │ ├── MpListing.ts │ ├── Network.ts │ ├── Nft.ts │ ├── SemiCustodialWallet.ts │ ├── Shyft.ts │ ├── Storage.ts │ ├── Token.ts │ ├── Transaction.ts │ ├── Wallet.ts │ └── index.ts └── utils │ ├── case-converter.ts │ ├── index.ts │ ├── rest-api-call.ts │ ├── rpc-call.ts │ ├── shyft-config.ts │ ├── shyft.ts │ ├── signer.ts │ └── utility.ts ├── tests ├── callback-client.spec.ts ├── candy-machine-client.spec.ts ├── index.spec.ts ├── marketplace-client.spec.ts ├── nft-client.spec.ts ├── rpc-client.spec.ts ├── semi-wallet-client.spec.ts ├── storage-client.spec.ts ├── token-client.spec.ts ├── transaction-client.spec.ts ├── txn-relayer-client.spec.ts └── wallet-client.spec.ts ├── tsconfig.cjs.json ├── tsconfig.esm.json └── tsconfig.json /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | release: 9 | name: 🚀 Release 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: 📚 Checkout 13 | uses: actions/checkout@v3 14 | # Setup .npmrc file to publish to npm 15 | - name: Setup Node.js 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: "lts/*" 19 | registry-url: "https://registry.npmjs.org" 20 | scope: "@shyft-to" 21 | - name: Install dependencies 22 | run: npm ci 23 | - name: 🛠 Build 24 | run: npm run build 25 | - name: 🚀 Publish 26 | run: npm publish --access public 27 | env: 28 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} 29 | 30 | - name: 📖 Create the docs directory locally in CI 31 | run: npm run prepare-doc 32 | - name: Deploy docs to Github pages 🚀 33 | uses: JamesIves/github-pages-deploy-action@4.1.4 34 | with: 35 | branch: gh-pages 36 | folder: docs 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | docs 6 | .env 7 | assets -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Shyft 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Shyft JS SDK 2 | 3 | | [ ![MIT License ](https://img.shields.io/npm/l/@shyft-to/js?registry_uri=https%3A%2F%2Fregistry.npmjs.com&style=plastic) ](https://github.com/Shyft-to/js/blob/main/LICENSE) | [ ![Typescript ](https://img.shields.io/npm/types/@shyft-to/js?style=plastic) ](https://www.npmjs.com/package/@shyft-to/js) | [ ![npm version ](https://img.shields.io/npm/v/@shyft-to/js?style=plastic) ](https://www.npmjs.com/package/@shyft-to/js) | [ ![Weekly Downloads ](https://img.shields.io/npm/dw/@shyft-to/js?style=plastic) ](https://www.npmjs.com/package/@shyft-to/js) | [ ![Typedoc ](https://img.shields.io/static/v1?label=Typedoc&message=0.24&color=blueviolet&style=plastic) ](https://shyft-to.github.io/js) | 4 | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | 5 | 6 | [Shyft](https://shyft.to) SDK is the mising piece of arsenal we needed in our Web3 development journey. It is the [ultimate Web3 development platform](https://shyft.to) wrapped inside an intuitive SDK to make development 10x easier and more efficient. Explore what super powers Shyft offers you [Docs](https://docs.shyft.to). 7 | 8 | ## Installation 9 | 10 | Install with npm 11 | 12 | ```bash 13 | npm install @shyft-to/js 14 | ``` 15 | 16 | ## Usage/Examples 17 | 18 | ### Imports 19 | 20 | ```typescript 21 | import { ShyftSdk, Network } from '@shyft-to/js'; 22 | ``` 23 | 24 | or 25 | 26 | ```javascript 27 | const { ShyftSdk, Network } = require('@shyft-to/js'); 28 | ``` 29 | 30 | The Shyft SDK currently supports the following clients: 31 | 32 | - `wallet`: Wallet APIs 33 | - `nft`: Shyft NFT APIs 34 | - `token`: Fungible tokens info 35 | - `candyMachine`: Candy Machine APIs 36 | - `marketplace`: Marketplace APIs 37 | - `transaction`: Transation APIs 38 | - `txnRelayer`: Transaction Relayer, allows you to seamlessly enable gas-less transactions for your users 39 | - `storage`: Storage APIs such as uploading asset or metadata and get IPFS uri 40 | - `semiCustodialWallet`: A simple in-app crypto wallet to securely and quickly onboard non-native crypto users to web3 dApps 41 | - `callback`: Get real time updates on addresses for your users 42 | - `rpc`: [Get access to DAS API (currently only works with `mainnet-beta` cluster) 🆕](#rpc) 43 | 44 | ### Shyft Wallet APIs 45 | 46 | The Wallet API in the SDK standardizes response types to reduce developer friction, but note this results in some differences compared to the Shyft REST endpoints: 47 | 48 | - `getBalance()`: Get wallet balance by providing address 49 | - `sendSol()`: Transfer SOL from one wallet to another 50 | - `getTokenBalance()`: Get the balance of a particular token in a wallet 51 | - `getAllTokenBalance()`: Gets the balance of all the tokens in your wallet 52 | - `getPortfolio()`: Gets all the token deatils (fungible and non-fungible) from a wallet 53 | - `getDomains()`: Gets all the .sol domain addresses associated with a wallet 54 | - `resolveDomainByAddress()`: Resolves the given name account to the associated .sol domain address 55 | - `collections()`: For all the NFTs in a wallet, this method returns a list of collections and NFTs under those collections 56 | - `transactionHistory()`: Get the transaction history of your wallet 57 | - `transaction()`: Get particular transaction details from the transaction signature 58 | - `parsedTransactionHistory()`: Get the transaction history of your wallet in a nutshell 59 | 60 | ### Fetch Wallet Balance 61 | 62 | ```typescript 63 | const shyft = new ShyftSdk({ apiKey: 'YOUR_API_KEY', network: Network.Devnet }); 64 | (async () => { 65 | const balance = await shyft.wallet.getBalance({ wallet: 'WALLET_ADDRESS' }); 66 | console.log(balance); 67 | })(); 68 | ``` 69 | 70 | ### Shyft NFT APIs 71 | 72 | The SDK currently supports the following NFT API endpoints under the shyft.nft namespace: 73 | 74 | - `getNftByMint()`: Get NFT on-chain and off-chain data. 75 | - `getNftsByMintAddresses()`: Get multiple NFTs on-chain and off-chain data. 76 | - `getNftByOwner()`: Get All NFTs held by a wallet address. 77 | - `getNftsByOwnerV2()`: Paginated version of `getNftByOwner`, returns the list of NFTs in a wallet. A maximum of 50 NFTs are returned in a single API request. 78 | - `getOwners()`: Returns NFT Owners for the provided NFT mint address list. 79 | - `createFromMetadata()`: Create an NFT from an already uploaded metadata URI. The on-chain metadata of the NFT is fetched from the off-chain metadata present at the given URI. 80 | > The metadata_uri should open a JSON document complying with Metaplex Non-Fungible Token Standard. If the JSON doesn't follow the Metaplex standard then the API returns an error. 81 | - `burn()`: Burn a particular NFT. 82 | - `burnMany()`: Burn as many NFTs from a wallet as you want. This API endpoint returns one or multiple encoded transaction strings, which have to be signed by the NFT owner's wallet and submitted to the blockchain for successful burns. 83 | - `transfer()`: Transfer an already minted NFT from one wallet to another. 84 | > Optionally, you can transfer update authority to the new owner as well. 85 | - `transferMultiple()`: Transfer multiple NFTs from one wallet to another. It returns an encoded transaction which you can sign using the [tansaction signer](#how-to-sign-transaction-using-the-sdk). 86 | - `createV2()`: Creating an NFT, and is just 1 simple API call, which internally does all the heavy lifting for you. 87 | - `updateV2()`: This call allows an external wallet to pay the gas fee for updating NFT on behalf of the NFT update authority. 88 | 89 | * `collection`: A sub-namespace to get NFTs and other interesting insights over NFT collections. 90 | 91 | - `getNfts()`: Get on-chain metadata for NFTs in a collection. This API supports pagination support, with a default page size of 10 and maximum 50 allowed. 92 | > This method supports pagination and only works with mainnet-beta network. 93 | 94 | * `compressed`: A sub-namespace to create, read, transfer and burn compressed NFTs. 95 | - `createMerkleTree()`: Creates a merkle tree. 96 | - `mint()`: Allows you to mint cNFTs. 97 | - `transfer()`: Transfer an already minted cNFT from one wallet to another. 98 | - `transferMany()`: Transfer multiple cNFTs from one wallet to another. 99 | - `burn()`: Burn a particular cNFT. 100 | - `burnMany()`: Bulk burn wallet cNFTs. 101 | - `update()`: Update metdata of a particular cNFT. 102 | - `read()`: Returns on-chain and off-chain cNFT data. 103 | - `readAll()`: Returns on-chain and off-chain data of all cNFTs in the wallet. 104 | - `readAllV2()`: A paginated version of Read All Compressed NFTs API, returns the list of cNFTs in a wallet. A maximum of 50 NFTs are returned in a single API request. 105 | - `readSelected()`: Returns on-chain and off-chain data of selected compressed NFTs. 106 | 107 | ### Fetch an NFT 108 | 109 | ```typescript 110 | const shyft = new ShyftSdk({ apiKey: 'YOUR_API_KEY', network: Network.Devnet }); 111 | (async () => { 112 | const nft = await shyft.nft.getNftByMint({ mint: 'NFT_MINT' }); 113 | console.log(nft); 114 | })(); 115 | ``` 116 | 117 | ### Mint a compressed NFT ✨ 118 | 119 | ```typescript 120 | const mintResponse = await shyft.nft.compressed.mint({ 121 | creatorWallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 122 | merkleTree: 'DwoJS9LVDVL55Qa2TwvGG8MqNB5He4JtbnrHQ7JCrkcP', 123 | metadataUri: 124 | 'https://nftstorage.link/ipfs/bafkreigjxlfjhnpync5qmgv73yi4lqz4e65axwgpgqumvbopgvdrkwjpcm', 125 | collectionAddress: 'DgXdP7xA31HEviRKw6pk9Xj342dEWy8HFn1yjcsXZ9M9', 126 | receiver: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 127 | }); 128 | console.log(mintResponse); 129 | ``` 130 | 131 | ### Shyft Token APIs 132 | 133 | The SDK currently supports the following Token API endpoints under the shyft.token namespace: 134 | 135 | - `getInfo()`: This method returns you the information about an already launched Token. 136 | - `getOwners()`: Returns all owners hold the token, sorted by the amount they are holding (high to low). 137 | > This method supports pagination and only works with mainnet-beta network. 138 | - `create()`: Create your own fungible tokens. 139 | - `mint()`: This API lets you mint and create new units of your Fungible Token. This will increase the total supply of the token. In order to mint a token, you need to create it first. 140 | - `burn()`: This API lets you burn or decrease the supply of your Fungible Tokens. 141 | - `transfer()`: Transfer already minted tokens from one wallet to another wallet address. This does not change the total supply of the token. 142 | - `airdrop()`: Airdrop any SPL-20 token to the accounts of your choice, from 1 source account. 143 | 144 | ### Fetch info of a Fungible Token 145 | 146 | ```typescript 147 | const shyft = new ShyftSdk({ apiKey: 'YOUR_API_KEY', network: Network.Devnet }); 148 | (async () => { 149 | const token = await shyft.token.getInfo({ 150 | network: Network.Mainnet, 151 | tokenAddress: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', 152 | }); 153 | console.log(token); 154 | })(); 155 | ``` 156 | 157 | ### Shyft CandyMachine APIs 158 | 159 | The SDK currently supports the following Candy Machine API endpoints under the shyft.candyMachine namespace: 160 | 161 | - `readMints()`: Get All NFT addresses minted using a particular candy machine by providing a Candy machine address. 162 | - `readNfts()`: Get All NFTs minted using a particular candy machine by providing a Candy machine address. Returns on-chain and off-chain NFT data. This is a paginated API. 163 | - `create()`: Create Candy Machine. 164 | - `insert()`: Insert Items in Candy Machine. 165 | - `mint()`: Mint NFTs from Candy Machine. 166 | - `monitor()`: All mints from candy machine are watched and updated real time. 167 | - `unmonitor()`: Stop monitoring candy machine. 168 | 169 | ### Get All NFT addresses of a CM 170 | 171 | ```typescript 172 | const shyft = new ShyftSdk({ apiKey: 'YOUR_API_KEY', network: Network.Devnet }); 173 | (async () => { 174 | const mints = await shyftcandyMachine.readMints({ 175 | address: 'H2oYLkXdkX38eQ6VTqs26KAWAvEpYEiCtLt4knEUJxpu', 176 | version: CandyMachineProgram.V2, 177 | }); 178 | console.log(mints); 179 | })(); 180 | ``` 181 | 182 | ### Shyft Marketplace APIs 183 | 184 | Now SDK supports all marketplace APIs. Possible to perform each operation on marketplace from SDK. 185 | 186 | Marketplace namespace: 187 | 188 | - `create()`: Create your own on-chain NFT marketplace 189 | - `update()`: Update an already created on-chain marketplace. 190 | - `find()`: Find information about your previous or current Solana marketplaces. This API fetches a marketplace's information from the blockchain. 191 | - `treasuryBalance()`: Check the fund balance in the marketplace treasury account. 192 | - `stats()`: Fetches detailed statistics of a marketplace. 193 | - `withdrawFee()`: Withdraw the transaction fees that got deposited in the marketplace treasury as a result of the sales transactions that happened in your marketplace. The withdrawn amount will go into the marketplace's fee recipient account. 194 | 195 | * `listing`: A sub-namespace to list, unlist, buy, check active listings and many more. 196 | 197 | - `active()`: Get details of all the active listings in a marketplace. 198 | - `detail()`: Get details of a particular listing in a marketplace. 199 | - `bySeller()`: Get all the listings created by a particular seller (wallet address) in a marketplace. 200 | - `activeSellers()`: Fetches a list of all the sellers (wallet addresses) who presently have active listings in the marketplace. 201 | - `list()`: List an NFT for sale in the marketplace. 202 | - `unlist()`: Unlist an already listed NFT. This operation will make the NFT unavailable for sale, and nobody would be able to buy this NFT until it is listed back again. 203 | - `buy()`: Buy the listed NFT from the marketplace 204 | 205 | * `bidding`: A sub-namespace to bid, cancel bid, accept bid, check active bids and many more. 206 | - `active()`: Get details of all the active bids in a marketplace. 207 | - `bid()`: Bid on an NFT for sale in the marketplace. 208 | - `cancelBid()`: Cancel the existing bid. 209 | - `acceptBid()`: NFT owner can accept the bid, the owner gets the bid amount and the bidder receives the NFT. 210 | 211 | ### Create a marketplace 212 | 213 | ```typescript 214 | const { encoded_transaction } = await shyft.marketplace.create({ 215 | creatorWallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 216 | }); 217 | console.log(encoded_transaction); 218 | ``` 219 | 220 | ### List an NFT on a marketplace 221 | 222 | ```typescript 223 | const { encoded_transaction } = await shyft.marketplace.listing.list({ 224 | marketplaceAddress: 'dKtXyGgDGCyXiWtj9mbXUXk7ww996Uyc46CVt3ukJwV', 225 | nftAddress: '7Ros6azxoYakj3agxZetDwTWySftQeYXRXAKYWgXTWvw', 226 | price: 50, 227 | sellerWallet: '8hDQqsj9o2LwMk2FPBs7Rz5jPuzqKpRvkeeo6hMJm5Cv', 228 | isGasLess: true, 229 | }); 230 | console.log(encoded_transaction); 231 | ``` 232 | 233 | ### Fetch active listings on a marketplace 234 | 235 | ```typescript 236 | const activeListings = await shyft.marketplace.listing.active({ 237 | network: Network.Mainnet, 238 | marketplaceAddress: 'AxrRwpzk4T6BsWhttPwVCmfeEMbfbasv1QxVc5JhUfvB', 239 | sortBy: 'price', 240 | sortOrder: 'desc', 241 | page: 1, 242 | size: 2, 243 | }); 244 | console.log(activeListings); 245 | ``` 246 | 247 | ### Transaction APIs 248 | 249 | Get parsed transaction and history easily. 250 | 251 | Transaction namespace: 252 | 253 | - `raw()`: Get raw transaction for a given txn signature. 254 | - `parsed()`: Get parsed transaction details for a given txn signature. Read more on [parsed transaction structure](https://docs.shyft.to/start-hacking/transactions/parsed-transaction-structure) 255 | - `history()`: Fetches a list of parsed transactions for an on-chain account. The response returns the transactions with the latest transactions first. The response can have results of a maximum of 10 transactions in 1 single call. 256 | - `parseSelected()`: Allows to retrieve specific transactions from a dataset by providing their unique transaction signatures. 257 | - `send()`: Lets you submit your signed transaction into the Solana blockchain, via Shyft's dedicated RPC node, which reduces the risk of transaction drop to near 0. 258 | - `sendMany()`: Lets you send bulk transactions to Solana blockchain, via Shyft's dedicated RPC node. 259 | 260 | ### Fetch transaction history 261 | 262 | ```typescript 263 | const transactions = await shyft.transaction.history({ 264 | network: Network.Devnet, 265 | account: 'Apeng15Pm8EjpAcaAXpNUxZjS2jMmGqikfs281Fz9hNj', 266 | enableRaw: true, 267 | }); 268 | console.dir(transactions, { depth: null }); 269 | ``` 270 | 271 | ### Transaction Relayer APIs 272 | 273 | It first creates a custodial wallet which gets mapped to your Shyft API key. On creation, it returns the wallet address associated with you SHYFT API key. You have to use this wallet address as,fee_payer while constructing your transactions. Then, you can send the transactions that need to be signed on the relayer’s sign endpoint. Relayer will retrieve the credentials associated with your API key, sign the transaction and send it to the blockchain. 274 | 275 | Txn Relayer namespace: 276 | 277 | - `getOrCreate()`: Get or create a new transaction relayer. 278 | - `sign()`: Sign and send a transaction using the relayer. Takes `encoded_transaction` and network as input request parameters. 279 | - `signMany()`: Sign and send multiple transactions using the relayer. Takes `encoded_transactions` and network as input request parameters. 280 | 281 | ### Storage APIs 282 | 283 | Your gateway to decentralized storage 284 | 285 | Storage namespace: 286 | 287 | - `uploadAsset()`: Upload anything to decentralized storage. Call the API with file: anything as form-data. 288 | > Note: For IPFS, you will get the same id on uploading same content. 289 | - `createMetadata()`: This lets you create an NFT metadata JSON file on decentralized storage (IPFS). 290 | 291 | ### Create NFT Metadata 292 | 293 | ```typescript 294 | const { uri } = await shyft.storage.createMetadata({ 295 | creator: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 296 | image: 297 | 'https://nftstorage.link/ipfs/bafkreiajrjd7xozubfr7qk6xdktlo3k66jg6jkeamgjugd2p3w5w2pifve', 298 | name: 'Nirvana', 299 | symbol: 'NVN', 300 | description: 'This is a test NFT', 301 | attributes: [ 302 | { trait_type: 'anger', value: 0 }, 303 | { trait_type: 'calmness', value: 100 }, 304 | ], 305 | sellerFeeBasisPoints: 500, 306 | }); 307 | console.log(uri); 308 | ``` 309 | 310 | ### Semi Custodial Wallet APIs 311 | 312 | A type of wallet where Shyft holds half of your private keys while the other half is with the client or the end user. 313 | 314 | semicustodial namespace: 315 | 316 | - `create()`: We create a standard Solana wallet using keypair.generate(). The private key is then encrypted with the provided password and random encryption parameters. In order to decrypt the key, we need the same password and the same encryption parameters. 317 | > Shyft never ever stores or logs your password at any time. This can be confirmed with our open source code. 318 | - `getKeypair()`: Get keypair of created semi custodial wallet. 319 | - `changePassword()`: Change password of semi custodial wallet. 320 | 321 | ### Callbacks 322 | 323 | Get real time updates on addresses for your users. Follow [docs](https://docs.shyft.to/start-hacking/callbacks) to know about callbacks. 324 | 325 | callback namespace: 326 | 327 | - `register()`: Register a callback. 328 | - `update()`: Update a callback. 329 | - `remove()`: Remove a callback. 330 | - `list()`: Returns a list of all the callbacks registered for a user. 331 | - `addAddresses()`: Add Addresses in Callback. 332 | - `removeAddresses()`: Remove Addresses from callback. 333 | - `pause()`: Pause a callback. 334 | - `resume()`: Resume a callback. 335 | 336 | ### RPC 337 | 338 | Access the new Solana DAS (Digital Asset Standard) API. 339 | 340 | rpc namespace: 341 | 342 | - `getAsset()`: Get an asset by its ID. 343 | - `getAssetProof()`: Get a merkle proof for a compressed asset by its ID. 344 | - `getAssetsByGroup()`: Get a list of assets by a group key and value. An example presented [here](#fetch-assets-of-a-collection). 345 | - `getAssetsByOwner()`: Get a list of assets owned by an address. 346 | - `getAssetsByCreator()`: Get a list of assets created by an address. 347 | - `getAssetsByAuthority()`: Get a list of assets with a specific authority. 348 | - `searchAssets()`: Search for assets by a variety of parameters. 349 | 350 | #### Fetch assets of a collection 351 | 352 | ```typescript 353 | import { ShyftSdk, Network } from '@shyft-to/js'; 354 | 355 | const shyft = new ShyftSdk({ 356 | apiKey: 'YOUR_API_KEY', 357 | network: Network.Mainnet, 358 | }); 359 | 360 | (async () => { 361 | const response = await shyft.rpc.getAssetsByGroup({ 362 | groupKey: 'collection', 363 | groupValue: 'BxWpbnau1LfemNAoXuAe9Pbft59yz2egTxaMWtncGRfN', 364 | sortBy: { sortBy: 'created', sortDirection: 'asc' }, 365 | page: 1, 366 | limit: 1000, 367 | }); 368 | const assets = response.items; 369 | console.log(assets); 370 | })(); 371 | ``` 372 | 373 | ## How to sign transaction using the SDK? 374 | 375 | ### Transaction signer usage (with private keys) 376 | 377 | ```typescript 378 | import { signAndSendTransactionWithPrivateKeys, Network } from '@shyft-to/js'; 379 | 380 | const network = Network.Devnet; 381 | const privateKeys = ['PRIVATE_KEY_ONE', 'PRIVATE_KEY_TWO']; 382 | // Get using Shyft API 383 | const encodedTransaction = 384 | '5eG1aSjNoPmScw84G1d7f9n2fgmWabtQEgRjTUXvpTrRH1qduEMwUvUFYiS8px22JNedkWFTUWj9PrRyq1MyessunKC8Mjyq3hH5WZkM15D3gsooH8hsFegyYRBmccLBTEnPph6fExEySkJwsfH6oGC62VmDDCpWyPHZLYv52e4qtUb1TBE6SgXE6FX3TFqrX5HApSkb9ZaCSz21FyyEbXtrmMxBQE1CR7BTyadWL1Vy9SLfo9tnsVpHHDHthFRr'( 385 | (async () => { 386 | try { 387 | const txnSignature = await signAndSendTransactionWithPrivateKeys( 388 | network, 389 | encodedTransaction, 390 | privateKeys 391 | ); 392 | console.log(txnSignature); 393 | } catch (error) { 394 | throw new Error(error); 395 | } 396 | } 397 | )(); 398 | ``` 399 | 400 | ### Transaction signer usage (without private key) 401 | 402 | ```tsx 403 | import { useConnection, useWallet } from '@solana/wallet-adapter-react'; 404 | import { signAndSendTransaction, Network, ShyftWallet } from '@shyft-to/js'; 405 | 406 | const { connection } = useConnection(); 407 | const wallet = useWallet(); 408 | 409 | // Get using Shyft API 410 | const encodedTransaction = 411 | '5eG1aSjNoPmScw84G1d7f9n2fgmWabtQEgRjTUXvpTrRH1qduEMwUvUFYiS8px22JNedkWFTUWj9PrRyq1MyessunKC8Mjyq3hH5WZkM15D3gsooH8hsFegyYRBmccLBTEnPph6fExEySkJwsfH6oGC62VmDDCpWyPHZLYv52e4qtUb1TBE6SgXE6FX3TFqrX5HApSkb9ZaCSz21FyyEbXtrmMxBQE1CR7BTyadWL1Vy9SLfo9tnsVpHHDHthFRr'; 412 | (async () => { 413 | try { 414 | const txnSignature = await signAndSendTransaction( 415 | connection, 416 | encodedTransaction, 417 | wallet 418 | ); 419 | console.log(txnSignature); 420 | } catch (error) { 421 | throw new Error(error); 422 | } 423 | })(); 424 | ``` 425 | 426 | ### Frontend usage 427 | 428 | Use any starter from [here](https://github.com/solana-labs/wallet-adapter/tree/master/packages/starter) and implement the above code snippet or follow [Shyft sample project](https://github.com/Shyft-to/community-projects/tree/main/shyft-signer-react). 429 | 430 | ## Roadmap 431 | 432 | - Integrate NFT create 433 | - Marketplace client 434 | - More features 435 | 436 | ## About Us 437 | 438 | We're here to provide a bridge between web2 & web3. 439 | 440 | ## 🚀 Powered By 441 | 442 | [Shyft](https://shyft.to) 443 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [['@babel/preset-env', {targets: {node: 'current'}}], '@babel/preset-typescript'], 3 | }; 4 | 5 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { pathsToModuleNameMapper } from 'ts-jest'; 2 | import { compilerOptions } from './tsconfig.json'; 3 | import type { JestConfigWithTsJest } from 'ts-jest'; 4 | 5 | const jestConfig: JestConfigWithTsJest = { 6 | roots: ['tests'], 7 | modulePaths: [compilerOptions.baseUrl], 8 | moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths), 9 | }; 10 | 11 | export default jestConfig; 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.40", 3 | "license": "MIT", 4 | "main": "dist/cjs/index.js", 5 | "module": "dist/esm/index.js", 6 | "files": [ 7 | "dist" 8 | ], 9 | "engines": { 10 | "node": ">=10" 11 | }, 12 | "scripts": { 13 | "test": "jest", 14 | "clean": "rimraf dist", 15 | "build:esm": "tsc --project tsconfig.esm.json && tsc-alias -p tsconfig.esm.json", 16 | "build:cjs": "tsc --project tsconfig.cjs.json && tsc-alias -p tsconfig.cjs.json", 17 | "build": "npm run clean && npm run build:esm && npm run build:cjs", 18 | "prepare-doc": "rimraf docs && npx typedoc" 19 | }, 20 | "prettier": { 21 | "printWidth": 80, 22 | "semi": true, 23 | "singleQuote": true, 24 | "trailingComma": "es5" 25 | }, 26 | "name": "@shyft-to/js", 27 | "author": "shyft-to", 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/Shyft-to/js.git" 31 | }, 32 | "bugs": { 33 | "url": "https://github.com/Shyft-to/js/issues" 34 | }, 35 | "homepage": "https://shyft.to", 36 | "devDependencies": { 37 | "@babel/core": "^7.20.12", 38 | "@babel/preset-env": "^7.20.2", 39 | "@babel/preset-typescript": "^7.18.6", 40 | "@types/bs58": "^4.0.1", 41 | "@types/jest": "^28.1.6", 42 | "babel-jest": "^29.4.1", 43 | "dotenv": "^16.0.3", 44 | "jest": "^29.4.1", 45 | "rimraf": "^4.1.2", 46 | "ts-jest": "^29.0.5", 47 | "ts-node": "^10.9.1", 48 | "tsc-alias": "^1.8.2", 49 | "tsconfig-paths": "^4.1.2", 50 | "typedoc": "^0.23.24", 51 | "typescript": "^4.8.4" 52 | }, 53 | "dependencies": { 54 | "@solana/web3.js": "^1.74.0", 55 | "axios": "^1.2.6", 56 | "bs58": "^5.0.0", 57 | "form-data": "^4.0.0", 58 | "lodash": "^4.17.21" 59 | }, 60 | "private": false, 61 | "publishConfig": { 62 | "access": "public" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/api/callback-client.ts: -------------------------------------------------------------------------------- 1 | import { ShyftConfig } from '@/utils'; 2 | import { restApiCall } from '@/utils'; 3 | import { 4 | Network, 5 | TxnAction, 6 | CallBack, 7 | CallbackType, 8 | CallbackEncoding, 9 | } from '@/types'; 10 | 11 | export class CallbackClient { 12 | constructor(private readonly config: ShyftConfig) {} 13 | 14 | async register(input: { 15 | network?: Network; 16 | addresses: string[]; 17 | callbackUrl: string; 18 | events?: TxnAction[]; 19 | enableRaw?: boolean; 20 | enableEvents?: boolean; 21 | type?: CallbackType; 22 | encoding?: CallbackEncoding; 23 | }): Promise> { 24 | try { 25 | if (!this.isValidUrl(input.callbackUrl)) { 26 | throw new Error(`not a valid URL: ${input.callbackUrl}`); 27 | } 28 | const reqBody = { 29 | network: input?.network ?? this.config.network, 30 | addresses: input.addresses, 31 | callback_url: input.callbackUrl, 32 | }; 33 | if (input?.events) { 34 | reqBody['events'] = input.events; 35 | } 36 | if (input?.enableRaw) { 37 | reqBody['enable_raw'] = input.enableRaw; 38 | } 39 | if (input?.enableEvents) { 40 | reqBody['enable_events'] = input.enableEvents; 41 | } 42 | if (input?.type) { 43 | reqBody['type'] = input.type; 44 | } 45 | if (input?.encoding) { 46 | reqBody['encoding'] = input.encoding; 47 | } 48 | const response = await restApiCall(this.config.apiKey, { 49 | method: 'post', 50 | url: 'callback/create', 51 | data: reqBody, 52 | }); 53 | const callback = response.result as Omit< 54 | CallBack, 55 | 'active' | 'created_at' | 'updated_at' 56 | >; 57 | return callback; 58 | } catch (error) { 59 | throw error; 60 | } 61 | } 62 | 63 | async remove(input: { id: string }): Promise { 64 | try { 65 | const reqBody = { 66 | id: input.id, 67 | }; 68 | const response = await restApiCall(this.config.apiKey, { 69 | method: 'delete', 70 | url: 'callback/remove', 71 | data: reqBody, 72 | }); 73 | const isRemoved = response.success as boolean; 74 | return isRemoved; 75 | } catch (error) { 76 | throw error; 77 | } 78 | } 79 | 80 | async update(input: { 81 | network?: Network; 82 | id: string; 83 | addresses: string[]; 84 | callbackUrl: string; 85 | events?: TxnAction[]; 86 | enableRaw?: boolean; 87 | enableEvents?: boolean; 88 | type?: CallbackType; 89 | encoding?: CallbackEncoding; 90 | }): Promise< 91 | Omit< 92 | CallBack, 93 | | 'callback_url' 94 | | 'enable_raw' 95 | | 'enable_events' 96 | | 'type' 97 | | 'active' 98 | | 'encoding' 99 | | 'created_at' 100 | | 'updated_at' 101 | > 102 | > { 103 | try { 104 | if (!this.isValidUrl(input.callbackUrl)) { 105 | throw new Error(`not a valid URL: ${input.callbackUrl}`); 106 | } 107 | const reqBody = { 108 | network: input?.network ?? this.config.network, 109 | id: input.id, 110 | addresses: input.addresses, 111 | callback_url: input.callbackUrl, 112 | }; 113 | if (input?.events) { 114 | reqBody['events'] = input.events; 115 | } 116 | if (input?.enableRaw) { 117 | reqBody['enable_raw'] = input.enableRaw; 118 | } 119 | if (input?.enableEvents) { 120 | reqBody['enable_events'] = input.enableEvents; 121 | } 122 | if (input?.type) { 123 | reqBody['type'] = input.type; 124 | } 125 | if (input?.encoding) { 126 | reqBody['encoding'] = input.encoding; 127 | } 128 | const response = await restApiCall(this.config.apiKey, { 129 | method: 'post', 130 | url: 'callback/update', 131 | data: reqBody, 132 | }); 133 | const callback = response.result as Omit< 134 | CallBack, 135 | | 'callback_url' 136 | | 'enable_raw' 137 | | 'enable_events' 138 | | 'type' 139 | | 'active' 140 | | 'encoding' 141 | | 'created_at' 142 | | 'updated_at' 143 | >; 144 | return callback; 145 | } catch (error) { 146 | throw error; 147 | } 148 | } 149 | 150 | async list(): Promise { 151 | try { 152 | const response = await restApiCall(this.config.apiKey, { 153 | method: 'get', 154 | url: 'callback/list', 155 | }); 156 | const callbacks = response.result.map((callback: any) => { 157 | return { 158 | id: callback?._id, 159 | network: callback?.network, 160 | addresses: callback?.addresses, 161 | callback_url: callback?.callback_url, 162 | events: callback?.events, 163 | enable_raw: callback?.enable_raw, 164 | enable_events: callback?.enable_events, 165 | type: callback?.type, 166 | encoding: callback?.encoding, 167 | created_at: new Date(callback.created_at), 168 | updated_at: new Date(callback.updated_at), 169 | } as CallBack; 170 | }); 171 | return callbacks; 172 | } catch (error) { 173 | throw error; 174 | } 175 | } 176 | 177 | async addAddresses(input: { 178 | id: string; 179 | addresses: string[]; 180 | }): Promise> { 181 | try { 182 | const reqBody = { 183 | id: input.id, 184 | addresses: input.addresses, 185 | }; 186 | const response = await restApiCall(this.config.apiKey, { 187 | method: 'post', 188 | url: 'callback/add-addresses', 189 | data: reqBody, 190 | }); 191 | const callback = response.result as Omit< 192 | CallBack, 193 | 'active' | 'callback_url' | 'enable_raw' 194 | >; 195 | return callback; 196 | } catch (error) { 197 | throw error; 198 | } 199 | } 200 | 201 | async removeAddresses(input: { 202 | id: string; 203 | addresses: string[]; 204 | }): Promise> { 205 | try { 206 | const reqBody = { 207 | id: input.id, 208 | addresses: input.addresses, 209 | }; 210 | const response = await restApiCall(this.config.apiKey, { 211 | method: 'post', 212 | url: 'callback/remove-addresses', 213 | data: reqBody, 214 | }); 215 | const callback = response.result as Omit< 216 | CallBack, 217 | 'active' | 'callback_url' | 'enable_raw' 218 | >; 219 | return callback; 220 | } catch (error) { 221 | throw error; 222 | } 223 | } 224 | 225 | async pause(input: { id: string }): Promise { 226 | try { 227 | const reqBody = { 228 | id: input.id, 229 | }; 230 | const response = await restApiCall(this.config.apiKey, { 231 | method: 'post', 232 | url: 'callback/pause', 233 | data: reqBody, 234 | }); 235 | const isPaused = response.success as boolean; 236 | return isPaused; 237 | } catch (error) { 238 | throw error; 239 | } 240 | } 241 | 242 | async resume(input: { id: string }): Promise { 243 | try { 244 | const reqBody = { 245 | id: input.id, 246 | }; 247 | const response = await restApiCall(this.config.apiKey, { 248 | method: 'post', 249 | url: 'callback/resume', 250 | data: reqBody, 251 | }); 252 | const isResumed = response.success as boolean; 253 | return isResumed; 254 | } catch (error) { 255 | throw error; 256 | } 257 | } 258 | 259 | private isValidUrl(url: string) { 260 | try { 261 | new URL(url); 262 | return true; 263 | } catch (err) { 264 | return false; 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/api/candy-machine-client.ts: -------------------------------------------------------------------------------- 1 | import { ShyftConfig, CaseConverter } from '@/utils'; 2 | import { restApiCall } from '@/utils'; 3 | import { 4 | BulkItemSettings, 5 | CandyMachineGroup, 6 | CandyMachineGuard, 7 | CandyMachineItem, 8 | CandyMachineProgram, 9 | CreateCandyMachineResp, 10 | Creator, 11 | InsertCandyMachineResp, 12 | ItemSettings, 13 | MintCandyMachineResp, 14 | Network, 15 | PaginatedNftResponse, 16 | } from '@/types'; 17 | 18 | export class CandyMachineClient { 19 | private caseConverter: CaseConverter; 20 | constructor(private readonly config: ShyftConfig) { 21 | this.caseConverter = new CaseConverter(); 22 | } 23 | 24 | async readMints(input: { 25 | network?: Network; 26 | address: string; 27 | version?: CandyMachineProgram; 28 | }): Promise { 29 | try { 30 | const params = { 31 | network: input?.network ?? this.config.network, 32 | address: input.address, 33 | }; 34 | if (input.version) { 35 | params['version'] = input.version; 36 | } 37 | const data = await restApiCall(this.config.apiKey, { 38 | method: 'get', 39 | url: 'candy_machine/nft_addresses', 40 | params, 41 | }); 42 | const mints = data.result as string[]; 43 | return mints; 44 | } catch (error) { 45 | throw error; 46 | } 47 | } 48 | 49 | async readNfts(input: { 50 | network?: Network; 51 | address: string; 52 | version?: CandyMachineProgram; 53 | page?: number; 54 | size?: number; 55 | }): Promise { 56 | try { 57 | const params = { 58 | network: input?.network ?? this.config.network, 59 | address: input.address, 60 | }; 61 | if (input.page) { 62 | params['page'] = input.page; 63 | } 64 | if (input.size) { 65 | params['size'] = input.size; 66 | } 67 | if (input.version) { 68 | params['version'] = input.version; 69 | } 70 | const data = await restApiCall(this.config.apiKey, { 71 | method: 'get', 72 | url: 'candy_machine/nft_addresses', 73 | params, 74 | }); 75 | const response = data.result as PaginatedNftResponse; 76 | return response; 77 | } catch (error) { 78 | throw error; 79 | } 80 | } 81 | 82 | async create(input: { 83 | network?: Network; 84 | wallet: string; 85 | feePayer?: string; 86 | symbol: string; 87 | maxSupply?: number; 88 | royalty?: number; 89 | itemsAvailable: number; 90 | amount?: number; 91 | collection: string; 92 | bulkItemSettings?: BulkItemSettings; 93 | itemSettings?: ItemSettings; 94 | creators?: Omit[]; 95 | guards?: CandyMachineGuard; 96 | groups?: CandyMachineGroup[]; 97 | }): Promise { 98 | try { 99 | const reqBody = { 100 | network: input?.network ?? this.config.network, 101 | wallet: input.wallet, 102 | symbol: input.symbol, 103 | items_available: input.itemsAvailable, 104 | collection: input.collection, 105 | }; 106 | if (input?.feePayer) { 107 | reqBody['fee_payer'] = input.feePayer; 108 | } 109 | if (input?.maxSupply) { 110 | reqBody['max_supply'] = input.maxSupply; 111 | } 112 | if (input?.royalty) { 113 | reqBody['royalty'] = input.royalty; 114 | } 115 | if (input?.amount) { 116 | reqBody['amount'] = input.amount; 117 | } 118 | if (input?.bulkItemSettings) { 119 | reqBody['bulk_item_settings'] = input.bulkItemSettings; 120 | } 121 | if (input?.itemSettings) { 122 | reqBody['item_settings'] = this.caseConverter.convertToSnakeCaseObject( 123 | input.itemSettings 124 | ); 125 | } 126 | if (input?.creators) { 127 | reqBody['creators'] = input.creators; 128 | } 129 | if (input?.creators) { 130 | reqBody['creators'] = input.creators; 131 | } 132 | if (input?.guards) { 133 | reqBody['guards'] = input.guards; 134 | } 135 | if (input?.groups) { 136 | reqBody['groups'] = input.groups; 137 | } 138 | 139 | const response = await restApiCall(this.config.apiKey, { 140 | method: 'post', 141 | url: 'candy_machine/create', 142 | data: reqBody, 143 | }); 144 | 145 | const candyMachineAndTx = response.result as CreateCandyMachineResp; 146 | return candyMachineAndTx; 147 | } catch (error) { 148 | throw error; 149 | } 150 | } 151 | 152 | async insert(input: { 153 | network?: Network; 154 | wallet: string; 155 | candyMachine: string; 156 | index?: number; 157 | items: CandyMachineItem[]; 158 | }): Promise { 159 | try { 160 | const reqBody = { 161 | network: input?.network ?? this.config.network, 162 | wallet: input.wallet, 163 | candy_machine: input.candyMachine, 164 | items: input.items, 165 | }; 166 | if (input?.index) { 167 | reqBody['index'] = input.index; 168 | } 169 | if (input.items.length === 0) { 170 | throw new Error('Atleast insert one item!'); 171 | } 172 | 173 | const response = await restApiCall(this.config.apiKey, { 174 | method: 'post', 175 | url: 'candy_machine/insert', 176 | data: reqBody, 177 | }); 178 | 179 | const candyMachineAndTx = response.result as InsertCandyMachineResp; 180 | return candyMachineAndTx; 181 | } catch (error) { 182 | throw error; 183 | } 184 | } 185 | 186 | async mint(input: { 187 | network?: Network; 188 | wallet: string; 189 | candyMachine: string; 190 | authority: string; 191 | mintGroup?: string; 192 | feePayer?: string; 193 | guardSettings?: Partial; 194 | }): Promise { 195 | try { 196 | const reqBody = { 197 | network: input?.network ?? this.config.network, 198 | wallet: input.wallet, 199 | candy_machine: input.candyMachine, 200 | authority: input.authority, 201 | }; 202 | if (input?.mintGroup) { 203 | reqBody['mint_group'] = input.mintGroup; 204 | } 205 | if (input?.feePayer) { 206 | reqBody['fee_payer'] = input.feePayer; 207 | } 208 | if (input?.guardSettings) { 209 | reqBody['guard_settings'] = input.guardSettings; 210 | } 211 | 212 | const response = await restApiCall(this.config.apiKey, { 213 | method: 'post', 214 | url: 'candy_machine/mint', 215 | data: reqBody, 216 | }); 217 | 218 | const mintAndTx = response.result as MintCandyMachineResp; 219 | return mintAndTx; 220 | } catch (error) { 221 | throw error; 222 | } 223 | } 224 | 225 | async monitor(input: { 226 | network?: Network; 227 | candyMachine: string; 228 | }): Promise { 229 | try { 230 | const reqBody = { 231 | network: input?.network ?? this.config.network, 232 | address: input.candyMachine, 233 | }; 234 | 235 | const response = await restApiCall(this.config.apiKey, { 236 | method: 'post', 237 | url: 'candy_machine/monitor', 238 | data: reqBody, 239 | }); 240 | 241 | const isMonitored = response.success as boolean; 242 | return isMonitored; 243 | } catch (error) { 244 | throw error; 245 | } 246 | } 247 | 248 | async unmonitor(input: { 249 | network?: Network; 250 | candyMachine: string; 251 | }): Promise { 252 | try { 253 | const reqBody = { 254 | network: input?.network ?? this.config.network, 255 | address: input.candyMachine, 256 | }; 257 | 258 | const response = await restApiCall(this.config.apiKey, { 259 | method: 'delete', 260 | url: 'candy_machine/unmonitor', 261 | data: reqBody, 262 | }); 263 | 264 | const isUnmonitored = response.success as boolean; 265 | return isUnmonitored; 266 | } catch (error) { 267 | throw error; 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /src/api/collection-client.ts: -------------------------------------------------------------------------------- 1 | import { isNumber } from 'lodash'; 2 | import { ShyftConfig } from '@/utils'; 3 | import { restApiCall } from '@/utils'; 4 | import { Network, CollectionNfts } from '@/types'; 5 | 6 | export class CollectionClient { 7 | constructor(private readonly config: ShyftConfig) {} 8 | 9 | async getNfts(input: { 10 | network?: Network; 11 | collectionAddress: string; 12 | page?: number; 13 | size?: number; 14 | }): Promise { 15 | try { 16 | const params = { 17 | network: input?.network ?? this.config.network, 18 | collection_address: input.collectionAddress, 19 | }; 20 | if (params.network !== Network.Mainnet) { 21 | throw new Error('This operation only available on mainnet-beta'); 22 | } 23 | if (isNumber(input?.page)) { 24 | if (input.page < 1) { 25 | throw new Error('should not be less than 1: size'); 26 | } 27 | params['page'] = input.page; 28 | } 29 | if (isNumber(input?.size)) { 30 | if (input.size > 50 || input.size < 1) { 31 | throw new Error('allowed between 1 to 50: size'); 32 | } 33 | params['size'] = input.size; 34 | } 35 | const data = await restApiCall(this.config.apiKey, { 36 | method: 'get', 37 | url: 'collections/get_nfts', 38 | params, 39 | }); 40 | const collectionNfts = data.result as CollectionNfts; 41 | return collectionNfts; 42 | } catch (error) { 43 | throw error; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/api/compressed-nft-client.ts: -------------------------------------------------------------------------------- 1 | import { ShyftConfig, restApiCall, CaseConverter } from '@/utils'; 2 | import { 3 | CNftBurnManyResp, 4 | CNftBurnResponse, 5 | CNftMintResponse, 6 | CNftTransferManyResp, 7 | CNftTransferResponse, 8 | CNftUpdateResponse, 9 | CreateMerkleTreeResponse, 10 | Network, 11 | Nft, 12 | PaginatedNfts, 13 | ServiceCharge, 14 | ValidDepthSizePair, 15 | } from '@/types'; 16 | import { isNumber } from 'lodash'; 17 | 18 | export class CompressedNftClient { 19 | private caseConverter: CaseConverter; 20 | constructor(private readonly config: ShyftConfig) { 21 | this.caseConverter = new CaseConverter(); 22 | } 23 | 24 | async createMerkleTree(input: { 25 | network?: Network; 26 | walletAddress: string; 27 | maxDepthSizePair: ValidDepthSizePair; 28 | canopyDepth: number; 29 | feePayer?: string; 30 | }): Promise { 31 | try { 32 | if (!CompressedNftClient.isValidDepthSizePair(input.maxDepthSizePair)) { 33 | throw new Error('Invalid depth size pair'); 34 | } 35 | const reqBody = { 36 | network: input.network ?? this.config.network, 37 | wallet_address: input.walletAddress, 38 | max_depth_size_pair: this.caseConverter.convertToSnakeCaseObject( 39 | input.maxDepthSizePair 40 | ), 41 | canopy_depth: input.canopyDepth, 42 | }; 43 | if (input?.feePayer) { 44 | reqBody['fee_payer'] = input.feePayer; 45 | } 46 | const data = await restApiCall(this.config.apiKey, { 47 | method: 'post', 48 | url: 'nft/compressed/create_tree', 49 | data: reqBody, 50 | }); 51 | const response = data.result as CreateMerkleTreeResponse; 52 | return response; 53 | } catch (error) { 54 | throw error; 55 | } 56 | } 57 | 58 | async mint(input: { 59 | network?: Network; 60 | creatorWallet: string; 61 | merkleTree: string; 62 | metadataUri: string; 63 | isDelegateAuthority?: boolean; 64 | collectionAddress?: string; 65 | maxSupply?: number; 66 | primarySaleHappend?: boolean; 67 | isMutable?: boolean; 68 | receiver?: string; 69 | feePayer?: string; 70 | priorityFee?: number; 71 | serviceCharge?: ServiceCharge; 72 | }): Promise { 73 | try { 74 | const reqBody = { 75 | network: input.network ?? this.config.network, 76 | creator_wallet: input.creatorWallet, 77 | merkle_tree: input.merkleTree, 78 | metadata_uri: input.metadataUri, 79 | }; 80 | if (input?.isDelegateAuthority) { 81 | reqBody['is_delegate_authority'] = input.isDelegateAuthority; 82 | } 83 | if (input?.collectionAddress) { 84 | reqBody['collection_address'] = input.collectionAddress; 85 | } 86 | if (input?.maxSupply) { 87 | reqBody['max_supply'] = input.maxSupply; 88 | } 89 | if (input?.primarySaleHappend) { 90 | reqBody['primary_sale_happend'] = input.primarySaleHappend; 91 | } 92 | if (input?.isMutable) { 93 | reqBody['is_mutable'] = input.isMutable; 94 | } 95 | if (input?.receiver) { 96 | reqBody['receiver'] = input.receiver; 97 | } 98 | if (input?.feePayer) { 99 | reqBody['fee_payer'] = input.feePayer; 100 | } 101 | if (input?.priorityFee) { 102 | reqBody['priority_fee'] = input.priorityFee; 103 | } 104 | if (input?.serviceCharge) { 105 | reqBody['service_charge'] = input.serviceCharge; 106 | } 107 | const data = await restApiCall(this.config.apiKey, { 108 | method: 'post', 109 | url: 'nft/compressed/mint', 110 | data: reqBody, 111 | }); 112 | const response = data.result as CNftMintResponse; 113 | return response; 114 | } catch (error) { 115 | throw error; 116 | } 117 | } 118 | 119 | async transfer(input: { 120 | network?: Network; 121 | mint: string; 122 | fromAddress: string; 123 | toAddress: string; 124 | feePayer?: string; 125 | priorityFee?: number; 126 | }): Promise { 127 | try { 128 | const reqBody = { 129 | network: input.network ?? this.config.network, 130 | nft_address: input.mint, 131 | sender: input.fromAddress, 132 | receiver: input.toAddress, 133 | }; 134 | if (input.feePayer) { 135 | reqBody['fee_payer'] = input.feePayer; 136 | } 137 | if (input.priorityFee) { 138 | reqBody['priority_fee'] = input.priorityFee; 139 | } 140 | const data = await restApiCall(this.config.apiKey, { 141 | method: 'post', 142 | url: 'nft/compressed/transfer', 143 | data: reqBody, 144 | }); 145 | const response = data.result as CNftTransferResponse; 146 | return response; 147 | } catch (error) { 148 | throw error; 149 | } 150 | } 151 | 152 | async transferMany(input: { 153 | network?: Network; 154 | mints: string[]; 155 | fromAddress: string; 156 | toAddress: string; 157 | feePayer?: string; 158 | priorityFee?: number; 159 | }): Promise { 160 | try { 161 | const reqBody = { 162 | network: input.network ?? this.config.network, 163 | nft_addresses: input.mints, 164 | from_address: input.fromAddress, 165 | to_address: input.toAddress, 166 | }; 167 | if (input.feePayer) { 168 | reqBody['fee_payer'] = input.feePayer; 169 | } 170 | if (input.priorityFee) { 171 | reqBody['priority_fee'] = input.priorityFee; 172 | } 173 | const data = await restApiCall(this.config.apiKey, { 174 | method: 'post', 175 | url: 'nft/compressed/transfer_many', 176 | data: reqBody, 177 | }); 178 | const response = data.result as CNftTransferManyResp; 179 | return response; 180 | } catch (error) { 181 | throw error; 182 | } 183 | } 184 | 185 | async burn(input: { 186 | network?: Network; 187 | walletAddress: string; 188 | mint: string; 189 | priorityFee?: number; 190 | }): Promise { 191 | try { 192 | const reqBody = { 193 | network: input.network ?? this.config.network, 194 | wallet_address: input.walletAddress, 195 | nft_address: input.mint, 196 | }; 197 | if (input.priorityFee) { 198 | reqBody['priority_fee'] = input.priorityFee; 199 | } 200 | const data = await restApiCall(this.config.apiKey, { 201 | method: 'delete', 202 | url: 'nft/compressed/burn', 203 | data: reqBody, 204 | }); 205 | const response = data.result as CNftBurnResponse; 206 | return response; 207 | } catch (error) { 208 | throw error; 209 | } 210 | } 211 | 212 | async burnMany(input: { 213 | network?: Network; 214 | mints: string[]; 215 | walletAddress: string; 216 | priorityFee?: number; 217 | }): Promise { 218 | try { 219 | const reqBody = { 220 | network: input.network ?? this.config.network, 221 | nft_addresses: input.mints, 222 | wallet_address: input.walletAddress, 223 | }; 224 | if (input.priorityFee) { 225 | reqBody['priority_fee'] = input.priorityFee; 226 | } 227 | const data = await restApiCall(this.config.apiKey, { 228 | method: 'delete', 229 | url: 'nft/compressed/burn_many', 230 | data: reqBody, 231 | }); 232 | const response = data.result as CNftBurnManyResp; 233 | return response; 234 | } catch (error) { 235 | throw error; 236 | } 237 | } 238 | 239 | async update(input: { 240 | network?: Network; 241 | authority: string; 242 | mint: string; 243 | name?: string; 244 | symbol?: string; 245 | metadataUri?: string; 246 | royalty?: number; 247 | primarySaleHappend?: boolean; 248 | isMutable?: boolean; 249 | feePayer?: string; 250 | priorityFee?: number; 251 | }): Promise { 252 | try { 253 | const reqBody = { 254 | network: input.network ?? this.config.network, 255 | authority: input.authority, 256 | nft_address: input.mint, 257 | }; 258 | if (input.name) { 259 | reqBody['name'] = input.name; 260 | } 261 | if (input.symbol) { 262 | reqBody['symbol'] = input.symbol; 263 | } 264 | if (input.metadataUri) { 265 | reqBody['metadata_uri'] = input.metadataUri; 266 | } 267 | if (input.royalty) { 268 | if (input.royalty > 100 || input.royalty < 0) { 269 | throw new Error('"royalty" must be between 0 and 100'); 270 | } 271 | reqBody['royalty'] = input.royalty; 272 | } 273 | if (input.primarySaleHappend !== undefined) { 274 | reqBody['primary_sale_happened'] = input.primarySaleHappend; 275 | } 276 | if (input.isMutable !== undefined) { 277 | reqBody['is_mutable'] = input.isMutable; 278 | } 279 | if (input.feePayer) { 280 | reqBody['fee_payer'] = input.feePayer; 281 | } 282 | if (input.priorityFee) { 283 | reqBody['priority_fee'] = input.priorityFee; 284 | } 285 | const data = await restApiCall(this.config.apiKey, { 286 | method: 'post', 287 | url: 'nft/compressed/update', 288 | data: reqBody, 289 | }); 290 | const response = data.result as CNftUpdateResponse; 291 | return response; 292 | } catch (error) { 293 | throw error; 294 | } 295 | } 296 | 297 | async read(input: { network?: Network; mint: string }): Promise { 298 | try { 299 | const params = { 300 | network: input.network ?? this.config.network, 301 | nft_address: input.mint, 302 | }; 303 | const data = await restApiCall(this.config.apiKey, { 304 | method: 'get', 305 | url: 'nft/compressed/read', 306 | params, 307 | }); 308 | const response = data.result as Nft; 309 | return response; 310 | } catch (error) { 311 | throw error; 312 | } 313 | } 314 | 315 | async readAll(input: { 316 | network?: Network; 317 | walletAddress: string; 318 | collection?: string; 319 | refresh?: boolean; 320 | }): Promise { 321 | try { 322 | const params = { 323 | network: input.network ?? this.config.network, 324 | wallet_address: input.walletAddress, 325 | }; 326 | if (input.collection) { 327 | params['collection'] = input.collection; 328 | } 329 | if (input.refresh) { 330 | params['refresh'] = input.refresh; 331 | } 332 | const data = await restApiCall(this.config.apiKey, { 333 | method: 'get', 334 | url: 'nft/compressed/read_all', 335 | params, 336 | }); 337 | const response = data.result.nfts as Nft[]; 338 | return response; 339 | } catch (error) { 340 | throw error; 341 | } 342 | } 343 | 344 | async readAllV2(input: { 345 | network?: Network; 346 | walletAddress: string; 347 | collection?: string; 348 | refresh?: boolean; 349 | page?: number; 350 | size?: number; 351 | }): Promise { 352 | try { 353 | const params = { 354 | network: input.network ?? this.config.network, 355 | wallet_address: input.walletAddress, 356 | }; 357 | if (input.collection) { 358 | params['collection'] = input.collection; 359 | } 360 | if (input.refresh) { 361 | params['refresh'] = input.refresh; 362 | } 363 | if (isNumber(input?.page)) { 364 | if (input.page < 1) { 365 | throw new Error('should not be less than 1: size'); 366 | } 367 | params['page'] = input.page; 368 | } 369 | if (isNumber(input?.size)) { 370 | if (input.size > 50 || input.size < 1) { 371 | throw new Error('allowed between 1 to 50: size'); 372 | } 373 | params['size'] = input.size; 374 | } 375 | const data = await restApiCall( 376 | this.config.apiKey, 377 | { 378 | method: 'get', 379 | url: 'nft/compressed/read_all', 380 | params, 381 | }, 382 | 'v2' 383 | ); 384 | const response = data.result as PaginatedNfts; 385 | return response; 386 | } catch (error) { 387 | throw error; 388 | } 389 | } 390 | 391 | async readSelected(input: { 392 | network?: Network; 393 | mints: string[]; 394 | refresh?: boolean; 395 | }): Promise { 396 | try { 397 | if (input.mints.length > 10 || input.mints.length < 1) { 398 | throw new Error('allowed between 1 to 10: mints'); 399 | } 400 | const reqBody = { 401 | network: input.network ?? this.config.network, 402 | nft_addresses: input.mints, 403 | }; 404 | if (input.refresh) { 405 | reqBody['refresh'] = input.refresh; 406 | } 407 | const data = await restApiCall(this.config.apiKey, { 408 | method: 'post', 409 | url: 'nft/compressed/read_selected', 410 | data: reqBody, 411 | }); 412 | const response = data.result as Nft[]; 413 | return response; 414 | } catch (error) { 415 | throw error; 416 | } 417 | } 418 | 419 | static isValidDepthSizePair(obj: any): obj is ValidDepthSizePair { 420 | if (!obj || typeof obj !== 'object') { 421 | return false; 422 | } 423 | const validPairs = [ 424 | { maxDepth: 3, maxBufferSize: 8 }, 425 | { maxDepth: 5, maxBufferSize: 8 }, 426 | { maxDepth: 14, maxBufferSize: 64 }, 427 | { maxDepth: 14, maxBufferSize: 256 }, 428 | { maxDepth: 14, maxBufferSize: 1024 }, 429 | { maxDepth: 14, maxBufferSize: 2048 }, 430 | { maxDepth: 15, maxBufferSize: 64 }, 431 | { maxDepth: 16, maxBufferSize: 64 }, 432 | { maxDepth: 17, maxBufferSize: 64 }, 433 | { maxDepth: 18, maxBufferSize: 64 }, 434 | { maxDepth: 19, maxBufferSize: 64 }, 435 | { maxDepth: 20, maxBufferSize: 64 }, 436 | { maxDepth: 20, maxBufferSize: 256 }, 437 | { maxDepth: 20, maxBufferSize: 1024 }, 438 | { maxDepth: 20, maxBufferSize: 2048 }, 439 | { maxDepth: 24, maxBufferSize: 64 }, 440 | { maxDepth: 24, maxBufferSize: 256 }, 441 | { maxDepth: 24, maxBufferSize: 512 }, 442 | { maxDepth: 24, maxBufferSize: 1024 }, 443 | { maxDepth: 24, maxBufferSize: 2048 }, 444 | { maxDepth: 26, maxBufferSize: 512 }, 445 | { maxDepth: 26, maxBufferSize: 1024 }, 446 | { maxDepth: 26, maxBufferSize: 2048 }, 447 | { maxDepth: 30, maxBufferSize: 512 }, 448 | { maxDepth: 30, maxBufferSize: 1024 }, 449 | { maxDepth: 30, maxBufferSize: 2048 }, 450 | ]; 451 | return validPairs.some((pair) => { 452 | return ( 453 | pair.maxDepth === obj.maxDepth && 454 | pair.maxBufferSize === obj.maxBufferSize 455 | ); 456 | }); 457 | } 458 | } 459 | -------------------------------------------------------------------------------- /src/api/index.ts: -------------------------------------------------------------------------------- 1 | export * from './nft-client'; 2 | export * from './wallet-client'; 3 | export * from './token-client'; 4 | export * from './candy-machine-client'; 5 | export * from './marketplace-client'; 6 | export * from './transaction-client'; 7 | export * from './txn-relayer-client'; 8 | export * from './storage-client'; 9 | export * from './callback-client'; 10 | export * from './rpc-client'; 11 | -------------------------------------------------------------------------------- /src/api/marketplace-client.ts: -------------------------------------------------------------------------------- 1 | import { ShyftConfig } from '@/utils'; 2 | import { restApiCall } from '@/utils'; 3 | import { 4 | Marketplace, 5 | MarketplaceStats, 6 | Network, 7 | TreasuryBalance, 8 | WithdrawFeeTxn, 9 | } from '@/types'; 10 | import { MpListingClient } from './mp-listing-client'; 11 | import { MpBiddingClient } from './mp-bidding-client'; 12 | 13 | const WRAPPED_SOL_ADDRESS = 'So11111111111111111111111111111111111111112'; 14 | 15 | export class MarketplaceClient { 16 | readonly listing: MpListingClient; 17 | readonly bidding: MpBiddingClient; 18 | constructor(private readonly config: ShyftConfig) { 19 | this.listing = new MpListingClient(this.config); 20 | this.bidding = new MpBiddingClient(this.config); 21 | } 22 | 23 | async create(input: { 24 | network?: Network; 25 | creatorWallet: string; 26 | authorityAddress?: string; 27 | currencyAddress?: string; 28 | feePayer?: string; 29 | feeRecipient?: string; 30 | transactionFee?: number; 31 | }): Promise< 32 | Marketplace & { 33 | encoded_transaction: string; 34 | } 35 | > { 36 | try { 37 | if ( 38 | typeof input?.transactionFee === 'number' && 39 | (input.transactionFee > 100 || input.transactionFee < 0) 40 | ) { 41 | throw new Error( 42 | 'transactionFee should not be greater than 100 or lower than 0' 43 | ); 44 | } 45 | const reqBody = { 46 | network: input?.network ?? this.config.network, 47 | creator_wallet: input.creatorWallet, 48 | transaction_fee: input?.transactionFee ?? 0, 49 | }; 50 | if (input?.authorityAddress) { 51 | reqBody['authority_address'] = input.authorityAddress; 52 | } 53 | if (input?.currencyAddress) { 54 | reqBody['currency_address'] = input.currencyAddress; 55 | } 56 | if (input?.feePayer) { 57 | reqBody['fee_payer'] = input.feePayer; 58 | } 59 | if (input?.feeRecipient) { 60 | reqBody['fee_recipient'] = input.feeRecipient; 61 | } 62 | 63 | const data = await restApiCall(this.config.apiKey, { 64 | method: 'post', 65 | url: 'marketplace/create', 66 | data: reqBody, 67 | }); 68 | const response = data.result as Marketplace & { 69 | encoded_transaction: string; 70 | }; 71 | return response; 72 | } catch (error) { 73 | throw error; 74 | } 75 | } 76 | 77 | async update(input: { 78 | network?: Network; 79 | authorityWallet: string; 80 | marketplaceAddress: string; 81 | newAuthorityAddress?: string; 82 | feePayer?: string; 83 | feeRecipient?: string; 84 | transactionFee?: number; 85 | }): Promise< 86 | Marketplace & { 87 | encoded_transaction: string; 88 | } 89 | > { 90 | try { 91 | const reqBody = { 92 | network: input?.network ?? this.config.network, 93 | authority_wallet: input.authorityWallet, 94 | marketplace_address: input.marketplaceAddress, 95 | }; 96 | if (input?.newAuthorityAddress) { 97 | reqBody['new_authority_address'] = input.newAuthorityAddress; 98 | } 99 | if (input?.transactionFee) { 100 | reqBody['transaction_fee'] = input.transactionFee; 101 | } 102 | if (input?.feePayer) { 103 | reqBody['fee_payer'] = input.feePayer; 104 | } 105 | if (input?.feeRecipient) { 106 | reqBody['fee_recipient'] = input.feeRecipient; 107 | } 108 | 109 | const data = await restApiCall(this.config.apiKey, { 110 | method: 'post', 111 | url: 'marketplace/update', 112 | data: reqBody, 113 | }); 114 | const response = data.result as Marketplace & { 115 | encoded_transaction: string; 116 | }; 117 | return response; 118 | } catch (error) { 119 | throw error; 120 | } 121 | } 122 | 123 | async find(input: { 124 | network?: Network; 125 | authorityAddress: string; 126 | currencyAddress?: string; 127 | }): Promise { 128 | try { 129 | const params = { 130 | network: input?.network ?? this.config.network, 131 | authority_address: input.authorityAddress, 132 | currency_address: input?.currencyAddress ?? WRAPPED_SOL_ADDRESS, 133 | }; 134 | 135 | const data = await restApiCall(this.config.apiKey, { 136 | method: 'get', 137 | url: 'marketplace/find', 138 | params, 139 | }); 140 | const response = data.result as Marketplace; 141 | return response; 142 | } catch (error) { 143 | throw error; 144 | } 145 | } 146 | 147 | async treasuryBalance(input: { 148 | network?: Network; 149 | marketplaceAddress: string; 150 | }): Promise { 151 | try { 152 | const params = { 153 | network: input?.network ?? this.config.network, 154 | marketplace_address: input.marketplaceAddress, 155 | }; 156 | 157 | const data = await restApiCall(this.config.apiKey, { 158 | method: 'get', 159 | url: 'marketplace/treasury_balance', 160 | params, 161 | }); 162 | const treasuryBalance = data.result as TreasuryBalance; 163 | return treasuryBalance; 164 | } catch (error) { 165 | throw error; 166 | } 167 | } 168 | 169 | async stats(input: { 170 | network?: Network; 171 | marketplaceAddress: string; 172 | startDate?: Date; 173 | endDate?: Date; 174 | }): Promise { 175 | try { 176 | const params = { 177 | network: input?.network ?? this.config.network, 178 | marketplace_address: input.marketplaceAddress, 179 | }; 180 | 181 | const data = await restApiCall(this.config.apiKey, { 182 | method: 'get', 183 | url: 'marketplace/stats', 184 | params, 185 | }); 186 | const stats = data.result as MarketplaceStats; 187 | stats.start_date = new Date(stats.start_date); 188 | stats.end_date = new Date(stats.end_date); 189 | return stats; 190 | } catch (error) { 191 | throw error; 192 | } 193 | } 194 | 195 | async withdrawFee(input: { 196 | network?: Network; 197 | marketplaceAddress: string; 198 | authorityWallet: string; 199 | amount: number; 200 | }): Promise { 201 | try { 202 | const reqBody = { 203 | network: input?.network ?? this.config.network, 204 | marketplace_address: input.marketplaceAddress, 205 | authority_wallet: input.authorityWallet, 206 | amount: input.amount, 207 | }; 208 | 209 | const data = await restApiCall(this.config.apiKey, { 210 | method: 'post', 211 | url: 'marketplace/withdraw_fee', 212 | data: reqBody, 213 | }); 214 | const withdrawFeeTxn = data.result as WithdrawFeeTxn; 215 | return withdrawFeeTxn; 216 | } catch (error) { 217 | throw error; 218 | } 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/api/mp-bidding-client.ts: -------------------------------------------------------------------------------- 1 | import { ShyftConfig } from '@/utils'; 2 | import { restApiCall } from '@/utils'; 3 | import { 4 | BidsSortOptions, 5 | BidsSortOrder, 6 | Network, 7 | NftBuyResponse, 8 | ServiceCharge, 9 | ActiveBids, 10 | NftBidResponse, 11 | } from '@/types'; 12 | 13 | export class MpBiddingClient { 14 | constructor(private readonly config: ShyftConfig) {} 15 | 16 | async active(input: { 17 | network?: Network; 18 | marketplaceAddress: string; 19 | buyerAddress?: string; 20 | collectionAddress?: string; 21 | nftAddress?: string; 22 | sortBy?: BidsSortOptions; 23 | sortOrder?: BidsSortOrder; 24 | page?: number; 25 | size?: number; 26 | }): Promise { 27 | try { 28 | const params = { 29 | network: input?.network ?? this.config.network, 30 | marketplace_address: input.marketplaceAddress, 31 | }; 32 | if (input?.buyerAddress) { 33 | params['buyer_address'] = input.buyerAddress; 34 | } 35 | if (input?.collectionAddress) { 36 | params['collection_address'] = input.collectionAddress; 37 | } 38 | if (input?.nftAddress) { 39 | params['nft_address'] = input.nftAddress; 40 | } 41 | if (input?.sortBy) { 42 | params['sort_by'] = input.sortBy; 43 | } 44 | if (input?.sortOrder) { 45 | params['sort_order'] = input.sortOrder; 46 | } 47 | if (input?.page) { 48 | params['page'] = input.page; 49 | } 50 | if (input?.size) { 51 | params['size'] = input.size; 52 | } 53 | 54 | const data = await restApiCall(this.config.apiKey, { 55 | method: 'get', 56 | url: 'marketplace/active_bids', 57 | params, 58 | }); 59 | const bids = data.result as ActiveBids; 60 | bids.data.forEach((x) => { 61 | x.created_at = new Date(x.created_at); 62 | }); 63 | return bids; 64 | } catch (error) { 65 | throw error; 66 | } 67 | } 68 | 69 | async bid(input: { 70 | network?: Network; 71 | marketplaceAddress: string; 72 | nftAddress: string; 73 | price: number; 74 | buyerWallet: string; 75 | isGasLess?: boolean; 76 | serviceCharge?: ServiceCharge; 77 | }): Promise { 78 | try { 79 | const reqBody = { 80 | network: input?.network ?? this.config.network, 81 | marketplace_address: input.marketplaceAddress, 82 | nft_address: input.nftAddress, 83 | price: input.price, 84 | buyer_wallet: input.buyerWallet, 85 | }; 86 | 87 | if (input?.isGasLess) { 88 | reqBody['on_the_house'] = input.isGasLess; 89 | } 90 | 91 | if (input?.serviceCharge) { 92 | reqBody['service_charge'] = input.serviceCharge; 93 | } 94 | 95 | const data = await restApiCall(this.config.apiKey, { 96 | method: 'post', 97 | url: 'marketplace/bid', 98 | data: reqBody, 99 | }); 100 | const response = data.result as NftBidResponse; 101 | return response; 102 | } catch (error) { 103 | throw error; 104 | } 105 | } 106 | 107 | async cancelBid(input: { 108 | network?: Network; 109 | marketplaceAddress: string; 110 | bidState: string; 111 | buyerWallet: string; 112 | isGasLess?: boolean; 113 | }): Promise { 114 | try { 115 | const reqBody = { 116 | network: input?.network ?? this.config.network, 117 | marketplace_address: input.marketplaceAddress, 118 | bid_state: input.bidState, 119 | buyer_wallet: input.buyerWallet, 120 | }; 121 | 122 | if (input?.isGasLess) { 123 | reqBody['on_the_house'] = input.isGasLess; 124 | } 125 | 126 | const data = await restApiCall(this.config.apiKey, { 127 | method: 'post', 128 | url: 'marketplace/cancel_bid', 129 | data: reqBody, 130 | }); 131 | const encodedTransaction = data.result.encoded_transaction as string; 132 | return encodedTransaction; 133 | } catch (error) { 134 | throw error; 135 | } 136 | } 137 | 138 | async acceptBid(input: { 139 | network?: Network; 140 | marketplaceAddress: string; 141 | bidState: string; 142 | sellerWallet: string; 143 | }): Promise { 144 | try { 145 | const reqBody = { 146 | network: input?.network ?? this.config.network, 147 | marketplace_address: input.marketplaceAddress, 148 | bid_state: input.bidState, 149 | seller_wallet: input.sellerWallet, 150 | }; 151 | 152 | const data = await restApiCall(this.config.apiKey, { 153 | method: 'post', 154 | url: 'marketplace/accept_bid', 155 | data: reqBody, 156 | }); 157 | const response = data.result as NftBuyResponse; 158 | return response; 159 | } catch (error) { 160 | throw error; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/api/mp-listing-client.ts: -------------------------------------------------------------------------------- 1 | import { ShyftConfig } from '@/utils'; 2 | import { restApiCall } from '@/utils'; 3 | import { 4 | ActiveListings, 5 | ActiveListingSortBy, 6 | ActiveListingSortOrder, 7 | ListedNftDetail, 8 | Network, 9 | NftBuyResponse, 10 | NftListResponse, 11 | ServiceCharge, 12 | } from '@/types'; 13 | 14 | export class MpListingClient { 15 | constructor(private readonly config: ShyftConfig) {} 16 | 17 | async active(input: { 18 | network?: Network; 19 | marketplaceAddress: string; 20 | sellerAddress?: string; 21 | collectionAddress?: string; 22 | nftAddress?: string; 23 | sortBy?: ActiveListingSortBy; 24 | sortOrder?: ActiveListingSortOrder; 25 | page?: number; 26 | size?: number; 27 | }): Promise { 28 | try { 29 | const params = { 30 | network: input?.network ?? this.config.network, 31 | marketplace_address: input.marketplaceAddress, 32 | }; 33 | if (input?.sellerAddress) { 34 | params['seller_address'] = input.sellerAddress; 35 | } 36 | if (input?.collectionAddress) { 37 | params['collection_address'] = input.collectionAddress; 38 | } 39 | if (input?.nftAddress) { 40 | params['nft_address'] = input.nftAddress; 41 | } 42 | if (input?.sortBy) { 43 | params['sort_by'] = input.sortBy; 44 | } 45 | if (input?.sortOrder) { 46 | params['sort_order'] = input.sortOrder; 47 | } 48 | if (input?.page) { 49 | params['page'] = input.page; 50 | } 51 | if (input?.size) { 52 | params['size'] = input.size; 53 | } 54 | 55 | const data = await restApiCall( 56 | this.config.apiKey, 57 | { 58 | method: 'get', 59 | url: 'marketplace/active_listings', 60 | params, 61 | }, 62 | 'v2' 63 | ); 64 | const listedNfts = data.result as ActiveListings; 65 | listedNfts.data.forEach((x) => { 66 | x.created_at = new Date(x.created_at); 67 | }); 68 | return listedNfts; 69 | } catch (error) { 70 | throw error; 71 | } 72 | } 73 | 74 | async detail(input: { 75 | network?: Network; 76 | marketplaceAddress: string; 77 | listState: string; 78 | }): Promise> { 79 | try { 80 | const params = { 81 | network: input?.network ?? this.config.network, 82 | marketplace_address: input.marketplaceAddress, 83 | list_state: input.listState, 84 | }; 85 | 86 | const data = await restApiCall(this.config.apiKey, { 87 | method: 'get', 88 | url: 'marketplace/list_details', 89 | params, 90 | }); 91 | const listedNft = data.result as Omit; 92 | listedNft.created_at = new Date(listedNft.created_at); 93 | if (listedNft?.cancelled_at) { 94 | listedNft.cancelled_at = new Date(listedNft.cancelled_at); 95 | } 96 | return listedNft; 97 | } catch (error) { 98 | throw error; 99 | } 100 | } 101 | 102 | async bySeller(input: { 103 | network?: Network; 104 | marketplaceAddress: string; 105 | sellerAddress: string; 106 | }): Promise[]> { 107 | try { 108 | const params = { 109 | network: input?.network ?? this.config.network, 110 | marketplace_address: input.marketplaceAddress, 111 | seller_address: input.sellerAddress, 112 | }; 113 | 114 | const data = await restApiCall(this.config.apiKey, { 115 | method: 'get', 116 | url: 'marketplace/seller_listings', 117 | params, 118 | }); 119 | const listedNfts = data.result as Omit[]; 120 | listedNfts.forEach((x) => { 121 | x.created_at = new Date(x.created_at); 122 | if (x?.cancelled_at) { 123 | x.cancelled_at = new Date(x.cancelled_at); 124 | } 125 | }); 126 | return listedNfts; 127 | } catch (error) { 128 | throw error; 129 | } 130 | } 131 | 132 | async activeSellers(input: { 133 | network?: Network; 134 | marketplaceAddress: string; 135 | }): Promise { 136 | try { 137 | const params = { 138 | network: input?.network ?? this.config.network, 139 | marketplace_address: input.marketplaceAddress, 140 | }; 141 | 142 | const data = await restApiCall(this.config.apiKey, { 143 | method: 'get', 144 | url: 'marketplace/active_sellers', 145 | params, 146 | }); 147 | const sellers = data.result as string[]; 148 | return sellers; 149 | } catch (error) { 150 | throw error; 151 | } 152 | } 153 | 154 | async list(input: { 155 | network?: Network; 156 | marketplaceAddress: string; 157 | nftAddress: string; 158 | price: number; 159 | sellerWallet: string; 160 | isGasLess?: boolean; 161 | serviceCharge?: ServiceCharge; 162 | }): Promise { 163 | try { 164 | const reqBody = { 165 | network: input?.network ?? this.config.network, 166 | marketplace_address: input.marketplaceAddress, 167 | nft_address: input.nftAddress, 168 | price: input.price, 169 | seller_wallet: input.sellerWallet, 170 | }; 171 | 172 | if (input?.isGasLess) { 173 | reqBody['on_the_house'] = input.isGasLess; 174 | } 175 | 176 | if (input?.serviceCharge) { 177 | reqBody['service_charge'] = input.serviceCharge; 178 | } 179 | 180 | const data = await restApiCall(this.config.apiKey, { 181 | method: 'post', 182 | url: 'marketplace/list', 183 | data: reqBody, 184 | }); 185 | const response = data.result as NftListResponse; 186 | return response; 187 | } catch (error) { 188 | throw error; 189 | } 190 | } 191 | 192 | async unlist(input: { 193 | network?: Network; 194 | marketplaceAddress: string; 195 | listState: string; 196 | sellerWallet: string; 197 | }): Promise { 198 | try { 199 | const reqBody = { 200 | network: input?.network ?? this.config.network, 201 | marketplace_address: input.marketplaceAddress, 202 | list_state: input.listState, 203 | seller_wallet: input.sellerWallet, 204 | }; 205 | 206 | const data = await restApiCall(this.config.apiKey, { 207 | method: 'post', 208 | url: 'marketplace/unlist', 209 | data: reqBody, 210 | }); 211 | const encodedTransaction = data.result.encoded_transaction as string; 212 | return encodedTransaction; 213 | } catch (error) { 214 | throw error; 215 | } 216 | } 217 | 218 | async buy(input: { 219 | network?: Network; 220 | marketplaceAddress: string; 221 | nftAddress: string; 222 | price: number; 223 | sellerWallet: string; 224 | buyerWallet: string; 225 | serviceCharge?: ServiceCharge; 226 | }): Promise { 227 | try { 228 | const reqBody = { 229 | network: input?.network ?? this.config.network, 230 | marketplace_address: input.marketplaceAddress, 231 | nft_address: input.nftAddress, 232 | price: input.price, 233 | seller_address: input.sellerWallet, 234 | buyer_wallet: input.buyerWallet, 235 | }; 236 | 237 | if (input?.serviceCharge) { 238 | reqBody['service_charge'] = input.serviceCharge; 239 | } 240 | 241 | const data = await restApiCall(this.config.apiKey, { 242 | method: 'post', 243 | url: 'marketplace/buy', 244 | data: reqBody, 245 | }); 246 | const response = data.result as NftBuyResponse & { signers: string[] }; 247 | return response; 248 | } catch (error) { 249 | throw error; 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/api/nft-client.ts: -------------------------------------------------------------------------------- 1 | import FormData from 'form-data'; 2 | import { ShyftConfig, restApiCall } from '@/utils'; 3 | import { 4 | Attribute, 5 | Network, 6 | Nft, 7 | NftMintAndOwner, 8 | PaginatedNfts, 9 | ServiceCharge, 10 | } from '@/types'; 11 | import { CollectionClient } from './collection-client'; 12 | import { CompressedNftClient } from './compressed-nft-client'; 13 | import { isNumber } from 'lodash'; 14 | 15 | export class NftClient { 16 | readonly collection: CollectionClient; 17 | readonly compressed: CompressedNftClient; 18 | constructor(private readonly config: ShyftConfig) { 19 | this.collection = new CollectionClient(this.config); 20 | this.compressed = new CompressedNftClient(this.config); 21 | } 22 | 23 | async getNftByMint(input: { 24 | network?: Network; 25 | mint: string; 26 | refresh?: boolean; 27 | tokenRecord?: boolean; 28 | }): Promise { 29 | try { 30 | const params = { 31 | network: input.network ?? this.config.network, 32 | token_address: input.mint, 33 | }; 34 | if (input.refresh) { 35 | params['refresh'] = input.refresh; 36 | } 37 | if (input.tokenRecord) { 38 | params['token_record'] = input.tokenRecord; 39 | } 40 | const data = await restApiCall(this.config.apiKey, { 41 | method: 'get', 42 | url: 'nft/read', 43 | params, 44 | }); 45 | const nft = data.result as Nft; 46 | return nft; 47 | } catch (error) { 48 | throw error; 49 | } 50 | } 51 | 52 | async getNftsByMintAddresses(input: { 53 | network?: Network; 54 | mints: string[]; 55 | refresh?: boolean; 56 | tokenRecord?: boolean; 57 | }): Promise { 58 | try { 59 | if (input.mints.length === 0) { 60 | throw new Error('At least one mint address is required'); 61 | } 62 | if (input.mints.length > 10) { 63 | throw new Error('Allowed between 1 to 10: mints'); 64 | } 65 | const reqBody = { 66 | network: input.network ?? this.config.network, 67 | token_addresses: input.mints, 68 | }; 69 | if (input.refresh) { 70 | reqBody['refresh'] = input.refresh; 71 | } 72 | if (input.tokenRecord) { 73 | reqBody['token_record'] = input.tokenRecord; 74 | } 75 | const data = await restApiCall(this.config.apiKey, { 76 | method: 'post', 77 | url: 'nft/read_selected', 78 | data: reqBody, 79 | }); 80 | const nfts = data.result as Nft[]; 81 | return nfts; 82 | } catch (error) { 83 | throw error; 84 | } 85 | } 86 | 87 | async getNftByOwner(input: { 88 | network?: Network; 89 | owner: string; 90 | }): Promise { 91 | try { 92 | const params = { 93 | network: input.network ?? this.config.network, 94 | address: input.owner, 95 | }; 96 | const data = await restApiCall(this.config.apiKey, { 97 | method: 'get', 98 | url: 'nft/read_all', 99 | params, 100 | }); 101 | const nft = data.result as Nft[]; 102 | return nft; 103 | } catch (error) { 104 | throw error; 105 | } 106 | } 107 | 108 | async getNftsByOwnerV2(input: { 109 | network?: Network; 110 | owner: string; 111 | updateAuthority?: string; 112 | refresh?: boolean; 113 | page?: number; 114 | size?: number; 115 | }): Promise { 116 | try { 117 | const params = { 118 | network: input.network ?? this.config.network, 119 | address: input.owner, 120 | }; 121 | if (input.updateAuthority) { 122 | params['update_authority'] = input.updateAuthority; 123 | } 124 | if (input.refresh) { 125 | params['refresh'] = input.refresh; 126 | } 127 | if (isNumber(input?.page)) { 128 | if (input.page < 1) { 129 | throw new Error('should not be less than 1: size'); 130 | } 131 | params['page'] = input.page; 132 | } 133 | if (isNumber(input?.size)) { 134 | if (input.size > 50 || input.size < 1) { 135 | throw new Error('allowed between 1 to 50: size'); 136 | } 137 | params['size'] = input.size; 138 | } 139 | const data = await restApiCall( 140 | this.config.apiKey, 141 | { 142 | method: 'get', 143 | url: 'nft/read_all', 144 | params, 145 | }, 146 | 'v2' 147 | ); 148 | const nft = data.result as PaginatedNfts; 149 | return nft; 150 | } catch (error) { 151 | throw error; 152 | } 153 | } 154 | 155 | async getOwners(input: { 156 | network?: Network; 157 | mints: string[]; 158 | }): Promise { 159 | try { 160 | if (input.mints.length < 1 || input.mints.length > 10) { 161 | throw new Error('allowed between 1 to 10: mints'); 162 | } 163 | const reqBody = { 164 | network: input.network ?? this.config.network, 165 | nft_addresses: input.mints, 166 | }; 167 | const response = await restApiCall(this.config.apiKey, { 168 | method: 'post', 169 | url: 'nft/get_owners', 170 | data: reqBody, 171 | }); 172 | const nftMintsAndOwners = response.result as NftMintAndOwner[]; 173 | return nftMintsAndOwners; 174 | } catch (error) { 175 | throw error; 176 | } 177 | } 178 | 179 | async createFromMetadata(input: { 180 | network?: Network; 181 | metadataUri: string; 182 | maxSupply?: number; 183 | collectionAddress?: string; 184 | receiver: string; 185 | feePayer?: string; 186 | serviceCharge?: ServiceCharge; 187 | }): Promise<{ encoded_transaction: string; mint: string }> { 188 | try { 189 | const reqBody = { 190 | network: input.network ?? this.config.network, 191 | metadata_uri: input.metadataUri, 192 | receiver: input.receiver, 193 | }; 194 | if (input?.maxSupply) { 195 | reqBody['max_supply'] = input.maxSupply; 196 | } 197 | if (input?.collectionAddress) { 198 | reqBody['collection_address'] = input.collectionAddress; 199 | } 200 | if (input?.feePayer) { 201 | reqBody['fee_payer'] = input.feePayer; 202 | } 203 | if (input?.serviceCharge) { 204 | reqBody['service_charge'] = input.serviceCharge; 205 | } 206 | const data = await restApiCall(this.config.apiKey, { 207 | method: 'post', 208 | url: 'nft/create_from_metadata', 209 | data: reqBody, 210 | }); 211 | const result = data.result as { 212 | encoded_transaction: string; 213 | mint: string; 214 | }; 215 | return result; 216 | } catch (error) { 217 | throw error; 218 | } 219 | } 220 | 221 | async burn(input: { 222 | network?: Network; 223 | wallet: string; 224 | mint: string; 225 | close?: boolean; 226 | }): Promise { 227 | try { 228 | const reqBody = { 229 | network: input.network ?? this.config.network, 230 | wallet: input.wallet, 231 | token_address: input.mint, 232 | }; 233 | if (input?.close) { 234 | reqBody['close'] = input.close; 235 | } 236 | const data = await restApiCall(this.config.apiKey, { 237 | method: 'delete', 238 | url: 'nft/burn_detach', 239 | data: reqBody, 240 | }); 241 | const encodedTransaction = data.result?.encoded_transaction as string; 242 | return encodedTransaction; 243 | } catch (error) { 244 | throw error; 245 | } 246 | } 247 | 248 | async burnMany(input: { 249 | network?: Network; 250 | wallet: string; 251 | mints: string[]; 252 | close?: boolean; 253 | }): Promise { 254 | try { 255 | const reqBody = { 256 | network: input.network ?? this.config.network, 257 | wallet: input.wallet, 258 | nft_addresses: input.mints, 259 | }; 260 | if (input?.close) { 261 | reqBody['close_accounts'] = input.close; 262 | } 263 | const data = await restApiCall(this.config.apiKey, { 264 | method: 'delete', 265 | url: 'nft/burn_many', 266 | data: reqBody, 267 | }); 268 | const encodedTransactions = data.result?.encoded_transactions as string[]; 269 | return encodedTransactions; 270 | } catch (error) { 271 | throw error; 272 | } 273 | } 274 | 275 | async transfer(input: { 276 | network?: Network; 277 | mint: string; 278 | fromAddress: string; 279 | toAddress: string; 280 | transferAuthority?: boolean; 281 | }): Promise { 282 | try { 283 | const reqBody = { 284 | network: input.network ?? this.config.network, 285 | token_address: input.mint, 286 | from_address: input.fromAddress, 287 | to_address: input.toAddress, 288 | transfer_authority: input?.transferAuthority ?? false, 289 | }; 290 | const data = await restApiCall(this.config.apiKey, { 291 | method: 'post', 292 | url: 'nft/transfer_detach', 293 | data: reqBody, 294 | }); 295 | const encodedTransaction = data.result?.encoded_transaction as string; 296 | return encodedTransaction; 297 | } catch (error) { 298 | throw error; 299 | } 300 | } 301 | 302 | async transferMultiple(input: { 303 | network?: Network; 304 | mints: string[]; 305 | fromAddress: string; 306 | toAddress: string; 307 | priorityFee?: number; 308 | }): Promise { 309 | try { 310 | const reqBody = { 311 | network: input.network ?? this.config.network, 312 | token_addresses: input.mints, 313 | from_address: input.fromAddress, 314 | to_address: input.toAddress, 315 | }; 316 | if (input?.priorityFee) { 317 | reqBody['priority_fee'] = input.priorityFee; 318 | } 319 | const data = await restApiCall(this.config.apiKey, { 320 | method: 'post', 321 | url: 'nft/transfer_many', 322 | data: reqBody, 323 | }); 324 | const encodedTransactions = data.result.encoded_transactions as string[]; 325 | return encodedTransactions; 326 | } catch (error) { 327 | throw error; 328 | } 329 | } 330 | 331 | async createV2(input: { 332 | network?: Network; 333 | creatorWallet: string; 334 | name: string; 335 | symbol: string; 336 | description?: string; 337 | attributes?: Attribute[]; 338 | externalUrl?: string; 339 | maxSupply?: number; 340 | royalty?: number; 341 | collectionAddress?: string; 342 | feePayer?: string; 343 | image: File; 344 | data?: File; 345 | }): Promise<{ encoded_transaction: string; mint: string }> { 346 | try { 347 | let data = new FormData(); 348 | data.append('network', input.network ?? this.config.network); 349 | data.append('creator_wallet', input.creatorWallet); 350 | data.append('name', input.name); 351 | data.append('symbol', input.symbol); 352 | if (input?.description) { 353 | data.append('description', input.description); 354 | } 355 | if (input?.attributes) { 356 | data.append('attributes', JSON.stringify(input.attributes)); 357 | } 358 | if (input?.externalUrl) { 359 | data.append('external_url', input.externalUrl); 360 | } 361 | if (input?.maxSupply) { 362 | data.append('max_supply', input.maxSupply.toString()); 363 | } 364 | if (input?.royalty) { 365 | data.append('royalty', input.royalty.toString()); 366 | } 367 | if (input?.collectionAddress) { 368 | data.append('collection_address', input.collectionAddress); 369 | } 370 | if (input?.feePayer) { 371 | data.append('fee_payer', input.feePayer); 372 | } 373 | data.append('image', input.image); 374 | if (input?.data) { 375 | data.append('data', input.data); 376 | } 377 | 378 | const response = await restApiCall( 379 | this.config.apiKey, 380 | { 381 | method: 'post', 382 | url: 'nft/create', 383 | maxBodyLength: Infinity, 384 | data, 385 | }, 386 | 'v2' 387 | ); 388 | 389 | const result = response.result as { 390 | encoded_transaction: string; 391 | mint: string; 392 | }; 393 | return result; 394 | } catch (error) { 395 | throw error; 396 | } 397 | } 398 | 399 | async updateV2(input: { 400 | network?: Network; 401 | mint: string; 402 | updateAuthority: string; 403 | name?: string; 404 | symbol?: string; 405 | description?: string; 406 | attributes?: Attribute[]; 407 | royalty?: number; 408 | image?: File; 409 | data?: File; 410 | feePayer?: string; 411 | serviceCharge?: ServiceCharge; 412 | }): Promise<{ encoded_transaction: string; mint: string }> { 413 | try { 414 | let data = new FormData(); 415 | data.append('network', input.network ?? this.config.network); 416 | data.append('token_address', input.mint); 417 | data.append('update_authority_address', input.updateAuthority); 418 | if (input?.name) { 419 | if (input.name.length > 32) { 420 | throw new Error('Max length allowed 32: name'); 421 | } 422 | data.append('name', input.name); 423 | } 424 | if (input?.symbol) { 425 | if (input.symbol.length > 10) { 426 | throw new Error('Max length allowed 10: symbol'); 427 | } 428 | data.append('symbol', input.symbol); 429 | } 430 | if (input?.description) { 431 | data.append('description', input.description); 432 | } 433 | if (input?.attributes) { 434 | data.append('attributes', JSON.stringify(input.attributes)); 435 | } 436 | if (input?.royalty) { 437 | data.append('royalty', input.royalty.toString()); 438 | } 439 | if (input?.image) { 440 | data.append('image', input.image); 441 | } 442 | if (input?.data) { 443 | data.append('data', input.data); 444 | } 445 | if (input?.feePayer) { 446 | data.append('fee_payer', input.feePayer); 447 | } 448 | if (input?.serviceCharge) { 449 | data.append('service_charge', input.serviceCharge); 450 | } 451 | 452 | const response = await restApiCall( 453 | this.config.apiKey, 454 | { 455 | method: 'post', 456 | url: 'nft/update', 457 | maxBodyLength: Infinity, 458 | data, 459 | }, 460 | 'v2' 461 | ); 462 | 463 | const result = { 464 | encoded_transaction: response.result as string, 465 | mint: input.mint, 466 | }; 467 | return result; 468 | } catch (error) { 469 | throw error; 470 | } 471 | } 472 | } 473 | -------------------------------------------------------------------------------- /src/api/rpc-client.ts: -------------------------------------------------------------------------------- 1 | import { DAS } from '@/types'; 2 | import { rpcCall } from '@/utils/rpc-call'; 3 | import { 4 | BlockhashWithExpiryBlockHeight, 5 | Commitment, 6 | Connection, 7 | GetLatestBlockhashConfig, 8 | } from '@solana/web3.js'; 9 | 10 | export class RpcClient { 11 | readonly id: string; 12 | constructor(private readonly connection: Connection) { 13 | this.id = 'shyft-sdk'; 14 | } 15 | 16 | async getLatestBlockhash( 17 | commitmentOrConfig: Commitment | GetLatestBlockhashConfig = 'finalized' 18 | ): Promise { 19 | return this.connection.getLatestBlockhash(commitmentOrConfig); 20 | } 21 | 22 | async getAsset(params: DAS.GetAssetRequest): Promise { 23 | try { 24 | const data = await rpcCall(this.connection, { 25 | data: { 26 | jsonrpc: '2.0', 27 | id: this.id, 28 | method: 'getAsset', 29 | params, 30 | }, 31 | }); 32 | if (data['error']) { 33 | return data['error']; 34 | } 35 | const result = data.result; 36 | return result as DAS.GetAssetResponse; 37 | } catch (error) { 38 | throw new Error(`Error in getAsset: ${error}`); 39 | } 40 | } 41 | 42 | async getAssetProof( 43 | params: DAS.GetAssetProofRequest 44 | ): Promise { 45 | try { 46 | const data = await rpcCall(this.connection, { 47 | data: { 48 | jsonrpc: '2.0', 49 | id: this.id, 50 | method: 'getAssetProof', 51 | params: params, 52 | }, 53 | }); 54 | if (data['error']) { 55 | return data['error']; 56 | } 57 | const result = data.result; 58 | return result as DAS.GetAssetProofResponse; 59 | } catch (error) { 60 | throw new Error(`Error in getAssetProof: ${error}`); 61 | } 62 | } 63 | 64 | async getAssetsByGroup( 65 | params: DAS.AssetsByGroupRequest 66 | ): Promise { 67 | try { 68 | const data = await rpcCall(this.connection, { 69 | data: { 70 | jsonrpc: '2.0', 71 | id: this.id, 72 | method: 'getAssetsByGroup', 73 | params: params, 74 | }, 75 | }); 76 | if (data['error']) { 77 | return data['error']; 78 | } 79 | const result = data.result; 80 | return result as DAS.GetAssetResponseList; 81 | } catch (error) { 82 | throw new Error(`Error in getAssetsByGroup: ${error}`); 83 | } 84 | } 85 | 86 | async getAssetsByOwner( 87 | params: DAS.AssetsByOwnerRequest 88 | ): Promise { 89 | try { 90 | const data = await rpcCall(this.connection, { 91 | data: { 92 | jsonrpc: '2.0', 93 | id: this.id, 94 | method: 'getAssetsByOwner', 95 | params: params, 96 | }, 97 | }); 98 | if (data['error']) { 99 | return data['error']; 100 | } 101 | const result = data.result; 102 | return result as DAS.GetAssetResponseList; 103 | } catch (error) { 104 | throw new Error(`Error in getAssetsByOwner: ${error}`); 105 | } 106 | } 107 | 108 | async getAssetsByCreator( 109 | params: DAS.AssetsByCreatorRequest 110 | ): Promise { 111 | try { 112 | const data = await rpcCall(this.connection, { 113 | data: { 114 | jsonrpc: '2.0', 115 | id: this.id, 116 | method: 'getAssetsByCreator', 117 | params: params, 118 | }, 119 | }); 120 | if (data['error']) { 121 | return data['error']; 122 | } 123 | const result = data.result; 124 | return result as DAS.GetAssetResponseList; 125 | } catch (error) { 126 | throw new Error(`Error in getAssetsByCreator: ${error}`); 127 | } 128 | } 129 | 130 | async getAssetsByAuthority( 131 | params: DAS.AssetsByAuthorityRequest 132 | ): Promise { 133 | try { 134 | const data = await rpcCall(this.connection, { 135 | data: { 136 | jsonrpc: '2.0', 137 | id: this.id, 138 | method: 'getAssetsByAuthority', 139 | params: params, 140 | }, 141 | }); 142 | if (data['error']) { 143 | return data['error']; 144 | } 145 | const result = data.result; 146 | return result as DAS.GetAssetResponseList; 147 | } catch (error) { 148 | throw new Error(`Error in getAssetsByAuthority: ${error}`); 149 | } 150 | } 151 | 152 | async searchAssets( 153 | params: DAS.SearchAssetsRequest 154 | ): Promise { 155 | try { 156 | const data = await rpcCall(this.connection, { 157 | data: { 158 | jsonrpc: '2.0', 159 | id: this.id, 160 | method: 'searchAssets', 161 | params: params, 162 | }, 163 | }); 164 | if (data['error']) { 165 | return data['error']; 166 | } 167 | const result = data.result; 168 | return result as DAS.GetAssetResponseList; 169 | } catch (error) { 170 | throw new Error(`Error in searchAssets: ${error}`); 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/api/semi-custodial-wallet-client.ts: -------------------------------------------------------------------------------- 1 | import { ShyftConfig } from '@/utils'; 2 | import { restApiCall } from '@/utils'; 3 | import { WalletKeypair } from '@/types'; 4 | 5 | export class SemiCustodialWalletClient { 6 | constructor(private readonly config: ShyftConfig) {} 7 | 8 | async create(input: { password: string }): Promise { 9 | try { 10 | const reqBody = { 11 | password: input.password, 12 | }; 13 | const data = await restApiCall(this.config.apiKey, { 14 | method: 'post', 15 | url: 'semi_wallet/create', 16 | data: reqBody, 17 | }); 18 | const walletAddress = data.result.wallet_address as string; 19 | return walletAddress; 20 | } catch (error) { 21 | throw error; 22 | } 23 | } 24 | 25 | async getKeypair(input: { 26 | password: string; 27 | walletAddress: string; 28 | }): Promise { 29 | try { 30 | const params = { 31 | password: input.password, 32 | wallet: input.walletAddress, 33 | }; 34 | const data = await restApiCall(this.config.apiKey, { 35 | method: 'get', 36 | url: 'semi_wallet/get_keypair', 37 | params, 38 | }); 39 | const wallet = data.result as WalletKeypair; 40 | return wallet; 41 | } catch (error) { 42 | throw error; 43 | } 44 | } 45 | 46 | async changePassword(input: { 47 | currentPassword: string; 48 | newPassword: string; 49 | walletAddress: string; 50 | }): Promise { 51 | try { 52 | const reqBody = { 53 | current_password: input.currentPassword, 54 | new_password: input.newPassword, 55 | wallet: input.walletAddress, 56 | }; 57 | const data = await restApiCall(this.config.apiKey, { 58 | method: 'post', 59 | url: 'semi_wallet/change_password', 60 | data: reqBody, 61 | }); 62 | const isPasswordChanged = data.success as boolean; 63 | return isPasswordChanged; 64 | } catch (error) { 65 | throw error; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/api/storage-client.ts: -------------------------------------------------------------------------------- 1 | import FormData from 'form-data'; 2 | import { ShyftConfig } from '@/utils'; 3 | import { restApiCall } from '@/utils'; 4 | import { Attribute, IpfsUploadResponse, NftFile } from '@/types'; 5 | 6 | export class StorageClient { 7 | constructor(private readonly config: ShyftConfig) {} 8 | 9 | async uploadAsset(input: { file: File }): Promise { 10 | try { 11 | let data = new FormData(); 12 | data.append('file', input.file); 13 | const response = await restApiCall(this.config.apiKey, { 14 | method: 'post', 15 | url: 'storage/upload', 16 | maxBodyLength: Infinity, 17 | data, 18 | }); 19 | const uploadedAsset = response.result as IpfsUploadResponse; 20 | return uploadedAsset; 21 | } catch (error) { 22 | throw error; 23 | } 24 | } 25 | 26 | async createMetadata(input: { 27 | creator: string; 28 | image: string; 29 | name: string; 30 | symbol: string; 31 | description: string; 32 | attributes: Attribute[]; 33 | external_url?: string; 34 | sellerFeeBasisPoints?: number; 35 | files?: NftFile[]; 36 | }): Promise { 37 | try { 38 | const reqBody = { 39 | creator: input.creator, 40 | image: input.image, 41 | name: input.name, 42 | symbol: input.symbol, 43 | description: input.description, 44 | attributes: input.attributes, 45 | share: 100, 46 | }; 47 | if (input?.external_url) { 48 | reqBody['external_url'] = input.external_url; 49 | } 50 | if (input?.sellerFeeBasisPoints) { 51 | reqBody['royalty'] = input.sellerFeeBasisPoints; 52 | } 53 | if (input?.files) { 54 | reqBody['files'] = input.files; 55 | } 56 | const data = await restApiCall(this.config.apiKey, { 57 | method: 'post', 58 | url: 'metadata/create', 59 | data: reqBody, 60 | }); 61 | 62 | const uploadedMetadata = data.result as IpfsUploadResponse; 63 | return uploadedMetadata; 64 | } catch (error) { 65 | throw error; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/api/token-client.ts: -------------------------------------------------------------------------------- 1 | import FormData from 'form-data'; 2 | import { ShyftConfig } from '@/utils'; 3 | import { restApiCall } from '@/utils'; 4 | import { 5 | TokenApiResponse, 6 | Network, 7 | TokenInfo, 8 | TokenOwners, 9 | TokenTransferTo, 10 | AirdropTokenResponse, 11 | } from '@/types'; 12 | 13 | export class TokenClient { 14 | constructor(private readonly config: ShyftConfig) {} 15 | 16 | async getInfo(input: { network?: Network; tokenAddress: string }) { 17 | try { 18 | const params = { 19 | network: input.network ?? this.config.network, 20 | token_address: input.tokenAddress, 21 | }; 22 | const data = await restApiCall(this.config.apiKey, { 23 | method: 'get', 24 | url: 'token/get_info', 25 | params, 26 | }); 27 | const tokenInfo = data.result as TokenInfo; 28 | return tokenInfo; 29 | } catch (error) { 30 | throw error; 31 | } 32 | } 33 | 34 | async getOwners(input: { 35 | network?: Network; 36 | tokenAddress: string; 37 | limit?: number; 38 | offset?: number; 39 | }) { 40 | try { 41 | const params = { 42 | network: input.network ?? this.config.network, 43 | token_address: input.tokenAddress, 44 | limit: input?.limit ?? 10, 45 | offset: input?.offset ?? 0, 46 | }; 47 | if (params.network !== Network.Mainnet) { 48 | throw new Error('This operation only available on mainnet-beta'); 49 | } 50 | const data = await restApiCall(this.config.apiKey, { 51 | method: 'get', 52 | url: 'token/get_owners', 53 | params, 54 | }); 55 | const tokenOwners = data.result as TokenOwners; 56 | return tokenOwners; 57 | } catch (error) { 58 | throw error; 59 | } 60 | } 61 | 62 | async create(input: { 63 | network?: Network; 64 | creatorWallet: string; 65 | name: string; 66 | symbol: string; 67 | description?: string; 68 | decimals: number; 69 | image: File; 70 | feePayer?: string; 71 | }): Promise { 72 | try { 73 | let data = new FormData(); 74 | data.append('network', input.network ?? this.config.network); 75 | data.append('wallet', input.creatorWallet); 76 | if (input.name.length > 32) { 77 | throw new Error('Max length allowed 32: name'); 78 | } 79 | data.append('name', input.name); 80 | if (input.symbol.length > 10) { 81 | throw new Error('Max length allowed 10: symbol'); 82 | } 83 | data.append('symbol', input.symbol); 84 | data.append('file', input.image); 85 | if (input.decimals < 0 || input.decimals > 9) { 86 | throw new Error('0 to 9 allowed: decimals'); 87 | } 88 | if (input?.description) { 89 | data.append('description', input.description); 90 | } 91 | if (input?.feePayer) { 92 | data.append('fee_payer', input.feePayer); 93 | } 94 | 95 | const response = await restApiCall(this.config.apiKey, { 96 | method: 'post', 97 | url: 'token/create_detach', 98 | maxBodyLength: Infinity, 99 | data, 100 | }); 101 | return response.result as TokenApiResponse; 102 | } catch (error) { 103 | throw error; 104 | } 105 | } 106 | 107 | async mint(input: { 108 | network?: Network; 109 | mintAuthority: string; 110 | receiver: string; 111 | tokenAddress: string; 112 | amount: number; 113 | message?: string; 114 | feePayer?: string; 115 | }): Promise { 116 | try { 117 | const reqBody = { 118 | network: input.network ?? this.config.network, 119 | mint_authority: input.mintAuthority, 120 | receiver: input.receiver, 121 | token_address: input.tokenAddress, 122 | amount: input.amount, 123 | }; 124 | if (input?.message) { 125 | reqBody['message'] = input.message; 126 | } 127 | if (input?.feePayer) { 128 | reqBody['fee_payer'] = input.feePayer; 129 | } 130 | 131 | const response = await restApiCall(this.config.apiKey, { 132 | method: 'post', 133 | url: 'token/mint_detach', 134 | data: reqBody, 135 | }); 136 | return response.result as TokenApiResponse; 137 | } catch (error) { 138 | throw error; 139 | } 140 | } 141 | 142 | async burn(input: { 143 | network?: Network; 144 | wallet: string; 145 | tokenAddress: string; 146 | amount: number; 147 | feePayer?: string; 148 | }): Promise> { 149 | try { 150 | const reqBody = { 151 | network: input.network ?? this.config.network, 152 | wallet: input.wallet, 153 | token_address: input.tokenAddress, 154 | amount: input.amount, 155 | }; 156 | if (input?.feePayer) { 157 | reqBody['fee_payer'] = input.feePayer; 158 | } 159 | 160 | const response = await restApiCall(this.config.apiKey, { 161 | method: 'delete', 162 | url: 'token/burn_detach', 163 | data: reqBody, 164 | }); 165 | return response.result as Omit; 166 | } catch (error) { 167 | throw error; 168 | } 169 | } 170 | 171 | async transfer(input: { 172 | network?: Network; 173 | fromAddress: string; 174 | toAddress: string; 175 | tokenAddress: string; 176 | amount: number; 177 | feePayer?: string; 178 | }): Promise> { 179 | try { 180 | if (input.fromAddress === input.toAddress) { 181 | throw new Error('fromAddress and toAddress must not be the same'); 182 | } 183 | const reqBody = { 184 | network: input.network ?? this.config.network, 185 | from_address: input.fromAddress, 186 | to_address: input.toAddress, 187 | token_address: input.tokenAddress, 188 | amount: input.amount, 189 | }; 190 | if (input?.feePayer) { 191 | reqBody['fee_payer'] = input.feePayer; 192 | } 193 | 194 | const response = await restApiCall(this.config.apiKey, { 195 | method: 'post', 196 | url: 'token/transfer_detach', 197 | data: reqBody, 198 | }); 199 | return response.result as Omit; 200 | } catch (error) { 201 | throw error; 202 | } 203 | } 204 | 205 | async airdrop(input: { 206 | network?: Network; 207 | fromAddress: string; 208 | tokenAddress: string; 209 | transferTo: TokenTransferTo[]; 210 | priorityFee?: number; 211 | }): Promise { 212 | try { 213 | const transferTo = input.transferTo.map((x) => { 214 | if (x.toAddress === input.fromAddress) { 215 | throw new Error('fromAddress and toAddress must not be the same'); 216 | } 217 | return { to_address: x.toAddress, amount: x.amount }; 218 | }); 219 | const reqBody = { 220 | network: input.network ?? this.config.network, 221 | from_address: input.fromAddress, 222 | transfer_info: transferTo, 223 | token_address: input.tokenAddress, 224 | }; 225 | 226 | if (input?.priorityFee) { 227 | reqBody['priority_fee'] = input.priorityFee; 228 | } 229 | 230 | const response = await restApiCall(this.config.apiKey, { 231 | method: 'post', 232 | url: 'token/airdrop', 233 | data: reqBody, 234 | }); 235 | return response.result as AirdropTokenResponse; 236 | } catch (error) { 237 | throw error; 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/api/transaction-client.ts: -------------------------------------------------------------------------------- 1 | import { ShyftConfig } from '@/utils'; 2 | import { restApiCall } from '@/utils'; 3 | import { 4 | Network, 5 | ParsedTxnSummary, 6 | RawTransaction, 7 | SendTransactionResp, 8 | TransactionHistory, 9 | TxnCommitment, 10 | } from '@/types'; 11 | 12 | export class TransactionClient { 13 | constructor(private readonly config: ShyftConfig) {} 14 | 15 | async raw(input: { 16 | network?: Network; 17 | txnSignature: string; 18 | }): Promise { 19 | const params = { 20 | network: input.network ?? this.config.network, 21 | txn_signature: input.txnSignature, 22 | }; 23 | const data = await restApiCall(this.config.apiKey, { 24 | method: 'get', 25 | url: 'transaction/raw', 26 | params, 27 | }); 28 | const transaction = data.result as RawTransaction; 29 | return transaction; 30 | } 31 | 32 | async parsed(input: { 33 | network?: Network; 34 | txnSignature: string; 35 | }): Promise { 36 | const params = { 37 | network: input.network ?? this.config.network, 38 | txn_signature: input.txnSignature, 39 | }; 40 | const data = await restApiCall(this.config.apiKey, { 41 | method: 'get', 42 | url: 'transaction/parsed', 43 | params, 44 | }); 45 | const transaction = data.result as ParsedTxnSummary; 46 | return transaction; 47 | } 48 | 49 | async history(input: { 50 | network?: Network; 51 | account: string; 52 | txNum?: number; 53 | beforeTxSignature?: string; 54 | untilTxSignature?: string; 55 | enableRaw?: boolean; 56 | enableEvents?: boolean; 57 | }): Promise { 58 | const params = { 59 | network: input.network ?? this.config.network, 60 | account: input.account, 61 | }; 62 | if (input?.txNum) { 63 | if (input.txNum > 100 || input.txNum < 1) 64 | throw new Error( 65 | "'txNum' should not be greater than 100 or less than 1" 66 | ); 67 | params['tx_num'] = input.txNum; 68 | } 69 | if (input?.beforeTxSignature) { 70 | params['before_tx_signature'] = input.beforeTxSignature; 71 | } 72 | if (input?.untilTxSignature) { 73 | params['until_tx_signature'] = input.untilTxSignature; 74 | } 75 | if (input?.enableRaw) { 76 | params['enable_raw'] = input.enableRaw; 77 | } 78 | if (input?.enableEvents) { 79 | params['enable_events'] = input.enableEvents; 80 | } 81 | 82 | const data = await restApiCall(this.config.apiKey, { 83 | method: 'get', 84 | url: 'transaction/history', 85 | params, 86 | }); 87 | const transactions = data.result as TransactionHistory; 88 | return transactions; 89 | } 90 | 91 | async parseSelected(input: { 92 | network?: Network; 93 | transactionSignatues: string[]; 94 | enableRaw?: boolean; 95 | enableEvents?: boolean; 96 | }): Promise { 97 | if ( 98 | input.transactionSignatues.length > 100 || 99 | input.transactionSignatues.length < 1 100 | ) { 101 | throw new Error('allowed between 1 to 100: transactionSignatues'); 102 | } 103 | const reqBody = { 104 | network: input.network ?? this.config.network, 105 | transaction_signatures: input.transactionSignatues, 106 | }; 107 | if (input?.enableRaw) { 108 | reqBody['enable_raw'] = input.enableRaw; 109 | } 110 | if (input?.enableEvents) { 111 | reqBody['enable_events'] = input.enableEvents; 112 | } 113 | 114 | const data = await restApiCall(this.config.apiKey, { 115 | method: 'post', 116 | url: 'transaction/parse_selected', 117 | data: reqBody, 118 | }); 119 | const transactions = data.result as TransactionHistory; 120 | return transactions; 121 | } 122 | 123 | async send(input: { 124 | network?: Network; 125 | encodedTransaction: string; 126 | }): Promise { 127 | const reqBody = { 128 | network: input.network ?? this.config.network, 129 | encoded_transaction: input.encodedTransaction, 130 | }; 131 | 132 | const data = await restApiCall(this.config.apiKey, { 133 | method: 'post', 134 | url: 'transaction/send_txn', 135 | data: reqBody, 136 | }); 137 | const result = data.result?.signature as string; 138 | return result; 139 | } 140 | 141 | async sendMany(input: { 142 | network?: Network; 143 | encodedTransactions: string[]; 144 | commitment?: TxnCommitment; 145 | }): Promise { 146 | if ( 147 | input.encodedTransactions.length > 50 || 148 | input.encodedTransactions.length < 1 149 | ) { 150 | throw new Error('allowed between 1 to 50: encodedTransactions'); 151 | } 152 | const reqBody = { 153 | network: input.network ?? this.config.network, 154 | encoded_transactions: input.encodedTransactions, 155 | }; 156 | if (input?.commitment) { 157 | reqBody['commitment'] = input.commitment; 158 | } 159 | 160 | const data = await restApiCall(this.config.apiKey, { 161 | method: 'post', 162 | url: 'transaction/send_many_txns', 163 | data: reqBody, 164 | }); 165 | const result = data.result as SendTransactionResp[]; 166 | return result; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/api/txn-relayer-client.ts: -------------------------------------------------------------------------------- 1 | import { ShyftConfig } from '@/utils'; 2 | import { restApiCall } from '@/utils'; 3 | import { Network, SendTransactionResp, TxnCommitment } from '@/types'; 4 | 5 | export class TxnRelayerClient { 6 | constructor(private readonly config: ShyftConfig) {} 7 | 8 | async getOrCreate(): Promise { 9 | const data = await restApiCall(this.config.apiKey, { 10 | method: 'post', 11 | url: 'txn_relayer/create', 12 | }); 13 | const wallet = data.result.wallet as string; 14 | return wallet; 15 | } 16 | 17 | async sign(input: { 18 | network?: Network; 19 | encodedTransaction: string; 20 | }): Promise { 21 | const reqBody = { 22 | network: input.network ?? this.config.network, 23 | encoded_transaction: input.encodedTransaction, 24 | }; 25 | 26 | const data = await restApiCall(this.config.apiKey, { 27 | method: 'post', 28 | url: 'txn_relayer/sign', 29 | data: reqBody, 30 | }); 31 | const result = data.result?.tx as string; 32 | return result; 33 | } 34 | 35 | async signMany(input: { 36 | network?: Network; 37 | encodedTransactions: string[]; 38 | commitment?: TxnCommitment; 39 | }): Promise { 40 | if ( 41 | input.encodedTransactions.length > 50 || 42 | input.encodedTransactions.length < 1 43 | ) { 44 | throw new Error('allowed between 1 to 50: encodedTransactions'); 45 | } 46 | const reqBody = { 47 | network: input.network ?? this.config.network, 48 | encoded_transactions: input.encodedTransactions, 49 | }; 50 | if (input?.commitment) { 51 | reqBody['commitment'] = input.commitment; 52 | } 53 | 54 | const data = await restApiCall(this.config.apiKey, { 55 | method: 'post', 56 | url: 'txn_relayer/sign_many', 57 | data: reqBody, 58 | }); 59 | const result = data.result as SendTransactionResp[]; 60 | return result; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/api/wallet-client.ts: -------------------------------------------------------------------------------- 1 | import { ParsedTransactionWithMeta } from '@solana/web3.js'; 2 | import { ShyftConfig } from '@/utils'; 3 | import { restApiCall } from '@/utils'; 4 | import { 5 | Domain, 6 | GroupNftsInCollection, 7 | Portfolio, 8 | TokenBalance, 9 | ParsedTranaction, 10 | Network, 11 | } from '@/types'; 12 | 13 | export class WalletClient { 14 | constructor(private readonly config: ShyftConfig) {} 15 | 16 | async getBalance(input: { 17 | network?: Network; 18 | wallet: string; 19 | }): Promise { 20 | try { 21 | const params = { 22 | network: input.network ?? this.config.network, 23 | wallet: input.wallet, 24 | }; 25 | const data = await restApiCall(this.config.apiKey, { 26 | method: 'get', 27 | url: 'wallet/balance', 28 | params, 29 | }); 30 | const balance = data.result.balance as number; 31 | return balance; 32 | } catch (error) { 33 | throw error; 34 | } 35 | } 36 | 37 | async sendSol(input: { 38 | network?: Network; 39 | fromAddress: string; 40 | toAddress: string; 41 | amount: number; 42 | }): Promise { 43 | try { 44 | const reqBody = { 45 | network: input.network ?? this.config.network, 46 | from_address: input.fromAddress, 47 | to_address: input.toAddress, 48 | amount: input.amount, 49 | }; 50 | const data = await restApiCall(this.config.apiKey, { 51 | method: 'post', 52 | url: 'wallet/send_sol', 53 | data: reqBody, 54 | }); 55 | const encodedTransaction = data.result.encoded_transaction as string; 56 | return encodedTransaction; 57 | } catch (error) { 58 | throw error; 59 | } 60 | } 61 | 62 | async getTokenBalance(input: { 63 | network?: Network; 64 | wallet: string; 65 | token: string; 66 | }): Promise< 67 | Omit & { isFrozen: boolean } 68 | > { 69 | try { 70 | const params = { 71 | network: input.network ?? this.config.network, 72 | wallet: input.wallet, 73 | token: input.token, 74 | }; 75 | const data = await restApiCall(this.config.apiKey, { 76 | method: 'get', 77 | url: 'wallet/token_balance', 78 | params, 79 | }); 80 | const tokenBalances = data.result; 81 | return tokenBalances; 82 | } catch (error) { 83 | throw error; 84 | } 85 | } 86 | 87 | async getAllTokenBalance(input: { 88 | network?: Network; 89 | wallet: string; 90 | }): Promise { 91 | try { 92 | const params = { 93 | network: input.network ?? this.config.network, 94 | wallet: input.wallet, 95 | }; 96 | const data = await restApiCall(this.config.apiKey, { 97 | method: 'get', 98 | url: 'wallet/all_tokens', 99 | params, 100 | }); 101 | const tokenBalances = data.result as TokenBalance[]; 102 | return tokenBalances; 103 | } catch (error) { 104 | throw error; 105 | } 106 | } 107 | 108 | async getPortfolio(input: { 109 | network?: Network; 110 | wallet: string; 111 | }): Promise { 112 | try { 113 | const params = { 114 | network: input.network ?? this.config.network, 115 | wallet: input.wallet, 116 | }; 117 | const data = await restApiCall(this.config.apiKey, { 118 | method: 'get', 119 | url: 'wallet/get_portfolio', 120 | params, 121 | }); 122 | const portfolio = data.result as Portfolio; 123 | return portfolio; 124 | } catch (error) { 125 | throw error; 126 | } 127 | } 128 | 129 | async getDomains(input: { 130 | network?: Network; 131 | wallet: string; 132 | }): Promise { 133 | try { 134 | const params = { 135 | network: input.network ?? this.config.network, 136 | wallet: input.wallet, 137 | }; 138 | const data = await restApiCall(this.config.apiKey, { 139 | method: 'get', 140 | url: 'wallet/get_domains', 141 | params, 142 | }); 143 | const domains = data.result as Domain[]; 144 | return domains; 145 | } catch (error) { 146 | throw error; 147 | } 148 | } 149 | 150 | async resolveDomainByAddress(input: { 151 | network?: Network; 152 | address: string; 153 | }): Promise { 154 | try { 155 | const params = { 156 | network: input.network ?? this.config.network, 157 | address: input.address, 158 | }; 159 | const data = await restApiCall(this.config.apiKey, { 160 | method: 'get', 161 | url: 'wallet/resolve_address', 162 | params, 163 | }); 164 | const domain = data.result.name as string; 165 | return domain; 166 | } catch (error) { 167 | throw error; 168 | } 169 | } 170 | 171 | async collections(input: { 172 | network?: Network; 173 | wallet: string; 174 | }): Promise { 175 | try { 176 | const params = { 177 | network: input.network ?? this.config.network, 178 | wallet_address: input.wallet, 179 | }; 180 | const data = await restApiCall(this.config.apiKey, { 181 | method: 'get', 182 | url: 'wallet/collections', 183 | params, 184 | }); 185 | const collections = data.result.collections as GroupNftsInCollection; 186 | return collections; 187 | } catch (error) { 188 | throw error; 189 | } 190 | } 191 | 192 | async transactionHistory(input: { 193 | network?: Network; 194 | wallet: string; 195 | limit?: number; 196 | beforeTxSignature?: string; 197 | }): Promise { 198 | try { 199 | const params = { 200 | network: input.network ?? this.config.network, 201 | wallet: input.wallet, 202 | tx_num: input.limit ? input.limit : 10, 203 | }; 204 | if (input?.beforeTxSignature) { 205 | params['before_tx_signature'] = input.beforeTxSignature; 206 | } 207 | const data = await restApiCall(this.config.apiKey, { 208 | method: 'get', 209 | url: 'wallet/transaction_history', 210 | params, 211 | }); 212 | const trsnsactions = data.result as ParsedTransactionWithMeta[]; 213 | return trsnsactions; 214 | } catch (error) { 215 | throw error; 216 | } 217 | } 218 | 219 | async transaction(input: { 220 | network?: Network; 221 | txnSignature: string; 222 | }): Promise { 223 | try { 224 | const params = { 225 | network: input.network ?? this.config.network, 226 | txn_signature: input.txnSignature, 227 | }; 228 | const data = await restApiCall(this.config.apiKey, { 229 | method: 'get', 230 | url: 'wallet/transaction', 231 | params, 232 | }); 233 | const trsnsaction = data.result as ParsedTransactionWithMeta; 234 | return trsnsaction; 235 | } catch (error) { 236 | throw error; 237 | } 238 | } 239 | 240 | async parsedTransactionHistory(input: { 241 | network?: Network; 242 | wallet: string; 243 | limit?: number; 244 | beforeTxSignature?: string; 245 | }): Promise { 246 | try { 247 | const params = { 248 | network: input.network ?? this.config.network, 249 | account: input.wallet, 250 | tx_num: input.limit ? input.limit : 10, 251 | }; 252 | if (input?.beforeTxSignature) { 253 | params['before_tx_signature'] = input.beforeTxSignature; 254 | } 255 | const data = await restApiCall(this.config.apiKey, { 256 | method: 'get', 257 | url: 'wallet/parsed_transaction_history', 258 | params, 259 | }); 260 | const trsnsactions = data.result as ParsedTranaction[]; 261 | return trsnsactions; 262 | } catch (error) { 263 | throw error; 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '@/utils/shyft'; 2 | export * from '@/utils/signer'; 3 | export * from '@/types'; 4 | -------------------------------------------------------------------------------- /src/types/Callback.ts: -------------------------------------------------------------------------------- 1 | import { Network } from './Network'; 2 | import { TxnAction } from './Transaction'; 3 | 4 | export type CallBack = { 5 | id: string; 6 | network: Network; 7 | addresses: string[]; 8 | callback_url: string; 9 | events: TxnAction & 'ANY'[]; 10 | enable_raw?: boolean; 11 | enable_events?: boolean; 12 | type?: CallbackType; 13 | active: boolean; 14 | encoding?: CallbackEncoding; 15 | created_at: Date; 16 | updated_at: Date; 17 | }; 18 | 19 | export type CallbackType = 'DISCORD' | 'CALLBACK' | 'ACCOUNT'; 20 | export type CallbackEncoding = 'RAW' | 'PARSED'; 21 | -------------------------------------------------------------------------------- /src/types/CandyMachine.ts: -------------------------------------------------------------------------------- 1 | import { TransactionVersion } from '@solana/web3.js'; 2 | import { Nft } from './Nft'; 3 | 4 | export enum CandyMachineProgram { 5 | V2 = 'v2', 6 | V3 = 'v3', 7 | } 8 | 9 | export type PaginatedNftResponse = { 10 | nfts: Nft[]; 11 | page: number; 12 | size: number; 13 | total_data: number; 14 | total_page: number; 15 | }; 16 | export type BulkItemSettings = { 17 | readonly name: string; 18 | readonly uri: string; 19 | }; 20 | 21 | export type ItemSettings = { 22 | readonly prefixName: string; 23 | readonly nameLength: number; 24 | readonly prefixUri: string; 25 | readonly uriLength: number; 26 | readonly isSequential: boolean; 27 | }; 28 | 29 | type Option = T | null; 30 | 31 | export type CandyMachineGuard = { 32 | [name: string]: Option; 33 | }; 34 | 35 | export type CandyMachineGroup = { 36 | label: string; 37 | guards: CandyMachineGuard; 38 | }; 39 | 40 | export type CandyMachineItem = { 41 | name: string; 42 | uri: string; 43 | }; 44 | 45 | export type CreateCandyMachineResp = { 46 | encoded_transaction: string; 47 | candy_machine: string; 48 | signers: string[]; 49 | }; 50 | 51 | export type InsertCandyMachineResp = { 52 | encoded_transaction: string; 53 | signers: string[]; 54 | }; 55 | 56 | export type MintCandyMachineResp = { 57 | encoded_transaction: string; 58 | transaction_version: TransactionVersion; 59 | mint: string; 60 | signers: string[]; 61 | }; 62 | -------------------------------------------------------------------------------- /src/types/CompressedNft.ts: -------------------------------------------------------------------------------- 1 | import { TransactionVersion } from '@solana/web3.js'; 2 | 3 | export type ValidDepthSizePair = 4 | | { maxDepth: 3; maxBufferSize: 8 } 5 | | { maxDepth: 5; maxBufferSize: 8 } 6 | | { maxDepth: 6; maxBufferSize: 16 } 7 | | { maxDepth: 7; maxBufferSize: 16 } 8 | | { maxDepth: 8; maxBufferSize: 16 } 9 | | { maxDepth: 9; maxBufferSize: 16 } 10 | | { maxDepth: 10; maxBufferSize: 32 } 11 | | { maxDepth: 11; maxBufferSize: 32 } 12 | | { maxDepth: 12; maxBufferSize: 32 } 13 | | { maxDepth: 13; maxBufferSize: 32 } 14 | | { maxDepth: 14; maxBufferSize: 64 } 15 | | { maxDepth: 14; maxBufferSize: 256 } 16 | | { maxDepth: 14; maxBufferSize: 1024 } 17 | | { maxDepth: 14; maxBufferSize: 2048 } 18 | | { maxDepth: 15; maxBufferSize: 64 } 19 | | { maxDepth: 16; maxBufferSize: 64 } 20 | | { maxDepth: 17; maxBufferSize: 64 } 21 | | { maxDepth: 18; maxBufferSize: 64 } 22 | | { maxDepth: 19; maxBufferSize: 64 } 23 | | { maxDepth: 20; maxBufferSize: 64 } 24 | | { maxDepth: 20; maxBufferSize: 256 } 25 | | { maxDepth: 20; maxBufferSize: 1024 } 26 | | { maxDepth: 20; maxBufferSize: 2048 } 27 | | { maxDepth: 24; maxBufferSize: 64 } 28 | | { maxDepth: 24; maxBufferSize: 256 } 29 | | { maxDepth: 24; maxBufferSize: 512 } 30 | | { maxDepth: 24; maxBufferSize: 1024 } 31 | | { maxDepth: 24; maxBufferSize: 2048 } 32 | | { maxDepth: 26; maxBufferSize: 512 } 33 | | { maxDepth: 26; maxBufferSize: 1024 } 34 | | { maxDepth: 26; maxBufferSize: 2048 } 35 | | { maxDepth: 30; maxBufferSize: 512 } 36 | | { maxDepth: 30; maxBufferSize: 1024 } 37 | | { maxDepth: 30; maxBufferSize: 2048 }; 38 | 39 | type CommonTxnResponse = { 40 | encoded_transaction: string; 41 | signers: string[]; 42 | }; 43 | 44 | export type CreateMerkleTreeResponse = CommonTxnResponse & { tree: string }; 45 | export type CNftMintResponse = CommonTxnResponse & { 46 | mint: string; 47 | transaction_version: TransactionVersion; 48 | }; 49 | export type CNftTransferResponse = CommonTxnResponse & { 50 | transaction_version: TransactionVersion; 51 | }; 52 | export type CNftBurnResponse = CommonTxnResponse & { 53 | transaction_version: TransactionVersion; 54 | }; 55 | export type CNftUpdateResponse = CommonTxnResponse & { 56 | transaction_version: TransactionVersion; 57 | }; 58 | export type CNftTransferManyResp = { 59 | encoded_transactions: string[]; 60 | signers: string[]; 61 | }; 62 | export type CNftBurnManyResp = { encoded_transactions: string[] }; 63 | -------------------------------------------------------------------------------- /src/types/DAS.ts: -------------------------------------------------------------------------------- 1 | export enum Interface { 2 | V1NFT = 'V1_NFT', 3 | CUSTOM = 'Custom', 4 | V1PRINT = 'V1_PRINT', 5 | LEGACYNFT = 'Legacy_NFT', 6 | V2NFT = 'V2_NFT', 7 | FUNGIBLE_ASSET = 'FungibleAsset', 8 | IDENTITY = 'Identity', 9 | EXECUTABLE = 'Executable', 10 | PROGRAMMABLENFT = 'ProgrammableNFT', 11 | } 12 | 13 | export enum Scope { 14 | FULL = 'full', 15 | ROYALTY = 'royalty', 16 | METADATA = 'metadata', 17 | EXTENSION = 'extension', 18 | } 19 | 20 | export enum Context { 21 | WalletDefault = 'wallet-default', 22 | WebDesktop = 'web-desktop', 23 | WebMobile = 'web-mobile', 24 | AppMobile = 'app-mobile', 25 | AppDesktop = 'app-desktop', 26 | App = 'app', 27 | Vr = 'vr', 28 | } 29 | 30 | export enum OwnershipModel { 31 | SINGLE = 'single', 32 | TOKEN = 'token', 33 | } 34 | 35 | export enum RoyaltyModel { 36 | CREATORS = 'creators', 37 | FANOUT = 'fanout', 38 | SINGLE = 'single', 39 | } 40 | 41 | export enum UseMethods { 42 | BURN = 'Burn', 43 | SINGLE = 'Single', 44 | MULTIPLE = 'Multiple', 45 | } 46 | 47 | export type AssetSortBy = 'created' | 'updated' | 'recent_action'; 48 | 49 | export type AssetSortDirection = 'asc' | 'desc'; 50 | 51 | export namespace DAS { 52 | export interface AssetsByOwnerRequest { 53 | ownerAddress: string; 54 | page: number; 55 | limit?: number; 56 | before?: string; 57 | after?: string; 58 | displayOptions?: DisplayOptions; 59 | sortBy?: AssetSortingRequest; 60 | } 61 | 62 | export type AssetsByCreatorRequest = { 63 | creatorAddress: string; 64 | page: number; 65 | onlyVerified?: boolean; 66 | limit?: number; 67 | before?: string; 68 | after?: string; 69 | displayOptions?: DisplayOptions; 70 | sortBy?: AssetSortingRequest; 71 | }; 72 | 73 | export type GetAssetBatchRequest = { 74 | ids: Array; 75 | displayOptions?: GetAssetDisplayOptions; 76 | }; 77 | 78 | export type AssetsByGroupRequest = { 79 | groupValue: string; 80 | groupKey: string; 81 | page: number; 82 | limit?: number; 83 | before?: string; 84 | after?: string; 85 | displayOptions?: DisplayOptions; 86 | sortBy?: AssetSortingRequest; 87 | }; 88 | export type GetAssetsBatchRequest = { 89 | ids: string[]; 90 | }; 91 | 92 | export interface SearchAssetsRequest { 93 | page: number; 94 | limit?: number; 95 | before?: string; 96 | after?: string; 97 | creatorAddress?: string; 98 | ownerAddress?: string; 99 | jsonUri?: string; 100 | grouping?: string[]; 101 | burnt?: boolean; 102 | sortBy?: AssetSortingRequest; 103 | frozen?: boolean; 104 | supplyMint?: string; 105 | supply?: number; 106 | interface?: string; 107 | delegate?: number; 108 | ownerType?: OwnershipModel; 109 | royaltyAmount?: number; 110 | royaltyTarget?: string; 111 | royaltyTargetType?: RoyaltyModel; 112 | compressible?: boolean; 113 | compressed?: boolean; 114 | } 115 | 116 | export type AssetsByAuthorityRequest = { 117 | authorityAddress: string; 118 | page: number; 119 | limit?: number; 120 | before?: string; 121 | after?: string; 122 | displayOptions?: DisplayOptions; 123 | sortBy?: AssetSortingRequest; 124 | }; 125 | 126 | export type GetAssetRequest = { 127 | id: string; 128 | displayOptions?: GetAssetDisplayOptions; 129 | }; 130 | 131 | export type GetAssetProofRequest = { 132 | id: string; 133 | }; 134 | 135 | export type GetSignaturesForAssetRequest = { 136 | id: string; 137 | page: number; 138 | limit?: number; 139 | before?: string; 140 | after?: string; 141 | }; 142 | 143 | export interface AssetSorting { 144 | sort_by: AssetSortBy; 145 | sort_direction: AssetSortDirection; 146 | } 147 | 148 | export type AssetSortingRequest = { 149 | sortBy: AssetSortBy; 150 | sortDirection: AssetSortDirection; 151 | }; 152 | 153 | export type GetAssetResponse = { 154 | interface: Interface; 155 | id: string; 156 | content?: Content; 157 | authorities?: Authorities[]; 158 | compression?: Compression; 159 | grouping?: Grouping[]; 160 | royalty?: Royalty; 161 | ownership: Ownership; 162 | creators?: Creators[]; 163 | uses?: Uses; 164 | supply?: Supply; 165 | mutable: boolean; 166 | burnt: boolean; 167 | }; 168 | 169 | export type GetAssetResponseList = { 170 | grand_total?: boolean; 171 | total: number; 172 | limit: number; 173 | page: number; 174 | items: GetAssetResponse[]; 175 | }; 176 | export interface GetAssetProofResponse { 177 | root: string; 178 | proof: Array; 179 | node_index: number; 180 | leaf: string; 181 | tree_id: string; 182 | } 183 | export interface GetSignaturesForAssetResponse { 184 | total: number; 185 | limit: number; 186 | page?: number; 187 | before?: string; 188 | after?: string; 189 | items: Array>; 190 | } 191 | 192 | export type DisplayOptions = { 193 | showUnverifiedCollections?: boolean; 194 | showCollectionMetadata?: boolean; 195 | showGrandTotal?: boolean; 196 | }; 197 | 198 | export type GetAssetDisplayOptions = { 199 | showUnverifiedCollections?: boolean; 200 | showCollectionMetadata?: boolean; 201 | }; 202 | 203 | export interface Ownership { 204 | frozen: boolean; 205 | delegated: boolean; 206 | delegate?: string; 207 | ownership_model: OwnershipModel; 208 | owner: string; 209 | } 210 | 211 | export interface Supply { 212 | print_max_supply: number; 213 | print_current_supply: number; 214 | edition_nonce?: number; 215 | } 216 | 217 | export interface Uses { 218 | use_method: UseMethods; 219 | remaining: number; 220 | total: number; 221 | } 222 | 223 | export interface Creators { 224 | address: string; 225 | share: number; 226 | verified: boolean; 227 | } 228 | 229 | export interface Royalty { 230 | royalty_model: RoyaltyModel; 231 | target?: string; 232 | percent: number; 233 | basis_points: number; 234 | primary_sale_happened: boolean; 235 | locked: boolean; 236 | } 237 | 238 | export interface Grouping { 239 | group_key: string; 240 | group_value: string; 241 | verified?: boolean; 242 | collection_metadata?: CollectionMetadata; 243 | } 244 | export interface CollectionMetadata { 245 | name?: string; 246 | symbol?: string; 247 | image?: string; 248 | description?: string; 249 | external_url?: string; 250 | } 251 | 252 | export interface Authorities { 253 | address: string; 254 | scopes: Array; 255 | } 256 | 257 | export type Links = { 258 | external_url?: string; 259 | image?: string; 260 | animation_url?: string; 261 | [Symbol.iterator](): Iterator; 262 | }; 263 | 264 | export interface Content { 265 | $schema: string; 266 | json_uri: string; 267 | files?: Files; 268 | metadata: Metadata; 269 | links?: Links; 270 | } 271 | 272 | export interface File { 273 | uri?: string; 274 | mime?: string; 275 | cdn_uri?: string; 276 | quality?: FileQuality; 277 | contexts?: Context[]; 278 | [Symbol.iterator](): Iterator; 279 | } 280 | 281 | export type Files = File[]; 282 | export interface FileQuality { 283 | schema: string; 284 | } 285 | 286 | export interface Metadata { 287 | attributes?: Attribute[]; 288 | description: string; 289 | name: string; 290 | symbol: string; 291 | } 292 | 293 | export interface Attribute { 294 | value: string; 295 | trait_type: string; 296 | } 297 | export interface Compression { 298 | eligible: boolean; 299 | compressed: boolean; 300 | data_hash: string; 301 | creator_hash: string; 302 | asset_hash: string; 303 | tree: string; 304 | seq: number; 305 | leaf_id: number; 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /src/types/Enums.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Shyft-to/js/64c0361107d56987059f78f79b776691c20c2844/src/types/Enums.ts -------------------------------------------------------------------------------- /src/types/Error.ts: -------------------------------------------------------------------------------- 1 | export type ApiError = { 2 | success: boolean; 3 | error: string | object | any; 4 | }; 5 | -------------------------------------------------------------------------------- /src/types/Marketplace.ts: -------------------------------------------------------------------------------- 1 | export type Marketplace = { 2 | network: string; 3 | address: string; 4 | fee_account: string; 5 | treasury_address: string; 6 | fee_payer: string; 7 | fee_recipient: string; 8 | fee_recipient_account: string; 9 | currency_address: string; 10 | currency_symbol?: string; 11 | creator?: string; 12 | transaction_fee: number; 13 | authority: string; 14 | }; 15 | 16 | export type TreasuryBalance = { 17 | amount: number; 18 | symbol: string; 19 | }; 20 | 21 | export type MarketplaceStats = { 22 | total_sales: number; 23 | sales_volume: number; 24 | total_sellers: number; 25 | total_listings: number; 26 | listed_volume: number; 27 | start_date: Date; 28 | end_date: Date; 29 | }; 30 | 31 | export type WithdrawFeeTxn = { 32 | from: string; 33 | to: string; 34 | amount: number; 35 | encoded_transaction: string; 36 | }; 37 | -------------------------------------------------------------------------------- /src/types/MpBidding.ts: -------------------------------------------------------------------------------- 1 | import { ListedNftDetail, NftListResponse } from './MpListing'; 2 | 3 | export type BidsSortOrder = 'asc' | 'desc'; 4 | 5 | export type BidsSortOptions = 'bid_date' | 'price'; 6 | 7 | export type Bid = Omit & { 8 | buyer_address: string; 9 | bid_state: string; 10 | }; 11 | 12 | export type ActiveBids = { 13 | data: Omit[]; 14 | page: number; 15 | size: number; 16 | total_data: number; 17 | total_page: number; 18 | }; 19 | 20 | export type NftBidResponse = Omit< 21 | NftListResponse, 22 | 'seller_address' | 'list_state' 23 | > & { buyer_address: string; bid_state: string }; 24 | -------------------------------------------------------------------------------- /src/types/MpListing.ts: -------------------------------------------------------------------------------- 1 | import { TransactionVersion } from '@solana/web3.js'; 2 | import { Network } from './Network'; 3 | import { Nft } from './Nft'; 4 | 5 | export enum NftStatus { 6 | Stale = 'stale', 7 | Active = 'active', 8 | } 9 | 10 | export type ListedNftDetail = { 11 | network: Network; 12 | marketplace_address: string; 13 | seller_address: string; 14 | price: number; 15 | currency_symbol: string; 16 | nft_address: string; 17 | nft: Nft; 18 | list_state: string; 19 | status: NftStatus; 20 | created_at: Date; 21 | receipt: string; 22 | purchase_receipt?: string; 23 | cancelled_at?: Date; 24 | purchased_at?: Date; 25 | }; 26 | 27 | export type ActiveListings = { 28 | data: Omit< 29 | ListedNftDetail, 30 | 'cancelled_at' | 'purchased_at' | 'purchase_receipt' 31 | >[]; 32 | page: number; 33 | size: number; 34 | total_data: number; 35 | total_page: number; 36 | }; 37 | 38 | export type NftListResponse = { 39 | network: Network; 40 | marketplace_address: string; 41 | seller_address: string; 42 | price: number; 43 | nft_address: string; 44 | list_state: string; 45 | currency_symbol: string; 46 | encoded_transaction: string; 47 | }; 48 | 49 | export type NftBuyResponse = { 50 | network: Network; 51 | marketplace_address: string; 52 | seller_address: string; 53 | price: number; 54 | nft_address: string; 55 | purchase_receipt?: string; 56 | currency_symbol: string; 57 | buyer_address: string; 58 | encoded_transaction: string; 59 | transaction_version: TransactionVersion; 60 | }; 61 | 62 | export type ActiveListingSortBy = 'list_date' | 'price'; 63 | export type ActiveListingSortOrder = 'asc' | 'desc'; 64 | -------------------------------------------------------------------------------- /src/types/Network.ts: -------------------------------------------------------------------------------- 1 | export enum Network { 2 | Mainnet = 'mainnet-beta', 3 | Testnet = 'testnet', 4 | Devnet = 'devnet', 5 | } 6 | -------------------------------------------------------------------------------- /src/types/Nft.ts: -------------------------------------------------------------------------------- 1 | export type CollectionInfo = { 2 | address?: string; 3 | verified?: boolean; 4 | name?: string; 5 | family?: string; 6 | }; 7 | 8 | export type NftFile = { 9 | uri: string; 10 | type: string; 11 | }; 12 | 13 | type TokenRecordData = { 14 | address: string; 15 | key: string; 16 | state: string; 17 | rule_set_revision: number | null; 18 | delegate: string; 19 | token_delegate_role: string; 20 | locked_transfer: string; 21 | }; 22 | 23 | export type Nft = { 24 | name: string; 25 | description: string; 26 | symbol: string; 27 | image_uri: string; 28 | royalty: number; 29 | mint: string; 30 | attributes: { [k: string]: string | number }; 31 | owner: string; 32 | update_authority: string; 33 | cached_image_uri: string; 34 | animation_url: string; 35 | cached_animation_url: string; 36 | metadata_uri: string; 37 | creators: Creator[]; 38 | collection: CollectionInfo; 39 | attributes_array: any; 40 | files: NftFile[]; 41 | external_url: string; 42 | is_loaded_metadata: boolean; 43 | primary_sale_happened: boolean; 44 | is_mutable: boolean; 45 | token_standard: string; 46 | is_compressed: boolean; 47 | merkle_tree: string; 48 | is_burnt: boolean; 49 | token_record?: TokenRecordData; 50 | }; 51 | 52 | export type PaginatedNfts = { 53 | nfts: Nft[]; 54 | total_count: number; 55 | page: number; 56 | size: number; 57 | total_pages: number; 58 | }; 59 | 60 | export type Creator = { 61 | address: string; 62 | verified: boolean; 63 | share: number; 64 | }; 65 | 66 | type CollectionNft = { 67 | name: string; 68 | symbol: string; 69 | royalty: number; 70 | mint: string; 71 | update_authority: string; 72 | metadata_uri: string; 73 | creators: Creator[]; 74 | }; 75 | 76 | export type GroupNftsInCollection = { 77 | address?: string; 78 | name: string; 79 | family?: string; 80 | nft_count: number; 81 | nfts: CollectionNft[]; 82 | }; 83 | 84 | export type NftMetadata = { 85 | model: string; 86 | address: string; 87 | mintAddress: string; 88 | updateAuthorityAddress: string; 89 | json: object | null; 90 | jsonLoaded: boolean; 91 | name: string; 92 | symbol: string; 93 | uri: string; 94 | isMutable: boolean; 95 | primarySaleHappened: boolean; 96 | sellerFeeBasisPoints: number; 97 | editionNonce: number; 98 | creators: Creator[]; 99 | tokenStandard: number; 100 | collection: Omit; 101 | collectionDetails: object | null; 102 | uses: object | null; 103 | }; 104 | 105 | export type Attribute = { 106 | trait_type: string; 107 | value: string | number; 108 | }; 109 | 110 | export type CollectionNfts = { 111 | nfts: Nft[]; 112 | total_count: number; 113 | total_pages: number; 114 | page: number; 115 | size: number; 116 | }; 117 | 118 | export type NftMintAndOwner = { 119 | nft_address: string; 120 | owner: string; 121 | }; 122 | -------------------------------------------------------------------------------- /src/types/SemiCustodialWallet.ts: -------------------------------------------------------------------------------- 1 | export type WalletKeypair = { 2 | publicKey: string; 3 | secretKey: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/types/Shyft.ts: -------------------------------------------------------------------------------- 1 | import { Transaction, VersionedTransaction } from '@solana/web3.js'; 2 | import { Network } from './Network'; 3 | 4 | export interface SignerWalletAdapterProps { 5 | signTransaction( 6 | transaction: T 7 | ): Promise; 8 | signAllTransactions( 9 | transactions: T[] | undefined 10 | ): Promise; 11 | signMessage(message: Uint8Array): Promise; 12 | } 13 | 14 | export interface ShyftWallet { 15 | signTransaction: SignerWalletAdapterProps['signTransaction'] | undefined; 16 | signAllTransactions: 17 | | SignerWalletAdapterProps['signAllTransactions'] 18 | | undefined; 19 | signMessage: SignerWalletAdapterProps['signMessage'] | undefined; 20 | connect(): Promise; 21 | disconnect(): Promise; 22 | [key: string]: any; 23 | } 24 | 25 | export type ShyftSettings = { 26 | apiKey: string; 27 | network: Network; 28 | }; 29 | -------------------------------------------------------------------------------- /src/types/Storage.ts: -------------------------------------------------------------------------------- 1 | export type IpfsUploadResponse = { 2 | cid: string; 3 | uri: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/types/Token.ts: -------------------------------------------------------------------------------- 1 | export type TokenInfo = { 2 | name: string; 3 | symbol: string; 4 | metadata_uri: string; 5 | image: string; 6 | address: string; 7 | mint_authority: string; 8 | freeze_authority: string; 9 | current_supply: number; 10 | decimals: number; 11 | }; 12 | 13 | type TokenOwner = { 14 | ata: string; 15 | amount: number; 16 | owner: string; 17 | rank: number; 18 | }; 19 | 20 | export type TokenOwners = { 21 | owners: TokenOwner[]; 22 | total: number; 23 | limit: number; 24 | offset: number; 25 | }; 26 | 27 | export type ServiceCharge = { 28 | receiver: string; 29 | token?: string; 30 | amount: number; 31 | }; 32 | 33 | export type TokenTransferTo = { 34 | toAddress: string; 35 | amount: number; 36 | }; 37 | 38 | export type TokenApiResponse = { 39 | encoded_transaction: string; 40 | mint: string; 41 | signers: string[]; 42 | }; 43 | 44 | export type AirdropTokenResponse = { 45 | encoded_transaction: string[]; 46 | signer: string; 47 | }; 48 | -------------------------------------------------------------------------------- /src/types/Transaction.ts: -------------------------------------------------------------------------------- 1 | import { ParsedTransactionWithMeta } from '@solana/web3.js'; 2 | 3 | export enum TxnAction { 4 | NFT_MINT = 'NFT_MINT', 5 | TOKEN_MINT = 'TOKEN_MINT', 6 | TOKEN_CREATE = 'TOKEN_CREATE', 7 | SOL_TRANSFER = 'SOL_TRANSFER', 8 | TOKEN_TRANSFER = 'TOKEN_TRANSFER', 9 | NFT_TRANSFER = 'NFT_TRANSFER', 10 | NFT_BURN = 'NFT_BURN', 11 | TOKEN_BURN = 'TOKEN_BURN', 12 | NFT_SALE = 'NFT_SALE', 13 | NFT_BID = 'NFT_BID', 14 | NFT_BID_CANCEL = 'NFT_BID_CANCEL', 15 | NFT_LIST = 'NFT_LIST', 16 | NFT_LIST_UPDATE = 'NFT_LIST_UPDATE', 17 | NFT_LIST_CANCEL = 'NFT_LIST_CANCEL', 18 | MARKETPLACE_WITHDRAW = 'MARKETPLACE_WITHDRAW', 19 | COMPRESSED_NFT_SALE = 'COMPRESSED_NFT_SALE', 20 | COMPRESSED_NFT_LIST = 'COMPRESSED_NFT_LIST', 21 | COMPRESSED_NFT_LIST_CANCEL = 'COMPRESSED_NFT_LIST_CANCEL', 22 | COMPRESSED_NFT_LIST_UPDATE = 'COMPRESSED_NFT_LIST_UPDATE', 23 | COMPRESSED_NFT_BID = 'COMPRESSED_NFT_BID', 24 | COMPRESSED_NFT_BID_CANCEL = 'COMPRESSED_NFT_BID_CANCEL', 25 | COMPRESSED_NFT_TAKE_BID = 'COMPRESSED_NFT_TAKE_BID', 26 | OFFER_LOAN = 'OFFER_LOAN', 27 | CANCEL_LOAN = 'CANCEL_LOAN', 28 | TAKE_LOAN = 'TAKE_LOAN', 29 | REPAY_LOAN = 'REPAY_LOAN', 30 | FORECLOSE_LOAN = 'FORECLOSE_LOAN', 31 | REPAY_ESCROW_LOAN = 'REPAY_ESCROW_LOAN', 32 | REQUEST_LOAN = 'REQUEST_LOAN', 33 | CANCEL_REQUEST_LOAN = 'CANCEL_REQUEST_LOAN', 34 | EXTEND_LOAN = 'EXTEND_LOAN', 35 | EXTEND_ESCROW_LOAN = 'EXTEND_ESCROW_LOAN', 36 | LIQUIDATE_LOAN = 'LIQUIDATE_LOAN', 37 | BUY_NOW_PAY_LATER = 'BUY_NOW_PAY_LATER', 38 | MEMO = 'MEMO', 39 | SWAP = 'SWAP', 40 | CREATE_POOL = 'CREATE_POOL', 41 | ADD_LIQUIDITY = 'ADD_LIQUIDITY', 42 | REMOVE_LIQUIDITY = 'REMOVE_LIQUIDITY', 43 | COLLECT_FEES = 'COLLECT_FEES', 44 | COLLECT_REWARD = 'COLLECT_REWARD', 45 | CREATE_RAFFLE = 'CREATE_RAFFLE', 46 | UPDATE_RAFFLE = 'UPDATE_RAFFLE', 47 | BUY_TICKETS = 'BUY_TICKETS', 48 | REVEAL_WINNERS = 'REVEAL_WINNERS', 49 | CLAIM_PRIZE = 'CLAIM_PRIZE', 50 | CLOSE_RAFFLE = 'CLOSE_RAFFLE', 51 | CANCEL_RAFFLE = 'CANCEL_RAFFLE', 52 | CREATE_TREE = 'CREATE_TREE', 53 | COMPRESSED_NFT_MINT = 'COMPRESSED_NFT_MINT', 54 | COMPRESSED_NFT_TRANSFER = 'COMPRESSED_NFT_TRANSFER', 55 | COMPRESSED_NFT_BURN = 'COMPRESSED_NFT_BURN', 56 | CREATE_REALM = 'CREATE_REALM', 57 | DEPOSIT_GOVERNING_TOKENS = 'DEPOSIT_GOVERNING_TOKENS', 58 | WITHDRAW_GOVERNING_TOKENS = 'WITHDRAW_GOVERNING_TOKENS', 59 | SET_GOVERNANCE_DELEGATE = 'SET_GOVERNANCE_DELEGATE', 60 | CREATE_GOVERNANCE = 'CREATE_GOVERNANCE', 61 | CREATE_PROGRAM_GOVERNANCE = 'CREATE_PROGRAM_GOVERNANCE', 62 | CREATE_PROPOSAL = 'CREATE_PROPOSAL', 63 | ADD_SIGNATORY = 'ADD_SIGNATORY', 64 | REMOVE_SIGNATORY = 'REMOVE_SIGNATORY', 65 | INSERT_TRANSACTION = 'INSERT_TRANSACTION', 66 | REMOVE_TRANSACTION = 'REMOVE_TRANSACTION', 67 | CANCEL_PROPOSAL = 'CANCEL_PROPOSAL', 68 | SIGN_OFF_PROPOSAL = 'SIGN_OFF_PROPOSAL', 69 | CAST_VOTE = 'CAST_VOTE', 70 | FINALIZE_VOTE = 'FINALIZE_VOTE', 71 | RELINQUISH_VOTE = 'RELINQUISH_VOTE', 72 | EXECUTE_TRANSACTION = 'EXECUTE_TRANSACTION', 73 | CREATE_MINT_GOVERNANCE = 'CREATE_MINT_GOVERNANCE', 74 | CREATE_TOKEN_GOVERNANCE = 'CREATE_TOKEN_GOVERNANCE', 75 | SET_GOVERNANCE_CONFIG = 'SET_GOVERNANCE_CONFIG', 76 | SET_REALM_AUTHORITY = 'SET_REALM_AUTHORITY', 77 | SET_REALM_CONFIG = 'SET_REALM_CONFIG', 78 | CREATE_TOKEN_OWNER_RECORD = 'CREATE_TOKEN_OWNER_RECORD', 79 | POST_MESSAGE = 'POST_MESSAGE', 80 | UNKNOWN = 'UNKNOWN', 81 | } 82 | 83 | type Condition = { 84 | actionName: string; 85 | fieldUpdates: { 86 | original: string; 87 | new: string; 88 | }; 89 | }; 90 | 91 | type Action = { 92 | name: string[]; 93 | type?: TxnAction; 94 | info: any; 95 | indices: string[]; 96 | conditions?: Condition[]; 97 | }; 98 | 99 | type ActionBrief = Omit, 'name'>; 100 | 101 | type ProtocolDetails = { 102 | name: string; 103 | }; 104 | 105 | export type ParsedTranaction = { 106 | timestamp?: string; 107 | fee?: number; 108 | fee_payer: string; 109 | actions?: ActionBrief[]; 110 | signatures: string[]; 111 | protocols?: Map; 112 | signers: string[]; 113 | }; 114 | 115 | type EasyProtocol = { 116 | address: string; 117 | name: string; 118 | }; 119 | 120 | type SimplifiedTxnInfo = { 121 | timestamp: string; 122 | fee: number; 123 | fee_payer: string; 124 | signatures: string[]; 125 | signers: string[]; 126 | type: string; 127 | protocol: EasyProtocol; 128 | }; 129 | 130 | type ActionSummary = { 131 | info: T; 132 | source_protocol: string; 133 | parent_protocol?: string; 134 | type: string; 135 | tags?: string[]; 136 | }; 137 | 138 | type NeatActionSummary = Omit, 'tags'>; 139 | 140 | type Event = { name: string; data: any }; 141 | 142 | export type ParsedTxnSummary = SimplifiedTxnInfo & { 143 | actions: NeatActionSummary[]; 144 | events?: Array; 145 | }; 146 | 147 | export type RawTransaction = ParsedTransactionWithMeta & { 148 | parsed: ParsedTxnSummary; 149 | }; 150 | 151 | export type TransactionHistory = (ParsedTxnSummary & { 152 | raw?: ParsedTransactionWithMeta; 153 | })[]; 154 | 155 | export type TxnCommitment = 'processed' | 'confirmed' | 'finalized'; 156 | 157 | type RpcError = { 158 | code: number; 159 | message: string; 160 | data: any; 161 | }; 162 | 163 | export type SendTransactionResp = { 164 | id: number | string; 165 | signature: string; 166 | error?: RpcError; 167 | status: TxnCommitment | null; 168 | }; 169 | -------------------------------------------------------------------------------- /src/types/Wallet.ts: -------------------------------------------------------------------------------- 1 | import { NftMetadata } from '.'; 2 | 3 | type TokenMetadata = { 4 | name: string; 5 | symbol: string; 6 | image: string; 7 | }; 8 | 9 | export type TokenBalance = { 10 | address: string; 11 | balance: number; 12 | associated_account: string; 13 | info: TokenMetadata; 14 | }; 15 | 16 | export type Portfolio = { 17 | sol_balance: number; 18 | num_tokens: number; 19 | tokens: Omit[]; 20 | num_nfts: number; 21 | nfts: NftMetadata[]; 22 | }; 23 | 24 | export type Domain = { 25 | address: string; 26 | name: string; 27 | }; 28 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | import { from } from 'form-data'; 2 | 3 | export type WithRequired = T & { [P in K]-?: T[P] }; 4 | 5 | export type ApiVersion = 'v1' | 'v2'; 6 | 7 | export * from './Shyft'; 8 | export * from './Error'; 9 | export * from './Network'; 10 | export * from './Nft'; 11 | export * from './Wallet'; 12 | export * from './Transaction'; 13 | export * from './Token'; 14 | export * from './CandyMachine'; 15 | export * from './Marketplace'; 16 | export * from './MpListing'; 17 | export * from './MpBidding'; 18 | export * from './Storage'; 19 | export * from './SemiCustodialWallet'; 20 | export * from './Callback'; 21 | export * from './CompressedNft'; 22 | export * from './DAS'; 23 | -------------------------------------------------------------------------------- /src/utils/case-converter.ts: -------------------------------------------------------------------------------- 1 | import { isArray, isObject, forEach, snakeCase, camelCase } from 'lodash'; 2 | 3 | export class CaseConverter { 4 | public convertToSnakeCaseObject(obj: any): any { 5 | if (isArray(obj)) { 6 | return obj.map((value) => this.convertToSnakeCaseObject(value)); 7 | } else if (isObject(obj)) { 8 | const snakeCaseObj = {}; 9 | forEach(obj, (value, key) => { 10 | snakeCaseObj[snakeCase(key)] = this.convertToSnakeCaseObject(value); 11 | }); 12 | return snakeCaseObj; 13 | } else { 14 | return obj; 15 | } 16 | } 17 | 18 | public convertToCamelCaseObject(obj: any): any { 19 | if (isArray(obj)) { 20 | return obj.map((value) => this.convertToCamelCaseObject(value)); 21 | } else if (isObject(obj)) { 22 | const camelCaseObj = {}; 23 | forEach(obj, (value, key) => { 24 | camelCaseObj[camelCase(key)] = this.convertToCamelCaseObject(value); 25 | }); 26 | return camelCaseObj; 27 | } else { 28 | return obj; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './shyft'; 2 | export * from './shyft-config'; 3 | export * from './rest-api-call'; 4 | export * from './signer'; 5 | export * from './case-converter'; 6 | -------------------------------------------------------------------------------- /src/utils/rest-api-call.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosError, AxiosRequestConfig } from 'axios'; 2 | import { ApiError, ApiVersion } from '@/types'; 3 | 4 | export async function restApiCall( 5 | apiKey: string, 6 | config: AxiosRequestConfig, 7 | version: ApiVersion = 'v1' 8 | ): Promise { 9 | try { 10 | const baseURL = `https://api.shyft.to/sol/${version}/`; 11 | const headers = { 'Access-Control-Allow-Origin': '*', 'x-api-key': apiKey }; 12 | const { data } = await axios.request({ ...config, baseURL, headers }); 13 | return data; 14 | } catch (error) { 15 | const err = error as AxiosError; 16 | const apiError = err.response?.data as ApiError; 17 | if (typeof apiError.error === 'object') { 18 | if ('message' in apiError.error) { 19 | throw new Error(apiError.error['message'] as string); 20 | } 21 | throw new Error(JSON.stringify(apiError.error)); 22 | } 23 | if (typeof apiError.error === 'string') { 24 | throw new Error(apiError.error); 25 | } 26 | if (typeof apiError['message'] === 'string') { 27 | throw new Error(apiError['message']); 28 | } 29 | throw apiError.error; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/rpc-call.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosError, AxiosRequestConfig } from 'axios'; 2 | import { ApiError } from '@/types'; 3 | import { Connection } from '@solana/web3.js'; 4 | 5 | export async function rpcCall( 6 | connection: Connection, 7 | config?: AxiosRequestConfig 8 | ): Promise { 9 | try { 10 | const rpcUrl = connection.rpcEndpoint; 11 | if (new URL(rpcUrl).hostname !== 'rpc.shyft.to') { 12 | throw new Error( 13 | "Currently DAS API support available only for the 'mainnet-beta' cluster" 14 | ); 15 | } 16 | const headers = { 17 | 'Content-Type': 'application/json', 18 | }; 19 | const { data } = await axios.request({ 20 | ...config, 21 | method: 'post', 22 | baseURL: rpcUrl, 23 | headers, 24 | }); 25 | return data; 26 | } catch (error) { 27 | if (error instanceof AxiosError) { 28 | const err = error as AxiosError; 29 | const apiError = err.response?.data as ApiError; 30 | if (typeof apiError.error === 'object') { 31 | if ('message' in apiError.error) { 32 | throw new Error(apiError.error['message'] as string); 33 | } 34 | throw new Error(JSON.stringify(apiError.error)); 35 | } 36 | if (typeof apiError.error === 'string') { 37 | throw new Error(apiError.error); 38 | } 39 | if (typeof apiError['message'] === 'string') { 40 | throw new Error(apiError['message']); 41 | } 42 | throw apiError.error; 43 | } else { 44 | throw error; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/utils/shyft-config.ts: -------------------------------------------------------------------------------- 1 | import { Network, ShyftSettings } from '@/types'; 2 | 3 | export class ShyftConfig { 4 | readonly apiKey: string; 5 | readonly network: Network; 6 | constructor(config: ShyftSettings) { 7 | this.apiKey = config.apiKey; 8 | this.network = config.network; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/utils/shyft.ts: -------------------------------------------------------------------------------- 1 | import { ShyftSettings } from '@/types'; 2 | import { 3 | WalletClient, 4 | NftClient, 5 | TokenClient, 6 | CandyMachineClient, 7 | MarketplaceClient, 8 | TransactionClient, 9 | StorageClient, 10 | CallbackClient, 11 | RpcClient, 12 | TxnRelayerClient, 13 | } from '@/api'; 14 | import { ShyftConfig } from '@/utils'; 15 | import { SemiCustodialWalletClient } from '@/api/semi-custodial-wallet-client'; 16 | import { Connection } from '@solana/web3.js'; 17 | import { shyftClusterApiUrl } from './utility'; 18 | 19 | export class ShyftSdk { 20 | readonly config: ShyftConfig; 21 | readonly connection: Connection; 22 | readonly wallet: WalletClient; 23 | readonly nft: NftClient; 24 | readonly token: TokenClient; 25 | readonly candyMachine: CandyMachineClient; 26 | readonly marketplace: MarketplaceClient; 27 | readonly transaction: TransactionClient; 28 | readonly txnRelayer: TxnRelayerClient; 29 | readonly storage: StorageClient; 30 | readonly semiCustodialWallet: SemiCustodialWalletClient; 31 | readonly callback: CallbackClient; 32 | readonly rpc: RpcClient; 33 | constructor(settings: ShyftSettings) { 34 | this.config = new ShyftConfig(settings); 35 | this.connection = new Connection( 36 | shyftClusterApiUrl(this.config.apiKey, this.config.network) 37 | ); 38 | this.wallet = new WalletClient(this.config); 39 | this.nft = new NftClient(this.config); 40 | this.token = new TokenClient(this.config); 41 | this.candyMachine = new CandyMachineClient(this.config); 42 | this.marketplace = new MarketplaceClient(this.config); 43 | this.transaction = new TransactionClient(this.config); 44 | this.txnRelayer = new TxnRelayerClient(this.config); 45 | this.storage = new StorageClient(this.config); 46 | this.semiCustodialWallet = new SemiCustodialWalletClient(this.config); 47 | this.callback = new CallbackClient(this.config); 48 | this.rpc = new RpcClient(this.connection); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/utils/signer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | clusterApiUrl, 3 | Connection, 4 | Keypair, 5 | Transaction, 6 | Signer, 7 | VersionedTransaction, 8 | } from '@solana/web3.js'; 9 | import { decode } from 'bs58'; 10 | 11 | import { Network, ShyftWallet } from '@/types'; 12 | 13 | /** 14 | * This function accepts the connection to the user’s wallet, 15 | * the encodedTransaction received in the response of the API call and the wallet object. 16 | * The only difference is, along with these parameters, 17 | * this function also takes in an array of private keys, 18 | * which can contain all the private keys required to partially sign the transaction. 19 | * 20 | * @param network solana rpc network (mainnet-beta/devnet/testnet) 21 | * @param encodedTransaction serialized transaction (base64 string) 22 | * @param privateKeys private key of wallets (Array of strings) 23 | * @returns transaction signature 24 | */ 25 | 26 | export async function signAndSendTransactionWithPrivateKeys( 27 | network: Network, 28 | encodedTransaction: string, 29 | privateKeys: string[] 30 | ): Promise { 31 | const connection = new Connection(clusterApiUrl(network), 'confirmed'); 32 | const signedTxn = await partialSignTransactionWithPrivateKeys( 33 | encodedTransaction, 34 | privateKeys 35 | ); 36 | 37 | const signature = await connection.sendRawTransaction(signedTxn.serialize()); 38 | return signature; 39 | } 40 | 41 | /** 42 | * This function accepts connection, the encoded_transaction from the SHYFT API response and the wallet object. 43 | * The wallet object and the connection to the user’s wallet can be obtained. 44 | * 45 | * @param connection solana rpc connection 46 | * @param encodedTransaction serialized transaction (base64 string) 47 | * @param wallet wallet 48 | * @returns transaction signature 49 | */ 50 | 51 | export async function signAndSendTransaction( 52 | connection: Connection, 53 | encodedTransaction: string, 54 | wallet: ShyftWallet 55 | ): Promise { 56 | const recoveredTransaction = getRawTransaction(encodedTransaction); 57 | const signedTx = await wallet.signTransaction!(recoveredTransaction); 58 | const confirmTransaction = await connection.sendRawTransaction( 59 | signedTx.serialize() 60 | ); 61 | return confirmTransaction; 62 | } 63 | 64 | /** 65 | * This function accepts the encodedTransaction received 66 | * in the response of the API call and takes an array of private keys, 67 | * which can contain all the private keys required to partially sign the transaction. 68 | * Could be useful when a transaction has required multiple signature to further proceed. 69 | * 70 | * @param encodedTransaction serialized transaction (base64 string) 71 | * @param privateKeys private keys of wallet (array of string) 72 | * @returns Raw transaction 73 | */ 74 | 75 | export async function partialSignTransactionWithPrivateKeys( 76 | encodedTransaction: string, 77 | privateKeys: string[] 78 | ): Promise { 79 | const recoveredTransaction = getRawTransaction(encodedTransaction); 80 | const signers = getSignersFromPrivateKeys(privateKeys); 81 | if (recoveredTransaction instanceof VersionedTransaction) { 82 | recoveredTransaction.sign(signers); 83 | } else { 84 | recoveredTransaction.partialSign(...signers); 85 | } 86 | return recoveredTransaction; 87 | } 88 | 89 | function getSignersFromPrivateKeys(privateKeys: string[]): Signer[] { 90 | return privateKeys.map((privateKey) => { 91 | const signer = Keypair.fromSecretKey(decode(privateKey)) as Signer; 92 | return signer; 93 | }); 94 | } 95 | 96 | function getRawTransaction( 97 | encodedTransaction: string 98 | ): Transaction | VersionedTransaction { 99 | let recoveredTransaction: Transaction | VersionedTransaction; 100 | try { 101 | recoveredTransaction = Transaction.from( 102 | Buffer.from(encodedTransaction, 'base64') 103 | ); 104 | } catch (error) { 105 | recoveredTransaction = VersionedTransaction.deserialize( 106 | Buffer.from(encodedTransaction, 'base64') 107 | ); 108 | } 109 | return recoveredTransaction; 110 | } 111 | -------------------------------------------------------------------------------- /src/utils/utility.ts: -------------------------------------------------------------------------------- 1 | import { Network } from '..'; 2 | 3 | export function shyftClusterApiUrl(apiKey: string, cluster: Network): string { 4 | switch (cluster) { 5 | case 'devnet': 6 | return `https://devnet-rpc.shyft.to/${apiKey}`; 7 | case 'mainnet-beta': 8 | return `https://rpc.shyft.to/${apiKey}`; 9 | default: 10 | return ''; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/callback-client.spec.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { ShyftSdk } from '@/index'; 3 | import { Network, TxnAction } from '@/types'; 4 | 5 | const shyft = new ShyftSdk({ 6 | apiKey: process.env.API_KEY as string, 7 | network: Network.Devnet, 8 | }); 9 | 10 | describe('callback test', () => { 11 | it.skip('register callback', async () => { 12 | const callback = await shyft.callback.register({ 13 | addresses: ['2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc'], 14 | callbackUrl: 'https://webhook.site/540c3516-b94c-4e64-a271-fa6450858869', 15 | enableRaw: true, 16 | }); 17 | console.log(callback); 18 | expect(callback).toBe('object'); 19 | }, 50000); 20 | 21 | it.skip('remove callback', async () => { 22 | const isRemoved = await shyft.callback.remove({ 23 | id: '643fec93b0b969e4b39b16b3', 24 | }); 25 | expect(isRemoved).toBe(true); 26 | }, 50000); 27 | 28 | it('register callback', async () => { 29 | const callback = await shyft.callback.update({ 30 | id: '643feecdb0b969e4b39b1a58', 31 | addresses: ['2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc'], 32 | callbackUrl: 'https://webhook.site/540c3516-b94c-4e64-a271-fa6450858869', 33 | enableRaw: true, 34 | events: [TxnAction.SOL_TRANSFER], 35 | }); 36 | console.log(callback); 37 | expect(typeof callback).toBe('object'); 38 | }, 50000); 39 | 40 | it('get callbacks', async () => { 41 | const callbacks = await shyft.callback.list(); 42 | console.log(callbacks); 43 | expect(callbacks[0]).toBe('object'); 44 | }, 50000); 45 | 46 | it('pause callback', async () => { 47 | const isPaused = await shyft.callback.pause({ 48 | id: '663889899aabe8e58dd4decb', 49 | }); 50 | expect(isPaused).toBe(true); 51 | }, 50000); 52 | 53 | it('resume callback', async () => { 54 | const isResumed = await shyft.callback.resume({ 55 | id: '663889899aabe8e58dd4decb', 56 | }); 57 | expect(isResumed).toBe(true); 58 | }, 50000); 59 | }); 60 | -------------------------------------------------------------------------------- /tests/candy-machine-client.spec.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { ShyftSdk } from '@/index'; 3 | import { 4 | Network, 5 | CandyMachineProgram, 6 | CreateCandyMachineResp, 7 | InsertCandyMachineResp, 8 | MintCandyMachineResp, 9 | } from '@/types'; 10 | 11 | const shyft = new ShyftSdk({ 12 | apiKey: process.env.API_KEY as string, 13 | network: Network.Devnet, 14 | }); 15 | 16 | describe('candy machine test', () => { 17 | it('candy machine mints', async () => { 18 | const mints = await shyft.candyMachine.readMints({ 19 | address: 'H2oYLkXdkX38eQ6VTqs26KAWAvEpYEiCtLt4knEUJxpu', 20 | version: CandyMachineProgram.V2, 21 | }); 22 | expect(typeof mints).toBe('object'); 23 | }, 50000); 24 | 25 | it('candy machine nft read', async () => { 26 | const mints = await shyft.candyMachine.readNfts({ 27 | address: 'CZ7zmhqpUSFAtaW7BsyXmgsRaLjKBb6gWHiEnNFE9DNj', 28 | }); 29 | expect(typeof mints).toBe('object'); 30 | }, 50000); 31 | 32 | it('create candy machine', async () => { 33 | const response = await shyft.candyMachine.create({ 34 | wallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 35 | symbol: 'GB', 36 | maxSupply: 0, 37 | royalty: 333, 38 | collection: 'CxKYibvYT9H8qBw827LvGuz6BWvXSsSJSeCsbm5t27hN', 39 | itemsAvailable: 10, 40 | creators: [ 41 | { address: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', share: 100 }, 42 | ], 43 | itemSettings: { 44 | prefixName: 'GetBoxie', 45 | nameLength: 10, 46 | prefixUri: 'https://nftstorage.link/ipfs/', 47 | uriLength: 100, 48 | isSequential: false, 49 | }, 50 | groups: [ 51 | { 52 | label: 'early', 53 | guards: { 54 | solPayment: { 55 | amount: 0.1, 56 | destination: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 57 | }, 58 | }, 59 | }, 60 | ], 61 | }); 62 | console.dir(response, { depth: null }); 63 | expect(response.signers.length).toBe(1); 64 | expect(response).toMatchObject({ 65 | encoded_transaction: expect.any(String), 66 | candy_machine: expect.any(String), 67 | signers: expect.any(Array), 68 | }); 69 | }, 50000); 70 | 71 | it('insert items to the candy machine', async () => { 72 | const response = await shyft.candyMachine.insert({ 73 | wallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 74 | candyMachine: '4tqTNb1uzRS3TakYpmnwbJzikyJ1iNB8BJ13v5ekdX56', 75 | items: [ 76 | { 77 | name: '-duckk', 78 | uri: 'bafkreihpo7yhx6nxvgu2qbabdp6zsiy5v6g62mnjwqp5ye7ejb5b2ubb64', 79 | }, 80 | ], 81 | }); 82 | console.dir(response, { depth: null }); 83 | expect(response.signers.length).toBe(1); 84 | expect(response).toMatchObject({ 85 | encoded_transaction: expect.any(String), 86 | signers: expect.any(Array), 87 | }); 88 | }, 50000); 89 | 90 | it('mint items from the candy machine', async () => { 91 | const response = await shyft.candyMachine.mint({ 92 | wallet: '3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf', 93 | candyMachine: '4tqTNb1uzRS3TakYpmnwbJzikyJ1iNB8BJ13v5ekdX56', 94 | authority: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 95 | mintGroup: 'early', 96 | }); 97 | console.dir(response, { depth: null }); 98 | expect(response.signers.length).toBe(2); 99 | expect(response.transaction_version).toBe('legacy'); 100 | expect(response).toMatchObject({ 101 | encoded_transaction: expect.any(String), 102 | transaction_version: expect.any(String), 103 | mint: expect.any(String), 104 | signers: expect.any(Array), 105 | }); 106 | }, 50000); 107 | 108 | it('mint another items from different candy machine', async () => { 109 | const response = await shyft.candyMachine.mint({ 110 | wallet: '3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf', 111 | candyMachine: 'GrhNqG48nTBBxecS7oDJYyBRuTykZo3nSVtKMZckWnrx', 112 | authority: 'AKhncfY9gnmsHCbpD4G48EJvpJHeCbg1uL6sczmsbX8V', 113 | feePayer: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 114 | guardSettings: { 115 | thirdPartySigner: { 116 | signer: 'AKhncfY9gnmsHCbpD4G48EJvpJHeCbg1uL6sczmsbX8V', 117 | }, 118 | }, 119 | }); 120 | console.dir(response, { depth: null }); 121 | expect(response.signers.length).toBe(3); 122 | expect(response.transaction_version).toBe(0); 123 | expect(response).toMatchObject({ 124 | encoded_transaction: expect.any(String), 125 | transaction_version: expect.any(Number), 126 | mint: expect.any(String), 127 | signers: expect.any(Array), 128 | }); 129 | }, 50000); 130 | 131 | it('monitor a candy machine', async () => { 132 | const response = await shyft.candyMachine.monitor({ 133 | candyMachine: '4tqTNb1uzRS3TakYpmnwbJzikyJ1iNB8BJ13v5ekdX56', 134 | }); 135 | expect(response).toBe(true); 136 | }, 50000); 137 | 138 | it('unmonitor a candy machine', async () => { 139 | const response = await shyft.candyMachine.unmonitor({ 140 | candyMachine: '4tqTNb1uzRS3TakYpmnwbJzikyJ1iNB8BJ13v5ekdX56', 141 | }); 142 | expect(response).toBe(true); 143 | }, 50000); 144 | }); 145 | -------------------------------------------------------------------------------- /tests/index.spec.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { Network } from '@/types'; 3 | 4 | import { signAndSendTransactionWithPrivateKeys } from '@/index'; 5 | 6 | const privateKeyOne = process.env.PRIVATE_KEY_ONE!; 7 | const privateKeyTwo = process.env.PRIVATE_KEY_TWO!; 8 | 9 | describe('test_cases', () => { 10 | it('confirmTxnByPrivateKeys', async () => { 11 | try { 12 | const encodedTransaction = 13 | 'AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABe6jnpzarNJhcjetQwpO8jt+dUkNvwNdYOvW8+F1JwyLUSQJVR5muXKjlhhfnLZsGMfaC8fHQPdFuePtHji6ACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMBBg4Yyp9RxUce7b6XJTmDH3BTasbGTY2hJe2h0xJ16/4PFf03x94/U2O4SQL6IcF0eGlfrdVFrrC7W+y0oqGTUWP2QCwHlX8JKKwKpawHjfHTp/CXruh+Lt6Hx8xs+BZ7UD8rhkoj+/vES50L3NHADaMSD/rr7M930xhbQr97Wowskj4tcOi9OU6097ok44hG1gMMWwE6s7vbFR0A9X0vfGgjUDMi6Zhw2/SeeDCwVHDlyCObPJcP3vUbc57nNxGIKDGF94zrICEdXsZxdK9N2umeVr2b2ihcbGoGW3U1Iy4qvbHNawqriyYByMpMO1+rBn5lGLpAdFwXMPn8wPVDimGFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADH3q/GBPdI2uw94BoJBVvw0aFRw/HAWDgbwtE/uDNYyXJY9OJInxuz0QKRSODYMLWhOZ2v8QhASOe9jb6fhZC3BlsePRfEU4nVJ/awTDzVi4bHMaoP21SbbRvAP4KUYGp9UXGSxcUSGMyUw9SvF/WNruCJuh/UTj29mKAAAAAAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpEtQg60AeD7LqLO6JSUu9JjePrIfls14iVS50u30DFyYHCAIAATQAAAAAYE0WAAAAAABSAAAAAAAAAAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpDQIBDEMAAEAsB5V/CSisCqWsB43x06fwl67ofi7eh8fMbPgWe1A/AUAsB5V/CSisCqWsB43x06fwl67ofi7eh8fMbPgWe1A/CgcABQIBCA0MAA0DAQUCCQcBAAAAAAAAAAsGBwECAAIIqgIQCwAAAFBsYXkgYnV0dG9uCgAAAFlPTSBCdXR0b266AAAAaHR0cHM6Ly9nYXRld2F5LnBpbmF0YS5jbG91ZC9pcGZzL1FtZGlqYllna1JiR29ZUW96TDJHZ2pwYUZmVmhHY0xlUFlHcUpaTFBHVUhFWVc/X2dsPTEqd29qZnA5Kl9nYSpNVGcyTWpNME16WXdNeTR4TmpZeE56VXpNalUzKl9nYV81Uk1QWEcxNFRFKk1UWTNOekV6TlRFek5pNDBMakV1TVRZM056RXpOVEUzTmk0eU1DNHdMakEuBQABAgAAAEAsB5V/CSisCqWsB43x06fwl67ofi7eh8fMbPgWe1A/ATIsLagmF5c4a6vJhMr74YTUznxniMs9+z4QYlMJ1x2b+AAyAAABCwgDAQICAAcNCAoRAQEAAAAAAAAADQQECQYACgyAlpgAAAAAAAk='; 14 | await signAndSendTransactionWithPrivateKeys( 15 | Network.Devnet, 16 | encodedTransaction, 17 | [privateKeyOne, privateKeyTwo] 18 | ); 19 | } catch (e: any) { 20 | expect(e.message).toBe( 21 | 'failed to send transaction: Transaction simulation failed: Blockhash not found' 22 | ); 23 | } 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /tests/marketplace-client.spec.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { ShyftSdk } from '@/index'; 3 | import { Network } from '@/types'; 4 | 5 | const shyft = new ShyftSdk({ 6 | apiKey: process.env.API_KEY as string, 7 | network: Network.Devnet, 8 | }); 9 | 10 | describe('Marketplace test', () => { 11 | beforeEach((): void => { 12 | jest.setTimeout(20000); 13 | }); 14 | 15 | it('create mp', async () => { 16 | const { encoded_transaction } = await shyft.marketplace.create({ 17 | creatorWallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 18 | }); 19 | expect(typeof encoded_transaction).toBe('string'); 20 | }); 21 | 22 | it('update mp', async () => { 23 | const { encoded_transaction } = await shyft.marketplace.update({ 24 | network: Network.Devnet, 25 | authorityWallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 26 | marketplaceAddress: '6FWpMCyaNV979duL5vUkhgAo87Gozcb6aHK2BsbynPrL', 27 | newAuthorityAddress: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 28 | }); 29 | expect(typeof encoded_transaction).toBe('string'); 30 | }); 31 | 32 | it('find mp', async () => { 33 | const { address } = await shyft.marketplace.find({ 34 | network: Network.Devnet, 35 | authorityAddress: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 36 | }); 37 | expect(typeof address).toBe('string'); 38 | }); 39 | 40 | it('fetch treasury balance of mp', async () => { 41 | const treasuryBalance = await shyft.marketplace.treasuryBalance({ 42 | network: Network.Devnet, 43 | marketplaceAddress: '6FWpMCyaNV979duL5vUkhgAo87Gozcb6aHK2BsbynPrL', 44 | }); 45 | expect(typeof treasuryBalance).toBe('object'); 46 | }); 47 | 48 | it('fetch stats of mp', async () => { 49 | const stats = await shyft.marketplace.stats({ 50 | network: Network.Devnet, 51 | marketplaceAddress: '6FWpMCyaNV979duL5vUkhgAo87Gozcb6aHK2BsbynPrL', 52 | startDate: new Date('2023-01-01T16:50:53.000Z'), 53 | }); 54 | expect(typeof stats.start_date).toBe('object'); 55 | }); 56 | 57 | it('fetch active listing', async () => { 58 | const activeListings = await shyft.marketplace.listing.active({ 59 | network: Network.Mainnet, 60 | marketplaceAddress: 'AxrRwpzk4T6BsWhttPwVCmfeEMbfbasv1QxVc5JhUfvB', 61 | sortBy: 'price', 62 | sortOrder: 'desc', 63 | page: 1, 64 | size: 2, 65 | }); 66 | expect(typeof activeListings).toBe('object'); 67 | }); 68 | 69 | it('fetch seller listings of mp', async () => { 70 | const listings = await shyft.marketplace.listing.bySeller({ 71 | network: Network.Mainnet, 72 | marketplaceAddress: 'AxrRwpzk4T6BsWhttPwVCmfeEMbfbasv1QxVc5JhUfvB', 73 | sellerAddress: '2ds8Azc8EC1MNyUWKBQrTsTKutyxg1wSvVFB9tthmd1J', 74 | }); 75 | expect(typeof listings[0].created_at).toBe('object'); 76 | }); 77 | 78 | it('list', async () => { 79 | const { encoded_transaction } = await shyft.marketplace.listing.list({ 80 | marketplaceAddress: 'dKtXyGgDGCyXiWtj9mbXUXk7ww996Uyc46CVt3ukJwV', 81 | nftAddress: '7Ros6azxoYakj3agxZetDwTWySftQeYXRXAKYWgXTWvw', 82 | price: 1, 83 | sellerWallet: '8hDQqsj9o2LwMk2FPBs7Rz5jPuzqKpRvkeeo6hMJm5Cv', 84 | }); 85 | expect(typeof encoded_transaction).toBe('string'); 86 | }); 87 | 88 | it('unlist', async () => { 89 | const encodedTransaction = await shyft.marketplace.listing.unlist({ 90 | marketplaceAddress: '5p4Bua5tSsSo1RJ94H1w5DiMSPfWcvMvnMVjPpZ6sJUb', 91 | listState: '9hXPhRfAYsR9fYcuPcDS36wimTRthVVjmX9NKgxub65G', 92 | sellerWallet: 'AaYFExyZuMHbJHzjimKyQBAH1yfA9sKTxSzBc6Nr5X4s', 93 | }); 94 | expect(typeof encodedTransaction).toBe('string'); 95 | }); 96 | 97 | it('buy', async () => { 98 | const { encoded_transaction, transaction_version, signers } = 99 | await shyft.marketplace.listing.buy({ 100 | network: Network.Devnet, 101 | marketplaceAddress: '5LSMwR5GLr4WjDHS5FoUXQCN5osZYHRoDGjmcEsS843B', 102 | nftAddress: '6PStcTWbV546dQCHbjVj4SP6Ht8e2eE4D8Mc951mH1yq', 103 | sellerWallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 104 | buyerWallet: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 105 | price: 0.3, 106 | serviceCharge: { 107 | receiver: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 108 | token: 'HtXwt7NchBTV7xoqmjhQJqEjSrApq5FNnB8rxYu6eC7k', 109 | amount: 0.01, 110 | }, 111 | }); 112 | 113 | expect(transaction_version).toBe(0); 114 | expect(signers.length).toBe(2); 115 | }); 116 | 117 | it('bid', async () => { 118 | const { encoded_transaction } = await shyft.marketplace.bidding.bid({ 119 | marketplaceAddress: '5LSMwR5GLr4WjDHS5FoUXQCN5osZYHRoDGjmcEsS843B', 120 | nftAddress: 'FSwx4c1qhuwr3YpBHHCneWkvo2R9uPuHoqjfr6qgixTh', 121 | price: 0.2, 122 | buyerWallet: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 123 | }); 124 | expect(typeof encoded_transaction).toBe('string'); 125 | }); 126 | 127 | it('cancel bid', async () => { 128 | const encodedTransaction = await shyft.marketplace.bidding.cancelBid({ 129 | marketplaceAddress: '5LSMwR5GLr4WjDHS5FoUXQCN5osZYHRoDGjmcEsS843B', 130 | bidState: 'Eqqwo4QTACNbvHMEHLyiTEdsc4qhNXts5effHCoqpg4e', 131 | buyerWallet: '3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf', 132 | }); 133 | expect(typeof encodedTransaction).toBe('string'); 134 | }); 135 | 136 | it('fetch active bids', async () => { 137 | const activeBidsResponse = await shyft.marketplace.bidding.active({ 138 | network: Network.Devnet, 139 | marketplaceAddress: '5LSMwR5GLr4WjDHS5FoUXQCN5osZYHRoDGjmcEsS843B', 140 | sortBy: 'bid_date', 141 | sortOrder: 'desc', 142 | page: 1, 143 | size: 2, 144 | }); 145 | expect(typeof activeBidsResponse).toBe('object'); 146 | }); 147 | 148 | it('accept bid', async () => { 149 | const response = await shyft.marketplace.bidding.acceptBid({ 150 | network: Network.Devnet, 151 | marketplaceAddress: '5LSMwR5GLr4WjDHS5FoUXQCN5osZYHRoDGjmcEsS843B', 152 | bidState: '3NNNdECW5qskMUev1bkcDeXAhbQ4Aeb2tj578KWbu7BJ', 153 | sellerWallet: '3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf', 154 | }); 155 | expect(typeof response.encoded_transaction).toBe('string'); 156 | }); 157 | }); 158 | -------------------------------------------------------------------------------- /tests/nft-client.spec.ts: -------------------------------------------------------------------------------- 1 | import { createReadStream } from 'fs'; 2 | import { resolve } from 'path'; 3 | import 'dotenv/config'; 4 | import { ShyftSdk } from '@/index'; 5 | import { 6 | CNftBurnManyResp, 7 | CNftBurnResponse, 8 | CNftMintResponse, 9 | CNftTransferManyResp, 10 | CNftTransferResponse, 11 | CreateMerkleTreeResponse, 12 | Network, 13 | NftMintAndOwner, 14 | } from '@/types'; 15 | 16 | const shyft = new ShyftSdk({ 17 | apiKey: process.env.API_KEY as string, 18 | network: Network.Devnet, 19 | }); 20 | 21 | describe('read NFT test', () => { 22 | it('read NFT', async () => { 23 | const nft = await shyft.nft.getNftByMint({ 24 | mint: '9K31Ti9VHH898VrZGRRXS5j1Zj8aTNAXJBcX8vHxTdDb', 25 | }); 26 | expect(typeof nft).toBe('object'); 27 | }); 28 | it('read paginated NFTs by owner', async () => { 29 | const result = await shyft.nft.getNftsByOwnerV2({ 30 | network: Network.Mainnet, 31 | owner: 'DBx5dQd9bCQUJfxAVjqXrinEsjVgPinjE3TXq1DZjytZ', 32 | page: 2, 33 | size: 7, 34 | }); 35 | console.log(result); 36 | expect(Array.isArray(result.nfts)).toBe(true); 37 | }); 38 | it('read selected NFTs', async () => { 39 | const nfts = await shyft.nft.getNftsByMintAddresses({ 40 | network: Network.Mainnet, 41 | mints: [ 42 | '3nd5gaYmu1oVdqthPrv8SWzvBaHgWPjn2npAGwPjhuQz', 43 | 'FW5peLvKtJhCykHqfA2E1GDGVCLsJrJYKkWPKXa8SZuL', 44 | 'Dx2XXfTUoTRsqRrBmB4dESraEeQy8Uby2XsLCtHW7GNS', 45 | ], 46 | tokenRecord: true, 47 | }); 48 | console.log(nfts); 49 | expect(Array.isArray(nfts)).toBe(true); 50 | }); 51 | it('create NFT', async () => { 52 | const response = await shyft.nft.createFromMetadata({ 53 | receiver: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 54 | metadataUri: 55 | 'https://gateway.pinata.cloud/ipfs/QmdijbYgkRbGoYQozL2GgjpaFfVhGcLePYGqJZLPGUHEYW?_gl=1*wojfp9*_ga*MTg2MjM0MzYwMy4xNjYxNzUzMjU3*_ga_5RMPXG14TE*MTY3NzEzNTEzNi40LjEuMTY3NzEzNTE3Ni4yMC4wLjA.', 56 | collectionAddress: 'AmeH6zUfie2gkFrT7ZZJcimsCCMhtk8PtTKD3Yxitvs5', 57 | }); 58 | expect(typeof response).toBe('object'); 59 | }); 60 | it('burn NFT', async () => { 61 | const encodedTxn = await shyft.nft.burn({ 62 | wallet: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 63 | mint: 'AmeH6zUfie2gkFrT7ZZJcimsCCMhtk8PtTKD3Yxitvs5', 64 | }); 65 | expect(typeof encodedTxn).toBe('string'); 66 | }); 67 | it('transfer multiple NFTs', async () => { 68 | const encodedTxns = await shyft.nft.transferMultiple({ 69 | fromAddress: 'BFefyp7jNF5Xq2A4JDLLFFGpxLq5oPEFKBAQ46KJHW2R', 70 | toAddress: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 71 | mints: [ 72 | '8pNjm9UmY6RhGQaLuCdtDt6uXhqXg5rFQX9t2oWq3PL1', 73 | 'CHqQS4sZCqKvkvgsKnar5FB4aQ1Ps6mLxBfdNjRgHYqS', 74 | ], 75 | }); 76 | expect(Array.isArray(encodedTxns)).toBe(true); 77 | }); 78 | it('create NFT', async () => { 79 | const { mint, encoded_transaction } = await shyft.nft.createV2({ 80 | creatorWallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 81 | name: 'JS ME', 82 | symbol: 'JSME', 83 | description: 'Test', 84 | attributes: [ 85 | { 86 | trait_type: 'sunday', 87 | value: 2, 88 | }, 89 | ], 90 | image: createReadStream( 91 | resolve(__dirname, '../assets/shyft_logo.png') 92 | ) as unknown as File, 93 | }); 94 | console.log(mint); 95 | console.log(encoded_transaction); 96 | expect(typeof mint).toBe('string'); 97 | }, 50000); 98 | it('update NFT', async () => { 99 | const { mint, encoded_transaction } = await shyft.nft.updateV2({ 100 | mint: 'FJwWVYH1UyL8PGwwnVcABviGJ8pfwwxSAbLNGkkWxjKP', 101 | updateAuthority: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 102 | name: 'JS ME', 103 | symbol: 'JSME', 104 | description: 'Test', 105 | attributes: [ 106 | { 107 | trait_type: 'sunday', 108 | value: 2, 109 | }, 110 | ], 111 | image: createReadStream( 112 | resolve(__dirname, '../assets/shyft_logo.png') 113 | ) as unknown as File, 114 | }); 115 | console.log(mint); 116 | console.log(encoded_transaction); 117 | expect(typeof mint).toBe('string'); 118 | }, 50000); 119 | it('burn many NFT', async () => { 120 | const encodedTransactions = await shyft.nft.burnMany({ 121 | wallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 122 | mints: [ 123 | 'CafGfg1bk66dMEy8FUmRNnQokJ6Be2uGXH75JpDesVcS', 124 | 'BouqF1CYN7L2GFrXTZ4LC3AKe4VhWi7JDy7G2fjTr79d', 125 | 'GpLzvmQYcQM3USH7RGehoriEzmJLJ8AfKVdLFZRoVBsz', 126 | '53pjayfUf7NjEFXRKuLTij3r6ujm7yPinGdTYm3chojA', 127 | 'FJwWVYH1UyL8PGwwnVcABviGJ8pfwwxSAbLNGkkWxjKP', 128 | '85q1xdTVMAF8S1MEF6yuJaSnyFhDjzVg8rgyppdt6bLn', 129 | '12BiLHRfcwRMYUwH6PAmRgPtSKkmBD8igytGqkKaBeha', 130 | 'GFxYSwRPWxDBdGex4DhrY2dStHPMn4UoocJyBAdfypsa', 131 | 'CHqQS4sZCqKvkvgsKnar5FB4aQ1Ps6mLxBfdNjRgHYqS', 132 | '3KhG4w7jmh8UaW7w9uQ9x6d4RbcVTJ1ZEgugDu22BJor', 133 | '6RXZGEmQmUJUXR4jKqb9xJ4dPGbviiHfXgpYHWs81fM8', 134 | '8pNjm9UmY6RhGQaLuCdtDt6uXhqXg5rFQX9t2oWq3PL1', 135 | '4CGQTpmh5QNbykCYZ8huq78RmVGqYF4bqs7T4qELVyS8', 136 | '9eEJEUwsuHDDXPv6LZf84xAYJM8wRkQt56zDB1oig5za', 137 | 'CHkwPfE51kSnx6wznJb27deW8Jb4AKGyfpLTQFvJUQLy', 138 | ], 139 | close: true, 140 | }); 141 | console.log(encodedTransactions); 142 | expect(encodedTransactions.length).toBe(2); 143 | }, 50000); 144 | it('fetch collection nfts', async () => { 145 | const collectionNfts = await shyft.nft.collection.getNfts({ 146 | network: Network.Mainnet, 147 | collectionAddress: '86vgk5fUs47pXrvh6iGdud2kg5XyBHV2XiZ16JPwCLKi', 148 | page: 1, 149 | size: 5, 150 | }); 151 | expect(collectionNfts.nfts.length).toBe(5); 152 | }, 50000); 153 | it('fetch nfts owners', async () => { 154 | const mintsAndOwners = await shyft.nft.getOwners({ 155 | network: Network.Devnet, 156 | mints: [ 157 | 'B9YonmJk175GfFCDMMKWDc18YpFgmHCmj2HQNkvvjeVw', 158 | 'BxMqbSQaccjZn2bVD13PzyP8DJ3BW3u1dEiD9WQmysGW', 159 | '85q1xdTVMAF8S1MEF6yuJaSnyFhDjzVg8rgyppdt6bLn', 160 | ], 161 | }); 162 | expect(mintsAndOwners.length).toBe(3); 163 | expect(mintsAndOwners[0]).toMatchObject({ 164 | nft_address: expect.any(String), 165 | owner: expect.any(String), 166 | }); 167 | }, 50000); 168 | it('create merkle tree', async () => { 169 | const merkleTreeResponse = await shyft.nft.compressed.createMerkleTree({ 170 | walletAddress: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 171 | maxDepthSizePair: { maxDepth: 14, maxBufferSize: 64 }, 172 | canopyDepth: 10, 173 | feePayer: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 174 | }); 175 | expect(merkleTreeResponse).toMatchObject({ 176 | encoded_transaction: expect.any(String), 177 | tree: expect.any(String), 178 | signers: expect.any(Array), 179 | }); 180 | }); 181 | it('compressed NFT mint', async () => { 182 | const mintResponse = await shyft.nft.compressed.mint({ 183 | creatorWallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 184 | merkleTree: 'DwoJS9LVDVL55Qa2TwvGG8MqNB5He4JtbnrHQ7JCrkcP', 185 | metadataUri: 186 | 'https://nftstorage.link/ipfs/bafkreigjxlfjhnpync5qmgv73yi4lqz4e65axwgpgqumvbopgvdrkwjpcm', 187 | collectionAddress: 'DgXdP7xA31HEviRKw6pk9Xj342dEWy8HFn1yjcsXZ9M9', 188 | receiver: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 189 | priorityFee: 100, 190 | }); 191 | expect(mintResponse).toMatchObject({ 192 | encoded_transaction: expect.any(String), 193 | transaction_version: expect.any(String), 194 | mint: expect.any(String), 195 | signers: expect.any(Array), 196 | }); 197 | }); 198 | it('compressed NFT transfer', async () => { 199 | const transferResponse = await shyft.nft.compressed.transfer({ 200 | network: Network.Devnet, 201 | mint: '6YQNj6b5N4WN6MmKvudF9toWquKgt4Uwpv25LhhJ7GW9', 202 | fromAddress: '3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf', 203 | toAddress: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 204 | feePayer: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 205 | }); 206 | expect(transferResponse).toMatchObject({ 207 | encoded_transaction: expect.any(String), 208 | transaction_version: expect.any(String), 209 | signers: expect.any(Array), 210 | }); 211 | }); 212 | it('multiple compressed NFTs transfer', async () => { 213 | const transferResponse = await shyft.nft.compressed.transferMany({ 214 | network: Network.Devnet, 215 | mints: [ 216 | 'J5eMJJzoyo3nxzQwHdLDreKF2RohPbbB8PZczePKUPYS', 217 | 'DDFWV1kCfx2Y2atUSu44YC7ibi8bCVBNexVHGEbomo5X', 218 | 'qBrB5WVi3FAcGMiyW1rRH3m9sGMftHu4qynUVCH4oa8', 219 | 'J5eMJJzoyo3nxzQwHdLDreKF2RohPbbB8PZczePKUPYS', 220 | ], 221 | fromAddress: '3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf', 222 | toAddress: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 223 | feePayer: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 224 | }); 225 | expect(transferResponse).toMatchObject({ 226 | encoded_transactions: expect.any(Array), 227 | signers: expect.any(Array), 228 | }); 229 | }); 230 | it('burn compressed NFT', async () => { 231 | const burnResponse = await shyft.nft.compressed.burn({ 232 | network: Network.Mainnet, 233 | mint: 'JDyXQ6Y6GAahgXPNN5LwTvyyGWpkNfiYy2KPQQ6WJrjs', 234 | walletAddress: '6YGVd8wdF76bbnZzNej6Ft6jgdYojakeLGSraXBm2jMq', 235 | }); 236 | expect(burnResponse).toMatchObject({ 237 | encoded_transaction: expect.any(String), 238 | transaction_version: expect.any(String), 239 | signers: expect.any(Array), 240 | }); 241 | }); 242 | it('read cNFT', async () => { 243 | const cNFT = await shyft.nft.compressed.read({ 244 | network: Network.Mainnet, 245 | mint: 'B1KpC7P66MUitUBjSEDtV1CM1rbYaEnRfpDjh7cB8QNf', 246 | }); 247 | expect(typeof cNFT).toBe('object'); 248 | }); 249 | it('fetch all cNFTs by a owner address', async () => { 250 | const cNFTs = await shyft.nft.compressed.readAll({ 251 | network: Network.Devnet, 252 | walletAddress: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 253 | collection: '5wwUuKaTrLWygZEQ1tCRC1uJFthG5pa17ArwpwApNAiw', 254 | }); 255 | expect(typeof cNFTs).toBe('object'); 256 | }, 50000); 257 | it('burn many compressed NFTs', async () => { 258 | const burnResponse = await shyft.nft.compressed.burnMany({ 259 | network: Network.Mainnet, 260 | mints: [ 261 | 'B1KpC7P66MUitUBjSEDtV1CM1rbYaEnRfpDjh7cB8QNf', 262 | '2ZBoTyJhmdWg8eNmxzcoRBopUyFAoV7XCnWW5WFMaTzv', 263 | '85hcC9Ga4r3cffpSWmSN81Lf6knyejeNmfd2Kj3UbTn7', 264 | ], 265 | walletAddress: '4u5iyKLBpHDMeyzjgLVQjEo2KLjiaGe7zRMib8J8Se4a', 266 | }); 267 | expect(burnResponse).toMatchObject({ 268 | encoded_transactions: expect.any(Array), 269 | }); 270 | }); 271 | 272 | it('read selected CNFTs', async () => { 273 | const nfts = await shyft.nft.compressed.readSelected({ 274 | network: Network.Mainnet, 275 | mints: [ 276 | '4S3Qv85aDvm3AoWgPiGb8BZByT63uQow9s5HgmrzkU5k', 277 | 'Fh2TN5zEoNeiaiGjy72SZ1uFUJLKsNDny1U56k15ia2r', 278 | 'DXiUUtHq5DqBZMyjADwRPARCsbY3UUhkpvYTjrJsLsys', 279 | 'FyZPRH1vbU12SxYx84GakunQFEXQ7dWJ34bFdx7CEe2J', 280 | '8VAWqL18XxnDY4USeCXtPq6MWU6nZZbqkqQMTqh3un94', 281 | 'HQh1WNX4G3VTJR6daAJNPoZGF7zk8SPTMeMB3vv4BDWn', 282 | 'A78wTE4tEuKLQJ8yxBmua7JNABuhLwsJaZZ72tr5HQ7L', 283 | '2b8zNXnMBZkwdUqYxq3sJzcKCizekwZWA3Q3fKLMd8nP', 284 | '6FCN7ibqo1N7GnR9Ho3ZCXbo73Jr9UxKDQEJDpiSvhzG', 285 | 'Di9MtELWic5tDBt3HHQwJDAnhhgfodgybkwJhWHNujxn', 286 | ], 287 | refresh: false, 288 | }); 289 | expect(Array.isArray(nfts)).toBe(true); 290 | }); 291 | }); 292 | -------------------------------------------------------------------------------- /tests/rpc-client.spec.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { ShyftSdk } from '@/index'; 3 | import { Network } from '@/types'; 4 | 5 | const shyft = new ShyftSdk({ 6 | apiKey: process.env.API_KEY as string, 7 | network: Network.Mainnet, 8 | }); 9 | 10 | describe('rpc test', () => { 11 | it('getRecentBlockHash', async () => { 12 | const response = await shyft.rpc.getLatestBlockhash('finalized'); 13 | expect(typeof response).toBe('object'); 14 | }, 50000); 15 | 16 | it('getAsset', async () => { 17 | const response = await shyft.rpc.getAsset({ 18 | id: 'DCwtwRu4abRHLeMHNRNudwBrSTbQqcGGUSfBLQzfX7A4', 19 | }); 20 | expect(response.compression?.leaf_id).toBe(37022); 21 | }, 50000); 22 | 23 | it('getAssetProof', async () => { 24 | const response = await shyft.rpc.getAssetProof({ 25 | id: 'DCwtwRu4abRHLeMHNRNudwBrSTbQqcGGUSfBLQzfX7A4', 26 | }); 27 | expect(Array.isArray(response.proof)).toBe(true); 28 | }, 50000); 29 | 30 | it('getAssetsByGroup', async () => { 31 | const response = await shyft.rpc.getAssetsByGroup({ 32 | groupKey: 'collection', 33 | groupValue: 'BxWpbnau1LfemNAoXuAe9Pbft59yz2egTxaMWtncGRfN', 34 | sortBy: { sortBy: 'created', sortDirection: 'asc' }, 35 | page: 1, 36 | limit: 1000, 37 | }); 38 | expect(Array.isArray(response.items)).toBe(true); 39 | }, 50000); 40 | 41 | it('getAssetsByOwner', async () => { 42 | const response = await shyft.rpc.getAssetsByOwner({ 43 | ownerAddress: 'EevH3LPRexR2431NSF6bCpBbPdQ2ViHbM1p84zujiEUs', 44 | page: 1, 45 | }); 46 | expect(Array.isArray(response.items)).toBe(true); 47 | }, 50000); 48 | 49 | it('getAssetsByCreator', async () => { 50 | const response = await shyft.rpc.getAssetsByCreator({ 51 | creatorAddress: 'EevH3LPRexR2431NSF6bCpBbPdQ2ViHbM1p84zujiEUs', 52 | page: 1, 53 | }); 54 | expect(Array.isArray(response.items)).toBe(true); 55 | }, 50000); 56 | 57 | it('getAssetsByAuthority', async () => { 58 | const response = await shyft.rpc.getAssetsByAuthority({ 59 | authorityAddress: 'EevH3LPRexR2431NSF6bCpBbPdQ2ViHbM1p84zujiEUs', 60 | page: 1, 61 | }); 62 | expect(Array.isArray(response.items)).toBe(true); 63 | }, 50000); 64 | 65 | it('searchAssets', async () => { 66 | const response = await shyft.rpc.searchAssets({ 67 | ownerAddress: 'EevH3LPRexR2431NSF6bCpBbPdQ2ViHbM1p84zujiEUs', 68 | creatorAddress: '2MiXcXedQeRc3bD5gTdvFsvLVi8pTyp298VdYNNghiN8', 69 | page: 1, 70 | limit: 10, 71 | }); 72 | expect(Array.isArray(response.items)).toBe(true); 73 | }, 50000); 74 | }); 75 | -------------------------------------------------------------------------------- /tests/semi-wallet-client.spec.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { ShyftSdk } from '@/index'; 3 | import { Network, WalletKeypair } from '@/types'; 4 | 5 | const shyft = new ShyftSdk({ 6 | apiKey: process.env.API_KEY as string, 7 | network: Network.Devnet, 8 | }); 9 | 10 | describe('Semi custodial wallet test', () => { 11 | it('create semi-custodial wallet', async () => { 12 | const wallet = await shyft.semiCustodialWallet.create({ 13 | password: 'XtbTyK@1', 14 | }); 15 | console.log(wallet); 16 | expect(typeof wallet).toBe('string'); 17 | }); 18 | 19 | it('get keypair of semi-custodial wallet', async () => { 20 | const wallet = await shyft.semiCustodialWallet.getKeypair({ 21 | password: 'XtbTyK@1', 22 | walletAddress: 'CEaeaWTKABRkUdmBXoG4NHgkXMqmLnuVBvEyq3D9zXhT', 23 | }); 24 | console.log(wallet); 25 | expect(wallet).toMatchObject({ 26 | publicKey: expect.any(String), 27 | secretKey: expect.any(String), 28 | }); 29 | }); 30 | 31 | it('change password of semi-custodial wallet', async () => { 32 | const isPasswordChanged = await shyft.semiCustodialWallet.changePassword({ 33 | currentPassword: 'XtbTyK@1', 34 | newPassword: 'Xtrakuo_@q2', 35 | walletAddress: 'CEaeaWTKABRkUdmBXoG4NHgkXMqmLnuVBvEyq3D9zXhT', 36 | }); 37 | console.log(isPasswordChanged); 38 | expect(isPasswordChanged).toBe(true); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/storage-client.spec.ts: -------------------------------------------------------------------------------- 1 | import { createReadStream } from 'fs'; 2 | import { resolve } from 'path'; 3 | import 'dotenv/config'; 4 | import { ShyftSdk } from '@/index'; 5 | import { IpfsUploadResponse, Network } from '@/types'; 6 | 7 | const shyft = new ShyftSdk({ 8 | apiKey: process.env.API_KEY as string, 9 | network: Network.Devnet, 10 | }); 11 | 12 | describe('Storage client test', () => { 13 | it.skip('upload asset', async () => { 14 | const response = await shyft.storage.uploadAsset({ 15 | file: createReadStream( 16 | resolve(__dirname, '../assets/shyft_logo.png') 17 | ) as unknown as File, 18 | }); 19 | console.log(response.uri); 20 | expect(response).toMatchObject({ 21 | uri: expect.any(String), 22 | cid: expect.any(String), 23 | }); 24 | }, 50000); 25 | 26 | it('craete metadata uri', async () => { 27 | const response = await shyft.storage.createMetadata({ 28 | creator: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 29 | image: 30 | 'https://nftstorage.link/ipfs/bafkreiajrjd7xozubfr7qk6xdktlo3k66jg6jkeamgjugd2p3w5w2pifve', 31 | name: 'Nirvana', 32 | symbol: 'NVN', 33 | description: 'This is a test NFT', 34 | attributes: [ 35 | { trait_type: 'anger', value: 0 }, 36 | { trait_type: 'calmness', value: 100 }, 37 | ], 38 | sellerFeeBasisPoints: 500, 39 | }); 40 | console.log(response.uri); 41 | expect(response).toMatchObject({ 42 | uri: expect.any(String), 43 | cid: expect.any(String), 44 | }); 45 | }, 50000); 46 | }); 47 | -------------------------------------------------------------------------------- /tests/token-client.spec.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { createReadStream } from 'fs'; 3 | import { resolve } from 'path'; 4 | import { ShyftSdk } from '@/index'; 5 | import { TokenApiResponse, Network, AirdropTokenResponse } from '@/types'; 6 | 7 | const shyft = new ShyftSdk({ 8 | apiKey: process.env.API_KEY as string, 9 | network: Network.Devnet, 10 | }); 11 | 12 | beforeEach((): void => { 13 | jest.setTimeout(20000); 14 | }); 15 | 16 | describe('token client test', () => { 17 | it('read token info', async () => { 18 | const tokenInfo = await shyft.token.getInfo({ 19 | network: Network.Devnet, 20 | tokenAddress: 'Gzs6vsQrGsH6MwF5vPXV2ghmxKa1Gh5zarR5whboUMFg', 21 | }); 22 | expect(typeof tokenInfo).toBe('object'); 23 | }); 24 | 25 | it('read token owners', async () => { 26 | try { 27 | const tokenOwners = await shyft.token.getOwners({ 28 | network: Network.Mainnet, 29 | tokenAddress: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', 30 | }); 31 | expect(typeof tokenOwners).toBe('object'); 32 | } catch (error) { 33 | expect(error).toBe( 34 | '[Error: This operation only available on mainnet-beta]' 35 | ); 36 | } 37 | }); 38 | 39 | it('create token', async () => { 40 | const result = await shyft.token.create({ 41 | network: Network.Devnet, 42 | creatorWallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 43 | name: 'Fast', 44 | symbol: 'FAST', 45 | description: 'Test token', 46 | decimals: 9, 47 | image: createReadStream( 48 | resolve(__dirname, '../assets/shyft_logo.png') 49 | ) as unknown as File, 50 | }); 51 | console.log(result); 52 | expect(result).toMatchObject({ 53 | encoded_transaction: expect.any(String), 54 | mint: expect.any(String), 55 | signers: expect.any(Array), 56 | }); 57 | }, 20000); 58 | 59 | it('mint token', async () => { 60 | const result = await shyft.token.mint({ 61 | network: Network.Devnet, 62 | mintAuthority: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 63 | receiver: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 64 | tokenAddress: '5QdFR7GPRv7v4MvpkskezXywZgYsAYRf7sB6tPtqLzwR', 65 | amount: 10000, 66 | feePayer: '3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf', 67 | }); 68 | console.log(result); 69 | expect(result).toMatchObject({ 70 | encoded_transaction: expect.any(String), 71 | mint: expect.any(String), 72 | signers: expect.any(Array), 73 | }); 74 | }, 20000); 75 | 76 | it('burn token', async () => { 77 | const result = await shyft.token.burn({ 78 | network: Network.Devnet, 79 | wallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 80 | tokenAddress: 'HtXwt7NchBTV7xoqmjhQJqEjSrApq5FNnB8rxYu6eC7k', 81 | amount: 10, 82 | feePayer: '3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf', 83 | }); 84 | console.log(result); 85 | expect(result).toMatchObject>({ 86 | encoded_transaction: expect.any(String), 87 | signers: expect.any(Array), 88 | }); 89 | }, 20000); 90 | 91 | it('transfer token', async () => { 92 | const result = await shyft.token.transfer({ 93 | network: Network.Devnet, 94 | fromAddress: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 95 | toAddress: '3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf', 96 | tokenAddress: 'HtXwt7NchBTV7xoqmjhQJqEjSrApq5FNnB8rxYu6eC7k', 97 | amount: 10, 98 | feePayer: '3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf', 99 | }); 100 | console.log(result); 101 | expect(result).toMatchObject>({ 102 | encoded_transaction: expect.any(String), 103 | signers: expect.any(Array), 104 | }); 105 | }, 20000); 106 | 107 | it('airdrop token', async () => { 108 | const result = await shyft.token.airdrop({ 109 | network: Network.Devnet, 110 | fromAddress: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 111 | tokenAddress: 'HtXwt7NchBTV7xoqmjhQJqEjSrApq5FNnB8rxYu6eC7k', 112 | transferTo: [ 113 | { 114 | toAddress: '3yTKSCKoDcjBFpbgxyJUh4cM1NG77gFXBimkVBx2hKrf', 115 | amount: 5, 116 | }, 117 | { 118 | toAddress: 'rexYt592c6v2MZKtLTXmWQt9zdEDdd3pZzbwSatHBrx', 119 | amount: 10, 120 | }, 121 | ], 122 | }); 123 | console.log(result); 124 | expect(result).toMatchObject({ 125 | encoded_transaction: expect.any(Array), 126 | signer: expect.any(String), 127 | }); 128 | }, 20000); 129 | }); 130 | -------------------------------------------------------------------------------- /tests/transaction-client.spec.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { ShyftSdk } from '@/index'; 3 | import { Network } from '@/types'; 4 | 5 | const shyft = new ShyftSdk({ 6 | apiKey: process.env.API_KEY as string, 7 | network: Network.Mainnet, 8 | }); 9 | 10 | describe('Transaction client test', () => { 11 | it('fetch parsed txn', async () => { 12 | const parsedTranaction = await shyft.transaction.parsed({ 13 | txnSignature: 14 | '3QBDUVjEhxuDbu2LL5FuwkUC1qsAVSayYYeCtBev8oi7XXnabxGirhyvkD7KTVKM3hBqpDnuxUQPo6kgZAXQtFRx', 15 | }); 16 | console.dir(parsedTranaction.timestamp, { depth: null }); 17 | expect(typeof parsedTranaction).toBe('object'); 18 | }); 19 | 20 | it('fetch raw txn', async () => { 21 | const rawTransaction = await shyft.transaction.raw({ 22 | txnSignature: 23 | '3QBDUVjEhxuDbu2LL5FuwkUC1qsAVSayYYeCtBev8oi7XXnabxGirhyvkD7KTVKM3hBqpDnuxUQPo6kgZAXQtFRx', 24 | }); 25 | expect(typeof rawTransaction).toBe('object'); 26 | }); 27 | 28 | it('fetch transaction history', async () => { 29 | const transactions = await shyft.transaction.history({ 30 | network: Network.Mainnet, 31 | account: 'JCFRaPv7852ESRwJJGRy2mysUMydXZgVVhrMLmExvmVp', 32 | enableRaw: false, 33 | enableEvents: true, 34 | }); 35 | console.dir(transactions[0].events, { depth: null }); 36 | expect(typeof transactions).toBe('object'); 37 | }); 38 | 39 | it('selected transactions fetch', async () => { 40 | const transactions = await shyft.transaction.parseSelected({ 41 | network: Network.Mainnet, 42 | transactionSignatues: [ 43 | '67BJFsmeLhpPWLonvXyi46dh4UszCHZCWecUznfAdGX2KGEJxxGCoTiKAixMgZsBJBGTUQsDNHf697BQLceRdfd9', 44 | '4NcmYNHKAdTFnm8y78wQoKMmGCedhTVFrWeHdEGYaThY4AYQ96fbeADY48B7XSgDz6RCZUDqnNPVWay34M18yXmg', 45 | ], 46 | enableRaw: false, 47 | enableEvents: true, 48 | }); 49 | expect(typeof transactions).toBe('object'); 50 | }); 51 | 52 | it('send transaction', async () => { 53 | const signature = await shyft.transaction.send({ 54 | network: Network.Devnet, 55 | encodedTransaction: 56 | 'ASCtcU9uCq3n1RanzUG0kn4tBX0jMBYJks1yruGSEpFR1zMKrTb6q4qttUGrx3Uq2/lCAbKCG6vZEvkwN9Q3xgkBAAEDGMqfUcVHHu2+lyU5gx9wU2rGxk2NoSXtodMSdev+DxVALAeVfwkorAqlrAeN8dOn8Jeu6H4u3ofHzGz4FntQPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWbWegET0pqstGQTS3s/zFAzsKX+EefI3qVuRYc29tMYBAgIAAQwCAAAAQEIPAAAAAAA=', 57 | }); 58 | expect(typeof signature).toBe('string'); 59 | }); 60 | 61 | it('send multiple transactions', async () => { 62 | const response = await shyft.transaction.sendMany({ 63 | network: Network.Devnet, 64 | encodedTransactions: [ 65 | 'AflRiSHDT+mkiqNqJ6MsY5cqITOcZ37+txZQqqYzazphSa/VBhFcFibFzi2rQ8IsaQkgBDHznqabolOzA/mNlAUBAAEDGMqfUcVHHu2+lyU5gx9wU2rGxk2NoSXtodMSdev+DxVALAeVfwkorAqlrAeN8dOn8Jeu6H4u3ofHzGz4FntQPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAR9uaAIxMGKAuXPKB9d9mAA3oMZ/GRc3kbW+YxugY/jcBAgIAAQwCAAAAMBsPAAAAAAA=', 66 | 'AW+cdqIs1kmQzpWS9YjXdx8eHqqv8IQsurH88P54ZUvbdysyCnsEpxEt2Y2xZ8+yCP/jj8hFyih8NZiWUqnb3QUBAAEDGMqfUcVHHu2+lyU5gx9wU2rGxk2NoSXtodMSdev+DxVALAeVfwkorAqlrAeN8dOn8Jeu6H4u3ofHzGz4FntQPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAR9uaAIxMGKAuXPKB9d9mAA3oMZ/GRc3kbW+YxugY/jcBAgIAAQwCAAAAQEIPAAAAAAA=', 67 | ], 68 | commitment: 'confirmed', 69 | }); 70 | expect(typeof response).toBe('object'); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /tests/txn-relayer-client.spec.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { ShyftSdk } from '@/index'; 3 | import { Network } from '@/types'; 4 | 5 | const shyft = new ShyftSdk({ 6 | apiKey: process.env.API_KEY as string, 7 | network: Network.Devnet, 8 | }); 9 | 10 | describe('Transaction Relayer client test', () => { 11 | it('get or create relay wallet', async () => { 12 | const wallet = await shyft.txnRelayer.getOrCreate(); 13 | expect(typeof wallet).toBe('string'); 14 | }); 15 | 16 | it('sign transaction', async () => { 17 | const signature = await shyft.txnRelayer.sign({ 18 | network: Network.Devnet, 19 | encodedTransaction: 20 | 'AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQACBc1gsUE/MCpM0cXSNRdR/uvKkw28CfT/UmFjeQO1P3RTZzmxZaKzGUFs516b0/MuXMRFJmwuLzL221Zha9e6/yURWkj1VBal8ukhGe3QD/WcUgoNM/i/hJJbB20A/LLeuAbd9uHXZaGT2cvhRs7reawctIXtX1s3kTqM9YV+/wCpxkeHlDF/XZwFVODF42SUvNKnI6z+t1y+mcP3Eqbp1vLLapjYzOUB2ZYSvnp0kYl1UD4ZcXxiFaQEf+mE1ygN6wEDBAEEAgAKDEBCDwAAAAAACQA=', 21 | }); 22 | expect(typeof signature).toBe('string'); 23 | }); 24 | 25 | it('sign multiple transactions', async () => { 26 | const response = await shyft.txnRelayer.signMany({ 27 | network: Network.Devnet, 28 | encodedTransactions: [ 29 | 'AflRiSHDT+mkiqNqJ6MsY5cqITOcZ37+txZQqqYzazphSa/VBhFcFibFzi2rQ8IsaQkgBDHznqabolOzA/mNlAUBAAEDGMqfUcVHHu2+lyU5gx9wU2rGxk2NoSXtodMSdev+DxVALAeVfwkorAqlrAeN8dOn8Jeu6H4u3ofHzGz4FntQPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAR9uaAIxMGKAuXPKB9d9mAA3oMZ/GRc3kbW+YxugY/jcBAgIAAQwCAAAAMBsPAAAAAAA=', 30 | 'AW+cdqIs1kmQzpWS9YjXdx8eHqqv8IQsurH88P54ZUvbdysyCnsEpxEt2Y2xZ8+yCP/jj8hFyih8NZiWUqnb3QUBAAEDGMqfUcVHHu2+lyU5gx9wU2rGxk2NoSXtodMSdev+DxVALAeVfwkorAqlrAeN8dOn8Jeu6H4u3ofHzGz4FntQPwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAR9uaAIxMGKAuXPKB9d9mAA3oMZ/GRc3kbW+YxugY/jcBAgIAAQwCAAAAQEIPAAAAAAA=', 31 | ], 32 | commitment: 'confirmed', 33 | }); 34 | expect(typeof response).toBe('object'); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /tests/wallet-client.spec.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | import { ShyftSdk } from '@/index'; 3 | import { Network, Domain, TokenBalance } from '@/types'; 4 | 5 | const shyft = new ShyftSdk({ 6 | apiKey: process.env.API_KEY as string, 7 | network: Network.Devnet, 8 | }); 9 | 10 | describe('wallet client tests', () => { 11 | it('wallet balance check', async () => { 12 | const balance = await shyft.wallet.getBalance({ 13 | wallet: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 14 | }); 15 | expect(typeof balance).toBe('number'); 16 | }); 17 | 18 | it('send SOL check', async () => { 19 | const encodedTransaction = await shyft.wallet.sendSol({ 20 | fromAddress: '5KW2twHzRsAaiLeEx4zYNV35CV2hRrZGw7NYbwMfL4a2', 21 | toAddress: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 22 | amount: 1, 23 | }); 24 | expect(typeof encodedTransaction).toBe('string'); 25 | }); 26 | 27 | it('get domains test', async () => { 28 | const domains = await shyft.wallet.getDomains({ 29 | network: Network.Mainnet, 30 | wallet: '9hqqMGMfG44L2R1a1osDgQRWKYt4YuegfUB6rUSaXrv8', 31 | }); 32 | domains.map((domain) => { 33 | expect(domain).toMatchObject({ 34 | address: expect.any(String), 35 | name: expect.any(String), 36 | }); 37 | }); 38 | }); 39 | 40 | it('token balance check', async () => { 41 | const tokenBalance = await shyft.wallet.getTokenBalance({ 42 | wallet: '2fmz8SuNVyxEP6QwKQs6LNaT2ATszySPEJdhUDesxktc', 43 | token: '1C3n35poNbm2di6W8YTKjG2BmhaFxmTtbScy1ox2xvY', 44 | }); 45 | expect(tokenBalance).toMatchObject< 46 | Omit & { isFrozen: boolean } 47 | >({ 48 | address: expect.any(String), 49 | balance: expect.any(Number), 50 | info: expect.any(Object), 51 | isFrozen: expect.any(Boolean), 52 | }); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2015", 5 | "module": "CommonJS", 6 | "outDir": "dist/cjs", 7 | "rootDir": "src" 8 | }, 9 | "exclude": [ 10 | "tests" 11 | ] 12 | } -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "target": "ES2018", 5 | "outDir": "dist/esm", 6 | "rootDir": "src" 7 | }, 8 | "exclude": [ 9 | "tests" 10 | ] 11 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "src", 4 | "tests" 5 | ], 6 | "compilerOptions": { 7 | "baseUrl": ".", 8 | "rootDir": ".", 9 | "target": "es2018", 10 | "moduleResolution": "node", 11 | "resolveJsonModule": true, 12 | "declaration": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "noImplicitAny": false, 16 | "esModuleInterop": true, 17 | "paths": { 18 | "@/*": [ 19 | "src/*" 20 | ] 21 | } 22 | }, 23 | "ts-node": { 24 | "require": [ 25 | "tsconfig-paths/register" 26 | ], 27 | "compilerOptions": { 28 | "module": "commonjs" 29 | } 30 | }, 31 | "typedocOptions": { 32 | "entryPoints": [ 33 | "src/index.ts" 34 | ], 35 | "out": "docs" 36 | } 37 | } --------------------------------------------------------------------------------