@arc402/sdk 0.2.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 (128) hide show
  1. package/README.md +184 -0
  2. package/dist/agent.d.ts +29 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +95 -0
  5. package/dist/agreement.d.ts +57 -0
  6. package/dist/agreement.d.ts.map +1 -0
  7. package/dist/agreement.js +156 -0
  8. package/dist/capability.d.ts +17 -0
  9. package/dist/capability.d.ts.map +1 -0
  10. package/dist/capability.js +19 -0
  11. package/dist/channel.d.ts +39 -0
  12. package/dist/channel.d.ts.map +1 -0
  13. package/dist/channel.js +160 -0
  14. package/dist/coldstart.d.ts +15 -0
  15. package/dist/coldstart.d.ts.map +1 -0
  16. package/dist/coldstart.js +44 -0
  17. package/dist/context.d.ts +2 -0
  18. package/dist/context.d.ts.map +1 -0
  19. package/dist/context.js +2 -0
  20. package/dist/contractinteraction.d.ts +47 -0
  21. package/dist/contractinteraction.d.ts.map +1 -0
  22. package/dist/contractinteraction.js +80 -0
  23. package/dist/contracts.d.ts +27 -0
  24. package/dist/contracts.d.ts.map +1 -0
  25. package/dist/contracts.js +187 -0
  26. package/dist/deliverable.d.ts +118 -0
  27. package/dist/deliverable.d.ts.map +1 -0
  28. package/dist/deliverable.js +156 -0
  29. package/dist/dispute-arbitration.d.ts +42 -0
  30. package/dist/dispute-arbitration.d.ts.map +1 -0
  31. package/dist/dispute-arbitration.js +160 -0
  32. package/dist/governance.d.ts +13 -0
  33. package/dist/governance.d.ts.map +1 -0
  34. package/dist/governance.js +15 -0
  35. package/dist/index.d.ts +31 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +92 -0
  38. package/dist/intent.d.ts +13 -0
  39. package/dist/intent.d.ts.map +1 -0
  40. package/dist/intent.js +26 -0
  41. package/dist/metadata.d.ts +55 -0
  42. package/dist/metadata.d.ts.map +1 -0
  43. package/dist/metadata.js +106 -0
  44. package/dist/migration.d.ts +11 -0
  45. package/dist/migration.d.ts.map +1 -0
  46. package/dist/migration.js +38 -0
  47. package/dist/multiparty.d.ts +56 -0
  48. package/dist/multiparty.d.ts.map +1 -0
  49. package/dist/multiparty.js +86 -0
  50. package/dist/negotiation-guard.d.ts +20 -0
  51. package/dist/negotiation-guard.d.ts.map +1 -0
  52. package/dist/negotiation-guard.js +96 -0
  53. package/dist/negotiation.d.ts +25 -0
  54. package/dist/negotiation.d.ts.map +1 -0
  55. package/dist/negotiation.js +102 -0
  56. package/dist/policy.d.ts +33 -0
  57. package/dist/policy.d.ts.map +1 -0
  58. package/dist/policy.js +72 -0
  59. package/dist/reputation.d.ts +13 -0
  60. package/dist/reputation.d.ts.map +1 -0
  61. package/dist/reputation.js +21 -0
  62. package/dist/session-manager.d.ts +13 -0
  63. package/dist/session-manager.d.ts.map +1 -0
  64. package/dist/session-manager.js +102 -0
  65. package/dist/settlement.d.ts +14 -0
  66. package/dist/settlement.d.ts.map +1 -0
  67. package/dist/settlement.js +35 -0
  68. package/dist/sponsorship.d.ts +17 -0
  69. package/dist/sponsorship.d.ts.map +1 -0
  70. package/dist/sponsorship.js +19 -0
  71. package/dist/trust.d.ts +22 -0
  72. package/dist/trust.d.ts.map +1 -0
  73. package/dist/trust.js +52 -0
  74. package/dist/types.d.ts +391 -0
  75. package/dist/types.d.ts.map +1 -0
  76. package/dist/types.js +119 -0
  77. package/dist/wallet.d.ts +31 -0
  78. package/dist/wallet.d.ts.map +1 -0
  79. package/dist/wallet.js +83 -0
  80. package/dist/watchtower.d.ts +60 -0
  81. package/dist/watchtower.d.ts.map +1 -0
  82. package/dist/watchtower.js +93 -0
  83. package/package.json +30 -0
  84. package/src/agent.ts +122 -0
  85. package/src/agreement.ts +236 -0
  86. package/src/capability.ts +18 -0
  87. package/src/channel.ts +203 -0
  88. package/src/coldstart.ts +52 -0
  89. package/src/context.ts +2 -0
  90. package/src/contractinteraction.d.ts +47 -0
  91. package/src/contractinteraction.d.ts.map +1 -0
  92. package/src/contractinteraction.js +81 -0
  93. package/src/contractinteraction.js.map +1 -0
  94. package/src/contractinteraction.ts +157 -0
  95. package/src/contracts.d.ts +27 -0
  96. package/src/contracts.d.ts.map +1 -0
  97. package/src/contracts.js +188 -0
  98. package/src/contracts.js.map +1 -0
  99. package/src/contracts.ts +186 -0
  100. package/src/deliverable.ts +231 -0
  101. package/src/demos/demo-insurance.ts +148 -0
  102. package/src/demos/demo-multiagent.ts +197 -0
  103. package/src/demos/demo-research.ts +124 -0
  104. package/src/dispute-arbitration.ts +196 -0
  105. package/src/governance.ts +14 -0
  106. package/src/index.ts +31 -0
  107. package/src/intent.ts +22 -0
  108. package/src/metadata.ts +158 -0
  109. package/src/migration.ts +43 -0
  110. package/src/multiparty.ts +132 -0
  111. package/src/negotiation-guard.ts +125 -0
  112. package/src/negotiation.ts +135 -0
  113. package/src/policy.ts +71 -0
  114. package/src/reputation.ts +20 -0
  115. package/src/session-manager.ts +80 -0
  116. package/src/settlement.ts +31 -0
  117. package/src/sponsorship.ts +18 -0
  118. package/src/trust.ts +43 -0
  119. package/src/types.d.ts +391 -0
  120. package/src/types.d.ts.map +1 -0
  121. package/src/types.js +113 -0
  122. package/src/types.js.map +1 -0
  123. package/src/types.ts +484 -0
  124. package/src/wallet.ts +86 -0
  125. package/src/watchtower.ts +124 -0
  126. package/test/negotiation-signing.test.js +157 -0
  127. package/test/sdk.test.js +19 -0
  128. package/tsconfig.json +17 -0
