mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-12-06 09:18:02 +00:00
support DynamicNFT (#2726)
* support DynamicNFT * Update history.md * use xrpl-codec-gen to generate definitions.json and modify ripple-binary-code HISTORY.md * use validateRequiredField for NFTokenID check * move comment to a proper place * Add some comment and modify integration test * update transaction number * update ci rippled version
This commit is contained in:
@@ -188,3 +188,4 @@ fixNFTokenPageLinks
|
|||||||
fixInnerObjTemplate2
|
fixInnerObjTemplate2
|
||||||
fixEnforceNFTokenTrustline
|
fixEnforceNFTokenTrustline
|
||||||
fixReducedOffersV2
|
fixReducedOffersV2
|
||||||
|
DynamicNFT
|
||||||
|
|||||||
2
.github/workflows/nodejs.yml
vendored
2
.github/workflows/nodejs.yml
vendored
@@ -4,7 +4,7 @@
|
|||||||
name: Node.js CI
|
name: Node.js CI
|
||||||
|
|
||||||
env:
|
env:
|
||||||
RIPPLED_DOCKER_IMAGE: rippleci/rippled:2.3.0-rc1
|
RIPPLED_DOCKER_IMAGE: rippleci/rippled:develop
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
* Support for the Price Oracles amendment (XLS-47).
|
* Support for the Price Oracles amendment (XLS-47).
|
||||||
|
* Add `NFTokenModify` transaction and add `tfMutable` flag in `NFTokenMint`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* Better error handling/error messages for serialization/deserialization errors.
|
* Better error handling/error messages for serialization/deserialization errors.
|
||||||
|
|||||||
@@ -3085,6 +3085,7 @@
|
|||||||
"NFTokenCancelOffer": 28,
|
"NFTokenCancelOffer": 28,
|
||||||
"NFTokenCreateOffer": 27,
|
"NFTokenCreateOffer": 27,
|
||||||
"NFTokenMint": 25,
|
"NFTokenMint": 25,
|
||||||
|
"NFTokenModify": 61,
|
||||||
"OfferCancel": 8,
|
"OfferCancel": 8,
|
||||||
"OfferCreate": 7,
|
"OfferCreate": 7,
|
||||||
"OracleDelete": 52,
|
"OracleDelete": 52,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
|
|||||||
* New `MPTAmount` type support for `Payment` and `Clawback` transactions
|
* New `MPTAmount` type support for `Payment` and `Clawback` transactions
|
||||||
* `parseTransactionFlags` as a utility function in the xrpl package to streamline transactions flags-to-map conversion
|
* `parseTransactionFlags` as a utility function in the xrpl package to streamline transactions flags-to-map conversion
|
||||||
* Support for XLS-70d (Credentials)
|
* Support for XLS-70d (Credentials)
|
||||||
|
* Add `NFTokenModify` transaction and add `tfMutable` flag in `NFTokenMint`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* `TransactionStream` model supports APIv2
|
* `TransactionStream` model supports APIv2
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ export enum NFTokenMintFlags {
|
|||||||
* issuer.
|
* issuer.
|
||||||
*/
|
*/
|
||||||
tfTransferable = 0x00000008,
|
tfTransferable = 0x00000008,
|
||||||
|
/**
|
||||||
|
* If set, indicates that this NFT's URI can be modified.
|
||||||
|
*/
|
||||||
|
tfMutable = 0x00000010,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,6 +55,7 @@ export interface NFTokenMintFlagsInterface extends GlobalFlags {
|
|||||||
tfOnlyXRP?: boolean
|
tfOnlyXRP?: boolean
|
||||||
tfTrustLine?: boolean
|
tfTrustLine?: boolean
|
||||||
tfTransferable?: boolean
|
tfTransferable?: boolean
|
||||||
|
tfMutable?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
67
packages/xrpl/src/models/transactions/NFTokenModify.ts
Normal file
67
packages/xrpl/src/models/transactions/NFTokenModify.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { ValidationError } from '../../errors'
|
||||||
|
import { isHex } from '../utils'
|
||||||
|
|
||||||
|
import {
|
||||||
|
BaseTransaction,
|
||||||
|
validateBaseTransaction,
|
||||||
|
isAccount,
|
||||||
|
isString,
|
||||||
|
validateOptionalField,
|
||||||
|
Account,
|
||||||
|
validateRequiredField,
|
||||||
|
} from './common'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The NFTokenModify transaction modifies an NFToken's URI
|
||||||
|
* if its tfMutable is set to true.
|
||||||
|
*/
|
||||||
|
export interface NFTokenModify extends BaseTransaction {
|
||||||
|
TransactionType: 'NFTokenModify'
|
||||||
|
/**
|
||||||
|
* Identifies the NFTokenID of the NFToken object that the
|
||||||
|
* offer references.
|
||||||
|
*/
|
||||||
|
NFTokenID: string
|
||||||
|
/**
|
||||||
|
* Indicates the AccountID of the account that owns the corresponding NFToken.
|
||||||
|
* Can be omitted if the owner is the account submitting this transaction
|
||||||
|
*/
|
||||||
|
Owner?: Account
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* This field must not be an empty string. Omit it from the transaction or
|
||||||
|
* set to `undefined` value if you do not use it.
|
||||||
|
*/
|
||||||
|
URI?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify the form and type of an NFTokenModify at runtime.
|
||||||
|
*
|
||||||
|
* @param tx - An NFTokenModify Transaction.
|
||||||
|
* @throws When the NFTokenModify is Malformed.
|
||||||
|
*/
|
||||||
|
export function validateNFTokenModify(tx: Record<string, unknown>): void {
|
||||||
|
validateBaseTransaction(tx)
|
||||||
|
|
||||||
|
validateRequiredField(tx, 'NFTokenID', isString)
|
||||||
|
validateOptionalField(tx, 'Owner', isAccount)
|
||||||
|
validateOptionalField(tx, 'URI', isString)
|
||||||
|
|
||||||
|
if (tx.URI !== undefined && typeof tx.URI === 'string') {
|
||||||
|
if (tx.URI === '') {
|
||||||
|
throw new ValidationError('NFTokenModify: URI must not be empty string')
|
||||||
|
}
|
||||||
|
if (!isHex(tx.URI)) {
|
||||||
|
throw new ValidationError('NFTokenModify: URI must be in hex format')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -71,6 +71,7 @@ export {
|
|||||||
NFTokenMintFlags,
|
NFTokenMintFlags,
|
||||||
NFTokenMintFlagsInterface,
|
NFTokenMintFlagsInterface,
|
||||||
} from './NFTokenMint'
|
} from './NFTokenMint'
|
||||||
|
export { NFTokenModify, validateNFTokenModify } from './NFTokenModify'
|
||||||
export { OfferCancel } from './offerCancel'
|
export { OfferCancel } from './offerCancel'
|
||||||
export {
|
export {
|
||||||
OfferCreateFlags,
|
OfferCreateFlags,
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ import {
|
|||||||
validateNFTokenCreateOffer,
|
validateNFTokenCreateOffer,
|
||||||
} from './NFTokenCreateOffer'
|
} from './NFTokenCreateOffer'
|
||||||
import { NFTokenMint, validateNFTokenMint } from './NFTokenMint'
|
import { NFTokenMint, validateNFTokenMint } from './NFTokenMint'
|
||||||
|
import { NFTokenModify, validateNFTokenModify } from './NFTokenModify'
|
||||||
import { OfferCancel, validateOfferCancel } from './offerCancel'
|
import { OfferCancel, validateOfferCancel } from './offerCancel'
|
||||||
import { OfferCreate, validateOfferCreate } from './offerCreate'
|
import { OfferCreate, validateOfferCreate } from './offerCreate'
|
||||||
import { OracleDelete, validateOracleDelete } from './oracleDelete'
|
import { OracleDelete, validateOracleDelete } from './oracleDelete'
|
||||||
@@ -143,6 +144,7 @@ export type SubmittableTransaction =
|
|||||||
| NFTokenCancelOffer
|
| NFTokenCancelOffer
|
||||||
| NFTokenCreateOffer
|
| NFTokenCreateOffer
|
||||||
| NFTokenMint
|
| NFTokenMint
|
||||||
|
| NFTokenModify
|
||||||
| OfferCancel
|
| OfferCancel
|
||||||
| OfferCreate
|
| OfferCreate
|
||||||
| OracleDelete
|
| OracleDelete
|
||||||
@@ -377,6 +379,10 @@ export function validate(transaction: Record<string, unknown>): void {
|
|||||||
validateNFTokenMint(tx)
|
validateNFTokenMint(tx)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'NFTokenModify':
|
||||||
|
validateNFTokenModify(tx)
|
||||||
|
break
|
||||||
|
|
||||||
case 'OfferCancel':
|
case 'OfferCancel':
|
||||||
validateOfferCancel(tx)
|
validateOfferCancel(tx)
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import { NFTokenModify } from '../../../dist/npm'
|
||||||
|
import { NFTokenMintFlags } from '../../../dist/npm/src'
|
||||||
|
import {
|
||||||
|
convertStringToHex,
|
||||||
|
getNFTokenID,
|
||||||
|
NFTokenMint,
|
||||||
|
TransactionMetadata,
|
||||||
|
TxRequest,
|
||||||
|
} from '../../../src'
|
||||||
|
import { hashSignedTx } from '../../../src/utils/hashes'
|
||||||
|
import serverUrl from '../serverUrl'
|
||||||
|
import {
|
||||||
|
setupClient,
|
||||||
|
teardownClient,
|
||||||
|
type XrplIntegrationTestContext,
|
||||||
|
} from '../setup'
|
||||||
|
import { testTransaction } from '../utils'
|
||||||
|
|
||||||
|
// how long before each test case times out
|
||||||
|
const TIMEOUT = 20000
|
||||||
|
|
||||||
|
describe('NFTokenModify', function () {
|
||||||
|
let testContext: XrplIntegrationTestContext
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
testContext = await setupClient(serverUrl)
|
||||||
|
})
|
||||||
|
afterEach(async () => teardownClient(testContext))
|
||||||
|
|
||||||
|
// Mint an NFToken with tfMutable flag and modify URI later
|
||||||
|
it(
|
||||||
|
'modify NFToken URI',
|
||||||
|
async function () {
|
||||||
|
const oldUri = convertStringToHex('https://www.google.com')
|
||||||
|
const newUri = convertStringToHex('https://www.youtube.com')
|
||||||
|
|
||||||
|
const mutableMint: NFTokenMint = {
|
||||||
|
TransactionType: 'NFTokenMint',
|
||||||
|
Account: testContext.wallet.address,
|
||||||
|
Flags: NFTokenMintFlags.tfMutable,
|
||||||
|
URI: oldUri,
|
||||||
|
NFTokenTaxon: 0,
|
||||||
|
}
|
||||||
|
const response = await testTransaction(
|
||||||
|
testContext.client,
|
||||||
|
mutableMint,
|
||||||
|
testContext.wallet,
|
||||||
|
)
|
||||||
|
assert.equal(response.type, 'response')
|
||||||
|
|
||||||
|
const mutableTx: TxRequest = {
|
||||||
|
command: 'tx',
|
||||||
|
transaction: hashSignedTx(response.result.tx_blob),
|
||||||
|
}
|
||||||
|
const mutableTxResponse = await testContext.client.request(mutableTx)
|
||||||
|
|
||||||
|
const mutableNFTokenID =
|
||||||
|
getNFTokenID(
|
||||||
|
mutableTxResponse.result.meta as TransactionMetadata<NFTokenMint>,
|
||||||
|
) ?? 'undefined'
|
||||||
|
|
||||||
|
const accountNFTs = await testContext.client.request({
|
||||||
|
command: 'account_nfts',
|
||||||
|
account: testContext.wallet.address,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
accountNFTs.result.account_nfts.find(
|
||||||
|
(nft) => nft.NFTokenID === mutableNFTokenID,
|
||||||
|
)?.URI,
|
||||||
|
oldUri,
|
||||||
|
)
|
||||||
|
|
||||||
|
const modifyTx: NFTokenModify = {
|
||||||
|
TransactionType: 'NFTokenModify',
|
||||||
|
Account: testContext.wallet.address,
|
||||||
|
NFTokenID: mutableNFTokenID,
|
||||||
|
URI: newUri,
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifyResponse = await testTransaction(
|
||||||
|
testContext.client,
|
||||||
|
modifyTx,
|
||||||
|
testContext.wallet,
|
||||||
|
)
|
||||||
|
assert.equal(modifyResponse.type, 'response')
|
||||||
|
|
||||||
|
const nfts = await testContext.client.request({
|
||||||
|
command: 'account_nfts',
|
||||||
|
account: testContext.wallet.address,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
nfts.result.account_nfts.find(
|
||||||
|
(nft) => nft.NFTokenID === mutableNFTokenID,
|
||||||
|
)?.URI,
|
||||||
|
newUri,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
TIMEOUT,
|
||||||
|
)
|
||||||
|
})
|
||||||
41
packages/xrpl/test/models/NFTokenModify.test.ts
Normal file
41
packages/xrpl/test/models/NFTokenModify.test.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { assert } from 'chai'
|
||||||
|
|
||||||
|
import { convertStringToHex, validate, ValidationError } from '../../src'
|
||||||
|
|
||||||
|
const TOKEN_ID =
|
||||||
|
'00090032B5F762798A53D543A014CAF8B297CFF8F2F937E844B17C9E00000003'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NFTokenModify Transaction Verification Testing.
|
||||||
|
*
|
||||||
|
* Providing runtime verification testing for each specific transaction type.
|
||||||
|
*/
|
||||||
|
describe('NFTokenModify', function () {
|
||||||
|
it(`verifies valid NFTokenModify`, function () {
|
||||||
|
const validNFTokenModify = {
|
||||||
|
TransactionType: 'NFTokenModify',
|
||||||
|
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||||
|
NFTokenID: TOKEN_ID,
|
||||||
|
Fee: '5000000',
|
||||||
|
Sequence: 2470665,
|
||||||
|
URI: convertStringToHex('http://xrpl.org'),
|
||||||
|
} as any
|
||||||
|
|
||||||
|
assert.doesNotThrow(() => validate(validNFTokenModify))
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`throws w/ missing NFTokenID`, function () {
|
||||||
|
const invalid = {
|
||||||
|
TransactionType: 'NFTokenModify',
|
||||||
|
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||||
|
Fee: '5000000',
|
||||||
|
Sequence: 2470665,
|
||||||
|
} as any
|
||||||
|
|
||||||
|
assert.throws(
|
||||||
|
() => validate(invalid),
|
||||||
|
ValidationError,
|
||||||
|
'NFTokenModify: missing field NFTokenID',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user