From 87b50cfe30ce6785ec3003d8437415854a66aacc Mon Sep 17 00:00:00 2001 From: Phu Pham Date: Tue, 25 Aug 2026 07:39:35 -1000 Subject: [PATCH] reconcile network id between client and custodian --- docs/api-md/classes/CustodyApiError.md | 10 +- docs/api-md/classes/CustodyAuthError.md | 2 +- docs/api-md/classes/DuplicateSignerError.md | 8 +- docs/api-md/classes/IntentPendingError.md | 10 +- docs/api-md/classes/MultiStepFailureError.md | 8 +- docs/api-md/classes/NetworkMismatchError.md | 75 ++++++++ docs/api-md/classes/PalisadeApiError.md | 10 +- docs/api-md/classes/PalisadeAuthError.md | 2 +- docs/api-md/classes/SimpleXRPLClient.md | 56 +++--- docs/api-md/classes/SimpleXRPLError.md | 1 + docs/api-md/classes/Token.md | 117 ++++------- docs/api-md/classes/XrpldSubmitError.md | 8 +- .../api-md/functions/validateTokenMetadata.md | 2 +- docs/api-md/globals.md | 22 +-- docs/api-md/interfaces/Account.md | 21 +- docs/api-md/interfaces/CancelOfferParams.md | 15 -- docs/api-md/interfaces/CreateOfferParams.md | 59 ------ docs/api-md/interfaces/Custodian.md | 18 +- docs/api-md/interfaces/FeeIntent.md | 6 +- docs/api-md/interfaces/IntentObserver.md | 6 +- docs/api-md/interfaces/MptFlags.md | 65 ------- docs/api-md/interfaces/MptIssueIntent.md | 15 -- docs/api-md/interfaces/NetworkInfo.md | 13 ++ docs/api-md/interfaces/OfferFlags.md | 62 ------ docs/api-md/interfaces/OnChainResult.md | 6 +- docs/api-md/interfaces/SignedEnvelope.md | 6 +- docs/api-md/interfaces/SubmissionContext.md | 18 +- docs/api-md/interfaces/SubmissionHandle.md | 14 +- .../interfaces/SubmissionResultFields.md | 10 +- ...orizeParams.md => TokenAuthorizeParams.md} | 2 +- docs/api-md/interfaces/TokenClawbackParams.md | 25 +++ docs/api-md/interfaces/TokenData.md | 20 +- ...DestroyParams.md => TokenDestroyParams.md} | 2 +- docs/api-md/interfaces/TokenFlags.md | 65 +++++++ ...ptHolderParams.md => TokenHolderParams.md} | 2 +- .../{MptIssueFlags.md => TokenIssueFlags.md} | 2 +- docs/api-md/interfaces/TokenIssueIntent.md | 15 ++ ...{MptIssueParams.md => TokenIssueParams.md} | 6 +- docs/api-md/interfaces/TokenListEntry.md | 8 +- .../interfaces/TokenListOffersParams.md | 21 -- docs/api-md/interfaces/TokenListParams.md | 6 +- docs/api-md/interfaces/TokenListResult.md | 6 +- .../{MptLockParams.md => TokenLockParams.md} | 2 +- docs/api-md/interfaces/TokenRetrieveParams.md | 4 +- docs/api-md/interfaces/TokenRetrieveResult.md | 6 +- docs/api-md/type-aliases/AccountSelector.md | 2 +- .../type-aliases/CustodyTransactionResult.md | 2 +- .../type-aliases/PalisadeTransactionResult.md | 2 +- docs/api-md/type-aliases/SubmissionResult.md | 2 +- src/client/account-index.ts | 160 ++++++++++++--- src/client/client.ts | 60 +++++- .../ripple/discovery/account-discovery.ts | 63 +++--- .../ripple/mapping/mpt-operations.ts | 21 +- .../ripple/submission/transaction-polling.ts | 49 ++++- .../ripple/transport/custody-http-client.ts | 16 +- src/domain/model.ts | 10 + src/errors.ts | 38 ++++ src/verticals/index.ts | 21 +- src/verticals/token.helpers.ts | 57 +----- src/verticals/token.reads.ts | 4 +- src/verticals/token.ts | 149 +++++--------- src/verticals/token.types.ts | 89 ++------- test/unit/client/network-selection.test.ts | 182 ++++++++++++++++++ .../account-discovery.test.ts | 4 + test/unit/custody-discovery/test-utils.ts | 8 +- .../custody-mapping/xrpl-operations.test.ts | 12 +- test/unit/flags/flag-matrix.test.ts | 28 +-- .../ripple-custody/ripple-custody.test.ts | 1 + .../transaction-polling.test.ts | 162 ++++++++++++++++ test/unit/token/token.test.ts | 60 ++---- 70 files changed, 1224 insertions(+), 835 deletions(-) create mode 100644 docs/api-md/classes/NetworkMismatchError.md delete mode 100644 docs/api-md/interfaces/CancelOfferParams.md delete mode 100644 docs/api-md/interfaces/CreateOfferParams.md delete mode 100644 docs/api-md/interfaces/MptFlags.md delete mode 100644 docs/api-md/interfaces/MptIssueIntent.md delete mode 100644 docs/api-md/interfaces/OfferFlags.md rename docs/api-md/interfaces/{MptAuthorizeParams.md => TokenAuthorizeParams.md} (92%) create mode 100644 docs/api-md/interfaces/TokenClawbackParams.md rename docs/api-md/interfaces/{MptDestroyParams.md => TokenDestroyParams.md} (92%) create mode 100644 docs/api-md/interfaces/TokenFlags.md rename docs/api-md/interfaces/{MptHolderParams.md => TokenHolderParams.md} (95%) rename docs/api-md/interfaces/{MptIssueFlags.md => TokenIssueFlags.md} (98%) create mode 100644 docs/api-md/interfaces/TokenIssueIntent.md rename docs/api-md/interfaces/{MptIssueParams.md => TokenIssueParams.md} (92%) delete mode 100644 docs/api-md/interfaces/TokenListOffersParams.md rename docs/api-md/interfaces/{MptLockParams.md => TokenLockParams.md} (95%) create mode 100644 test/unit/client/network-selection.test.ts create mode 100644 test/unit/ripple-custody/transaction-polling.test.ts diff --git a/docs/api-md/classes/CustodyApiError.md b/docs/api-md/classes/CustodyApiError.md index ef347c7..9eacff5 100644 --- a/docs/api-md/classes/CustodyApiError.md +++ b/docs/api-md/classes/CustodyApiError.md @@ -1,6 +1,6 @@ # Class: CustodyApiError -Defined in: [errors.ts:116](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L116) +Defined in: [errors.ts:154](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L154) A Ripple Custody API call returned an error. The diagnostic `hint` and full response body are preserved for the caller to surface. @@ -15,7 +15,7 @@ response body are preserved for the caller to surface. > **new CustodyApiError**(`status`, `raw`, `hint`?): [`CustodyApiError`](CustodyApiError.md) -Defined in: [errors.ts:128](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L128) +Defined in: [errors.ts:166](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L166) Construct a CustodyApiError. @@ -41,7 +41,7 @@ Construct a CustodyApiError. > `readonly` `optional` **hint**: `string` -Defined in: [errors.ts:118](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L118) +Defined in: [errors.ts:156](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L156) *** @@ -61,7 +61,7 @@ Defined in: [errors.ts:9](https://github.com/ripple/simpleXRPL/blob/main/src/err > `readonly` **raw**: `unknown` -Defined in: [errors.ts:119](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L119) +Defined in: [errors.ts:157](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L157) *** @@ -69,4 +69,4 @@ Defined in: [errors.ts:119](https://github.com/ripple/simpleXRPL/blob/main/src/e > `readonly` **status**: `number` -Defined in: [errors.ts:117](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L117) +Defined in: [errors.ts:155](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L155) diff --git a/docs/api-md/classes/CustodyAuthError.md b/docs/api-md/classes/CustodyAuthError.md index 1ecde3e..450d38a 100644 --- a/docs/api-md/classes/CustodyAuthError.md +++ b/docs/api-md/classes/CustodyAuthError.md @@ -1,6 +1,6 @@ # Class: CustodyAuthError -Defined in: [errors.ts:110](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L110) +Defined in: [errors.ts:148](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L148) Authenticating with Ripple Custody failed (challenge/JWT exchange or refresh). diff --git a/docs/api-md/classes/DuplicateSignerError.md b/docs/api-md/classes/DuplicateSignerError.md index 9fa186b..ccd25e4 100644 --- a/docs/api-md/classes/DuplicateSignerError.md +++ b/docs/api-md/classes/DuplicateSignerError.md @@ -1,6 +1,6 @@ # Class: DuplicateSignerError -Defined in: [errors.ts:88](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L88) +Defined in: [errors.ts:126](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L126) Two configured signers point at the same backend tenant — the same `kind` and the same `tenantId` (§3.1). The client rejects this at init so @@ -16,7 +16,7 @@ one backend is never registered twice; drop the duplicate signer. > **new DuplicateSignerError**(`kind`, `tenantId`): [`DuplicateSignerError`](DuplicateSignerError.md) -Defined in: [errors.ts:98](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L98) +Defined in: [errors.ts:136](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L136) Construct a DuplicateSignerError. @@ -41,7 +41,7 @@ Construct a DuplicateSignerError. > `readonly` **kind**: [`CustodianKind`](../type-aliases/CustodianKind.md) -Defined in: [errors.ts:89](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L89) +Defined in: [errors.ts:127](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L127) *** @@ -61,4 +61,4 @@ Defined in: [errors.ts:9](https://github.com/ripple/simpleXRPL/blob/main/src/err > `readonly` **tenantId**: `string` -Defined in: [errors.ts:90](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L90) +Defined in: [errors.ts:128](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L128) diff --git a/docs/api-md/classes/IntentPendingError.md b/docs/api-md/classes/IntentPendingError.md index 9c5542e..a9c9953 100644 --- a/docs/api-md/classes/IntentPendingError.md +++ b/docs/api-md/classes/IntentPendingError.md @@ -1,6 +1,6 @@ # Class: IntentPendingError -Defined in: [errors.ts:171](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L171) +Defined in: [errors.ts:209](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L209) Not a failure — a "still waiting" signal raised when a custodian intent has not reached a terminal state before the SDK's timeout. Resume later with the @@ -16,7 +16,7 @@ carried `intentId`. > **new IntentPendingError**(`intentId`, `custodian`, `lastState`): [`IntentPendingError`](IntentPendingError.md) -Defined in: [errors.ts:183](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L183) +Defined in: [errors.ts:221](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L221) Construct an IntentPendingError. @@ -42,7 +42,7 @@ Construct an IntentPendingError. > `readonly` **custodian**: `"ripple-custody"` \| `"palisade-custody"` -Defined in: [errors.ts:173](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L173) +Defined in: [errors.ts:211](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L211) *** @@ -50,7 +50,7 @@ Defined in: [errors.ts:173](https://github.com/ripple/simpleXRPL/blob/main/src/e > `readonly` **intentId**: `string` -Defined in: [errors.ts:172](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L172) +Defined in: [errors.ts:210](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L210) *** @@ -58,7 +58,7 @@ Defined in: [errors.ts:172](https://github.com/ripple/simpleXRPL/blob/main/src/e > `readonly` **lastState**: `string` -Defined in: [errors.ts:174](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L174) +Defined in: [errors.ts:212](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L212) *** diff --git a/docs/api-md/classes/MultiStepFailureError.md b/docs/api-md/classes/MultiStepFailureError.md index 9cc4164..1684da2 100644 --- a/docs/api-md/classes/MultiStepFailureError.md +++ b/docs/api-md/classes/MultiStepFailureError.md @@ -1,6 +1,6 @@ # Class: MultiStepFailureError -Defined in: [errors.ts:220](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L220) +Defined in: [errors.ts:258](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L258) A multi-step operation failed partway through. simpleXRPL does not roll back; the already-committed steps are carried so the caller can reconcile manually. @@ -15,7 +15,7 @@ already-committed steps are carried so the caller can reconcile manually. > **new MultiStepFailureError**(`committed`, `failed`): [`MultiStepFailureError`](MultiStepFailureError.md) -Defined in: [errors.ts:235](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L235) +Defined in: [errors.ts:273](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L273) Construct a MultiStepFailureError. @@ -42,7 +42,7 @@ Construct a MultiStepFailureError. > `readonly` **committed**: readonly [`SubmissionResult`](../type-aliases/SubmissionResult.md)[] -Defined in: [errors.ts:221](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L221) +Defined in: [errors.ts:259](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L259) *** @@ -50,7 +50,7 @@ Defined in: [errors.ts:221](https://github.com/ripple/simpleXRPL/blob/main/src/e > `readonly` **failed**: \{ `error`: [`SimpleXRPLError`](SimpleXRPLError.md); `step`: `number`; \} -Defined in: [errors.ts:222](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L222) +Defined in: [errors.ts:260](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L260) #### error diff --git a/docs/api-md/classes/NetworkMismatchError.md b/docs/api-md/classes/NetworkMismatchError.md new file mode 100644 index 0000000..b68814b --- /dev/null +++ b/docs/api-md/classes/NetworkMismatchError.md @@ -0,0 +1,75 @@ +# Class: NetworkMismatchError + +Defined in: [errors.ts:90](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L90) + +An account exists at a custodian, but only on XRPL network(s) other than the +one the client is connected to. The SDK refuses to route a transaction to the +wrong network (which would silently strand it), so point the client's +`xrpldUrl` at a node on a matching network, or register the address on this +network at the custodian. + +## Extends + +- [`SimpleXRPLError`](SimpleXRPLError.md) + +## Constructors + +### new NetworkMismatchError() + +> **new NetworkMismatchError**(`account`, `clientNetworkId`, `availableNetworkIds`): [`NetworkMismatchError`](NetworkMismatchError.md) + +Defined in: [errors.ts:103](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L103) + +Construct a NetworkMismatchError. + +#### Parameters + +| Parameter | Type | Description | +| ------ | ------ | ------ | +| `account` | `string` | The r-address that has no record on the client's network. | +| `clientNetworkId` | `undefined` \| `number` | The network id the client is connected to, or `undefined` when it could not be determined. | +| `availableNetworkIds` | readonly `number`[] | The network ids the account does exist on. | + +#### Returns + +[`NetworkMismatchError`](NetworkMismatchError.md) + +#### Overrides + +[`SimpleXRPLError`](SimpleXRPLError.md).[`constructor`](SimpleXRPLError.md#constructors) + +## Properties + +### account + +> `readonly` **account**: `string` + +Defined in: [errors.ts:91](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L91) + +*** + +### availableNetworkIds + +> `readonly` **availableNetworkIds**: readonly `number`[] + +Defined in: [errors.ts:93](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L93) + +*** + +### clientNetworkId + +> `readonly` **clientNetworkId**: `undefined` \| `number` + +Defined in: [errors.ts:92](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L92) + +*** + +### name + +> `readonly` **name**: `string` + +Defined in: [errors.ts:9](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L9) + +#### Inherited from + +[`SimpleXRPLError`](SimpleXRPLError.md).[`name`](SimpleXRPLError.md#name) diff --git a/docs/api-md/classes/PalisadeApiError.md b/docs/api-md/classes/PalisadeApiError.md index f0b6516..3a20a08 100644 --- a/docs/api-md/classes/PalisadeApiError.md +++ b/docs/api-md/classes/PalisadeApiError.md @@ -1,6 +1,6 @@ # Class: PalisadeApiError -Defined in: [errors.ts:146](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L146) +Defined in: [errors.ts:184](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L184) A Palisade API call returned an error. The diagnostic `hint` (the `rpcStatus.message` Palisade's equivalent of Custody's `processing.hint`) @@ -16,7 +16,7 @@ and full response body are preserved for the caller to surface. > **new PalisadeApiError**(`status`, `raw`, `hint`?): [`PalisadeApiError`](PalisadeApiError.md) -Defined in: [errors.ts:158](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L158) +Defined in: [errors.ts:196](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L196) Construct a PalisadeApiError. @@ -42,7 +42,7 @@ Construct a PalisadeApiError. > `readonly` `optional` **hint**: `string` -Defined in: [errors.ts:148](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L148) +Defined in: [errors.ts:186](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L186) *** @@ -62,7 +62,7 @@ Defined in: [errors.ts:9](https://github.com/ripple/simpleXRPL/blob/main/src/err > `readonly` **raw**: `unknown` -Defined in: [errors.ts:149](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L149) +Defined in: [errors.ts:187](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L187) *** @@ -70,4 +70,4 @@ Defined in: [errors.ts:149](https://github.com/ripple/simpleXRPL/blob/main/src/e > `readonly` **status**: `number` -Defined in: [errors.ts:147](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L147) +Defined in: [errors.ts:185](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L185) diff --git a/docs/api-md/classes/PalisadeAuthError.md b/docs/api-md/classes/PalisadeAuthError.md index 869221a..d0be407 100644 --- a/docs/api-md/classes/PalisadeAuthError.md +++ b/docs/api-md/classes/PalisadeAuthError.md @@ -1,6 +1,6 @@ # Class: PalisadeAuthError -Defined in: [errors.ts:139](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L139) +Defined in: [errors.ts:177](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L177) Authenticating with Palisade failed (API key). diff --git a/docs/api-md/classes/SimpleXRPLClient.md b/docs/api-md/classes/SimpleXRPLClient.md index 55a8f09..8ca94df 100644 --- a/docs/api-md/classes/SimpleXRPLClient.md +++ b/docs/api-md/classes/SimpleXRPLClient.md @@ -1,6 +1,6 @@ # Class: SimpleXRPLClient -Defined in: [client/client.ts:43](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L43) +Defined in: [client/client.ts:70](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L70) The runtime client. Binds a set of pre-constructed custodians to a network, flattens their discovered accounts into a single address to custodian index, @@ -20,7 +20,7 @@ its custodian through the acted-on account at call time. > `readonly` **account**: [`AccountVertical`](AccountVertical.md) -Defined in: [client/client.ts:69](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L69) +Defined in: [client/client.ts:96](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L96) Account settings, regular key, and deposit preauthorization. @@ -30,7 +30,7 @@ Account settings, regular key, and deposit preauthorization. > `readonly` **credential**: [`Credential`](Credential.md) -Defined in: [client/client.ts:63](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L63) +Defined in: [client/client.ts:90](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L90) On-ledger credentials (issue, accept, delete). @@ -40,7 +40,7 @@ On-ledger credentials (issue, accept, delete). > `readonly` **domain**: [`Domain`](Domain.md) -Defined in: [client/client.ts:66](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L66) +Defined in: [client/client.ts:93](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L93) Permissioned domains (create, update, delete). @@ -50,7 +50,7 @@ Permissioned domains (create, update, delete). > `readonly` **intent**: [`IntentInspector`](IntentInspector.md) -Defined in: [client/client.ts:72](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L72) +Defined in: [client/client.ts:99](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L99) Read-only observation of custodian governance intents (status/await). @@ -60,7 +60,7 @@ Read-only observation of custodian governance intents (status/await). > `readonly` **iou**: [`IOU`](IOU.md) -Defined in: [client/client.ts:57](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L57) +Defined in: [client/client.ts:84](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L84) Issued-currency (IOU) operations: issue, transfer, authorize, lock, offers. @@ -70,7 +70,7 @@ Issued-currency (IOU) operations: issue, transfer, authorize, lock, offers. > `readonly` **network**: [`NetworkInfo`](../interfaces/NetworkInfo.md) -Defined in: [client/client.ts:45](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L45) +Defined in: [client/client.ts:72](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L72) The network this client is bound to. @@ -80,7 +80,7 @@ The network this client is bound to. > `readonly` **pollMptIssuanceId**: `undefined` \| (`intentId`) => `Promise`\<`string`\> -Defined in: [client/client.ts:79](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L79) +Defined in: [client/client.ts:106](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L106) Poll the Ripple Custody transaction layer until the on-chain transaction linked to `intentId` is confirmed, then return its MPT issuance ID. @@ -96,7 +96,7 @@ linked to `intentId` is confirmed, then return its MPT issuance ID. > `readonly` **primarySigner**: `undefined` \| [`Custodian`](../interfaces/Custodian.md) -Defined in: [client/client.ts:51](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L51) +Defined in: [client/client.ts:78](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L78) The default signer, used when an operation is called without an explicit account. @@ -106,7 +106,7 @@ The default signer, used when an operation is called without an explicit account > `readonly` **signers**: readonly [`Custodian`](../interfaces/Custodian.md)[] -Defined in: [client/client.ts:48](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L48) +Defined in: [client/client.ts:75](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L75) The registered custodians (0..N). @@ -116,9 +116,9 @@ The registered custodians (0..N). > `readonly` **token**: [`Token`](Token.md) -Defined in: [client/client.ts:60](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L60) +Defined in: [client/client.ts:87](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L87) -Multi-Purpose Token (MPT) family and DEX offers. +Multi-Purpose Token (MPT) family. *** @@ -126,7 +126,7 @@ Multi-Purpose Token (MPT) family and DEX offers. > `readonly` **xrp**: [`XRP`](XRP.md) -Defined in: [client/client.ts:54](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L54) +Defined in: [client/client.ts:81](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L81) Native-XRP value transfers. @@ -138,7 +138,7 @@ Native-XRP value transfers. > **get** **accounts**(): `ReadonlyMap`\<`string`, [`Account`](../interfaces/Account.md)\> -Defined in: [client/client.ts:119](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L119) +Defined in: [client/client.ts:146](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L146) All discovered accounts, keyed by r-address. @@ -156,7 +156,7 @@ The address to account index. > **get** **ledger**(): [`LedgerPort`](../interfaces/LedgerPort.md) -Defined in: [client/client.ts:129](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L129) +Defined in: [client/client.ts:156](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L156) The ledger connection for reads, autofill, and Local/raw submission. Created lazily from `network.xrpldUrl` when none was injected. @@ -179,7 +179,7 @@ The shared ledger connection for autofill and Local/raw submission. > **connect**(): `Promise`\<`void`\> -Defined in: [client/client.ts:272](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L272) +Defined in: [client/client.ts:320](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L320) Open the ledger connection. Optional — the ledger connects lazily on first use (reads, autofill, submission), so most callers never need to call this; @@ -195,7 +195,7 @@ it's useful only to pre-warm the connection. Idempotent. > **disconnect**(): `Promise`\<`void`\> -Defined in: [client/client.ts:277](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L277) +Defined in: [client/client.ts:325](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L325) Close the ledger connection (no-op for a ledger that manages its own). @@ -209,7 +209,7 @@ Close the ledger connection (no-op for a ledger that manages its own). > **primaryAddress**(): `undefined` \| `string` -Defined in: [client/client.ts:216](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L216) +Defined in: [client/client.ts:264](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L264) The primary signer's account address, or `undefined` on a no-signer client. Read methods use this as the default account to query; it never throws, so @@ -231,7 +231,7 @@ The primary account's r-address, or `undefined`. > **refreshAccounts**(): `Promise`\<`void`\> -Defined in: [client/client.ts:192](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L192) +Defined in: [client/client.ts:237](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L237) Re-discover every custodian's accounts and rebuild the index. New accounts become addressable; accounts removed upstream are gone on next lookup. @@ -244,13 +244,18 @@ become addressable; accounts removed upstream are gone on next lookup. [AmbiguousAccountError](AmbiguousAccountError.md) if an r-address is claimed by two custodians. +#### Throws + +[NetworkMismatchError](NetworkMismatchError.md) if a signer's primary account exists only + on XRPL networks other than the connected one. + *** ### registerLocalAccount() > **registerLocalAccount**(`seed`): [`Account`](../interfaces/Account.md) -Defined in: [client/client.ts:203](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L203) +Defined in: [client/client.ts:251](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L251) Register a locally-signed account at runtime so subsequent operations can act on it. Used by `Account.create` to make a freshly generated account usable @@ -278,7 +283,7 @@ The registered account. > **requireSigner**(): [`Custodian`](../interfaces/Custodian.md) -Defined in: [client/client.ts:258](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L258) +Defined in: [client/client.ts:306](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L306) Return the primary signer, or throw if the client has none. @@ -298,7 +303,7 @@ The primary signer. > **resolveAccount**(`selector`?): [`Account`](../interfaces/Account.md) -Defined in: [client/client.ts:230](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L230) +Defined in: [client/client.ts:278](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L278) Resolve the account an operation acts on. @@ -333,7 +338,7 @@ The resolved account. > `static` **init**(`config`): `Promise`\<[`SimpleXRPLClient`](SimpleXRPLClient.md)\> -Defined in: [client/client.ts:146](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L146) +Defined in: [client/client.ts:175](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L175) Bind custodians to a network and discover their accounts. The only entry point; the runtime client is never constructed via `new`. @@ -357,3 +362,8 @@ A ready client. #### Throws [AmbiguousAccountError](AmbiguousAccountError.md) if an r-address is claimed by two custodians. + +#### Throws + +[NetworkMismatchError](NetworkMismatchError.md) if a signer's primary account exists only + on XRPL networks other than the one `xrpldUrl` points at. diff --git a/docs/api-md/classes/SimpleXRPLError.md b/docs/api-md/classes/SimpleXRPLError.md index 7fce16a..5e86344 100644 --- a/docs/api-md/classes/SimpleXRPLError.md +++ b/docs/api-md/classes/SimpleXRPLError.md @@ -16,6 +16,7 @@ typed: distinct underlying failure modes are not flattened into one class. - [`NoSignerError`](NoSignerError.md) - [`AccountNotFoundError`](AccountNotFoundError.md) - [`AmbiguousAccountError`](AmbiguousAccountError.md) +- [`NetworkMismatchError`](NetworkMismatchError.md) - [`DuplicateSignerError`](DuplicateSignerError.md) - [`CustodyAuthError`](CustodyAuthError.md) - [`CustodyApiError`](CustodyApiError.md) diff --git a/docs/api-md/classes/Token.md b/docs/api-md/classes/Token.md index 0e96807..4e7a0d0 100644 --- a/docs/api-md/classes/Token.md +++ b/docs/api-md/classes/Token.md @@ -1,8 +1,12 @@ # Class: Token -Defined in: [verticals/token.ts:48](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L48) +Defined in: [verticals/token.ts:41](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L41) -The Token vertical: the Multi-Purpose Token (MPT) family and DEX offers. +The Token vertical: the Multi-Purpose Token (MPT) family. + +DEX offers are not exposed here: the MPT DEX amendment is not yet live +on-chain, so MPTs cannot be traded on the order book. XRP/IOU offers belong +to the IOU vertical (`client.iou.buyOffer`/`sellOffer`/`cancelOffer`). ## Constructors @@ -10,7 +14,7 @@ The Token vertical: the Multi-Purpose Token (MPT) family and DEX offers. > **new Token**(`host`): [`Token`](Token.md) -Defined in: [verticals/token.ts:56](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L56) +Defined in: [verticals/token.ts:49](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L49) Construct the Token vertical. @@ -30,7 +34,7 @@ Construct the Token vertical. > **authorize**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<\{ `mptIssuanceId`: `string`; \}\>\> -Defined in: [verticals/token.ts:168](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L168) +Defined in: [verticals/token.ts:146](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L146) Opt the calling account in to hold an MPT issuance. @@ -38,7 +42,7 @@ Opt the calling account in to hold an MPT issuance. | Parameter | Type | Description | | ------ | ------ | ------ | -| `params` | [`MptAuthorizeParams`](../interfaces/MptAuthorizeParams.md) | The issuance id. | +| `params` | [`TokenAuthorizeParams`](../interfaces/TokenAuthorizeParams.md) | The issuance id. | | `options`? | [`TokenWriteOptions`](../interfaces/TokenWriteOptions.md) | Source account and fee override. | #### Returns @@ -49,53 +53,34 @@ The submission result. *** -### cancelOffer() - -> **cancelOffer**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<\{ `offerSequence`: `number`; \}\>\> - -Defined in: [verticals/token.ts:366](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L366) - -Cancel a standing offer. - -#### Parameters - -| Parameter | Type | Description | -| ------ | ------ | ------ | -| `params` | [`CancelOfferParams`](../interfaces/CancelOfferParams.md) | The sequence number of the offer to cancel. | -| `options`? | [`TokenWriteOptions`](../interfaces/TokenWriteOptions.md) | Source account and fee override. | - -#### Returns +### clawback() -`Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<\{ `offerSequence`: `number`; \}\>\> +> **clawback**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<\{ `amount`: `string`; `holder`: `string`; \}\>\> -The submission result. +Defined in: [verticals/token.ts:311](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L311) -*** +Reclaim a holder's MPT balance back to the issuer. -### createOffer() - -> **createOffer**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<`undefined`\>\> - -Defined in: [verticals/token.ts:329](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L329) - -Place an offer on the decentralized exchange. +Requires the issuance to have been created with `canClawback` (the SDK +default). The holder whose balance is reclaimed is named explicitly, and +the amount's asset must be an MPT. #### Parameters | Parameter | Type | Description | | ------ | ------ | ------ | -| `params` | [`CreateOfferParams`](../interfaces/CreateOfferParams.md) | The amounts to give and receive, plus offer flags. | -| `options`? | [`TokenWriteOptions`](../interfaces/TokenWriteOptions.md) | Source account and fee override. | +| `params` | [`TokenClawbackParams`](../interfaces/TokenClawbackParams.md) | The holder and MPT amount to claw back. | +| `options`? | [`TokenWriteOptions`](../interfaces/TokenWriteOptions.md) | Issuer account, fee override, and idempotency key. | #### Returns -`Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<`undefined`\>\> +`Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<\{ `amount`: `string`; `holder`: `string`; \}\>\> -The submission result. +The result, echoing `{ holder, amount }` as its intent output. #### Throws -[IntentValidationError](IntentValidationError.md) if either amount is an MPT. +[IntentValidationError](IntentValidationError.md) if the amount's asset is not an MPT. *** @@ -103,7 +88,7 @@ The submission result. > **destroy**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<\{ `mptIssuanceId`: `string`; \}\>\> -Defined in: [verticals/token.ts:252](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L252) +Defined in: [verticals/token.ts:230](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L230) Destroy an MPT issuance (only when no tokens are outstanding). @@ -111,7 +96,7 @@ Destroy an MPT issuance (only when no tokens are outstanding). | Parameter | Type | Description | | ------ | ------ | ------ | -| `params` | [`MptDestroyParams`](../interfaces/MptDestroyParams.md) | The issuance id. | +| `params` | [`TokenDestroyParams`](../interfaces/TokenDestroyParams.md) | The issuance id. | | `options`? | [`TokenWriteOptions`](../interfaces/TokenWriteOptions.md) | Source account and fee override. | #### Returns @@ -126,7 +111,7 @@ The submission result. > **grantHolder**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<\{ `mptIssuanceId`: `string`; \}\>\> -Defined in: [verticals/token.ts:196](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L196) +Defined in: [verticals/token.ts:174](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L174) Issuer grants a specific holder permission to hold this MPT (allow-listing). @@ -134,7 +119,7 @@ Issuer grants a specific holder permission to hold this MPT (allow-listing). | Parameter | Type | Description | | ------ | ------ | ------ | -| `params` | [`MptHolderParams`](../interfaces/MptHolderParams.md) | The issuance id and the holder to authorize. | +| `params` | [`TokenHolderParams`](../interfaces/TokenHolderParams.md) | The issuance id and the holder to authorize. | | `options`? | [`TokenWriteOptions`](../interfaces/TokenWriteOptions.md) | Source account and fee override. | #### Returns @@ -147,9 +132,9 @@ The submission result. ### issue() -> **issue**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<[`MptIssueIntent`](../interfaces/MptIssueIntent.md)\>\> +> **issue**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<[`TokenIssueIntent`](../interfaces/TokenIssueIntent.md)\>\> -Defined in: [verticals/token.ts:134](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L134) +Defined in: [verticals/token.ts:112](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L112) Create a new MPT issuance. @@ -172,12 +157,12 @@ https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0089-multi-purpose-token | Parameter | Type | Description | | ------ | ------ | ------ | -| `params` | [`MptIssueParams`](../interfaces/MptIssueParams.md) | Issuance settings (metadata required) and flag overrides. | +| `params` | [`TokenIssueParams`](../interfaces/TokenIssueParams.md) | Issuance settings (metadata required) and flag overrides. | | `options`? | [`TokenWriteOptions`](../interfaces/TokenWriteOptions.md) | Source account and fee override. | #### Returns -`Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<[`MptIssueIntent`](../interfaces/MptIssueIntent.md)\>\> +`Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<[`TokenIssueIntent`](../interfaces/TokenIssueIntent.md)\>\> The result, with the new `mptIssuanceId` as its intent output. @@ -202,7 +187,7 @@ await client.token.issue({ > **list**(`params`?): `Promise`\<[`TokenListResult`](../interfaces/TokenListResult.md)\> -Defined in: [verticals/token.ts:79](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L79) +Defined in: [verticals/token.ts:72](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L72) List the MPTs an account holds (default) or issued. No signer required. @@ -220,33 +205,11 @@ The token ids and shaped entries, index-aligned. *** -### listOffers() - -> **listOffers**(`params`?): `Promise`\<[`ListOffersResult`](../interfaces/ListOffersResult.md)\> - -Defined in: [verticals/token.ts:89](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L89) - -List the open DEX offers placed by an account. No signer required. - -#### Parameters - -| Parameter | Type | Description | -| ------ | ------ | ------ | -| `params`? | [`TokenListOffersParams`](../interfaces/TokenListOffersParams.md) | The account (default: the primary signer's account). | - -#### Returns - -`Promise`\<[`ListOffersResult`](../interfaces/ListOffersResult.md)\> - -The shaped offers (composable into offer write operations). - -*** - ### lock() > **lock**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<\{ `locked`: `boolean`; `mptIssuanceId`: `string`; \}\>\> -Defined in: [verticals/token.ts:224](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L224) +Defined in: [verticals/token.ts:202](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L202) Lock an MPT issuance, or a specific holder's balance when `holder` is given. @@ -254,7 +217,7 @@ Lock an MPT issuance, or a specific holder's balance when `holder` is given. | Parameter | Type | Description | | ------ | ------ | ------ | -| `params` | [`MptLockParams`](../interfaces/MptLockParams.md) | The issuance id and optional holder. | +| `params` | [`TokenLockParams`](../interfaces/TokenLockParams.md) | The issuance id and optional holder. | | `options`? | [`TokenWriteOptions`](../interfaces/TokenWriteOptions.md) | Source account and fee override. | #### Returns @@ -269,7 +232,7 @@ The submission result. > **retrieve**(`params`): `Promise`\<[`TokenRetrieveResult`](../interfaces/TokenRetrieveResult.md)\> -Defined in: [verticals/token.ts:67](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L67) +Defined in: [verticals/token.ts:60](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L60) Retrieve a single MPT issuance by id (point-in-time), with flags decoded to booleans and XLS-89 metadata decoded. No signer required. @@ -292,7 +255,7 @@ The issuance id and snapshot (or `undefined` data if absent). > **revokeHolder**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<\{ `mptIssuanceId`: `string`; \}\>\> -Defined in: [verticals/token.ts:210](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L210) +Defined in: [verticals/token.ts:188](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L188) Issuer revokes a specific holder's permission to hold this MPT. @@ -300,7 +263,7 @@ Issuer revokes a specific holder's permission to hold this MPT. | Parameter | Type | Description | | ------ | ------ | ------ | -| `params` | [`MptHolderParams`](../interfaces/MptHolderParams.md) | The issuance id and the holder to revoke. | +| `params` | [`TokenHolderParams`](../interfaces/TokenHolderParams.md) | The issuance id and the holder to revoke. | | `options`? | [`TokenWriteOptions`](../interfaces/TokenWriteOptions.md) | Source account and fee override. | #### Returns @@ -315,7 +278,7 @@ The submission result. > **transfer**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<\{ `amount`: `string`; `to`: `string`; \}\>\> -Defined in: [verticals/token.ts:296](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L296) +Defined in: [verticals/token.ts:274](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L274) Send an MPT amount to another account. @@ -342,7 +305,7 @@ The result, echoing the transfer as its intent output. > **unauthorize**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<\{ `mptIssuanceId`: `string`; \}\>\> -Defined in: [verticals/token.ts:182](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L182) +Defined in: [verticals/token.ts:160](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L160) Opt the calling account out of holding an MPT issuance (balance must be 0). @@ -350,7 +313,7 @@ Opt the calling account out of holding an MPT issuance (balance must be 0). | Parameter | Type | Description | | ------ | ------ | ------ | -| `params` | [`MptAuthorizeParams`](../interfaces/MptAuthorizeParams.md) | The issuance id. | +| `params` | [`TokenAuthorizeParams`](../interfaces/TokenAuthorizeParams.md) | The issuance id. | | `options`? | [`TokenWriteOptions`](../interfaces/TokenWriteOptions.md) | Source account and fee override. | #### Returns @@ -365,7 +328,7 @@ The submission result. > **unlock**(`params`, `options`?): `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<\{ `locked`: `boolean`; `mptIssuanceId`: `string`; \}\>\> -Defined in: [verticals/token.ts:238](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L238) +Defined in: [verticals/token.ts:216](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.ts#L216) Unlock a previously locked MPT issuance or holder balance. @@ -373,7 +336,7 @@ Unlock a previously locked MPT issuance or holder balance. | Parameter | Type | Description | | ------ | ------ | ------ | -| `params` | [`MptLockParams`](../interfaces/MptLockParams.md) | The issuance id and optional holder. | +| `params` | [`TokenLockParams`](../interfaces/TokenLockParams.md) | The issuance id and optional holder. | | `options`? | [`TokenWriteOptions`](../interfaces/TokenWriteOptions.md) | Source account and fee override. | #### Returns diff --git a/docs/api-md/classes/XrpldSubmitError.md b/docs/api-md/classes/XrpldSubmitError.md index d9c2185..e68bffb 100644 --- a/docs/api-md/classes/XrpldSubmitError.md +++ b/docs/api-md/classes/XrpldSubmitError.md @@ -1,6 +1,6 @@ # Class: XrpldSubmitError -Defined in: [errors.ts:199](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L199) +Defined in: [errors.ts:237](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L237) A xrpld submission was rejected. The `engineResult` and full response are preserved verbatim. @@ -15,7 +15,7 @@ preserved verbatim. > **new XrpldSubmitError**(`engineResult`, `raw`): [`XrpldSubmitError`](XrpldSubmitError.md) -Defined in: [errors.ts:209](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L209) +Defined in: [errors.ts:247](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L247) Construct a XrpldSubmitError. @@ -40,7 +40,7 @@ Construct a XrpldSubmitError. > `readonly` **engineResult**: `string` -Defined in: [errors.ts:200](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L200) +Defined in: [errors.ts:238](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L238) *** @@ -60,4 +60,4 @@ Defined in: [errors.ts:9](https://github.com/ripple/simpleXRPL/blob/main/src/err > `readonly` **raw**: `unknown` -Defined in: [errors.ts:201](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L201) +Defined in: [errors.ts:239](https://github.com/ripple/simpleXRPL/blob/main/src/errors.ts#L239) diff --git a/docs/api-md/functions/validateTokenMetadata.md b/docs/api-md/functions/validateTokenMetadata.md index fd653f8..17579ee 100644 --- a/docs/api-md/functions/validateTokenMetadata.md +++ b/docs/api-md/functions/validateTokenMetadata.md @@ -2,7 +2,7 @@ > **validateTokenMetadata**(`metadata`): `string`[] -Defined in: [verticals/token.helpers.ts:173](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.helpers.ts#L173) +Defined in: [verticals/token.helpers.ts:126](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.helpers.ts#L126) Check MPT metadata against the XLS-89 standard without throwing — the pre-flight companion to `Token.issue`. Accepts a structured object or a raw diff --git a/docs/api-md/globals.md b/docs/api-md/globals.md index 3501737..e33ed90 100644 --- a/docs/api-md/globals.md +++ b/docs/api-md/globals.md @@ -22,6 +22,7 @@ verticals, core types, and error classes) is built out incrementally. - [IOU](classes/IOU.md) - [LocalSigner](classes/LocalSigner.md) - [MultiStepFailureError](classes/MultiStepFailureError.md) +- [NetworkMismatchError](classes/NetworkMismatchError.md) - [NoSignerError](classes/NoSignerError.md) - [PalisadeApi](classes/PalisadeApi.md) - [PalisadeApiError](classes/PalisadeApiError.md) @@ -51,9 +52,7 @@ verticals, core types, and error classes) is built out incrementally. - [AccountSetParams](interfaces/AccountSetParams.md) - [AccountWriteOptions](interfaces/AccountWriteOptions.md) - [Amount](interfaces/Amount.md) -- [CancelOfferParams](interfaces/CancelOfferParams.md) - [Clock](interfaces/Clock.md) -- [CreateOfferParams](interfaces/CreateOfferParams.md) - [CredentialAcceptParams](interfaces/CredentialAcceptParams.md) - [CredentialData](interfaces/CredentialData.md) - [CredentialDeleteParams](interfaces/CredentialDeleteParams.md) @@ -110,17 +109,8 @@ verticals, core types, and error classes) is built out incrementally. - [LocalSignerCreateOptions](interfaces/LocalSignerCreateOptions.md) - [LocalSignerFromEnvOptions](interfaces/LocalSignerFromEnvOptions.md) - [Logger](interfaces/Logger.md) -- [MptAuthorizeParams](interfaces/MptAuthorizeParams.md) -- [MptDestroyParams](interfaces/MptDestroyParams.md) -- [MptFlags](interfaces/MptFlags.md) -- [MptHolderParams](interfaces/MptHolderParams.md) -- [MptIssueFlags](interfaces/MptIssueFlags.md) -- [MptIssueIntent](interfaces/MptIssueIntent.md) -- [MptIssueParams](interfaces/MptIssueParams.md) -- [MptLockParams](interfaces/MptLockParams.md) - [NativeSubmit](interfaces/NativeSubmit.md) - [NetworkInfo](interfaces/NetworkInfo.md) -- [OfferFlags](interfaces/OfferFlags.md) - [OfferSummary](interfaces/OfferSummary.md) - [OnChainResult](interfaces/OnChainResult.md) - [PalisadeCallArgs](interfaces/PalisadeCallArgs.md) @@ -142,11 +132,19 @@ verticals, core types, and error classes) is built out incrementally. - [SubmissionHost](interfaces/SubmissionHost.md) - [SubmissionResultFields](interfaces/SubmissionResultFields.md) - [SubmitRequest](interfaces/SubmitRequest.md) +- [TokenAuthorizeParams](interfaces/TokenAuthorizeParams.md) +- [TokenClawbackParams](interfaces/TokenClawbackParams.md) - [TokenData](interfaces/TokenData.md) +- [TokenDestroyParams](interfaces/TokenDestroyParams.md) +- [TokenFlags](interfaces/TokenFlags.md) +- [TokenHolderParams](interfaces/TokenHolderParams.md) +- [TokenIssueFlags](interfaces/TokenIssueFlags.md) +- [TokenIssueIntent](interfaces/TokenIssueIntent.md) +- [TokenIssueParams](interfaces/TokenIssueParams.md) - [TokenListEntry](interfaces/TokenListEntry.md) -- [TokenListOffersParams](interfaces/TokenListOffersParams.md) - [TokenListParams](interfaces/TokenListParams.md) - [TokenListResult](interfaces/TokenListResult.md) +- [TokenLockParams](interfaces/TokenLockParams.md) - [TokenRetrieveParams](interfaces/TokenRetrieveParams.md) - [TokenRetrieveResult](interfaces/TokenRetrieveResult.md) - [TokenTransferParams](interfaces/TokenTransferParams.md) diff --git a/docs/api-md/interfaces/Account.md b/docs/api-md/interfaces/Account.md index 1a3ac7e..4c6b69f 100644 --- a/docs/api-md/interfaces/Account.md +++ b/docs/api-md/interfaces/Account.md @@ -65,7 +65,7 @@ accounts, which carry no ledger default of their own). > `readonly` `optional` **metadata**: \{ `kind`: [`CustodianKind`](../type-aliases/CustodianKind.md); `tags`: readonly `string`[]; \} -Defined in: [domain/model.ts:59](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L59) +Defined in: [domain/model.ts:69](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L69) Optional, advisory-only metadata. @@ -79,11 +79,26 @@ Optional, advisory-only metadata. *** +### networkId? + +> `readonly` `optional` **networkId**: `number` + +Defined in: [domain/model.ts:56](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L56) + +The XRPL network this account record is scoped to, as the `network_id` a +node reports via `server_info` (Mainnet 0, Testnet 1, Devnet 2). The client +uses it to pick, among several records for one r-address, the one matching +the network it is connected to. `undefined` for network-agnostic holders +(a local wallet key works on any network) and for backends that expose no +network id. + +*** + ### publicKey? > `readonly` `optional` **publicKey**: `string` -Defined in: [domain/model.ts:53](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L53) +Defined in: [domain/model.ts:63](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L63) The account's XRPL public key (hex), when the custodian exposes it. Used to populate `SigningPubKey` on transactions signed by a backend that returns @@ -95,6 +110,6 @@ only the signature (e.g. Palisade's raw sign-only path). > `readonly` **signer**: [`Custodian`](Custodian.md) -Defined in: [domain/model.ts:56](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L56) +Defined in: [domain/model.ts:66](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L66) The custodian that discovered and signs for this account. diff --git a/docs/api-md/interfaces/CancelOfferParams.md b/docs/api-md/interfaces/CancelOfferParams.md deleted file mode 100644 index 92a3dcc..0000000 --- a/docs/api-md/interfaces/CancelOfferParams.md +++ /dev/null @@ -1,15 +0,0 @@ -# Interface: CancelOfferParams - -Defined in: [verticals/token.types.ts:187](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L187) - -Parameters for `Token.cancelOffer`. - -## Properties - -### offerSequence - -> `readonly` **offerSequence**: `number` - -Defined in: [verticals/token.types.ts:189](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L189) - -The sequence number of the offer to cancel. diff --git a/docs/api-md/interfaces/CreateOfferParams.md b/docs/api-md/interfaces/CreateOfferParams.md deleted file mode 100644 index 2bbcc73..0000000 --- a/docs/api-md/interfaces/CreateOfferParams.md +++ /dev/null @@ -1,59 +0,0 @@ -# Interface: CreateOfferParams - -Defined in: [verticals/token.types.ts:169](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L169) - -Parameters for `Token.createOffer`. - -## Properties - -### expiration? - -> `readonly` `optional` **expiration**: `number` - -Defined in: [verticals/token.types.ts:175](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L175) - -Offer expiration (seconds since the Ripple epoch). - -*** - -### flags? - -> `readonly` `optional` **flags**: [`OfferFlags`](OfferFlags.md) - -Defined in: [verticals/token.types.ts:183](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L183) - -Offer flags. Omit for a plain resting limit offer. - -#### Default Value - -No flags set — see [OfferFlags](OfferFlags.md) (all `false`). - -*** - -### offerSequence? - -> `readonly` `optional` **offerSequence**: `number` - -Defined in: [verticals/token.types.ts:177](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L177) - -A prior offer sequence to replace. - -*** - -### takerGets - -> `readonly` **takerGets**: [`Amount`](Amount.md) - -Defined in: [verticals/token.types.ts:171](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L171) - -What the account gives (XRP or IOU — MPT is not DEX-tradeable). - -*** - -### takerPays - -> `readonly` **takerPays**: [`Amount`](Amount.md) - -Defined in: [verticals/token.types.ts:173](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L173) - -What the account wants (XRP or IOU). diff --git a/docs/api-md/interfaces/Custodian.md b/docs/api-md/interfaces/Custodian.md index 51fdc31..8e20641 100644 --- a/docs/api-md/interfaces/Custodian.md +++ b/docs/api-md/interfaces/Custodian.md @@ -1,6 +1,6 @@ # Interface: Custodian -Defined in: [domain/model.ts:206](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L206) +Defined in: [domain/model.ts:216](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L216) A signing backend. Each implementation (local, Ripple Custody, Palisade) adapts the canonical xrpl.js transaction to one backend's API and submission @@ -12,7 +12,7 @@ flow, and is the unit of configuration on a client. > `readonly` **capabilities**: () => [`SignerCapabilities`](SignerCapabilities.md) -Defined in: [domain/model.ts:226](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L226) +Defined in: [domain/model.ts:236](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L236) What this custodian can sign, consulted at dispatch time. @@ -26,7 +26,7 @@ What this custodian can sign, consulted at dispatch time. > `readonly` **kind**: [`CustodianKind`](../type-aliases/CustodianKind.md) -Defined in: [domain/model.ts:208](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L208) +Defined in: [domain/model.ts:218](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L218) Which backend this custodian adapts. @@ -36,7 +36,7 @@ Which backend this custodian adapts. > `readonly` **listAccounts**: () => `Promise`\<[`Account`](Account.md)[]\> -Defined in: [domain/model.ts:223](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L223) +Defined in: [domain/model.ts:233](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L233) The full account list, discovered at construction. @@ -50,7 +50,7 @@ The full account list, discovered at construction. > `readonly` **primary**: [`AccountRef`](AccountRef.md) -Defined in: [domain/model.ts:220](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L220) +Defined in: [domain/model.ts:230](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L230) The custodian's primary account; it owns this account. @@ -60,7 +60,7 @@ The custodian's primary account; it owns this account. > `readonly` **sign**: (`tx`, `ctx`) => `Promise`\<[`SignedEnvelope`](SignedEnvelope.md)\> -Defined in: [domain/model.ts:229](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L229) +Defined in: [domain/model.ts:239](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L239) Produce a signed envelope for a transaction (raw-signing paths). @@ -81,7 +81,7 @@ Produce a signed envelope for a transaction (raw-signing paths). > `readonly` **submitAndWait**: (`tx`, `ctx`) => `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<`unknown`\>\> -Defined in: [domain/model.ts:239](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L239) +Defined in: [domain/model.ts:249](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L249) Submit and block until the transaction reaches a terminal state. The custodian returns the transport result; the vertical attaches the typed @@ -104,7 +104,7 @@ custodian returns the transport result; the vertical attaches the typed > `readonly` **submitAsync**: (`tx`, `ctx`) => `Promise`\<[`SubmissionHandle`](SubmissionHandle.md)\> -Defined in: [domain/model.ts:245](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L245) +Defined in: [domain/model.ts:255](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L255) Submit and return a handle once the backend has accepted the intent. @@ -125,7 +125,7 @@ Submit and return a handle once the backend has accepted the intent. > `readonly` `optional` **tenantId**: `string` -Defined in: [domain/model.ts:217](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L217) +Defined in: [domain/model.ts:227](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L227) The backend tenant this custodian is bound to — a Custody domain id, a Palisade org/client identity, etc. Two signers with the same `kind` and diff --git a/docs/api-md/interfaces/FeeIntent.md b/docs/api-md/interfaces/FeeIntent.md index 8b9b78e..a16f45c 100644 --- a/docs/api-md/interfaces/FeeIntent.md +++ b/docs/api-md/interfaces/FeeIntent.md @@ -1,6 +1,6 @@ # Interface: FeeIntent -Defined in: [domain/model.ts:89](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L89) +Defined in: [domain/model.ts:99](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L99) A normalized, optional fee intent. The public surface never takes raw drops; each path translates this to its backend's fee model. @@ -11,7 +11,7 @@ each path translates this to its backend's fee model. > `readonly` `optional` **maxFeeDrops**: `string` -Defined in: [domain/model.ts:94](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L94) +Defined in: [domain/model.ts:104](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L104) The maximum fee cap, in drops — the common contract across all paths. @@ -21,6 +21,6 @@ The maximum fee cap, in drops — the common contract across all paths. > `readonly` `optional` **priority**: `"low"` \| `"medium"` \| `"high"` -Defined in: [domain/model.ts:91](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L91) +Defined in: [domain/model.ts:101](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L101) Priority tier; backends that cannot honor it auto-price and warn. diff --git a/docs/api-md/interfaces/IntentObserver.md b/docs/api-md/interfaces/IntentObserver.md index 59b0a90..27451f8 100644 --- a/docs/api-md/interfaces/IntentObserver.md +++ b/docs/api-md/interfaces/IntentObserver.md @@ -1,6 +1,6 @@ # Interface: IntentObserver -Defined in: [domain/model.ts:258](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L258) +Defined in: [domain/model.ts:268](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L268) A custodian that can resume observation of a governance intent it previously created, addressed by the intent id (§10.4). Only backends with a governed @@ -14,7 +14,7 @@ await an intent whose original submission has already returned. > `readonly` **kind**: [`CustodianKind`](../type-aliases/CustodianKind.md) -Defined in: [domain/model.ts:260](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L260) +Defined in: [domain/model.ts:270](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L270) Which backend owns the intents this observer resumes. @@ -24,7 +24,7 @@ Which backend owns the intents this observer resumes. > `readonly` **observeIntent**: (`intentId`) => [`SubmissionHandle`](SubmissionHandle.md) -Defined in: [domain/model.ts:268](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L268) +Defined in: [domain/model.ts:278](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L278) Build a handle over an intent this custodian previously created. diff --git a/docs/api-md/interfaces/MptFlags.md b/docs/api-md/interfaces/MptFlags.md deleted file mode 100644 index 7f51ab9..0000000 --- a/docs/api-md/interfaces/MptFlags.md +++ /dev/null @@ -1,65 +0,0 @@ -# Interface: MptFlags - -Defined in: [verticals/token.types.ts:199](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L199) - -An MPT issuance's capability flags, decoded to booleans. - -## Properties - -### canClawback - -> `readonly` **canClawback**: `boolean` - -Defined in: [verticals/token.types.ts:211](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L211) - -The issuer can claw back the token. - -*** - -### canEscrow - -> `readonly` **canEscrow**: `boolean` - -Defined in: [verticals/token.types.ts:205](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L205) - -The token can be used in escrows. - -*** - -### canLock - -> `readonly` **canLock**: `boolean` - -Defined in: [verticals/token.types.ts:201](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L201) - -The issuer can lock the token. - -*** - -### canTrade - -> `readonly` **canTrade**: `boolean` - -Defined in: [verticals/token.types.ts:207](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L207) - -The token can be traded on the DEX. - -*** - -### canTransfer - -> `readonly` **canTransfer**: `boolean` - -Defined in: [verticals/token.types.ts:209](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L209) - -The token can be transferred between holders. - -*** - -### requireAuth - -> `readonly` **requireAuth**: `boolean` - -Defined in: [verticals/token.types.ts:203](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L203) - -Holders must be authorized before holding. diff --git a/docs/api-md/interfaces/MptIssueIntent.md b/docs/api-md/interfaces/MptIssueIntent.md deleted file mode 100644 index 83973e8..0000000 --- a/docs/api-md/interfaces/MptIssueIntent.md +++ /dev/null @@ -1,15 +0,0 @@ -# Interface: MptIssueIntent - -Defined in: [verticals/token.types.ts:193](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L193) - -Output attached to a `Token.issue` result. - -## Properties - -### mptIssuanceId - -> `readonly` **mptIssuanceId**: `string` - -Defined in: [verticals/token.types.ts:195](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L195) - -The id of the newly created MPT issuance. diff --git a/docs/api-md/interfaces/NetworkInfo.md b/docs/api-md/interfaces/NetworkInfo.md index 27c1f4e..74fd0ba 100644 --- a/docs/api-md/interfaces/NetworkInfo.md +++ b/docs/api-md/interfaces/NetworkInfo.md @@ -16,6 +16,19 @@ Faucet endpoint, used on test networks only. *** +### networkId? + +> `readonly` `optional` **networkId**: `number` + +Defined in: [client/client.ts:39](https://github.com/ripple/simpleXRPL/blob/main/src/client/client.ts#L39) + +The connected node's XRPL network id (`server_info` `network_id`: Mainnet +0, Testnet 1, Devnet 2), resolved at init when a custodian record is +network-scoped. `undefined` when no record needed it (so it was never +probed) or the probe could not reach the node. + +*** + ### xrpldUrl > `readonly` **xrpldUrl**: `string` diff --git a/docs/api-md/interfaces/OfferFlags.md b/docs/api-md/interfaces/OfferFlags.md deleted file mode 100644 index 65eeb3a..0000000 --- a/docs/api-md/interfaces/OfferFlags.md +++ /dev/null @@ -1,62 +0,0 @@ -# Interface: OfferFlags - -Defined in: [verticals/token.types.ts:141](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L141) - -Flags for `Token.createOffer`. Every flag defaults to `false` (a plain, -resting limit offer that buys `TakerPays` with `TakerGets`). - -## Properties - -### fillOrKill? - -> `readonly` `optional` **fillOrKill**: `boolean` - -Defined in: [verticals/token.types.ts:159](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L159) - -Consume the full amount or cancel entirely. - -#### Default Value - -`false` - -*** - -### immediateOrCancel? - -> `readonly` `optional` **immediateOrCancel**: `boolean` - -Defined in: [verticals/token.types.ts:153](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L153) - -Consume matching offers immediately; never place the remainder. - -#### Default Value - -`false` - -*** - -### passive? - -> `readonly` `optional` **passive**: `boolean` - -Defined in: [verticals/token.types.ts:147](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L147) - -Do not consume offers that exactly match. - -#### Default Value - -`false` - -*** - -### sell? - -> `readonly` `optional` **sell**: `boolean` - -Defined in: [verticals/token.types.ts:165](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L165) - -Interpret the offer as selling `TakerGets`. - -#### Default Value - -`false` diff --git a/docs/api-md/interfaces/OnChainResult.md b/docs/api-md/interfaces/OnChainResult.md index 6b262da..4ed9c85 100644 --- a/docs/api-md/interfaces/OnChainResult.md +++ b/docs/api-md/interfaces/OnChainResult.md @@ -1,6 +1,6 @@ # Interface: OnChainResult -Defined in: [domain/model.ts:276](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L276) +Defined in: [domain/model.ts:286](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L286) The on-chain outcome of a custodian-submitted transaction, available once the ledger has confirmed it. Returned by OnChainObserver.awaitOnChain @@ -12,7 +12,7 @@ and surfaced via `client.intent.awaitOnChain`. > `readonly` `optional` **mptIssuanceId**: `string` -Defined in: [domain/model.ts:280](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L280) +Defined in: [domain/model.ts:290](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L290) Present when the transaction created an MPT issuance. @@ -22,6 +22,6 @@ Present when the transaction created an MPT issuance. > `readonly` **txHash**: `string` -Defined in: [domain/model.ts:278](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L278) +Defined in: [domain/model.ts:288](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L288) The XRPL transaction hash. diff --git a/docs/api-md/interfaces/SignedEnvelope.md b/docs/api-md/interfaces/SignedEnvelope.md index 96db0e5..f155c8e 100644 --- a/docs/api-md/interfaces/SignedEnvelope.md +++ b/docs/api-md/interfaces/SignedEnvelope.md @@ -1,6 +1,6 @@ # Interface: SignedEnvelope -Defined in: [domain/model.ts:77](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L77) +Defined in: [domain/model.ts:87](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L87) A signed transaction ready to submit to xrpld. @@ -10,7 +10,7 @@ A signed transaction ready to submit to xrpld. > `readonly` `optional` **hash**: `string` -Defined in: [domain/model.ts:82](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L82) +Defined in: [domain/model.ts:92](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L92) The transaction hash, when the backend returns it. @@ -20,6 +20,6 @@ The transaction hash, when the backend returns it. > `readonly` **txBlob**: `string` -Defined in: [domain/model.ts:79](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L79) +Defined in: [domain/model.ts:89](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L89) The signed transaction blob (hex). diff --git a/docs/api-md/interfaces/SubmissionContext.md b/docs/api-md/interfaces/SubmissionContext.md index e0d09ef..c1fbb86 100644 --- a/docs/api-md/interfaces/SubmissionContext.md +++ b/docs/api-md/interfaces/SubmissionContext.md @@ -1,6 +1,6 @@ # Interface: SubmissionContext -Defined in: [domain/model.ts:100](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L100) +Defined in: [domain/model.ts:110](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L110) Per-submission context threaded through the pipeline to the custodian. @@ -10,7 +10,7 @@ Per-submission context threaded through the pipeline to the custodian. > `readonly` **account**: [`Account`](Account.md) -Defined in: [domain/model.ts:102](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L102) +Defined in: [domain/model.ts:112](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L112) The resolved source account the transaction acts on. @@ -20,7 +20,7 @@ The resolved source account the transaction acts on. > `readonly` `optional` **async**: `boolean` -Defined in: [domain/model.ts:114](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L114) +Defined in: [domain/model.ts:124](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L124) Return a handle instead of blocking until the transaction is terminal. @@ -30,7 +30,7 @@ Return a handle instead of blocking until the transaction is terminal. > `readonly` `optional` **customProperties**: `Record`\<`string`, `unknown`\> -Defined in: [domain/model.ts:120](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L120) +Defined in: [domain/model.ts:130](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L130) Human-readable approval metadata stamped on custody intents. @@ -40,7 +40,7 @@ Human-readable approval metadata stamped on custody intents. > `readonly` `optional` **dryRun**: `boolean` -Defined in: [domain/model.ts:111](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L111) +Defined in: [domain/model.ts:121](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L121) Pre-flight the write through the backend's dry-run, where supported. @@ -50,7 +50,7 @@ Pre-flight the write through the backend's dry-run, where supported. > `readonly` `optional` **fee**: [`FeeIntent`](FeeIntent.md) -Defined in: [domain/model.ts:108](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L108) +Defined in: [domain/model.ts:118](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L118) Optional fee override; falls back to the custodian's configured default. @@ -60,7 +60,7 @@ Optional fee override; falls back to the custodian's configured default. > `readonly` `optional` **idempotencyKey**: `string` -Defined in: [domain/model.ts:117](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L117) +Defined in: [domain/model.ts:127](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L127) Stable, client-generated id that makes a retry resolve to the same intent. @@ -70,7 +70,7 @@ Stable, client-generated id that makes a retry resolve to the same intent. > `readonly` **ledger**: [`LedgerPort`](LedgerPort.md) -Defined in: [domain/model.ts:105](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L105) +Defined in: [domain/model.ts:115](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L115) The shared ledger connection the custodian submits through. @@ -80,6 +80,6 @@ The shared ledger connection the custodian submits through. > `readonly` `optional` **timeoutMs**: `number` -Defined in: [domain/model.ts:123](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L123) +Defined in: [domain/model.ts:133](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L133) How long to wait before handing control back to the caller. diff --git a/docs/api-md/interfaces/SubmissionHandle.md b/docs/api-md/interfaces/SubmissionHandle.md index 9002a1b..325dbba 100644 --- a/docs/api-md/interfaces/SubmissionHandle.md +++ b/docs/api-md/interfaces/SubmissionHandle.md @@ -1,6 +1,6 @@ # Interface: SubmissionHandle -Defined in: [domain/model.ts:181](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L181) +Defined in: [domain/model.ts:191](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L191) Handle returned by an async submission, used to poll or wait for a terminal state without holding the original request open. @@ -11,7 +11,7 @@ state without holding the original request open. > `readonly` `optional` **cancel**: () => `Promise`\<`void`\> -Defined in: [domain/model.ts:198](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L198) +Defined in: [domain/model.ts:208](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L208) Cancel the pending intent where the backend supports it. @@ -25,7 +25,7 @@ Cancel the pending intent where the backend supports it. > `readonly` **custodian**: [`Custodian`](Custodian.md) -Defined in: [domain/model.ts:189](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L189) +Defined in: [domain/model.ts:199](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L199) The custodian that produced this handle. @@ -35,7 +35,7 @@ The custodian that produced this handle. > `readonly` **id**: `string` -Defined in: [domain/model.ts:186](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L186) +Defined in: [domain/model.ts:196](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L196) Custodian-native id (intent id), or the XRPL transaction hash for local. @@ -45,7 +45,7 @@ Custodian-native id (intent id), or the XRPL transaction hash for local. > `readonly` **kind**: [`CustodianKind`](../type-aliases/CustodianKind.md) -Defined in: [domain/model.ts:183](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L183) +Defined in: [domain/model.ts:193](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L193) The custodian kind that owns the underlying intent or transaction. @@ -55,7 +55,7 @@ The custodian kind that owns the underlying intent or transaction. > `readonly` **poll**: () => `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<`unknown`\>\> -Defined in: [domain/model.ts:192](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L192) +Defined in: [domain/model.ts:202](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L202) A non-blocking snapshot of the current state. @@ -69,7 +69,7 @@ A non-blocking snapshot of the current state. > `readonly` **wait**: (`timeoutMs`?) => `Promise`\<[`SubmissionResult`](../type-aliases/SubmissionResult.md)\<`unknown`\>\> -Defined in: [domain/model.ts:195](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L195) +Defined in: [domain/model.ts:205](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L205) Block until terminal state or the timeout (defaults to the custodian's). diff --git a/docs/api-md/interfaces/SubmissionResultFields.md b/docs/api-md/interfaces/SubmissionResultFields.md index eb6ab5a..08f7c15 100644 --- a/docs/api-md/interfaces/SubmissionResultFields.md +++ b/docs/api-md/interfaces/SubmissionResultFields.md @@ -1,6 +1,6 @@ # Interface: SubmissionResultFields\ -Defined in: [domain/model.ts:141](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L141) +Defined in: [domain/model.ts:151](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L151) Fields shared by every [SubmissionResult](../type-aliases/SubmissionResult.md) variant. @@ -16,7 +16,7 @@ Fields shared by every [SubmissionResult](../type-aliases/SubmissionResult.md) v > `readonly` `optional` **idempotencyKey**: `string` -Defined in: [domain/model.ts:156](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L156) +Defined in: [domain/model.ts:166](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L166) The stable, client-generated id (a UUIDv7) this submission carried (§8). Re-submitting with the same id resolves to the same intent rather than @@ -28,7 +28,7 @@ creating a duplicate; pass it back as an operation's `idempotencyKey` to retry. > `readonly` **intent**: `T` -Defined in: [domain/model.ts:143](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L143) +Defined in: [domain/model.ts:153](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L153) Vertical-specific output (e.g. a minted token id). @@ -38,7 +38,7 @@ Vertical-specific output (e.g. a minted token id). > `readonly` `optional` **intentId**: `string` -Defined in: [domain/model.ts:146](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L146) +Defined in: [domain/model.ts:156](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L156) Custodian intent id, when the path produced one. @@ -48,6 +48,6 @@ Custodian intent id, when the path produced one. > `readonly` `optional` **txHash**: `string` -Defined in: [domain/model.ts:149](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L149) +Defined in: [domain/model.ts:159](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L159) XRPL transaction hash once the transaction is on-ledger. diff --git a/docs/api-md/interfaces/MptAuthorizeParams.md b/docs/api-md/interfaces/TokenAuthorizeParams.md similarity index 92% rename from docs/api-md/interfaces/MptAuthorizeParams.md rename to docs/api-md/interfaces/TokenAuthorizeParams.md index 801316a..8a22712 100644 --- a/docs/api-md/interfaces/MptAuthorizeParams.md +++ b/docs/api-md/interfaces/TokenAuthorizeParams.md @@ -1,4 +1,4 @@ -# Interface: MptAuthorizeParams +# Interface: TokenAuthorizeParams Defined in: [verticals/token.types.ts:104](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L104) diff --git a/docs/api-md/interfaces/TokenClawbackParams.md b/docs/api-md/interfaces/TokenClawbackParams.md new file mode 100644 index 0000000..abd9812 --- /dev/null +++ b/docs/api-md/interfaces/TokenClawbackParams.md @@ -0,0 +1,25 @@ +# Interface: TokenClawbackParams + +Defined in: [verticals/token.types.ts:140](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L140) + +Parameters for `Token.clawback`. + +## Properties + +### amount + +> `readonly` **amount**: [`Amount`](Amount.md) + +Defined in: [verticals/token.types.ts:144](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L144) + +The MPT amount to claw back (its asset must be an MPT). + +*** + +### holder + +> `readonly` **holder**: `string` + +Defined in: [verticals/token.types.ts:142](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L142) + +The holder whose balance is reclaimed to the issuer. diff --git a/docs/api-md/interfaces/TokenData.md b/docs/api-md/interfaces/TokenData.md index 42f28ef..8ec0960 100644 --- a/docs/api-md/interfaces/TokenData.md +++ b/docs/api-md/interfaces/TokenData.md @@ -1,6 +1,6 @@ # Interface: TokenData -Defined in: [verticals/token.types.ts:215](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L215) +Defined in: [verticals/token.types.ts:170](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L170) A shaped MPT issuance (from `ledger_entry`). @@ -10,7 +10,7 @@ A shaped MPT issuance (from `ledger_entry`). > `readonly` **assetScale**: `number` -Defined in: [verticals/token.types.ts:221](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L221) +Defined in: [verticals/token.types.ts:176](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L176) Decimal places between display value and base units. @@ -18,9 +18,9 @@ Decimal places between display value and base units. ### flags -> `readonly` **flags**: [`MptFlags`](MptFlags.md) +> `readonly` **flags**: [`TokenFlags`](TokenFlags.md) -Defined in: [verticals/token.types.ts:229](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L229) +Defined in: [verticals/token.types.ts:184](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L184) Capability flags. @@ -30,7 +30,7 @@ Capability flags. > `readonly` **issuer**: `string` -Defined in: [verticals/token.types.ts:219](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L219) +Defined in: [verticals/token.types.ts:174](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L174) The issuer r-address. @@ -40,7 +40,7 @@ The issuer r-address. > `readonly` `optional` **maximumAmount**: `string` -Defined in: [verticals/token.types.ts:223](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L223) +Defined in: [verticals/token.types.ts:178](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L178) Maximum issuable amount (base units), if capped. @@ -50,7 +50,7 @@ Maximum issuable amount (base units), if capped. > `readonly` `optional` **metadata**: `MPTokenMetadata` -Defined in: [verticals/token.types.ts:231](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L231) +Defined in: [verticals/token.types.ts:186](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L186) Decoded XLS-89 metadata, if present and well-formed. @@ -60,7 +60,7 @@ Decoded XLS-89 metadata, if present and well-formed. > `readonly` **outstandingAmount**: `string` -Defined in: [verticals/token.types.ts:225](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L225) +Defined in: [verticals/token.types.ts:180](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L180) Amount currently in circulation (base units). @@ -70,7 +70,7 @@ Amount currently in circulation (base units). > `readonly` **tokenID**: `string` -Defined in: [verticals/token.types.ts:217](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L217) +Defined in: [verticals/token.types.ts:172](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L172) The MPT issuance id. @@ -80,6 +80,6 @@ The MPT issuance id. > `readonly` **transferFee**: `number` -Defined in: [verticals/token.types.ts:227](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L227) +Defined in: [verticals/token.types.ts:182](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L182) Secondary-transfer fee, as a percentage. diff --git a/docs/api-md/interfaces/MptDestroyParams.md b/docs/api-md/interfaces/TokenDestroyParams.md similarity index 92% rename from docs/api-md/interfaces/MptDestroyParams.md rename to docs/api-md/interfaces/TokenDestroyParams.md index f3b5b1c..80fde26 100644 --- a/docs/api-md/interfaces/MptDestroyParams.md +++ b/docs/api-md/interfaces/TokenDestroyParams.md @@ -1,4 +1,4 @@ -# Interface: MptDestroyParams +# Interface: TokenDestroyParams Defined in: [verticals/token.types.ts:126](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L126) diff --git a/docs/api-md/interfaces/TokenFlags.md b/docs/api-md/interfaces/TokenFlags.md new file mode 100644 index 0000000..bb88a29 --- /dev/null +++ b/docs/api-md/interfaces/TokenFlags.md @@ -0,0 +1,65 @@ +# Interface: TokenFlags + +Defined in: [verticals/token.types.ts:154](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L154) + +An MPT issuance's capability flags, decoded to booleans. + +## Properties + +### canClawback + +> `readonly` **canClawback**: `boolean` + +Defined in: [verticals/token.types.ts:166](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L166) + +The issuer can claw back the token. + +*** + +### canEscrow + +> `readonly` **canEscrow**: `boolean` + +Defined in: [verticals/token.types.ts:160](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L160) + +The token can be used in escrows. + +*** + +### canLock + +> `readonly` **canLock**: `boolean` + +Defined in: [verticals/token.types.ts:156](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L156) + +The issuer can lock the token. + +*** + +### canTrade + +> `readonly` **canTrade**: `boolean` + +Defined in: [verticals/token.types.ts:162](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L162) + +The token can be traded on the DEX. + +*** + +### canTransfer + +> `readonly` **canTransfer**: `boolean` + +Defined in: [verticals/token.types.ts:164](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L164) + +The token can be transferred between holders. + +*** + +### requireAuth + +> `readonly` **requireAuth**: `boolean` + +Defined in: [verticals/token.types.ts:158](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L158) + +Holders must be authorized before holding. diff --git a/docs/api-md/interfaces/MptHolderParams.md b/docs/api-md/interfaces/TokenHolderParams.md similarity index 95% rename from docs/api-md/interfaces/MptHolderParams.md rename to docs/api-md/interfaces/TokenHolderParams.md index 4eb6fda..bbf51fa 100644 --- a/docs/api-md/interfaces/MptHolderParams.md +++ b/docs/api-md/interfaces/TokenHolderParams.md @@ -1,4 +1,4 @@ -# Interface: MptHolderParams +# Interface: TokenHolderParams Defined in: [verticals/token.types.ts:110](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L110) diff --git a/docs/api-md/interfaces/MptIssueFlags.md b/docs/api-md/interfaces/TokenIssueFlags.md similarity index 98% rename from docs/api-md/interfaces/MptIssueFlags.md rename to docs/api-md/interfaces/TokenIssueFlags.md index 1c88015..6dbcc9b 100644 --- a/docs/api-md/interfaces/MptIssueFlags.md +++ b/docs/api-md/interfaces/TokenIssueFlags.md @@ -1,4 +1,4 @@ -# Interface: MptIssueFlags +# Interface: TokenIssueFlags Defined in: [verticals/token.types.ts:26](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L26) diff --git a/docs/api-md/interfaces/TokenIssueIntent.md b/docs/api-md/interfaces/TokenIssueIntent.md new file mode 100644 index 0000000..ba5a072 --- /dev/null +++ b/docs/api-md/interfaces/TokenIssueIntent.md @@ -0,0 +1,15 @@ +# Interface: TokenIssueIntent + +Defined in: [verticals/token.types.ts:148](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L148) + +Output attached to a `Token.issue` result. + +## Properties + +### mptIssuanceId + +> `readonly` **mptIssuanceId**: `string` + +Defined in: [verticals/token.types.ts:150](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L150) + +The id of the newly created MPT issuance. diff --git a/docs/api-md/interfaces/MptIssueParams.md b/docs/api-md/interfaces/TokenIssueParams.md similarity index 92% rename from docs/api-md/interfaces/MptIssueParams.md rename to docs/api-md/interfaces/TokenIssueParams.md index 92201f1..93d0411 100644 --- a/docs/api-md/interfaces/MptIssueParams.md +++ b/docs/api-md/interfaces/TokenIssueParams.md @@ -1,4 +1,4 @@ -# Interface: MptIssueParams +# Interface: TokenIssueParams Defined in: [verticals/token.types.ts:66](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L66) @@ -22,12 +22,12 @@ Decimal places between display value and base units. ### flags? -> `readonly` `optional` **flags**: [`MptIssueFlags`](MptIssueFlags.md) +> `readonly` `optional` **flags**: [`TokenIssueFlags`](TokenIssueFlags.md) Defined in: [verticals/token.types.ts:100](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L100) Capability flags. Any flag omitted (or the whole object omitted) takes the -per-flag SDK default; see [MptIssueFlags](MptIssueFlags.md). +per-flag SDK default; see [TokenIssueFlags](TokenIssueFlags.md). #### Default Value diff --git a/docs/api-md/interfaces/TokenListEntry.md b/docs/api-md/interfaces/TokenListEntry.md index b12c5df..4074547 100644 --- a/docs/api-md/interfaces/TokenListEntry.md +++ b/docs/api-md/interfaces/TokenListEntry.md @@ -1,6 +1,6 @@ # Interface: TokenListEntry -Defined in: [verticals/token.types.ts:249](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L249) +Defined in: [verticals/token.types.ts:204](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L204) An entry in [Token.list](../classes/Token.md#list): a full issuance (issuer) or a holding. @@ -10,7 +10,7 @@ An entry in [Token.list](../classes/Token.md#list): a full issuance (issuer) or > `readonly` `optional` **balance**: `string` -Defined in: [verticals/token.types.ts:253](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L253) +Defined in: [verticals/token.types.ts:208](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L208) The account's balance (present for `role: 'holder'`). @@ -20,7 +20,7 @@ The account's balance (present for `role: 'holder'`). > `readonly` `optional` **issuance**: [`TokenData`](TokenData.md) -Defined in: [verticals/token.types.ts:255](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L255) +Defined in: [verticals/token.types.ts:210](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L210) The full issuance snapshot (present for `role: 'issuer'`). @@ -30,6 +30,6 @@ The full issuance snapshot (present for `role: 'issuer'`). > `readonly` **tokenID**: `string` -Defined in: [verticals/token.types.ts:251](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L251) +Defined in: [verticals/token.types.ts:206](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L206) The MPT issuance id. diff --git a/docs/api-md/interfaces/TokenListOffersParams.md b/docs/api-md/interfaces/TokenListOffersParams.md deleted file mode 100644 index 731b189..0000000 --- a/docs/api-md/interfaces/TokenListOffersParams.md +++ /dev/null @@ -1,21 +0,0 @@ -# Interface: TokenListOffersParams - -Defined in: [verticals/token.types.ts:283](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L283) - -Parameters for [Token.listOffers](../classes/Token.md#listoffers). - -## Properties - -### account? - -> `readonly` `optional` **account**: `string` - -Defined in: [verticals/token.types.ts:289](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L289) - -The account whose offers to list. - -#### Default Value - -```ts -The primary signer's account. -``` diff --git a/docs/api-md/interfaces/TokenListParams.md b/docs/api-md/interfaces/TokenListParams.md index 46aa286..00276a2 100644 --- a/docs/api-md/interfaces/TokenListParams.md +++ b/docs/api-md/interfaces/TokenListParams.md @@ -1,6 +1,6 @@ # Interface: TokenListParams -Defined in: [verticals/token.types.ts:259](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L259) +Defined in: [verticals/token.types.ts:214](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L214) Parameters for [Token.list](../classes/Token.md#list). @@ -10,7 +10,7 @@ Parameters for [Token.list](../classes/Token.md#list). > `readonly` `optional` **account**: `string` -Defined in: [verticals/token.types.ts:271](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L271) +Defined in: [verticals/token.types.ts:226](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L226) The account to query; defaults to the primary signer's account. @@ -26,7 +26,7 @@ The primary signer's account. > `readonly` `optional` **role**: `"issuer"` \| `"holder"` -Defined in: [verticals/token.types.ts:265](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L265) +Defined in: [verticals/token.types.ts:220](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L220) List tokens the account `holder`s or `issuer`d. diff --git a/docs/api-md/interfaces/TokenListResult.md b/docs/api-md/interfaces/TokenListResult.md index eb83723..1bc4fbd 100644 --- a/docs/api-md/interfaces/TokenListResult.md +++ b/docs/api-md/interfaces/TokenListResult.md @@ -1,6 +1,6 @@ # Interface: TokenListResult -Defined in: [verticals/token.types.ts:275](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L275) +Defined in: [verticals/token.types.ts:230](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L230) Result of [Token.list](../classes/Token.md#list): `tokens[i]` corresponds to `data[i]`. @@ -10,7 +10,7 @@ Result of [Token.list](../classes/Token.md#list): `tokens[i]` corresponds to `da > `readonly` **data**: readonly [`TokenListEntry`](TokenListEntry.md)[] -Defined in: [verticals/token.types.ts:279](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L279) +Defined in: [verticals/token.types.ts:234](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L234) The shaped entries. @@ -20,6 +20,6 @@ The shaped entries. > `readonly` **tokens**: readonly `string`[] -Defined in: [verticals/token.types.ts:277](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L277) +Defined in: [verticals/token.types.ts:232](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L232) The MPT issuance id of each token. diff --git a/docs/api-md/interfaces/MptLockParams.md b/docs/api-md/interfaces/TokenLockParams.md similarity index 95% rename from docs/api-md/interfaces/MptLockParams.md rename to docs/api-md/interfaces/TokenLockParams.md index 617ea1a..1192832 100644 --- a/docs/api-md/interfaces/MptLockParams.md +++ b/docs/api-md/interfaces/TokenLockParams.md @@ -1,4 +1,4 @@ -# Interface: MptLockParams +# Interface: TokenLockParams Defined in: [verticals/token.types.ts:118](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L118) diff --git a/docs/api-md/interfaces/TokenRetrieveParams.md b/docs/api-md/interfaces/TokenRetrieveParams.md index c87945c..14c3e68 100644 --- a/docs/api-md/interfaces/TokenRetrieveParams.md +++ b/docs/api-md/interfaces/TokenRetrieveParams.md @@ -1,6 +1,6 @@ # Interface: TokenRetrieveParams -Defined in: [verticals/token.types.ts:235](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L235) +Defined in: [verticals/token.types.ts:190](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L190) Parameters for [Token.retrieve](../classes/Token.md#retrieve). @@ -10,6 +10,6 @@ Parameters for [Token.retrieve](../classes/Token.md#retrieve). > `readonly` **mptIssuanceId**: `string` -Defined in: [verticals/token.types.ts:237](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L237) +Defined in: [verticals/token.types.ts:192](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L192) The MPT issuance id to fetch. diff --git a/docs/api-md/interfaces/TokenRetrieveResult.md b/docs/api-md/interfaces/TokenRetrieveResult.md index a2be262..fa33411 100644 --- a/docs/api-md/interfaces/TokenRetrieveResult.md +++ b/docs/api-md/interfaces/TokenRetrieveResult.md @@ -1,6 +1,6 @@ # Interface: TokenRetrieveResult -Defined in: [verticals/token.types.ts:241](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L241) +Defined in: [verticals/token.types.ts:196](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L196) Result of [Token.retrieve](../classes/Token.md#retrieve). @@ -10,7 +10,7 @@ Result of [Token.retrieve](../classes/Token.md#retrieve). > `readonly` **data**: `undefined` \| [`TokenData`](TokenData.md) -Defined in: [verticals/token.types.ts:245](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L245) +Defined in: [verticals/token.types.ts:200](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L200) The issuance snapshot, or `undefined` if no such issuance exists. @@ -20,6 +20,6 @@ The issuance snapshot, or `undefined` if no such issuance exists. > `readonly` **tokenID**: `string` -Defined in: [verticals/token.types.ts:243](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L243) +Defined in: [verticals/token.types.ts:198](https://github.com/ripple/simpleXRPL/blob/main/src/verticals/token.types.ts#L198) The queried MPT issuance id. diff --git a/docs/api-md/type-aliases/AccountSelector.md b/docs/api-md/type-aliases/AccountSelector.md index ad1a918..9f5cb6d 100644 --- a/docs/api-md/type-aliases/AccountSelector.md +++ b/docs/api-md/type-aliases/AccountSelector.md @@ -2,7 +2,7 @@ > **AccountSelector**: `string` \| \{ `address`: `string`; \} \| \{ `account`: `string`; `signer`: [`Custodian`](../interfaces/Custodian.md); \} -Defined in: [domain/model.ts:69](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L69) +Defined in: [domain/model.ts:79](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L79) Caller-facing way to choose the source account for an operation: a bare address, an explicit address, or a signer (optionally narrowed to one of its accounts). diff --git a/docs/api-md/type-aliases/CustodyTransactionResult.md b/docs/api-md/type-aliases/CustodyTransactionResult.md index 859daac..0668416 100644 --- a/docs/api-md/type-aliases/CustodyTransactionResult.md +++ b/docs/api-md/type-aliases/CustodyTransactionResult.md @@ -2,7 +2,7 @@ > **CustodyTransactionResult**: `unknown` -Defined in: [domain/model.ts:130](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L130) +Defined in: [domain/model.ts:140](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L140) Custodian-native transaction result, refined to the generated wire schema in the Custody adapter work; opaque to the core today. diff --git a/docs/api-md/type-aliases/PalisadeTransactionResult.md b/docs/api-md/type-aliases/PalisadeTransactionResult.md index 18c4c75..4539454 100644 --- a/docs/api-md/type-aliases/PalisadeTransactionResult.md +++ b/docs/api-md/type-aliases/PalisadeTransactionResult.md @@ -2,7 +2,7 @@ > **PalisadeTransactionResult**: `unknown` -Defined in: [domain/model.ts:136](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L136) +Defined in: [domain/model.ts:146](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L146) Palisade-native transaction result, refined to the generated wire schema in the Palisade adapter work; opaque to the core today. diff --git a/docs/api-md/type-aliases/SubmissionResult.md b/docs/api-md/type-aliases/SubmissionResult.md index 3193b0a..0af5035 100644 --- a/docs/api-md/type-aliases/SubmissionResult.md +++ b/docs/api-md/type-aliases/SubmissionResult.md @@ -2,7 +2,7 @@ > **SubmissionResult**\<`T`\>: [`SubmissionResultFields`](../interfaces/SubmissionResultFields.md)\<`T`\> & \{ `response`: `TxResponse`; `source`: `"xrpld"`; \} \| [`SubmissionResultFields`](../interfaces/SubmissionResultFields.md)\<`T`\> & \{ `response`: [`CustodyTransactionResult`](CustodyTransactionResult.md); `source`: `"custody"`; \} \| [`SubmissionResultFields`](../interfaces/SubmissionResultFields.md)\<`T`\> & \{ `response`: [`PalisadeTransactionResult`](PalisadeTransactionResult.md); `source`: `"palisade"`; \} -Defined in: [domain/model.ts:163](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L163) +Defined in: [domain/model.ts:173](https://github.com/ripple/simpleXRPL/blob/main/src/domain/model.ts#L173) The discriminated-union result every write resolves to, tagged by `source` with the backend-specific response preserved verbatim. diff --git a/src/client/account-index.ts b/src/client/account-index.ts index 23b7f0b..1305cf5 100644 --- a/src/client/account-index.ts +++ b/src/client/account-index.ts @@ -1,5 +1,9 @@ import type { Account, Custodian } from '../domain/index.js' -import { AmbiguousAccountError, DuplicateSignerError } from '../errors.js' +import { + AmbiguousAccountError, + DuplicateSignerError, + NetworkMismatchError, +} from '../errors.js' /** * Reject two signers bound to the same backend tenant — the same `kind` and @@ -23,28 +27,95 @@ export function assertDistinctTenants(signers: readonly Custodian[]): void { } /** - * Merge one custodian's discovered accounts into the shared index, rejecting a - * r-address that a different custodian already claims. + * Choose the single account record to bind for one r-address, given the network + * the client is connected to. All candidates here belong to one custodian (a + * cross-custodian collision is rejected earlier); they differ only by the XRPL + * network their record is scoped to — the case where Ripple Custody registers + * the same address on several ledgers (mainnet, testnet, …). * - * @param index - The address to account index being built. - * @param custodian - The custodian whose accounts are being added. - * @param accounts - The accounts discovered by `custodian`. - * @throws {@link AmbiguousAccountError} if an r-address is claimed by two custodians. + * - Client network known: prefer the record on that exact network, else a + * network-agnostic record (one that works on any network). No match returns + * `undefined` so the address is left unbound rather than mis-routed. + * - Client network unknown (a single record, or the network probe failed): a + * lone or network-agnostic record is unambiguous and is returned; several + * network-scoped records cannot be told apart, so the address is left unbound. + * + * @param candidates - The records discovered for one r-address, one custodian. + * @param clientNetworkId - The network id the client is connected to, if known. + * @returns The record to bind, or `undefined` to leave the address unbound. + */ +function selectForNetwork( + candidates: readonly Account[], + clientNetworkId: number | undefined, +): Account | undefined { + if (clientNetworkId !== undefined) { + return ( + candidates.find((record) => record.networkId === clientNetworkId) ?? + candidates.find((record) => record.networkId === undefined) + ) + } + const scoped = candidates.filter((record) => record.networkId !== undefined) + return scoped.length <= 1 ? candidates[0] : undefined +} + +/** + * Group every discovered account by r-address, preserving the owning custodian. + * + * @param discovered - Each custodian paired with the accounts it discovered. + * @returns A map from r-address to the records claiming it. */ -function addAccounts( - index: Map, - custodian: Custodian, - accounts: readonly Account[], -): void { - for (const account of accounts) { - const existing = index.get(account.address) - if (existing !== undefined && existing.signer !== custodian) { - throw new AmbiguousAccountError(account.address, [ - existing.signer.kind, - custodian.kind, - ]) +function groupByAddress( + discovered: ReadonlyArray<{ accounts: readonly Account[] }>, +): Map { + const byAddress = new Map() + for (const { accounts } of discovered) { + for (const account of accounts) { + const list = byAddress.get(account.address) + if (list === undefined) { + byAddress.set(account.address, [account]) + } else { + list.push(account) + } } - index.set(account.address, account) + } + return byAddress +} + +/** The already-computed state a primary-address check reads. */ +interface BoundState { + /** Every discovered record, grouped by r-address. */ + readonly byAddress: ReadonlyMap + /** The bound index built so far. */ + readonly index: ReadonlyMap + /** The network id the client is connected to, if known. */ + readonly clientNetworkId: number | undefined +} + +/** + * A configured signer whose primary address was discovered, but only on XRPL + * networks other than the one the client is connected to, is a hard error — the + * caller pointed the client at the wrong network and a transaction would be + * silently stranded. An address that was never discovered, or that resolved to + * a bound record, is left to normal lookup. + * + * @param custodian - The signer whose primary address is checked. + * @param state - The grouped records, bound index, and connected network id. + * @throws {@link NetworkMismatchError} if the primary exists only on other networks. + */ +function assertPrimaryOnNetwork(custodian: Custodian, state: BoundState): void { + const address = custodian.primary.address + if (state.index.has(address)) { + return + } + const available = Array.from( + new Set( + (state.byAddress.get(address) ?? []) + .map((record) => record.networkId) + .filter((id): id is number => id !== undefined), + ), + ) + if (available.length > 0) { + throw new NetworkMismatchError(address, state.clientNetworkId, available) } } @@ -53,26 +124,65 @@ function addAccounts( * custodian's accounts. The same r-address appearing under two custodians is * rejected so dispatch can always resolve a single owning custodian. * + * When any discovered record is network-scoped (Ripple Custody registers one + * address on several ledgers), the client's connected network — resolved lazily + * via `resolveNetworkId`, and only then — decides which record to bind, so an + * intent is never routed to the wrong network's registration. + * * Note: a tenant/domain-level duplicate-signer check is not performed here — the * custodian model does not yet expose tenancy; only r-address collisions are * validated today. * * @param signers - The registered custodians. - * @returns A map from r-address to the owning {@link Account}. + * @param resolveNetworkId - Resolves the connected network's id, invoked only + * when some record is network-scoped; resolves `undefined` if it can't be + * determined. + * @returns The bound index and the resolved network id (`undefined` when no + * record was network-scoped, so no resolution was attempted). * @throws {@link AmbiguousAccountError} if an r-address is claimed by two custodians. + * @throws {@link NetworkMismatchError} if a signer's primary exists only on other networks. */ export async function buildAccountIndex( signers: readonly Custodian[], -): Promise> { + resolveNetworkId: () => Promise, +): Promise<{ index: Map; networkId: number | undefined }> { const discovered = await Promise.all( signers.map(async (custodian) => ({ custodian, accounts: await custodian.listAccounts(), })), ) + + const byAddress = groupByAddress(discovered) + + // Only pay for a network round-trip when a record actually depends on it. + const networkScoped = Array.from(byAddress.values()).some((list) => + list.some((record) => record.networkId !== undefined), + ) + const networkId = networkScoped ? await resolveNetworkId() : undefined + const index = new Map() - for (const { custodian, accounts } of discovered) { - addAccounts(index, custodian, accounts) + for (const [address, candidates] of byAddress) { + const owners = new Set(candidates.map((record) => record.signer)) + if (owners.size > 1) { + throw new AmbiguousAccountError( + address, + Array.from(owners, (signer) => signer.kind), + ) + } + const chosen = selectForNetwork(candidates, networkId) + if (chosen !== undefined) { + index.set(address, chosen) + } } - return index + + for (const { custodian } of discovered) { + assertPrimaryOnNetwork(custodian, { + byAddress, + index, + clientNetworkId: networkId, + }) + } + + return { index, networkId } } diff --git a/src/client/client.ts b/src/client/client.ts index 7ec9fe0..49f69a0 100644 --- a/src/client/client.ts +++ b/src/client/client.ts @@ -29,6 +29,33 @@ export interface NetworkInfo { /** Faucet endpoint, used on test networks only. */ readonly faucetUrl?: string + + /** + * The connected node's XRPL network id (`server_info` `network_id`: Mainnet + * 0, Testnet 1, Devnet 2), resolved at init when a custodian record is + * network-scoped. `undefined` when no record needed it (so it was never + * probed) or the probe could not reach the node. + */ + readonly networkId?: number +} + +/** + * Ask a node for its XRPL network id via `server_info`. Best-effort: a node + * that can't be reached or that omits the field yields `undefined`, so account + * binding can fall back rather than fail on the probe alone. + * + * @param ledger - The ledger connection to query. + * @returns The node's network id, or `undefined` if unavailable. + */ +async function queryNetworkId(ledger: LedgerPort): Promise { + try { + const info = await ledger.request<{ + result: { info: { network_id?: number } } + }>({ command: 'server_info' }) + return info.result.info.network_id + } catch { + return undefined + } } /** @@ -56,7 +83,7 @@ export class SimpleXRPLClient implements SubmissionHost { /** Issued-currency (IOU) operations: issue, transfer, authorize, lock, offers. */ public readonly iou: IOU - /** Multi-Purpose Token (MPT) family and DEX offers. */ + /** Multi-Purpose Token (MPT) family. */ public readonly token: Token /** On-ledger credentials (issue, accept, delete). */ @@ -142,6 +169,8 @@ export class SimpleXRPLClient implements SubmissionHost { * @returns A ready client. * @throws {@link DuplicateSignerError} if two signers share a kind and tenant id. * @throws {@link AmbiguousAccountError} if an r-address is claimed by two custodians. + * @throws {@link NetworkMismatchError} if a signer's primary account exists only + * on XRPL networks other than the one `xrpldUrl` points at. */ public static async init( config: SimpleXRPLConfig, @@ -152,13 +181,27 @@ export class SimpleXRPLClient implements SubmissionHost { signers, config.primarySigner, ) - const accountIndex = await buildAccountIndex(signers) + // Materialize the ledger lazily: a setup with no network-scoped account + // never connects, so no-signer and local-only clients stay offline at init. + let ledger = config.ledger + async function resolveNetworkId(): Promise { + ledger ??= new XrplLedger(config.xrpldUrl, config.faucetUrl) + return queryNetworkId(ledger) + } + const { index, networkId } = await buildAccountIndex( + signers, + resolveNetworkId, + ) return new SimpleXRPLClient({ - network: { xrpldUrl: config.xrpldUrl, faucetUrl: config.faucetUrl }, + network: { + xrpldUrl: config.xrpldUrl, + faucetUrl: config.faucetUrl, + networkId, + }, signers, primarySigner, - accountIndex, - ledger: config.ledger, + accountIndex: index, + ledger, }) } @@ -188,9 +231,14 @@ export class SimpleXRPLClient implements SubmissionHost { * become addressable; accounts removed upstream are gone on next lookup. * * @throws {@link AmbiguousAccountError} if an r-address is claimed by two custodians. + * @throws {@link NetworkMismatchError} if a signer's primary account exists only + * on XRPL networks other than the connected one. */ public async refreshAccounts(): Promise { - this.accountIndex = await buildAccountIndex(this.signers) + const { index } = await buildAccountIndex(this.signers, async () => + queryNetworkId(this.ledger), + ) + this.accountIndex = index } /** diff --git a/src/custodians/ripple/discovery/account-discovery.ts b/src/custodians/ripple/discovery/account-discovery.ts index fa9a2d1..124aff2 100644 --- a/src/custodians/ripple/discovery/account-discovery.ts +++ b/src/custodians/ripple/discovery/account-discovery.ts @@ -50,29 +50,33 @@ async function collectPages( } /** - * Resolve which ledger ids in this Custody environment are XRPL ledgers, by - * matching the ledger-parameters discriminator (`type === 'XRPL'`). Avoids - * hardcoding an environment-specific id. + * Resolve the XRPL ledgers in this Custody environment, mapping each ledger id + * to the XRPL `network_id` it runs on (Mainnet 0, Testnet 1, Devnet 2). Ledgers + * are matched by the parameters discriminator (`type === 'XRPL'`), which also + * narrows `parameters` so `networkId` is readable — avoiding a hardcoded, + * environment-specific id. The network id lets the client pick the ledger + * matching the node it is connected to when one address is registered on + * several ledgers. * * @param client - The authenticated Custody client. - * @returns The set of XRPL ledger ids. + * @returns A map from XRPL ledger id to its network id. */ -async function resolveXrplLedgerIds( +async function resolveXrplLedgers( client: CustodyHttpClient, -): Promise> { +): Promise> { const ledgers = await collectPages(async (startingAfter) => client.get('/v1/ledgers', { limit: PAGE_LIMIT, startingAfter, }), ) - const ids = new Set() + const byId = new Map() for (const ledger of ledgers) { if (ledger.data.parameters.type === 'XRPL') { - ids.add(ledger.data.id) + byId.set(ledger.data.id, ledger.data.parameters.networkId) } } - return ids + return byId } /** Inputs for listing one account's external XRPL addresses. */ @@ -80,35 +84,40 @@ interface AddressLookup { client: CustodyHttpClient domainId: string accountId: string - xrplLedgerIds: Set + xrplLedgers: Map } -/** One external XRPL address, with the ledger id it's actually on. */ +/** One external XRPL address, with the ledger id and network id it's on. */ interface ExternalAddress { address: string ledgerId: string + networkId: number } /** * List the external XRPL addresses of one Custody account. * - * @param lookup - The client, domain, account, and XRPL ledger ids. - * @returns The external r-addresses for the account, each with its ledger id. + * @param lookup - The client, domain, account, and XRPL ledgers. + * @returns The external r-addresses for the account, each with its ledger id + * and network id. */ async function listExternalAddresses( lookup: AddressLookup, ): Promise { - const { client, domainId, accountId, xrplLedgerIds } = lookup + const { client, domainId, accountId, xrplLedgers } = lookup const path = `/v1/domains/${domainId}/accounts/${accountId}/addresses` const addresses = await collectPages(async (startingAfter) => client.get(path, { limit: PAGE_LIMIT, startingAfter }), ) return addresses .map((entry) => entry.data) - .filter( - (data) => data.scope === 'External' && xrplLedgerIds.has(data.ledgerId), - ) - .map((data) => ({ address: data.address, ledgerId: data.ledgerId })) + .flatMap((data) => { + const networkId = xrplLedgers.get(data.ledgerId) + if (data.scope !== 'External' || networkId === undefined) { + return [] + } + return [{ address: data.address, ledgerId: data.ledgerId, networkId }] + }) } /** @@ -118,20 +127,19 @@ async function listExternalAddresses( * `additionalDetails.ledgers`. * * @param apiAccount - The raw Custody API account envelope. - * @param xrplLedgerIds - The XRPL ledger ids in this Custody environment. + * @param xrplLedgers - The XRPL ledgers in this Custody environment. * @returns `true` if this account has an activated XRPL ledger. */ function hasActivatedXrplLedger( apiAccount: components['schemas']['Core_ApiAccount'], - xrplLedgerIds: Set, + xrplLedgers: Map, ): boolean { const { ledgerId } = apiAccount.data - if (ledgerId !== undefined && xrplLedgerIds.has(ledgerId)) { + if (ledgerId !== undefined && xrplLedgers.has(ledgerId)) { return true } return (apiAccount.additionalDetails?.ledgers ?? []).some( - (entry) => - entry.status === 'Activated' && xrplLedgerIds.has(entry.ledgerId), + (entry) => entry.status === 'Activated' && xrplLedgers.has(entry.ledgerId), ) } @@ -152,7 +160,7 @@ export async function discoverXrplAccounts( domainId: string, signer: Custodian, ): Promise { - const xrplLedgerIds = await resolveXrplLedgerIds(client) + const xrplLedgers = await resolveXrplLedgers(client) const apiAccounts = await collectPages(async (startingAfter) => client.get(`/v1/domains/${domainId}/accounts`, { @@ -162,7 +170,7 @@ export async function discoverXrplAccounts( ) const xrplAccounts = apiAccounts - .filter((apiAccount) => hasActivatedXrplLedger(apiAccount, xrplLedgerIds)) + .filter((apiAccount) => hasActivatedXrplLedger(apiAccount, xrplLedgers)) .map((apiAccount) => apiAccount.data) const accounts: Account[] = [] @@ -172,14 +180,15 @@ export async function discoverXrplAccounts( client, domainId, accountId: account.id, - xrplLedgerIds, + xrplLedgers, }) - for (const { address, ledgerId } of addresses) { + for (const { address, ledgerId, networkId } of addresses) { accounts.push({ address, alias: account.alias, custodianRef: account.id, ledgerId, + networkId, signer, }) } diff --git a/src/custodians/ripple/mapping/mpt-operations.ts b/src/custodians/ripple/mapping/mpt-operations.ts index 0eb68d4..2be3a26 100644 --- a/src/custodians/ripple/mapping/mpt-operations.ts +++ b/src/custodians/ripple/mapping/mpt-operations.ts @@ -49,19 +49,32 @@ export function mapMPTokenAuthorize( } } -/** `MPTokenIssuanceCreateFlags` bit values, in the exact order Custody's enum lists them. */ +/** + * `MPTokenIssuanceCreateFlags` bit values, in the exact order the Custody + * gateway emits them when it reconstructs the operation to verify the intent + * signature. + * + * This order is load-bearing, not cosmetic. Every intent is signed over the + * JCS-canonicalized request (RFC 8785), and JCS preserves array order — it only + * sorts object keys. The gateway decodes our `flags` array to a bitmask, then + * re-encodes it to a string array in *this* fixed order before it canonicalizes + * and verifies. If our array is in any other order the two canonical forms + * differ and the gateway rejects the intent with `InvalidSignatureError`. The + * order is a hand-maintained server enum (it matches no sort of the bit values), + * so it is pinned here empirically and must be kept in lockstep with the gateway. + */ const MPT_ISSUANCE_CREATE_FLAGS: ReadonlyArray< readonly [ number, components['schemas']['Core_Xrpl_MPTokenIssuanceCreateFlag'], ] > = [ - [MPTokenIssuanceCreateFlags.tfMPTRequireAuth, 'tfMPTRequireAuth'], - [MPTokenIssuanceCreateFlags.tfMPTCanClawback, 'tfMPTCanClawback'], [MPTokenIssuanceCreateFlags.tfMPTCanTransfer, 'tfMPTCanTransfer'], - [MPTokenIssuanceCreateFlags.tfMPTCanEscrow, 'tfMPTCanEscrow'], [MPTokenIssuanceCreateFlags.tfMPTCanLock, 'tfMPTCanLock'], + [MPTokenIssuanceCreateFlags.tfMPTRequireAuth, 'tfMPTRequireAuth'], [MPTokenIssuanceCreateFlags.tfMPTCanTrade, 'tfMPTCanTrade'], + [MPTokenIssuanceCreateFlags.tfMPTCanClawback, 'tfMPTCanClawback'], + [MPTokenIssuanceCreateFlags.tfMPTCanEscrow, 'tfMPTCanEscrow'], ] /** diff --git a/src/custodians/ripple/submission/transaction-polling.ts b/src/custodians/ripple/submission/transaction-polling.ts index cba790e..68484a6 100644 --- a/src/custodians/ripple/submission/transaction-polling.ts +++ b/src/custodians/ripple/submission/transaction-polling.ts @@ -1,3 +1,5 @@ +import { decode, decodeAccountID } from 'xrpl' + import type { OnChainResult } from '../../../domain/index.js' import type { components } from '../../../generated/custody.js' import type { PollSchedule } from '../../poll-schedule.js' @@ -22,6 +24,48 @@ async function sleep(ms: number): Promise { }) } +/** + * Recover an `MPTokenIssuanceID` from a signed `MPTokenIssuanceCreate` blob. + * + * Custody can report a transaction as `Confirmed` while leaving the structured + * `ledgerData` null, so the issuance id isn't always readable from `tokenData`. + * It is deterministic, though: the id is the creating transaction's `Sequence` + * (4-byte big-endian) followed by the 20-byte issuer account id. We reconstruct + * it from the signed `rawTransaction` when the structured field is missing. + * + * @param rawTransaction - The signed transaction blob (hex), if present. + * @returns The 192-bit issuance id (hex), or `undefined` when the blob is + * absent, undecodable, or not an `MPTokenIssuanceCreate` bearing a sequence. + */ +function mptIssuanceIdFromRaw( + rawTransaction: string | undefined, +): string | undefined { + if (rawTransaction === undefined) { + return undefined + } + let tx: ReturnType + try { + tx = decode(rawTransaction) + } catch { + return undefined + } + if ( + tx.TransactionType !== 'MPTokenIssuanceCreate' || + typeof tx.Account !== 'string' || + typeof tx.Sequence !== 'number' || + tx.Sequence === 0 + ) { + return undefined + } + const sequence = tx.Sequence + const account = tx.Account + const sequenceHex = sequence.toString(16).toUpperCase().padStart(8, '0') + const accountIdHex = Buffer.from(decodeAccountID(account)) + .toString('hex') + .toUpperCase() + return `${sequenceHex}${accountIdHex}` +} + /** * Extract the on-chain result fields from a confirmed Custody transaction. * @@ -33,8 +77,11 @@ function toOnChainResult(tx: ApiTransaction): OnChainResult { const ledgerData = tx.ledgerTransactionData const txHash = ledgerData?.ledgerTransactionId ?? '' const onLedger = ledgerData?.ledgerData + // Prefer Custody's structured issuance id; fall back to reconstructing it + // from the signed blob, which stays populated even when `ledgerData` is null. const mptIssuanceId = - onLedger?.type === 'Xrpl' ? onLedger.tokenData?.issuanceId : undefined + (onLedger?.type === 'Xrpl' ? onLedger.tokenData?.issuanceId : undefined) ?? + mptIssuanceIdFromRaw(ledgerData?.rawTransaction) return { txHash, ...(mptIssuanceId !== undefined && { mptIssuanceId }) } } diff --git a/src/custodians/ripple/transport/custody-http-client.ts b/src/custodians/ripple/transport/custody-http-client.ts index f3bd396..e897c39 100644 --- a/src/custodians/ripple/transport/custody-http-client.ts +++ b/src/custodians/ripple/transport/custody-http-client.ts @@ -72,16 +72,22 @@ function extractHint(body: string): string | undefined { * @returns A {@link CustodyAuthError} for 401, else a {@link CustodyApiError}. */ function toError(response: HttpResponse): SimpleXRPLError { + const apiError = new CustodyApiError( + response.status, + response.body, + extractHint(response.body), + ) if (response.status === HTTP_UNAUTHORIZED) { + // A 401 on `/v1/intents` is one of InvalidJwtError, InvalidSignatureError, + // or PermissionDeniedError — the refresh-and-replay only recovers the + // first. Preserve the response body as the cause so the actual error type + // isn't swallowed by the generic "after token refresh" message. return new CustodyAuthError( 'Custody API authentication failed after token refresh', + { cause: apiError }, ) } - return new CustodyApiError( - response.status, - response.body, - extractHint(response.body), - ) + return apiError } /** diff --git a/src/domain/model.ts b/src/domain/model.ts index b3f4852..7675a4d 100644 --- a/src/domain/model.ts +++ b/src/domain/model.ts @@ -45,6 +45,16 @@ export interface Account extends AccountRef { */ readonly ledgerId?: string + /** + * The XRPL network this account record is scoped to, as the `network_id` a + * node reports via `server_info` (Mainnet 0, Testnet 1, Devnet 2). The client + * uses it to pick, among several records for one r-address, the one matching + * the network it is connected to. `undefined` for network-agnostic holders + * (a local wallet key works on any network) and for backends that expose no + * network id. + */ + readonly networkId?: number + /** * The account's XRPL public key (hex), when the custodian exposes it. Used to * populate `SigningPubKey` on transactions signed by a backend that returns diff --git a/src/errors.ts b/src/errors.ts index 3cca472..3a21e93 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -80,6 +80,44 @@ export class AmbiguousAccountError extends SimpleXRPLError { } } +/** + * An account exists at a custodian, but only on XRPL network(s) other than the + * one the client is connected to. The SDK refuses to route a transaction to the + * wrong network (which would silently strand it), so point the client's + * `xrpldUrl` at a node on a matching network, or register the address on this + * network at the custodian. + */ +export class NetworkMismatchError extends SimpleXRPLError { + public readonly account: string + public readonly clientNetworkId: number | undefined + public readonly availableNetworkIds: readonly number[] + + /** + * Construct a NetworkMismatchError. + * + * @param account - The r-address that has no record on the client's network. + * @param clientNetworkId - The network id the client is connected to, or + * `undefined` when it could not be determined. + * @param availableNetworkIds - The network ids the account does exist on. + */ + public constructor( + account: string, + clientNetworkId: number | undefined, + availableNetworkIds: readonly number[], + ) { + const on = availableNetworkIds.join(', ') + super( + `Account ${account} is not available on the client's XRPL network ` + + `(network id ${clientNetworkId ?? 'unknown'}); it exists on network ` + + `id(s) [${on}]. Point the client's xrpldUrl at a node on a matching ` + + `network, or register the address on this network at the custodian.`, + ) + this.account = account + this.clientNetworkId = clientNetworkId + this.availableNetworkIds = availableNetworkIds + } +} + /** * Two configured signers point at the same backend tenant — the same * `kind` and the same `tenantId` (§3.1). The client rejects this at init so diff --git a/src/verticals/index.ts b/src/verticals/index.ts index 3dd8a31..827d833 100644 --- a/src/verticals/index.ts +++ b/src/verticals/index.ts @@ -33,20 +33,17 @@ export type { export { Token } from './token.js' export { validateTokenMetadata } from './token.helpers.js' export type { - CancelOfferParams, - CreateOfferParams, - MptAuthorizeParams, - MptDestroyParams, - MptHolderParams, - MptFlags, - MptIssueFlags, - MptIssueIntent, - MptIssueParams, - MptLockParams, - OfferFlags, + TokenAuthorizeParams, + TokenDestroyParams, + TokenHolderParams, + TokenFlags, + TokenIssueFlags, + TokenIssueIntent, + TokenIssueParams, + TokenLockParams, + TokenClawbackParams, TokenData, TokenListEntry, - TokenListOffersParams, TokenListParams, TokenListResult, TokenRetrieveParams, diff --git a/src/verticals/token.helpers.ts b/src/verticals/token.helpers.ts index ea6109f..f05ca29 100644 --- a/src/verticals/token.helpers.ts +++ b/src/verticals/token.helpers.ts @@ -1,27 +1,16 @@ import { encodeMPTokenMetadata, MPTokenIssuanceCreateFlags, - OfferCreateFlags, validateMPTokenMetadata, } from 'xrpl' -import type { - IssuedCurrencyAmount, - MPTokenIssuanceCreate, - MPTokenMetadata, -} from 'xrpl' +import type { MPTokenIssuanceCreate, MPTokenMetadata } from 'xrpl' -import type { Amount } from '../amount/index.js' -import { toLedgerAmount } from '../amount/index.js' import type { SubmissionResult } from '../domain/index.js' import { IntentValidationError } from '../errors.js' import type { SubmissionHost } from '../pipeline/index.js' import { percentToTransferFee } from './fee.js' -import type { - MptIssueParams, - MptIssueFlags, - OfferFlags, -} from './token.types.js' +import type { TokenIssueParams, TokenIssueFlags } from './token.types.js' /** Default decimal places for a new issuance when `assetScale` is omitted. */ const DEFAULT_ASSET_SCALE = 2 @@ -36,7 +25,7 @@ const DEFAULT_ASSET_SCALE = 2 */ export function buildIssuance( account: string, - params: MptIssueParams, + params: TokenIssueParams, ): MPTokenIssuanceCreate { const tx: MPTokenIssuanceCreate = { TransactionType: 'MPTokenIssuanceCreate', @@ -83,7 +72,7 @@ function combineFlags( * `{ canClawback: false }`). Note MPT capability flags are permanent once the * issuance is created. */ -export const DEFAULT_ISSUE_FLAGS: Required = { +export const DEFAULT_ISSUE_FLAGS: Required = { canLock: true, requireAuth: false, canEscrow: true, @@ -99,7 +88,7 @@ export const DEFAULT_ISSUE_FLAGS: Required = { * @param flags - The caller's capability-flag overrides, if any. * @returns The combined flag number, or `undefined` when none are enabled. */ -export function issueFlags(flags?: MptIssueFlags): number | undefined { +export function issueFlags(flags?: TokenIssueFlags): number | undefined { const merged = { ...DEFAULT_ISSUE_FLAGS, ...flags } return combineFlags([ [merged.canLock, MPTokenIssuanceCreateFlags.tfMPTCanLock], @@ -111,42 +100,6 @@ export function issueFlags(flags?: MptIssueFlags): number | undefined { ]) } -/** - * Map offer flag booleans to the combined flag value. - * - * @param flags - The offer flags, if any. - * @returns The combined flag number, or `undefined` when none are set. - */ -export function offerFlags(flags?: OfferFlags): number | undefined { - if (flags === undefined) { - return undefined - } - return combineFlags([ - [flags.passive, OfferCreateFlags.tfPassive], - [flags.immediateOrCancel, OfferCreateFlags.tfImmediateOrCancel], - [flags.fillOrKill, OfferCreateFlags.tfFillOrKill], - [flags.sell, OfferCreateFlags.tfSell], - ]) -} - -/** - * Convert an amount for a DEX offer, rejecting MPT (not DEX-tradeable). - * - * @param amount - The offer amount. - * @returns The ledger amount (XRP drops string or issued-currency amount). - * @throws {@link IntentValidationError} if the amount's asset is an MPT. - */ -export function toDexAmount(amount: Amount): IssuedCurrencyAmount | string { - if (amount.asset.kind === 'mpt') { - throw new IntentValidationError('Offers do not support MPT amounts') - } - const ledger = toLedgerAmount(amount) - if (typeof ledger !== 'string' && 'mpt_issuance_id' in ledger) { - throw new IntentValidationError('Offers do not support MPT amounts') - } - return ledger -} - /** * Encode structured or string metadata to the on-ledger hex form. A structured * object goes through the xrpl helper; a raw string is UTF-8 hex-encoded as-is. diff --git a/src/verticals/token.reads.ts b/src/verticals/token.reads.ts index 2100e10..cf3af3a 100644 --- a/src/verticals/token.reads.ts +++ b/src/verticals/token.reads.ts @@ -5,7 +5,7 @@ import type { SubmissionHost } from '../pipeline/index.js' import { ledgerEntryNode, readAccountAddress } from '../reads/read-helpers.js' import type { - MptFlags, + TokenFlags, TokenData, TokenListParams, TokenListResult, @@ -61,7 +61,7 @@ function hasFlag(flags: number, bit: number): boolean { * @param flags - The raw `Flags` bitmask. * @returns The capability flags. */ -function decodeFlags(flags: number): MptFlags { +function decodeFlags(flags: number): TokenFlags { return { canLock: hasFlag(flags, LSF_CAN_LOCK), requireAuth: hasFlag(flags, LSF_REQUIRE_AUTH), diff --git a/src/verticals/token.ts b/src/verticals/token.ts index 501b8ee..c6a4df2 100644 --- a/src/verticals/token.ts +++ b/src/verticals/token.ts @@ -1,10 +1,9 @@ import { MPTokenAuthorizeFlags, MPTokenIssuanceSetFlags } from 'xrpl' import type { + Clawback, MPTokenAuthorize, MPTokenIssuanceDestroy, MPTokenIssuanceSet, - OfferCancel, - OfferCreate, Payment, } from 'xrpl' @@ -13,27 +12,17 @@ import type { SubmissionResult } from '../domain/index.js' import { IntentValidationError } from '../errors.js' import type { SubmissionHost } from '../pipeline/index.js' import { submitTransaction, withIntent } from '../pipeline/index.js' -import { listAccountOffers } from '../reads/offers.js' -import type { ListOffersResult } from '../reads/offers.js' -import { readAccountAddress } from '../reads/read-helpers.js' -import { - buildIssuance, - extractMptIssuanceId, - offerFlags, - toDexAmount, -} from './token.helpers.js' +import { buildIssuance, extractMptIssuanceId } from './token.helpers.js' import { listTokens, retrieveToken } from './token.reads.js' import type { - CancelOfferParams, - CreateOfferParams, - MptAuthorizeParams, - MptDestroyParams, - MptHolderParams, - MptIssueIntent, - MptIssueParams, - MptLockParams, - TokenListOffersParams, + TokenAuthorizeParams, + TokenDestroyParams, + TokenHolderParams, + TokenIssueIntent, + TokenIssueParams, + TokenLockParams, + TokenClawbackParams, TokenListParams, TokenListResult, TokenRetrieveParams, @@ -43,7 +32,11 @@ import type { } from './token.types.js' /** - * The Token vertical: the Multi-Purpose Token (MPT) family and DEX offers. + * The Token vertical: the Multi-Purpose Token (MPT) family. + * + * DEX offers are not exposed here: the MPT DEX amendment is not yet live + * on-chain, so MPTs cannot be traded on the order book. XRP/IOU offers belong + * to the IOU vertical (`client.iou.buyOffer`/`sellOffer`/`cancelOffer`). */ export class Token { private readonly host: SubmissionHost @@ -80,21 +73,6 @@ export class Token { return listTokens(this.host, params) } - /** - * List the open DEX offers placed by an account. No signer required. - * - * @param params - The account (default: the primary signer's account). - * @returns The shaped offers (composable into offer write operations). - */ - public async listOffers( - params?: TokenListOffersParams, - ): Promise { - return listAccountOffers( - this.host, - readAccountAddress(this.host, params?.account), - ) - } - /** * Create a new MPT issuance. * @@ -132,9 +110,9 @@ export class Token { * @returns The result, with the new `mptIssuanceId` as its intent output. */ public async issue( - params: MptIssueParams, + params: TokenIssueParams, options?: TokenWriteOptions, - ): Promise> { + ): Promise> { const account = this.host.resolveAccount(options?.from) const result = await submitTransaction(this.host, { @@ -166,7 +144,7 @@ export class Token { * @returns The submission result. */ public async authorize( - params: MptAuthorizeParams, + params: TokenAuthorizeParams, options?: TokenWriteOptions, ): Promise> { return this.submitAuthorize(params, false, options) @@ -180,7 +158,7 @@ export class Token { * @returns The submission result. */ public async unauthorize( - params: MptAuthorizeParams, + params: TokenAuthorizeParams, options?: TokenWriteOptions, ): Promise> { return this.submitAuthorize(params, true, options) @@ -194,7 +172,7 @@ export class Token { * @returns The submission result. */ public async grantHolder( - params: MptHolderParams, + params: TokenHolderParams, options?: TokenWriteOptions, ): Promise> { return this.submitAuthorize(params, false, options) @@ -208,7 +186,7 @@ export class Token { * @returns The submission result. */ public async revokeHolder( - params: MptHolderParams, + params: TokenHolderParams, options?: TokenWriteOptions, ): Promise> { return this.submitAuthorize(params, true, options) @@ -222,7 +200,7 @@ export class Token { * @returns The submission result. */ public async lock( - params: MptLockParams, + params: TokenLockParams, options?: TokenWriteOptions, ): Promise> { return this.submitLock(params, true, options) @@ -236,7 +214,7 @@ export class Token { * @returns The submission result. */ public async unlock( - params: MptLockParams, + params: TokenLockParams, options?: TokenWriteOptions, ): Promise> { return this.submitLock(params, false, options) @@ -250,7 +228,7 @@ export class Token { * @returns The submission result. */ public async destroy( - params: MptDestroyParams, + params: TokenDestroyParams, options?: TokenWriteOptions, ): Promise> { const account = this.host.resolveAccount(options?.from) @@ -319,67 +297,46 @@ export class Token { } /** - * Place an offer on the decentralized exchange. + * Reclaim a holder's MPT balance back to the issuer. * - * @param params - The amounts to give and receive, plus offer flags. - * @param options - Source account and fee override. - * @returns The submission result. - * @throws {@link IntentValidationError} if either amount is an MPT. + * Requires the issuance to have been created with `canClawback` (the SDK + * default). The holder whose balance is reclaimed is named explicitly, and + * the amount's asset must be an MPT. + * + * @param params - The holder and MPT amount to claw back. + * @param options - Issuer account, fee override, and idempotency key. + * @returns The result, echoing `{ holder, amount }` as its intent output. + * @throws {@link IntentValidationError} if the amount's asset is not an MPT. */ - public async createOffer( - params: CreateOfferParams, + public async clawback( + params: TokenClawbackParams, options?: TokenWriteOptions, - ): Promise> { - const account = this.host.resolveAccount(options?.from) - const tx: OfferCreate = { - TransactionType: 'OfferCreate', - Account: account.address, - TakerGets: toDexAmount(params.takerGets), - TakerPays: toDexAmount(params.takerPays), - } - if (params.expiration !== undefined) { - tx.Expiration = params.expiration - } - if (params.offerSequence !== undefined) { - tx.OfferSequence = params.offerSequence + ): Promise> { + const issuer = this.host.resolveAccount(options?.from) + const amount = toLedgerAmount(params.amount) + // A non-MPT amount produces a string (XRP) or an issued-currency object; + // narrow to the MPT shape Clawback requires, and steer IOU callers away. + if (typeof amount === 'string' || !('mpt_issuance_id' in amount)) { + throw new IntentValidationError( + 'Token.clawback requires an MPT amount; use iou.clawback for issued currencies.', + ) } - const flags = offerFlags(params.flags) - if (flags !== undefined) { - tx.Flags = flags + const transaction: Clawback = { + TransactionType: 'Clawback', + Account: issuer.address, + Amount: amount, + Holder: params.holder, } const result = await submitTransaction(this.host, { - transaction: tx, - account, + transaction, + account: issuer, fee: options?.fee, idempotencyKey: options?.idempotencyKey, }) - return withIntent(result, undefined) - } - - /** - * Cancel a standing offer. - * - * @param params - The sequence number of the offer to cancel. - * @param options - Source account and fee override. - * @returns The submission result. - */ - public async cancelOffer( - params: CancelOfferParams, - options?: TokenWriteOptions, - ): Promise> { - const account = this.host.resolveAccount(options?.from) - const tx: OfferCancel = { - TransactionType: 'OfferCancel', - Account: account.address, - OfferSequence: params.offerSequence, - } - const result = await submitTransaction(this.host, { - transaction: tx, - account, - fee: options?.fee, - idempotencyKey: options?.idempotencyKey, + return withIntent(result, { + holder: params.holder, + amount: params.amount.value, }) - return withIntent(result, { offerSequence: params.offerSequence }) } /** diff --git a/src/verticals/token.types.ts b/src/verticals/token.types.ts index 0b08c13..79fa118 100644 --- a/src/verticals/token.types.ts +++ b/src/verticals/token.types.ts @@ -23,7 +23,7 @@ export interface TokenWriteOptions { * unset takes the SDK default below (a fully capable, transferable token). * These capabilities are **permanent** once the issuance is created. */ -export interface MptIssueFlags { +export interface TokenIssueFlags { /** * The issuer can lock the token (globally or per-holder). * @@ -63,7 +63,7 @@ export interface MptIssueFlags { } /** Parameters for `Token.issue`. */ -export interface MptIssueParams { +export interface TokenIssueParams { /** * Decimal places between display value and base units. * @@ -93,21 +93,21 @@ export interface MptIssueParams { readonly metadata: MPTokenMetadata | string /** * Capability flags. Any flag omitted (or the whole object omitted) takes the - * per-flag SDK default; see {@link MptIssueFlags}. + * per-flag SDK default; see {@link TokenIssueFlags}. * * @defaultValue `{ canLock: true, requireAuth: false, canEscrow: true, canTrade: true, canTransfer: true, canClawback: true }` */ - readonly flags?: MptIssueFlags + readonly flags?: TokenIssueFlags } /** Parameters for `Token.authorize` / `Token.unauthorize` (calling account). */ -export interface MptAuthorizeParams { +export interface TokenAuthorizeParams { /** The MPT issuance id. */ readonly mptIssuanceId: string } /** Parameters for `Token.grantHolder` / `Token.revokeHolder` (issuer-side). */ -export interface MptHolderParams { +export interface TokenHolderParams { /** The MPT issuance id. */ readonly mptIssuanceId: string /** The holder to grant or revoke. */ @@ -115,7 +115,7 @@ export interface MptHolderParams { } /** Parameters for `Token.lock` / `Token.unlock`. */ -export interface MptLockParams { +export interface TokenLockParams { /** The MPT issuance id. */ readonly mptIssuanceId: string /** A specific holder to (un)lock; omit to affect the whole issuance. */ @@ -123,7 +123,7 @@ export interface MptLockParams { } /** Parameters for `Token.destroy`. */ -export interface MptDestroyParams { +export interface TokenDestroyParams { /** The MPT issuance id. */ readonly mptIssuanceId: string } @@ -136,67 +136,22 @@ export interface TokenTransferParams { readonly amount: Amount } -/** Flags for `Token.createOffer`. Every flag defaults to `false` (a plain, - * resting limit offer that buys `TakerPays` with `TakerGets`). */ -export interface OfferFlags { - /** - * Do not consume offers that exactly match. - * - * @defaultValue `false` - */ - readonly passive?: boolean - /** - * Consume matching offers immediately; never place the remainder. - * - * @defaultValue `false` - */ - readonly immediateOrCancel?: boolean - /** - * Consume the full amount or cancel entirely. - * - * @defaultValue `false` - */ - readonly fillOrKill?: boolean - /** - * Interpret the offer as selling `TakerGets`. - * - * @defaultValue `false` - */ - readonly sell?: boolean -} - -/** Parameters for `Token.createOffer`. */ -export interface CreateOfferParams { - /** What the account gives (XRP or IOU — MPT is not DEX-tradeable). */ - readonly takerGets: Amount - /** What the account wants (XRP or IOU). */ - readonly takerPays: Amount - /** Offer expiration (seconds since the Ripple epoch). */ - readonly expiration?: number - /** A prior offer sequence to replace. */ - readonly offerSequence?: number - /** - * Offer flags. Omit for a plain resting limit offer. - * - * @defaultValue No flags set — see {@link OfferFlags} (all `false`). - */ - readonly flags?: OfferFlags -} - -/** Parameters for `Token.cancelOffer`. */ -export interface CancelOfferParams { - /** The sequence number of the offer to cancel. */ - readonly offerSequence: number +/** Parameters for `Token.clawback`. */ +export interface TokenClawbackParams { + /** The holder whose balance is reclaimed to the issuer. */ + readonly holder: string + /** The MPT amount to claw back (its asset must be an MPT). */ + readonly amount: Amount } /** Output attached to a `Token.issue` result. */ -export interface MptIssueIntent { +export interface TokenIssueIntent { /** The id of the newly created MPT issuance. */ readonly mptIssuanceId: string } /** An MPT issuance's capability flags, decoded to booleans. */ -export interface MptFlags { +export interface TokenFlags { /** The issuer can lock the token. */ readonly canLock: boolean /** Holders must be authorized before holding. */ @@ -226,7 +181,7 @@ export interface TokenData { /** Secondary-transfer fee, as a percentage. */ readonly transferFee: number /** Capability flags. */ - readonly flags: MptFlags + readonly flags: TokenFlags /** Decoded XLS-89 metadata, if present and well-formed. */ readonly metadata?: MPTokenMetadata } @@ -278,13 +233,3 @@ export interface TokenListResult { /** The shaped entries. */ readonly data: readonly TokenListEntry[] } - -/** Parameters for {@link Token.listOffers}. */ -export interface TokenListOffersParams { - /** - * The account whose offers to list. - * - * @defaultValue The primary signer's account. - */ - readonly account?: string -} diff --git a/test/unit/client/network-selection.test.ts b/test/unit/client/network-selection.test.ts new file mode 100644 index 0000000..f2512a0 --- /dev/null +++ b/test/unit/client/network-selection.test.ts @@ -0,0 +1,182 @@ +import { buildAccountIndex } from '../../../src/client/account-index.js' +import type { + Account, + Custodian, + CustodianKind, +} from '../../../src/domain/index.js' +import { + AmbiguousAccountError, + NetworkMismatchError, +} from '../../../src/index.js' + +const notImplemented = async (): Promise => { + throw new Error('not implemented in tests') +} + +/** One discovered record: an r-address optionally scoped to a network. */ +interface Record { + address: string + networkId?: number + ledgerId?: string +} + +/** + * Build a custodian whose `listAccounts` returns exactly `records` (each + * back-referencing the custodian), with the given primary address. + * + * @param kind - The custodian kind. + * @param primaryAddress - The custodian's primary r-address. + * @param records - The records this custodian discovers. + * @returns The stub custodian. + */ +function makeSigner( + kind: CustodianKind, + primaryAddress: string, + records: readonly Record[], +): Custodian { + const custodian: Custodian = { + kind, + primary: { address: primaryAddress }, + capabilities: () => ({ nativeOps: new Set(), allowRaw: false }), + listAccounts: async (): Promise => + records.map((record) => ({ + address: record.address, + networkId: record.networkId, + ledgerId: record.ledgerId, + signer: custodian, + })), + sign: notImplemented, + submitAndWait: notImplemented, + submitAsync: notImplemented, + } + return custodian +} + +/** + * A `resolveNetworkId` stub that resolves `value` and counts its invocations. + * + * @param value - The network id to resolve. + * @returns The stub function paired with a live call counter. + */ +function resolver(value: number | undefined): { + fn: () => Promise + calls: () => number +} { + let count = 0 + return { + async fn(): Promise { + count += 1 + return value + }, + calls: (): number => count, + } +} + +describe('buildAccountIndex network selection', () => { + it('binds the record on the connected network when an address spans several', async () => { + const custody = makeSigner('ripple-custody', 'rMulti', [ + { address: 'rMulti', networkId: 0, ledgerId: 'xrpl-mainnet' }, + { address: 'rMulti', networkId: 1, ledgerId: 'xrpl-testnet' }, + ]) + const probe = resolver(1) + + const { index, networkId } = await buildAccountIndex([custody], probe.fn) + + expect(networkId).toBe(1) + expect(index.get('rMulti')?.ledgerId).toBe('xrpl-testnet') + expect(index.get('rMulti')?.networkId).toBe(1) + }) + + it('leaves a non-primary address unbound when it exists only on another network', async () => { + const custody = makeSigner('ripple-custody', 'rPrimary', [ + { address: 'rPrimary', networkId: 1, ledgerId: 'xrpl-testnet' }, + { address: 'rMainnetOnly', networkId: 0, ledgerId: 'xrpl-mainnet' }, + ]) + const probe = resolver(1) + + const { index } = await buildAccountIndex([custody], probe.fn) + + expect(index.has('rPrimary')).toBe(true) + expect(index.has('rMainnetOnly')).toBe(false) + }) + + it('throws NetworkMismatchError when a primary exists only on another network', async () => { + const custody = makeSigner('ripple-custody', 'rPrimary', [ + { address: 'rPrimary', networkId: 0, ledgerId: 'xrpl-mainnet' }, + ]) + const probe = resolver(1) + + const promise = buildAccountIndex([custody], probe.fn) + + await expect(promise).rejects.toBeInstanceOf(NetworkMismatchError) + await promise.catch((error: unknown) => { + const mismatch = error as NetworkMismatchError + expect(mismatch.account).toBe('rPrimary') + expect(mismatch.clientNetworkId).toBe(1) + expect(mismatch.availableNetworkIds).toStrictEqual([0]) + }) + }) + + it('never probes the network when no record is network-scoped', async () => { + const local = makeSigner('local', 'rLocal1', [ + { address: 'rLocal1' }, + { address: 'rLocal2' }, + ]) + const probe = resolver(1) + + const { index, networkId } = await buildAccountIndex([local], probe.fn) + + expect(probe.calls()).toBe(0) + expect(networkId).toBeUndefined() + expect( + Array.from(index.keys()).sort((left, right) => left.localeCompare(right)), + ).toStrictEqual(['rLocal1', 'rLocal2']) + }) + + it('keeps a network-agnostic (local) account alongside a network-scoped one', async () => { + const custody = makeSigner('ripple-custody', 'rCustody', [ + { address: 'rCustody', networkId: 1, ledgerId: 'xrpl-testnet' }, + ]) + const local = makeSigner('local', 'rLocal', [{ address: 'rLocal' }]) + const probe = resolver(1) + + const { index } = await buildAccountIndex([custody, local], probe.fn) + + expect(index.get('rCustody')?.signer).toBe(custody) + expect(index.get('rLocal')?.signer).toBe(local) + }) + + it('still rejects an address claimed by two custodians as ambiguous', async () => { + const custody = makeSigner('ripple-custody', 'rShared', [ + { address: 'rShared', networkId: 1 }, + ]) + const local = makeSigner('local', 'rShared', [{ address: 'rShared' }]) + const probe = resolver(1) + + await expect( + buildAccountIndex([custody, local], probe.fn), + ).rejects.toBeInstanceOf(AmbiguousAccountError) + }) + + it('cannot disambiguate a multi-network primary when the probe fails, and errors', async () => { + const custody = makeSigner('ripple-custody', 'rMulti', [ + { address: 'rMulti', networkId: 0 }, + { address: 'rMulti', networkId: 1 }, + ]) + const probe = resolver(undefined) + + const promise = buildAccountIndex([custody], probe.fn) + + await expect(promise).rejects.toBeInstanceOf(NetworkMismatchError) + await promise.catch((error: unknown) => { + const mismatch = error as NetworkMismatchError + expect(mismatch.account).toBe('rMulti') + expect(mismatch.clientNetworkId).toBeUndefined() + expect( + Array.from(mismatch.availableNetworkIds).sort( + (left, right) => left - right, + ), + ).toStrictEqual([0, 1]) + }) + }) +}) diff --git a/test/unit/custody-discovery/account-discovery.test.ts b/test/unit/custody-discovery/account-discovery.test.ts index 87cb4e2..f50ef1f 100644 --- a/test/unit/custody-discovery/account-discovery.test.ts +++ b/test/unit/custody-discovery/account-discovery.test.ts @@ -85,6 +85,7 @@ describe('discoverXrplAccounts', () => { alias: 'treasury', custodianRef: 'acc-1', ledgerId: 'xrpl-1', + networkId: 0, signer: SIGNER, }, ]) @@ -224,6 +225,7 @@ describe('discoverXrplAccounts', () => { alias: 'primary_account', custodianRef: 'acc-vault', ledgerId: 'xrpl-1', + networkId: 0, signer: SIGNER, }, ]) @@ -290,6 +292,7 @@ describe('discoverXrplAccounts', () => { alias: 'multi', custodianRef: 'acc-1', ledgerId: 'xrpl-1', + networkId: 0, signer: SIGNER, }, { @@ -297,6 +300,7 @@ describe('discoverXrplAccounts', () => { alias: 'multi', custodianRef: 'acc-1', ledgerId: 'xrpl-1', + networkId: 0, signer: SIGNER, }, ]) diff --git a/test/unit/custody-discovery/test-utils.ts b/test/unit/custody-discovery/test-utils.ts index 2bc8735..6b1111b 100644 --- a/test/unit/custody-discovery/test-utils.ts +++ b/test/unit/custody-discovery/test-utils.ts @@ -84,20 +84,22 @@ export function status(code: number, body: unknown = {}): HttpResponse { } /** - * Build a `getLedgers` collection body from `{ id, type }` ledger specs. + * Build a `getLedgers` collection body from `{ id, type, networkId }` ledger + * specs. `networkId` (the XRPL `server_info` network id) defaults to 0 and is + * read by discovery only for `type: 'XRPL'` ledgers. * * @param ledgers - The ledger specs to include. * @returns A ledgers collection body. */ export function ledgersBody( - ledgers: ReadonlyArray<{ id: string; type: string }>, + ledgers: ReadonlyArray<{ id: string; type: string; networkId?: number }>, ): JsonBody { return { items: ledgers.map((ledger) => ({ data: { id: ledger.id, alias: ledger.id, - parameters: { type: ledger.type }, + parameters: { type: ledger.type, networkId: ledger.networkId ?? 0 }, metadata: {}, }, signature: '', diff --git a/test/unit/custody-mapping/xrpl-operations.test.ts b/test/unit/custody-mapping/xrpl-operations.test.ts index 0211272..53b4585 100644 --- a/test/unit/custody-mapping/xrpl-operations.test.ts +++ b/test/unit/custody-mapping/xrpl-operations.test.ts @@ -253,7 +253,7 @@ describe('txToOperation', () => { }) describe('MPTokenIssuanceCreate', () => { - it('maps every field and all 6 flags', () => { + it('maps every field and all 6 flags in the gateway canonical order', () => { const tx: MPTokenIssuanceCreate = { TransactionType: 'MPTokenIssuanceCreate', Account: 'rFrom', @@ -264,15 +264,19 @@ describe('txToOperation', () => { // All 6 flags combined: 4 | 64 | 32 | 8 | 2 | 16 = 126 Flags: 126, } + // The flag order is load-bearing: the gateway re-encodes the bitmask to + // this exact sequence before it verifies the intent signature over the + // JCS-canonicalized request (which preserves array order). Any other + // order canonicalizes differently and is rejected as InvalidSignature. expect(txToOperation(tx)).toEqual({ type: 'MPTokenIssuanceCreate', flags: [ - 'tfMPTRequireAuth', - 'tfMPTCanClawback', 'tfMPTCanTransfer', - 'tfMPTCanEscrow', 'tfMPTCanLock', + 'tfMPTRequireAuth', 'tfMPTCanTrade', + 'tfMPTCanClawback', + 'tfMPTCanEscrow', ], assetScale: 2, transferFee: 100, diff --git a/test/unit/flags/flag-matrix.test.ts b/test/unit/flags/flag-matrix.test.ts index a076c59..78cd6cc 100644 --- a/test/unit/flags/flag-matrix.test.ts +++ b/test/unit/flags/flag-matrix.test.ts @@ -5,13 +5,9 @@ import { } from 'xrpl' import type { AccountSet } from 'xrpl' -import type { - AccountSetParams, - MptIssueFlags, - OfferFlags, -} from '../../../src/index.js' +import type { AccountSetParams, TokenIssueFlags } from '../../../src/index.js' import { orderTypeFlags } from '../../../src/verticals/iou.helpers.js' -import { issueFlags, offerFlags } from '../../../src/verticals/token.helpers.js' +import { issueFlags } from '../../../src/verticals/token.helpers.js' import { recordingClient } from '../helpers/recording-ledger.js' // A full matrix over every SDK-exposed flag: each named flag must map to the @@ -51,7 +47,7 @@ describe('Account.set flag matrix', () => { describe('Token issuance capability flag matrix', () => { const flags: ReadonlyArray< - [keyof MptIssueFlags, MPTokenIssuanceCreateFlags] + [keyof TokenIssueFlags, MPTokenIssuanceCreateFlags] > = [ ['canLock', MPTokenIssuanceCreateFlags.tfMPTCanLock], ['requireAuth', MPTokenIssuanceCreateFlags.tfMPTRequireAuth], @@ -60,7 +56,7 @@ describe('Token issuance capability flag matrix', () => { ['canTransfer', MPTokenIssuanceCreateFlags.tfMPTCanTransfer], ['canClawback', MPTokenIssuanceCreateFlags.tfMPTCanClawback], ] - const allOff: Required = { + const allOff: Required = { canLock: false, requireAuth: false, canEscrow: false, @@ -74,7 +70,7 @@ describe('Token issuance capability flag matrix', () => { }) it('combines every capability into one mask', () => { - const all: Required = { + const all: Required = { canLock: true, requireAuth: true, canEscrow: true, @@ -116,18 +112,4 @@ describe('Offer flag matrix', () => { it.each(orderTypes)('sell %s → its flag | tfSell', (orderType, bit) => { expect(orderTypeFlags(orderType, true)).toBe(bit | OfferCreateFlags.tfSell) }) - - const offerBits: ReadonlyArray<[keyof OfferFlags, OfferCreateFlags]> = [ - ['passive', OfferCreateFlags.tfPassive], - ['immediateOrCancel', OfferCreateFlags.tfImmediateOrCancel], - ['fillOrKill', OfferCreateFlags.tfFillOrKill], - ['sell', OfferCreateFlags.tfSell], - ] - it.each(offerBits)('token offer flag %s → its bit', (name, bit) => { - expect(offerFlags({ [name]: true })).toBe(bit) - }) - - it('token offerFlags returns undefined when none set', () => { - expect(offerFlags({})).toBeUndefined() - }) }) diff --git a/test/unit/ripple-custody/ripple-custody.test.ts b/test/unit/ripple-custody/ripple-custody.test.ts index 0fb4bb2..cf0b92a 100644 --- a/test/unit/ripple-custody/ripple-custody.test.ts +++ b/test/unit/ripple-custody/ripple-custody.test.ts @@ -256,6 +256,7 @@ describe('RippleCustody.create', () => { alias: '', custodianRef: ACCOUNT_ID, ledgerId: 'xrpl-1', + networkId: 0, signer: custody, }, ]) diff --git a/test/unit/ripple-custody/transaction-polling.test.ts b/test/unit/ripple-custody/transaction-polling.test.ts new file mode 100644 index 0000000..af2cd49 --- /dev/null +++ b/test/unit/ripple-custody/transaction-polling.test.ts @@ -0,0 +1,162 @@ +import { pollTransactionOnChain } from '../../../src/custodians/ripple/submission/transaction-polling.js' + +import { DOMAIN_ID, makeClient, ok } from './test-utils.js' + +// A real signed MPTokenIssuanceCreate blob (issuer rMJvBxh…, Sequence +// 19495980) and the issuance id the ledger derives from it. Custody returns +// this in `rawTransaction` even when it leaves the structured `ledgerData` +// null, so it is the only source of the id in the sandbox. +const MPT_CREATE_RAW = + '12003622000000322401297C2C201B01323E9C6840000000000000C873' + + '2102218AB4A0A775F860183DE429BB86862D134C91D4D885A0AAF10C5AA1E350C794' + + '74473045022100B64DF0C394D95E4D398D9B53874D3AF8FE44C504C6605C318F8E82' + + '8085ACF86602205E288887028EE670EC536B8B63552C5F895C4E6118603D3350CE3F' + + '9BB8610772701EC1197B227469636B6572223A22494E5350222C226E616D65223A22' + + '496E73706563746F7220546F6B656E222C2269636F6E223A22646174613A696D6167' + + '652F706E673B6261736536342C6956424F5277304B47676F414141414E5355684555' + + '674141414145414141414243415941414141664663534A4141414144556C45515652' + + '34326D4E6B2B4D39514477414468674741576A5239617741414141424A5253453572' + + '6B4A6767673D3D222C2261737365745F636C617373223A226F74686572222C226973' + + '737565725F6E616D65223A22496E73706563746F72227D' + + '8114DEC5FC9E0628D406B1A134ED2718C9CAFCFAACF1' +const MPT_CREATE_ID = '01297C2CDEC5FC9E0628D406B1A134ED2718C9CAFCFAACF1' + +// A signed Payment blob for the same account — decodes fine, but must never +// yield an issuance id. +const PAYMENT_RAW = + '1200002401297C2C6140000000000F424068400000000000000A73008114DEC5FC9E' + + '0628D406B1A134ED2718C9CAFCFAACF18314DEC5FC9E0628D406B1A134ED2718C9CA' + + 'FCFAACF1' + +const INTENT_ID = 'intent-1' + +/** + * A one-item transactions collection carrying the given ledger data. + * + * @param ledgerTransactionData - The `ledgerTransactionData` field to attach. + * @returns A `Core_TransactionsCollection`-shaped body. + */ +function txCollection(ledgerTransactionData: unknown): Record { + return { + count: 1, + items: [ + { + id: 'tx-1', + orderReference: { id: INTENT_ID, domainId: DOMAIN_ID }, + ledgerTransactionData, + }, + ], + } +} + +describe('pollTransactionOnChain', () => { + const options = { domainId: DOMAIN_ID, intentId: INTENT_ID, timeoutMs: 5000 } + + it('returns the issuance id from structured ledgerData when present', async () => { + const { client } = makeClient(() => + ok( + txCollection({ + ledgerStatus: 'Confirmed', + ledgerTransactionId: 'HASH1', + ledgerData: { type: 'Xrpl', tokenData: { issuanceId: 'STRUCTURED' } }, + }), + ), + ) + + const result = await pollTransactionOnChain({ client, ...options }) + + expect(result).toEqual({ txHash: 'HASH1', mptIssuanceId: 'STRUCTURED' }) + }) + + it('reconstructs the issuance id from rawTransaction when ledgerData is null', async () => { + // The Custody sandbox confirms MPT issuances yet returns `ledgerData: null`, + // so the id has to be recovered from the signed blob. + const { client } = makeClient(() => + ok( + txCollection({ + ledgerStatus: 'Confirmed', + ledgerTransactionId: 'HASH2', + ledgerData: null, + rawTransaction: MPT_CREATE_RAW, + }), + ), + ) + + const result = await pollTransactionOnChain({ client, ...options }) + + expect(result).toEqual({ txHash: 'HASH2', mptIssuanceId: MPT_CREATE_ID }) + }) + + it('prefers structured tokenData over the raw blob when both are present', async () => { + const { client } = makeClient(() => + ok( + txCollection({ + ledgerStatus: 'Confirmed', + ledgerTransactionId: 'HASH3', + ledgerData: { type: 'Xrpl', tokenData: { issuanceId: 'STRUCTURED' } }, + rawTransaction: MPT_CREATE_RAW, + }), + ), + ) + + const result = await pollTransactionOnChain({ client, ...options }) + + expect(result?.mptIssuanceId).toBe('STRUCTURED') + }) + + it('omits the issuance id for a confirmed non-MPT transaction', async () => { + const { client } = makeClient(() => + ok( + txCollection({ + ledgerStatus: 'Confirmed', + ledgerTransactionId: 'HASH4', + ledgerData: null, + rawTransaction: PAYMENT_RAW, + }), + ), + ) + + const result = await pollTransactionOnChain({ client, ...options }) + + expect(result).toEqual({ txHash: 'HASH4' }) + }) + + it('omits the issuance id when neither structured data nor a raw blob is present', async () => { + const { client } = makeClient(() => + ok( + txCollection({ + ledgerStatus: 'Confirmed', + ledgerTransactionId: 'HASH5', + ledgerData: null, + }), + ), + ) + + const result = await pollTransactionOnChain({ client, ...options }) + + expect(result).toEqual({ txHash: 'HASH5' }) + }) + + it('returns undefined when confirmation never arrives before the timeout', async () => { + const { client } = makeClient(() => + ok( + txCollection({ + ledgerStatus: 'Pending', + ledgerTransactionId: '', + ledgerData: null, + }), + ), + ) + + // The first backoff (5s) already exceeds this deadline, so the poll gives + // up on the opening attempt without sleeping. + const result = await pollTransactionOnChain({ + client, + domainId: DOMAIN_ID, + intentId: INTENT_ID, + timeoutMs: 500, + }) + + expect(result).toBeUndefined() + }) +}) diff --git a/test/unit/token/token.test.ts b/test/unit/token/token.test.ts index d4ab329..d964cf8 100644 --- a/test/unit/token/token.test.ts +++ b/test/unit/token/token.test.ts @@ -2,16 +2,14 @@ import { MPTokenAuthorizeFlags, MPTokenIssuanceCreateFlags, MPTokenIssuanceSetFlags, - OfferCreateFlags, Wallet, encodeMPTokenMetadata, + type Clawback, type MPTokenAuthorize, type MPTokenIssuanceCreate, type MPTokenIssuanceDestroy, type MPTokenIssuanceSet, type MPTokenMetadata, - type OfferCancel, - type OfferCreate, type Payment, type SubmitResponse, type Transaction, @@ -20,7 +18,6 @@ import { import { IntentValidationError, - iou, LocalSigner, mpt, SimpleXRPL, @@ -349,54 +346,33 @@ describe('Token vertical', () => { }) }) - describe('createOffer / cancelOffer', () => { - it('builds OfferCreate with XRP/IOU amounts and flags', async () => { + describe('clawback', () => { + it('builds a Clawback with the holder and a scaled MPT amount', async () => { const { client, txs } = await tokenClient() - const issuer = Wallet.generate().classicAddress - await client.token.createOffer({ - takerGets: { asset: XRP_ASSET, value: '1' }, - takerPays: { asset: iou('USD', issuer), value: '10' }, - flags: { immediateOrCancel: true }, - }) - const tx = txs[0] as OfferCreate - expect(tx.TransactionType).toBe('OfferCreate') - expect(tx.TakerGets).toBe('1000000') - expect(tx.TakerPays).toStrictEqual({ - currency: 'USD', - issuer, - value: '10', + const holder = Wallet.generate().classicAddress + const result = await client.token.clawback({ + holder, + amount: { asset: mpt(MPT_ID, 2), value: '10.5' }, }) - expect(tx.Flags).toBe(OfferCreateFlags.tfImmediateOrCancel) - }) - - it('omits Flags when the offer sets none', async () => { - const { client, txs } = await tokenClient() - const issuer = Wallet.generate().classicAddress - await client.token.createOffer({ - takerGets: { asset: XRP_ASSET, value: '1' }, - takerPays: { asset: iou('USD', issuer), value: '10' }, + expect(result.intent).toStrictEqual({ holder, amount: '10.5' }) + const tx = txs[0] as Clawback + expect(tx.TransactionType).toBe('Clawback') + expect(tx.Holder).toBe(holder) + expect(tx.Amount).toStrictEqual({ + mpt_issuance_id: MPT_ID, + value: '1050', }) - // A plain (non-passive, non-sell) offer must not carry Flags: 0. - expect((txs[0] as OfferCreate).Flags).toBeUndefined() }) - it('rejects an MPT amount in an offer', async () => { + it('rejects a non-MPT amount', async () => { const { client } = await tokenClient() await expect( - client.token.createOffer({ - takerGets: { asset: mpt('MPT-1', 0), value: '1' }, - takerPays: { asset: XRP_ASSET, value: '1' }, + client.token.clawback({ + holder: 'rHolder', + amount: { asset: XRP_ASSET, value: '10' }, }), ).rejects.toBeInstanceOf(IntentValidationError) }) - - it('builds OfferCancel with the offer sequence', async () => { - const { client, txs } = await tokenClient() - await client.token.cancelOffer({ offerSequence: 42 }) - const tx = txs[0] as OfferCancel - expect(tx.TransactionType).toBe('OfferCancel') - expect(tx.OfferSequence).toBe(42) - }) }) })