@towns-labs/app-framework 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +147 -0
  2. package/dist/app.d.ts +680 -0
  3. package/dist/app.d.ts.map +1 -0
  4. package/dist/app.js +2324 -0
  5. package/dist/app.js.map +1 -0
  6. package/dist/app.test.d.ts +2 -0
  7. package/dist/app.test.d.ts.map +1 -0
  8. package/dist/app.test.js +2070 -0
  9. package/dist/app.test.js.map +1 -0
  10. package/dist/identity-types.d.ts +43 -0
  11. package/dist/identity-types.d.ts.map +1 -0
  12. package/dist/identity-types.js +2 -0
  13. package/dist/identity-types.js.map +1 -0
  14. package/dist/index.d.ts +9 -0
  15. package/dist/index.d.ts.map +1 -0
  16. package/dist/index.js +9 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/modules/eventDedup.d.ts +73 -0
  19. package/dist/modules/eventDedup.d.ts.map +1 -0
  20. package/dist/modules/eventDedup.js +105 -0
  21. package/dist/modules/eventDedup.js.map +1 -0
  22. package/dist/modules/eventDedup.test.d.ts +2 -0
  23. package/dist/modules/eventDedup.test.d.ts.map +1 -0
  24. package/dist/modules/eventDedup.test.js +222 -0
  25. package/dist/modules/eventDedup.test.js.map +1 -0
  26. package/dist/modules/interaction-api.d.ts +101 -0
  27. package/dist/modules/interaction-api.d.ts.map +1 -0
  28. package/dist/modules/interaction-api.js +213 -0
  29. package/dist/modules/interaction-api.js.map +1 -0
  30. package/dist/modules/payments.d.ts +89 -0
  31. package/dist/modules/payments.d.ts.map +1 -0
  32. package/dist/modules/payments.js +139 -0
  33. package/dist/modules/payments.js.map +1 -0
  34. package/dist/modules/user.d.ts +17 -0
  35. package/dist/modules/user.d.ts.map +1 -0
  36. package/dist/modules/user.js +54 -0
  37. package/dist/modules/user.js.map +1 -0
  38. package/dist/snapshot-getter.d.ts +21 -0
  39. package/dist/snapshot-getter.d.ts.map +1 -0
  40. package/dist/snapshot-getter.js +27 -0
  41. package/dist/snapshot-getter.js.map +1 -0
  42. package/package.json +66 -0
