mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-12-01 09:35:48 +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
|
||||
fixEnforceNFTokenTrustline
|
||||
fixReducedOffersV2
|
||||
DynamicNFT
|
||||
|
||||
2
.github/workflows/nodejs.yml
vendored
2
.github/workflows/nodejs.yml
vendored
@@ -4,7 +4,7 @@
|
||||
name: Node.js CI
|
||||
|
||||
env:
|
||||
RIPPLED_DOCKER_IMAGE: rippleci/rippled:2.3.0-rc1
|
||||
RIPPLED_DOCKER_IMAGE: rippleci/rippled:develop
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
### Added
|
||||
* Support for the Price Oracles amendment (XLS-47).
|
||||
* Add `NFTokenModify` transaction and add `tfMutable` flag in `NFTokenMint`
|
||||
|
||||
### Fixed
|
||||
* Better error handling/error messages for serialization/deserialization errors.
|
||||
|
||||
@@ -3085,6 +3085,7 @@
|
||||
"NFTokenCancelOffer": 28,
|
||||
"NFTokenCreateOffer": 27,
|
||||
"NFTokenMint": 25,
|
||||
"NFTokenModify": 61,
|
||||
"OfferCancel": 8,
|
||||
"OfferCreate": 7,
|
||||
"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
|
||||
* `parseTransactionFlags` as a utility function in the xrpl package to streamline transactions flags-to-map conversion
|
||||
* Support for XLS-70d (Credentials)
|
||||
* Add `NFTokenModify` transaction and add `tfMutable` flag in `NFTokenMint`
|
||||
|
||||
### Fixed
|
||||
* `TransactionStream` model supports APIv2
|
||||
|
||||
@@ -38,6 +38,10 @@ export enum NFTokenMintFlags {
|
||||
* issuer.
|
||||
*/
|
||||
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
|
||||
tfTrustLine?: 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,
|
||||
NFTokenMintFlagsInterface,
|
||||
} from './NFTokenMint'
|
||||
export { NFTokenModify, validateNFTokenModify } from './NFTokenModify'
|
||||
export { OfferCancel } from './offerCancel'
|
||||
export {
|
||||
OfferCreateFlags,
|
||||
|
||||
@@ -57,6 +57,7 @@ import {
|
||||
validateNFTokenCreateOffer,
|
||||
} from './NFTokenCreateOffer'
|
||||
import { NFTokenMint, validateNFTokenMint } from './NFTokenMint'
|
||||
import { NFTokenModify, validateNFTokenModify } from './NFTokenModify'
|
||||
import { OfferCancel, validateOfferCancel } from './offerCancel'
|
||||
import { OfferCreate, validateOfferCreate } from './offerCreate'
|
||||
import { OracleDelete, validateOracleDelete } from './oracleDelete'
|
||||
@@ -143,6 +144,7 @@ export type SubmittableTransaction =
|
||||
| NFTokenCancelOffer
|
||||
| NFTokenCreateOffer
|
||||
| NFTokenMint
|
||||
| NFTokenModify
|
||||
| OfferCancel
|
||||
| OfferCreate
|
||||
| OracleDelete
|
||||
@@ -377,6 +379,10 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
validateNFTokenMint(tx)
|
||||
break
|
||||
|
||||
case 'NFTokenModify':
|
||||
validateNFTokenModify(tx)
|
||||
break
|
||||
|
||||
case 'OfferCancel':
|
||||
validateOfferCancel(tx)
|
||||
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