mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-04 13:05:49 +00:00
feat: NFT support (#1829)
* add NFTokenBurn and NFTokenMint * add NFTokenCreateOffer * add NFTokenCancelOffer * add NFTokenAcceptOffer * add requests and responses * make a beta 2.1.0 * rename TokenIDs to TokenOffers * add validations and fixup docs * add tests * add missing error codes to binary codec * adds history changes
This commit is contained in:
2
package-lock.json
generated
2
package-lock.json
generated
@@ -2803,7 +2803,6 @@
|
||||
},
|
||||
"node_modules/@types/create-hash": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/create-hash/-/create-hash-1.2.2.tgz",
|
||||
"integrity": "sha512-Fg8/kfMJObbETFU/Tn+Y0jieYewryLrbKwLCEIwPyklZZVY2qB+64KFjhplGSw+cseZosfFXctXO+PyIYD8iZQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
@@ -18861,7 +18860,6 @@
|
||||
},
|
||||
"@types/create-hash": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/create-hash/-/create-hash-1.2.2.tgz",
|
||||
"integrity": "sha512-Fg8/kfMJObbETFU/Tn+Y0jieYewryLrbKwLCEIwPyklZZVY2qB+64KFjhplGSw+cseZosfFXctXO+PyIYD8iZQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# ripple-binary-codec Release History
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
- Exported `TRANSACTION_TYPES` value
|
||||
### Fixed
|
||||
- Adds missing fields from XLS-20 NFT implementation
|
||||
|
||||
## 1.2.2 (2021-12-2)
|
||||
- Fix issue where unsupported currency codes weren't being correctly processed
|
||||
- Added a workaround for rippled UNLModify encoding bug (#1830)
|
||||
|
||||
@@ -855,6 +855,16 @@
|
||||
"type": "Amount"
|
||||
}
|
||||
],
|
||||
[
|
||||
"BrokerFee",
|
||||
{
|
||||
"nth": 19,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "Amount"
|
||||
}
|
||||
],
|
||||
[
|
||||
"taker_gets_funded",
|
||||
{
|
||||
@@ -1536,7 +1546,7 @@
|
||||
}
|
||||
],
|
||||
[
|
||||
"TokenIDs",
|
||||
"TokenOffers",
|
||||
{
|
||||
"nth": 4,
|
||||
"isVLEncoded": true,
|
||||
@@ -1856,6 +1866,8 @@
|
||||
"tefBAD_AUTH_MASTER": -183,
|
||||
"tefINVARIANT_FAILED": -182,
|
||||
"tefTOO_BIG": -181,
|
||||
"tefNO_TICKET": -180,
|
||||
"tefTOKEN_IS_NOT_TRANSFERABLE": -179,
|
||||
|
||||
"terRETRY": -99,
|
||||
"terFUNDS_SPENT": -98,
|
||||
|
||||
@@ -3,6 +3,13 @@ import { SerializedType } from '../types/serialized-type'
|
||||
import { Buffer } from 'buffer/'
|
||||
import { BytesList } from '../binary'
|
||||
|
||||
/*
|
||||
* @brief: All valid transaction types
|
||||
*/
|
||||
export const TRANSACTION_TYPES = Object.entries(enums.TRANSACTION_TYPES)
|
||||
.filter(([_key, value]) => value >= 0)
|
||||
.map(([key, _value]) => key)
|
||||
|
||||
const TYPE_WIDTH = 2
|
||||
const LEDGER_ENTRY_WIDTH = 2
|
||||
const TRANSACTION_TYPE_WIDTH = 2
|
||||
|
||||
@@ -3,6 +3,8 @@ import { quality, binary } from './coretypes'
|
||||
import { decodeLedgerData } from './ledger-hashes'
|
||||
import { ClaimObject } from './binary'
|
||||
import { JsonObject } from './types/serialized-type'
|
||||
import { TRANSACTION_TYPES } from './enums'
|
||||
|
||||
const {
|
||||
signingData,
|
||||
signingClaimData,
|
||||
@@ -109,4 +111,5 @@ export = {
|
||||
encodeQuality,
|
||||
decodeQuality,
|
||||
decodeLedgerData,
|
||||
TRANSACTION_TYPES,
|
||||
}
|
||||
|
||||
@@ -328,7 +328,7 @@
|
||||
"LastLedgerSequence": 27,
|
||||
"Sequence": 10,
|
||||
"SigningPubKey": "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
|
||||
"TokenIDs": [
|
||||
"TokenOffers": [
|
||||
"00090032B5F762798A53D543A014CAF8B297CFF8F2F937E80000099B00000000"
|
||||
],
|
||||
"TransactionType": "NFTokenCancelOffer",
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xrpl-announce) for release announcements. We recommend that xrpl.js (ripple-lib) users stay up-to-date with the latest stable release.
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
* Support for the [XLS-20 NFT proposal](https://github.com/XRPLF/XRPL-Standards/discussions/46)
|
||||
|
||||
## 2.0.3 (2021-12-1)
|
||||
* Removes requirement for npm version > 7 for non-contributors
|
||||
* For contributors -
|
||||
|
||||
@@ -65,3 +65,18 @@ export interface SignerEntry {
|
||||
SignerWeight: number
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* One offer that might be returned from either an {@link NFTBuyOffersRequest}
|
||||
* or an {@link NFTSellOffersRequest}.
|
||||
*
|
||||
* @category Responses
|
||||
*/
|
||||
export interface NFTOffer {
|
||||
amount: Amount
|
||||
flags: number
|
||||
index: string
|
||||
owner: string
|
||||
destination?: string
|
||||
expiration?: number
|
||||
}
|
||||
|
||||
71
packages/xrpl/src/models/methods/accountNFTs.ts
Normal file
71
packages/xrpl/src/models/methods/accountNFTs.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { BaseRequest, BaseResponse } from './baseMethod'
|
||||
|
||||
/**
|
||||
* The `account_nfts` method retrieves all of the NFTs currently owned by the
|
||||
* specified account.
|
||||
*
|
||||
* @category Requests
|
||||
*/
|
||||
export interface AccountNFTsRequest extends BaseRequest {
|
||||
command: 'account_nfts'
|
||||
/**
|
||||
* The unique identifier of an account, typically the account's address. The
|
||||
* request returns NFTs owned by this account.
|
||||
*/
|
||||
account: string
|
||||
/**
|
||||
* Limit the number of NFTokens to retrieve.
|
||||
*/
|
||||
limit?: number
|
||||
/**
|
||||
* Value from a previous paginated response. Resume retrieving data where
|
||||
* that response left off.
|
||||
*/
|
||||
marker?: unknown
|
||||
}
|
||||
|
||||
/**
|
||||
* One NFToken that might be returned from an {@link AccountNFTsRequest}.
|
||||
*
|
||||
* @category Responses
|
||||
*/
|
||||
interface AccountNFToken {
|
||||
Flags: number
|
||||
Issuer: string
|
||||
TokenID: string
|
||||
TokenTaxon: number
|
||||
nft_serial: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Response expected from an {@link AccountNFTsRequest}.
|
||||
*
|
||||
* @category Responses
|
||||
*/
|
||||
export interface AccountNFTsResponse extends BaseResponse {
|
||||
result: {
|
||||
/**
|
||||
* The account requested.
|
||||
*/
|
||||
account: string
|
||||
/**
|
||||
* A list of NFTs owned by the specified account.
|
||||
*/
|
||||
account_nfts: AccountNFToken[]
|
||||
/**
|
||||
* The ledger index of the current open ledger, which was used when
|
||||
* retrieving this information.
|
||||
*/
|
||||
ledger_current_index: number
|
||||
/** If true, this data comes from a validated ledger. */
|
||||
validated: boolean
|
||||
/**
|
||||
* Server-defined value indicating the response is paginated. Pass this to
|
||||
* the next call to resume where this call left off. Omitted when there are
|
||||
* No additional pages after this one.
|
||||
*/
|
||||
marker?: unknown
|
||||
/** The limit that was used to fulfill this request. */
|
||||
limit?: number
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
} from './accountCurrencies'
|
||||
import { AccountInfoRequest, AccountInfoResponse } from './accountInfo'
|
||||
import { AccountLinesRequest, AccountLinesResponse } from './accountLines'
|
||||
import { AccountNFTsRequest, AccountNFTsResponse } from './accountNFTs'
|
||||
import { AccountObjectsRequest, AccountObjectsResponse } from './accountObjects'
|
||||
import { AccountOffersRequest, AccountOffersResponse } from './accountOffers'
|
||||
import { AccountTxRequest, AccountTxResponse } from './accountTx'
|
||||
@@ -29,6 +30,8 @@ import { LedgerCurrentRequest, LedgerCurrentResponse } from './ledgerCurrent'
|
||||
import { LedgerDataRequest, LedgerDataResponse } from './ledgerData'
|
||||
import { LedgerEntryRequest, LedgerEntryResponse } from './ledgerEntry'
|
||||
import { ManifestRequest, ManifestResponse } from './manifest'
|
||||
import { NFTBuyOffersRequest, NFTBuyOffersResponse } from './nftBuyOffers'
|
||||
import { NFTSellOffersRequest, NFTSellOffersResponse } from './nftSellOffers'
|
||||
import { NoRippleCheckRequest, NoRippleCheckResponse } from './norippleCheck'
|
||||
import {
|
||||
PathFindRequest,
|
||||
@@ -70,10 +73,12 @@ import { UnsubscribeRequest, UnsubscribeResponse } from './unsubscribe'
|
||||
* @category Requests
|
||||
*/
|
||||
type Request =
|
||||
// account methods
|
||||
| AccountChannelsRequest
|
||||
| AccountCurrenciesRequest
|
||||
| AccountInfoRequest
|
||||
| AccountLinesRequest
|
||||
| AccountNFTsRequest
|
||||
| AccountObjectsRequest
|
||||
| AccountOffersRequest
|
||||
| AccountTxRequest
|
||||
@@ -108,15 +113,20 @@ type Request =
|
||||
// utility methods
|
||||
| PingRequest
|
||||
| RandomRequest
|
||||
// NFT methods
|
||||
| NFTBuyOffersRequest
|
||||
| NFTSellOffersRequest
|
||||
|
||||
/**
|
||||
* @category Responses
|
||||
*/
|
||||
type Response =
|
||||
// account methods
|
||||
| AccountChannelsResponse
|
||||
| AccountCurrenciesResponse
|
||||
| AccountInfoResponse
|
||||
| AccountLinesResponse
|
||||
| AccountNFTsResponse
|
||||
| AccountObjectsResponse
|
||||
| AccountOffersResponse
|
||||
| AccountTxResponse
|
||||
@@ -151,6 +161,9 @@ type Response =
|
||||
// utility methods
|
||||
| PingResponse
|
||||
| RandomResponse
|
||||
// NFT methods
|
||||
| NFTBuyOffersResponse
|
||||
| NFTSellOffersResponse
|
||||
|
||||
export {
|
||||
Request,
|
||||
@@ -164,6 +177,8 @@ export {
|
||||
AccountInfoResponse,
|
||||
AccountLinesRequest,
|
||||
AccountLinesResponse,
|
||||
AccountNFTsRequest,
|
||||
AccountNFTsResponse,
|
||||
AccountObjectsRequest,
|
||||
AccountObjectsResponse,
|
||||
AccountOffersRequest,
|
||||
@@ -238,4 +253,9 @@ export {
|
||||
RandomRequest,
|
||||
RandomResponse,
|
||||
ErrorResponse,
|
||||
// NFT methods
|
||||
NFTBuyOffersRequest,
|
||||
NFTBuyOffersResponse,
|
||||
NFTSellOffersRequest,
|
||||
NFTSellOffersResponse,
|
||||
}
|
||||
|
||||
35
packages/xrpl/src/models/methods/nftBuyOffers.ts
Normal file
35
packages/xrpl/src/models/methods/nftBuyOffers.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { NFTOffer } from '../common'
|
||||
|
||||
import { BaseRequest, BaseResponse } from './baseMethod'
|
||||
|
||||
/**
|
||||
* The `nft_buy_offers` method retrieves all of buy offers for the specified
|
||||
* NFToken.
|
||||
*
|
||||
* @category Requests
|
||||
*/
|
||||
export interface NFTBuyOffersRequest extends BaseRequest {
|
||||
command: 'nft_buy_offers'
|
||||
/**
|
||||
* The unique identifier of an NFToken. The request returns buy offers for this NFToken.
|
||||
*/
|
||||
tokenid: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Response expected from an {@link NFTBuyOffersRequest}.
|
||||
*
|
||||
* @category Responses
|
||||
*/
|
||||
export interface NFTBuyOffersResponse extends BaseResponse {
|
||||
result: {
|
||||
/**
|
||||
* A list of buy offers for the specified NFToken.
|
||||
*/
|
||||
offers: NFTOffer[]
|
||||
/**
|
||||
* The token ID of the NFToken to which these offers pertain.
|
||||
*/
|
||||
tokenid: string
|
||||
}
|
||||
}
|
||||
35
packages/xrpl/src/models/methods/nftSellOffers.ts
Normal file
35
packages/xrpl/src/models/methods/nftSellOffers.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { NFTOffer } from '../common'
|
||||
|
||||
import { BaseRequest, BaseResponse } from './baseMethod'
|
||||
|
||||
/**
|
||||
* The `nft_sell_offers` method retrieves all of sell offers for the specified
|
||||
* NFToken.
|
||||
*
|
||||
* @category Requests
|
||||
*/
|
||||
export interface NFTSellOffersRequest extends BaseRequest {
|
||||
command: 'nft_sell_offers'
|
||||
/**
|
||||
* The unique identifier of an NFToken. The request returns sell offers for this NFToken.
|
||||
*/
|
||||
tokenid: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Response expected from an {@link NFTSellOffersRequest}.
|
||||
*
|
||||
* @category Responses
|
||||
*/
|
||||
export interface NFTSellOffersResponse extends BaseResponse {
|
||||
result: {
|
||||
/**
|
||||
* A list of sell offers for the specified NFToken.
|
||||
*/
|
||||
offers: NFTOffer[]
|
||||
/**
|
||||
* The token ID of the NFToken to which these offers pertain.
|
||||
*/
|
||||
tokenid: string
|
||||
}
|
||||
}
|
||||
104
packages/xrpl/src/models/transactions/NFTokenAcceptOffer.ts
Normal file
104
packages/xrpl/src/models/transactions/NFTokenAcceptOffer.ts
Normal file
@@ -0,0 +1,104 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
import { Amount } from '../common'
|
||||
|
||||
import {
|
||||
BaseTransaction,
|
||||
parseAmountValue,
|
||||
validateBaseTransaction,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
* The NFTokenOfferAccept transaction is used to accept offers
|
||||
* to buy or sell an NFToken. It can either:
|
||||
*
|
||||
* 1. Allow one offer to be accepted. This is called direct
|
||||
* mode.
|
||||
* 2. Allow two distinct offers, one offering to buy a
|
||||
* given NFToken and the other offering to sell the same
|
||||
* NFToken, to be accepted in an atomic fashion. This is
|
||||
* called brokered mode.
|
||||
*
|
||||
* To indicate direct mode, use either the `sell_offer` or
|
||||
* `buy_offer` fields, but not both. To indicate brokered mode,
|
||||
* use both the `sell_offer` and `buy_offer` fields. If you use
|
||||
* neither `sell_offer` nor `buy_offer`, the transaction is invalid.
|
||||
*/
|
||||
export interface NFTokenAcceptOffer extends BaseTransaction {
|
||||
TransactionType: 'NFTokenAcceptOffer'
|
||||
/**
|
||||
* Identifies the NFTokenOffer that offers to sell the NFToken.
|
||||
*
|
||||
* In direct mode this field is optional, but either SellOffer or
|
||||
* BuyOffer must be specified. In brokered mode, both SellOffer
|
||||
* and BuyOffer must be specified.
|
||||
*/
|
||||
SellOffer?: string
|
||||
/**
|
||||
* Identifies the NFTokenOffer that offers to buy the NFToken.
|
||||
*
|
||||
* In direct mode this field is optional, but either SellOffer or
|
||||
* BuyOffer must be specified. In brokered mode, both SellOffer
|
||||
* and BuyOffer must be specified.
|
||||
*/
|
||||
BuyOffer?: string
|
||||
/**
|
||||
* This field is only valid in brokered mode. It specifies the
|
||||
* amount that the broker will keep as part of their fee for
|
||||
* bringing the two offers together; the remaining amount will
|
||||
* be sent to the seller of the NFToken being bought. If
|
||||
* specified, the fee must be such that, prior to accounting
|
||||
* for the transfer fee charged by the issuer, the amount that
|
||||
* the seller would receive is at least as much as the amount
|
||||
* indicated in the sell offer.
|
||||
*
|
||||
* This functionality is intended to allow the owner of an
|
||||
* NFToken to offer their token for sale to a third party
|
||||
* broker, who may then attempt to sell the NFToken on for a
|
||||
* larger amount, without the broker having to own the NFToken
|
||||
* or custody funds.
|
||||
*
|
||||
* Note: in brokered mode, the offers referenced by BuyOffer
|
||||
* and SellOffer must both specify the same TokenID; that is,
|
||||
* both must be for the same NFToken.
|
||||
*/
|
||||
BrokerFee?: Amount
|
||||
}
|
||||
|
||||
function validateBrokerFee(tx: Record<string, unknown>): void {
|
||||
const value = parseAmountValue(tx.BrokerFee)
|
||||
if (Number.isNaN(value)) {
|
||||
throw new ValidationError('NFTokenAcceptOffer: invalid BrokerFee')
|
||||
}
|
||||
|
||||
if (value <= 0) {
|
||||
throw new ValidationError(
|
||||
'NFTokenAcceptOffer: BrokerFee must be greater than 0; omit if there is no fee',
|
||||
)
|
||||
}
|
||||
|
||||
if (tx.SellOffer == null || tx.BuyOffer == null) {
|
||||
throw new ValidationError(
|
||||
'NFTokenAcceptOffer: both SellOffer and BuyOffer must be set if using brokered mode',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of an NFTokenAcceptOffer at runtime.
|
||||
*
|
||||
* @param tx - An NFTokenAcceptOffer Transaction.
|
||||
* @throws When the NFTokenAcceptOffer is Malformed.
|
||||
*/
|
||||
export function validateNFTokenAcceptOffer(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
if (tx.BrokerFee != null) {
|
||||
validateBrokerFee(tx)
|
||||
}
|
||||
|
||||
if (tx.SellOffer == null && tx.BuyOffer == null) {
|
||||
throw new ValidationError(
|
||||
'NFTokenAcceptOffer: must set either SellOffer or BuyOffer',
|
||||
)
|
||||
}
|
||||
}
|
||||
42
packages/xrpl/src/models/transactions/NFTokenBurn.ts
Normal file
42
packages/xrpl/src/models/transactions/NFTokenBurn.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
|
||||
import { BaseTransaction, validateBaseTransaction } from './common'
|
||||
|
||||
/**
|
||||
* The NFTokenBurn transaction is used to remove an NFToken object from the
|
||||
* NFTokenPage in which it is being held, effectively removing the token from
|
||||
* the ledger ("burning" it).
|
||||
*
|
||||
* If this operation succeeds, the corresponding NFToken is removed. If this
|
||||
* operation empties the NFTokenPage holding the NFToken or results in the
|
||||
* consolidation, thus removing an NFTokenPage, the owner’s reserve requirement
|
||||
* is reduced by one.
|
||||
*/
|
||||
export interface NFTokenBurn extends BaseTransaction {
|
||||
TransactionType: 'NFTokenBurn'
|
||||
/**
|
||||
* Indicates the AccountID that submitted this transaction. The account MUST
|
||||
* be either the present owner of the token or, if the lsfBurnable flag is set
|
||||
* in the NFToken, either the issuer account or an account authorized by the
|
||||
* issuer, i.e. MintAccount.
|
||||
*/
|
||||
Account: string
|
||||
/**
|
||||
* Identifies the NFToken object to be removed by the transaction.
|
||||
*/
|
||||
TokenID: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of an NFTokenBurn at runtime.
|
||||
*
|
||||
* @param tx - An NFTokenBurn Transaction.
|
||||
* @throws When the NFTokenBurn is Malformed.
|
||||
*/
|
||||
export function validateNFTokenBurn(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
if (tx.TokenID == null) {
|
||||
throw new ValidationError('NFTokenBurn: missing field TokenID')
|
||||
}
|
||||
}
|
||||
45
packages/xrpl/src/models/transactions/NFTokenCancelOffer.ts
Normal file
45
packages/xrpl/src/models/transactions/NFTokenCancelOffer.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
|
||||
import { BaseTransaction, validateBaseTransaction } from './common'
|
||||
|
||||
/**
|
||||
* The NFTokenCancelOffer transaction deletes existing NFTokenOffer objects.
|
||||
* It is useful if you want to free up space on your account to lower your
|
||||
* reserve requirement.
|
||||
*
|
||||
* The transaction can be executed by the account that originally created
|
||||
* the NFTokenOffer, the account in the `Recipient` field of the NFTokenOffer
|
||||
* (if present), or any account if the NFTokenOffer has an `Expiration` and
|
||||
* the NFTokenOffer has already expired.
|
||||
*/
|
||||
export interface NFTokenCancelOffer extends BaseTransaction {
|
||||
TransactionType: 'NFTokenCancelOffer'
|
||||
/**
|
||||
* An array of identifiers of NFTokenOffer objects that should be cancelled
|
||||
* by this transaction.
|
||||
*
|
||||
* It is an error if an entry in this list points to an
|
||||
* object that is not an NFTokenOffer object. It is not an
|
||||
* error if an entry in this list points to an object that
|
||||
* does not exist. This field is required.
|
||||
*/
|
||||
TokenOffers: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of an NFTokenCancelOffer at runtime.
|
||||
*
|
||||
* @param tx - An NFTokenCancelOffer Transaction.
|
||||
* @throws When the NFTokenCancelOffer is Malformed.
|
||||
*/
|
||||
export function validateNFTokenCancelOffer(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
if (!Array.isArray(tx.TokenOffers)) {
|
||||
throw new ValidationError('NFTokenCancelOffer: missing field TokenOffers')
|
||||
}
|
||||
|
||||
if (tx.TokenOffers.length < 1) {
|
||||
throw new ValidationError('NFTokenCancelOffer: empty field TokenOffers')
|
||||
}
|
||||
}
|
||||
145
packages/xrpl/src/models/transactions/NFTokenCreateOffer.ts
Normal file
145
packages/xrpl/src/models/transactions/NFTokenCreateOffer.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
import { Amount } from '../common'
|
||||
import { isFlagEnabled } from '../utils'
|
||||
|
||||
import {
|
||||
BaseTransaction,
|
||||
GlobalFlags,
|
||||
validateBaseTransaction,
|
||||
isAmount,
|
||||
parseAmountValue,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
* Transaction Flags for an NFTokenCreateOffer Transaction.
|
||||
*
|
||||
* @category Transaction Flags
|
||||
*/
|
||||
export enum NFTokenCreateOfferFlags {
|
||||
/**
|
||||
* If set, indicates that the offer is a sell offer.
|
||||
* Otherwise, it is a buy offer.
|
||||
*/
|
||||
tfSellToken = 0x00000001,
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of flags to boolean values representing {@link NFTokenCreateOffer} transaction
|
||||
* flags.
|
||||
*
|
||||
* @category Transaction Flags
|
||||
*/
|
||||
export interface NFTokenCreateOfferFlagsInterface extends GlobalFlags {
|
||||
tfSellToken?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* The NFTokenCreateOffer transaction creates either an offer to buy an
|
||||
* NFT the submitting account does not own, or an offer to sell an NFT
|
||||
* the submitting account does own.
|
||||
*/
|
||||
export interface NFTokenCreateOffer extends BaseTransaction {
|
||||
TransactionType: 'NFTokenCreateOffer'
|
||||
/**
|
||||
* Identifies the TokenID of the NFToken object that the
|
||||
* offer references.
|
||||
*/
|
||||
TokenID: string
|
||||
/**
|
||||
* Indicates the amount expected or offered for the Token.
|
||||
*
|
||||
* The amount must be non-zero, except when this is a sell
|
||||
* offer and the asset is XRP. This would indicate that the current
|
||||
* owner of the token is giving it away free, either to anyone at all,
|
||||
* or to the account identified by the Destination field.
|
||||
*/
|
||||
Amount: Amount
|
||||
/**
|
||||
* Indicates the AccountID of the account that owns the
|
||||
* corresponding NFToken.
|
||||
*
|
||||
* If the offer is to buy a token, this field must be present
|
||||
* and it must be different than Account (since an offer to
|
||||
* buy a token one already holds is meaningless).
|
||||
*
|
||||
* If the offer is to sell a token, this field must not be
|
||||
* present, as the owner is, implicitly, the same as Account
|
||||
* (since an offer to sell a token one doesn't already hold
|
||||
* is meaningless).
|
||||
*/
|
||||
Owner?: string
|
||||
/**
|
||||
* Indicates the time after which the offer will no longer
|
||||
* be valid. The value is the number of seconds since the
|
||||
* Ripple Epoch.
|
||||
*/
|
||||
Expiration?: number
|
||||
/**
|
||||
* If present, indicates that this offer may only be
|
||||
* accepted by the specified account. Attempts by other
|
||||
* accounts to accept this offer MUST fail.
|
||||
*/
|
||||
Destination?: string
|
||||
Flags?: number | NFTokenCreateOfferFlagsInterface
|
||||
}
|
||||
|
||||
function validateSellOfferCases(tx: Record<string, unknown>): void {
|
||||
if (tx.Owner != null) {
|
||||
throw new ValidationError(
|
||||
'NFTokenCreateOffer: Owner must not be present for sell offers',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function validateBuyOfferCases(tx: Record<string, unknown>): void {
|
||||
if (tx.Owner == null) {
|
||||
throw new ValidationError(
|
||||
'NFTokenCreateOffer: Owner must be present for buy offers',
|
||||
)
|
||||
}
|
||||
|
||||
if (parseAmountValue(tx.Amount) <= 0) {
|
||||
throw new ValidationError(
|
||||
'NFTokenCreateOffer: Amount must be greater than 0 for buy offers',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of an NFTokenCreateOffer at runtime.
|
||||
*
|
||||
* @param tx - An NFTokenCreateOffer Transaction.
|
||||
* @throws When the NFTokenCreateOffer is Malformed.
|
||||
*/
|
||||
export function validateNFTokenCreateOffer(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
if (tx.Account === tx.Owner) {
|
||||
throw new ValidationError(
|
||||
'NFTokenCreateOffer: Owner and Account must not be equal',
|
||||
)
|
||||
}
|
||||
|
||||
if (tx.Account === tx.Destination) {
|
||||
throw new ValidationError(
|
||||
'NFTokenCreateOffer: Destination and Account must not be equal',
|
||||
)
|
||||
}
|
||||
|
||||
if (tx.TokenID == null) {
|
||||
throw new ValidationError('NFTokenCreateOffer: missing field TokenID')
|
||||
}
|
||||
|
||||
if (!isAmount(tx.Amount)) {
|
||||
throw new ValidationError('NFTokenCreateOffer: invalid Amount')
|
||||
}
|
||||
|
||||
if (
|
||||
typeof tx.Flags === 'number' &&
|
||||
isFlagEnabled(tx.Flags, NFTokenCreateOfferFlags.tfSellToken)
|
||||
) {
|
||||
validateSellOfferCases(tx)
|
||||
} else {
|
||||
validateBuyOfferCases(tx)
|
||||
}
|
||||
}
|
||||
111
packages/xrpl/src/models/transactions/NFTokenMint.ts
Normal file
111
packages/xrpl/src/models/transactions/NFTokenMint.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
|
||||
import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common'
|
||||
|
||||
/**
|
||||
* Transaction Flags for an NFTokenMint Transaction.
|
||||
*
|
||||
* @category Transaction Flags
|
||||
*/
|
||||
export enum NFTokenMintFlags {
|
||||
/**
|
||||
* If set, indicates that the minted token may be burned by the issuer even
|
||||
* if the issuer does not currently hold the token. The current holder of
|
||||
* the token may always burn it.
|
||||
*/
|
||||
tfBurnable = 0x00000001,
|
||||
/**
|
||||
* If set, indicates that the token may only be offered or sold for XRP.
|
||||
*/
|
||||
tfOnlyXRP = 0x00000002,
|
||||
/**
|
||||
* If set, indicates that the issuer wants a trustline to be automatically
|
||||
* created.
|
||||
*/
|
||||
tfTrustLine = 0x00000004,
|
||||
/**
|
||||
* If set, indicates that this NFT can be transferred. This flag has no
|
||||
* effect if the token is being transferred from the issuer or to the
|
||||
* issuer.
|
||||
*/
|
||||
tfTransferable = 0x00000008,
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of flags to boolean values representing {@link NFTokenMint} transaction
|
||||
* flags.
|
||||
*
|
||||
* @category Transaction Flags
|
||||
*/
|
||||
export interface NFTokenMintFlagsInterface extends GlobalFlags {
|
||||
tfBurnable?: boolean
|
||||
tfOnlyXRP?: boolean
|
||||
tfTrustLine?: boolean
|
||||
tfTransferable?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* The NFTokenMint transaction creates an NFToken object and adds it to the
|
||||
* relevant NFTokenPage object of the minter. If the transaction is
|
||||
* successful, the newly minted token will be owned by the minter account
|
||||
* specified by the transaction.
|
||||
*/
|
||||
export interface NFTokenMint extends BaseTransaction {
|
||||
TransactionType: 'NFTokenMint'
|
||||
/**
|
||||
* Indicates the taxon associated with this token. The taxon is generally a
|
||||
* value chosen by the minter of the token and a given taxon may be used for
|
||||
* multiple tokens. The implementation reserves taxon identifiers greater
|
||||
* than or equal to 2147483648 (0x80000000). If you have no use for this
|
||||
* field, set it to 0.
|
||||
*/
|
||||
TokenTaxon: number
|
||||
/**
|
||||
* Indicates the account that should be the issuer of this token. This value
|
||||
* is optional and should only be specified if the account executing the
|
||||
* transaction is not the `Issuer` of the `NFToken` object. If it is
|
||||
* present, the `MintAccount` field in the `AccountRoot` of the `Issuer`
|
||||
* field must match the `Account`, otherwise the transaction will fail.
|
||||
*/
|
||||
Issuer?: string
|
||||
/**
|
||||
* Specifies the fee charged by the issuer for secondary sales of the Token,
|
||||
* if such sales are allowed. Valid values for this field are between 0 and
|
||||
* 50000 inclusive, allowing transfer rates between 0.000% and 50.000% in
|
||||
* increments of 0.001%. This field must NOT be present if the
|
||||
* `tfTransferable` flag is not set.
|
||||
*/
|
||||
TransferFee?: number
|
||||
/**
|
||||
* URI that points to the data and/or metadata associated with the NFT.
|
||||
* This field need not be an HTTP or HTTPS URL; it could be an IPFS URI, a
|
||||
* magnet link, immediate data encoded as an RFC2379 "data" URL, or even an
|
||||
* opaque issuer-specific encoding. The URI is NOT checked for validity, but
|
||||
* the field is limited to a maximum length of 256 bytes.
|
||||
*
|
||||
* This field must be hex-encoded. You can use `convertStringToHex` to
|
||||
* convert this field to the proper encoding.
|
||||
*/
|
||||
URI?: string
|
||||
Flags?: number | NFTokenMintFlagsInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of an NFTokenMint at runtime.
|
||||
*
|
||||
* @param tx - An NFTokenMint Transaction.
|
||||
* @throws When the NFTokenMint is Malformed.
|
||||
*/
|
||||
export function validateNFTokenMint(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
if (tx.Account === tx.Issuer) {
|
||||
throw new ValidationError(
|
||||
'NFTokenMint: Issuer must not be equal to Account',
|
||||
)
|
||||
}
|
||||
|
||||
if (tx.TokenTaxon == null) {
|
||||
throw new ValidationError('NFTokenMint: missing field TokenTaxon')
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,10 @@ export enum AccountSetAsfFlags {
|
||||
asfDefaultRipple = 8,
|
||||
/** Enable Deposit Authorization on this account. */
|
||||
asfDepositAuth = 9,
|
||||
/**
|
||||
* Allow another account to mint and burn tokens on behalf of this account.
|
||||
*/
|
||||
asfAuthorizedMinter = 10,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,6 +139,11 @@ export interface AccountSet extends BaseTransaction {
|
||||
* digits. Valid values are 3 to 15 inclusive, or 0 to disable.
|
||||
*/
|
||||
TickSize?: number
|
||||
/**
|
||||
* Sets an alternate account that is allowed to mint NFTokens on this
|
||||
* account's behalf using NFTokenMint's `Issuer` field.
|
||||
*/
|
||||
Minter?: string
|
||||
}
|
||||
|
||||
const MIN_TICK_SIZE = 3
|
||||
|
||||
@@ -1,31 +1,11 @@
|
||||
/* eslint-disable max-lines-per-function -- Necessary for validateBaseTransaction */
|
||||
/* eslint-disable complexity -- Necessary for validateBaseTransaction */
|
||||
/* eslint-disable max-statements -- Necessary for validateBaseTransaction */
|
||||
import { ValidationError } from '../../errors'
|
||||
import { Memo, Signer } from '../common'
|
||||
import { onlyHasFields } from '../utils'
|
||||
import { TRANSACTION_TYPES } from 'ripple-binary-codec'
|
||||
|
||||
const transactionTypes = [
|
||||
'AccountSet',
|
||||
'AccountDelete',
|
||||
'CheckCancel',
|
||||
'CheckCash',
|
||||
'CheckCreate',
|
||||
'DepositPreauth',
|
||||
'EscrowCancel',
|
||||
'EscrowCreate',
|
||||
'EscrowFinish',
|
||||
'OfferCancel',
|
||||
'OfferCreate',
|
||||
'Payment',
|
||||
'PaymentChannelClaim',
|
||||
'PaymentChannelCreate',
|
||||
'PaymentChannelFund',
|
||||
'SetRegularKey',
|
||||
'SignerListSet',
|
||||
'TicketCreate',
|
||||
'TrustSet',
|
||||
]
|
||||
import { ValidationError } from '../../errors'
|
||||
import { Amount, IssuedCurrencyAmount, Memo, Signer } from '../common'
|
||||
import { onlyHasFields } from '../utils'
|
||||
|
||||
const MEMO_SIZE = 3
|
||||
|
||||
@@ -72,18 +52,25 @@ function isSigner(obj: unknown): boolean {
|
||||
|
||||
const ISSUED_CURRENCY_SIZE = 3
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === 'object'
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of an IssuedCurrencyAmount at runtime.
|
||||
*
|
||||
* @param obj - The object to check the form and type of.
|
||||
* @param input - The input to check the form and type of.
|
||||
* @returns Whether the IssuedCurrencyAmount is malformed.
|
||||
*/
|
||||
export function isIssuedCurrency(obj: Record<string, unknown>): boolean {
|
||||
export function isIssuedCurrency(
|
||||
input: unknown,
|
||||
): input is IssuedCurrencyAmount {
|
||||
return (
|
||||
Object.keys(obj).length === ISSUED_CURRENCY_SIZE &&
|
||||
typeof obj.value === 'string' &&
|
||||
typeof obj.issuer === 'string' &&
|
||||
typeof obj.currency === 'string'
|
||||
isRecord(input) &&
|
||||
Object.keys(input).length === ISSUED_CURRENCY_SIZE &&
|
||||
typeof input.value === 'string' &&
|
||||
typeof input.issuer === 'string' &&
|
||||
typeof input.currency === 'string'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -93,12 +80,8 @@ export function isIssuedCurrency(obj: Record<string, unknown>): boolean {
|
||||
* @param amount - The object to check the form and type of.
|
||||
* @returns Whether the Amount is malformed.
|
||||
*/
|
||||
export function isAmount(amount: unknown): boolean {
|
||||
return (
|
||||
typeof amount === 'string' ||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Only used by JS
|
||||
isIssuedCurrency(amount as Record<string, unknown>)
|
||||
)
|
||||
export function isAmount(amount: unknown): amount is Amount {
|
||||
return typeof amount === 'string' || isIssuedCurrency(amount)
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface -- no global flags right now, so this is fine
|
||||
@@ -203,7 +186,7 @@ export function validateBaseTransaction(common: Record<string, unknown>): void {
|
||||
throw new ValidationError('BaseTransaction: TransactionType not string')
|
||||
}
|
||||
|
||||
if (!transactionTypes.includes(common.TransactionType)) {
|
||||
if (!TRANSACTION_TYPES.includes(common.TransactionType)) {
|
||||
throw new ValidationError('BaseTransaction: Unknown TransactionType')
|
||||
}
|
||||
|
||||
@@ -270,3 +253,19 @@ export function validateBaseTransaction(common: Record<string, unknown>): void {
|
||||
throw new ValidationError('BaseTransaction: invalid TxnSignature')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the value of an amount, expressed either in XRP or as an Issued Currency, into a number.
|
||||
*
|
||||
* @param amount - An Amount to parse for its value.
|
||||
* @returns The parsed amount value, or NaN if the amount count not be parsed.
|
||||
*/
|
||||
export function parseAmountValue(amount: unknown): number {
|
||||
if (!isAmount(amount)) {
|
||||
return NaN
|
||||
}
|
||||
if (typeof amount === 'string') {
|
||||
return parseFloat(amount)
|
||||
}
|
||||
return parseFloat(amount.value)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,19 @@ export { DepositPreauth } from './depositPreauth'
|
||||
export { EscrowCancel } from './escrowCancel'
|
||||
export { EscrowCreate } from './escrowCreate'
|
||||
export { EscrowFinish } from './escrowFinish'
|
||||
export { NFTokenAcceptOffer } from './NFTokenAcceptOffer'
|
||||
export { NFTokenBurn } from './NFTokenBurn'
|
||||
export { NFTokenCancelOffer } from './NFTokenCancelOffer'
|
||||
export {
|
||||
NFTokenCreateOffer,
|
||||
NFTokenCreateOfferFlags,
|
||||
NFTokenCreateOfferFlagsInterface,
|
||||
} from './NFTokenCreateOffer'
|
||||
export {
|
||||
NFTokenMint,
|
||||
NFTokenMintFlags,
|
||||
NFTokenMintFlagsInterface,
|
||||
} from './NFTokenMint'
|
||||
export { OfferCancel } from './offerCancel'
|
||||
export {
|
||||
OfferCreateFlags,
|
||||
|
||||
@@ -17,6 +17,20 @@ import { EscrowCancel, validateEscrowCancel } from './escrowCancel'
|
||||
import { EscrowCreate, validateEscrowCreate } from './escrowCreate'
|
||||
import { EscrowFinish, validateEscrowFinish } from './escrowFinish'
|
||||
import { TransactionMetadata } from './metadata'
|
||||
import {
|
||||
NFTokenAcceptOffer,
|
||||
validateNFTokenAcceptOffer,
|
||||
} from './NFTokenAcceptOffer'
|
||||
import { NFTokenBurn, validateNFTokenBurn } from './NFTokenBurn'
|
||||
import {
|
||||
NFTokenCancelOffer,
|
||||
validateNFTokenCancelOffer,
|
||||
} from './NFTokenCancelOffer'
|
||||
import {
|
||||
NFTokenCreateOffer,
|
||||
validateNFTokenCreateOffer,
|
||||
} from './NFTokenCreateOffer'
|
||||
import { NFTokenMint, validateNFTokenMint } from './NFTokenMint'
|
||||
import { OfferCancel, validateOfferCancel } from './offerCancel'
|
||||
import { OfferCreate, validateOfferCreate } from './offerCreate'
|
||||
import { Payment, validatePayment } from './payment'
|
||||
@@ -50,6 +64,11 @@ export type Transaction =
|
||||
| EscrowCancel
|
||||
| EscrowCreate
|
||||
| EscrowFinish
|
||||
| NFTokenAcceptOffer
|
||||
| NFTokenBurn
|
||||
| NFTokenCancelOffer
|
||||
| NFTokenCreateOffer
|
||||
| NFTokenMint
|
||||
| OfferCancel
|
||||
| OfferCreate
|
||||
| Payment
|
||||
@@ -124,6 +143,26 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
validateEscrowFinish(tx)
|
||||
break
|
||||
|
||||
case 'NFTokenAcceptOffer':
|
||||
validateNFTokenAcceptOffer(tx)
|
||||
break
|
||||
|
||||
case 'NFTokenBurn':
|
||||
validateNFTokenBurn(tx)
|
||||
break
|
||||
|
||||
case 'NFTokenCancelOffer':
|
||||
validateNFTokenCancelOffer(tx)
|
||||
break
|
||||
|
||||
case 'NFTokenCreateOffer':
|
||||
validateNFTokenCreateOffer(tx)
|
||||
break
|
||||
|
||||
case 'NFTokenMint':
|
||||
validateNFTokenMint(tx)
|
||||
break
|
||||
|
||||
case 'OfferCancel':
|
||||
validateOfferCancel(tx)
|
||||
break
|
||||
|
||||
178
packages/xrpl/test/models/NFTokenAcceptOffer.ts
Normal file
178
packages/xrpl/test/models/NFTokenAcceptOffer.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { assert } from 'chai'
|
||||
import { validate, ValidationError } from 'xrpl-local'
|
||||
|
||||
const BUY_OFFER =
|
||||
'AED08CC1F50DD5F23A1948AF86153A3F3B7593E5EC77D65A02BB1B29E05AB6AF'
|
||||
const SELL_OFFER =
|
||||
'AED08CC1F50DD5F23A1948AF86153A3F3B7593E5EC77D65A02BB1B29E05AB6AE'
|
||||
|
||||
/**
|
||||
* NFTokenAcceptOffer Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('NFTokenAcceptOffer', function () {
|
||||
it(`verifies valid NFTokenAcceptOffer with BuyOffer`, function () {
|
||||
const validNFTokenAcceptOffer = {
|
||||
TransactionType: 'NFTokenAcceptOffer',
|
||||
BuyOffer: BUY_OFFER,
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.doesNotThrow(() => validate(validNFTokenAcceptOffer))
|
||||
})
|
||||
|
||||
it(`verifies valid NFTokenAcceptOffer with SellOffer`, function () {
|
||||
const validNFTokenAcceptOffer = {
|
||||
TransactionType: 'NFTokenAcceptOffer',
|
||||
SellOffer: SELL_OFFER,
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.doesNotThrow(() => validate(validNFTokenAcceptOffer))
|
||||
})
|
||||
|
||||
it(`throws w/ missing SellOffer and BuyOffer`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenAcceptOffer',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenAcceptOffer: must set either SellOffer or BuyOffer',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ missing SellOffer and present BrokerFee`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenAcceptOffer',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
BuyOffer: BUY_OFFER,
|
||||
BrokerFee: '1',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenAcceptOffer: both SellOffer and BuyOffer must be set if using brokered mode',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ missing BuyOffer and present BrokerFee`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenAcceptOffer',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
SellOffer: SELL_OFFER,
|
||||
BrokerFee: '1',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenAcceptOffer: both SellOffer and BuyOffer must be set if using brokered mode',
|
||||
)
|
||||
})
|
||||
|
||||
it(`verifies valid NFTokenAcceptOffer with both offers and no BrokerFee`, function () {
|
||||
const validNFTokenAcceptOffer = {
|
||||
TransactionType: 'NFTokenAcceptOffer',
|
||||
SellOffer: SELL_OFFER,
|
||||
BuyOffer: BUY_OFFER,
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.doesNotThrow(() => validate(validNFTokenAcceptOffer))
|
||||
})
|
||||
|
||||
it(`verifies valid NFTokenAcceptOffer with BrokerFee`, function () {
|
||||
const validNFTokenAcceptOffer = {
|
||||
TransactionType: 'NFTokenAcceptOffer',
|
||||
SellOffer: SELL_OFFER,
|
||||
BuyOffer: BUY_OFFER,
|
||||
BrokerFee: '1',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.doesNotThrow(() => validate(validNFTokenAcceptOffer))
|
||||
})
|
||||
|
||||
it(`throws w/ BrokerFee === 0`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenAcceptOffer',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
SellOffer: SELL_OFFER,
|
||||
BuyOffer: BUY_OFFER,
|
||||
BrokerFee: '0',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenAcceptOffer: BrokerFee must be greater than 0; omit if there is no fee',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ BrokerFee < 0`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenAcceptOffer',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
SellOffer: SELL_OFFER,
|
||||
BuyOffer: BUY_OFFER,
|
||||
BrokerFee: '-1',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenAcceptOffer: BrokerFee must be greater than 0; omit if there is no fee',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid BrokerFee`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenAcceptOffer',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
SellOffer: SELL_OFFER,
|
||||
BuyOffer: BUY_OFFER,
|
||||
BrokerFee: 1,
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenAcceptOffer: invalid BrokerFee',
|
||||
)
|
||||
})
|
||||
})
|
||||
41
packages/xrpl/test/models/NFTokenBurn.ts
Normal file
41
packages/xrpl/test/models/NFTokenBurn.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { assert } from 'chai'
|
||||
import { validate, ValidationError } from 'xrpl-local'
|
||||
|
||||
const TOKEN_ID =
|
||||
'00090032B5F762798A53D543A014CAF8B297CFF8F2F937E844B17C9E00000003'
|
||||
|
||||
/**
|
||||
* NFTokenBurn Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('NFTokenBurn', function () {
|
||||
it(`verifies valid NFTokenBurn`, function () {
|
||||
const validNFTokenBurn = {
|
||||
TransactionType: 'NFTokenBurn',
|
||||
TokenID: TOKEN_ID,
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.doesNotThrow(() => validate(validNFTokenBurn))
|
||||
})
|
||||
|
||||
it(`throws w/ missing TokenID`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenBurn',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenBurn: missing field TokenID',
|
||||
)
|
||||
})
|
||||
})
|
||||
58
packages/xrpl/test/models/NFTokenCancelOffer.ts
Normal file
58
packages/xrpl/test/models/NFTokenCancelOffer.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { assert } from 'chai'
|
||||
import { validate, ValidationError } from 'xrpl-local'
|
||||
|
||||
const BUY_OFFER =
|
||||
'AED08CC1F50DD5F23A1948AF86153A3F3B7593E5EC77D65A02BB1B29E05AB6AF'
|
||||
|
||||
/**
|
||||
* NFTokenCancelOffer Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('NFTokenCancelOffer', function () {
|
||||
it(`verifies valid NFTokenCancelOffer`, function () {
|
||||
const validNFTokenCancelOffer = {
|
||||
TransactionType: 'NFTokenCancelOffer',
|
||||
TokenOffers: [BUY_OFFER],
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.doesNotThrow(() => validate(validNFTokenCancelOffer))
|
||||
})
|
||||
|
||||
it(`throws w/ missing TokenOffers`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenCancelOffer',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenCancelOffer: missing field TokenOffers',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ empty TokenOffers`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenCancelOffer',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
TokenOffers: [],
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenCancelOffer: empty field TokenOffers',
|
||||
)
|
||||
})
|
||||
})
|
||||
214
packages/xrpl/test/models/NFTokenCreateOffer.ts
Normal file
214
packages/xrpl/test/models/NFTokenCreateOffer.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
import { assert } from 'chai'
|
||||
import { validate, ValidationError, NFTokenCreateOfferFlags } from 'xrpl-local'
|
||||
|
||||
const TOKEN_ID =
|
||||
'00090032B5F762798A53D543A014CAF8B297CFF8F2F937E844B17C9E00000003'
|
||||
|
||||
/**
|
||||
* NFTokenCreateOffer Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('NFTokenCreateOffer', function () {
|
||||
it(`verifies valid NFTokenCreateOffer buyside`, function () {
|
||||
const validNFTokenCreateOffer = {
|
||||
TransactionType: 'NFTokenCreateOffer',
|
||||
TokenID: TOKEN_ID,
|
||||
Amount: '1',
|
||||
Owner: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
Expiration: 1000,
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Destination: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
} as any
|
||||
|
||||
assert.doesNotThrow(() => validate(validNFTokenCreateOffer))
|
||||
})
|
||||
|
||||
it(`verifies valid NFTokenCreateOffer sellside`, function () {
|
||||
const validNFTokenCreateOffer = {
|
||||
TransactionType: 'NFTokenCreateOffer',
|
||||
TokenID: TOKEN_ID,
|
||||
Amount: '1',
|
||||
Flags: NFTokenCreateOfferFlags.tfSellToken,
|
||||
Expiration: 1000,
|
||||
Destination: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
} as any
|
||||
|
||||
assert.doesNotThrow(() => validate(validNFTokenCreateOffer))
|
||||
})
|
||||
|
||||
it(`verifies w/ 0 Amount NFTokenCreateOffer sellside`, function () {
|
||||
const validNFTokenCreateOffer = {
|
||||
TransactionType: 'NFTokenCreateOffer',
|
||||
TokenID: TOKEN_ID,
|
||||
Amount: '0',
|
||||
Flags: NFTokenCreateOfferFlags.tfSellToken,
|
||||
Expiration: 1000,
|
||||
Destination: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
} as any
|
||||
|
||||
assert.doesNotThrow(() => validate(validNFTokenCreateOffer))
|
||||
})
|
||||
|
||||
it(`throws w/ Account === Owner`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenCreateOffer',
|
||||
TokenID: TOKEN_ID,
|
||||
Amount: '1',
|
||||
Expiration: 1000,
|
||||
Owner: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenCreateOffer: Owner and Account must not be equal',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ Account === Destination`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenCreateOffer',
|
||||
TokenID: TOKEN_ID,
|
||||
Amount: '1',
|
||||
Flags: NFTokenCreateOfferFlags.tfSellToken,
|
||||
Expiration: 1000,
|
||||
Destination: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenCreateOffer: Destination and Account must not be equal',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/out TokenID`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenCreateOffer',
|
||||
Amount: '1',
|
||||
Owner: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXe',
|
||||
Expiration: 1000,
|
||||
Destination: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenCreateOffer: missing field TokenID',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid Amount`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenCreateOffer',
|
||||
TokenID: TOKEN_ID,
|
||||
Amount: 1,
|
||||
Owner: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXe',
|
||||
Expiration: 1000,
|
||||
Destination: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenCreateOffer: invalid Amount',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ missing Amount`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenCreateOffer',
|
||||
Owner: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXe',
|
||||
Expiration: 1000,
|
||||
TokenID: TOKEN_ID,
|
||||
Destination: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenCreateOffer: invalid Amount',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ Owner for sell offer`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenCreateOffer',
|
||||
Expiration: 1000,
|
||||
Owner: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
TokenID: TOKEN_ID,
|
||||
Flags: NFTokenCreateOfferFlags.tfSellToken,
|
||||
Amount: '1',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenCreateOffer: Owner must not be present for sell offers',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/out Owner for buy offer`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenCreateOffer',
|
||||
Expiration: 1000,
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Amount: '1',
|
||||
TokenID: TOKEN_ID,
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenCreateOffer: Owner must be present for buy offers',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ 0 Amount for buy offer`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenCreateOffer',
|
||||
Expiration: 1000,
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Owner: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
Amount: '0',
|
||||
Fee: '5000000',
|
||||
TokenID: TOKEN_ID,
|
||||
Sequence: 2470665,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenCreateOffer: Amount must be greater than 0 for buy offers',
|
||||
)
|
||||
})
|
||||
})
|
||||
69
packages/xrpl/test/models/NFTokenMint.ts
Normal file
69
packages/xrpl/test/models/NFTokenMint.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { assert } from 'chai'
|
||||
import {
|
||||
convertStringToHex,
|
||||
validate,
|
||||
ValidationError,
|
||||
NFTokenMintFlags,
|
||||
} from 'xrpl-local'
|
||||
|
||||
/**
|
||||
* NFTokenMint Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('NFTokenMint', function () {
|
||||
it(`verifies valid NFTokenMint`, function () {
|
||||
const validNFTokenMint = {
|
||||
TransactionType: 'NFTokenMint',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: NFTokenMintFlags.tfTransferable,
|
||||
TokenTaxon: 0,
|
||||
Issuer: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
TransferFee: 1,
|
||||
URI: convertStringToHex('http://xrpl.org'),
|
||||
} as any
|
||||
|
||||
assert.doesNotThrow(() => validate(validNFTokenMint))
|
||||
})
|
||||
|
||||
it(`throws w/ missing TokenTaxon`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenMint',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: NFTokenMintFlags.tfTransferable,
|
||||
Issuer: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
TransferFee: 1,
|
||||
URI: convertStringToHex('http://xrpl.org'),
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenMint: missing field TokenTaxon',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ Account === Issuer`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenMint',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: NFTokenMintFlags.tfTransferable,
|
||||
Issuer: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
TransferFee: 1,
|
||||
TokenTaxon: 0,
|
||||
URI: convertStringToHex('http://xrpl.org'),
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenMint: Issuer must not be equal to Account',
|
||||
)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user