@sdxc/activitypub 0.0.0-pre.1 → 2026.10.9
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.
- package/README.md +49 -3
- package/dist/federation.d.ts +22 -0
- package/dist/federation.js +57 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/inbox-handle.d.ts +18 -0
- package/dist/lib/inbox-handle.js +2 -2
- package/dist/seen-activities.d.ts +30 -0
- package/dist/seen-activities.js +48 -0
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -315,6 +315,25 @@ already serialized. An activity over 120 KB (`MAX_ACTIVITY_BYTES`) fails `too-la
|
|
|
315
315
|
delivery message could carry it, and a failing queue fails a retryable `enqueue`. A retried
|
|
316
316
|
fan-out may queue some deliveries twice, which receivers absorb by activity id.
|
|
317
317
|
|
|
318
|
+
#### `federation.approve(follower: string): Promise<Result<void, ActivityPubError>>`
|
|
319
|
+
|
|
320
|
+
Accepts a follower a `"pending"` decision held back: it is stored as `accepted`, so it joins the
|
|
321
|
+
followers collection and every later fan-out, and an `Accept` of its stored Follow is queued to
|
|
322
|
+
its inbox. Approving an accepted follower sends the same Accept again, so a retry is safe.
|
|
323
|
+
|
|
324
|
+
#### `federation.reject(follower: string): Promise<Result<void, ActivityPubError>>`
|
|
325
|
+
|
|
326
|
+
Refuses a follower, pending or accepted: a `Reject` of its stored Follow is queued to its inbox,
|
|
327
|
+
then the follower is removed. It stays stored until the Reject is queued, so retrying a failed
|
|
328
|
+
call still sends it.
|
|
329
|
+
|
|
330
|
+
Both answer `not-found` for an actor that does not follow the local actor, and a retryable
|
|
331
|
+
`store` or `enqueue` failure when the store or the queue fails.
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
let approved = await federation.approve("https://mastodon.social/users/alice");
|
|
335
|
+
```
|
|
336
|
+
|
|
318
337
|
#### `federation.lookup(handle: string): Promise<Result<ActivityPub.Actor, ActivityPubFetchError>>`
|
|
319
338
|
|
|
320
339
|
Resolves `@user@host`, `user@host` or `acct:user@host` the way Mastodon does: WebFinger on the
|
|
@@ -551,7 +570,7 @@ Every error extends `ActivityPubError`, which carries a `code` and a `retryable`
|
|
|
551
570
|
### Storage
|
|
552
571
|
|
|
553
572
|
The package keeps no state of its own. It calls four interfaces, exported as types, and the
|
|
554
|
-
app implements them over its own database
|
|
573
|
+
app implements them over its own database, or uses `CacheSeenActivities` for `SeenActivities`:
|
|
555
574
|
|
|
556
575
|
- `FollowerStore`: followers keyed by `(actor, id)`. `put` inserts or replaces, so a repeated
|
|
557
576
|
Follow refreshes the inbox and `followId`. `list`, `count` and `inboxes` see accepted
|
|
@@ -563,6 +582,33 @@ app implements them over its own database:
|
|
|
563
582
|
- `LocalObjects`: `find(id)` answers the object the app serves under `id`, or `null`.
|
|
564
583
|
- `KeyProvider`: `keysOf(actor)` answers a hosted actor's `ActorKeys`, or `null`.
|
|
565
584
|
|
|
585
|
+
### `CacheSeenActivities`
|
|
586
|
+
|
|
587
|
+
A shipped `SeenActivities` over any `@sdxc/cache` `Cache`, so an app that already caches remote
|
|
588
|
+
documents in Workers KV can hold its claims there too.
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
import { CacheSeenActivities, Federation } from "@sdxc/activitypub";
|
|
592
|
+
import { WorkerKVCache } from "@sdxc/cache/worker-kv";
|
|
593
|
+
|
|
594
|
+
let cache = new WorkerKVCache(env.CACHE);
|
|
595
|
+
|
|
596
|
+
let federation = new Federation({
|
|
597
|
+
// ...
|
|
598
|
+
stores: { followers, seen: new CacheSeenActivities(cache), objects },
|
|
599
|
+
cache,
|
|
600
|
+
});
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
- Claims live under `activitypub:seen:<id>`, apart from anything else the cache holds.
|
|
604
|
+
- A claim's TTL is rounded up to whole seconds and held for at least 60, the shortest
|
|
605
|
+
expiration Workers KV accepts, so a TTL under a minute is stretched rather than refused.
|
|
606
|
+
- `claim` fails with a `RangeError` for a TTL that is not a positive duration, and with the
|
|
607
|
+
cache's error when the key cannot be read. A refused write still answers `true`: the
|
|
608
|
+
activity is processed, and a redelivery is processed again.
|
|
609
|
+
- A claim reads the key and writes it when missing, so two deliveries racing through different
|
|
610
|
+
isolates can both win. Every handler is idempotent, which makes that harmless.
|
|
611
|
+
|
|
566
612
|
### `@sdxc/activitypub/testing`
|
|
567
613
|
|
|
568
614
|
In-memory implementations of every storage interface, and Vitest suites an app registers
|
|
@@ -637,8 +683,8 @@ export default createJobHandler(definitions.federation, async (ctx) => {
|
|
|
637
683
|
let processed = await federationFor(storesOf(ctx)).process(ctx.input, { attempts: ctx.attempts });
|
|
638
684
|
if (isFailure(processed)) {
|
|
639
685
|
if (processed.error.retryable)
|
|
640
|
-
ctx.retry({ delay: processed.error.delay, cause: processed.error });
|
|
641
|
-
ctx.ack(processed.error.message);
|
|
686
|
+
return ctx.retry({ delay: processed.error.delay, cause: processed.error });
|
|
687
|
+
return ctx.ack(processed.error.message);
|
|
642
688
|
}
|
|
643
689
|
});
|
|
644
690
|
```
|
package/dist/federation.d.ts
CHANGED
|
@@ -130,6 +130,28 @@ export declare class Federation {
|
|
|
130
130
|
* @example await federation.publish({ id: `${post.url}#create`, type: "Create", actor: ACTOR_ID, object: article, to: [PUBLIC], cc: [FOLLOWERS_ID] });
|
|
131
131
|
*/
|
|
132
132
|
publish(activity: ActivityPub.Draft<ActivityPub.Activity> | string): Promise<Result<void, ActivityPubError>>;
|
|
133
|
+
/**
|
|
134
|
+
* Accepts a follower held `pending` by a manual-approval decision: it joins the
|
|
135
|
+
* followers collection and fan-outs, and an `Accept` of its stored Follow is queued to
|
|
136
|
+
* its inbox. Approving an accepted follower re-sends the same Accept, so a retry is safe.
|
|
137
|
+
*
|
|
138
|
+
* @param follower - The remote actor's id.
|
|
139
|
+
* @returns `not-found` when the actor does not follow the local actor, or a retryable
|
|
140
|
+
* `store` or `enqueue` failure.
|
|
141
|
+
* @example let approved = await federation.approve("https://mastodon.social/users/alice");
|
|
142
|
+
*/
|
|
143
|
+
approve(follower: string): Promise<Result<void, ActivityPubError>>;
|
|
144
|
+
/**
|
|
145
|
+
* Refuses a follower, pending or accepted: a `Reject` of its stored Follow is queued to
|
|
146
|
+
* its inbox, then the follower is forgotten. The follower stays stored until the Reject
|
|
147
|
+
* is queued, so retrying a failed call still sends it.
|
|
148
|
+
*
|
|
149
|
+
* @param follower - The remote actor's id.
|
|
150
|
+
* @returns `not-found` when the actor does not follow the local actor, or a retryable
|
|
151
|
+
* `store` or `enqueue` failure.
|
|
152
|
+
* @example let rejected = await federation.reject("https://mastodon.social/users/alice");
|
|
153
|
+
*/
|
|
154
|
+
reject(follower: string): Promise<Result<void, ActivityPubError>>;
|
|
133
155
|
/**
|
|
134
156
|
* Resolves a handle to its actor the way Mastodon does: WebFinger on the handle's host,
|
|
135
157
|
* the `self` link typed as ActivityStreams, then the actor, whose own host must confirm
|
package/dist/federation.js
CHANGED
|
@@ -5,7 +5,7 @@ import { lookup } from "./discovery.js";
|
|
|
5
5
|
import { ActivityPubError, FederationError } from "./errors.js";
|
|
6
6
|
import { fanOut, MAX_ACTIVITY_BYTES } from "./lib/delivery-fan-out.js";
|
|
7
7
|
import { deliver, DELIVERY_BACKOFF } from "./lib/delivery-post.js";
|
|
8
|
-
import { handle } from "./lib/inbox-handle.js";
|
|
8
|
+
import { answerTo, handle, store } from "./lib/inbox-handle.js";
|
|
9
9
|
import { receive, verifyFetch } from "./lib/inbox-receive.js";
|
|
10
10
|
import { summarize } from "./lib/inbox-summarize.js";
|
|
11
11
|
import { MESSAGE } from "./lib/messages.js";
|
|
@@ -213,6 +213,45 @@ export class Federation {
|
|
|
213
213
|
}
|
|
214
214
|
return this.#enqueue([{ kind: "fanOut", actor: this.#options.actor.id, activity: text }]);
|
|
215
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* Accepts a follower held `pending` by a manual-approval decision: it joins the
|
|
218
|
+
* followers collection and fan-outs, and an `Accept` of its stored Follow is queued to
|
|
219
|
+
* its inbox. Approving an accepted follower re-sends the same Accept, so a retry is safe.
|
|
220
|
+
*
|
|
221
|
+
* @param follower - The remote actor's id.
|
|
222
|
+
* @returns `not-found` when the actor does not follow the local actor, or a retryable
|
|
223
|
+
* `store` or `enqueue` failure.
|
|
224
|
+
* @example let approved = await federation.approve("https://mastodon.social/users/alice");
|
|
225
|
+
*/
|
|
226
|
+
async approve(follower) {
|
|
227
|
+
let found = await this.#follower(follower);
|
|
228
|
+
if (isFailure(found))
|
|
229
|
+
return found;
|
|
230
|
+
let put = store(await this.#options.stores.followers.put({ ...found.data, state: "accepted" }));
|
|
231
|
+
if (isFailure(put))
|
|
232
|
+
return put;
|
|
233
|
+
return this.#answer("Accept", found.data);
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Refuses a follower, pending or accepted: a `Reject` of its stored Follow is queued to
|
|
237
|
+
* its inbox, then the follower is forgotten. The follower stays stored until the Reject
|
|
238
|
+
* is queued, so retrying a failed call still sends it.
|
|
239
|
+
*
|
|
240
|
+
* @param follower - The remote actor's id.
|
|
241
|
+
* @returns `not-found` when the actor does not follow the local actor, or a retryable
|
|
242
|
+
* `store` or `enqueue` failure.
|
|
243
|
+
* @example let rejected = await federation.reject("https://mastodon.social/users/alice");
|
|
244
|
+
*/
|
|
245
|
+
async reject(follower) {
|
|
246
|
+
let found = await this.#follower(follower);
|
|
247
|
+
if (isFailure(found))
|
|
248
|
+
return found;
|
|
249
|
+
let answered = await this.#answer("Reject", found.data);
|
|
250
|
+
if (isFailure(answered))
|
|
251
|
+
return answered;
|
|
252
|
+
let actor = this.#options.actor.id;
|
|
253
|
+
return store(await this.#options.stores.followers.remove(actor, follower));
|
|
254
|
+
}
|
|
216
255
|
/**
|
|
217
256
|
* Resolves a handle to its actor the way Mastodon does: WebFinger on the handle's host,
|
|
218
257
|
* the `self` link typed as ActivityStreams, then the actor, whose own host must confirm
|
|
@@ -403,6 +442,23 @@ export class Federation {
|
|
|
403
442
|
let delay = Math.max(retryAfter ?? 0, this.#backoff.delay(attempts));
|
|
404
443
|
return failure(new FederationError(kind, error, delay));
|
|
405
444
|
}
|
|
445
|
+
/** The local actor's stored follower `id`, or `not-found`. */
|
|
446
|
+
async #follower(id) {
|
|
447
|
+
let found = store(await this.#options.stores.followers.get(this.#options.actor.id, id));
|
|
448
|
+
if (isFailure(found))
|
|
449
|
+
return found;
|
|
450
|
+
if (found.data === null) {
|
|
451
|
+
return failure(new ActivityPubError("not-found", `${id} does not follow the local actor`));
|
|
452
|
+
}
|
|
453
|
+
return success(found.data);
|
|
454
|
+
}
|
|
455
|
+
/** Queues an answer to the follower's stored Follow, delivered to its own inbox. */
|
|
456
|
+
async #answer(type, follower) {
|
|
457
|
+
let actor = this.#options.actor.id;
|
|
458
|
+
let follow = { id: follower.followId, type: "Follow", actor: follower.id, object: actor };
|
|
459
|
+
let activity = stringify(answerTo(type, actor, follow));
|
|
460
|
+
return this.#enqueue([{ kind: "deliver", actor, activity, inbox: follower.inbox }]);
|
|
461
|
+
}
|
|
406
462
|
/**
|
|
407
463
|
* Writes messages to the app's queue, through `enqueueMany` when it has one. A failure,
|
|
408
464
|
* or an exception, answers a retryable `enqueue` error.
|
package/dist/index.d.ts
CHANGED
|
@@ -31,3 +31,4 @@ export { wantsActivity } from "./lib/response.js";
|
|
|
31
31
|
export { EXTENSION_CONTEXT, stringify } from "./lib/stringify.js";
|
|
32
32
|
export { tombstone } from "./lib/tombstone.js";
|
|
33
33
|
export { RemoteResolver } from "./remote.js";
|
|
34
|
+
export { CacheSeenActivities } from "./seen-activities.js";
|
package/dist/index.js
CHANGED
|
@@ -20,3 +20,4 @@ export { wantsActivity } from "./lib/response.js";
|
|
|
20
20
|
export { EXTENSION_CONTEXT, stringify } from "./lib/stringify.js";
|
|
21
21
|
export { tombstone } from "./lib/tombstone.js";
|
|
22
22
|
export { RemoteResolver } from "./remote.js";
|
|
23
|
+
export { CacheSeenActivities } from "./seen-activities.js";
|
|
@@ -117,3 +117,21 @@ export interface HandleOutcome {
|
|
|
117
117
|
* if (isFailure(outcome) && outcome.error.retryable) return ctx.retry({ cause: outcome.error });
|
|
118
118
|
*/
|
|
119
119
|
export declare function handle(input: Received, options: HandleOptions): Promise<Result<HandleOutcome, ActivityPubError>>;
|
|
120
|
+
/**
|
|
121
|
+
* An `Accept` or `Reject` of a Follow, embedding it, since some servers match the answer
|
|
122
|
+
* on the embedded Follow rather than its id. The id derives from the Follow's, so a
|
|
123
|
+
* re-sent answer to the same Follow is the same activity.
|
|
124
|
+
*
|
|
125
|
+
* @param type - Which answer.
|
|
126
|
+
* @param local - The local actor answering.
|
|
127
|
+
* @param follow - The Follow answered.
|
|
128
|
+
*/
|
|
129
|
+
export declare function answerTo(type: "Accept" | "Reject", local: string, follow: ActivityPub.Draft<ActivityPub.Activity>): ActivityPub.Draft<ActivityPub.Activity>;
|
|
130
|
+
/**
|
|
131
|
+
* A store's answer, its failure made retryable, since a store that failed once can answer
|
|
132
|
+
* the next attempt.
|
|
133
|
+
*
|
|
134
|
+
* @param result - What the store answered.
|
|
135
|
+
* @template T - What the store answers.
|
|
136
|
+
*/
|
|
137
|
+
export declare function store<T>(result: Result<T, Error>): Result<T, ActivityPubError>;
|
package/dist/lib/inbox-handle.js
CHANGED
|
@@ -528,7 +528,7 @@ class Run {
|
|
|
528
528
|
* @param local - The local actor answering.
|
|
529
529
|
* @param follow - The Follow answered.
|
|
530
530
|
*/
|
|
531
|
-
function answerTo(type, local, follow) {
|
|
531
|
+
export function answerTo(type, local, follow) {
|
|
532
532
|
let path = type === "Accept" ? "accepts" : "rejects";
|
|
533
533
|
return {
|
|
534
534
|
id: `${local}#${path}/${encodeURIComponent(follow.id)}`,
|
|
@@ -562,7 +562,7 @@ async function settle(called) {
|
|
|
562
562
|
* @param result - What the store answered.
|
|
563
563
|
* @template T - What the store answers.
|
|
564
564
|
*/
|
|
565
|
-
function store(result) {
|
|
565
|
+
export function store(result) {
|
|
566
566
|
if (!isFailure(result))
|
|
567
567
|
return result;
|
|
568
568
|
return failure(new ActivityPubError("store", result.error.message, { retryable: true, cause: result.error }));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `SeenActivities` store over any `@sdxc/cache` `Cache`, so an app whose remote-document
|
|
3
|
+
* cache already sits on Workers KV can remember processed activity ids in the same place.
|
|
4
|
+
* Best effort by contract: every handler is idempotent, so a lost claim only costs work.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
import type { Cache } from "@sdxc/cache";
|
|
10
|
+
import type { DurationInput } from "@sdxc/duration";
|
|
11
|
+
import type { Result } from "@sdxc/result";
|
|
12
|
+
import type { SeenActivities } from "./store.js";
|
|
13
|
+
/**
|
|
14
|
+
* Claims activity ids in a `Cache` under `activitypub:seen:<id>`. A claim reads the key and
|
|
15
|
+
* writes it when missing, so two deliveries racing through different isolates can both win;
|
|
16
|
+
* the handlers' idempotency covers that.
|
|
17
|
+
*
|
|
18
|
+
* @example new CacheSeenActivities(new WorkerKVCache(env.CACHE))
|
|
19
|
+
*/
|
|
20
|
+
export declare class CacheSeenActivities implements SeenActivities {
|
|
21
|
+
#private;
|
|
22
|
+
/** @param cache Where claims are held until their TTL passes. */
|
|
23
|
+
constructor(cache: Cache);
|
|
24
|
+
/**
|
|
25
|
+
* `true` when `id` was not claimed within its TTL, held from now for `ttl` rounded up to
|
|
26
|
+
* whole seconds and at least a minute. A cache that cannot be read is a failure; one that
|
|
27
|
+
* refuses the write still answers `true`, since processing again is harmless.
|
|
28
|
+
*/
|
|
29
|
+
claim(id: string, ttl: DurationInput): Promise<Result<boolean, Error>>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `SeenActivities` store over any `@sdxc/cache` `Cache`, so an app whose remote-document
|
|
3
|
+
* cache already sits on Workers KV can remember processed activity ids in the same place.
|
|
4
|
+
* Best effort by contract: every handler is idempotent, so a lost claim only costs work.
|
|
5
|
+
*
|
|
6
|
+
* @author [Sergio Xalambrí](https://sergiodxa.com)
|
|
7
|
+
* @copyright Sergio Xalambrí 2026
|
|
8
|
+
*/
|
|
9
|
+
import { toMs } from "@sdxc/duration";
|
|
10
|
+
import { failure, isFailure, success } from "@sdxc/result";
|
|
11
|
+
/** Keeps the claims apart from anything else the cache holds. */
|
|
12
|
+
const KEY_PREFIX = "activitypub:seen:";
|
|
13
|
+
/** Workers KV refuses an expiration under a minute, so a shorter claim is held for one. */
|
|
14
|
+
const MIN_TTL_SECONDS = 60;
|
|
15
|
+
/**
|
|
16
|
+
* Claims activity ids in a `Cache` under `activitypub:seen:<id>`. A claim reads the key and
|
|
17
|
+
* writes it when missing, so two deliveries racing through different isolates can both win;
|
|
18
|
+
* the handlers' idempotency covers that.
|
|
19
|
+
*
|
|
20
|
+
* @example new CacheSeenActivities(new WorkerKVCache(env.CACHE))
|
|
21
|
+
*/
|
|
22
|
+
export class CacheSeenActivities {
|
|
23
|
+
#cache;
|
|
24
|
+
/** @param cache Where claims are held until their TTL passes. */
|
|
25
|
+
constructor(cache) {
|
|
26
|
+
this.#cache = cache;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* `true` when `id` was not claimed within its TTL, held from now for `ttl` rounded up to
|
|
30
|
+
* whole seconds and at least a minute. A cache that cannot be read is a failure; one that
|
|
31
|
+
* refuses the write still answers `true`, since processing again is harmless.
|
|
32
|
+
*/
|
|
33
|
+
async claim(id, ttl) {
|
|
34
|
+
let ms = toMs(ttl);
|
|
35
|
+
if (!Number.isFinite(ms) || ms <= 0) {
|
|
36
|
+
return failure(new RangeError(`"${String(ttl)}" is not a duration a claim can last.`));
|
|
37
|
+
}
|
|
38
|
+
let key = `${KEY_PREFIX}${id}`;
|
|
39
|
+
let held = await this.#cache.read(key);
|
|
40
|
+
if (isFailure(held))
|
|
41
|
+
return held;
|
|
42
|
+
if (held.data !== null)
|
|
43
|
+
return success(false);
|
|
44
|
+
let seconds = Math.max(MIN_TTL_SECONDS, Math.ceil(ms / 1000));
|
|
45
|
+
await this.#cache.write(key, Date.now(), { ttl: seconds });
|
|
46
|
+
return success(true);
|
|
47
|
+
}
|
|
48
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdxc/activitypub",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2026.10.9",
|
|
4
4
|
"description": "ActivityPub protocol logic: vocabulary, actors, discovery, a verified inbox and signed delivery",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,18 +10,18 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@sdxc/backoff": "2026.10.6",
|
|
13
|
-
"@sdxc/cache": "2026.10.
|
|
14
|
-
"@sdxc/crypto": "2026.10.
|
|
15
|
-
"@sdxc/digest-fields": "
|
|
13
|
+
"@sdxc/cache": "2026.10.9",
|
|
14
|
+
"@sdxc/crypto": "2026.10.9",
|
|
15
|
+
"@sdxc/digest-fields": "2026.10.9",
|
|
16
16
|
"@sdxc/duration": "2026.10.6",
|
|
17
17
|
"@sdxc/html": "2026.10.6",
|
|
18
|
-
"@sdxc/http": "2026.10.
|
|
19
|
-
"@sdxc/http-signatures": "
|
|
20
|
-
"@sdxc/logger": "2026.10.
|
|
21
|
-
"@sdxc/outbound": "2026.10.
|
|
18
|
+
"@sdxc/http": "2026.10.9",
|
|
19
|
+
"@sdxc/http-signatures": "2026.10.9",
|
|
20
|
+
"@sdxc/logger": "2026.10.9",
|
|
21
|
+
"@sdxc/outbound": "2026.10.9",
|
|
22
22
|
"@sdxc/result": "2026.10.6",
|
|
23
23
|
"@sdxc/validate": "2026.10.6",
|
|
24
|
-
"@sdxc/well-known": "2026.10.
|
|
24
|
+
"@sdxc/well-known": "2026.10.9",
|
|
25
25
|
"@standard-schema/spec": "^1.1.0",
|
|
26
26
|
"remix": "3.0.0"
|
|
27
27
|
},
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"optional": true
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "64fa732ed0a8ca8693a35aeef6345ae5be2534ef",
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|