@geoprotocol/geo-sdk 0.19.3 → 0.19.4

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 (36) hide show
  1. package/dist/src/abis/dao-space-v2.test.d.ts +2 -0
  2. package/dist/src/abis/dao-space-v2.test.d.ts.map +1 -0
  3. package/dist/src/abis/dao-space-v2.test.js +67 -0
  4. package/dist/src/abis/dao-space-v2.test.js.map +1 -0
  5. package/dist/src/api-surface.e2e.test.d.ts +2 -0
  6. package/dist/src/api-surface.e2e.test.d.ts.map +1 -0
  7. package/dist/src/api-surface.e2e.test.js +1007 -0
  8. package/dist/src/api-surface.e2e.test.js.map +1 -0
  9. package/dist/src/contracts-v2/local-geobrowser.e2e.test.d.ts +2 -0
  10. package/dist/src/contracts-v2/local-geobrowser.e2e.test.d.ts.map +1 -0
  11. package/dist/src/contracts-v2/local-geobrowser.e2e.test.js +239 -0
  12. package/dist/src/contracts-v2/local-geobrowser.e2e.test.js.map +1 -0
  13. package/dist/src/dao-space/propose-update-voting-settings.d.ts +8 -0
  14. package/dist/src/dao-space/propose-update-voting-settings.d.ts.map +1 -0
  15. package/dist/src/dao-space/propose-update-voting-settings.js +19 -0
  16. package/dist/src/dao-space/propose-update-voting-settings.js.map +1 -0
  17. package/dist/src/dao-space/propose-update-voting-settings.test.d.ts +2 -0
  18. package/dist/src/dao-space/propose-update-voting-settings.test.d.ts.map +1 -0
  19. package/dist/src/dao-space/propose-update-voting-settings.test.js +118 -0
  20. package/dist/src/dao-space/propose-update-voting-settings.test.js.map +1 -0
  21. package/dist/src/e2e-test-environment.d.ts +26 -0
  22. package/dist/src/e2e-test-environment.d.ts.map +1 -0
  23. package/dist/src/e2e-test-environment.js +150 -0
  24. package/dist/src/e2e-test-environment.js.map +1 -0
  25. package/dist/src/legacy-api-surface.e2e.test.d.ts +2 -0
  26. package/dist/src/legacy-api-surface.e2e.test.d.ts.map +1 -0
  27. package/dist/src/legacy-api-surface.e2e.test.js +860 -0
  28. package/dist/src/legacy-api-surface.e2e.test.js.map +1 -0
  29. package/dist/src/ranks/create-rank.test.js +21 -0
  30. package/dist/src/ranks/create-rank.test.js.map +1 -1
  31. package/dist/src/ranks/update-rank.test.js +11 -0
  32. package/dist/src/ranks/update-rank.test.js.map +1 -1
  33. package/dist/src/ranks/vote-ops.d.ts.map +1 -1
  34. package/dist/src/ranks/vote-ops.js +5 -2
  35. package/dist/src/ranks/vote-ops.js.map +1 -1
  36. package/package.json +1 -1