@@ -0,0 +1,124 @@
1
+ import { AbstractSigner, ContractRunner, ethers } from "ethers";
2
+ import type { ChannelState } from "./types";
3
+ import { ChannelClient } from "./channel";
4
+
5
+ const WATCHTOWER_REGISTRY_ABI = [
6
+ "function register(string name, string description, string[] capabilities) external",
7
+ "function getWatchtower(address watchtower) external view returns (tuple(address addr, string name, string description, string[] capabilities, bool active, uint256 registeredAt))",
8
+ "function isRegistered(address watchtower) external view returns (bool)",
9
+ "event WatchtowerRegistered(address indexed watchtower, string name)",
10
+ ] as const;
11
+
12
+ const SESSION_CHANNEL_WATCHTOWER_ABI = [
13
+ "function authorizeWatchtower(bytes32 channelId, address watchtower) external",
14
+ "function revokeWatchtower(bytes32 channelId, address watchtower) external",
15
+ "function submitWatchtowerChallenge(bytes32 channelId, bytes latestState, address beneficiary) external",
16
+ "function isWatchtowerAuthorized(bytes32 channelId, address watchtower) external view returns (bool)",
17
+ ] as const;
18
+
19
+ export interface WatchtowerMetadata {
20
+ name: string;
21
+ description: string;
22
+ capabilities: string[];
23
+ }
24
+
25
+ export interface WatchtowerStatus {
26
+ addr: string;
27
+ name: string;
28
+ description: string;
29
+ capabilities: string[];
30
+ active: boolean;
31
+ registeredAt: number;
32
+ }
33
+
34
+ export class WatchtowerClient {
35
+ private registry: ethers.Contract;
36
+ private channelContract: ethers.Contract;
37
+ private signer: ethers.Signer | null;
38
+ private _channelClient: ChannelClient;
39
+
40
+ constructor(
41
+ registryAddress: string,
42
+ channelContractAddress: string,
43
+ runner: ContractRunner
44
+ ) {
45
+ this.registry = new ethers.Contract(registryAddress, WATCHTOWER_REGISTRY_ABI, runner);
46
+ this.channelContract = new ethers.Contract(channelContractAddress, SESSION_CHANNEL_WATCHTOWER_ABI, runner);
47
+ this.signer = runner instanceof AbstractSigner ? (runner as ethers.Signer) : null;
48
+ this._channelClient = new ChannelClient(channelContractAddress, runner);
49
+ }
50
+
51
+ /**
52
+ * Register this node as a watchtower in the WatchtowerRegistry.
53
+ */
54
+ async registerWatchtower(metadata: WatchtowerMetadata): Promise<{ txHash: string }> {
55
+ if (!this.signer) throw new Error("Signer required");
56
+ const tx = await this.registry.register(metadata.name, metadata.description, metadata.capabilities);
57
+ const receipt = await tx.wait();
58
+ return { txHash: receipt.hash };
59
+ }
60
+
61
+ /**
62
+ * Submit a watchtower challenge on behalf of `beneficiary`.
63
+ * The `state` must be a doubly-signed ChannelState with a higher sequenceNumber
64
+ * than the one currently recorded on-chain.
65
+ */
66
+ async submitChallenge(
67
+ channelId: string,
68
+ state: ChannelState,
69
+ beneficiary: string
70
+ ): Promise<{ txHash: string }> {
71
+ if (!this.signer) throw new Error("Signer required");
72
+ const encoded = this._channelClient.encodeChannelState(state);
73
+ const tx = await this.channelContract.submitWatchtowerChallenge(channelId, encoded, beneficiary);
74
+ const receipt = await tx.wait();
75
+ return { txHash: receipt.hash };
76
+ }
77
+
78
+ /**
79
+ * Get the on-chain registration status of a watchtower address.
80
+ */
81
+ async getWatchtowerStatus(watchtowerAddress: string): Promise<WatchtowerStatus> {
82
+ const raw = await this.registry.getWatchtower(watchtowerAddress);
83
+ return {
84
+ addr: raw.addr,
85
+ name: raw.name,
86
+ description: raw.description,
87
+ capabilities: [...raw.capabilities],
88
+ active: raw.active,
89
+ registeredAt: Number(raw.registeredAt),
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Authorize a watchtower to challenge on your behalf for a specific channel.
95
+ */
96
+ async authorizeWatchtower(channelId: string, watchtower: string): Promise<{ txHash: string }> {
97
+ if (!this.signer) throw new Error("Signer required");
98
+ const tx = await this.channelContract.authorizeWatchtower(channelId, watchtower);
99
+ const receipt = await tx.wait();
100
+ return { txHash: receipt.hash };
101
+ }
102
+
103
+ /**
104
+ * Revoke a watchtower's authorization for a channel.
105
+ */
106
+ async revokeWatchtower(channelId: string, watchtower: string): Promise<{ txHash: string }> {
107
+ if (!this.signer) throw new Error("Signer required");
108
+ const tx = await this.channelContract.revokeWatchtower(channelId, watchtower);
109
+ const receipt = await tx.wait();
110
+ return { txHash: receipt.hash };
111
+ }
112
+
113
+ /**
114
+ * Check whether a watchtower is authorized for a channel.
115
+ */
116
+ async isWatchtowerAuthorized(channelId: string, watchtower: string): Promise<boolean> {
117
+ return this.channelContract.isWatchtowerAuthorized(channelId, watchtower);
118
+ }
119
+
120
+ /** Expose ChannelClient for polling in watch loops. */
121
+ get channelClient(): ChannelClient {
122
+ return this._channelClient;
123
+ }
124
+ }
@@ -0,0 +1,157 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const { ethers } = require('ethers');
4
+ const {
5
+ signNegotiationMessage,
6
+ createSignedProposal,
7
+ parseNegotiationMessage,
8
+ } = require('../dist');
9
+ const { NegotiationGuard } = require('../dist');
10
+
11
+ // Minimal mock ContractRunner + Registry that always returns isRegistered=true
12
+ function makeRunner(isRegistered = true) {
13
+ // NegotiationGuard calls this.registry.isRegistered(addr)
14
+ // We stub it by creating a fake contract
15
+ return { isRegistered };
16
+ }
17
+
18
+ function makeMockGuard(isRegistered = true, opts = {}) {
19
+ const guard = new NegotiationGuard({
20
+ agentRegistryAddress: '0x0000000000000000000000000000000000000001',
21
+ runner: {},
22
+ ...opts,
23
+ });
24
+ // Replace registry with a mock
25
+ guard['registry'] = {
26
+ isRegistered: async () => isRegistered,
27
+ };
28
+ return guard;
29
+ }
30
+
31
+ const WALLET = ethers.Wallet.createRandom();
32
+
33
+ const BASE_INPUT = {
34
+ from: WALLET.address,
35
+ to: '0x0000000000000000000000000000000000000002',
36
+ serviceType: 'legal.patent-analysis.us.v1',
37
+ price: '50000000000000000',
38
+ token: '0x0000000000000000000000000000000000000000',
39
+ deadline: '2026-03-11T22:00:00Z',
40
+ spec: 'Analyze patent US11234567',
41
+ specHash: '0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1',
42
+ };
43
+
44
+ test('sign and verify a PROPOSE message passes NegotiationGuard', async () => {
45
+ const proposal = await createSignedProposal(BASE_INPUT, WALLET);
46
+ assert.equal(proposal.type, 'PROPOSE');
47
+ assert.ok(proposal.sig.startsWith('0x'));
48
+ assert.ok(proposal.timestamp > 0);
49
+ assert.ok(proposal.expiresAt > proposal.timestamp);
50
+
51
+ const guard = makeMockGuard(true);
52
+ const result = await guard.verify(JSON.stringify(proposal));
53
+ assert.equal(result.valid, true, `expected valid but got error: ${result.error}`);
54
+ assert.equal(result.recoveredSigner.toLowerCase(), WALLET.address.toLowerCase());
55
+ });
56
+
57
+ test('stale timestamp fails with TIMESTAMP_TOO_OLD', async () => {
58
+ const proposal = await createSignedProposal(BASE_INPUT, WALLET);
59
+ // Backdate timestamp by 120 seconds (beyond 60s tolerance)
60
+ const stale = { ...proposal, timestamp: proposal.timestamp - 120 };
61
+ // Re-sign with the altered timestamp so signature is valid but timestamp is old
62
+ const { ethers: _ethers } = require('ethers');
63
+ // We just inject a stale timestamp without re-signing — sig will be invalid
64
+ // but timestamp check fires first
65
+ const guard = makeMockGuard(true);
66
+ const result = await guard.verify(JSON.stringify(stale));
67
+ assert.equal(result.valid, false);
68
+ assert.equal(result.error, 'TIMESTAMP_TOO_OLD');
69
+ });
70
+
71
+ test('future timestamp fails with TIMESTAMP_IN_FUTURE', async () => {
72
+ const proposal = await createSignedProposal(BASE_INPUT, WALLET);
73
+ const future = { ...proposal, timestamp: proposal.timestamp + 120 };
74
+ const guard = makeMockGuard(true);
75
+ const result = await guard.verify(JSON.stringify(future));
76
+ assert.equal(result.valid, false);
77
+ assert.equal(result.error, 'TIMESTAMP_IN_FUTURE');
78
+ });
79
+
80
+ test('wrong signer (from mismatch) fails with INVALID_SIGNATURE', async () => {
81
+ const proposal = await createSignedProposal(BASE_INPUT, WALLET);
82
+ // Swap from to a different address
83
+ const tampered = {
84
+ ...proposal,
85
+ from: '0x0000000000000000000000000000000000000099',
86
+ };
87
+ const guard = makeMockGuard(true);
88
+ const result = await guard.verify(JSON.stringify(tampered));
89
+ assert.equal(result.valid, false);
90
+ assert.equal(result.error, 'INVALID_SIGNATURE');
91
+ });
92
+
93
+ test('replayed nonce fails with NONCE_REPLAYED', async () => {
94
+ const proposal = await createSignedProposal(BASE_INPUT, WALLET);
95
+ const guard = makeMockGuard(true);
96
+ const first = await guard.verify(JSON.stringify(proposal));
97
+ assert.equal(first.valid, true);
98
+ const second = await guard.verify(JSON.stringify(proposal));
99
+ assert.equal(second.valid, false);
100
+ assert.equal(second.error, 'NONCE_REPLAYED');
101
+ });
102
+
103
+ test('oversized message fails with MESSAGE_TOO_LARGE', async () => {
104
+ const bigSpec = 'x'.repeat(64 * 1024 + 1);
105
+ const guard = makeMockGuard(true);
106
+ const result = await guard.verify(JSON.stringify({ spec: bigSpec }));
107
+ assert.equal(result.valid, false);
108
+ assert.equal(result.error, 'MESSAGE_TOO_LARGE');
109
+ });
110
+
111
+ test('parseNegotiationMessage throws on oversized message', () => {
112
+ const big = JSON.stringify({ type: 'PROPOSE', data: 'x'.repeat(64 * 1024 + 1) });
113
+ assert.throws(() => parseNegotiationMessage(big), /64KB/);
114
+ });
115
+
116
+ test('expired PROPOSE fails with MESSAGE_EXPIRED', async () => {
117
+ const now = Math.floor(Date.now() / 1000);
118
+ // Build a message with expiresAt in the past, but valid timestamp
119
+ const unsigned = {
120
+ type: 'PROPOSE',
121
+ from: WALLET.address,
122
+ to: BASE_INPUT.to,
123
+ serviceType: BASE_INPUT.serviceType,
124
+ price: BASE_INPUT.price,
125
+ token: BASE_INPUT.token,
126
+ deadline: BASE_INPUT.deadline,
127
+ spec: BASE_INPUT.spec,
128
+ specHash: BASE_INPUT.specHash,
129
+ nonce: ethers.hexlify(ethers.randomBytes(16)),
130
+ timestamp: now,
131
+ expiresAt: now - 10, // already expired
132
+ };
133
+ const proposal = await signNegotiationMessage(unsigned, WALLET);
134
+ const guard = makeMockGuard(true);
135
+ const result = await guard.verify(JSON.stringify(proposal));
136
+ assert.equal(result.valid, false);
137
+ assert.equal(result.error, 'MESSAGE_EXPIRED');
138
+ });
139
+
140
+ test('unregistered signer fails with SIGNER_NOT_REGISTERED', async () => {
141
+ const proposal = await createSignedProposal(BASE_INPUT, WALLET);
142
+ const guard = makeMockGuard(false); // registry returns false
143
+ const result = await guard.verify(JSON.stringify(proposal));
144
+ assert.equal(result.valid, false);
145
+ assert.equal(result.error, 'SIGNER_NOT_REGISTERED');
146
+ });
147
+
148
+ test('registry downtime fails open (signature-only verification passes)', async () => {
149
+ const proposal = await createSignedProposal(BASE_INPUT, WALLET);
150
+ const guard = makeMockGuard(true);
151
+ // Override registry to throw
152
+ guard['registry'] = { isRegistered: async () => { throw new Error('network error'); } };
153
+ const result = await guard.verify(JSON.stringify(proposal));
154
+ // Should pass with signature-only verification
155
+ assert.equal(result.valid, true);
156
+ assert.equal(result.recoveredSigner.toLowerCase(), WALLET.address.toLowerCase());
157
+ });
@@ -0,0 +1,19 @@
1
+ const test = require('node:test');
2
+ const assert = require('node:assert/strict');
3
+ const { createNegotiationProposal, createNegotiationAccept, AgreementStatus, ProviderResponseType, DisputeOutcome, EvidenceType, ArbitrationVote } = require('../dist');
4
+
5
+ test('negotiation helpers generate shaped messages', () => {
6
+ const proposal = createNegotiationProposal({ from: '0x1', to: '0x2', serviceType: 'legal.patent-analysis.us.v1', price: '1', token: '0x0000000000000000000000000000000000000000', deadline: '2026-03-11T22:00:00Z', spec: 'Analyze filing', specHash: '0xabc' });
7
+ assert.equal(proposal.type, 'PROPOSE');
8
+ assert.ok(proposal.nonce.startsWith('0x'));
9
+ const accept = createNegotiationAccept({ from: '0x2', to: '0x1', agreedPrice: '1', agreedDeadline: '2026-03-11T22:00:00Z', refNonce: proposal.nonce });
10
+ assert.equal(accept.refNonce, proposal.nonce);
11
+ });
12
+
13
+ test('enum surfaces include remediation and dispute workflow states', () => {
14
+ assert.equal(AgreementStatus.REVISION_REQUESTED, 6);
15
+ assert.equal(ProviderResponseType.REQUEST_HUMAN_REVIEW, 5);
16
+ assert.equal(DisputeOutcome.PARTIAL_PROVIDER, 4);
17
+ assert.equal(EvidenceType.DELIVERABLE, 2);
18
+ assert.equal(ArbitrationVote.SPLIT, 3);
19
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "dist",
7
+ "rootDir": "src",
8
+ "strict": false,
9
+ "esModuleInterop": true,
10
+ "resolveJsonModule": true,
11
+ "skipLibCheck": true,
12
+ "declaration": true,
13
+ "declarationMap": true
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist", "src/demos"]
17
+ }