Skip to content
Draft
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
3 changes: 3 additions & 0 deletions packages/xrpl/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr

## Unreleased

### Added
* Support for `Sponsor` (XLS-68d)

### Fixed
* Fix incorrect type checking in `validateVaultCreate` that prevented vault creation with MPT as an asset.

Expand Down
6 changes: 6 additions & 0 deletions packages/xrpl/src/models/ledger/AccountRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,12 @@ export default interface AccountRoot extends BaseLedgerEntry, HasPreviousTxnID {
MintedNFTokens?: number
/** Another account that can mint NFTokens on behalf of this account. */
NFTokenMinter?: string
/** The number of objects this account is sponsored by. */
SponsoredOwnerCount?: number
/** The number of objects this account is sponsoring. */
SponsoringOwnerCount?: number
/** The number of accounts this account is sponsoring. */
SponsoringAccountCount?: number
}

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/xrpl/src/models/ledger/BaseLedgerEntry.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export interface BaseLedgerEntry {
index: string
/** The account that is sponsoring the account or object. */
SponsorAccount?: string
}

export interface HasPreviousTxnID {
Expand Down
25 changes: 25 additions & 0 deletions packages/xrpl/src/models/transactions/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import {
} from '../common'
import { isHex, onlyHasFields } from '../utils'

import { Sponsor, SponsorSignature } from './sponsorshipSet'

const MEMO_SIZE = 3
export const MAX_AUTHORIZED_CREDENTIALS = 8
const MAX_CREDENTIAL_BYTE_LENGTH = 64
Expand Down Expand Up @@ -533,6 +535,14 @@ export interface BaseTransaction extends Record<string, unknown> {
* The delegate account that is sending the transaction.
*/
Delegate?: Account
/**
* The sponsor of the transaction.
*/
Sponsor?: Sponsor
/**
* The signature of the sponsor.
*/
SponsorSignature?: SponsorSignature
}

/**
Expand Down Expand Up @@ -609,6 +619,21 @@ export function validateBaseTransaction(
'BaseTransaction: Account and Delegate addresses cannot be the same',
)
}

// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- avoid type unknown for Sponsor
const sponsor = common.Sponsor as Sponsor | undefined
if (sponsor != null && sponsor.Account === common.Account) {
throw new ValidationError(
'BaseTransaction: Account and Sponsor addresses cannot be the same',
)
}

const sponsorSignature = common.SponsorSignature
if (sponsor == null && sponsorSignature != null) {
throw new ValidationError(
'BaseTransaction: Sponsor is required when SponsorSignature is provided',
)
}
}

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/xrpl/src/models/transactions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ export { PermissionedDomainDelete } from './permissionedDomainDelete'
export { SetFee, SetFeePreAmendment, SetFeePostAmendment } from './setFee'
export { SetRegularKey } from './setRegularKey'
export { SignerListSet } from './signerListSet'
export { SponsorshipSet } from './sponsorshipSet'
export { SponsorshipTransfer } from './sponsorshipTransfer'
export { TicketCreate } from './ticketCreate'
export { TrustSetFlagsInterface, TrustSetFlags, TrustSet } from './trustSet'
export { UNLModify } from './UNLModify'
Expand Down
4 changes: 4 additions & 0 deletions packages/xrpl/src/models/transactions/payment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export enum PaymentFlags {
* details.
*/
tfLimitQuality = 0x00040000,
/**
* The destination account will created as a sponsored Account.
*/
tfSponsorCreatedAccount = 0x00080000,
}

/**
Expand Down
116 changes: 116 additions & 0 deletions packages/xrpl/src/models/transactions/sponsorshipSet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { ValidationError } from '../../errors'
import { Signer } from '../common'

import {
BaseTransaction,
GlobalFlagsInterface,
isAccount,
isNumber,
isString,
validateBaseTransaction,
validateOptionalField,
} from './common'

export enum SponsorFlags {
/** Sponsor will pay the fee for the transaction. */
tfSponsorFee = 0x00000001,
/** Sponsor will burden the reserves of the transaction. */
tfSponsorReserve = 0x00000002,
}

