From b316c027a715be90c9c19a778c9496b083f406ab Mon Sep 17 00:00:00 2001 From: Alex Stelea Date: Thu, 17 Oct 2024 11:17:47 +0100 Subject: [PATCH] feat(rdt): add subintent request builder --- examples/simple-dapp/src/main.ts | 16 ++- packages/dapp-toolkit/src/_types.ts | 10 +- .../src/modules/wallet-request/index.ts | 1 + .../pre-authorization-request/index.ts | 1 + .../subintent-builder.spec.ts | 55 +++++++++ .../subintent-builder.ts | 106 ++++++++++++++++++ .../modules/wallet-request/wallet-request.ts | 13 +-- packages/dapp-toolkit/src/schemas/index.ts | 2 +- 8 files changed, 175 insertions(+), 29 deletions(-) create mode 100644 packages/dapp-toolkit/src/modules/wallet-request/pre-authorization-request/index.ts create mode 100644 packages/dapp-toolkit/src/modules/wallet-request/pre-authorization-request/subintent-builder.spec.ts create mode 100644 packages/dapp-toolkit/src/modules/wallet-request/pre-authorization-request/subintent-builder.ts diff --git a/examples/simple-dapp/src/main.ts b/examples/simple-dapp/src/main.ts index 9f34c98d..56e41af1 100644 --- a/examples/simple-dapp/src/main.ts +++ b/examples/simple-dapp/src/main.ts @@ -8,6 +8,7 @@ import { OneTimeDataRequestBuilder, LocalStorageModule, generateRolaChallenge, + SubintentRequestBuilder, } from '@radixdlt/radix-dapp-toolkit' const dAppDefinitionAddress = import.meta.env.VITE_DAPP_DEFINITION_ADDRESS @@ -85,16 +86,13 @@ removeCb.onclick = () => { subintentButton.onclick = async () => { console.log(subintentManifest.value) - const result = await dAppToolkit.walletApi.sendPreAuthorizationRequest({ - transactionManifest: subintentManifest.value, - childSubintentHashes: [], - expiration: { - discriminator: 'expireAfterSignature', - value: 3600, - }, - }) + const result = await dAppToolkit.walletApi.sendPreAuthorizationRequest( + SubintentRequestBuilder() + .manifest(subintentManifest.value) + .setExpiration('secondsAfterSignature', 3600), + ) - console.log(result); + console.log(result) } addCb.onclick = () => { diff --git a/packages/dapp-toolkit/src/_types.ts b/packages/dapp-toolkit/src/_types.ts index b84fbd8f..bc4f670d 100644 --- a/packages/dapp-toolkit/src/_types.ts +++ b/packages/dapp-toolkit/src/_types.ts @@ -26,6 +26,7 @@ import type { WalletRequestModule, ConnectButtonModule, } from './modules' +import { BuildableSubintentRequest } from './modules/wallet-request/pre-authorization-request/subintent-builder' export type Providers = { connectButtonModule: ConnectButtonModule @@ -100,14 +101,7 @@ export type SendTransactionInput = { onTransactionId?: (transactionId: string) => void } -export type SendPreAuthorizationRequestInput = { - transactionManifest: string - version?: number - blobs?: string[] - message?: string - childSubintentHashes: string[] - expiration: ExpireAtTime | ExpireAfterSignature -} +export type SendPreAuthorizationRequestInput = BuildableSubintentRequest export type ButtonApi = { setMode: (value: 'light' | 'dark') => void diff --git a/packages/dapp-toolkit/src/modules/wallet-request/index.ts b/packages/dapp-toolkit/src/modules/wallet-request/index.ts index 2db1f238..9ba1455d 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/index.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/index.ts @@ -6,3 +6,4 @@ export * from './session/session.module' export * from './transport' export * from './wallet-request-sdk' export * from './wallet-request' +export * from './pre-authorization-request' diff --git a/packages/dapp-toolkit/src/modules/wallet-request/pre-authorization-request/index.ts b/packages/dapp-toolkit/src/modules/wallet-request/pre-authorization-request/index.ts new file mode 100644 index 00000000..7b48a2e5 --- /dev/null +++ b/packages/dapp-toolkit/src/modules/wallet-request/pre-authorization-request/index.ts @@ -0,0 +1 @@ +export * from './subintent-builder' diff --git a/packages/dapp-toolkit/src/modules/wallet-request/pre-authorization-request/subintent-builder.spec.ts b/packages/dapp-toolkit/src/modules/wallet-request/pre-authorization-request/subintent-builder.spec.ts new file mode 100644 index 00000000..b36f85b9 --- /dev/null +++ b/packages/dapp-toolkit/src/modules/wallet-request/pre-authorization-request/subintent-builder.spec.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from 'vitest' +import { SubintentRequestBuilder } from './subintent-builder' + +describe('SubintentRequestBuilder', () => { + it('should build a subintent request', () => { + const tx = SubintentRequestBuilder() + .manifest('...') + .setExpiration('secondsAfterSignature', 60) + .addBlobs('deadbeef', 'beefdead') + .message('hello') + .toRequestItem() + + expect(tx).toEqual({ + discriminator: 'subintent', + version: 1, + transactionManifestVersion: 1, + transactionManifest: '...', + expiration: { + discriminator: 'expireAfterSignature', + value: 60, + }, + blobs: ['deadbeef', 'beefdead'], + message: 'hello', + }) + }) + + it('should build a subintent request using raw object', () => { + const tx = SubintentRequestBuilder() + .rawConfig({ + version: 1, + transactionManifestVersion: 1, + transactionManifest: '...', + expiration: { + discriminator: 'expireAfterSignature', + value: 60, + }, + blobs: ['deadbeef', 'beefdead'], + message: 'hello', + }) + .toRequestItem() + + expect(tx).toEqual({ + discriminator: 'subintent', + version: 1, + transactionManifestVersion: 1, + transactionManifest: '...', + expiration: { + discriminator: 'expireAfterSignature', + value: 60, + }, + blobs: ['deadbeef', 'beefdead'], + message: 'hello', + }) + }) +}) diff --git a/packages/dapp-toolkit/src/modules/wallet-request/pre-authorization-request/subintent-builder.ts b/packages/dapp-toolkit/src/modules/wallet-request/pre-authorization-request/subintent-builder.ts new file mode 100644 index 00000000..aa3a89cc --- /dev/null +++ b/packages/dapp-toolkit/src/modules/wallet-request/pre-authorization-request/subintent-builder.ts @@ -0,0 +1,106 @@ +import { SubintentRequestItem } from '../../../schemas' + +export type BuildableSubintentRequest = { + toRequestItem: () => SubintentRequestItem +} +/** + * A builder function for creating a SubintentRequest. + * + * @returns An object with methods to configure and build a SubintentRequestItem. + * + * @example + * const builder = SubintentRequestBuilder(); + * const requestItem = builder + * .manifest('some-manifest') + * .setExpiration('atTime', 1234567890) + * .addBlobs('blob1', 'blob2') + * .message('This is a message') + * + * @method setExpiration + * Sets the expiration for the subintent request. + * + * @param type - The type of expiration, either 'atTime' or 'secondsAfterSignature'. + * @param value - The value of the expiration. If type is 'atTime', this is a Unix timestamp. If type is 'secondsAfterSignature', this is the number of seconds after the signature is created. + * @returns The API object for chaining. + * + * @method addBlobs + * Adds blobs to the subintent request. + * + * @param values - The blobs to add. + * @returns The API object for chaining. + * + * @method message + * Sets a message for the subintent request. + * + * @param value - The message to set. + * @returns The API object for chaining. + * + * @method manifest + * Sets the transaction manifest for the subintent request. + * + * @param value - The transaction manifest to set. + * @returns The API object for chaining. + * + * @method toRequestItem + * Converts the current state to a SubintentRequestItem. + * + * @returns The SubintentRequestItem. + * + * @method rawConfig + * Sets the raw configuration for the subintent request. + * + * @param rawConfig - The raw configuration object, excluding the discriminator. + * @returns An object with the toRequestItem method. + */ +export const SubintentRequestBuilder = () => { + let state: Partial = { + discriminator: 'subintent', + version: 1, + transactionManifestVersion: 1, + } + + const setExpiration = ( + type: 'atTime' | 'secondsAfterSignature', + value: number, + ) => { + state.expiration = { + discriminator: + type === 'atTime' ? 'expireAtTime' : 'expireAfterSignature', + value, + } + return api + } + + const addBlobs = (...values: string[]) => { + state.blobs = values + return api + } + + const message = (value: string) => { + state.message = value + return api + } + + const manifest = (value: string) => { + state.transactionManifest = value + return api + } + + const toRequestItem = () => state as SubintentRequestItem + + const rawConfig = ( + rawConfig: Omit, + ) => { + state = { ...rawConfig, discriminator: 'subintent' } + return { toRequestItem } + } + + const api = { + setExpiration, + addBlobs, + message, + toRequestItem, + } as const + + return { manifest, rawConfig } +} diff --git a/packages/dapp-toolkit/src/modules/wallet-request/wallet-request.ts b/packages/dapp-toolkit/src/modules/wallet-request/wallet-request.ts index 43e27f89..df5a0878 100644 --- a/packages/dapp-toolkit/src/modules/wallet-request/wallet-request.ts +++ b/packages/dapp-toolkit/src/modules/wallet-request/wallet-request.ts @@ -11,10 +11,7 @@ import { import { validateRolaChallenge, type Logger } from '../../helpers' import { TransactionStatus } from '../gateway' import { ResultAsync, err, ok, okAsync } from 'neverthrow' -import type { - MessageLifeCycleEvent, - WalletInteraction, -} from '../../schemas' +import type { MessageLifeCycleEvent, WalletInteraction } from '../../schemas' import { SdkError } from '../../error' import { DataRequestBuilderItem, @@ -310,13 +307,7 @@ export const WalletRequestModule = (input: { > => { const walletInteraction = walletRequestSdk.createWalletInteraction({ discriminator: 'preAuthorizationRequest', - subintent: { - discriminator: 'subintent', - blobs: value.blobs, - transactionManifest: value.transactionManifest, - message: value.message, - version: value.version ?? 1, - }, + subintent: value.toRequestItem(), }) return addNewRequest('preAuthorizationRequest', walletInteraction, false) diff --git a/packages/dapp-toolkit/src/schemas/index.ts b/packages/dapp-toolkit/src/schemas/index.ts index 200a0caa..85c86c4c 100644 --- a/packages/dapp-toolkit/src/schemas/index.ts +++ b/packages/dapp-toolkit/src/schemas/index.ts @@ -260,10 +260,10 @@ export type SubintentRequestItem = InferOutput export const SubintentRequestItem = object({ discriminator: literal('subintent'), version: number(), + transactionManifestVersion: number(), transactionManifest: string(), blobs: optional(array(string())), message: optional(string()), - childSubintentHashes: optional(array(string())), expiration: optional(union([ExpireAtTime, ExpireAfterSignature])), })