@swarm-tips/client 0.0.0 → 0.1.1
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 +17 -1
- package/SBOM.spdx.json +1558 -0
- package/dist/cli/swarm-tx.d.ts +2 -0
- package/dist/cli/swarm-tx.js +46 -0
- package/dist/cli/vow-verify.d.ts +2 -0
- package/dist/cli/vow-verify.js +69 -0
- package/dist/contracts/generated/coordination_game.d.ts +6107 -0
- package/dist/contracts/generated/coordination_game.js +1 -0
- package/dist/contracts/generated/shillbot.d.ts +4209 -0
- package/dist/contracts/generated/shillbot.js +1 -0
- package/dist/contracts/idl/coordination_game.json +6101 -0
- package/dist/contracts/idl/shillbot.json +4203 -0
- package/dist/contracts/index.d.ts +33 -0
- package/dist/contracts/index.js +84 -0
- package/dist/coordination-game/battery.d.ts +40 -0
- package/dist/coordination-game/battery.js +82 -0
- package/dist/coordination-game/index.d.ts +4 -0
- package/dist/coordination-game/index.js +3 -0
- package/dist/coordination-game/outcome-oracle.d.ts +53 -0
- package/dist/coordination-game/outcome-oracle.js +136 -0
- package/dist/evm/index.d.ts +3 -0
- package/dist/evm/index.js +6 -0
- package/dist/evm/sign.d.ts +12 -0
- package/dist/evm/sign.js +23 -0
- package/dist/evm/testing.d.ts +21 -0
- package/dist/evm/testing.js +34 -0
- package/dist/evm/tx.d.ts +27 -0
- package/dist/evm/tx.js +18 -0
- package/dist/evm/wallet.d.ts +58 -0
- package/dist/evm/wallet.js +182 -0
- package/dist/inbox/client.d.ts +189 -0
- package/dist/inbox/client.js +334 -0
- package/dist/inbox/index.d.ts +2 -0
- package/dist/inbox/index.js +4 -0
- package/dist/inbox/solana.d.ts +11 -0
- package/dist/inbox/solana.js +13 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/shillbot/index.d.ts +115 -0
- package/dist/shillbot/index.js +327 -0
- package/dist/shillbot/payout-oracle.d.ts +63 -0
- package/dist/shillbot/payout-oracle.js +172 -0
- package/dist/shillbot/switchboard.d.ts +27 -0
- package/dist/shillbot/switchboard.js +205 -0
- package/dist/vow/decoders/extension.d.ts +62 -0
- package/dist/vow/decoders/extension.js +48 -0
- package/dist/vow/decoders/shillbot.d.ts +59 -0
- package/dist/vow/decoders/shillbot.js +130 -0
- package/dist/vow/discriminator.d.ts +12 -0
- package/dist/vow/discriminator.js +25 -0
- package/dist/vow/index.d.ts +7 -0
- package/dist/vow/index.js +5 -0
- package/dist/vow/schema.d.ts +14 -0
- package/dist/vow/schema.js +132 -0
- package/dist/vow/types.d.ts +107 -0
- package/dist/vow/types.js +1 -0
- package/dist/vow/verify.d.ts +23 -0
- package/dist/vow/verify.js +145 -0
- package/package.json +99 -6
- package/index.js +0 -1
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
/**
|
|
3
|
+
* Step 1 — schema check. Implements the well-formed-per-type table
|
|
4
|
+
* from `docs/specs/vow-v1.md` §4. Returns the first failure reason
|
|
5
|
+
* encountered, or null if the schema check passes.
|
|
6
|
+
*
|
|
7
|
+
* Pure — no RPC, no I/O.
|
|
8
|
+
*/
|
|
9
|
+
export function checkSchema(input) {
|
|
10
|
+
if (typeof input !== "object" || input === null || Array.isArray(input)) {
|
|
11
|
+
return "schema_invalid:<root>";
|
|
12
|
+
}
|
|
13
|
+
const a = input;
|
|
14
|
+
if (a.version !== "vow/v1")
|
|
15
|
+
return "schema_invalid:version";
|
|
16
|
+
if (!isPubkey(a.program_id))
|
|
17
|
+
return "schema_invalid:program_id";
|
|
18
|
+
if (!isPubkey(a.account))
|
|
19
|
+
return "schema_invalid:account";
|
|
20
|
+
if (!isPubkey(a.client))
|
|
21
|
+
return "schema_invalid:client";
|
|
22
|
+
if (!isPubkey(a.agent))
|
|
23
|
+
return "schema_invalid:agent";
|
|
24
|
+
if (a.oracle_feed !== null && !isPubkey(a.oracle_feed)) {
|
|
25
|
+
return "schema_invalid:oracle_feed";
|
|
26
|
+
}
|
|
27
|
+
if (a.network !== "mainnet" && a.network !== "devnet") {
|
|
28
|
+
return "schema_invalid:network";
|
|
29
|
+
}
|
|
30
|
+
if (!isU8Number(a.platform))
|
|
31
|
+
return "schema_invalid:platform";
|
|
32
|
+
if (!isDecimalU64(a.task_id))
|
|
33
|
+
return "schema_invalid:task_id";
|
|
34
|
+
if (!isDecimalU64(a.composite_score))
|
|
35
|
+
return "schema_invalid:composite_score";
|
|
36
|
+
if (!isDecimalU64(a.score_max))
|
|
37
|
+
return "schema_invalid:score_max";
|
|
38
|
+
if (!isHex32(a.verification_hash))
|
|
39
|
+
return "schema_invalid:verification_hash";
|
|
40
|
+
if (!isHex32(a.content_hash))
|
|
41
|
+
return "schema_invalid:content_hash";
|
|
42
|
+
if (!isHex32(a.content_id_hash))
|
|
43
|
+
return "schema_invalid:content_id_hash";
|
|
44
|
+
if (!isEnumString(a.state))
|
|
45
|
+
return "schema_invalid:state";
|
|
46
|
+
if (!isEnumString(a.account_kind))
|
|
47
|
+
return "schema_invalid:account_kind";
|
|
48
|
+
if (!isRfc3339NoFraction(a.verified_at)) {
|
|
49
|
+
return "schema_invalid:verified_at";
|
|
50
|
+
}
|
|
51
|
+
if (a.extensions !== undefined && !isPlainObject(a.extensions)) {
|
|
52
|
+
return "schema_invalid:extensions";
|
|
53
|
+
}
|
|
54
|
+
if (a.verifier_instructions !== undefined &&
|
|
55
|
+
typeof a.verifier_instructions !== "string") {
|
|
56
|
+
return "schema_invalid:verifier_instructions";
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/** Base58, decodes to exactly 32 bytes. */
|
|
61
|
+
function isPubkey(v) {
|
|
62
|
+
if (typeof v !== "string" || v.length === 0)
|
|
63
|
+
return false;
|
|
64
|
+
try {
|
|
65
|
+
const bytes = new PublicKey(v).toBytes();
|
|
66
|
+
return bytes.length === 32;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/** JSON number in [0, 255]. */
|
|
73
|
+
function isU8Number(v) {
|
|
74
|
+
return typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 255;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Decimal string matching ^[0-9]+$, no leading zeros except the literal
|
|
78
|
+
* "0", fits in u64 (max 18446744073709551615 = 2^64 - 1).
|
|
79
|
+
*/
|
|
80
|
+
function isDecimalU64(v) {
|
|
81
|
+
if (typeof v !== "string")
|
|
82
|
+
return false;
|
|
83
|
+
if (!/^[0-9]+$/.test(v))
|
|
84
|
+
return false;
|
|
85
|
+
if (v.length > 1 && v.startsWith("0"))
|
|
86
|
+
return false;
|
|
87
|
+
// Compare as BigInt to avoid Number precision loss.
|
|
88
|
+
try {
|
|
89
|
+
const n = BigInt(v);
|
|
90
|
+
return n >= 0n && n <= U64_MAX;
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const U64_MAX = (1n << 64n) - 1n;
|
|
97
|
+
/** Lowercase hex of length 64 (= 32 bytes). */
|
|
98
|
+
function isHex32(v) {
|
|
99
|
+
return typeof v === "string" && /^[0-9a-f]{64}$/.test(v);
|
|
100
|
+
}
|
|
101
|
+
/** Non-empty, no whitespace. Lowercase optional (verifiers tolerate). */
|
|
102
|
+
function isEnumString(v) {
|
|
103
|
+
return typeof v === "string" && v.length > 0 && !/\s/.test(v);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* RFC 3339 timestamp with NO fractional seconds. Either `Z` or `+00:00`
|
|
107
|
+
* UTC offset (per spec §3 row for `verified_at`). Verifiers MUST accept
|
|
108
|
+
* both forms.
|
|
109
|
+
*/
|
|
110
|
+
function isRfc3339NoFraction(v) {
|
|
111
|
+
if (typeof v !== "string")
|
|
112
|
+
return false;
|
|
113
|
+
// Anchor pattern: YYYY-MM-DDTHH:MM:SS{Z|+00:00}
|
|
114
|
+
// No fractional seconds (".123" is rejected).
|
|
115
|
+
const pattern = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|\+00:00)$/;
|
|
116
|
+
if (!pattern.test(v))
|
|
117
|
+
return false;
|
|
118
|
+
// Round-trip via Date to confirm the components describe a real
|
|
119
|
+
// moment (rejects e.g. "2026-13-32T25:61:99Z").
|
|
120
|
+
const ms = Date.parse(v);
|
|
121
|
+
return !Number.isNaN(ms);
|
|
122
|
+
}
|
|
123
|
+
function isPlainObject(v) {
|
|
124
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Once `checkSchema` passes, narrow the input to `AasV1Attestation`.
|
|
128
|
+
* Pure runtime cast — no defensive checks (those already ran).
|
|
129
|
+
*/
|
|
130
|
+
export function asV1(a) {
|
|
131
|
+
return a;
|
|
132
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VOW v1 wire format. Mirrors the type table in
|
|
3
|
+
* `docs/specs/vow-v1.md` §3. All u64-typed on-chain values are
|
|
4
|
+
* decimal strings on the wire (per v1 §3 to avoid JS
|
|
5
|
+
* Number.MAX_SAFE_INTEGER truncation).
|
|
6
|
+
*/
|
|
7
|
+
export interface VowV1Attestation {
|
|
8
|
+
version: "vow/v1";
|
|
9
|
+
network: "mainnet" | "devnet";
|
|
10
|
+
program_id: string;
|
|
11
|
+
account: string;
|
|
12
|
+
account_kind: string;
|
|
13
|
+
task_id: string;
|
|
14
|
+
client: string;
|
|
15
|
+
agent: string;
|
|
16
|
+
state: string;
|
|
17
|
+
platform: number;
|
|
18
|
+
composite_score: string;
|
|
19
|
+
score_max: string;
|
|
20
|
+
verified_at: string;
|
|
21
|
+
verification_hash: string;
|
|
22
|
+
content_hash: string;
|
|
23
|
+
content_id_hash: string;
|
|
24
|
+
oracle_feed: string | null;
|
|
25
|
+
verifier_instructions?: string;
|
|
26
|
+
extensions?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Closed taxonomy of failure reasons, matching `docs/specs/vow-v1.md`
|
|
30
|
+
* §4 step-to-reason table. The string is the entire taxonomy — a
|
|
31
|
+
* verifier MUST return one of these strings (with the named field
|
|
32
|
+
* substituted into `field_mismatch:<field>` and `schema_invalid:<field>`).
|
|
33
|
+
*/
|
|
34
|
+
export type FailureReason = `schema_invalid:${string}` | "account_closed" | "owner_mismatch" | "discriminator_mismatch" | `field_mismatch:${string}` | "score_above_max" | "state_invalid";
|
|
35
|
+
/**
|
|
36
|
+
* @deprecated Renamed to {@link VowV1Attestation} when the standard became VOW
|
|
37
|
+
* (Verifiable On-chain Work) on 2026-07-10. The wire format is `vow/v1`; this
|
|
38
|
+
* alias only keeps existing importers compiling and will be removed.
|
|
39
|
+
*/
|
|
40
|
+
export type AasV1Attestation = VowV1Attestation;
|
|
41
|
+
export type Verdict = {
|
|
42
|
+
valid: true;
|
|
43
|
+
attestation: AasV1Attestation;
|
|
44
|
+
checked_at: string;
|
|
45
|
+
rpc_endpoint: string;
|
|
46
|
+
} | {
|
|
47
|
+
valid: false;
|
|
48
|
+
attestation: AasV1Attestation;
|
|
49
|
+
checked_at: string;
|
|
50
|
+
rpc_endpoint: string;
|
|
51
|
+
failure_reason: FailureReason;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Decoded representation of the on-chain account body (sans the 8-byte
|
|
55
|
+
* Anchor discriminator). The reference shillbot decoder lives in
|
|
56
|
+
* `decoders/shillbot.ts`; protocols emit their own decoder for VOW v1
|
|
57
|
+
* conformance. The verifier compares each field below to the
|
|
58
|
+
* attestation's wire-format claim.
|
|
59
|
+
*/
|
|
60
|
+
export interface DecodedAccount {
|
|
61
|
+
task_id: bigint;
|
|
62
|
+
client: string;
|
|
63
|
+
agent: string;
|
|
64
|
+
state: number;
|
|
65
|
+
composite_score: bigint;
|
|
66
|
+
verified_at: bigint;
|
|
67
|
+
verification_hash: string;
|
|
68
|
+
content_hash: string;
|
|
69
|
+
content_id_hash: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Per-protocol pluggable decoder. The verifier calls this with the
|
|
73
|
+
* raw account bytes (after stripping the 8-byte Anchor discriminator)
|
|
74
|
+
* and the attestation's `account_kind` for routing.
|
|
75
|
+
*/
|
|
76
|
+
export type AccountDecoder = (data: Uint8Array, accountKind: string) => DecodedAccount;
|
|
77
|
+
/**
|
|
78
|
+
* Per-protocol state-name resolver. The verifier calls this to map the
|
|
79
|
+
* decoded `state` u8 (e.g. 3) to the published lower-case enum name
|
|
80
|
+
* (e.g. "verified"). Protocols that publish their state enum under a
|
|
81
|
+
* different naming convention can pass a custom resolver.
|
|
82
|
+
*
|
|
83
|
+
* Returns `null` for unknown discriminants — the verifier treats that
|
|
84
|
+
* as `state_invalid`.
|
|
85
|
+
*/
|
|
86
|
+
export type StateResolver = (state: number, accountKind: string) => string | null;
|
|
87
|
+
/**
|
|
88
|
+
* Per-protocol "states valid for attestation" lookup. Per spec §4 step
|
|
89
|
+
* 7, protocols publish which on-chain states are attestable. The
|
|
90
|
+
* verifier rejects an attestation whose `state` isn't in this list.
|
|
91
|
+
*
|
|
92
|
+
* Shillbot exports `["verified"]` (only Verified is attestable; finalize
|
|
93
|
+
* closes the on-chain account, see spec §6 capture window). Other
|
|
94
|
+
* protocols MAY allow multiple states.
|
|
95
|
+
*/
|
|
96
|
+
export type ValidStates = (accountKind: string) => readonly string[];
|
|
97
|
+
/**
|
|
98
|
+
* The three pluggable hooks a non-Shillbot protocol passes when calling
|
|
99
|
+
* the verifier. For Shillbot, `sdk/client/src/vow/decoders/shillbot.ts`
|
|
100
|
+
* exports all three with the canonical Task layout, TaskState enum,
|
|
101
|
+
* and the `["verified"]`-only attestation policy.
|
|
102
|
+
*/
|
|
103
|
+
export interface ProtocolHandler {
|
|
104
|
+
decode: AccountDecoder;
|
|
105
|
+
resolveState: StateResolver;
|
|
106
|
+
validStates: ValidStates;
|
|
107
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Connection } from "@solana/web3.js";
|
|
2
|
+
import type { AasV1Attestation, FailureReason, ProtocolHandler, Verdict } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Step-by-step verifier per `docs/specs/vow-v1.md` §4.
|
|
5
|
+
*
|
|
6
|
+
* Steps split:
|
|
7
|
+
* - `verifyV1Schema` — pure (steps 1, 6).
|
|
8
|
+
* - `verifyV1OnChain` — touches RPC (steps 2-5, 7).
|
|
9
|
+
* - `verifyV1` (this) — full 7-step pipeline.
|
|
10
|
+
*
|
|
11
|
+
* Splitting lets unit tests cover the pure parts without an RPC.
|
|
12
|
+
*/
|
|
13
|
+
export declare function verifyV1(attestation: unknown, protocol: ProtocolHandler, rpc: Connection): Promise<Verdict>;
|
|
14
|
+
/**
|
|
15
|
+
* Pure step 1 + step 6 of the protocol. Returns null on success or
|
|
16
|
+
* the first failure reason.
|
|
17
|
+
*/
|
|
18
|
+
export declare function verifyV1Schema(attestation: unknown): FailureReason | null;
|
|
19
|
+
/**
|
|
20
|
+
* Steps 2-5 + 7 — touches RPC. Returns null on success or the first
|
|
21
|
+
* failure reason.
|
|
22
|
+
*/
|
|
23
|
+
export declare function verifyV1OnChain(a: AasV1Attestation, protocol: ProtocolHandler, rpc: Connection): Promise<FailureReason | null>;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
|
2
|
+
import { anchorDiscriminator, bytesEqual } from "./discriminator.js";
|
|
3
|
+
import { asV1, checkSchema } from "./schema.js";
|
|
4
|
+
/**
|
|
5
|
+
* Step-by-step verifier per `docs/specs/vow-v1.md` §4.
|
|
6
|
+
*
|
|
7
|
+
* Steps split:
|
|
8
|
+
* - `verifyV1Schema` — pure (steps 1, 6).
|
|
9
|
+
* - `verifyV1OnChain` — touches RPC (steps 2-5, 7).
|
|
10
|
+
* - `verifyV1` (this) — full 7-step pipeline.
|
|
11
|
+
*
|
|
12
|
+
* Splitting lets unit tests cover the pure parts without an RPC.
|
|
13
|
+
*/
|
|
14
|
+
export async function verifyV1(attestation, protocol, rpc) {
|
|
15
|
+
const checkedAt = nowRfc3339();
|
|
16
|
+
const rpcEndpoint = rpc.rpcEndpoint;
|
|
17
|
+
const schemaFail = verifyV1Schema(attestation);
|
|
18
|
+
if (schemaFail) {
|
|
19
|
+
return {
|
|
20
|
+
valid: false,
|
|
21
|
+
attestation: attestation,
|
|
22
|
+
checked_at: checkedAt,
|
|
23
|
+
rpc_endpoint: rpcEndpoint,
|
|
24
|
+
failure_reason: schemaFail,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const a = asV1(attestation);
|
|
28
|
+
const onChainFail = await verifyV1OnChain(a, protocol, rpc);
|
|
29
|
+
if (onChainFail) {
|
|
30
|
+
return {
|
|
31
|
+
valid: false,
|
|
32
|
+
attestation: a,
|
|
33
|
+
checked_at: checkedAt,
|
|
34
|
+
rpc_endpoint: rpcEndpoint,
|
|
35
|
+
failure_reason: onChainFail,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
valid: true,
|
|
40
|
+
attestation: a,
|
|
41
|
+
checked_at: checkedAt,
|
|
42
|
+
rpc_endpoint: rpcEndpoint,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Pure step 1 + step 6 of the protocol. Returns null on success or
|
|
47
|
+
* the first failure reason.
|
|
48
|
+
*/
|
|
49
|
+
export function verifyV1Schema(attestation) {
|
|
50
|
+
const schemaFail = checkSchema(attestation);
|
|
51
|
+
if (schemaFail)
|
|
52
|
+
return schemaFail;
|
|
53
|
+
const a = asV1(attestation);
|
|
54
|
+
// Step 6 — domain bound. Both are decimal-string u64 (passed step 1).
|
|
55
|
+
if (BigInt(a.composite_score) > BigInt(a.score_max)) {
|
|
56
|
+
return "score_above_max";
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Steps 2-5 + 7 — touches RPC. Returns null on success or the first
|
|
62
|
+
* failure reason.
|
|
63
|
+
*/
|
|
64
|
+
export async function verifyV1OnChain(a, protocol, rpc) {
|
|
65
|
+
// Step 2 — fetch the on-chain account.
|
|
66
|
+
const accountInfo = await rpc.getAccountInfo(new PublicKey(a.account));
|
|
67
|
+
if (accountInfo === null) {
|
|
68
|
+
return "account_closed";
|
|
69
|
+
}
|
|
70
|
+
const data = new Uint8Array(accountInfo.data);
|
|
71
|
+
// Step 3 — owner check.
|
|
72
|
+
if (accountInfo.owner.toBase58() !== a.program_id) {
|
|
73
|
+
return "owner_mismatch";
|
|
74
|
+
}
|
|
75
|
+
// Step 4 — Anchor discriminator check.
|
|
76
|
+
if (data.length < 8) {
|
|
77
|
+
return "discriminator_mismatch";
|
|
78
|
+
}
|
|
79
|
+
const expectedDisc = anchorDiscriminator(a.account_kind);
|
|
80
|
+
if (!bytesEqual(expectedDisc, data.slice(0, 8))) {
|
|
81
|
+
return "discriminator_mismatch";
|
|
82
|
+
}
|
|
83
|
+
// Step 5 — field equality. Decoder strips the 8-byte discriminator.
|
|
84
|
+
// The decoders THROW on a short/malformed body (shillbot rejects < 286 bytes,
|
|
85
|
+
// extension likewise). Letting that escape turned "this account does not
|
|
86
|
+
// verify" into an exception the caller had to catch, so a truncated or
|
|
87
|
+
// wrong-kind account crashed the verifier instead of returning a verdict.
|
|
88
|
+
// A body we cannot decode IS a schema failure — report it as one.
|
|
89
|
+
let decoded;
|
|
90
|
+
try {
|
|
91
|
+
decoded = protocol.decode(data.slice(8), a.account_kind);
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
95
|
+
return `schema_invalid:${msg}`;
|
|
96
|
+
}
|
|
97
|
+
if (decoded.task_id.toString() !== a.task_id) {
|
|
98
|
+
return "field_mismatch:task_id";
|
|
99
|
+
}
|
|
100
|
+
if (decoded.client !== a.client) {
|
|
101
|
+
return "field_mismatch:client";
|
|
102
|
+
}
|
|
103
|
+
if (decoded.agent !== a.agent) {
|
|
104
|
+
return "field_mismatch:agent";
|
|
105
|
+
}
|
|
106
|
+
if (decoded.composite_score.toString() !== a.composite_score) {
|
|
107
|
+
return "field_mismatch:composite_score";
|
|
108
|
+
}
|
|
109
|
+
if (decoded.verification_hash !== a.verification_hash) {
|
|
110
|
+
return "field_mismatch:verification_hash";
|
|
111
|
+
}
|
|
112
|
+
if (decoded.content_hash !== a.content_hash) {
|
|
113
|
+
return "field_mismatch:content_hash";
|
|
114
|
+
}
|
|
115
|
+
if (decoded.content_id_hash !== a.content_id_hash) {
|
|
116
|
+
return "field_mismatch:content_id_hash";
|
|
117
|
+
}
|
|
118
|
+
// verified_at: chain stores i64 unix seconds; wire is RFC 3339 with
|
|
119
|
+
// no fractional seconds. Convert and compare with ±1 second
|
|
120
|
+
// tolerance (per spec §4 step 5).
|
|
121
|
+
const wireSeconds = Math.floor(Date.parse(a.verified_at) / 1000);
|
|
122
|
+
const chainSeconds = Number(decoded.verified_at);
|
|
123
|
+
if (Math.abs(wireSeconds - chainSeconds) > 1) {
|
|
124
|
+
return "field_mismatch:verified_at";
|
|
125
|
+
}
|
|
126
|
+
// Step 5 (state) — map decoded u8 to the protocol's published name.
|
|
127
|
+
const resolved = protocol.resolveState(decoded.state, a.account_kind);
|
|
128
|
+
if (resolved === null || resolved !== a.state) {
|
|
129
|
+
return "field_mismatch:state";
|
|
130
|
+
}
|
|
131
|
+
// Step 7 — state validity. Spec §4 step 7 says protocols publish
|
|
132
|
+
// which on-chain states are "valid for attestation"; the verifier
|
|
133
|
+
// delegates to the protocol handler's `validStates` to avoid
|
|
134
|
+
// hardcoding any one protocol's policy. Shillbot returns
|
|
135
|
+
// `["verified"]`; other protocols can return larger sets without
|
|
136
|
+
// forking this verifier.
|
|
137
|
+
const valid = protocol.validStates(a.account_kind);
|
|
138
|
+
if (!valid.includes(a.state)) {
|
|
139
|
+
return "state_invalid";
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
function nowRfc3339() {
|
|
144
|
+
return new Date().toISOString().replace(/\.\d+Z$/, "Z");
|
|
145
|
+
}
|
package/package.json
CHANGED
|
@@ -1,17 +1,110 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@swarm-tips/client",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Typed browser and Node.js clients for Swarm Tips, Shillbot, and Coordination Game",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "index.js",
|
|
7
|
-
"
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./shillbot": {
|
|
16
|
+
"types": "./dist/shillbot/index.d.ts",
|
|
17
|
+
"import": "./dist/shillbot/index.js",
|
|
18
|
+
"require": "./dist/shillbot/index.js"
|
|
19
|
+
},
|
|
20
|
+
"./coordination-game": {
|
|
21
|
+
"types": "./dist/coordination-game/index.d.ts",
|
|
22
|
+
"import": "./dist/coordination-game/index.js",
|
|
23
|
+
"require": "./dist/coordination-game/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./evm": {
|
|
26
|
+
"types": "./dist/evm/index.d.ts",
|
|
27
|
+
"import": "./dist/evm/index.js",
|
|
28
|
+
"require": "./dist/evm/index.js"
|
|
29
|
+
},
|
|
30
|
+
"./evm/testing": {
|
|
31
|
+
"types": "./dist/evm/testing.d.ts",
|
|
32
|
+
"import": "./dist/evm/testing.js",
|
|
33
|
+
"require": "./dist/evm/testing.js"
|
|
34
|
+
},
|
|
35
|
+
"./inbox": {
|
|
36
|
+
"types": "./dist/inbox/index.d.ts",
|
|
37
|
+
"import": "./dist/inbox/index.js",
|
|
38
|
+
"require": "./dist/inbox/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./vow": {
|
|
41
|
+
"types": "./dist/vow/index.d.ts",
|
|
42
|
+
"import": "./dist/vow/index.js",
|
|
43
|
+
"require": "./dist/vow/index.js"
|
|
44
|
+
},
|
|
45
|
+
"./idl/shillbot": "./dist/contracts/idl/shillbot.json",
|
|
46
|
+
"./idl/coordination-game": "./dist/contracts/idl/coordination_game.json",
|
|
47
|
+
"./package.json": "./package.json"
|
|
48
|
+
},
|
|
49
|
+
"bin": {
|
|
50
|
+
"swarm-tx": "dist/cli/swarm-tx.js",
|
|
51
|
+
"vow-verify": "dist/cli/vow-verify.js"
|
|
52
|
+
},
|
|
53
|
+
"files": [
|
|
54
|
+
"dist",
|
|
55
|
+
"README.md",
|
|
56
|
+
"SBOM.spdx.json"
|
|
57
|
+
],
|
|
8
58
|
"repository": {
|
|
9
59
|
"type": "git",
|
|
10
60
|
"url": "git+https://github.com/corsur/swarm-tips.git",
|
|
11
61
|
"directory": "sdk/client"
|
|
12
62
|
},
|
|
63
|
+
"homepage": "https://github.com/corsur/swarm-tips/tree/main/sdk/client#readme",
|
|
64
|
+
"bugs": "https://github.com/corsur/swarm-tips/issues",
|
|
13
65
|
"license": "MIT",
|
|
14
66
|
"publishConfig": {
|
|
15
|
-
"access": "public"
|
|
67
|
+
"access": "public",
|
|
68
|
+
"provenance": true
|
|
69
|
+
},
|
|
70
|
+
"dependencies": {
|
|
71
|
+
"@coral-xyz/anchor": "^0.32.1",
|
|
72
|
+
"@solana/web3.js": "^1.98.4",
|
|
73
|
+
"@switchboard-xyz/common": "^5.7.0",
|
|
74
|
+
"bs58": "^6.0.0",
|
|
75
|
+
"buffer": "^6.0.3",
|
|
76
|
+
"js-sha256": "^0.11.0"
|
|
77
|
+
},
|
|
78
|
+
"peerDependencies": {
|
|
79
|
+
"viem": "^2.52.2",
|
|
80
|
+
"wagmi": "^2.19.5"
|
|
81
|
+
},
|
|
82
|
+
"peerDependenciesMeta": {
|
|
83
|
+
"viem": {
|
|
84
|
+
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"wagmi": {
|
|
87
|
+
"optional": true
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"devDependencies": {
|
|
91
|
+
"@types/node": "^20.0.0",
|
|
92
|
+
"esbuild": "^0.25.9",
|
|
93
|
+
"typescript": "^5.7.3",
|
|
94
|
+
"viem": "^2.52.2",
|
|
95
|
+
"vitest": "^2.1.9",
|
|
96
|
+
"wagmi": "^2.19.5"
|
|
97
|
+
},
|
|
98
|
+
"engines": {
|
|
99
|
+
"node": ">=20"
|
|
100
|
+
},
|
|
101
|
+
"scripts": {
|
|
102
|
+
"build": "tsc -p tsconfig.build.json",
|
|
103
|
+
"test": "vitest run",
|
|
104
|
+
"lint": "tsc --noEmit -p tsconfig.json",
|
|
105
|
+
"check:generated": "node scripts/sync-generated.mjs --check",
|
|
106
|
+
"sync:generated": "node scripts/sync-generated.mjs",
|
|
107
|
+
"sbom": "node scripts/generate-sbom.mjs",
|
|
108
|
+
"verify:packed": "node scripts/verify-packed.mjs"
|
|
16
109
|
}
|
|
17
|
-
}
|
|
110
|
+
}
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const CLIENT_VERSION = "0.0.0";
|