diff --git a/docs/.astro/settings.json b/docs/.astro/settings.json deleted file mode 100644 index 9e6829d..0000000 --- a/docs/.astro/settings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "_variables": { - "lastUpdateCheck": 1723148542874 - } -} diff --git a/docs/.astro/types.d.ts b/docs/.astro/types.d.ts deleted file mode 100644 index c268a41..0000000 --- a/docs/.astro/types.d.ts +++ /dev/null @@ -1,374 +0,0 @@ -declare module 'astro:content' { - interface Render { - '.mdx': Promise<{ - Content: import('astro').MarkdownInstance<{}>['Content']; - headings: import('astro').MarkdownHeading[]; - remarkPluginFrontmatter: Record; - }>; - } -} - -declare module 'astro:content' { - interface Render { - '.md': Promise<{ - Content: import('astro').MarkdownInstance<{}>['Content']; - headings: import('astro').MarkdownHeading[]; - remarkPluginFrontmatter: Record; - }>; - } -} - -declare module 'astro:content' { - type Flatten = T extends { [K: string]: infer U } ? U : never; - - export type CollectionKey = keyof AnyEntryMap; - export type CollectionEntry = Flatten; - - export type ContentCollectionKey = keyof ContentEntryMap; - export type DataCollectionKey = keyof DataEntryMap; - - type AllValuesOf = T extends any ? T[keyof T] : never; - type ValidContentEntrySlug = AllValuesOf< - ContentEntryMap[C] - >['slug']; - - export function getEntryBySlug< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}), - >( - collection: C, - // Note that this has to accept a regular string too, for SSR - entrySlug: E - ): E extends ValidContentEntrySlug - ? Promise> - : Promise | undefined>; - - export function getDataEntryById( - collection: C, - entryId: E - ): Promise>; - - export function getCollection>( - collection: C, - filter?: (entry: CollectionEntry) => entry is E - ): Promise; - export function getCollection( - collection: C, - filter?: (entry: CollectionEntry) => unknown - ): Promise[]>; - - export function getEntry< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}), - >(entry: { - collection: C; - slug: E; - }): E extends ValidContentEntrySlug - ? Promise> - : Promise | undefined>; - export function getEntry< - C extends keyof DataEntryMap, - E extends keyof DataEntryMap[C] | (string & {}), - >(entry: { - collection: C; - id: E; - }): E extends keyof DataEntryMap[C] - ? Promise - : Promise | undefined>; - export function getEntry< - C extends keyof ContentEntryMap, - E extends ValidContentEntrySlug | (string & {}), - >( - collection: C, - slug: E - ): E extends ValidContentEntrySlug - ? Promise> - : Promise | undefined>; - export function getEntry< - C extends keyof DataEntryMap, - E extends keyof DataEntryMap[C] | (string & {}), - >( - collection: C, - id: E - ): E extends keyof DataEntryMap[C] - ? Promise - : Promise | undefined>; - - /** Resolve an array of entry references from the same collection */ - export function getEntries( - entries: { - collection: C; - slug: ValidContentEntrySlug; - }[] - ): Promise[]>; - export function getEntries( - entries: { - collection: C; - id: keyof DataEntryMap[C]; - }[] - ): Promise[]>; - - export function reference( - collection: C - ): import('astro/zod').ZodEffects< - import('astro/zod').ZodString, - C extends keyof ContentEntryMap - ? { - collection: C; - slug: ValidContentEntrySlug; - } - : { - collection: C; - id: keyof DataEntryMap[C]; - } - >; - // Allow generic `string` to avoid excessive type errors in the config - // if `dev` is not running to update as you edit. - // Invalid collection names will be caught at build time. - export function reference( - collection: C - ): import('astro/zod').ZodEffects; - - type ReturnTypeOrOriginal = T extends (...args: any[]) => infer R ? R : T; - type InferEntrySchema = import('astro/zod').infer< - ReturnTypeOrOriginal['schema']> - >; - - type ContentEntryMap = { - "docs": { -"architecture.md": { - id: "architecture.md"; - slug: "architecture"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".md"] }; -"clients/android/answer.mdx": { - id: "clients/android/answer.mdx"; - slug: "clients/android/answer"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"clients/android/authentication.mdx": { - id: "clients/android/authentication.mdx"; - slug: "clients/android/authentication"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"clients/android/introduction.mdx": { - id: "clients/android/introduction.mdx"; - slug: "clients/android/introduction"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"clients/android/offer.mdx": { - id: "clients/android/offer.mdx"; - slug: "clients/android/offer"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"clients/android/provider-service/authenticate.md": { - id: "clients/android/provider-service/authenticate.md"; - slug: "clients/android/provider-service/authenticate"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".md"] }; -"clients/android/provider-service/introduction.mdx": { - id: "clients/android/provider-service/introduction.mdx"; - slug: "clients/android/provider-service/introduction"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"clients/android/provider-service/register.md": { - id: "clients/android/provider-service/register.md"; - slug: "clients/android/provider-service/register"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".md"] }; -"clients/android/registration.mdx": { - id: "clients/android/registration.mdx"; - slug: "clients/android/registration"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"clients/browser/answer.mdx": { - id: "clients/browser/answer.mdx"; - slug: "clients/browser/answer"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"clients/browser/authentication.md": { - id: "clients/browser/authentication.md"; - slug: "clients/browser/authentication"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".md"] }; -"clients/browser/example.md": { - id: "clients/browser/example.md"; - slug: "clients/browser/example"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".md"] }; -"clients/browser/introduction.mdx": { - id: "clients/browser/introduction.mdx"; - slug: "clients/browser/introduction"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"clients/browser/offer.mdx": { - id: "clients/browser/offer.mdx"; - slug: "clients/browser/offer"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"clients/browser/registration.mdx": { - id: "clients/browser/registration.mdx"; - slug: "clients/browser/registration"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"guides/Passkey/authentication.mdx": { - id: "guides/Passkey/authentication.mdx"; - slug: "guides/passkey/authentication"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"guides/Passkey/extension.mdx": { - id: "guides/Passkey/extension.mdx"; - slug: "guides/passkey/extension"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"guides/Passkey/registration.mdx": { - id: "guides/Passkey/registration.mdx"; - slug: "guides/passkey/registration"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"guides/Peer to Peer/answer.mdx": { - id: "guides/Peer to Peer/answer.mdx"; - slug: "guides/peer-to-peer/answer"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"guides/Peer to Peer/exchange.mdx": { - id: "guides/Peer to Peer/exchange.mdx"; - slug: "guides/peer-to-peer/exchange"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"guides/Peer to Peer/offer.mdx": { - id: "guides/Peer to Peer/offer.mdx"; - slug: "guides/peer-to-peer/offer"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"guides/components.mdx": { - id: "guides/components.mdx"; - slug: "guides/components"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"guides/concepts.mdx": { - id: "guides/concepts.mdx"; - slug: "guides/concepts"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"guides/getting-started.mdx": { - id: "guides/getting-started.mdx"; - slug: "guides/getting-started"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"guides/linking.mdx": { - id: "guides/linking.mdx"; - slug: "guides/linking"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".mdx"] }; -"introduction.md": { - id: "introduction.md"; - slug: "introduction"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".md"] }; -"server/environment-variables.md": { - id: "server/environment-variables.md"; - slug: "server/environment-variables"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".md"] }; -"server/integrations.md": { - id: "server/integrations.md"; - slug: "server/integrations"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".md"] }; -"server/introduction.md": { - id: "server/introduction.md"; - slug: "server/introduction"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".md"] }; -"server/running-locally.md": { - id: "server/running-locally.md"; - slug: "server/running-locally"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".md"] }; -"server/tmp.md": { - id: "server/tmp.md"; - slug: "server/tmp"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".md"] }; -"server/tmp2.md": { - id: "server/tmp2.md"; - slug: "server/tmp2"; - body: string; - collection: "docs"; - data: any -} & { render(): Render[".md"] }; -}; - - }; - - type DataEntryMap = { - - }; - - type AnyEntryMap = ContentEntryMap & DataEntryMap; - - export type ContentConfig = never; -} diff --git a/docs/.gitignore b/docs/.gitignore index 2aa7be8..68e7196 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,3 +1,5 @@ +.astro + clients/liquid-auth-js clients/liquid-auth-android diff --git a/docs/package-lock.json b/docs/package-lock.json index 08561c0..38c3b25 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -33,7 +33,8 @@ "devDependencies": { "@vitejs/plugin-basic-ssl": "^1.1.0", "socket.io-client": "^4.7.5", - "tweetnacl": "^1.0.3" + "tweetnacl": "^1.0.3", + "uuid": "^10.0.0" } }, "node_modules/@algorandfoundation/liquid-client": { @@ -50,18 +51,6 @@ "uuid": "^10.0.0" } }, - "node_modules/@algorandfoundation/liquid-client/node_modules/uuid": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", - "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -6926,6 +6915,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mermaid/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/micromark": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", @@ -10629,9 +10630,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "funding": [ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" diff --git a/docs/package.json b/docs/package.json index 44b5fe4..233fd38 100644 --- a/docs/package.json +++ b/docs/package.json @@ -35,6 +35,7 @@ "devDependencies": { "@vitejs/plugin-basic-ssl": "^1.1.0", "socket.io-client": "^4.7.5", - "tweetnacl": "^1.0.3" + "tweetnacl": "^1.0.3", + "uuid": "^10.0.0" } } diff --git a/docs/src/content/docs/clients/android/answer.mdx b/docs/src/content/docs/clients/android/answer.mdx index 7d8ecc8..55651c4 100644 --- a/docs/src/content/docs/clients/android/answer.mdx +++ b/docs/src/content/docs/clients/android/answer.mdx @@ -24,16 +24,49 @@ val dc = client.peer(requestId, "offer" ) ``` -## Data Channel[WIP] +## Data Channel -We are working on a more robust way to handle data channel messages. For now, you can use the following: +Handling the Datachannel can be done with the `foundation.algorand.provider` library. ```kotlin // Example.kt -// Handle Peer Messages +// AVM Encoder +val encoder = foundation.algorand.crypto.avm.Encoder() + +// Create a list of transactions that are the msgpack bytes represented as Base64URL strings +val transactionsToSign: List = createTransactions() + +// Crete the Params +val params = SignTransactionsParams( + providerId, transactionsToSign +) + +// Create the Request +val request = RequestMessage("UUID_OF_MESSAGE", "arc0027:sign_transactions:request", params) + +// Send the request +dc.send(Base64.UrlSafe.encode(request.toByteArray(EncoderType.CBOR))) + +// Handle Response Messages client.handleDataChannel(dc, { - Log.d(TAG, "onMessage($it)") + // Decode Message + val response = encoder.decode(Base64.UrlSafe.decode($it), EncoderType.CBOR) + when (response.reference) { + "arc0027:sign_transactions:response" -> { + // Decode the Result + val result = encoder.decode( + encoder.encode(message.params, EncoderType.NONE), EncoderType.NONE + ) + // Process the signatures in your project, + // they will be keyed to the original request + processSignatureResult(result) + } + else -> { + throw IllegalArgumentException("Invalid reference: ${message.reference}") + } + } + }, { Log.d(TAG, "onStateChange($it)") }) diff --git a/docs/src/content/docs/clients/android/offer.mdx b/docs/src/content/docs/clients/android/offer.mdx index 2e6dc85..73b1054 100644 --- a/docs/src/content/docs/clients/android/offer.mdx +++ b/docs/src/content/docs/clients/android/offer.mdx @@ -27,15 +27,104 @@ val client = SignalClient(origin, context, httpClient) val dc = client.peer(requestId, "answer" ) ``` -## Data Channel[WIP] +## Data Channel -We are working on a more robust way to handle data channel messages. For now, you can use the following: +Handling the Datachannel can be done with the `foundation.algorand.provider` library. +Wallets can implement their own Provider strategy for handling messages + +The following is an example of a Provider Implementation: + +```kotlin +package com.example.my-wallet + +import com.algorand.algosdk.transaction.Transaction +import com.algorand.algosdk.util.Encoder +import foundation.algorand.crypto.EncoderType +import foundation.algorand.crypto.avm.KeyPairs +import foundation.algorand.provider.Message +import foundation.algorand.provider.avm.models.* +import java.security.KeyPair +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi + + +/** + * A provider for the Algorand Virtual Machine (AVM). + * Used to test the provider.avm.models package. + */ +class AVMProvider(val providerId: String) { + val encoder = foundation.algorand.crypto.avm.Encoder() + // A Security KeyPair + var keyPair: KeyPair? = null + /** + * Handle a message from a channel + */ + fun handleRequestMessage(msg: Message, keyPair: KeyPair): Any { + val message = encoder.decode(msg.data, msg.encoding) + this.keyPair = keyPair + + // Handle the message references + when (message.reference) { + "arc0027:sign_transactions:request" -> { + val request = encoder.decode( + encoder.encode(message.params, EncoderType.NONE), EncoderType.NONE + ) + return processSignTransactions(request) + } + else -> { + throw IllegalArgumentException("Invalid reference: ${message.reference}") + } + } + } + /** + * Decode Unsigned Transaction + */ + @OptIn(ExperimentalEncodingApi::class) + private fun decodeUnsignedTransaction(unsignedTxn: String): Transaction? { + return Encoder.decodeFromMsgPack(Base64.decode(unsignedTxn), Transaction::class.java) + } + /** + * Process the Sign Transactions Requests + */ + @OptIn(ExperimentalEncodingApi::class) + fun processSignTransactions(params: SignTransactionsParams): SignTransactionsResult { + require(params.validate()) + + val signedTxns = mutableListOf() + params.txns.forEach { txn -> + val inst = decodeUnsignedTransaction(txn.txn!!) + val signature = KeyPairs.rawSignBytes(inst!!.bytesToSign(), this.keyPair!!.private) + signedTxns.add(Base64.UrlSafe.encode(signature!!)) + } + // Create the response payload + return SignTransactionsResult(providerId, signedTxns) + } +} +``` + +The following is using a Provider with the SignalClient ```kotlin // Example.kt +import com.fasterxml.uuid.Generators +import foundation.algorand.crypto.EncoderType +import foundation.algorand.provider.Message +import foundation.algorand.provider.avm.models.SignMessageParams +import foundation.algorand.provider.avm.models.SignMessageResult +import foundation.algorand.provider.avm.models.SignTransactionsResult +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi + +val provider = AVMProvider("PROVIDER_UUID") +val keyPair = getKeypairFromSecureStore() client.handleDataChannel(dc, { - Log.d(TAG, "onMessage($it)") + // Decode the Message + val msg = Message(Base64.UrlSafe.decode($it), EncoderType.CBOR) + // Create the Result + val result = provider.handleRequestMessage(msg, keyPair) + // Send to the Response + dc.send(Base64.UrlSafe.encode(result.toByteArray(EncoderType.CBOR))) }, { Log.d(TAG, "onStateChange($it)") }) diff --git a/docs/src/content/docs/clients/browser/answer.mdx b/docs/src/content/docs/clients/browser/answer.mdx index 550ba1d..fd67b20 100644 --- a/docs/src/content/docs/clients/browser/answer.mdx +++ b/docs/src/content/docs/clients/browser/answer.mdx @@ -34,17 +34,76 @@ client const blob = await client.qrCode() ``` -## Data Channel[WIP] +## Data Channel -We are working on a more robust way to handle data channel messages. For now, you can use the following: +Handling the Datachannel can be done with the `@algorandfoundation/provider` library ```typescript +import {encode as cborEncode, decode as cborDecode} from 'cbor-x'; +import {encode as msgpackEncode} from 'algorand-msgpack'; +import { v7 as uuidv7 } from 'uuid'; -function handleDataChannel(dc: RTCDataChannel){ - dc.onmessage = (event: MessageEvent) => { - console.log(event.data) +import { + RequestMessageBuilder, + SignTransactionsParamsBuilder, + toBase64URL, + fromBase64URL, +} from "@algorandfoundation/provider"; + + +let dc: RTCDataChannel + +// Provider ID of the Wallet, otherwise it should be the default Liquid UUID +const providerId = uuidv7() + +// Create an encoded transaction using your algorand specific library +const txns = [ + toBase64URL( + // Replace this with Encoded Transaction + new Uint8Array(64) + ) +] + +// UUID of the Message +const messageId = uuidv7() +const params = new SignTransactionsParamsBuilder() + .addProviderId(providerId) + .addTxns(txns) + .get() + +// Create the Request Message +const request = new RequestMessageBuilder(messageId, "arc0027:sign_transactions:request") + .addParams(params) + .get() + +// Send the Request Message +dc.send(toBase64URL(cborEncode(request))) + +// Wait for a response for the message +dc.onmessage = async (evt: {data: string}) => { + const message = cborDecode(fromBase64URL(evt.data)) + // Handle message types and create response + + if(message.reference === '"arc0027:sign_transactions:response'){ + // Make sure it's the appropriate message we are attaching the signature to + if(message.requestId !== messageId) return + + const encodedSignatures: string[] = message.params.stxns + + // Attach Signature Example: + const transactionsToSend: string[] = txns.map((encodedTxn, idx)=>{ + // Getting the Transaction Bytes + const txnBytes = fromBase64URL(encodedTxn) + + // Decode and attach the signature with the library you prefer: + const txn = decodeUnsignedTransaction(txnBytes) + return txn.attachSignature(fromBase64URL(encodedSignatures[idx])) + }) + + // Send the txns to the network: + for(const txn in transactionsToSend){ + const { txId } = await algod.sendRawTransaction(txn).do(); } - dc.send('Hello world!') + } } -``` - +``` \ No newline at end of file diff --git a/docs/src/content/docs/clients/browser/offer.mdx b/docs/src/content/docs/clients/browser/offer.mdx index 619f0fc..378cc94 100644 --- a/docs/src/content/docs/clients/browser/offer.mdx +++ b/docs/src/content/docs/clients/browser/offer.mdx @@ -41,13 +41,63 @@ const dc = await client.peer( ) ``` -## Data Channel[WIP] +## Data Channel -We are working on a more robust way to handle data channel messages. For now, you can use the following: +Handling the Datachannel can be done with the `@algorandfoundation/provider` library ```typescript -dc.onmessage = (event: MessageEvent) => { - console.log(event.data) +import {encode as cborEncode, decode as cborDecode} from 'cbor-x' +import { + ResponseMessageBuilder, + SignTransactionsResultBuilder, + toBase64URL, + fromBase64URL, + IARC0001Transaction, +} from "@algorandfoundation/provider"; + +import { randomBytes } from "tweetnacl"; +import { v7 as uuidv7 } from 'uuid'; + +let dc: RTCDataChannel + +// Provider ID for your wallet +const providerId = uuidv7() + +dc.onmessage = (evt: {data: string}) => { + const message = cborDecode(fromBase64URL(evt.data)) + // Handle message types and create response + + if(message.reference === '"arc0027:sign_transactions:request'){ + // Make sure it's the appropriate provider + if(message.params.providerId !== providerId) return + + const encodedTxns: IARC0001Transaction[] = message.params.txns + + // Decode the transactions and add the signatures using any available method + + // Fake Signature Example: + const stxns: string[] = [ + // Replace with actual signatures + toBase64URL(randomBytes()) + ] + + // Create the Sign Transactions Result + const signTransactionsResult = SignTransactionsResultBuilder() + .addProviderId(providerId) + .addSignedTxns(stxns) + .get() + + // Create the Response Message + const response = ResponseMessageBuilder( + uuidv7(), // New UUID for Response Message + message.id, // UUID of the Request Message + "arc0027:sign_transactions:response", // Reference Type of the Message + ) + .addResult(signTransactionsResult) + .get() + + // Send the Response + dc.send(toBase64URL(cborEncode(response))) } -dc.send('Hello world!') +} ```