export interface Sponsor {
Account: string
Flags: number | SponsorFlags
}

export interface SponsorSignature {
SigningPubKey?: string
TxnSignature?: string
Signers?: Signer[]
}

/**
* Enum representing values of {@link SponsorshipSet} transaction flags.
*
* @category Transaction Flags
*/
export enum SponsorshipSetFlags {
tfSponsorshipSetRequireSignForFee = 0x00010000,
tfSponsorshipClearRequireSignForFee = 0x00020000,
tfSponsorshipSetRequireSignForReserve = 0x00040000,
tfSponsorshipClearRequireSignForReserve = 0x00080000,
tfDeleteObject = 0x00100000,
}

/**
* Map of flags to boolean values representing {@link SponsorshipSet} transaction
* flags.
*
* @category Transaction Flags
*/
export interface SponsorshipSetFlagsInterface extends GlobalFlagsInterface {
tfSponsorshipSetRequireSignForFee?: boolean
tfSponsorshipClearRequireSignForFee?: boolean
tfSponsorshipSetRequireSignForReserve?: boolean
tfSponsorshipClearRequireSignForReserve?: boolean
tfDeleteObject?: boolean
}

/**
* A SponsorshipSet transaction creates, changes, or removes the sponsorship
* of an account or object.
*
* @category Transaction Models
*/
export interface SponsorshipSet extends BaseTransaction {
TransactionType: 'SponsorshipSet'
Flags?: number | SponsorshipSetFlagsInterface
/**
* The account that is sponsoring the account or object.
*/
SponsorAccount?: string
/**
* The account that is being sponsored.
*/
Sponsee?: string
/**
* The total amount of fee to be allowed to be paid for the sponsored transaction.
*/
FeeAmount?: string
/**
* The maximum amount of fee to be paid for the one transaction.
*/
MaxFee?: string
/**
* The number of reserves to be allowed to be burned for the sponsored transaction.
*/
ReserveCount?: number
}

/**
* Verify the form and type of a SponsorshipSet at runtime.
*
* @param tx - A SponsorshipSet Transaction.
* @throws When the SponsorshipSet is malformed.
*/
export function validateSponsorshipSet(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)

validateOptionalField(tx, 'SponsorAccount', isAccount)
validateOptionalField(tx, 'Sponsee', isAccount)

if (tx.SponsorAccount === undefined && tx.Sponsee === undefined) {
throw new ValidationError(
'SponsorshipSet: SponsorAccount or Sponsee must be set, but not both',
)
}
if (tx.SponsorAccount !== undefined && tx.Sponsee !== undefined) {
throw new ValidationError(
'SponsorshipSet: SponsorAccount and Sponsee cannot be both set',
)
}

validateOptionalField(tx, 'FeeAmount', isString)
validateOptionalField(tx, 'MaxFee', isString)
validateOptionalField(tx, 'ReserveCount', isNumber)
}
39 changes: 39 additions & 0 deletions packages/xrpl/src/models/transactions/sponsorshipTransfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ValidationError } from '../../errors'

import {
BaseTransaction,
isString,
validateBaseTransaction,
validateOptionalField,
} from './common'

/**
* A SponsorshipTransfer transaction transfers the sponsorship
* of an account or object.
*
* @category Transaction Models
*/
export interface SponsorshipTransfer extends BaseTransaction {
TransactionType: 'SponsorshipTransfer'
ObjectID: string
}

/**
* Verify the form and type of a SponsorshipTransfer at runtime.
*
* @param tx - A SponsorshipTransfer Transaction.
* @throws When the SponsorshipTransfer is malformed.
*/
export function validateSponsorshipTransfer(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)

