Breaking Change: Update to latest XO-Engine #2
48
package-lock.json
generated
48
package-lock.json
generated
@@ -17,6 +17,7 @@
|
|||||||
"@xo-cash/state": "file:../state",
|
"@xo-cash/state": "file:../state",
|
||||||
"@xo-cash/templates": "file:../templates",
|
"@xo-cash/templates": "file:../templates",
|
||||||
"@xo-cash/types": "^0.0.1",
|
"@xo-cash/types": "^0.0.1",
|
||||||
|
"@xo-cash/utils": "file:../utils",
|
||||||
"better-sqlite3": "^12.6.2",
|
"better-sqlite3": "^12.6.2",
|
||||||
"clipboardy": "^5.1.0",
|
"clipboardy": "^5.1.0",
|
||||||
"ink": "^6.6.0",
|
"ink": "^6.6.0",
|
||||||
@@ -47,16 +48,16 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@bitauth/libauth": "^3.1.0-next.8",
|
"@bitauth/libauth": "^3.1.0-next.8",
|
||||||
"@electrum-cash/application": "^0.2.3-development.13424909069",
|
"@electrum-cash/application": "^0.2.3-development.13447192992",
|
||||||
"@electrum-cash/network": "^4.2.2",
|
"@electrum-cash/network": "^4.2.2",
|
||||||
"@electrum-cash/protocol": "^2.3.1",
|
"@electrum-cash/protocol": "^2.3.1",
|
||||||
"@electrum-cash/servers": "^3.1.0",
|
"@electrum-cash/servers": "^3.1.0",
|
||||||
"@xo-cash/crypto": "^0.0.1",
|
"@xo-cash/crypto": "0.0.1",
|
||||||
"@xo-cash/primitives": "0.0.1",
|
"@xo-cash/primitives": "file:../primitives",
|
||||||
"@xo-cash/state": "0.0.2",
|
"@xo-cash/state": "file:../state",
|
||||||
"@xo-cash/templates": "0.0.1",
|
"@xo-cash/templates": "0.0.1",
|
||||||
"@xo-cash/types": "0.0.1",
|
"@xo-cash/types": "^0.0.1-development.14519184304",
|
||||||
"@xo-cash/utils": "0.0.1",
|
"@xo-cash/utils": "^0.0.1-development.14519184505",
|
||||||
"eventemitter3": "^5.0.1"
|
"eventemitter3": "^5.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -140,6 +141,37 @@
|
|||||||
"vitest": "^4.0.17"
|
"vitest": "^4.0.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"../utils": {
|
||||||
|
"name": "@xo-cash/utils",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@bitauth/libauth": "^3.1.0-next.8",
|
||||||
|
"@xo-cash/types": "0.0.1",
|
||||||
|
"zod": "^4.3.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@chalp/eslint-airbnb": "^1.3.0",
|
||||||
|
"@generalprotocols/cspell-dictionary": "^1.0.1",
|
||||||
|
"@stylistic/eslint-plugin": "^5.7.0",
|
||||||
|
"@types/node": "^25.5.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
||||||
|
"@typescript-eslint/parser": "^8.53.1",
|
||||||
|
"@vitest/coverage-v8": "^4.0.17",
|
||||||
|
"@viz-kit/esbuild-analyzer": "^1.0.0",
|
||||||
|
"@xo-cash/eslint-config": "1.0.1",
|
||||||
|
"@xo-cash/templates": "0.0.1",
|
||||||
|
"cspell": "^9.6.0",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
|
"tsdown": "^0.20.0-beta.4",
|
||||||
|
"typedoc": "^0.28.16",
|
||||||
|
"typedoc-plugin-coverage": "^4.0.2",
|
||||||
|
"typescript": "^5.3.2",
|
||||||
|
"typescript-eslint": "^8.53.1",
|
||||||
|
"vitest": "^4.0.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@alcalzone/ansi-tokenize": {
|
"node_modules/@alcalzone/ansi-tokenize": {
|
||||||
"version": "0.2.4",
|
"version": "0.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/@alcalzone/ansi-tokenize/-/ansi-tokenize-0.2.4.tgz",
|
||||||
@@ -977,6 +1009,10 @@
|
|||||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@xo-cash/utils": {
|
||||||
|
"resolved": "../utils",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/ansi-escapes": {
|
"node_modules/ansi-escapes": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.2.0.tgz",
|
||||||
|
|||||||
@@ -41,6 +41,7 @@
|
|||||||
"@xo-cash/state": "file:../state",
|
"@xo-cash/state": "file:../state",
|
||||||
"@xo-cash/templates": "file:../templates",
|
"@xo-cash/templates": "file:../templates",
|
||||||
"@xo-cash/types": "^0.0.1",
|
"@xo-cash/types": "^0.0.1",
|
||||||
|
"@xo-cash/utils": "file:../utils",
|
||||||
"better-sqlite3": "^12.6.2",
|
"better-sqlite3": "^12.6.2",
|
||||||
"clipboardy": "^5.1.0",
|
"clipboardy": "^5.1.0",
|
||||||
"ink": "^6.6.0",
|
"ink": "^6.6.0",
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ import { EventEmitter } from "../utils/event-emitter.js";
|
|||||||
|
|
||||||
// TODO: Remove this. Exists to hash the seed for database namespace.
|
// TODO: Remove this. Exists to hash the seed for database namespace.
|
||||||
import { createHash } from "crypto";
|
import { createHash } from "crypto";
|
||||||
import { p2pkhTemplate } from "@xo-cash/templates";
|
|
||||||
import { hexToBin } from "@bitauth/libauth";
|
import { hexToBin } from "@bitauth/libauth";
|
||||||
import { parseTemplate } from "@xo-cash/engine";
|
import { parseTemplate } from "@xo-cash/engine";
|
||||||
|
|
||||||
|
import { p2pkhTemplate } from "@xo-cash/templates";
|
||||||
|
import { vendingMachineTemplate } from "../templates/vending-machine.js";
|
||||||
|
import { wrapBCHTemplate } from "../templates/wrap-template.js";
|
||||||
|
|
||||||
export type AppEventMap = {
|
export type AppEventMap = {
|
||||||
"invitation-added": Invitation;
|
"invitation-added": Invitation;
|
||||||
"invitation-removed": Invitation;
|
"invitation-removed": Invitation;
|
||||||
@@ -53,6 +56,12 @@ export class AppService extends EventEmitter<AppEventMap> {
|
|||||||
public settings: SettingsService;
|
public settings: SettingsService;
|
||||||
|
|
||||||
public invitations: Invitation[] = [];
|
public invitations: Invitation[] = [];
|
||||||
|
/**
|
||||||
|
* Incremented whenever the invitation list or any invitation's data/status changes.
|
||||||
|
* Used by TUI hooks so useSyncExternalStore snapshots change on in-place mutations.
|
||||||
|
*/
|
||||||
|
public invitationsRevision = 0;
|
||||||
|
private invitationRevisions = new Map<string, number>();
|
||||||
private invitationEventCleanup = new Map<
|
private invitationEventCleanup = new Map<
|
||||||
string,
|
string,
|
||||||
{
|
{
|
||||||
@@ -82,7 +91,9 @@ export class AppService extends EventEmitter<AppEventMap> {
|
|||||||
// TODO: We *technically* dont want this here, but we also need some initial templates for the wallet, so im doing it here
|
// TODO: We *technically* dont want this here, but we also need some initial templates for the wallet, so im doing it here
|
||||||
// Import the default P2PKH template
|
// Import the default P2PKH template
|
||||||
await engine.importTemplate(p2pkhTemplate);
|
await engine.importTemplate(p2pkhTemplate);
|
||||||
|
await engine.importTemplate(vendingMachineTemplate);
|
||||||
|
await engine.importTemplate(wrapBCHTemplate);
|
||||||
|
|
||||||
// Update all the unspents for every template, and subscribe to the locking bytecodes for changes
|
// Update all the unspents for every template, and subscribe to the locking bytecodes for changes
|
||||||
// TODO: Remove the above lines that do the same thing. Minimising changes for BLISS.
|
// TODO: Remove the above lines that do the same thing. Minimising changes for BLISS.
|
||||||
const updateTemplates = async () => {
|
const updateTemplates = async () => {
|
||||||
@@ -160,8 +171,9 @@ export class AppService extends EventEmitter<AppEventMap> {
|
|||||||
// Create the invitation
|
// Create the invitation
|
||||||
const invitationInstance = await Invitation.create(invitation, deps);
|
const invitationInstance = await Invitation.create(invitation, deps);
|
||||||
|
|
||||||
// Add the invitation to the invitations array
|
// Attach listeners before SSE connects so updates are not missed.
|
||||||
await this.addInvitation(invitationInstance);
|
await this.addInvitation(invitationInstance);
|
||||||
|
await invitationInstance.start();
|
||||||
|
|
||||||
return invitationInstance;
|
return invitationInstance;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { compileCashAssemblyString, type Engine } from "@xo-cash/engine";
|
|||||||
import type { ScriptHashData, State, UnspentOutputData } from "@xo-cash/state";
|
import type { ScriptHashData, State, UnspentOutputData } from "@xo-cash/state";
|
||||||
import type {
|
import type {
|
||||||
XOInvitation,
|
XOInvitation,
|
||||||
XOInvitationCommit,
|
|
||||||
XOInvitationInput,
|
XOInvitationInput,
|
||||||
XOInvitationOutput,
|
XOInvitationOutput,
|
||||||
XOInvitationVariableValue,
|
XOInvitationVariableValue,
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import type {
|
import type {
|
||||||
AcceptInvitationParameters,
|
InvitationParameters,
|
||||||
AppendInvitationParameters,
|
|
||||||
Engine,
|
Engine,
|
||||||
GetSpendableResourcesParameters,
|
GetSpendableResourcesParameters,
|
||||||
} from "@xo-cash/engine";
|
} from "@xo-cash/engine";
|
||||||
import { generateTemplateIdentifier, hasInvitationExpired, mergeInvitationCommits } from "@xo-cash/engine";
|
import { generateTemplateIdentifier, hasInvitationExpired, mergeInvitationCommits, serializeInvitation } from "@xo-cash/engine";
|
||||||
import type {
|
import type {
|
||||||
XOInvitation,
|
XOInvitation,
|
||||||
XOInvitationCommit,
|
XOInvitationCommit,
|
||||||
@@ -15,10 +14,6 @@ import type {
|
|||||||
} from "@xo-cash/types";
|
} from "@xo-cash/types";
|
||||||
import type { UnspentOutputData } from "@xo-cash/state";
|
import type { UnspentOutputData } from "@xo-cash/state";
|
||||||
import {
|
import {
|
||||||
bigIntToBinUint64LE,
|
|
||||||
bigIntToBinUintBE,
|
|
||||||
bigIntToBinUintLE,
|
|
||||||
bigIntToVmNumber,
|
|
||||||
binToHex,
|
binToHex,
|
||||||
encodeTransaction,
|
encodeTransaction,
|
||||||
generateTransaction,
|
generateTransaction,
|
||||||
@@ -90,13 +85,13 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// engine invitation (I have no idea if this is required)
|
// engine invitation (I have no idea if this is required)
|
||||||
const engineInvitation = await dependencies.engine.acceptInvitation(invitation);
|
const engineInvitation = await dependencies.engine.importInvitation(serializeInvitation(invitation));
|
||||||
|
|
||||||
// Create the invitation
|
// Create the invitation
|
||||||
const invitationInstance = new Invitation(engineInvitation, dependencies);
|
const invitationInstance = new Invitation(engineInvitation, dependencies);
|
||||||
|
|
||||||
// Start the invitation and its tracking
|
// Start the invitation and its tracking
|
||||||
await invitationInstance.start();
|
invitationInstance.start();
|
||||||
|
|
||||||
return invitationInstance;
|
return invitationInstance;
|
||||||
}
|
}
|
||||||
@@ -387,7 +382,7 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
|
|||||||
/**
|
/**
|
||||||
* Accept the invitation
|
* Accept the invitation
|
||||||
*/
|
*/
|
||||||
async accept(acceptParams?: AcceptInvitationParameters): Promise<void> {
|
async accept(acceptParams?: InvitationParameters): Promise<void> {
|
||||||
// Accept the invitation
|
// Accept the invitation
|
||||||
this.data = await this.engine.acceptInvitation(this.data, acceptParams);
|
this.data = await this.engine.acceptInvitation(this.data, acceptParams);
|
||||||
|
|
||||||
@@ -438,7 +433,13 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
|
|||||||
/**
|
/**
|
||||||
* Append a commit to the invitation
|
* Append a commit to the invitation
|
||||||
*/
|
*/
|
||||||
async append(data: AppendInvitationParameters): Promise<void> {
|
async append(data: InvitationParameters): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.engine.acceptInvitation(this.data);
|
||||||
|
} catch (err) {
|
||||||
|
// Literally do nothing here. We are just trying to accept the invitation in case we haven't already
|
||||||
|
}
|
||||||
|
|
||||||
// Append the commit to the invitation
|
// Append the commit to the invitation
|
||||||
this.data = await this.engine.appendInvitation(this.data.invitationIdentifier, data);
|
this.data = await this.engine.appendInvitation(this.data.invitationIdentifier, data);
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { OracleClient } from '@generalprotocols/oracle-client';
|
||||||
import { EventEmitter } from '../utils/event-emitter.js';
|
import { EventEmitter } from '../utils/event-emitter.js';
|
||||||
import {
|
import {
|
||||||
type RatesEventMap,
|
type RatesEventMap,
|
||||||
@@ -73,8 +74,17 @@ export class RatesService extends EventEmitter<RatesServiceEventMap> {
|
|||||||
settings: SettingsService,
|
settings: SettingsService,
|
||||||
adapter?: RatesAdapter,
|
adapter?: RatesAdapter,
|
||||||
): Promise<RatesService> {
|
): Promise<RatesService> {
|
||||||
const resolvedAdapter = adapter ?? (await RatesOracle.from(undefined, settings));
|
if (adapter) {
|
||||||
return new RatesService(resolvedAdapter, settings);
|
return new RatesService(adapter, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
const oracleClient = new OracleClient();
|
||||||
|
oracleClient.start();
|
||||||
|
|
||||||
|
const ratesOracle = new RatesOracle(oracleClient, settings);
|
||||||
|
ratesOracle.start();
|
||||||
|
|
||||||
|
return new RatesService(ratesOracle, settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
277
src/templates/vending-machine.ts
Normal file
277
src/templates/vending-machine.ts
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
import type { XOTemplate } from '@xo-cash/types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vending machine payment template.
|
||||||
|
*
|
||||||
|
* Merchant creates a purchaseItems invitation with receipt variables;
|
||||||
|
* customer funds and signs the composable transaction.
|
||||||
|
*/
|
||||||
|
export const vendingMachineTemplate: XOTemplate = {
|
||||||
|
$schema: 'https://libauth.org/schemas/wallet-template-v0.schema.json',
|
||||||
|
name: 'Vending Machine',
|
||||||
|
description: 'Purchase items from a vending machine with an itemized receipt.',
|
||||||
|
icon: 'wallet',
|
||||||
|
version: '1',
|
||||||
|
supported: ['BCH_2023_05', 'BCH_2024_05', 'BCH_2025_05', 'BCH_2026_05'],
|
||||||
|
|
||||||
|
defaults: {
|
||||||
|
change: {
|
||||||
|
output: 'changeOutput',
|
||||||
|
role: 'merchant',
|
||||||
|
generate: ['merchantKey'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
roles: {
|
||||||
|
merchant: {
|
||||||
|
name: 'Merchant',
|
||||||
|
description: 'The vending machine operator receiving payment.',
|
||||||
|
icon: 'owner',
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
name: 'Customer',
|
||||||
|
description: 'The customer paying for items.',
|
||||||
|
icon: 'sender',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
start: [
|
||||||
|
{
|
||||||
|
action: 'purchaseItems',
|
||||||
|
role: 'merchant',
|
||||||
|
generate: ['merchantKey'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
purchaseItems: {
|
||||||
|
name: 'Purchase Items',
|
||||||
|
description: 'Purchase: $(<receiptSummary>) for $(<totalSatoshis>) sats',
|
||||||
|
icon: 'request',
|
||||||
|
|
||||||
|
roles: {
|
||||||
|
merchant: {
|
||||||
|
name: 'Sell Items',
|
||||||
|
description: 'Receive payment for $(<receiptSummary>)',
|
||||||
|
icon: 'request',
|
||||||
|
requirements: {
|
||||||
|
secrets: ['merchantKey'],
|
||||||
|
variables: [
|
||||||
|
'totalSatoshis',
|
||||||
|
'orderId',
|
||||||
|
'merchantName',
|
||||||
|
'receiptSummary',
|
||||||
|
'lineItemsJson',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
name: 'Pay',
|
||||||
|
description: 'Pay $(<totalSatoshis>) sats for $(<receiptSummary>)',
|
||||||
|
icon: 'send',
|
||||||
|
requirements: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
requirements: {
|
||||||
|
participants: [
|
||||||
|
{ role: 'merchant', slots: { min: 1, max: 1 } },
|
||||||
|
{ role: 'customer', slots: { min: 1 } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
transaction: 'purchaseItemsTransaction',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
transactions: {
|
||||||
|
purchaseItemsTransaction: {
|
||||||
|
name: 'Vending Purchase',
|
||||||
|
description: 'Order $(<orderId>): $(<receiptSummary>)',
|
||||||
|
icon: 'request',
|
||||||
|
|
||||||
|
roles: {
|
||||||
|
merchant: {
|
||||||
|
name: 'Received Payment',
|
||||||
|
description: 'Received $(<totalSatoshis>) sats from $(<merchantName>) sale',
|
||||||
|
icon: 'receive',
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
name: 'Sent Payment',
|
||||||
|
description: 'Paid $(<totalSatoshis>) sats for $(<receiptSummary>)',
|
||||||
|
icon: 'send',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
inputs: [],
|
||||||
|
outputs: [{ output: 'purchaseOutput' }],
|
||||||
|
version: 2,
|
||||||
|
locktime: 0,
|
||||||
|
composable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
/** No custom input templates — customer UTXOs are selected at funding time. */
|
||||||
|
inputs: {},
|
||||||
|
|
||||||
|
outputs: {
|
||||||
|
changeOutput: {
|
||||||
|
name: 'Change',
|
||||||
|
description: 'Funds returned as change.',
|
||||||
|
icon: 'receive',
|
||||||
|
lockingScript: 'merchantReceivingLockingScript',
|
||||||
|
},
|
||||||
|
purchaseOutput: {
|
||||||
|
name: 'Purchase Payment',
|
||||||
|
description: '$(<totalSatoshis>) sats to $(<merchantName>)',
|
||||||
|
icon: 'request',
|
||||||
|
|
||||||
|
roles: {
|
||||||
|
merchant: {
|
||||||
|
name: 'Payment Received',
|
||||||
|
description: 'Received $(<totalSatoshis>) sats for $(<receiptSummary>)',
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
name: 'Payment Sent',
|
||||||
|
description: 'Sent $(<totalSatoshis>) sats for $(<receiptSummary>)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
lockingScript: 'merchantReceivingLockingScript',
|
||||||
|
valueSatoshis: '$(<totalSatoshis>)',
|
||||||
|
token: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
lockingScripts: {
|
||||||
|
merchantReceivingLockingScript: {
|
||||||
|
name: 'Merchant Receive',
|
||||||
|
description: 'Funds received by the vending machine merchant.',
|
||||||
|
icon: 'address',
|
||||||
|
lockingType: 'p2pkh',
|
||||||
|
lockingBytecode: 'lockMerchantP2PKH',
|
||||||
|
unlockingBytecode: 'unlockMerchantP2PKH',
|
||||||
|
actions: [],
|
||||||
|
state: { variables: [], secrets: [] },
|
||||||
|
balance: {},
|
||||||
|
roles: {
|
||||||
|
merchant: {
|
||||||
|
state: {
|
||||||
|
variables: [],
|
||||||
|
secrets: ['merchantKey'],
|
||||||
|
},
|
||||||
|
actions: [],
|
||||||
|
balance: {
|
||||||
|
satoshis: true,
|
||||||
|
fungibleTokens: true,
|
||||||
|
nonfungibleTokens: true,
|
||||||
|
},
|
||||||
|
selectable: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
scripts: {
|
||||||
|
lockMerchantP2PKH:
|
||||||
|
'OP_DUP OP_HASH160 <$(<merchantKey.public_key> OP_HASH160)> OP_EQUALVERIFY OP_CHECKSIG',
|
||||||
|
unlockMerchantP2PKH:
|
||||||
|
'<merchantKey.schnorr_signature.all_outputs> <merchantKey.public_key>',
|
||||||
|
},
|
||||||
|
|
||||||
|
constants: {
|
||||||
|
dustLimit: {
|
||||||
|
name: 'Dust Limit',
|
||||||
|
description: 'Minimum satoshis for P2PKH outputs.',
|
||||||
|
type: 'integer',
|
||||||
|
value: 546,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
variables: {
|
||||||
|
merchantKey: {
|
||||||
|
name: 'Merchant Private Key',
|
||||||
|
description: 'Private key for the vending machine merchant wallet.',
|
||||||
|
type: 'bytes',
|
||||||
|
hint: 'private_key',
|
||||||
|
},
|
||||||
|
totalSatoshis: {
|
||||||
|
name: 'Total Price',
|
||||||
|
description: 'Total purchase price in satoshis',
|
||||||
|
type: 'integer',
|
||||||
|
hint: 'satoshis',
|
||||||
|
},
|
||||||
|
orderId: {
|
||||||
|
name: 'Order ID',
|
||||||
|
description: 'Unique order identifier',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
merchantName: {
|
||||||
|
name: 'Merchant Name',
|
||||||
|
description: 'Display name of the vending machine',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
receiptSummary: {
|
||||||
|
name: 'Receipt Summary',
|
||||||
|
description: 'Human-readable list of purchased items',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
lineItemsJson: {
|
||||||
|
name: 'Line Items',
|
||||||
|
description: 'JSON-encoded line items for the purchase',
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
icons: [
|
||||||
|
{ name: 'wallet', hash: '0000000000000000000000' },
|
||||||
|
{ name: 'owner', hash: '0000000000000000000000' },
|
||||||
|
{ name: 'sender', hash: '0000000000000000000000' },
|
||||||
|
{ name: 'request', hash: '0000000000000000000000' },
|
||||||
|
{ name: 'receive', hash: '0000000000000000000000' },
|
||||||
|
{ name: 'send', hash: '0000000000000000000000' },
|
||||||
|
],
|
||||||
|
|
||||||
|
scenarios: [
|
||||||
|
{
|
||||||
|
name: 'purchase items happy path',
|
||||||
|
description: 'Merchant requests payment for vending machine items.',
|
||||||
|
action: 'purchaseItems',
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
role: 'merchant',
|
||||||
|
values: {
|
||||||
|
generated: {
|
||||||
|
merchantKey: 'KyRQa5pEXuzVcDwnXRLpYAascjchQW5DoxVRMbj4DTxS83573mz8',
|
||||||
|
},
|
||||||
|
variables: {
|
||||||
|
totalSatoshis: 3500,
|
||||||
|
orderId: 'order-demo-1',
|
||||||
|
merchantName: 'XO Snack Machine',
|
||||||
|
receiptSummary: '2× Cola, 1× Chips',
|
||||||
|
lineItemsJson: '[{"name":"Cola","qty":2},{"name":"Chips","qty":1}]',
|
||||||
|
},
|
||||||
|
secrets: {},
|
||||||
|
inputs: [],
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
lockingBytecode: '76a91475c715ecb74178fe87933e57e947e5e92d904b8188ac',
|
||||||
|
valueSatoshis: 3500,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'customer',
|
||||||
|
values: {
|
||||||
|
generated: {},
|
||||||
|
variables: {},
|
||||||
|
secrets: {},
|
||||||
|
inputs: [],
|
||||||
|
outputs: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -71,7 +71,7 @@ export function AppProvider({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Start the AppService (loads existing invitations)
|
// Start the AppService (loads existing invitations)
|
||||||
await service.start();
|
service.start();
|
||||||
|
|
||||||
// Set the service and mark as initialized
|
// Set the service and mark as initialized
|
||||||
setAppService(service);
|
setAppService(service);
|
||||||
|
|||||||
@@ -158,9 +158,7 @@ export function SeedInputScreen(): React.ReactElement {
|
|||||||
setSeedPhrase('');
|
setSeedPhrase('');
|
||||||
setSaveMnemonicChecked(false);
|
setSaveMnemonicChecked(false);
|
||||||
|
|
||||||
setTimeout(() => {
|
navigate('wallet');
|
||||||
navigate('wallet');
|
|
||||||
}, 500);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
error instanceof Error ? error.message : 'Failed to initialize wallet';
|
error instanceof Error ? error.message : 'Failed to initialize wallet';
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export class RatesOracle extends BaseRates<RatesOracleEventMap> {
|
|||||||
private targetDenominatorUnitCode: string = 'BCH';
|
private targetDenominatorUnitCode: string = 'BCH';
|
||||||
private unsubscribeFromSettings: OffCallback | null = null;
|
private unsubscribeFromSettings: OffCallback | null = null;
|
||||||
|
|
||||||
private constructor(client: OracleClient, settings: SettingsService) {
|
public constructor(client: OracleClient, settings: SettingsService) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { XOInvitation } from "@xo-cash/types";
|
import type { XOInvitation } from "@xo-cash/types";
|
||||||
import { EventEmitter } from "./event-emitter.js";
|
import { EventEmitter } from "./event-emitter.js";
|
||||||
import { SSESession, type SSEvent } from "./sse-client.js";
|
// import { SSESession, type SSEvent } from "./sse-client.js";
|
||||||
|
import { SSESession, type SSEvent } from "@xo-cash/utils";
|
||||||
import { deserializeInvitation, serializeInvitation } from "@xo-cash/engine";
|
import { deserializeInvitation, serializeInvitation } from "@xo-cash/engine";
|
||||||
|
|
||||||
export type SyncServerEventMap = {
|
export type SyncServerEventMap = {
|
||||||
@@ -38,7 +39,6 @@ export class SyncServer extends EventEmitter<SyncServerEventMap> {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Create our event bubblers
|
// Create our event bubblers
|
||||||
onMessage: (event: SSEvent) => this.emit("message", event),
|
|
||||||
onError: (error: unknown) =>
|
onError: (error: unknown) =>
|
||||||
this.emit(
|
this.emit(
|
||||||
"error",
|
"error",
|
||||||
@@ -48,6 +48,8 @@ export class SyncServer extends EventEmitter<SyncServerEventMap> {
|
|||||||
onConnected: () => this.emit("connected", undefined),
|
onConnected: () => this.emit("connected", undefined),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.sse.on("message", (event: SSEvent) => this.emit("message", event));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,7 +65,7 @@ export class SyncServer extends EventEmitter<SyncServerEventMap> {
|
|||||||
*/
|
*/
|
||||||
async disconnect(): Promise<void> {
|
async disconnect(): Promise<void> {
|
||||||
// Disconnect from the SSE Session
|
// Disconnect from the SSE Session
|
||||||
this.sse.close();
|
await this.sse.disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user