Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DO NOT MERGE] support DynamicNFT #2726

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/ripple-binary-codec/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,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.
Expand Down
1 change: 1 addition & 0 deletions packages/ripple-binary-codec/src/enums/definitions.json
Original file line number Diff line number Diff line change
Expand Up @@ -2974,6 +2974,7 @@
"DIDDelete": 50,
"OracleSet": 51,
"OracleDelete": 52,
"NFTokenModify": 53,
"EnableAmendment": 100,
"SetFee": 101,
"UNLModify": 102
Expand Down
1 change: 1 addition & 0 deletions packages/xrpl/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr

### Added
* Add `nfts_by_issuer` clio-only API definition
* Add `NFTokenModify` transaction and add `tfMutable` flag in `NFTokenMint`

## 3.1.0 (2024-06-03)

Expand Down
5 changes: 5 additions & 0 deletions packages/xrpl/src/models/transactions/NFTokenMint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ export enum NFTokenMintFlags {
* issuer.
*/
tfTransferable = 0x00000008,
/**
* If set, indicates that this NFT's URI can be modified.
*/
tfMutable = 0x00000010,
}

/**
Expand All @@ -51,6 +55,7 @@ export interface NFTokenMintFlagsInterface extends GlobalFlags {
tfOnlyXRP?: boolean
tfTrustLine?: boolean
tfTransferable?: boolean
tfMutable?: boolean
}

/**
Expand Down
67 changes: 67 additions & 0 deletions packages/xrpl/src/models/transactions/NFTokenModify.ts
Original file line number Diff line number Diff line change
@@ -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')
}
yinyiqian1 marked this conversation as resolved.
Show resolved Hide resolved
if (!isHex(tx.URI)) {
throw new ValidationError('NFTokenModify: URI must be in hex format')
}
}
}
1 change: 1 addition & 0 deletions packages/xrpl/src/models/transactions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export {
NFTokenMintFlags,
NFTokenMintFlagsInterface,
} from './NFTokenMint'
export { NFTokenModify, validateNFTokenModify } from './NFTokenModify'
export { OfferCancel } from './offerCancel'
export {
OfferCreateFlags,
Expand Down
6 changes: 6 additions & 0 deletions packages/xrpl/src/models/transactions/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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'
Expand Down Expand Up @@ -120,6 +121,7 @@ export type SubmittableTransaction =
| NFTokenCancelOffer
| NFTokenCreateOffer
| NFTokenMint
| NFTokenModify
| OfferCancel
| OfferCreate
| OracleDelete
Expand Down Expand Up @@ -326,6 +328,10 @@ export function validate(transaction: Record<string, unknown>): void {
validateNFTokenMint(tx)
break

case 'NFTokenModify':
validateNFTokenModify(tx)
break

case 'OfferCancel':
validateOfferCancel(tx)
break
Expand Down
104 changes: 104 additions & 0 deletions packages/xrpl/test/integration/transactions/nftokenModify.test.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 41 additions & 0 deletions packages/xrpl/test/models/NFTokenModify.test.ts
Original file line number Diff line number Diff line change
@@ -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',
)
})
})