@@ -0,0 +1,1007 @@
1
+ import { createPublicClient, createWalletClient, http } from 'viem';
2
+ import { privateKeyToAccount } from 'viem/accounts';
3
+ import { describe, expect, it } from 'vitest';
4
+ import { createGeoClient, Ops } from '../index.js';
5
+ import { SpaceRegistryAbi } from './abis/index.js';
6
+ import { DESCRIPTION_PROPERTY, RELATION_TYPE, REPLY_TO_PROPERTY } from './core/ids/system.js';
7
+ import { createE2ETestEnvironment } from './e2e-test-environment.js';
8
+ import { deriveCommentName } from './graph/comment-utils.js';
9
+ import { generate, toGrcId } from './id-utils.js';
10
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
11
+ const EMPTY_SPACE_ID = '0x00000000000000000000000000000000';
12
+ const INDEXER_TIMEOUT_MS = 120_000;
13
+ const TEST_TIMEOUT_MS = 600_000;
14
+ const replyToGrcId = toGrcId(REPLY_TO_PROPERTY);
15
+ let cachedE2E;
16
+ function getE2E() {
17
+ cachedE2E ??= createE2ETestEnvironment();
18
+ return cachedE2E;
19
+ }
20
+ const e2e = new Proxy({}, {
21
+ get(_target, property) {
22
+ return getE2E()[property];
23
+ },
24
+ });
25
+ let cachedGeo;
26
+ function getGeo() {
27
+ cachedGeo ??= createGeoClient({ network: getE2E().network });
28
+ return cachedGeo;
29
+ }
30
+ const geo = new Proxy({}, {
31
+ get(_target, property) {
32
+ return getGeo()[property];
33
+ },
34
+ });
35
+ class GraphQlRequestError extends Error {
36
+ errors;
37
+ constructor(errors) {
38
+ super(`GraphQL errors: ${JSON.stringify(errors)}`);
39
+ this.errors = errors;
40
+ }
41
+ hasValidationError() {
42
+ return JSON.stringify(this.errors).includes('GRAPHQL_VALIDATION_FAILED');
43
+ }
44
+ }
45
+ function sleep(ms) {
46
+ return new Promise(resolve => setTimeout(resolve, ms));
47
+ }
48
+ function filterReplyToRelations(ops) {
49
+ return ops.filter((op) => op.type === 'createRelation' &&
50
+ 'relationType' in op &&
51
+ op.relationType.every((b, i) => b === replyToGrcId[i]));
52
+ }
53
+ function hexToUuid(hex) {
54
+ return hex.slice(2, 34).toLowerCase();
55
+ }
56
+ function uniqueName(prefix) {
57
+ return `${prefix} ${Date.now().toString(36)}`;
58
+ }
59
+ function tinyPngBlob() {
60
+ return new Blob([
61
+ new Uint8Array([
62
+ 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 1, 0, 0, 0, 1, 8, 6, 0, 0, 0, 31, 21,
63
+ 196, 137, 0, 0, 0, 13, 73, 68, 65, 84, 120, 156, 99, 248, 255, 255, 63, 0, 5, 254, 2, 254, 167, 53, 129, 132, 0,
64
+ 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130,
65
+ ]),
66
+ ], { type: 'image/png' });
67
+ }
68
+ function entityQuery(id, spaceId) {
69
+ const normalizedSpaceId = spaceId.replaceAll('-', '');
70
+ return `query entity {
71
+ entity(id: ${JSON.stringify(id)}) {
72
+ id
73
+ name
74
+ valuesList(filter: { spaceId: { in: [${JSON.stringify(normalizedSpaceId)}] } }) {
75
+ propertyId
76
+ spaceId
77
+ }
78
+ relationsList(filter: { spaceId: { in: [${JSON.stringify(normalizedSpaceId)}] } }) {
79
+ id
80
+ spaceId
81
+ }
82
+ }
83
+ }`;
84
+ }
85
+ function replyToRelationsQuery(id) {
86
+ return `query entity {
87
+ entity(id: ${JSON.stringify(id)}) {
88
+ relationsList(filter: { typeId: { in: [${JSON.stringify(REPLY_TO_PROPERTY)}] } }) {
89
+ toEntity { id }
90
+ toSpace { id }
91
+ position
92
+ }
93
+ }
94
+ }`;
95
+ }
96
+ function proposalUuid(proposalId) {
97
+ return proposalId.replace(/^0x/, '').toLowerCase();
98
+ }
99
+ function proposalQuery(proposalId) {
100
+ const id = proposalUuid(proposalId);
101
+ return `query proposal {
102
+ proposals(condition: { id: ${JSON.stringify(id)} }) {
103
+ id
104
+ spaceId
105
+ proposedBy
106
+ currentVersion
107
+ proposalVersions {
108
+ proposalVersion
109
+ votingMode
110
+ name
111
+ yesCount
112
+ noCount
113
+ abstainCount
114
+ }
115
+ }
116
+ proposalActions(condition: { proposalId: ${JSON.stringify(id)} }) {
117
+ proposalId
118
+ proposalVersion
119
+ actionType
120
+ targetId
121
+ contentUri
122
+ }
123
+ }`;
124
+ }
125
+ function proposalVoteQuery(proposalId, voterId, spaceId) {
126
+ return `query proposalVote {
127
+ proposalVotes(condition: {
128
+ proposalId: ${JSON.stringify(proposalUuid(proposalId))}
129
+ voterId: ${JSON.stringify(voterId.replaceAll('-', ''))}
130
+ spaceId: ${JSON.stringify(spaceId.replaceAll('-', ''))}
131
+ }) {
132
+ proposalId
133
+ voterId
134
+ spaceId
135
+ vote
136
+ }
137
+ }`;
138
+ }
139
+ function entityVoteQuery(entityId, voterId, spaceId) {
140
+ return `query entityVote {
141
+ votes(condition: {
142
+ voterId: ${JSON.stringify(voterId.replaceAll('-', ''))}
143
+ objectId: ${JSON.stringify(entityId.replaceAll('-', ''))}
144
+ objectType: 0
145
+ spaceId: ${JSON.stringify(spaceId.replaceAll('-', ''))}
146
+ }) {
147
+ voterId
148
+ objectId
149
+ objectType
150
+ spaceId
151
+ vote
152
+ }
153
+ }`;
154
+ }
155
+ function spaceTopicQuery(spaceId) {
156
+ const normalizedSpaceId = spaceId.replaceAll('-', '').toLowerCase();
157
+ return `query spaces {
158
+ spaces(filter: { id: { is: ${JSON.stringify(normalizedSpaceId)} } }) {
159
+ topicId
160
+ }
161
+ }`;
162
+ }
163
+ async function queryGraph(query) {
164
+ const response = await geo.api.graphql(query);
165
+ if (response.errors) {
166
+ throw new GraphQlRequestError(response.errors);
167
+ }
168
+ if (response.data === undefined) {
169
+ throw new Error('GraphQL response did not include data');
170
+ }
171
+ return response.data;
172
+ }
173
+ async function waitFor(label, read, predicate) {
174
+ const deadline = Date.now() + INDEXER_TIMEOUT_MS;
175
+ let lastValue;
176
+ let lastError;
177
+ while (Date.now() < deadline) {
178
+ try {
179
+ lastValue = await read();
180
+ lastError = undefined;
181
+ if (predicate(lastValue)) {
182
+ return lastValue;
183
+ }
184
+ }
185
+ catch (error) {
186
+ if (error instanceof GraphQlRequestError && error.hasValidationError()) {
187
+ throw error;
188
+ }
189
+ lastError = error;
190
+ }
191
+ await sleep(3_000);
192
+ }
193
+ throw new Error(`Timed out waiting for ${label}. Last value: ${JSON.stringify(lastValue)}. Last error: ${String(lastError)}`);
194
+ }
195
+ async function waitForEntityName(entityId, spaceId, expectedName) {
196
+ const data = await waitFor(`entity ${entityId} name "${expectedName}"`, () => queryGraph(entityQuery(entityId, spaceId)), value => value.entity?.name === expectedName);
197
+ expect(data.entity?.name).toBe(expectedName);
198
+ return data.entity;
199
+ }
200
+ async function waitForEntityDeleted(entityId, spaceId) {
201
+ await waitFor(`entity ${entityId} deletion in space ${spaceId}`, () => queryGraph(entityQuery(entityId, spaceId)), value => !value.entity || (value.entity.valuesList.length === 0 && value.entity.relationsList.length === 0));
202
+ }
203
+ async function waitForReplyToRelations(entityId, expectedTargets) {
204
+ const data = await waitFor(`reply-to relations for ${entityId}`, () => queryGraph(replyToRelationsQuery(entityId)), value => {
205
+ const targets = value.entity?.relationsList.map(relation => relation.toEntity.id) ?? [];
206
+ return expectedTargets.every(target => targets.includes(target));
207
+ });
208
+ const targets = data.entity?.relationsList.map(relation => relation.toEntity.id) ?? [];
209
+ expect(targets).toEqual(expect.arrayContaining(expectedTargets));
210
+ return data.entity?.relationsList ?? [];
211
+ }
212
+ async function waitForProposal(proposalId, expected) {
213
+ const data = await waitFor(`proposal ${proposalUuid(proposalId)}`, () => queryGraph(proposalQuery(proposalId)), value => {
214
+ const proposal = value.proposals[0];
215
+ if (!proposal)
216
+ return false;
217
+ if (proposal.spaceId !== expected.daoSpaceId.replaceAll('-', '')) {
218
+ return false;
219
+ }
220
+ if (proposal.proposedBy !== expected.proposedBy.replaceAll('-', '')) {
221
+ return false;
222
+ }
223
+ const currentVersion = proposal.proposalVersions.find(version => version.proposalVersion === proposal.currentVersion);
224
+ if (expected.votingMode && currentVersion?.votingMode !== expected.votingMode) {
225
+ return false;
226
+ }
227
+ if (!expected.actionType)
228
+ return true;
229
+ const expectedActionTypes = Array.isArray(expected.actionType) ? expected.actionType : [expected.actionType];
230
+ return value.proposalActions.some(action => expectedActionTypes.includes(action.actionType) &&
231
+ (expected.targetId === undefined ||
232
+ action.targetId === expected.targetId.replace(/^0x/, '').replaceAll('-', '')) &&
233
+ (expected.contentUri === undefined || action.contentUri === expected.contentUri));
234
+ });
235
+ expect(data.proposals[0]?.id).toBe(proposalUuid(proposalId));
236
+ if (expected.actionType) {
237
+ const expectedActionTypes = Array.isArray(expected.actionType) ? expected.actionType : [expected.actionType];
238
+ expect(data.proposalActions.some(action => expectedActionTypes.includes(action.actionType))).toBe(true);
239
+ }
240
+ return data;
241
+ }
242
+ async function waitForProposalVote(proposalId, voterId, daoSpaceId, vote) {
243
+ const data = await waitFor(`proposal ${proposalUuid(proposalId)} vote ${vote}`, () => queryGraph(proposalVoteQuery(proposalId, voterId, daoSpaceId)), value => value.proposalVotes.some(proposalVote => proposalVote.vote === vote));
244
+ expect(data.proposalVotes.map(proposalVote => proposalVote.vote)).toContain(vote);
245
+ }
246
+ async function waitForEntityVote(entityId, voterId, spaceId, predicate) {
247
+ const data = await waitFor(`entity vote for ${entityId}`, () => queryGraph(entityVoteQuery(entityId, voterId, spaceId)), value => predicate(value.votes));
248
+ expect(predicate(data.votes)).toBe(true);
249
+ return data.votes;
250
+ }
251
+ async function waitForSpaceTopicId(spaceId, topicId) {
252
+ const normalizedTopicId = topicId.replaceAll('-', '').toLowerCase();
253
+ const data = await waitFor(`space ${spaceId} topic ${normalizedTopicId}`, () => queryGraph(spaceTopicQuery(spaceId)), value => value.spaces[0]?.topicId === normalizedTopicId);
254
+ expect(data.spaces[0]?.topicId).toBe(normalizedTopicId);
255
+ return data.spaces[0];
256
+ }
257
+ async function readSpaceTopicId(spaceId) {
258
+ const data = await queryGraph(spaceTopicQuery(spaceId));
259
+ return data.spaces[0]?.topicId ?? null;
260
+ }
261
+ async function getSpaceIdHex(publicClient, address) {
262
+ return (await publicClient.readContract({
263
+ address: e2e.contracts.SPACE_REGISTRY_ADDRESS,
264
+ abi: SpaceRegistryAbi,
265
+ functionName: 'addressToSpaceId',
266
+ args: [address],
267
+ }));
268
+ }
269
+ async function ensurePersonalSpace({ accountAddress, account, publicClient, walletClient, }) {
270
+ let spaceIdHex = await getSpaceIdHex(publicClient, accountAddress);
271
+ const hasExistingSpace = await geo.personalSpaces.hasSpace({
272
+ address: accountAddress,
273
+ });
274
+ expect(hasExistingSpace).toBe(spaceIdHex.toLowerCase() !== EMPTY_SPACE_ID.toLowerCase());
275
+ if (spaceIdHex.toLowerCase() === EMPTY_SPACE_ID.toLowerCase()) {
276
+ const createSpace = geo.personalSpaces.create({
277
+ name: 'E2E API Surface Personal Space',
278
+ accountAddress,
279
+ });
280
+ await sendTransactionAndWait({ account, publicClient, walletClient }, {
281
+ label: 'create personal space',
282
+ to: createSpace.to,
283
+ calldata: createSpace.calldata,
284
+ });
285
+ spaceIdHex = await getSpaceIdHex(publicClient, accountAddress);
286
+ if (spaceIdHex.toLowerCase() !== EMPTY_SPACE_ID.toLowerCase()) {
287
+ const spaceId = hexToUuid(spaceIdHex);
288
+ const publishProfile = await geo.personalSpaces.publishEdit({
289
+ name: 'Create personal space profile',
290
+ spaceId,
291
+ author: spaceId,
292
+ ops: createSpace.ops,
293
+ });
294
+ await sendTransactionAndWait({ account, publicClient, walletClient }, {
295
+ label: 'publish personal space profile',
296
+ to: publishProfile.to,
297
+ calldata: publishProfile.calldata,
298
+ });
299
+ await waitForEntityName(createSpace.spaceEntityId, spaceId, 'E2E API Surface Personal Space');
300
+ const setTopic = geo.personalSpaces.setTopic({
301
+ spaceId,
302
+ topicId: createSpace.spaceEntityId,
303
+ });
304
+ await sendTransactionAndWait({ account, publicClient, walletClient }, {
305
+ label: 'set personal space topic',
306
+ to: setTopic.to,
307
+ calldata: setTopic.calldata,
308
+ });
309
+ await waitForSpaceTopicId(spaceId, createSpace.spaceEntityId);
310
+ }
311
+ }
312
+ if (spaceIdHex.toLowerCase() === EMPTY_SPACE_ID.toLowerCase()) {
313
+ throw new Error(`Failed to create personal space for address ${accountAddress}`);
314
+ }
315
+ return {
316
+ spaceIdHex,
317
+ spaceId: hexToUuid(spaceIdHex),
318
+ };
319
+ }
320
+ async function setupWallet() {
321
+ const account = privateKeyToAccount(e2e.privateKey);
322
+ const walletClient = createWalletClient({
323
+ account,
324
+ chain: e2e.chain,
325
+ transport: http(e2e.rpcUrl),
326
+ });
327
+ const publicClient = createPublicClient({
328
+ chain: e2e.chain,
329
+ transport: http(e2e.rpcUrl),
330
+ });
331
+ return { account, publicClient, walletClient };
332
+ }
333
+ async function sendTransactionAndWait({ account, publicClient, walletClient }, { label, to, calldata, value = 0n, }) {
334
+ const [latestNonce, pendingNonce] = await Promise.all([
335
+ publicClient.getTransactionCount({
336
+ address: account.address,
337
+ blockTag: 'latest',
338
+ }),
339
+ publicClient.getTransactionCount({
340
+ address: account.address,
341
+ blockTag: 'pending',
342
+ }),
343
+ ]);
344
+ let nonce = Math.max(latestNonce, pendingNonce, nextNonceByAddress.get(account.address) ?? 0);
345
+ let hash;
346
+ for (let attempt = 0; attempt < 3; attempt++) {
347
+ try {
348
+ hash = await walletClient.sendTransaction({
349
+ account,
350
+ chain: walletClient.chain ?? null,
351
+ to,
352
+ value,
353
+ data: calldata,
354
+ nonce,
355
+ });
356
+ nextNonceByAddress.set(account.address, nonce + 1);
357
+ break;
358
+ }
359
+ catch (error) {
360
+ if (!String(error).toLowerCase().includes('nonce too low') || attempt === 2) {
361
+ throw error;
362
+ }
363
+ nonce += 1;
364
+ nextNonceByAddress.set(account.address, nonce);
365
+ }
366
+ }
367
+ if (!hash) {
368
+ throw new Error(`Failed to send ${label} transaction`);
369
+ }
370
+ const receipt = await publicClient.waitForTransactionReceipt({ hash });
371
+ expect(receipt.status, `${label} transaction ${hash}`).toBe('success');
372
+ return { hash, receipt };
373
+ }
374
+ let contextPromise;
375
+ let daoContextPromise;
376
+ const nextNonceByAddress = new Map();
377
+ async function getTestContext() {
378
+ contextPromise ??= (async () => {
379
+ const wallet = await setupWallet();
380
+ const { spaceId, spaceIdHex } = await ensurePersonalSpace({
381
+ ...wallet,
382
+ accountAddress: wallet.account.address,
383
+ });
384
+ return {
385
+ ...wallet,
386
+ spaceId,
387
+ spaceIdHex,
388
+ authorSpaceId: spaceId,
389
+ };
390
+ })();
391
+ return contextPromise;
392
+ }
393
+ async function publishOps(context, name, ops, spaceId = context.spaceId) {
394
+ const publish = await geo.personalSpaces.publishEdit({
395
+ name,
396
+ spaceId,
397
+ author: context.authorSpaceId,
398
+ ops,
399
+ });
400
+ await sendTransactionAndWait(context, {
401
+ label: name,
402
+ to: publish.to,
403
+ calldata: publish.calldata,
404
+ });
405
+ return publish;
406
+ }
407
+ async function createIndexedEntity(context, name = uniqueName('E2E New Entity')) {
408
+ const entity = Ops.entities.create({ name });
409
+ await publishOps(context, `Publish ${name}`, entity.ops);
410
+ await waitForEntityName(entity.id, context.spaceId, name);
411
+ return entity;
412
+ }
413
+ async function getDaoContext() {
414
+ daoContextPromise ??= (async () => {
415
+ const context = await getTestContext();
416
+ const daoName = uniqueName('E2E New DAO Space');
417
+ const daoSpace = await geo.daoSpaces.create({
418
+ name: daoName,
419
+ votingSettings: {
420
+ partialPercentageSupportThreshold: 50,
421
+ universalPercentageSupportThreshold: 90,
422
+ flatSupportThreshold: 1,
423
+ quorum: 1,
424
+ durationInDays: 2,
425
+ disableFastPathAccessForNewMembers: true,
426
+ executionGracePeriodInDays: 14,
427
+ },
428
+ initialEditorSpaceIds: [context.spaceIdHex],
429
+ author: context.authorSpaceId,
430
+ });
431
+ const daoCreateTx = await sendTransactionAndWait(context, {
432
+ label: 'create new API DAO space',
433
+ to: daoSpace.to,
434
+ calldata: daoSpace.calldata,
435
+ });
436
+ const daoSpaceAddress = daoCreateTx.receipt.logs.find(log => log.address.toLowerCase() !== daoSpace.to.toLowerCase())?.address;
437
+ if (!daoSpaceAddress) {
438
+ throw new Error('Could not find DAO space address in creation logs');
439
+ }
440
+ const daoSpaceIdHex = await getSpaceIdHex(context.publicClient, daoSpaceAddress);
441
+ expect(daoSpaceIdHex.toLowerCase()).not.toBe(EMPTY_SPACE_ID.toLowerCase());
442
+ const daoSpaceId = hexToUuid(daoSpaceIdHex);
443
+ await waitForEntityName(daoSpace.spaceEntityId, daoSpaceId, daoName);
444
+ await waitForSpaceTopicId(daoSpaceId, daoSpace.spaceEntityId);
445
+ return {
446
+ ...context,
447
+ daoSpaceAddress,
448
+ daoSpaceId,
449
+ daoSpaceIdHex,
450
+ daoSpaceEntityId: daoSpace.spaceEntityId,
451
+ };
452
+ })();
453
+ return daoContextPromise;
454
+ }
455
+ async function createAddMemberProposal(context, label) {
456
+ const proposal = geo.daoSpaces.proposeAddMember({
457
+ authorSpaceId: context.spaceIdHex,
458
+ spaceId: context.daoSpaceIdHex,
459
+ daoSpaceAddress: context.daoSpaceAddress,
460
+ newMemberSpaceId: context.spaceIdHex,
461
+ });
462
+ await sendTransactionAndWait(context, {
463
+ label,
464
+ to: proposal.to,
465
+ calldata: proposal.calldata,
466
+ });
467
+ await waitForProposal(proposal.proposalId, {
468
+ daoSpaceId: context.daoSpaceId,
469
+ proposedBy: context.authorSpaceId,
470
+ votingMode: 'SLOW',
471
+ actionType: 'ADD_MEMBER',
472
+ targetId: context.spaceId,
473
+ });
474
+ return proposal;
475
+ }
476
+ describe.sequential('new API e2e surface', () => {
477
+ it('geo.personalSpaces.hasSpace validates the account space onchain', async () => {
478
+ const context = await getTestContext();
479
+ const hasSpace = await geo.personalSpaces.hasSpace({
480
+ address: context.account.address,
481
+ });
482
+ expect(hasSpace).toBe(true);
483
+ const spaceAddress = (await context.publicClient.readContract({
484
+ address: e2e.contracts.SPACE_REGISTRY_ADDRESS,
485
+ abi: SpaceRegistryAbi,
486
+ functionName: 'spaceIdToAddress',
487
+ args: [context.spaceIdHex],
488
+ }));
489
+ expect(spaceAddress.toLowerCase()).not.toBe(ZERO_ADDRESS.toLowerCase());
490
+ }, TEST_TIMEOUT_MS);
491
+ it('geo.personalSpaces.setTopic updates the indexed space topic and restores the previous topic', async () => {
492
+ const context = await getTestContext();
493
+ const previousTopicId = await readSpaceTopicId(context.spaceId);
494
+ let randomTopicId = generate();
495
+ if (previousTopicId && randomTopicId === previousTopicId) {
496
+ randomTopicId = generate();
497
+ }
498
+ const setRandomTopic = geo.personalSpaces.setTopic({
499
+ spaceId: context.spaceId,
500
+ topicId: randomTopicId,
501
+ });
502
+ try {
503
+ await sendTransactionAndWait(context, {
504
+ label: 'set random personal space topic',
505
+ to: setRandomTopic.to,
506
+ calldata: setRandomTopic.calldata,
507
+ });
508
+ await waitForSpaceTopicId(context.spaceId, randomTopicId);
509
+ }
510
+ finally {
511
+ if (previousTopicId) {
512
+ const restoreTopic = geo.personalSpaces.setTopic({
513
+ spaceId: context.spaceId,
514
+ topicId: previousTopicId,
515
+ });
516
+ await sendTransactionAndWait(context, {
517
+ label: 'restore personal space topic',
518
+ to: restoreTopic.to,
519
+ calldata: restoreTopic.calldata,
520
+ });
521
+ await waitForSpaceTopicId(context.spaceId, previousTopicId);
522
+ }
523
+ }
524
+ }, TEST_TIMEOUT_MS);
525
+ it('geo.api.graphql reads indexed entity state', async () => {
526
+ const context = await getTestContext();
527
+ const entity = await createIndexedEntity(context, uniqueName('E2E New API GraphQL Entity'));
528
+ const data = await queryGraph(entityQuery(entity.id, context.spaceId));
529
+ expect(data.entity?.id).toBe(entity.id);
530
+ }, TEST_TIMEOUT_MS);
531
+ it('geo.storage.uploadCSV uploads CSV data', async () => {
532
+ const csvCid = await geo.storage.uploadCSV(`name,run\nNew API surface,${Date.now().toString(36)}`);
533
+ expect(csvCid).toMatch(/^ipfs:\/\//);
534
+ }, TEST_TIMEOUT_MS);
535
+ it('geo.storage.uploadImage uploads an image and returns dimensions', async () => {
536
+ const uploadedImage = await geo.storage.uploadImage({
537
+ blob: tinyPngBlob(),
538
+ });
539
+ expect(uploadedImage.cid).toMatch(/^ipfs:\/\//);
540
+ expect(uploadedImage.dimensions).toEqual({ width: 1, height: 1 });
541
+ }, TEST_TIMEOUT_MS);
542
+ it('geo.images.create builds publishable image ops', async () => {
543
+ const context = await getTestContext();
544
+ const imageName = uniqueName('E2E New Image');
545
+ const image = await geo.images.create({
546
+ blob: tinyPngBlob(),
547
+ name: imageName,
548
+ description: 'Created by the new API e2e surface test',
549
+ });
550
+ expect(image.cid).toMatch(/^ipfs:\/\//);
551
+ await publishOps(context, 'E2E new API image', image.ops);
552
+ await waitForEntityName(image.id, context.spaceId, imageName);
553
+ }, TEST_TIMEOUT_MS);
554
+ it('geo.personalSpaces.publishEdit publishes ops into an indexed personal space', async () => {
555
+ const context = await getTestContext();
556
+ const entityName = uniqueName('E2E New Publish Edit Entity');
557
+ const entity = Ops.entities.create({ name: entityName });
558
+ await publishOps(context, 'E2E new API personal publish', entity.ops);
559
+ await waitForEntityName(entity.id, context.spaceId, entityName);
560
+ }, TEST_TIMEOUT_MS);
561
+ it('Ops.properties.create creates an indexed property entity', async () => {
562
+ const context = await getTestContext();
563
+ const propertyName = uniqueName('E2E New Property');
564
+ const property = Ops.properties.create({
565
+ name: propertyName,
566
+ dataType: 'TEXT',
567
+ });
568
+ await publishOps(context, 'E2E new API property', property.ops);
569
+ await waitForEntityName(property.id, context.spaceId, propertyName);
570
+ }, TEST_TIMEOUT_MS);
571
+ it('Ops.types.create creates an indexed type entity', async () => {
572
+ const context = await getTestContext();
573
+ const property = Ops.properties.create({
574
+ name: uniqueName('E2E New Type Property'),
575
+ dataType: 'TEXT',
576
+ });
577
+ const typeName = uniqueName('E2E New Type');
578
+ const type = Ops.types.create({
579
+ name: typeName,
580
+ properties: [property.id],
581
+ });
582
+ await publishOps(context, 'E2E new API type', [...property.ops, ...type.ops]);
583
+ await waitForEntityName(type.id, context.spaceId, typeName);
584
+ }, TEST_TIMEOUT_MS);
585
+ it('Ops.entities.create creates an indexed entity', async () => {
586
+ const context = await getTestContext();
587
+ const entityName = uniqueName('E2E New Entity');
588
+ const entity = Ops.entities.create({
589
+ name: entityName,
590
+ description: 'Created through the new API e2e surface test',
591
+ });
592
+ await publishOps(context, 'E2E new API entity', entity.ops);
593
+ await waitForEntityName(entity.id, context.spaceId, entityName);
594
+ }, TEST_TIMEOUT_MS);
595
+ it('Ops.entities.update updates an indexed entity', async () => {
596
+ const context = await getTestContext();
597
+ const entity = await createIndexedEntity(context, uniqueName('E2E New Entity To Update'));
598
+ const updatedName = `${entity.id} updated`;
599
+ const update = Ops.entities.update({
600
+ id: entity.id,
601
+ name: updatedName,
602
+ unset: [{ property: DESCRIPTION_PROPERTY }],
603
+ });
604
+ await publishOps(context, 'E2E new API entity update', update.ops);
605
+ await waitForEntityName(entity.id, context.spaceId, updatedName);
606
+ }, TEST_TIMEOUT_MS);
607
+ it('Ops.relations.create creates an indexed relation', async () => {
608
+ const context = await getTestContext();
609
+ const fromName = uniqueName('E2E New Relation From');
610
+ const toName = uniqueName('E2E New Relation To');
611
+ const from = Ops.entities.create({ name: fromName });
612
+ const to = Ops.entities.create({ name: toName });
613
+ const relation = Ops.relations.create({
614
+ fromEntity: from.id,
615
+ toEntity: to.id,
616
+ type: RELATION_TYPE,
617
+ position: 'a0',
618
+ });
619
+ await publishOps(context, 'E2E new API relation', [...from.ops, ...to.ops, ...relation.ops]);
620
+ const entity = await waitForEntityName(from.id, context.spaceId, fromName);
621
+ expect(entity?.relationsList.map(item => item.id)).toContain(relation.id);
622
+ }, TEST_TIMEOUT_MS);
623
+ it('Ops.relations.update keeps an updated relation indexed', async () => {
624
+ const context = await getTestContext();
625
+ const fromName = uniqueName('E2E New Relation Update From');
626
+ const from = Ops.entities.create({ name: fromName });
627
+ const to = Ops.entities.create({
628
+ name: uniqueName('E2E New Relation Update To'),
629
+ });
630
+ const relation = Ops.relations.create({
631
+ fromEntity: from.id,
632
+ toEntity: to.id,
633
+ type: RELATION_TYPE,
634
+ position: 'a0',
635
+ });
636
+ const update = Ops.relations.update({
637
+ id: relation.id,
638
+ position: 'a1',
639
+ });
640
+ await publishOps(context, 'E2E new API relation update', [
641
+ ...from.ops,
642
+ ...to.ops,
643
+ ...relation.ops,
644
+ ...update.ops,
645
+ ]);
646
+ const entity = await waitForEntityName(from.id, context.spaceId, fromName);
647
+ expect(entity?.relationsList.map(item => item.id)).toContain(relation.id);
648
+ }, TEST_TIMEOUT_MS);
649
+ it('Ops.relations.delete removes an indexed relation', async () => {
650
+ const context = await getTestContext();
651
+ const fromName = uniqueName('E2E New Relation Delete From');
652
+ const from = Ops.entities.create({ name: fromName });
653
+ const to = Ops.entities.create({
654
+ name: uniqueName('E2E New Relation Delete To'),
655
+ });
656
+ const relation = Ops.relations.create({
657
+ fromEntity: from.id,
658
+ toEntity: to.id,
659
+ type: RELATION_TYPE,
660
+ });
661
+ await publishOps(context, 'E2E new API relation delete setup', [...from.ops, ...to.ops, ...relation.ops]);
662
+ await waitForEntityName(from.id, context.spaceId, fromName);
663
+ const deleteRelation = Ops.relations.delete({ id: relation.id });
664
+ await publishOps(context, 'E2E new API relation delete', deleteRelation.ops);
665
+ await waitFor(`relation ${relation.id} deletion`, () => queryGraph(entityQuery(from.id, context.spaceId)), value => !(value.entity?.relationsList ?? []).some(item => item.id === relation.id));
666
+ }, TEST_TIMEOUT_MS);
667
+ it('Ops.proposalReviews.create creates an indexed proposal review', async () => {
668
+ const context = await getTestContext();
669
+ const proposal = await createIndexedEntity(context, uniqueName('E2E New Reviewed Proposal'));
670
+ const reviewName = uniqueName('E2E New Proposal Review');
671
+ const review = Ops.proposalReviews.create({
672
+ proposal: { id: proposal.id, name: reviewName },
673
+ pass: true,
674
+ content: 'The proposal looks good.',
675
+ completeness: 1,
676
+ accuracy: 0.8,
677
+ skill: 0.8,
678
+ effort: 0.6,
679
+ });
680
+ await publishOps(context, 'E2E new API proposal review', review.ops);
681
+ await waitForEntityName(review.id, context.spaceId, reviewName);
682
+ }, TEST_TIMEOUT_MS);
683
+ it('Ops.proposalReviews.update updates an indexed proposal review', async () => {
684
+ const context = await getTestContext();
685
+ const proposal = await createIndexedEntity(context, uniqueName('E2E New Reviewed Proposal Update'));
686
+ const reviewName = uniqueName('E2E New Proposal Review Update');
687
+ const review = Ops.proposalReviews.create({
688
+ proposal: { id: proposal.id, name: reviewName },
689
+ pass: true,
690
+ content: 'The proposal looks good.',
691
+ });
692
+ await publishOps(context, 'E2E new API proposal review setup', review.ops);
693
+ await waitForEntityName(review.id, context.spaceId, reviewName);
694
+ const reviewUpdate = Ops.proposalReviews.update({
695
+ proposalReviewId: review.id,
696
+ pass: false,
697
+ content: 'Updated review content.',
698
+ });
699
+ await publishOps(context, 'E2E new API proposal review update', reviewUpdate.ops);
700
+ await waitForEntityName(review.id, context.spaceId, reviewName);
701
+ }, TEST_TIMEOUT_MS);
702
+ it('geo.comments.create creates indexed comments with reply-to chains', async () => {
703
+ const context = await getTestContext();
704
+ const entity = await createIndexedEntity(context, uniqueName('E2E New Commented Entity'));
705
+ const commentAContent = 'New API comment A on the entity';
706
+ const commentA = await geo.comments.create({
707
+ content: commentAContent,
708
+ replyTo: { entityId: entity.id, spaceId: context.spaceId },
709
+ });
710
+ expect(filterReplyToRelations(commentA.ops)).toHaveLength(1);
711
+ await publishOps(context, 'E2E new API comment A', commentA.ops);
712
+ await waitForEntityName(commentA.id, context.spaceId, deriveCommentName(commentAContent));
713
+ await waitForReplyToRelations(commentA.id, [entity.id]);
714
+ const commentBContent = 'New API comment B on comment A';
715
+ const commentB = await geo.comments.create({
716
+ content: commentBContent,
717
+ replyTo: { entityId: commentA.id, spaceId: context.spaceId },
718
+ });
719
+ expect(filterReplyToRelations(commentB.ops)).toHaveLength(2);
720
+ await publishOps(context, 'E2E new API comment B', commentB.ops);
721
+ await waitForEntityName(commentB.id, context.spaceId, deriveCommentName(commentBContent));
722
+ await waitForReplyToRelations(commentB.id, [commentA.id, entity.id]);
723
+ }, TEST_TIMEOUT_MS);
724
+ it('geo.comments.update updates an indexed comment', async () => {
725
+ const context = await getTestContext();
726
+ const entity = await createIndexedEntity(context, uniqueName('E2E New Comment Update Entity'));
727
+ const comment = await geo.comments.create({
728
+ content: 'New API comment before update',
729
+ replyTo: { entityId: entity.id, spaceId: context.spaceId },
730
+ });
731
+ await publishOps(context, 'E2E new API comment update setup', comment.ops);
732
+ await waitForEntityName(comment.id, context.spaceId, deriveCommentName('New API comment before update'));
733
+ const updatedContent = 'New API comment after update';
734
+ const update = geo.comments.update({
735
+ id: comment.id,
736
+ content: updatedContent,
737
+ resolved: true,
738
+ });
739
+ await publishOps(context, 'E2E new API comment update', update.ops);
740
+ await waitForEntityName(comment.id, context.spaceId, deriveCommentName(updatedContent));
741
+ }, TEST_TIMEOUT_MS);
742
+ it('geo.entities.delete removes indexed entity values and relations', async () => {
743
+ const context = await getTestContext();
744
+ const related = await createIndexedEntity(context, uniqueName('E2E New Delete Related Entity'));
745
+ const deleteContextName = uniqueName('E2E New Entity To Delete');
746
+ const deleteContextEntity = Ops.entities.create({
747
+ name: deleteContextName,
748
+ });
749
+ const deleteContextRelation = Ops.relations.create({
750
+ fromEntity: deleteContextEntity.id,
751
+ toEntity: related.id,
752
+ type: RELATION_TYPE,
753
+ });
754
+ await publishOps(context, 'E2E new API create entity for deletion', [
755
+ ...deleteContextEntity.ops,
756
+ ...deleteContextRelation.ops,
757
+ ]);
758
+ await waitForEntityName(deleteContextEntity.id, context.spaceId, deleteContextName);
759
+ const deleteResult = await geo.entities.delete({
760
+ id: deleteContextEntity.id,
761
+ spaceId: context.spaceId,
762
+ });
763
+ expect(deleteResult.ops.length).toBeGreaterThan(0);
764
+ await publishOps(context, 'E2E new API delete entity', deleteResult.ops);
765
+ await waitForEntityDeleted(deleteContextEntity.id, context.spaceId);
766
+ }, TEST_TIMEOUT_MS);
767
+ it('geo.entityVotes.upvote submits and indexes an upvote', async () => {
768
+ const context = await getTestContext();
769
+ const entity = await createIndexedEntity(context, uniqueName('E2E New Upvoted Entity'));
770
+ const upvote = geo.entityVotes.upvote({
771
+ authorSpaceId: context.authorSpaceId,
772
+ spaceId: context.spaceId,
773
+ entityId: entity.id,
774
+ });
775
+ await sendTransactionAndWait(context, {
776
+ label: 'E2E new API upvote entity',
777
+ to: upvote.to,
778
+ calldata: upvote.calldata,
779
+ });
780
+ await waitForEntityVote(entity.id, context.authorSpaceId, context.spaceId, votes => votes.some(vote => vote.vote === 0));
781
+ }, TEST_TIMEOUT_MS);
782
+ it('geo.entityVotes.downvote submits and indexes a downvote', async () => {
783
+ const context = await getTestContext();
784
+ const entity = await createIndexedEntity(context, uniqueName('E2E New Downvoted Entity'));
785
+ const downvote = geo.entityVotes.downvote({
786
+ authorSpaceId: context.authorSpaceId,
787
+ spaceId: context.spaceId,
788
+ entityId: entity.id,
789
+ });
790
+ await sendTransactionAndWait(context, {
791
+ label: 'E2E new API downvote entity',
792
+ to: downvote.to,
793
+ calldata: downvote.calldata,
794
+ });
795
+ await waitForEntityVote(entity.id, context.authorSpaceId, context.spaceId, votes => votes.some(vote => vote.vote === 1));
796
+ }, TEST_TIMEOUT_MS);
797
+ it('geo.entityVotes.withdraw removes an indexed entity vote', async () => {
798
+ const context = await getTestContext();
799
+ const entity = await createIndexedEntity(context, uniqueName('E2E New Vote Withdraw Entity'));
800
+ const upvote = geo.entityVotes.upvote({
801
+ authorSpaceId: context.authorSpaceId,
802
+ spaceId: context.spaceId,
803
+ entityId: entity.id,
804
+ });
805
+ await sendTransactionAndWait(context, {
806
+ label: 'E2E new API upvote before withdraw',
807
+ to: upvote.to,
808
+ calldata: upvote.calldata,
809
+ });
810
+ await waitForEntityVote(entity.id, context.authorSpaceId, context.spaceId, votes => votes.length > 0);
811
+ const withdraw = geo.entityVotes.withdraw({
812
+ authorSpaceId: context.authorSpaceId,
813
+ spaceId: context.spaceId,
814
+ entityId: entity.id,
815
+ });
816
+ await sendTransactionAndWait(context, {
817
+ label: 'E2E new API withdraw entity vote',
818
+ to: withdraw.to,
819
+ calldata: withdraw.calldata,
820
+ });
821
+ await waitForEntityVote(entity.id, context.authorSpaceId, context.spaceId, votes => votes.some(vote => vote.vote === 2));
822
+ }, TEST_TIMEOUT_MS);
823
+ it('geo.daoSpaces.create creates an indexed DAO space', async () => {
824
+ const dao = await getDaoContext();
825
+ expect(dao.daoSpaceAddress.toLowerCase()).not.toBe(ZERO_ADDRESS.toLowerCase());
826
+ expect(dao.daoSpaceIdHex.toLowerCase()).not.toBe(EMPTY_SPACE_ID.toLowerCase());
827
+ expect(await readSpaceTopicId(dao.daoSpaceId)).toBe(dao.daoSpaceEntityId);
828
+ }, TEST_TIMEOUT_MS);
829
+ it('geo.daoSpaces.proposeEdit creates an indexed publish proposal', async () => {
830
+ const dao = await getDaoContext();
831
+ const daoEntity = Ops.entities.create({
832
+ name: uniqueName('E2E New DAO Proposed Entity'),
833
+ });
834
+ const proposal = await geo.daoSpaces.proposeEdit({
835
+ name: 'E2E new API DAO propose edit',
836
+ ops: daoEntity.ops,
837
+ author: dao.authorSpaceId,
838
+ daoSpaceAddress: dao.daoSpaceAddress,
839
+ callerSpaceId: dao.spaceIdHex,
840
+ daoSpaceId: dao.daoSpaceIdHex,
841
+ votingMode: 'FAST',
842
+ });
843
+ await sendTransactionAndWait(dao, {
844
+ label: 'new API DAO propose edit',
845
+ to: proposal.to,
846
+ calldata: proposal.calldata,
847
+ });
848
+ await waitForProposal(proposal.proposalId, {
849
+ daoSpaceId: dao.daoSpaceId,
850
+ proposedBy: dao.authorSpaceId,
851
+ votingMode: 'FAST',
852
+ actionType: ['PUBLISH', 'UNKNOWN'],
853
+ });
854
+ }, TEST_TIMEOUT_MS);
855
+ it('geo.daoSpaces.proposeAddMember creates an indexed add-member proposal', async () => {
856
+ const dao = await getDaoContext();
857
+ await createAddMemberProposal(dao, 'new API DAO propose add member');
858
+ }, TEST_TIMEOUT_MS);
859
+ it('geo.daoSpaces.proposeRemoveMember creates an indexed remove-member proposal', async () => {
860
+ const dao = await getDaoContext();
861
+ const proposal = geo.daoSpaces.proposeRemoveMember({
862
+ authorSpaceId: dao.spaceIdHex,
863
+ spaceId: dao.daoSpaceIdHex,
864
+ daoSpaceAddress: dao.daoSpaceAddress,
865
+ memberToRemoveSpaceId: dao.spaceIdHex,
866
+ });
867
+ await sendTransactionAndWait(dao, {
868
+ label: 'new API DAO propose remove member',
869
+ to: proposal.to,
870
+ calldata: proposal.calldata,
871
+ });
872
+ await waitForProposal(proposal.proposalId, {
873
+ daoSpaceId: dao.daoSpaceId,
874
+ proposedBy: dao.authorSpaceId,
875
+ votingMode: 'SLOW',
876
+ actionType: 'REMOVE_MEMBER',
877
+ targetId: dao.spaceId,
878
+ });
879
+ }, TEST_TIMEOUT_MS);
880
+ it('geo.daoSpaces.proposeAddEditor creates an indexed add-editor proposal', async () => {
881
+ const dao = await getDaoContext();
882
+ const proposal = geo.daoSpaces.proposeAddEditor({
883
+ authorSpaceId: dao.spaceIdHex,
884
+ spaceId: dao.daoSpaceIdHex,
885
+ daoSpaceAddress: dao.daoSpaceAddress,
886
+ newEditorSpaceId: dao.spaceIdHex,
887
+ });
888
+ await sendTransactionAndWait(dao, {
889
+ label: 'new API DAO propose add editor',
890
+ to: proposal.to,
891
+ calldata: proposal.calldata,
892
+ });
893
+ await waitForProposal(proposal.proposalId, {
894
+ daoSpaceId: dao.daoSpaceId,
895
+ proposedBy: dao.authorSpaceId,
896
+ votingMode: 'SLOW',
897
+ actionType: 'ADD_EDITOR',
898
+ targetId: dao.spaceId,
899
+ });
900
+ }, TEST_TIMEOUT_MS);
901
+ it('geo.daoSpaces.proposeAddEditor rejects FAST voting mode', async () => {
902
+ const dao = await getDaoContext();
903
+ expect(() => geo.daoSpaces.proposeAddEditor({
904
+ authorSpaceId: dao.spaceIdHex,
905
+ spaceId: dao.daoSpaceIdHex,
906
+ daoSpaceAddress: dao.daoSpaceAddress,
907
+ newEditorSpaceId: dao.spaceIdHex,
908
+ votingMode: 'FAST',
909
+ })).toThrow('proposeAddEditor only supports SLOW voting mode');
910
+ }, TEST_TIMEOUT_MS);
911
+ it('geo.daoSpaces.proposeRemoveEditor creates an indexed remove-editor proposal', async () => {
912
+ const dao = await getDaoContext();
913
+ const proposal = geo.daoSpaces.proposeRemoveEditor({
914
+ authorSpaceId: dao.spaceIdHex,
915
+ spaceId: dao.daoSpaceIdHex,
916
+ daoSpaceAddress: dao.daoSpaceAddress,
917
+ editorToRemoveSpaceId: dao.spaceIdHex,
918
+ });
919
+ await sendTransactionAndWait(dao, {
920
+ label: 'new API DAO propose remove editor',
921
+ to: proposal.to,
922
+ calldata: proposal.calldata,
923
+ });
924
+ await waitForProposal(proposal.proposalId, {
925
+ daoSpaceId: dao.daoSpaceId,
926
+ proposedBy: dao.authorSpaceId,
927
+ votingMode: 'SLOW',
928
+ actionType: 'REMOVE_EDITOR',
929
+ targetId: dao.spaceId,
930
+ });
931
+ }, TEST_TIMEOUT_MS);
932
+ it('geo.daoSpaces.proposeRequestMembership creates an indexed membership proposal', async () => {
933
+ const dao = await getDaoContext();
934
+ const proposal = geo.daoSpaces.proposeRequestMembership({
935
+ authorSpaceId: dao.spaceIdHex,
936
+ spaceId: dao.daoSpaceIdHex,
937
+ });
938
+ await sendTransactionAndWait(dao, {
939
+ label: 'new API DAO propose request membership',
940
+ to: proposal.to,
941
+ calldata: proposal.calldata,
942
+ });
943
+ await waitForProposal(proposal.proposalId, {
944
+ daoSpaceId: dao.daoSpaceId,
945
+ proposedBy: dao.daoSpaceId,
946
+ votingMode: 'FAST',
947
+ actionType: 'ADD_MEMBER',
948
+ targetId: dao.authorSpaceId,
949
+ });
950
+ }, TEST_TIMEOUT_MS);
951
+ it('geo.daoSpaces.proposeUpdateVotingSettings creates an indexed update-voting-settings proposal', async () => {
952
+ const dao = await getDaoContext();
953
+ const proposal = geo.daoSpaces.proposeUpdateVotingSettings({
954
+ authorSpaceId: dao.spaceIdHex,
955
+ spaceId: dao.daoSpaceIdHex,
956
+ daoSpaceAddress: dao.daoSpaceAddress,
957
+ votingSettings: {
958
+ partialPercentageSupportThreshold: 50,
959
+ universalPercentageSupportThreshold: 90,
960
+ flatSupportThreshold: 1,
961
+ quorum: 1,
962
+ durationInDays: 2,
963
+ disableFastPathAccessForNewMembers: true,
964
+ executionGracePeriodInDays: 14,
965
+ },
966
+ });
967
+ await sendTransactionAndWait(dao, {
968
+ label: 'new API DAO propose update voting settings',
969
+ to: proposal.to,
970
+ calldata: proposal.calldata,
971
+ });
972
+ await waitForProposal(proposal.proposalId, {
973
+ daoSpaceId: dao.daoSpaceId,
974
+ proposedBy: dao.authorSpaceId,
975
+ votingMode: 'SLOW',
976
+ actionType: 'UPDATE_VOTING_SETTINGS',
977
+ });
978
+ }, TEST_TIMEOUT_MS);
979
+ it('geo.daoSpaces.voteProposal creates an indexed proposal vote', async () => {
980
+ const dao = await getDaoContext();
981
+ const proposal = await createAddMemberProposal(dao, 'new API DAO proposal for vote');
982
+ const vote = geo.daoSpaces.voteProposal({
983
+ authorSpaceId: dao.spaceIdHex,
984
+ spaceId: dao.daoSpaceIdHex,
985
+ proposalId: proposal.proposalId,
986
+ vote: 'YES',
987
+ });
988
+ await sendTransactionAndWait(dao, {
989
+ label: 'new API DAO vote proposal',
990
+ to: vote.to,
991
+ calldata: vote.calldata,
992
+ });
993
+ await waitForProposalVote(proposal.proposalId, dao.authorSpaceId, dao.daoSpaceId, 'YES');
994
+ }, TEST_TIMEOUT_MS);
995
+ it('geo.daoSpaces.executeProposal builds execute calldata for an indexed proposal', async () => {
996
+ const dao = await getDaoContext();
997
+ const proposal = await createAddMemberProposal(dao, 'new API DAO proposal for execute calldata');
998
+ const execute = geo.daoSpaces.executeProposal({
999
+ authorSpaceId: dao.spaceIdHex,
1000
+ spaceId: dao.daoSpaceIdHex,
1001
+ proposalId: proposal.proposalId,
1002
+ });
1003
+ expect(execute.to).toBe(e2e.contracts.SPACE_REGISTRY_ADDRESS);
1004
+ expect(execute.calldata).toMatch(/^0x[0-9a-fA-F]+$/);
1005
+ }, TEST_TIMEOUT_MS);
1006
+ });
1007
+ //# sourceMappingURL=api-surface.e2e.test.js.map