diff --git a/.env.example b/.env.example index 7368d7b..8ee6dc6 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,5 @@ # Wallet mnemonic (seed phrase) # Ensure default account has sufficient funds for transactions -# This is my seed phrase for sequence XYO_WALLET_MNEMONIC="orbit torch boil interest warrior spoon lounge ecology knock cereal mango habit" # RPC URL for the XYO network [Localhost] diff --git a/.vscode/launch.json b/.vscode/launch.json index d1c01b0..2b2a4f0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,26 +4,22 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { + { "type": "node", "request": "launch", - "name": "Node Hello World Sample", - "program": "${workspaceFolder}/dist/index.js", - "args": [], - "outFiles": [ - "${workspaceFolder}/dist/**" - ], + "name": "Node Hello World Runner", + "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/tsx", "runtimeArgs": [ "--max-old-space-size=8192" ], + "program": "${workspaceFolder}/src/index.ts", + "args": [], "envFile": "${workspaceFolder}/.env", "sourceMaps": true, - "resolveSourceMapLocations": [ - "!**/node_modules/**", - ], - "pauseForSourceMap": true, "cwd": "${workspaceFolder}", - "preLaunchTask": "build", + "skipFiles": [ + "/**" + ], }, { "type": "node", diff --git a/package.json b/package.json index 59d4ebd..4a4b8c6 100644 --- a/package.json +++ b/package.json @@ -25,27 +25,26 @@ "updo": "npx npm-check-updates -i" }, "dependencies": { - "@xylabs/assert": "5.0.33", - "@xylabs/delay": "5.0.33", - "@xylabs/typeof": "5.0.33", - "@xylabs/tsconfig": "7.2.8", - "@xyo-network/payload-builder": "5.1.22", - "@xyo-network/wallet": "5.1.22", - "@xyo-network/xl1-protocol": "1.13.11", - "@xyo-network/xl1-protocol-sdk": "1.16.12", - "@xyo-network/xl1-rpc": "1.16.12", + "@xylabs/assert": "5.0.63", + "@xylabs/delay": "5.0.63", + "@xylabs/tsconfig": "7.2.32", + "@xylabs/typeof": "5.0.63", + "@xyo-network/payload-builder": "5.2.25", + "@xyo-network/wallet": "5.2.25", + "@xyo-network/xl1-sdk": "1.18.33", "dotenv": "17.2.3" }, "devDependencies": { - "@types/node": "24.10.1", - "@xylabs/eslint-config-flat": "7.2.8", - "@xyo-network/payload-model": "5.1.22", - "@xyo-network/xl1-cli": "1.16.12", - "eslint": "9.39.1", + "@types/node": "25.0.9", + "@xylabs/eslint-config-flat": "7.2.32", + "@xyo-network/account-model": "5.2.25", + "@xyo-network/payload-model": "5.2.25", + "@xyo-network/xl1-cli": "1.18.1", + "eslint": "9.39.2", "eslint-import-resolver-typescript": "4.4.4", - "npm-check-updates": "19.1.2", - "tsx": "4.20.6", - "typedoc": "0.28.14", + "npm-check-updates": "19.3.1", + "tsx": "4.21.0", + "typedoc": "0.28.16", "typedoc-plugin-markdown": "4.9.0", "typescript": "5.9.3" }, @@ -60,4 +59,4 @@ "publishConfig": { "access": "restricted" } -} \ No newline at end of file +} diff --git a/src/getGateway.ts b/src/getGateway.ts new file mode 100644 index 0000000..90cf338 --- /dev/null +++ b/src/getGateway.ts @@ -0,0 +1,27 @@ +import { isDefined } from '@xylabs/typeof' +import type { SimpleXyoSigner, XyoConnection } from '@xyo-network/xl1-sdk' +import { + SimpleXyoGatewayRunner, XyoConnectionMoniker, XyoSignerMoniker, +} from '@xyo-network/xl1-sdk' + +import { getLocator } from './getLocator.ts' + +let gateway: SimpleXyoGatewayRunner | undefined + +export const getGateway = async () => { + // If existing gateway, return it + if (isDefined(gateway)) return gateway + + // Otherwise, build a new gateway + + // Get locator + const locator = await getLocator() + + // Use locator to get connection and signer + const connection = await locator.getInstance(XyoConnectionMoniker) + const signer = await locator.getInstance(XyoSignerMoniker) + + // Create gateway from connection and signer + gateway = new SimpleXyoGatewayRunner(connection, signer) + return gateway +} diff --git a/src/getLocator.ts b/src/getLocator.ts new file mode 100644 index 0000000..42943f4 --- /dev/null +++ b/src/getLocator.ts @@ -0,0 +1,24 @@ +import { isDefined } from '@xylabs/typeof' +import type { ProviderFactoryLocator } from '@xyo-network/xl1-sdk' +import { buildJsonRpcProviderLocator, SimpleXyoSigner } from '@xyo-network/xl1-sdk' + +import { getSignerAccount } from './getSignerAccount.ts' +import { getTransportFactory } from './getTransportFactory.ts' + +let locator: ProviderFactoryLocator + +export const getLocator = async () => { + // If existing locator, return it + if (isDefined(locator)) return locator + + // Build a new locator + const transportFactory = getTransportFactory() + locator = await buildJsonRpcProviderLocator({ transportFactory }) + + // Register the signer with the locator + const account = await getSignerAccount() + locator.register(SimpleXyoSigner.factory(SimpleXyoSigner.dependencies, { account })) + + // Return the locator + return locator +} diff --git a/src/getRandomTransactionData.ts b/src/getRandomTransactionData.ts new file mode 100644 index 0000000..7890277 --- /dev/null +++ b/src/getRandomTransactionData.ts @@ -0,0 +1,22 @@ +import { PayloadBuilder } from '@xyo-network/payload-builder' +import type { Payload } from '@xyo-network/payload-model' +import type { HashPayload } from '@xyo-network/xl1-sdk' + +/** + * Generates random data for a transaction. + * @returns An object containing off-chain and on-chain data for the transaction. + */ +export const getRandomTransactionData = async () => { + // Data to store off-chain + const salt = `Hello from Sample - ${new Date().toISOString()}` + const idPayload: Payload<{ salt: string }> = { schema: 'network.xyo.id', salt } + + // Data to store on-chain (can reference the off-chain data) + const hash = await PayloadBuilder.hash(idPayload) + const hashPayload: HashPayload = { schema: 'network.xyo.hash', hash } + + return { + offChainData: [idPayload], + onChainData: [hashPayload], + } +} diff --git a/src/getRpcUrl.ts b/src/getRpcUrl.ts new file mode 100644 index 0000000..3d41d3d --- /dev/null +++ b/src/getRpcUrl.ts @@ -0,0 +1,13 @@ +import { config } from 'dotenv' + +// Load environment variables from .env file +config({ quiet: true }) + +// Parse the relevant ENV VARs or use defaults +const rpcUrl = process.env.XYO_CHAIN_RPC_URL ?? 'http://localhost:8080/rpc' + +/** + * Gets the rpcUrl to use for interacting with the chain + * @returns The rpcUrl to use for interacting with the chain + */ +export const getRpcUrl = (): string => rpcUrl diff --git a/src/getSignerAccount.ts b/src/getSignerAccount.ts new file mode 100644 index 0000000..e9ca7ba --- /dev/null +++ b/src/getSignerAccount.ts @@ -0,0 +1,23 @@ +import { isDefined } from '@xylabs/typeof' +import type { AccountInstance } from '@xyo-network/account-model' +import { ADDRESS_INDEX, generateXyoBaseWalletFromPhrase } from '@xyo-network/xl1-sdk' + +import { getWalletMnemonic } from './getWalletMnemonic.ts' + +let signerAccount: AccountInstance | undefined + +/** + * Retrieves the signer account derived from the configured mnemonic. + * @returns The derived account + */ +export const getSignerAccount = async () => { + // If existing signer account, return it + if (isDefined (signerAccount)) return signerAccount + + // Create new signer account + const walletMnemonic = getWalletMnemonic() + const wallet = await generateXyoBaseWalletFromPhrase(walletMnemonic) + signerAccount = await wallet.derivePath(ADDRESS_INDEX.XYO) + console.log('Using signer account:', signerAccount.address) + return signerAccount +} diff --git a/src/getTransportFactory.ts b/src/getTransportFactory.ts new file mode 100644 index 0000000..10e3fe0 --- /dev/null +++ b/src/getTransportFactory.ts @@ -0,0 +1,22 @@ +import { isDefined } from '@xylabs/typeof' +import type { RpcSchemaMap, TransportFactory } from '@xyo-network/xl1-sdk' +import { HttpRpcTransport } from '@xyo-network/xl1-sdk' + +import { getRpcUrl } from './getRpcUrl.ts' + +let transportFactory: TransportFactory | undefined + +/** + * Retrieves a transport factory for the configured RPC URL. + * @returns A transport factory for the configured RPC URL + */ +export const getTransportFactory = () => { + // If existing transport factory, return it + if (isDefined(transportFactory)) return transportFactory + + // Build a new transport factory + const rpcUrl = getRpcUrl() + console.log('Using rpcUrl:', rpcUrl) + transportFactory = (schemas: RpcSchemaMap) => new HttpRpcTransport(rpcUrl, schemas) + return transportFactory +} diff --git a/src/getWalletMnemonic.ts b/src/getWalletMnemonic.ts new file mode 100644 index 0000000..fbf1530 --- /dev/null +++ b/src/getWalletMnemonic.ts @@ -0,0 +1,14 @@ +import { HDWallet } from '@xyo-network/wallet' +import { config } from 'dotenv' + +// Load environment variables from .env file +config({ quiet: true }) + +// Parse the relevant ENV VARs or use defaults +const mnemonic = process.env.XYO_WALLET_MNEMONIC ?? HDWallet.generateMnemonic() + +/** + * Gets the mnemonic to use for the wallet + * @returns The mnemonic to use for the wallet + */ +export const getWalletMnemonic = (): string => mnemonic diff --git a/src/helloWorld.ts b/src/helloWorld.ts index ec0897f..158d3dc 100644 --- a/src/helloWorld.ts +++ b/src/helloWorld.ts @@ -1,42 +1,25 @@ -import { assertEx } from '@xylabs/assert' import { isError } from '@xylabs/typeof' -import { PayloadBuilder } from '@xyo-network/payload-builder' -import type { Payload } from '@xyo-network/payload-model' -import type { HashPayload, SignedHydratedTransaction } from '@xyo-network/xl1-protocol' -import { - ADDRESS_INDEX, generateXyoBaseWalletFromPhrase, - SimpleXyoGatewayRunner, - SimpleXyoSigner, -} from '@xyo-network/xl1-protocol-sdk' -import { HttpRpcXyoConnection } from '@xyo-network/xl1-rpc' -import { config } from 'dotenv' +import type { SignedHydratedTransaction } from '@xyo-network/xl1-protocol' -// Load environment variables from .env file -config({ quiet: true }) +import { getGateway } from './getGateway.ts' +import { getRandomTransactionData } from './getRandomTransactionData.ts' +// Log to console const logger = console -export async function helloWorld(mnemonic?: string, rpcEndpoint = 'http://localhost:8080/rpc'): Promise { +/** + * Runs a simple "Hello World" transaction on the XL1 network. + */ +export async function helloWorld(): Promise { try { console.log('\n**** Starting XL1 Hello World NodeJs Sample ****\n') - // Load the account to use for the transaction - const walletMnemonic = assertEx(process.env.XYO_WALLET_MNEMONIC ?? mnemonic, () => 'Unable to resolve mnemonic from environment variable or argument') - const account = await (await generateXyoBaseWalletFromPhrase(walletMnemonic)).derivePath(ADDRESS_INDEX.XYO) - console.log('Using account:', account.address) - - // Determine the RPC endpoint to use for the chain connection - const endpoint = process.env.XYO_CHAIN_RPC_URL ?? rpcEndpoint - console.log('Using endpoint:', endpoint) - - // Create a new RPC connection - const connection = new HttpRpcXyoConnection({ endpoint }) - const signer = new SimpleXyoSigner(account) - const gateway = new SimpleXyoGatewayRunner(connection, signer) - // Generate random data to send in the transaction const { onChainData, offChainData } = await getRandomTransactionData() + // Get gateway to interact with the chain + const gateway = await getGateway() + // Send the transaction to the network const [txHash] = await gateway.addPayloadsToChain(onChainData, offChainData) @@ -54,22 +37,3 @@ const logSuccess = (_tx: SignedHydratedTransaction) => { console.log('1. Install the XYO Layer One Wallet from https://chromewebstore.google.com/detail/xl1-wallet/fblbagcjeigmhakkfgjpdlcapcgmcfbm') console.log('2. In that same browser, go to: https://explore.xyo.network/xl1/local/') } - -/** - * Generates random data for a transaction. - * @returns An object containing off-chain and on-chain data for the transaction. - */ -const getRandomTransactionData = async () => { - // Data to store off-chain - const salt = `Hello from Sample - ${new Date().toISOString()}` - const idPayload: Payload<{ salt: string }> = { schema: 'network.xyo.id', salt } - - // Data to store on-chain (can reference the off-chain data) - const hash = await PayloadBuilder.hash(idPayload) - const hashPayload: HashPayload = { schema: 'network.xyo.hash', hash } - - return { - offChainData: [idPayload], - onChainData: [hashPayload], - } -} diff --git a/src/helloWorldRunner.ts b/src/helloWorldRunner.ts index 69a4b4b..73877fb 100644 --- a/src/helloWorldRunner.ts +++ b/src/helloWorldRunner.ts @@ -1,11 +1,17 @@ import { type ChildProcess, spawn } from 'node:child_process' -import { HDWallet } from '@xyo-network/wallet' -import { ADDRESS_INDEX, generateXyoBaseWalletFromPhrase } from '@xyo-network/xl1-protocol-sdk' +import type { XyoViewer } from '@xyo-network/xl1-protocol-sdk' +import { XyoViewerMoniker } from '@xyo-network/xl1-protocol-sdk' +import { getLocator } from './getLocator.ts' +import { getSignerAccount } from './getSignerAccount.ts' +import { getWalletMnemonic } from './getWalletMnemonic.ts' import { helloWorld } from './helloWorld.js' import { waitForInitialBlocks } from './waitForInitialBlocks.js' +// Parse the relevant ENV VARs or use defaults +const mnemonic = getWalletMnemonic() + /** * Starts the XL1 node using command in a child process * The child process will be terminated when the parent process exits @@ -14,8 +20,6 @@ import { waitForInitialBlocks } from './waitForInitialBlocks.js' async function startXl1(): Promise { console.log('Starting XL1...') - const mnemonic = process.env.XYO_WALLET_MNEMONIC ?? HDWallet.generateMnemonic() - // Track the child process let xl1Process: ChildProcess | null = null @@ -45,12 +49,10 @@ async function startXl1(): Promise { }) try { - // log out the mnemonic and wallet address using same steps as producer - const wallet = await generateXyoBaseWalletFromPhrase(mnemonic) - const account = await wallet.derivePath(ADDRESS_INDEX.XYO) - - console.log('Generated mnemonic:', mnemonic) - console.log('Producer Wallet address:', account.address) + // Log out the mnemonic and signer address in case random was generated + const account = await getSignerAccount() + console.log('Using signer mnemonic:', mnemonic) + console.log('Using producer address:', account.address) // Spawn the XL1 process xl1Process = spawn('node', ['./node_modules/@xyo-network/xl1-cli/scripts/xl1.mjs', '--logLevel="warn"', '--producer.mnemonic', JSON.stringify(mnemonic)], { @@ -83,7 +85,12 @@ async function startXl1(): Promise { throw error }) - await waitForInitialBlocks() + // Get the XyoViewer instance + const locator = await getLocator() + const viewer = await locator.getInstance(XyoViewerMoniker) + + // Wait for the initial blocks to be created + await waitForInitialBlocks(viewer) return mnemonic } catch (error) { @@ -92,10 +99,8 @@ async function startXl1(): Promise { } } -let mnemonic: string - try { - mnemonic = await startXl1() + await startXl1() } catch (ex) { console.error('Failed to start XL1:', ex) // eslint-disable-next-line unicorn/no-process-exit @@ -105,7 +110,7 @@ try { console.log('XL1 is ready, starting sample...') try { - await helloWorld(mnemonic) + await helloWorld() } catch (error) { console.error('Error importing application:', error) } diff --git a/src/waitForInitialBlocks.ts b/src/waitForInitialBlocks.ts index 97119ec..40b7819 100644 --- a/src/waitForInitialBlocks.ts +++ b/src/waitForInitialBlocks.ts @@ -1,12 +1,10 @@ import { assertEx } from '@xylabs/assert' import { delay } from '@xylabs/delay' import { isDefined } from '@xylabs/typeof' -import { HttpRpcXyoConnection } from '@xyo-network/xl1-rpc' - -export const waitForInitialBlocks = async (maxAttempts = 10): Promise => { - const connection = new HttpRpcXyoConnection({ endpoint: 'http://localhost:8080/rpc' }) - const viewer = assertEx(connection.viewer, () => 'Connection viewer is undefined') +import type { XyoViewer } from '@xyo-network/xl1-sdk' +export const waitForInitialBlocks = async (viewer: XyoViewer, maxAttempts = 10): Promise => { + assertEx(viewer, () => 'Connection viewer is undefined') console.log('\n⏳ Waiting for genesis block creation...') let attempts = 0 while (attempts < maxAttempts) {