@zerox1/sdk 0.2.18 → 0.2.20

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/dist/index.js CHANGED
@@ -36,7 +36,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.HostedAgent = exports.Zerox1Agent = void 0;
39
+ exports.HostedAgent = exports.Zerox1Agent = exports.DEFAULT_SWAP_WHITELIST = void 0;
40
+ exports.encodeProposePayload = encodeProposePayload;
41
+ exports.encodeCounterPayload = encodeCounterPayload;
42
+ exports.decodeProposePayload = decodeProposePayload;
43
+ exports.encodeAcceptPayload = encodeAcceptPayload;
44
+ exports.decodeAcceptPayload = decodeAcceptPayload;
45
+ exports.decodeCounterPayload = decodeCounterPayload;
40
46
  const fs = __importStar(require("fs"));
41
47
  const net = __importStar(require("net"));
42
48
  const os = __importStar(require("os"));
@@ -45,6 +51,144 @@ const child_process_1 = require("child_process");
45
51
  const ws_1 = __importDefault(require("ws"));
46
52
  const ed = __importStar(require("@noble/ed25519"));
47
53
  // ============================================================================
54
+ // Token swap whitelist
55
+ // ============================================================================
56
+ /**
57
+ * Default token mint addresses allowed in agent-to-agent swaps.
58
+ * Prevents agents from being tricked into swapping into fraudulent tokens.
59
+ *
60
+ * Both devnet and mainnet mints are included; the node validates against
61
+ * whichever network it is connected to.
62
+ *
63
+ * Override per-agent with `Zerox1Agent.setSwapWhitelist()`.
64
+ */
65
+ exports.DEFAULT_SWAP_WHITELIST = new Set([
66
+ // SOL (wrapped)
67
+ 'So11111111111111111111111111111111111111112',
68
+ // USDC — mainnet
69
+ 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
70
+ // USDC — devnet
71
+ '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU',
72
+ // USDT — mainnet
73
+ 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB',
74
+ // JUP
75
+ 'JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN',
76
+ // BONK
77
+ 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263',
78
+ // RAY
79
+ '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R',
80
+ // WIF
81
+ 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm',
82
+ // BAGS — mainnet
83
+ 'Bags4uLBdNscWBnHmqBozrjSScnEqPx5qZBzLiqnRVN7',
84
+ ]);
85
+ // ============================================================================
86
+ // PROPOSE / COUNTER payload encode + decode helpers
87
+ // ============================================================================
88
+ function writeBidPrefix(amount) {
89
+ const buf = Buffer.alloc(16);
90
+ buf.writeBigUInt64LE(amount & 0xffffffffffffffffn, 0);
91
+ buf.writeBigUInt64LE(amount >> 64n, 8);
92
+ return buf;
93
+ }
94
+ function readBidPrefix(raw) {
95
+ const lo = raw.readBigUInt64LE(0);
96
+ const hi = raw.readBigUInt64LE(8);
97
+ return (hi << 64n) | lo;
98
+ }
99
+ /**
100
+ * Encode a PROPOSE payload into the structured wire format:
101
+ * `[16-byte LE i128 amount][JSON {"max_rounds": N, "message": "..."}]`
102
+ */
103
+ function encodeProposePayload(message, amount = 0n, maxRounds = 2) {
104
+ const prefix = writeBidPrefix(amount);
105
+ const json = Buffer.from(JSON.stringify({ max_rounds: maxRounds, message }));
106
+ return Buffer.concat([prefix, json]);
107
+ }
108
+ /**
109
+ * Encode a COUNTER payload into the structured wire format:
110
+ * `[16-byte LE i128 amount][JSON {"round": N, "max_rounds": M, "message": "..."}]`
111
+ */
112
+ function encodeCounterPayload(amount, round, maxRounds, message = '') {
113
+ const prefix = writeBidPrefix(amount);
114
+ const json = Buffer.from(JSON.stringify({ round, max_rounds: maxRounds, message }));
115
+ return Buffer.concat([prefix, json]);
116
+ }
117
+ /**
118
+ * Decode a PROPOSE envelope payload.
119
+ * Returns `null` if the payload is not in the structured format
120
+ * (e.g. a raw-string PROPOSE from an older agent).
121
+ */
122
+ function decodeProposePayload(payloadB64) {
123
+ const raw = Buffer.from(payloadB64, 'base64');
124
+ if (raw.length < 17 || raw[16] !== 0x7b /* '{' */)
125
+ return null;
126
+ try {
127
+ const body = JSON.parse(raw.slice(16).toString('utf8'));
128
+ return {
129
+ amount: readBidPrefix(raw),
130
+ maxRounds: Number(body['max_rounds'] ?? 2),
131
+ message: String(body['message'] ?? ''),
132
+ };
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ }
138
+ /**
139
+ * Encode an ACCEPT payload.
140
+ * `[16-byte LE i128 amount][JSON {"message": "..."}]`
141
+ *
142
+ * Both parties must use the same `amount` — it is the agreed price that
143
+ * will be passed to `lockPayment` on-chain.
144
+ */
145
+ function encodeAcceptPayload(amount, message = '') {
146
+ const prefix = writeBidPrefix(amount);
147
+ const json = Buffer.from(JSON.stringify({ message }));
148
+ return Buffer.concat([prefix, json]);
149
+ }
150
+ /**
151
+ * Decode an ACCEPT envelope payload.
152
+ * Returns `null` if the payload is not in the structured format
153
+ * (older agents may send a plain-text ACCEPT).
154
+ */
155
+ function decodeAcceptPayload(payloadB64) {
156
+ const raw = Buffer.from(payloadB64, 'base64');
157
+ if (raw.length < 17 || raw[16] !== 0x7b /* '{' */)
158
+ return null;
159
+ try {
160
+ const body = JSON.parse(raw.slice(16).toString('utf8'));
161
+ return {
162
+ amount: readBidPrefix(raw),
163
+ message: String(body['message'] ?? ''),
164
+ };
165
+ }
166
+ catch {
167
+ return null;
168
+ }
169
+ }
170
+ /**
171
+ * Decode a COUNTER envelope payload.
172
+ * Returns `null` if the payload is not in the structured format.
173
+ */
174
+ function decodeCounterPayload(payloadB64) {
175
+ const raw = Buffer.from(payloadB64, 'base64');
176
+ if (raw.length < 17 || raw[16] !== 0x7b /* '{' */)
177
+ return null;
178
+ try {
179
+ const body = JSON.parse(raw.slice(16).toString('utf8'));
180
+ return {
181
+ amount: readBidPrefix(raw),
182
+ round: Number(body['round'] ?? 1),
183
+ maxRounds: Number(body['max_rounds'] ?? 2),
184
+ message: String(body['message'] ?? ''),
185
+ };
186
+ }
187
+ catch {
188
+ return null;
189
+ }
190
+ }
191
+ // ============================================================================
48
192
  // CBOR encoding for FEEDBACK payload
49
193
  //
50
194
  // FEEDBACK payloads must be CBOR-encoded. Receiving nodes run
@@ -153,6 +297,7 @@ class Zerox1Agent {
153
297
  this.port = 0;
154
298
  this.nodeUrl = '';
155
299
  this._reconnectDelay = 1000;
300
+ this._swapWhitelist = exports.DEFAULT_SWAP_WHITELIST;
156
301
  }
157
302
  // ── Factory ───────────────────────────────────────────────────────────────
158
303
  /**
@@ -401,6 +546,192 @@ class Zerox1Agent {
401
546
  payload,
402
547
  });
403
548
  }
549
+ /**
550
+ * Send a PROPOSE envelope.
551
+ *
552
+ * Calls POST /negotiate/propose — the node handles binary payload encoding.
553
+ * Returns the conversation ID used (auto-generated if not supplied)
554
+ * along with the send confirmation.
555
+ */
556
+ async sendPropose(params) {
557
+ const res = await fetch(`${this.nodeUrl}/negotiate/propose`, {
558
+ method: 'POST',
559
+ headers: { 'Content-Type': 'application/json' },
560
+ body: JSON.stringify({
561
+ recipient: params.recipient,
562
+ conversation_id: params.conversationId,
563
+ amount_usdc_micro: params.amount !== undefined ? Number(params.amount) : undefined,
564
+ max_rounds: params.maxRounds,
565
+ message: params.message,
566
+ }),
567
+ });
568
+ if (!res.ok) {
569
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
570
+ throw new Error(err['error'] ?? `HTTP ${res.status}`);
571
+ }
572
+ const json = await res.json();
573
+ return {
574
+ conversationId: json['conversation_id'],
575
+ confirmation: { nonce: json['nonce'], payloadHash: json['payload_hash'] },
576
+ };
577
+ }
578
+ /**
579
+ * Send a COUNTER envelope.
580
+ *
581
+ * Calls POST /negotiate/counter — the node handles binary payload encoding.
582
+ * Protocol rules: `round` must be 1-indexed and <= `maxRounds`.
583
+ */
584
+ async sendCounter(params) {
585
+ if (params.round < 1 || params.round > params.maxRounds) {
586
+ throw new RangeError(`round ${params.round} is out of range [1, ${params.maxRounds}]`);
587
+ }
588
+ const res = await fetch(`${this.nodeUrl}/negotiate/counter`, {
589
+ method: 'POST',
590
+ headers: { 'Content-Type': 'application/json' },
591
+ body: JSON.stringify({
592
+ recipient: params.recipient,
593
+ conversation_id: params.conversationId,
594
+ amount_usdc_micro: Number(params.amount),
595
+ round: params.round,
596
+ max_rounds: params.maxRounds,
597
+ message: params.message,
598
+ }),
599
+ });
600
+ if (!res.ok) {
601
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
602
+ throw new Error(err['error'] ?? `HTTP ${res.status}`);
603
+ }
604
+ const json = await res.json();
605
+ return { nonce: json['nonce'], payloadHash: json['payload_hash'] };
606
+ }
607
+ /**
608
+ * Send an ACCEPT envelope with the agreed amount.
609
+ *
610
+ * Calls POST /negotiate/accept — the node handles binary payload encoding.
611
+ * The `amount` must match the most-recent COUNTER (or original PROPOSE if
612
+ * there was no counter). Both parties use this value to call `lockPayment`.
613
+ */
614
+ async sendAccept(params) {
615
+ const res = await fetch(`${this.nodeUrl}/negotiate/accept`, {
616
+ method: 'POST',
617
+ headers: { 'Content-Type': 'application/json' },
618
+ body: JSON.stringify({
619
+ recipient: params.recipient,
620
+ conversation_id: params.conversationId,
621
+ amount_usdc_micro: Number(params.amount),
622
+ message: params.message,
623
+ }),
624
+ });
625
+ if (!res.ok) {
626
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
627
+ throw new Error(err['error'] ?? `HTTP ${res.status}`);
628
+ }
629
+ const json = await res.json();
630
+ return { nonce: json['nonce'], payloadHash: json['payload_hash'] };
631
+ }
632
+ /**
633
+ * Lock USDC in the escrow program on-chain.
634
+ *
635
+ * Call this after `sendAccept()` to fund the escrow account before the
636
+ * provider begins work. The node signs the Solana transaction using its
637
+ * own keypair (this agent is the requester / payer).
638
+ *
639
+ * The automatic lock triggered by `sendAccept()` (via the node loop) uses
640
+ * default parameters. Use this method for explicit control — e.g. a custom
641
+ * notary or timeout.
642
+ *
643
+ * @param params.amount — must match the amount in the ACCEPT payload exactly.
644
+ */
645
+ async lockPayment(params) {
646
+ const res = await fetch(`${this.nodeUrl}/escrow/lock`, {
647
+ method: 'POST',
648
+ headers: { 'Content-Type': 'application/json' },
649
+ body: JSON.stringify({
650
+ provider: params.provider,
651
+ conversation_id: params.conversationId,
652
+ amount_usdc_micro: Number(params.amount),
653
+ notary_fee: params.notaryFee !== undefined ? Number(params.notaryFee) : undefined,
654
+ timeout_slots: params.timeoutSlots,
655
+ notary: params.notary,
656
+ }),
657
+ });
658
+ if (!res.ok) {
659
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
660
+ throw new Error(`lockPayment failed: ${err['error'] ?? res.status}`);
661
+ }
662
+ }
663
+ /**
664
+ * Approve and release a locked escrow payment to the provider.
665
+ *
666
+ * Call this after verifying the provider's DELIVER output is satisfactory.
667
+ * The node signs as the approver (notary or requester).
668
+ *
669
+ * @param params.notary — defaults to this agent (self-approval when no separate notary).
670
+ */
671
+ async approvePayment(params) {
672
+ const res = await fetch(`${this.nodeUrl}/escrow/approve`, {
673
+ method: 'POST',
674
+ headers: { 'Content-Type': 'application/json' },
675
+ body: JSON.stringify({
676
+ requester: params.requester,
677
+ provider: params.provider,
678
+ conversation_id: params.conversationId,
679
+ notary: params.notary,
680
+ }),
681
+ });
682
+ if (!res.ok) {
683
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
684
+ throw new Error(`approvePayment failed: ${err['error'] ?? res.status}`);
685
+ }
686
+ }
687
+ // ── Token swap ────────────────────────────────────────────────────────────
688
+ /**
689
+ * Override the token whitelist for this agent instance.
690
+ * Pass an empty Set to disable whitelist enforcement (not recommended).
691
+ */
692
+ setSwapWhitelist(whitelist) {
693
+ this._swapWhitelist = whitelist;
694
+ }
695
+ /**
696
+ * Execute a Jupiter token swap via the node's `/trade/swap` endpoint.
697
+ *
698
+ * Both `inputMint` and `outputMint` must be in the active whitelist
699
+ * (DEFAULT_SWAP_WHITELIST unless overridden via `setSwapWhitelist()`).
700
+ * This prevents agents from being deceived into swapping fraudulent tokens.
701
+ *
702
+ * @throws If either mint is not whitelisted, or the node rejects the swap.
703
+ */
704
+ async swap(params) {
705
+ const whitelist = params.whitelist ?? this._swapWhitelist;
706
+ if (whitelist.size > 0) {
707
+ if (!whitelist.has(params.inputMint)) {
708
+ throw new Error(`swap: inputMint ${params.inputMint} is not in the token whitelist`);
709
+ }
710
+ if (!whitelist.has(params.outputMint)) {
711
+ throw new Error(`swap: outputMint ${params.outputMint} is not in the token whitelist`);
712
+ }
713
+ }
714
+ const res = await fetch(`${this.nodeUrl}/trade/swap`, {
715
+ method: 'POST',
716
+ headers: { 'Content-Type': 'application/json' },
717
+ body: JSON.stringify({
718
+ input_mint: params.inputMint,
719
+ output_mint: params.outputMint,
720
+ amount: params.amount.toString(),
721
+ slippage_bps: params.slippageBps ?? 50,
722
+ }),
723
+ });
724
+ if (!res.ok) {
725
+ const err = await res.json().catch(() => ({ error: `HTTP ${res.status}` }));
726
+ throw new Error(`swap failed: ${err['error'] ?? res.status}`);
727
+ }
728
+ const data = await res.json();
729
+ return {
730
+ inAmount: BigInt(data.in_amount),
731
+ outAmount: BigInt(data.out_amount),
732
+ signature: data.signature,
733
+ };
734
+ }
404
735
  // ── Utilities ─────────────────────────────────────────────────────────────
405
736
  /** Generate a random 16-byte conversation ID as hex. */
406
737
  newConversationId() {
@@ -411,6 +742,7 @@ class Zerox1Agent {
411
742
  /**
412
743
  * Encode a bid value (i128 LE) into the first 16 bytes of a payload,
413
744
  * followed by optional extra bytes (your terms).
745
+ * @deprecated Use `encodeProposePayload()` or `encodeCounterPayload()` instead.
414
746
  */
415
747
  encodeBidValue(value, rest = Buffer.alloc(0)) {
416
748
  const buf = Buffer.alloc(16);
@@ -607,6 +939,74 @@ class HostedAgent {
607
939
  const payload = encodeFeedbackCbor(params.conversationId, params.targetAgent, params.score, outcomeMap[params.outcome], false, roleMap[params.role]);
608
940
  return this.send({ msgType: 'FEEDBACK', conversationId: params.conversationId, payload });
609
941
  }
942
+ /** Send a PROPOSE envelope via POST /hosted/negotiate/propose. */
943
+ async sendPropose(params) {
944
+ const res = await fetch(`${this.baseUrl}/hosted/negotiate/propose`, {
945
+ method: 'POST',
946
+ headers: {
947
+ 'Content-Type': 'application/json',
948
+ 'Authorization': `Bearer ${this.token}`,
949
+ },
950
+ body: JSON.stringify({
951
+ recipient: params.recipient,
952
+ conversation_id: params.conversationId,
953
+ amount_usdc_micro: params.amount !== undefined ? Number(params.amount) : undefined,
954
+ max_rounds: params.maxRounds,
955
+ message: params.message,
956
+ }),
957
+ });
958
+ if (!res.ok) {
959
+ const body = await res.text();
960
+ throw new Error(`hosted propose failed (${res.status}): ${body}`);
961
+ }
962
+ const json = await res.json();
963
+ return { conversationId: json['conversation_id'] };
964
+ }
965
+ /** Send a COUNTER envelope via POST /hosted/negotiate/counter. */
966
+ async sendCounter(params) {
967
+ if (params.round < 1 || params.round > params.maxRounds) {
968
+ throw new RangeError(`round ${params.round} is out of range [1, ${params.maxRounds}]`);
969
+ }
970
+ const res = await fetch(`${this.baseUrl}/hosted/negotiate/counter`, {
971
+ method: 'POST',
972
+ headers: {
973
+ 'Content-Type': 'application/json',
974
+ 'Authorization': `Bearer ${this.token}`,
975
+ },
976
+ body: JSON.stringify({
977
+ recipient: params.recipient,
978
+ conversation_id: params.conversationId,
979
+ amount_usdc_micro: Number(params.amount),
980
+ round: params.round,
981
+ max_rounds: params.maxRounds,
982
+ message: params.message,
983
+ }),
984
+ });
985
+ if (!res.ok && res.status !== 204) {
986
+ const body = await res.text();
987
+ throw new Error(`hosted counter failed (${res.status}): ${body}`);
988
+ }
989
+ }
990
+ /** Send an ACCEPT envelope via POST /hosted/negotiate/accept. */
991
+ async sendAccept(params) {
992
+ const res = await fetch(`${this.baseUrl}/hosted/negotiate/accept`, {
993
+ method: 'POST',
994
+ headers: {
995
+ 'Content-Type': 'application/json',
996
+ 'Authorization': `Bearer ${this.token}`,
997
+ },
998
+ body: JSON.stringify({
999
+ recipient: params.recipient,
1000
+ conversation_id: params.conversationId,
1001
+ amount_usdc_micro: Number(params.amount),
1002
+ message: params.message,
1003
+ }),
1004
+ });
1005
+ if (!res.ok && res.status !== 204) {
1006
+ const body = await res.text();
1007
+ throw new Error(`hosted accept failed (${res.status}): ${body}`);
1008
+ }
1009
+ }
610
1010
  /** Generate a random 16-byte conversation ID as hex. */
611
1011
  newConversationId() {
612
1012
  const bytes = new Uint8Array(16);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerox1/sdk",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "0x01 mesh agent SDK — zero-config, binary bundled, works on every platform",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -14,10 +14,10 @@
14
14
  "ws": "^8.18.0"
15
15
  },
16
16
  "optionalDependencies": {
17
- "@zerox1/sdk-darwin-arm64": "0.2.18",
18
- "@zerox1/sdk-darwin-x64": "0.2.18",
19
- "@zerox1/sdk-linux-x64": "0.2.18",
20
- "@zerox1/sdk-win32-x64": "0.2.18"
17
+ "@zerox1/sdk-darwin-arm64": "0.2.20",
18
+ "@zerox1/sdk-darwin-x64": "0.2.20",
19
+ "@zerox1/sdk-linux-x64": "0.2.20",
20
+ "@zerox1/sdk-win32-x64": "0.2.20"
21
21
  },
22
22
  "devDependencies": {
23
23
  "@types/node": "^22.0.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerox1/sdk-darwin-arm64",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "zerox1-node binary for macOS ARM64 (Apple Silicon)",
5
5
  "os": [
6
6
  "darwin"
Binary file
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerox1/sdk-darwin-x64",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "zerox1-node binary for macOS x64 (Intel)",
5
5
  "os": [
6
6
  "darwin"
Binary file
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerox1/sdk-linux-x64",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "zerox1-node binary for Linux x64",
5
5
  "os": [
6
6
  "linux"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerox1/sdk-win32-x64",
3
- "version": "0.2.18",
3
+ "version": "0.2.20",
4
4
  "description": "zerox1-node binary for Windows x64",
5
5
  "os": [
6
6
  "win32"