validateOptionalField(tx, 'ObjectID', isString)
if (tx.ObjectID !== undefined) {
// @typescript-eslint/no-magic-numbers - objectID length is 64
if (tx.ObjectID.length !== 64) {
throw new ValidationError(
'SponsorshipTransfer: ObjectID must be a valid ObjectID',
)
}
}
}
15 changes: 15 additions & 0 deletions packages/xrpl/src/models/transactions/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ import {
import { SetFee } from './setFee'
import { SetRegularKey, validateSetRegularKey } from './setRegularKey'
import { SignerListSet, validateSignerListSet } from './signerListSet'
import { SponsorshipSet, validateSponsorshipSet } from './sponsorshipSet'
import {
SponsorshipTransfer,
validateSponsorshipTransfer,
} from './sponsorshipTransfer'
import { TicketCreate, validateTicketCreate } from './ticketCreate'
import { TrustSet, validateTrustSet } from './trustSet'
import { UNLModify } from './UNLModify'
Expand Down Expand Up @@ -179,6 +184,8 @@ export type SubmittableTransaction =
| PermissionedDomainDelete
| SetRegularKey
| SignerListSet
| SponsorshipSet
| SponsorshipTransfer
| TicketCreate
| TrustSet
| VaultClawback
Expand Down Expand Up @@ -447,6 +454,14 @@ export function validate(transaction: Record<string, unknown>): void {
validateSignerListSet(tx)
break

case 'SponsorshipSet':
validateSponsorshipSet(tx)
break

case 'SponsorshipTransfer':
validateSponsorshipTransfer(tx)
break

case 'TicketCreate':
validateTicketCreate(tx)
break
Expand Down
2 changes: 2 additions & 0 deletions packages/xrpl/src/models/utils/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { NFTokenMintFlags } from '../transactions/NFTokenMint'
import { OfferCreateFlags } from '../transactions/offerCreate'
import { PaymentFlags } from '../transactions/payment'
import { PaymentChannelClaimFlags } from '../transactions/paymentChannelClaim'
import { SponsorshipSetFlags } from '../transactions/sponsorshipSet'
import type { Transaction } from '../transactions/transaction'
import { TrustSetFlags } from '../transactions/trustSet'
import { VaultCreateFlags } from '../transactions/vaultCreate'
Expand Down Expand Up @@ -66,6 +67,7 @@ const txToFlag = {
TrustSet: TrustSetFlags,
VaultCreate: VaultCreateFlags,
XChainModifyBridge: XChainModifyBridgeFlags,
SponsorshipSet: SponsorshipSetFlags,
}

function isTxToFlagKey(
Expand Down
49 changes: 49 additions & 0 deletions packages/xrpl/test/models/sponsorshipSet.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { validateTrustSet } from '../../src/models/transactions/trustSet'
import { assertTxIsValid, assertTxValidationError } from '../testUtils'

const assertValid = (tx: any): void => assertTxIsValid(tx, validateTrustSet)
const assertInvalid = (tx: any, message: string): void =>
assertTxValidationError(tx, validateTrustSet, message)

/**
* SponsorshipSet Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('SponsorshipSet', function () {
let sponsorshipSet: any

beforeEach(function () {
sponsorshipSet = {
TransactionType: 'SponsorshipSet',
SponsorAccount: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
FeeAmount: '100',
MaxFee: '100',
ReserveCount: 1,
}
})

it('verifies valid SponsorshipSet', function () {
assertValid(sponsorshipSet)

delete sponsorshipSet.SponsorAccount
sponsorshipSet.Sponsee = 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo'
assertValid(sponsorshipSet)
})

it('throws when invalid SponsorAccount and Sponsee', function () {
delete sponsorshipSet.SponsorAccount
delete sponsorshipSet.Sponsee
assertInvalid(
sponsorshipSet,
'SponsorshipSet: SponsorAccount or Sponsee must be set, but not both',
)

sponsorshipSet.SponsorAccount = 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo'
sponsorshipSet.Sponsee = 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo'
assertInvalid(
sponsorshipSet,
'SponsorshipSet: SponsorAccount and Sponsee cannot be both set',
)
})
})
Loading
Loading