@@ -0,0 +1,89 @@
1
+ import type { Address, Hex } from 'viem';
2
+ import type { AgentHandler, BasePayload } from '../app';
3
+ import type { Price, RouteConfig } from 'x402/types';
4
+ export declare const USDC_ADDRESSES: Record<number, Address>;
5
+ export interface PendingPayment {
6
+ command: string;
7
+ channelId: string;
8
+ userId: string;
9
+ /** The eventId of the interaction request message (to delete after signing) */
10
+ interactionEventId: string;
11
+ event: BasePayload & {
12
+ command: string;
13
+ args: string[];
14
+ mentions: any[];
15
+ replyId: string | undefined;
16
+ threadId: string | undefined;
17
+ };
18
+ params: TransferAuthorizationParams;
19
+ }
20
+ export declare function chainIdToNetwork(chainId: number): 'base' | 'base-sepolia';
21
+ export declare function getUsdcAddress(chainId: number): Address;
22
+ export interface TransferAuthorizationParams {
23
+ from: Address;
24
+ to: Address;
25
+ value: bigint;
26
+ validAfter: bigint;
27
+ validBefore: bigint;
28
+ nonce: Hex;
29
+ chainId: number;
30
+ verifyingContract: Address;
31
+ }
32
+ export interface TypedData {
33
+ types: {
34
+ [key: string]: Array<{
35
+ name: string;
36
+ type: string;
37
+ }>;
38
+ };
39
+ domain: {
40
+ name: string;
41
+ version: string;
42
+ chainId: number;
43
+ verifyingContract: Address;
44
+ };
45
+ primaryType: string;
46
+ message: {
47
+ [key: string]: any;
48
+ };
49
+ }
50
+ /**
51
+ * Generate a unique nonce for USDC authorization
52
+ * Uses crypto-safe random bytes
53
+ */
54
+ export declare function generateNonce(): Hex;
55
+ /**
56
+ * Get validity window for authorization
57
+ * validAfter: current timestamp
58
+ * validBefore: current timestamp + 1 hour
59
+ */
60
+ export declare function getValidityWindow(): {
61
+ validAfter: bigint;
62
+ validBefore: bigint;
63
+ };
64
+ /**
65
+ * Build EIP-712 typed data for USDC transferWithAuthorization signature request
66
+ * Synchronous version for sending to client - no contract calls needed
67
+ */
68
+ export declare function buildTransferAuthorizationTypedDataForSigning(params: TransferAuthorizationParams): TypedData;
69
+ /**
70
+ * Simplified payment request builder
71
+ * Returns all data needed to track and verify the payment
72
+ */
73
+ export declare function createPaymentRequest(handler: AgentHandler, event: BasePayload, chainId: number, fromAddress: Address, recipientAddress: Address, paymentConfig: RouteConfig, command: string): Promise<{
74
+ signatureId: string;
75
+ params: TransferAuthorizationParams;
76
+ eventId: string;
77
+ }>;
78
+ /**
79
+ * Parses a USDC price to atomic units (6 decimals).
80
+ * Handles dollar signs, commas, and decimal amounts.
81
+ * Only string and number inputs are supported.
82
+ *
83
+ * @example
84
+ * parseUSDCPrice("$1.00") // 1000000n
85
+ * parseUSDCPrice(1.50) // 1500000n
86
+ * parseUSDCPrice("$1,000.00") // 1000000000n
87
+ */
88
+ export declare function parseUSDCPrice(price: Price): bigint;
89
+ //# sourceMappingURL=payments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payments.d.ts","sourceRoot":"","sources":["../../src/modules/payments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAGxC,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AACvD,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAEpD,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAGlD,CAAA;AAED,MAAM,WAAW,cAAc;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,+EAA+E;IAC/E,kBAAkB,EAAE,MAAM,CAAA;IAC1B,KAAK,EAAE,WAAW,GAAG;QACjB,OAAO,EAAE,MAAM,CAAA;QACf,IAAI,EAAE,MAAM,EAAE,CAAA;QACd,QAAQ,EAAE,GAAG,EAAE,CAAA;QACf,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;QAC3B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAA;KAC/B,CAAA;IACD,MAAM,EAAE,2BAA2B,CAAA;CACtC;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,cAAc,CASzE;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAMvD;AAGD,MAAM,WAAW,2BAA2B;IACxC,IAAI,EAAE,OAAO,CAAA;IACb,EAAE,EAAE,OAAO,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,GAAG,CAAA;IACV,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,OAAO,CAAA;CAC7B;AAED,MAAM,WAAW,SAAS;IACtB,KAAK,EAAE;QACH,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KACvD,CAAA;IACD,MAAM,EAAE;QACJ,IAAI,EAAE,MAAM,CAAA;QACZ,OAAO,EAAE,MAAM,CAAA;QACf,OAAO,EAAE,MAAM,CAAA;QACf,iBAAiB,EAAE,OAAO,CAAA;KAC7B,CAAA;IACD,WAAW,EAAE,MAAM,CAAA;IACnB,OAAO,EAAE;QACL,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KACrB,CAAA;CACJ;AAED;;;GAGG;AACH,wBAAgB,aAAa,IAAI,GAAG,CAGnC;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI;IACjC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,EAAE,MAAM,CAAA;CACtB,CAMA;AAED;;;GAGG;AACH,wBAAgB,6CAA6C,CACzD,MAAM,EAAE,2BAA2B,GACpC,SAAS,CA4BX;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACtC,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,OAAO,EACpB,gBAAgB,EAAE,OAAO,EACzB,aAAa,EAAE,WAAW,EAC1B,OAAO,EAAE,MAAM;;;;GAyClB;AAED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAkBnD"}
@@ -0,0 +1,139 @@
1
+ import { keccak256 } from 'viem';
2
+ import { baseSepolia, base } from 'viem/chains';
3
+ export const USDC_ADDRESSES = {
4
+ [baseSepolia.id]: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
5
+ [base.id]: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
6
+ };
7
+ export function chainIdToNetwork(chainId) {
8
+ switch (chainId) {
9
+ case base.id:
10
+ return 'base';
11
+ case baseSepolia.id:
12
+ return 'base-sepolia';
13
+ default:
14
+ throw new Error(`Unsupported chain ID: ${chainId}`);
15
+ }
16
+ }
17
+ export function getUsdcAddress(chainId) {
18
+ const address = USDC_ADDRESSES[chainId];
19
+ if (!address) {
20
+ throw new Error(`USDC address not found for chain ID: ${chainId}`);
21
+ }
22
+ return address;
23
+ }
24
+ /**
25
+ * Generate a unique nonce for USDC authorization
26
+ * Uses crypto-safe random bytes
27
+ */
28
+ export function generateNonce() {
29
+ const randomBytes = crypto.getRandomValues(new Uint8Array(32));
30
+ return keccak256(randomBytes);
31
+ }
32
+ /**
33
+ * Get validity window for authorization
34
+ * validAfter: current timestamp
35
+ * validBefore: current timestamp + 1 hour
36
+ */
37
+ export function getValidityWindow() {
38
+ const now = BigInt(Math.floor(Date.now() / 1000));
39
+ return {
40
+ validAfter: now,
41
+ validBefore: now + 3600n, // 1 hour
42
+ };
43
+ }
44
+ /**
45
+ * Build EIP-712 typed data for USDC transferWithAuthorization signature request
46
+ * Synchronous version for sending to client - no contract calls needed
47
+ */
48
+ export function buildTransferAuthorizationTypedDataForSigning(params) {
49
+ return {
50
+ types: {
51
+ TransferWithAuthorization: [
52
+ { name: 'from', type: 'address' },
53
+ { name: 'to', type: 'address' },
54
+ { name: 'value', type: 'uint256' },
55
+ { name: 'validAfter', type: 'uint256' },
56
+ { name: 'validBefore', type: 'uint256' },
57
+ { name: 'nonce', type: 'bytes32' },
58
+ ],
59
+ },
60
+ domain: {
61
+ name: 'USDC',
62
+ version: '2',
63
+ chainId: params.chainId,
64
+ verifyingContract: params.verifyingContract,
65
+ },
66
+ primaryType: 'TransferWithAuthorization',
67
+ message: {
68
+ from: params.from,
69
+ to: params.to,
70
+ value: params.value.toString(),
71
+ validAfter: params.validAfter.toString(),
72
+ validBefore: params.validBefore.toString(),
73
+ nonce: params.nonce,
74
+ },
75
+ };
76
+ }
77
+ /**
78
+ * Simplified payment request builder
79
+ * Returns all data needed to track and verify the payment
80
+ */
81
+ export async function createPaymentRequest(handler, event, chainId, fromAddress, recipientAddress, paymentConfig, command) {
82
+ const usdcAddr = getUsdcAddress(chainId);
83
+ const nonce = generateNonce();
84
+ const { validAfter, validBefore } = getValidityWindow();
85
+ const value = parseUSDCPrice(paymentConfig.price);
86
+ const params = {
87
+ from: fromAddress,
88
+ to: recipientAddress,
89
+ value: value,
90
+ validAfter,
91
+ validBefore,
92
+ nonce,
93
+ chainId: chainId,
94
+ verifyingContract: usdcAddr,
95
+ };
96
+ const typedData = buildTransferAuthorizationTypedDataForSigning(params);
97
+ // Format price for display
98
+ const priceDisplay = typeof paymentConfig.price === 'string' || typeof paymentConfig.price === 'number'
99
+ ? String(paymentConfig.price)
100
+ : JSON.stringify(paymentConfig.price);
101
+ const result = await handler.sendInteractionRequest(event.channelId, {
102
+ type: 'signature',
103
+ method: 'typed_data',
104
+ data: JSON.stringify(typedData),
105
+ chainId: params.chainId.toString(),
106
+ signerWallet: fromAddress,
107
+ title: `Payment Required for /${command} • ${priceDisplay} USDC`,
108
+ subtitle: `Sign to authorize payment`,
109
+ });
110
+ return {
111
+ signatureId: result.requestId,
112
+ params,
113
+ eventId: result.eventId,
114
+ };
115
+ }
116
+ /**
117
+ * Parses a USDC price to atomic units (6 decimals).
118
+ * Handles dollar signs, commas, and decimal amounts.
119
+ * Only string and number inputs are supported.
120
+ *
121
+ * @example
122
+ * parseUSDCPrice("$1.00") // 1000000n
123
+ * parseUSDCPrice(1.50) // 1500000n
124
+ * parseUSDCPrice("$1,000.00") // 1000000000n
125
+ */
126
+ export function parseUSDCPrice(price) {
127
+ if (typeof price !== 'string' && typeof price !== 'number') {
128
+ throw new Error(`parseUSDCPrice only supports string or number inputs, got: ${typeof price}`);
129
+ }
130
+ const rawStr = String(price);
131
+ const sanitized = rawStr.replace(/[$,\s]/g, '').trim();
132
+ if (!/^\d+(\.\d{0,6})?$/.test(sanitized)) {
133
+ throw new Error(`Invalid USDC price format: ${JSON.stringify(price)}`);
134
+ }
135
+ const [whole, frac = ''] = sanitized.split('.');
136
+ const paddedFrac = frac.padEnd(6, '0').slice(0, 6);
137
+ return BigInt(whole || '0') * 1000000n + BigInt(paddedFrac);
138
+ }
139
+ //# sourceMappingURL=payments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payments.js","sourceRoot":"","sources":["../../src/modules/payments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { Address } from 'viem';
2
+ import type { AppRegistryRpcClient } from '@towns-labs/sdk';
3
+ export type UserInfo = {
4
+ /** The user's address (0x string) */
5
+ id: Address;
6
+ /** The user's display name from the app registry */
7
+ displayName: string;
8
+ /** The user's username from the app registry */
9
+ username: string;
10
+ };
11
+ export declare class UserInfoCache {
12
+ private cache;
13
+ get(userId: string): UserInfo | undefined;
14
+ set(userId: string, info: UserInfo): void;
15
+ }
16
+ export declare function getUserBulk(userIds: Address[], cache: UserInfoCache, getAppRegistryClient: () => Promise<AppRegistryRpcClient>): Promise<Map<Address, UserInfo | undefined>>;
17
+ //# sourceMappingURL=user.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user.d.ts","sourceRoot":"","sources":["../../src/modules/user.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAG3D,MAAM,MAAM,QAAQ,GAAG;IACnB,qCAAqC;IACrC,EAAE,EAAE,OAAO,CAAA;IACX,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAA;IACnB,gDAAgD;IAChD,QAAQ,EAAE,MAAM,CAAA;CACnB,CAAA;AASD,qBAAa,aAAa;IACtB,OAAO,CAAC,KAAK,CAAoC;IAEjD,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS;IAUzC,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,IAAI;CAM5C;AAED,wBAAsB,WAAW,CAC7B,OAAO,EAAE,OAAO,EAAE,EAClB,KAAK,EAAE,aAAa,EACpB,oBAAoB,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,GAC1D,OAAO,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,GAAG,SAAS,CAAC,CAAC,CAsC7C"}
@@ -0,0 +1,54 @@
1
+ import { addressFromUserId, userIdFromAddress } from '@towns-labs/sdk';
2
+ const USER_INFO_TTL_MS = 5 * 60 * 1000; // 5 minutes
3
+ export class UserInfoCache {
4
+ cache = new Map();
5
+ get(userId) {
6
+ const entry = this.cache.get(userId.toLowerCase());
7
+ if (!entry)
8
+ return undefined;
9
+ if (Date.now() > entry.expiresAt) {
10
+ this.cache.delete(userId.toLowerCase());
11
+ return undefined;
12
+ }
13
+ return entry.value;
14
+ }
15
+ set(userId, info) {
16
+ this.cache.set(userId.toLowerCase(), {
17
+ value: info,
18
+ expiresAt: Date.now() + USER_INFO_TTL_MS,
19
+ });
20
+ }
21
+ }
22
+ export async function getUserBulk(userIds, cache, getAppRegistryClient) {
23
+ const looked = userIds.map((id) => [id, cache.get(id)]);
24
+ const uncached = looked.filter(([, v]) => !v).map(([id]) => id);
25
+ const result = new Map(looked.filter(([, v]) => v).map(([id, v]) => [id, v]));
26
+ if (uncached.length === 0)
27
+ return result;
28
+ try {
29
+ const appRegistry = await getAppRegistryClient();
30
+ const response = await appRegistry.getBatchEntityNames({
31
+ entityIds: uncached.map((userId) => addressFromUserId(userId)),
32
+ });
33
+ const found = new Map((response.entities ?? []).map((entity) => {
34
+ const resolvedUserId = userIdFromAddress(entity.entityId);
35
+ const info = {
36
+ id: resolvedUserId,
37
+ displayName: entity.displayName ?? '',
38
+ username: entity.username ?? '',
39
+ };
40
+ cache.set(resolvedUserId, info);
41
+ return [resolvedUserId.toLowerCase(), info];
42
+ }));
43
+ for (const userId of uncached) {
44
+ result.set(userId, found.get(userId.toLowerCase()));
45
+ }
46
+ }
47
+ catch {
48
+ for (const userId of uncached) {
49
+ result.set(userId, undefined);
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+ //# sourceMappingURL=user.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/modules/user.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import type { PlainMessage, Snapshot } from '@towns-labs/proto';
2
+ import type { ParsedStreamResponse, Prettify } from '@towns-labs/sdk';
3
+ type RemoveContent<T extends string> = T extends `${infer Prefix}Content` ? Prefix : T;
4
+ type SnapshotContent = Exclude<Snapshot['content'], {
5
+ case: undefined;
6
+ }>;
7
+ type SnapshotTypeMap = {
8
+ [K in SnapshotContent['case']]: Extract<SnapshotContent, {
9
+ case: K;
10
+ }>['value'];
11
+ };
12
+ type GenerateGetters<SnapshotCase extends keyof SnapshotTypeMap, SnapshotType = PlainMessage<SnapshotTypeMap[SnapshotCase]>> = {
13
+ [Prop in NonNullable<keyof SnapshotType> as `get${Capitalize<RemoveContent<SnapshotCase>>}${Capitalize<string & Prop>}`]: (streamId: string) => Promise<SnapshotType[Prop]>;
14
+ };
15
+ type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
16
+ type GetterFunctions = UnionToIntersection<{
17
+ [K in keyof SnapshotTypeMap]: GenerateGetters<K>;
18
+ }[keyof SnapshotTypeMap]>;
19
+ export declare const SnapshotGetter: (getStream: (streamId: string) => Promise<ParsedStreamResponse>) => Prettify<GetterFunctions>;
20
+ export {};
21
+ //# sourceMappingURL=snapshot-getter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-getter.d.ts","sourceRoot":"","sources":["../src/snapshot-getter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAoB,MAAM,mBAAmB,CAAA;AACjF,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAA;AAIrE,KAAK,aAAa,CAAC,CAAC,SAAS,MAAM,IAAI,CAAC,SAAS,GAAG,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,CAAA;AAGtF,KAAK,eAAe,GAAG,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC,CAAA;AAGxE,KAAK,eAAe,GAAG;KAClB,CAAC,IAAI,eAAe,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,eAAe,EAAE;QAAE,IAAI,EAAE,CAAC,CAAA;KAAE,CAAC,CAAC,OAAO,CAAC;CACjF,CAAA;AAGD,KAAK,eAAe,CAChB,YAAY,SAAS,MAAM,eAAe,EAC1C,YAAY,GAAG,YAAY,CAAC,eAAe,CAAC,YAAY,CAAC,CAAC,IAC1D;KACC,IAAI,IAAI,WAAW,CAChB,MAAM,YAAY,CACrB,IAAI,MAAM,UAAU,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,GAAG,CAC/E,QAAQ,EAAE,MAAM,KACf,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;CACnC,CAAA;AAGD,KAAK,mBAAmB,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,IAAI,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,KAAK,IAAI,GAC7F,CAAC,GACD,KAAK,CAAA;AAGX,KAAK,eAAe,GAAG,mBAAmB,CACtC;KACK,CAAC,IAAI,MAAM,eAAe,GAAG,eAAe,CAAC,CAAC,CAAC;CACnD,CAAC,MAAM,eAAe,CAAC,CAC3B,CAAA;AAsBD,eAAO,MAAM,cAAc,GAAI,WAAW,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,CAAC,KAyBpF,QAAQ,CAAC,eAAe,CAAC,CAAA"}
@@ -0,0 +1,27 @@
1
+ const getFromSnapshot = (getStream) => async (streamId, snapshotCase, propertyKey) => {
2
+ const stream = await getStream(streamId);
3
+ if (stream.snapshot.content.case === snapshotCase) {
4
+ const snapshotValue = stream.snapshot.content.value;
5
+ return snapshotValue[propertyKey];
6
+ }
7
+ return undefined;
8
+ };
9
+ export const SnapshotGetter = (getStream) => new Proxy({}, {
10
+ get(_target, prop) {
11
+ return async (streamId) => {
12
+ // Parse the getter name to extract snapshot type and property
13
+ const propName = String(prop);
14
+ // Match pattern like getSpaceInception, getUserMemberships, etc.
15
+ const match = propName.match(/^get([A-Z][a-z]+)([A-Z][a-zA-Z]+)$/);
16
+ if (!match) {
17
+ throw new Error(`Invalid getter name: ${propName}`);
18
+ }
19
+ const [, snapshotType, propertyName] = match;
20
+ const snapshotCase = `${snapshotType.toLowerCase()}Content`;
21
+ const property = (propertyName.charAt(0).toLowerCase() +
22
+ propertyName.slice(1));
23
+ return getFromSnapshot(getStream)(streamId, snapshotCase, property);
24
+ };
25
+ },
26
+ });
27
+ //# sourceMappingURL=snapshot-getter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot-getter.js","sourceRoot":"","sources":["../src/snapshot-getter.ts"],"names":[],"mappings":"AA4CA,MAAM,eAAe,GACjB,CAAC,SAA8D,EAAE,EAAE,CACnE,KAAK,EACD,QAAgB,EAChB,YAAmB,EACnB,WAAiB,EACqC,EAAE;IACxD,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAA;IACxC,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;QAChD,MAAM,aAAa,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAoC,CAAA;QAClF,OAAO,aAAa,CAAC,WAAW,CAAC,CAAA;IACrC,CAAC;IACD,OAAO,SAAS,CAAA;AACpB,CAAC,CAAA;AAEL,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,SAA8D,EAAE,EAAE,CAC7F,IAAI,KAAK,CACL,EAAE,EACF;IACI,GAAG,CAAC,OAAO,EAAE,IAAY;QACrB,OAAO,KAAK,EAAE,QAAgB,EAAE,EAAE;YAC9B,8DAA8D;YAC9D,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAA;YAE7B,iEAAiE;YACjE,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,oCAAoC,CAAC,CAAA;YAClE,IAAI,CAAC,KAAK,EAAE,CAAC;gBACT,MAAM,IAAI,KAAK,CAAC,wBAAwB,QAAQ,EAAE,CAAC,CAAA;YACvD,CAAC;YAED,MAAM,CAAC,EAAE,YAAY,EAAE,YAAY,CAAC,GAAG,KAAK,CAAA;YAC5C,MAAM,YAAY,GACd,GAAG,YAAY,CAAC,WAAW,EAAE,SAAkC,CAAA;YACnE,MAAM,QAAQ,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE;gBAClD,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC,CAAoD,CAAA;YAE7E,OAAO,eAAe,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAA;QACvE,CAAC,CAAA;IACL,CAAC;CACJ,CACyB,CAAA"}
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@towns-labs/app-framework",
3
+ "version": "4.0.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "clean": "rm -rf dist",
10
+ "lint": "eslint --format unix ./src --max-warnings=0",
11
+ "lint:fix": "bun run lint --fix",
12
+ "test": "vitest run",
13
+ "test:ci": "vitest run --silent=passed-only",
14
+ "test:watch": "vitest --watch",
15
+ "typecheck": "tsc --noEmit",
16
+ "watch": "tsc --watch"
17
+ },
18
+ "dependencies": {
19
+ "@bufbuild/protobuf": "^2.9.0",
20
+ "@connectrpc/connect-node": "^2.1.0",
21
+ "@standard-schema/spec": "^1.0.0-beta.9",
22
+ "@towns-labs/encryption": "^4.0.0",
23
+ "@towns-labs/proto": "^4.0.0",
24
+ "@towns-labs/sdk": "^4.0.0",
25
+ "@towns-labs/sdk-crypto": "^4.0.0",
26
+ "@towns-labs/utils": "^4.0.0",
27
+ "@towns-labs/web3": "^4.0.0",
28
+ "ethers": "^5.8.0",
29
+ "image-size": "^2.0.2",
30
+ "jsonwebtoken": "^9.0.2",
31
+ "nanoevents": "^9.1.0",
32
+ "superjson": "^2.2.2",
33
+ "x402": "^0.7.3"
34
+ },
35
+ "devDependencies": {
36
+ "@hono/node-server": "^1.19.9",
37
+ "@towns-labs/contracts": "^4.0.0",
38
+ "@towns-labs/relayer-client": "^4.0.0",
39
+ "@types/jsonwebtoken": "^9.0.9",
40
+ "@types/node": "^20.14.8",
41
+ "@typescript-eslint/eslint-plugin": "^8.29.0",
42
+ "@typescript-eslint/parser": "^8.29.0",
43
+ "eslint": "^8.57.1",
44
+ "eslint-import-resolver-typescript": "^4.3.2",
45
+ "eslint-plugin-import-x": "^4.10.2",
46
+ "eslint-plugin-tsdoc": "^0.3.0",
47
+ "fake-indexeddb": "^6.0.1",
48
+ "hono": "^4.11.7",
49
+ "nanoid": "^4.0.0",
50
+ "typescript": "~5.8.3",
51
+ "viem": "2.45.1",
52
+ "vitest": "^3.2.3",
53
+ "zod": "^4.3.6"
54
+ },
55
+ "files": [
56
+ "/dist",
57
+ "package.json"
58
+ ],
59
+ "peerDependencies": {
60
+ "hono": ">=4",
61
+ "viem": "2.x"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ }
66
+ }