diff --git a/README.md b/README.md index a7a206b9..475df767 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ const transport = createTransport(createMessagePortProvider(port)); const truapi = createClient(transport); const result = await truapi.accountManagement.accountGet({ - productAccountId: { dotNsIdentifier: "my-product.dot", derivationIndex: 0 }, + productAccountId: { derivationIndex: 0 }, }); ``` diff --git a/docs/rfcs/0022-remove-dotns-from-default-access.md b/docs/rfcs/0022-remove-dotns-from-default-access.md new file mode 100644 index 00000000..78b2f4d4 --- /dev/null +++ b/docs/rfcs/0022-remove-dotns-from-default-access.md @@ -0,0 +1,204 @@ +# RFC-0022: Optional `dotNsIdentifier` and permissioned external-account access + +| | | +| --------------- | --------------------------------------------------------------------- | +| **RFC Number** | 22 | +| **Start Date** | 2026-06-30 | +| **Description** | Account, signing, and statement-store calls address the caller's own accounts by default, and a foreign dotNS identifier opts into permissioned cross-product access. | +| **Authors** | Valentin Fernandez | + +## Summary + +The account, signing, and statement-store methods that operate on a product account take a +`ProductAccountId { dotNsIdentifier, derivationIndex }`. `dotNsIdentifier` is now **optional**. Omit it +and the call resolves against the caller's own dotNS domain, so the common case only needs a +`derivationIndex`. + +```typescript +// Own account, the common case +await truapi.account.getAccount({ productAccountId: { derivationIndex: 0 } }); + +// Another product's account, requires the ExternalAccount permission +await truapi.account.getAccount({ + productAccountId: { dotNsIdentifier: "another-product.dot", derivationIndex: 0 }, +}); +``` + +Supplying a `dotNsIdentifier` that names a different product is cross-product access, gated behind a +new `ExternalAccount` remote permission. When the field is omitted the host resolves the account's +domain from the authenticated caller. When it names a foreign domain the host accepts or rejects the +call based on whether the user granted `ExternalAccount`. + +This RFC covers seven call sites: + +- [`getAccount()`](https://paritytech.github.io/truapi/v/main/method/Account/get_account) +- [`getAccountAlias()`](https://paritytech.github.io/truapi/v/main/method/Account/get_account_alias) +- [`createAccountProof()`](https://paritytech.github.io/truapi/v/main/method/Account/create_account_proof) +- [`createTransaction()`](https://paritytech.github.io/truapi/v/main/method/Signing/create_transaction) +- [`signRaw()`](https://paritytech.github.io/truapi/v/main/method/Signing/sign_raw) +- [`signPayload()`](https://paritytech.github.io/truapi/v/main/method/Signing/sign_payload) +- [`statementStore.createProof()`](https://paritytech.github.io/truapi/v/main/method/StatementStore/create_proof) + +## Motivation + +For the common case, a product addressing its own accounts, `dotNsIdentifier` is redundant. The host +already knows the calling product's dotNS domain and can resolve it from the authenticated caller. +Making the field optional lets that case carry only a `derivationIndex`. + +The field still serves a real purpose, though. Some products have a legitimate reason to read, and +possibly sign with, *another* product's account. That capability should be explicit and consented, not +an unguarded consequence of an always-present parameter. So instead of dropping `dotNsIdentifier` +entirely, which would remove the capability, or leaving it mandatory, which offers no isolation, it +becomes an optional opt-in: absent for own-product access, present and permissioned for cross-product +access. + +Split out from [#222](https://github.com/paritytech/truapi/issues/222), tracked in +[#243](https://github.com/paritytech/truapi/issues/243). + +## Stakeholders + +- **Product developers** omit the domain for their own accounts. To reach another product's account + they supply a `dotNsIdentifier` and hold the `ExternalAccount` grant. +- **Host developers** resolve the domain from the authenticated caller when it is omitted, and enforce + the `ExternalAccount` permission when a foreign domain is supplied. + +## Explanation + +`ProductAccountId` carries an optional domain: + +```rust +struct ProductAccountId { + dot_ns_identifier: Option, + derivation_index: u32, +} +``` + +Resolution rules: + +- `dot_ns_identifier == None` resolves to the caller's own product. +- `dot_ns_identifier == Some(domain)` where `domain` is the caller's own also resolves to the caller's + own product. +- `dot_ns_identifier == Some(domain)` where `domain` is a different product is cross-product access. It + is permitted only when the user has granted the caller the `ExternalAccount` permission, otherwise + the host rejects the call. + +Each of the seven request types carries a `ProductAccountId`: + +```rust +struct HostAccountGetRequest { product_account_id: ProductAccountId } +struct HostAccountGetAliasRequest { product_account_id: ProductAccountId } +struct HostAccountCreateProofRequest { + product_account_id: ProductAccountId, + ring_location: RingLocation, + context: Vec, +} +struct ProductAccountTxPayload { + signer: ProductAccountId, + genesis_hash: GenesisHash, + call_data: Vec, + extensions: Vec, + tx_ext_version: u8, +} +struct HostSignRawRequest { account: ProductAccountId, payload: RawPayload } +struct HostSignPayloadRequest { account: ProductAccountId, payload: HostSignPayloadData } +struct RemoteStatementStoreCreateProofRequest { + product_account_id: ProductAccountId, + statement: Statement, +} +``` + +In TypeScript `dotNsIdentifier` is optional on the nested identifier, so own-account calls pass only +the index: + +```typescript +await truapi.account.getAccount({ productAccountId: { derivationIndex: 0 } }); +await truapi.account.getAccountAlias({ productAccountId: { derivationIndex: 0 } }); +await truapi.account.createAccountProof({ productAccountId: { derivationIndex: 0 }, ringLocation, context: "0x" }); +await truapi.signing.createTransaction({ signer: { derivationIndex: 0 }, genesisHash, callData, extensions: [], txExtVersion: 0 }); +await truapi.signing.signRaw({ account: { derivationIndex: 0 }, payload }); +await truapi.signing.signPayload({ account: { derivationIndex: 0 }, payload }); +await truapi.statementStore.createProof({ productAccountId: { derivationIndex: 0 }, statement }); +``` + +### `ExternalAccount` permission + +A new `ExternalAccount` variant on `RemotePermission` governs cross-product access. Like `ChainSubmit` +and `StatementSubmit`, it is triggered implicitly by the business call. The first time a product issues +one of these calls with a foreign `dotNsIdentifier`, the host prompts for the grant, and the user's +decision persists. A product that only ever addresses its own accounts never sees the prompt. + +The change is made in place on `v01`, with no `v02` of these messages. Their wire IDs are unchanged. +Only the request body shape changes, and `dotNsIdentifier` moves from a required `String` to an +optional one. The `*WithLegacyAccount` signing variants are unaffected, since they identify a legacy +account by raw `AccountId`, never by `dotNsIdentifier`. + +`statementStore.createProofAuthorized()` already takes only the statement (it uses a pre-allocated +allowance account) and so needs no change. The deprecated `statementStore.createProof()` is the +statement-store call still carrying `ProductAccountId`, and is covered here for consistency. + +## Drawbacks + +Cross-product access adds a permission surface the host must enforce and the user must reason about. +The capability is opt-in on both sides, though: a product reaches another product's account only by +supplying a foreign domain, and only after the user grants `ExternalAccount`. Products that never opt +in are unaffected, and the default call surface stays minimal. + +## Testing, Security, and Privacy + +Cross-product access is only possible through an explicit foreign `dotNsIdentifier` combined with a +user-granted `ExternalAccount` permission. Own-account calls omit the domain entirely, so the host +resolves it from the authenticated caller and there is no caller-supplied domain to validate in the +common case. The host stays the sole authority on which domain a call resolves against, and the +permission prompt keeps the user in the loop before any product reads or signs with another product's +account. + +## Performance, Ergonomics, and Compatibility + +### Ergonomics + +The common case, a product's own account, needs only an index, and there is no domain string to supply +or get wrong. Cross-product access is expressed by the presence of a domain plus a permission, so the +capability is discoverable instead of hidden behind an always-present field. + +### Compatibility + +This is a breaking wire change to seven `v01` request bodies, and the protocol gives it **no version +signal**. Each message is wrapped in a `versioned_type!` envelope whose SCALE discriminant byte is the +version (`V1` is codec index `0`), but every one of the protocol's envelopes is currently +single-variant `V1`. There is no live multi-version support and no cross-version conversion anywhere in +the tree. The handshake negotiates only the SCALE *codec* version, not the API version. So editing the +body of a `V1` message keeps the version byte at `0` while changing the bytes that follow it. Because +SCALE is positional and not self-describing, an un-upgraded host reads "V1" and silently misdecodes the +new layout. There is no clean failure. + +The change is therefore safe only while the host and product SDK ship together, that is, while the +v0.1 wire is not frozen against independently-deployed hosts. That is the repository's current posture. +`v01` is the single, still-evolving wire, and breaking changes land in it in place (see +[RFC-0020](0020-create-transaction.md)). The `versioned_type!` machinery, though fully scaffolded, has +never been exercised with a second variant. This RFC follows that posture. Some of these methods do +have a live consumer (the playground calls `signRaw` and `createProof`), and those call sites are +updated in the same change. The PR stays a draft until the protocol-wide versioning strategy is +settled. + +Once the v0.1 wire is frozen and hosts deploy independently of the SDK, a breaking change like this one +must instead add a `V2` variant (`{ V1 => v01::X, V2 => v02::X }`) with hand-written +`FromLatest`/`IntoLatest`, so the version byte distinguishes the shapes (an old host rejects `V2` +cleanly instead of corrupting) and a dual-version host can bridge both. Adopting that for these seven +messages alone, while the rest of the protocol stays single-variant `V1`, would buy no real safety. It +is a protocol-wide discipline to take on at the freeze, tracked separately from this RFC. + +## Prior Art and References + +- [RFC-0002](0002-permission-model.md), the permission model that `ExternalAccount` extends. +- [RFC-0020](0020-create-transaction.md), which removed a field from a signing request body in place on + `v01` and establishes the in-place-change precedent followed here. +- [#222](https://github.com/paritytech/truapi/issues/222), the parent issue this is split out from. +- [#243](https://github.com/paritytech/truapi/issues/243), the tracking issue for this RFC. + +## Unresolved Questions + +- **In-place vs versioned migration.** This RFC changes the seven `v01` bodies in place, which is sound + only while hosts and the SDK ship together (see [Compatibility](#compatibility)). If v0.1 is frozen + against independently-deployed hosts before this lands, it must move to a `V2` envelope instead, and + that decision is really protocol-wide (today all envelopes are single-variant `V1`), not specific to + these seven messages. diff --git a/docs/rfcs/_index.md b/docs/rfcs/_index.md index 3bac6f9a..f58f9c9f 100644 --- a/docs/rfcs/_index.md +++ b/docs/rfcs/_index.md @@ -22,3 +22,4 @@ created: 2026-03-13 | 0019 | [Scheduled Push Notifications](0019-scheduled-notifications.md) | accepted | @johnthecat | — | | 0020 | [Remove `context` from `create_transaction` and mirror in Accounts Protocol](0020-create-transaction.md) | accepted | Valentin Sergeev | — | | 0021 | [Add Coins variant to PaymentTopUpSource](0021-payment-topup-coins.md) | accepted | @filippovecchiato | — | +| 0022 | [Optional `dotNsIdentifier` and permissioned external-account access](0022-remove-dotns-from-default-access.md) | draft | Valentin Fernandez | [#245](https://github.com/paritytech/truapi/pull/245) | diff --git a/rust/crates/truapi/src/api/account.rs b/rust/crates/truapi/src/api/account.rs index 7c4e065f..a60e7f3d 100644 --- a/rust/crates/truapi/src/api/account.rs +++ b/rust/crates/truapi/src/api/account.rs @@ -35,14 +35,16 @@ pub trait Account: Send + Sync { /// Retrieve a product-scoped account. /// /// ```ts - /// const result = await truapi.account.getAccount({ - /// productAccountId: { - /// dotNsIdentifier: "truapi-playground.dot", - /// derivationIndex: 0, - /// }, - /// }); + /// const result = await truapi.account.getAccount({ productAccountId: { derivationIndex: 0 } }); /// assert(result.isOk(), "getAccount failed:", result); /// console.log("account retrieved:", result.value); + /// + /// // To read another product's account, pass its dotNS domain. This is cross-product + /// // access and requires the `ExternalAccount` permission — host-side enforcement is + /// // not yet implemented. + /// // await truapi.account.getAccount({ + /// // productAccountId: { dotNsIdentifier: "another-product.dot", derivationIndex: 0 }, + /// // }); /// ``` #[wire(request_id = 22)] async fn get_account( @@ -56,12 +58,7 @@ pub trait Account: Send + Sync { /// Retrieve a contextual alias for a product account. /// /// ```ts - /// const result = await truapi.account.getAccountAlias({ - /// productAccountId: { - /// dotNsIdentifier: "truapi-playground.dot", - /// derivationIndex: 0, - /// }, - /// }); + /// const result = await truapi.account.getAccountAlias({ productAccountId: { derivationIndex: 0 } }); /// assert(result.isOk(), "getAccountAlias failed:", result); /// console.log("account alias:", result.value); /// ``` @@ -80,10 +77,7 @@ pub trait Account: Send + Sync { /// import { PASEO_NEXT_V2_ASSET_HUB } from "@parity/truapi"; /// /// const result = await truapi.account.createAccountProof({ - /// productAccountId: { - /// dotNsIdentifier: "truapi-playground.dot", - /// derivationIndex: 0, - /// }, + /// productAccountId: { derivationIndex: 0 }, /// ringLocation: { /// genesisHash: PASEO_NEXT_V2_ASSET_HUB.genesis, /// ringRootHash: "0xd6eec26135305a8ad257a20d003357284c8aa03d0bdb2b357ab0a22371e11ef2", diff --git a/rust/crates/truapi/src/api/signing.rs b/rust/crates/truapi/src/api/signing.rs index be41d9c5..cf33a0d6 100644 --- a/rust/crates/truapi/src/api/signing.rs +++ b/rust/crates/truapi/src/api/signing.rs @@ -23,10 +23,7 @@ pub trait Signing: Send + Sync { /// import { PASEO_NEXT_V2_ASSET_HUB } from "@parity/truapi"; /// /// const result = await truapi.signing.createTransaction({ - /// signer: { - /// dotNsIdentifier: "truapi-playground.dot", - /// derivationIndex: 0, - /// }, + /// signer: { derivationIndex: 0 }, /// genesisHash: PASEO_NEXT_V2_ASSET_HUB.genesis, /// callData: "0x0000", /// extensions: [], @@ -34,6 +31,17 @@ pub trait Signing: Send + Sync { /// }); /// assert(result.isOk(), "createTransaction failed:", result); /// console.log("transaction created:", result.value); + /// + /// // To sign with another product's account, set `signer.dotNsIdentifier`. This is + /// // cross-product access and requires the `ExternalAccount` permission — host-side + /// // enforcement is not yet implemented. + /// // await truapi.signing.createTransaction({ + /// // signer: { dotNsIdentifier: "another-product.dot", derivationIndex: 0 }, + /// // genesisHash: PASEO_NEXT_V2_ASSET_HUB.genesis, + /// // callData: "0x0000", + /// // extensions: [], + /// // txExtVersion: 0, + /// // }); /// ``` #[wire(request_id = 30)] async fn create_transaction( @@ -134,7 +142,7 @@ pub trait Signing: Send + Sync { /// /// ```ts /// const result = await truapi.signing.signRaw({ - /// account: { dotNsIdentifier: "truapi-playground.dot", derivationIndex: 0 }, + /// account: { derivationIndex: 0 }, /// payload: { /// tag: "Bytes", /// value: { @@ -144,6 +152,14 @@ pub trait Signing: Send + Sync { /// }); /// assert(result.isOk(), "signRaw failed:", result); /// console.log("raw bytes signed:", result.value); + /// + /// // To sign with another product's account, set `account.dotNsIdentifier`. This is + /// // cross-product access and requires the `ExternalAccount` permission — host-side + /// // enforcement is not yet implemented. + /// // await truapi.signing.signRaw({ + /// // account: { dotNsIdentifier: "another-product.dot", derivationIndex: 0 }, + /// // payload: { tag: "Bytes", value: { bytes: "0x48656c6c6f2c20776f726c6421" } }, + /// // }); /// ``` #[wire(request_id = 114)] async fn sign_raw( @@ -160,7 +176,7 @@ pub trait Signing: Send + Sync { /// import { PASEO_NEXT_V2_ASSET_HUB } from "@parity/truapi"; /// /// const result = await truapi.signing.signPayload({ - /// account: { dotNsIdentifier: "truapi-playground.dot", derivationIndex: 0 }, + /// account: { derivationIndex: 0 }, /// payload: { /// blockHash: "0xd6eec26135305a8ad257a20d003357284c8aa03d0bdb2b357ab0a22371e11ef2", /// blockNumber: "0x00000000", diff --git a/rust/crates/truapi/src/api/statement_store.rs b/rust/crates/truapi/src/api/statement_store.rs index 5addc9b7..1bdaa510 100644 --- a/rust/crates/truapi/src/api/statement_store.rs +++ b/rust/crates/truapi/src/api/statement_store.rs @@ -25,10 +25,7 @@ pub trait StatementStore: Send + Sync { /// const statement: Statement = { expiry, topics: [topic] }; /// /// const proofResult = await truapi.statementStore.createProof({ - /// productAccountId: { - /// dotNsIdentifier: "truapi-playground.dot", - /// derivationIndex: 0, - /// }, + /// productAccountId: { derivationIndex: 0 }, /// statement, /// }); /// assert(proofResult.isOk(), "createProof failed:", proofResult); @@ -74,10 +71,7 @@ pub trait StatementStore: Send + Sync { /// const topic: `0x${string}` = `0x${bytes.toHex()}`; /// const statement = { expiry, topics: [topic] }; /// const result = await truapi.statementStore.createProof({ - /// productAccountId: { - /// dotNsIdentifier: "truapi-playground.dot", - /// derivationIndex: 0, - /// }, + /// productAccountId: { derivationIndex: 0 }, /// statement, /// }); /// assert(result.isOk(), "createProof failed:", result); @@ -133,10 +127,7 @@ pub trait StatementStore: Send + Sync { /// const statement = { expiry, topics: [topic] }; /// /// const proofResult = await truapi.statementStore.createProof({ - /// productAccountId: { - /// dotNsIdentifier: "truapi-playground.dot", - /// derivationIndex: 0, - /// }, + /// productAccountId: { derivationIndex: 0 }, /// statement, /// }); /// assert(proofResult.isOk(), "createProof failed:", proofResult); diff --git a/rust/crates/truapi/src/v01/account.rs b/rust/crates/truapi/src/v01/account.rs index f3b928ec..e3b11167 100644 --- a/rust/crates/truapi/src/v01/account.rs +++ b/rust/crates/truapi/src/v01/account.rs @@ -1,11 +1,17 @@ use parity_scale_codec::{Decode, Encode}; -/// Identifies a product-specific account by combining a dotNS domain name with a +/// Identifies a product-specific account by an optional dotNS domain name and a /// derivation index. +/// +/// When `dot_ns_identifier` is `None`, the account resolves against the caller's +/// own product. When it is `Some(domain)` naming a different product, the call is +/// cross-product access and requires the +/// [`ExternalAccount`](crate::v01::RemotePermission::ExternalAccount) permission. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct ProductAccountId { - /// A dotNS domain name identifier (e.g., `"my-product.dot"`). - pub dot_ns_identifier: String, + /// Optional dotNS domain name identifier (e.g., `"my-product.dot"`). `None` + /// targets the caller's own product. + pub dot_ns_identifier: Option, /// Key derivation index for generating product-specific accounts. pub derivation_index: u32, } @@ -60,7 +66,7 @@ pub struct RingLocation { /// Request to create a ring VRF proof for a product account. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostAccountCreateProofRequest { - /// Product account that should create the proof. + /// Account that should create the proof. pub product_account_id: ProductAccountId, /// Ring location to use for proof generation. pub ring_location: RingLocation, @@ -127,7 +133,7 @@ pub enum HostAccountCreateProofError { /// Request to retrieve a product-scoped account. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostAccountGetRequest { - /// Product account to retrieve. + /// Account to retrieve. pub product_account_id: ProductAccountId, } @@ -159,7 +165,7 @@ pub enum HostGetUserIdError { /// Request to retrieve a contextual alias for a product account. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostAccountGetAliasRequest { - /// Product account to derive the alias for. + /// Account to derive the alias for. pub product_account_id: ProductAccountId, } diff --git a/rust/crates/truapi/src/v01/permissions.rs b/rust/crates/truapi/src/v01/permissions.rs index c4873a6f..86eaf486 100644 --- a/rust/crates/truapi/src/v01/permissions.rs +++ b/rust/crates/truapi/src/v01/permissions.rs @@ -31,8 +31,9 @@ pub enum HostDevicePermissionRequest { /// One remote-operation permission requested by the product (RFC 0002). /// -/// `ChainSubmit`, `PreimageSubmit`, and `StatementSubmit` are also triggered -/// implicitly by the corresponding business calls when not yet granted. +/// `ChainSubmit`, `PreimageSubmit`, `StatementSubmit`, and `ExternalAccount` are +/// also triggered implicitly by the corresponding business calls when not yet +/// granted. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, Display)] pub enum RemotePermission { /// Outbound HTTP/WebSocket access to a set of domains. @@ -53,6 +54,9 @@ pub enum RemotePermission { /// Submitting statements on behalf of the user via `remote_statement_store_submit`. #[display("submit statements")] StatementSubmit, + /// Accessing accounts of another product, addressed by a foreign dotNS identifier. + #[display("access external product accounts")] + ExternalAccount, } /// remote-permission request (RFC 0002). diff --git a/rust/crates/truapi/src/v01/signing.rs b/rust/crates/truapi/src/v01/signing.rs index f0c1324d..78e22d2c 100644 --- a/rust/crates/truapi/src/v01/signing.rs +++ b/rust/crates/truapi/src/v01/signing.rs @@ -1,6 +1,5 @@ -use parity_scale_codec::{Decode, Encode}; - use super::ProductAccountId; +use parity_scale_codec::{Decode, Encode}; /// Full Substrate extrinsic signing payload with all fields needed for signature /// generation. @@ -41,7 +40,7 @@ pub struct HostSignPayloadData { /// Request to sign an extrinsic payload with a product account. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostSignPayloadRequest { - /// Product account that will sign this payload. + /// Account that will sign this payload. pub account: ProductAccountId, /// The extrinsic payload to sign. pub payload: HostSignPayloadData, @@ -65,7 +64,7 @@ pub enum RawPayload { /// A raw signing request pairing an account with the payload to sign. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct HostSignRawRequest { - /// Product account that will sign this payload. + /// Account that will sign this payload. pub account: ProductAccountId, /// The payload to sign. pub payload: RawPayload, diff --git a/rust/crates/truapi/src/v01/statement_store.rs b/rust/crates/truapi/src/v01/statement_store.rs index c4217005..972baf18 100644 --- a/rust/crates/truapi/src/v01/statement_store.rs +++ b/rust/crates/truapi/src/v01/statement_store.rs @@ -1,6 +1,5 @@ -use parity_scale_codec::{Decode, Encode}; - use super::ProductAccountId; +use parity_scale_codec::{Decode, Encode}; /// Cryptographic proof for a statement. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] @@ -65,7 +64,7 @@ pub struct SignedStatement { /// Request to create a cryptographic proof for a statement. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct RemoteStatementStoreCreateProofRequest { - /// Product account that should create the proof. + /// Account that should create the proof. pub product_account_id: ProductAccountId, /// Statement to prove. pub statement: Statement, diff --git a/rust/crates/truapi/src/v01/transaction.rs b/rust/crates/truapi/src/v01/transaction.rs index 82db1b4a..6c4f485a 100644 --- a/rust/crates/truapi/src/v01/transaction.rs +++ b/rust/crates/truapi/src/v01/transaction.rs @@ -1,6 +1,5 @@ -use parity_scale_codec::{Decode, Encode}; - use super::ProductAccountId; +use parity_scale_codec::{Decode, Encode}; /// A 32-byte chain genesis hash used to identify the target chain. pub type GenesisHash = [u8; 32]; @@ -21,12 +20,11 @@ pub struct TxPayloadExtension { /// Transaction payload for a product account. /// -/// Contains everything the host needs to construct a signed extrinsic. -/// The signer is a [`ProductAccountId`]; the host resolves the -/// corresponding key pair through its account management layer. +/// Contains everything the host needs to construct a signed extrinsic. The host +/// resolves the signer's key pair through its account management layer. #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct ProductAccountTxPayload { - /// Product account that will sign the transaction. + /// Account that will sign the transaction. pub signer: ProductAccountId, /// Chain where the transaction will execute. pub genesis_hash: GenesisHash,