@agirails/sdk 2.0.0-beta

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 (154) hide show
  1. package/README.md +183 -0
  2. package/dist/ACTPClient.d.ts +52 -0
  3. package/dist/ACTPClient.d.ts.map +1 -0
  4. package/dist/ACTPClient.js +120 -0
  5. package/dist/ACTPClient.js.map +1 -0
  6. package/dist/abi/ACTPKernel.json +1340 -0
  7. package/dist/abi/ERC20.json +38 -0
  8. package/dist/abi/EscrowVault.json +64 -0
  9. package/dist/builders/DeliveryProofBuilder.d.ts +37 -0
  10. package/dist/builders/DeliveryProofBuilder.d.ts.map +1 -0
  11. package/dist/builders/DeliveryProofBuilder.js +165 -0
  12. package/dist/builders/DeliveryProofBuilder.js.map +1 -0
  13. package/dist/builders/QuoteBuilder.d.ts +68 -0
  14. package/dist/builders/QuoteBuilder.d.ts.map +1 -0
  15. package/dist/builders/QuoteBuilder.js +255 -0
  16. package/dist/builders/QuoteBuilder.js.map +1 -0
  17. package/dist/builders/index.d.ts +3 -0
  18. package/dist/builders/index.d.ts.map +1 -0
  19. package/dist/builders/index.js +10 -0
  20. package/dist/builders/index.js.map +1 -0
  21. package/dist/config/networks.d.ts +27 -0
  22. package/dist/config/networks.d.ts.map +1 -0
  23. package/dist/config/networks.js +103 -0
  24. package/dist/config/networks.js.map +1 -0
  25. package/dist/errors/index.d.ts +38 -0
  26. package/dist/errors/index.d.ts.map +1 -0
  27. package/dist/errors/index.js +87 -0
  28. package/dist/errors/index.js.map +1 -0
  29. package/dist/index.d.ts +19 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +68 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/protocol/ACTPKernel.d.ts +30 -0
  34. package/dist/protocol/ACTPKernel.d.ts.map +1 -0
  35. package/dist/protocol/ACTPKernel.js +261 -0
  36. package/dist/protocol/ACTPKernel.js.map +1 -0
  37. package/dist/protocol/EASHelper.d.ts +23 -0
  38. package/dist/protocol/EASHelper.d.ts.map +1 -0
  39. package/dist/protocol/EASHelper.js +106 -0
  40. package/dist/protocol/EASHelper.js.map +1 -0
  41. package/dist/protocol/EscrowVault.d.ts +24 -0
  42. package/dist/protocol/EscrowVault.d.ts.map +1 -0
  43. package/dist/protocol/EscrowVault.js +114 -0
  44. package/dist/protocol/EscrowVault.js.map +1 -0
  45. package/dist/protocol/EventMonitor.d.ts +18 -0
  46. package/dist/protocol/EventMonitor.d.ts.map +1 -0
  47. package/dist/protocol/EventMonitor.js +92 -0
  48. package/dist/protocol/EventMonitor.js.map +1 -0
  49. package/dist/protocol/MessageSigner.d.ts +23 -0
  50. package/dist/protocol/MessageSigner.d.ts.map +1 -0
  51. package/dist/protocol/MessageSigner.js +178 -0
  52. package/dist/protocol/MessageSigner.js.map +1 -0
  53. package/dist/protocol/ProofGenerator.d.ts +22 -0
  54. package/dist/protocol/ProofGenerator.d.ts.map +1 -0
  55. package/dist/protocol/ProofGenerator.js +64 -0
  56. package/dist/protocol/ProofGenerator.js.map +1 -0
  57. package/dist/protocol/QuoteBuilder.d.ts +2 -0
  58. package/dist/protocol/QuoteBuilder.d.ts.map +1 -0
  59. package/dist/protocol/QuoteBuilder.js +7 -0
  60. package/dist/protocol/QuoteBuilder.js.map +1 -0
  61. package/dist/types/eip712.d.ts +106 -0
  62. package/dist/types/eip712.d.ts.map +1 -0
  63. package/dist/types/eip712.js +84 -0
  64. package/dist/types/eip712.js.map +1 -0
  65. package/dist/types/escrow.d.ts +18 -0
  66. package/dist/types/escrow.d.ts.map +1 -0
  67. package/dist/types/escrow.js +3 -0
  68. package/dist/types/escrow.js.map +1 -0
  69. package/dist/types/index.d.ts +6 -0
  70. package/dist/types/index.d.ts.map +1 -0
  71. package/dist/types/index.js +22 -0
  72. package/dist/types/index.js.map +1 -0
  73. package/dist/types/message.d.ts +109 -0
  74. package/dist/types/message.d.ts.map +1 -0
  75. package/dist/types/message.js +3 -0
  76. package/dist/types/message.js.map +1 -0
  77. package/dist/types/state.d.ts +19 -0
  78. package/dist/types/state.d.ts.map +1 -0
  79. package/dist/types/state.js +49 -0
  80. package/dist/types/state.js.map +1 -0
  81. package/dist/types/transaction.d.ts +36 -0
  82. package/dist/types/transaction.d.ts.map +1 -0
  83. package/dist/types/transaction.js +3 -0
  84. package/dist/types/transaction.js.map +1 -0
  85. package/dist/utils/IPFSClient.d.ts +37 -0
  86. package/dist/utils/IPFSClient.d.ts.map +1 -0
  87. package/dist/utils/IPFSClient.js +128 -0
  88. package/dist/utils/IPFSClient.js.map +1 -0
  89. package/dist/utils/NonceManager.d.ts +34 -0
  90. package/dist/utils/NonceManager.d.ts.map +1 -0
  91. package/dist/utils/NonceManager.js +114 -0
  92. package/dist/utils/NonceManager.js.map +1 -0
  93. package/dist/utils/ReceivedNonceTracker.d.ts +35 -0
  94. package/dist/utils/ReceivedNonceTracker.d.ts.map +1 -0
  95. package/dist/utils/ReceivedNonceTracker.js +196 -0
  96. package/dist/utils/ReceivedNonceTracker.js.map +1 -0
  97. package/dist/utils/canonicalJson.d.ts +4 -0
  98. package/dist/utils/canonicalJson.d.ts.map +1 -0
  99. package/dist/utils/canonicalJson.js +21 -0
  100. package/dist/utils/canonicalJson.js.map +1 -0
  101. package/dist/utils/computeTypeHash.d.ts +3 -0
  102. package/dist/utils/computeTypeHash.d.ts.map +1 -0
  103. package/dist/utils/computeTypeHash.js +30 -0
  104. package/dist/utils/computeTypeHash.js.map +1 -0
  105. package/dist/utils/validation.d.ts +6 -0
  106. package/dist/utils/validation.d.ts.map +1 -0
  107. package/dist/utils/validation.js +46 -0
  108. package/dist/utils/validation.js.map +1 -0
  109. package/package.json +73 -0
  110. package/src/ACTPClient.ts +276 -0
  111. package/src/__tests__/ProofGenerator.test.ts +124 -0
  112. package/src/__tests__/QuoteBuilder.test.ts +516 -0
  113. package/src/__tests__/StateMachine.test.ts +82 -0
  114. package/src/__tests__/builders/DeliveryProofBuilder.test.ts +581 -0
  115. package/src/__tests__/integration/ACTPClient.test.ts +263 -0
  116. package/src/__tests__/integration.test.ts +289 -0
  117. package/src/__tests__/protocol/EASHelper.test.ts +472 -0
  118. package/src/__tests__/protocol/EventMonitor.test.ts +382 -0
  119. package/src/__tests__/security/ACTPKernel.security.test.ts +1167 -0
  120. package/src/__tests__/security/EscrowVault.security.test.ts +570 -0
  121. package/src/__tests__/security/MessageSigner.security.test.ts +286 -0
  122. package/src/__tests__/security/NonceReplay.security.test.ts +501 -0
  123. package/src/__tests__/security/validation.security.test.ts +376 -0
  124. package/src/__tests__/utils/IPFSClient.test.ts +262 -0
  125. package/src/__tests__/utils/NonceManager.test.ts +205 -0
  126. package/src/__tests__/utils/canonicalJson.test.ts +153 -0
  127. package/src/abi/ACTPKernel.json +1340 -0
  128. package/src/abi/ERC20.json +40 -0
  129. package/src/abi/EscrowVault.json +66 -0
  130. package/src/builders/DeliveryProofBuilder.ts +326 -0
  131. package/src/builders/QuoteBuilder.ts +483 -0
  132. package/src/builders/index.ts +17 -0
  133. package/src/config/networks.ts +165 -0
  134. package/src/errors/index.ts +130 -0
  135. package/src/index.ts +108 -0
  136. package/src/protocol/ACTPKernel.ts +625 -0
  137. package/src/protocol/EASHelper.ts +197 -0
  138. package/src/protocol/EscrowVault.ts +237 -0
  139. package/src/protocol/EventMonitor.ts +161 -0
  140. package/src/protocol/MessageSigner.ts +336 -0
  141. package/src/protocol/ProofGenerator.ts +119 -0
  142. package/src/protocol/QuoteBuilder.ts +15 -0
  143. package/src/types/eip712.ts +175 -0
  144. package/src/types/escrow.ts +26 -0
  145. package/src/types/index.ts +10 -0
  146. package/src/types/message.ts +145 -0
  147. package/src/types/state.ts +77 -0
  148. package/src/types/transaction.ts +54 -0
  149. package/src/utils/IPFSClient.ts +248 -0
  150. package/src/utils/NonceManager.ts +293 -0
  151. package/src/utils/ReceivedNonceTracker.ts +397 -0
  152. package/src/utils/canonicalJson.ts +38 -0
  153. package/src/utils/computeTypeHash.ts +50 -0
  154. package/src/utils/validation.ts +82 -0
@@ -0,0 +1,145 @@
1
+ /**
2
+ * ACTP Message Types
3
+ * Reference: Yellow Paper §4-10 (AIPs)
4
+ */
5
+
6
+ /**
7
+ * Base ACTP message structure
8
+ */
9
+ export interface ACTPMessage {
10
+ type: string;
11
+ version: string;
12
+ from: string; // DID
13
+ to: string; // DID
14
+ timestamp: number;
15
+ nonce: string;
16
+ signature?: string;
17
+ [key: string]: any;
18
+ }
19
+
20
+ /**
21
+ * Quote Request (AIP-2)
22
+ * Reference: Yellow Paper §6.2.1
23
+ */
24
+ export interface QuoteRequest extends ACTPMessage {
25
+ type: 'quote.request';
26
+ serviceRequest: {
27
+ capabilityType: string;
28
+ parameters: Record<string, any>;
29
+ deliveryRequirements: {
30
+ deadline: string;
31
+ maxDeliveryTime: string;
32
+ };
33
+ };
34
+ budgetConstraints: {
35
+ maxPrice: string;
36
+ currency: 'USDC';
37
+ };
38
+ expiresAt: string;
39
+ }
40
+
41
+ /**
42
+ * Quote Response (AIP-2)
43
+ * Reference: Yellow Paper §6.2.2
44
+ */
45
+ export interface QuoteResponse extends ACTPMessage {
46
+ type: 'quote.response';
47
+ inResponseTo: string;
48
+ quoteId: string;
49
+ pricing: {
50
+ totalPrice: string;
51
+ currency: 'USDC';
52
+ breakdown: Array<{ item: string; amount: string }>;
53
+ platformFee: string;
54
+ };
55
+ sla: {
56
+ successRateGuarantee: number;
57
+ refundPolicy: string;
58
+ };
59
+ expiresAt: string;
60
+ }
61
+
62
+ /**
63
+ * Delivery Proof (AIP-4) - DEPRECATED
64
+ * @deprecated Use DeliveryProofMessage instead (AIP-4 v1.1)
65
+ */
66
+ export interface DeliveryProof {
67
+ type: 'delivery.proof';
68
+ txId: string;
69
+ contentHash: string;
70
+ timestamp: number;
71
+ deliveryUrl?: string;
72
+ metadata: {
73
+ size: number;
74
+ mimeType: string;
75
+ [key: string]: any;
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Delivery Proof Message (AIP-4 v1.1)
81
+ * Reference: AIP-4 §3.2, §8.1
82
+ * Complete schema for delivery proofs with EAS attestations
83
+ */
84
+ export interface DeliveryProofMessage {
85
+ type: 'agirails.delivery.v1';
86
+ version: string; // Semantic version (e.g., "1.0.0")
87
+ txId: string; // bytes32 (0x-prefixed)
88
+ provider: string; // DID (e.g., "did:ethr:84532:0x...")
89
+ consumer: string; // DID
90
+ resultCID: string; // IPFS CID (CIDv1, base32, e.g., "bafybeig...")
91
+ resultHash: string; // Keccak256 hash of canonical result JSON
92
+ metadata?: {
93
+ executionTime?: number; // Seconds
94
+ outputFormat?: string; // MIME type
95
+ outputSize?: number; // Bytes
96
+ notes?: string; // Max 500 chars
97
+ };
98
+ easAttestationUID: string; // bytes32 (0x-prefixed)
99
+ deliveredAt: number; // Unix timestamp (seconds)
100
+ chainId: number; // 84532 (Base Sepolia) or 8453 (Base Mainnet)
101
+ nonce: number; // Monotonically increasing
102
+ signature: string; // EIP-712 signature (0x-prefixed, 130 chars)
103
+ }
104
+
105
+ /**
106
+ * EAS Attestation Data for delivery proofs
107
+ */
108
+ export interface EASAttestationData {
109
+ schema: string; // EAS schema UID
110
+ recipient: string; // Provider address
111
+ expirationTime: number; // Unix timestamp (0 for no expiration)
112
+ revocable: boolean;
113
+ refUID: string; // Reference to transaction
114
+ data: string; // ABI-encoded attestation data
115
+ }
116
+
117
+ /**
118
+ * Quote Message (AIP-2 v1.0)
119
+ * Reference: AIP-2 §2.1, §2.2
120
+ * Price quote submitted by provider for negotiated transactions
121
+ */
122
+ export interface QuoteMessageV2 {
123
+ type: 'agirails.quote.v1';
124
+ version: '1.0.0';
125
+ txId: string; // bytes32 (0x-prefixed)
126
+ provider: string; // DID (e.g., "did:ethr:84532:0x...")
127
+ consumer: string; // DID
128
+ quotedAmount: string; // Provider's quoted price (base units, string to avoid JS overflow)
129
+ originalAmount: string; // Consumer's original offer from AIP-1
130
+ maxPrice: string; // Consumer's maximum acceptable price from AIP-1
131
+ currency: string; // Currently "USDC" only
132
+ decimals: number; // Token decimals (6 for USDC)
133
+ quotedAt: number; // Unix timestamp (seconds)
134
+ expiresAt: number; // Unix timestamp (seconds)
135
+ justification?: {
136
+ reason?: string;
137
+ estimatedTime?: number;
138
+ computeCost?: number;
139
+ breakdown?: Record<string, any>;
140
+ };
141
+ chainId: number; // 84532 (Base Sepolia) or 8453 (Base Mainnet)
142
+ nonce: number; // Monotonically increasing per provider + message type
143
+ signature: string; // EIP-712 signature (0x-prefixed, 130 chars)
144
+ }
145
+
@@ -0,0 +1,77 @@
1
+ /**
2
+ * ACTP State Machine
3
+ * Reference: Yellow Paper §3.2
4
+ */
5
+
6
+ export enum State {
7
+ INITIATED = 0,
8
+ QUOTED = 1,
9
+ COMMITTED = 2,
10
+ IN_PROGRESS = 3,
11
+ DELIVERED = 4,
12
+ SETTLED = 5,
13
+ DISPUTED = 6,
14
+ CANCELLED = 7
15
+ }
16
+
17
+ export class StateMachine {
18
+ /**
19
+ * Valid state transitions per Yellow Paper §3.2.2
20
+ */
21
+ private static readonly TRANSITIONS: Record<State, State[]> = {
22
+ [State.INITIATED]: [State.QUOTED, State.COMMITTED, State.CANCELLED], // Allow direct INITIATED → COMMITTED (AIP-3)
23
+ [State.QUOTED]: [State.COMMITTED, State.CANCELLED],
24
+ [State.COMMITTED]: [State.IN_PROGRESS, State.CANCELLED],
25
+ [State.IN_PROGRESS]: [State.DELIVERED, State.DISPUTED],
26
+ [State.DELIVERED]: [State.SETTLED, State.DISPUTED],
27
+ [State.DISPUTED]: [State.SETTLED, State.CANCELLED],
28
+ [State.SETTLED]: [], // Terminal state
29
+ [State.CANCELLED]: [] // Terminal state
30
+ };
31
+
32
+ /**
33
+ * Check if state transition is valid
34
+ */
35
+ static isValidTransition(from: State, to: State): boolean {
36
+ return this.TRANSITIONS[from]?.includes(to) ?? false;
37
+ }
38
+
39
+ /**
40
+ * Check if state is terminal (no further transitions)
41
+ */
42
+ static isTerminalState(state: State): boolean {
43
+ return state === State.SETTLED || state === State.CANCELLED;
44
+ }
45
+
46
+ /**
47
+ * Get human-readable state name
48
+ */
49
+ static getStateName(state: State): string {
50
+ return State[state];
51
+ }
52
+
53
+ /**
54
+ * Get all valid next states from current state
55
+ */
56
+ static getNextValidStates(currentState: State): State[] {
57
+ return this.TRANSITIONS[currentState] || [];
58
+ }
59
+
60
+ /**
61
+ * Validate state transition or throw error
62
+ */
63
+ static validateTransition(from: State, to: State): void {
64
+ if (!this.isValidTransition(from, to)) {
65
+ const validStates = this.getNextValidStates(from)
66
+ .map(s => State[s])
67
+ .join(', ');
68
+
69
+ throw new Error(
70
+ `Invalid state transition: ${State[from]} → ${State[to]}. ` +
71
+ `Valid transitions from ${State[from]}: ${validStates || 'none (terminal state)'}`
72
+ );
73
+ }
74
+ }
75
+ }
76
+
77
+
@@ -0,0 +1,54 @@
1
+ import { State } from './state';
2
+
3
+ /**
4
+ * ACTP Transaction
5
+ * Reference: Yellow Paper §3.1
6
+ */
7
+ export interface Transaction {
8
+ txId: string;
9
+ requester: string;
10
+ provider: string;
11
+ amount: bigint;
12
+ state: State;
13
+ createdAt: number;
14
+ deadline: number;
15
+ disputeWindow: number;
16
+ escrowContract: string;
17
+ escrowId: string;
18
+ metadata: string;
19
+ }
20
+
21
+ /**
22
+ * Parameters for creating a new transaction
23
+ */
24
+ export interface CreateTransactionParams {
25
+ provider: string;
26
+ requester: string;
27
+ amount: bigint;
28
+ deadline: number;
29
+ disputeWindow: number;
30
+ metadata?: string;
31
+ }
32
+
33
+ /**
34
+ * Dispute resolution split
35
+ */
36
+ export interface DisputeResolution {
37
+ requesterAmount: bigint;
38
+ providerAmount: bigint;
39
+ mediatorAmount: bigint;
40
+ mediator?: string;
41
+ }
42
+
43
+ /**
44
+ * Economic parameters (fee structure)
45
+ */
46
+ export interface EconomicParams {
47
+ baseFeeNumerator: number;
48
+ baseFeeDenominator: number;
49
+ feeRecipient: string;
50
+ requesterPenaltyBps: number;
51
+ providerPenaltyBps: number;
52
+ }
53
+
54
+
@@ -0,0 +1,248 @@
1
+ /**
2
+ * IPFS Client Implementation
3
+ * Wrapper around kubo-rpc-client (formerly ipfs-http-client) for AIP-4 delivery proof uploads
4
+ */
5
+
6
+ import { create, IPFSHTTPClient, Options } from 'kubo-rpc-client';
7
+
8
+ /**
9
+ * IPFS Client Interface (from DeliveryProofBuilder)
10
+ */
11
+ export interface IPFSClient {
12
+ /**
13
+ * Upload data to IPFS
14
+ * @param data - JSON string or buffer
15
+ * @returns CIDv1 string (base32, e.g., "bafybeig...")
16
+ */
17
+ add(data: string | Buffer): Promise<string>;
18
+
19
+ /**
20
+ * Pin content to prevent garbage collection
21
+ * @param cid - IPFS CID
22
+ */
23
+ pin(cid: string): Promise<void>;
24
+
25
+ /**
26
+ * Retrieve content from IPFS
27
+ * @param cid - IPFS CID
28
+ * @returns Content as string
29
+ */
30
+ get(cid: string): Promise<string>;
31
+ }
32
+
33
+ /**
34
+ * IPFS Client Configuration
35
+ */
36
+ export interface IPFSClientConfig {
37
+ /**
38
+ * IPFS HTTP API endpoint
39
+ * Default: http://localhost:5001 (local IPFS daemon)
40
+ * Production: https://ipfs.infura.io:5001/api/v0 (Infura)
41
+ */
42
+ url?: string;
43
+
44
+ /**
45
+ * API authentication (for Infura, Pinata, etc.)
46
+ */
47
+ auth?: {
48
+ username: string;
49
+ password: string;
50
+ };
51
+
52
+ /**
53
+ * HTTP headers (for API keys)
54
+ */
55
+ headers?: Record<string, string>;
56
+
57
+ /**
58
+ * Request timeout (ms)
59
+ * Default: 60000 (60 seconds)
60
+ */
61
+ timeout?: number;
62
+ }
63
+
64
+ /**
65
+ * Default IPFS configurations
66
+ */
67
+ export const IPFS_CONFIGS = {
68
+ local: {
69
+ url: 'http://localhost:5001'
70
+ },
71
+ infura: {
72
+ url: 'https://ipfs.infura.io:5001/api/v0'
73
+ // Auth required: Set INFURA_PROJECT_ID and INFURA_PROJECT_SECRET
74
+ },
75
+ pinata: {
76
+ url: 'https://api.pinata.cloud'
77
+ // Headers required: Set pinata_api_key and pinata_secret_api_key
78
+ }
79
+ };
80
+
81
+ /**
82
+ * IPFS HTTP Client Implementation
83
+ * Uses ipfs-http-client library
84
+ */
85
+ export class IPFSHTTPClientImpl implements IPFSClient {
86
+ private client: IPFSHTTPClient;
87
+ private config: IPFSClientConfig;
88
+
89
+ /**
90
+ * Create IPFS client
91
+ * @param config - IPFS client configuration
92
+ */
93
+ constructor(config: IPFSClientConfig = {}) {
94
+ this.config = {
95
+ url: config.url || 'http://localhost:5001',
96
+ timeout: config.timeout || 60000,
97
+ ...config
98
+ };
99
+
100
+ const options: Options = {
101
+ url: this.config.url,
102
+ timeout: this.config.timeout
103
+ };
104
+
105
+ // Add authentication if provided
106
+ if (this.config.auth) {
107
+ options.headers = {
108
+ ...this.config.headers,
109
+ authorization: 'Basic ' + Buffer.from(
110
+ `${this.config.auth.username}:${this.config.auth.password}`
111
+ ).toString('base64')
112
+ };
113
+ } else if (this.config.headers) {
114
+ options.headers = this.config.headers;
115
+ }
116
+
117
+ this.client = create(options);
118
+ }
119
+
120
+ /**
121
+ * Upload data to IPFS
122
+ * @param data - JSON string or buffer
123
+ * @returns CIDv1 string (base32)
124
+ */
125
+ async add(data: string | Buffer): Promise<string> {
126
+ try {
127
+ const content = typeof data === 'string' ? Buffer.from(data, 'utf-8') : data;
128
+
129
+ const result = await this.client.add(content, {
130
+ cidVersion: 1, // Use CIDv1 (base32)
131
+ hashAlg: 'sha2-256',
132
+ pin: true // Auto-pin on upload
133
+ });
134
+
135
+ // Convert CID to base32 string (e.g., "bafybeig...")
136
+ return result.cid.toString();
137
+ } catch (error) {
138
+ throw new Error(`IPFS upload failed: ${error instanceof Error ? error.message : String(error)}`);
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Pin content to prevent garbage collection
144
+ * @param cid - IPFS CID
145
+ */
146
+ async pin(cid: string): Promise<void> {
147
+ try {
148
+ await this.client.pin.add(cid);
149
+ } catch (error) {
150
+ throw new Error(`IPFS pin failed: ${error instanceof Error ? error.message : String(error)}`);
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Retrieve content from IPFS
156
+ * @param cid - IPFS CID
157
+ * @returns Content as string
158
+ */
159
+ async get(cid: string): Promise<string> {
160
+ try {
161
+ const chunks: Uint8Array[] = [];
162
+
163
+ for await (const chunk of this.client.cat(cid)) {
164
+ chunks.push(chunk);
165
+ }
166
+
167
+ // Concatenate all chunks
168
+ const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
169
+ const result = new Uint8Array(totalLength);
170
+ let offset = 0;
171
+
172
+ for (const chunk of chunks) {
173
+ result.set(chunk, offset);
174
+ offset += chunk.length;
175
+ }
176
+
177
+ return Buffer.from(result).toString('utf-8');
178
+ } catch (error) {
179
+ throw new Error(`IPFS retrieval failed: ${error instanceof Error ? error.message : String(error)}`);
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Check if IPFS daemon is reachable
185
+ * @returns true if connected, false otherwise
186
+ */
187
+ async isOnline(): Promise<boolean> {
188
+ try {
189
+ await this.client.id();
190
+ return true;
191
+ } catch {
192
+ return false;
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Get IPFS node ID
198
+ * @returns IPFS node ID
199
+ */
200
+ async getNodeId(): Promise<string> {
201
+ try {
202
+ const id = await this.client.id();
203
+ return id.id.toString();
204
+ } catch (error) {
205
+ throw new Error(`Failed to get node ID: ${error instanceof Error ? error.message : String(error)}`);
206
+ }
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Create IPFS client with environment-based configuration
212
+ * Checks for IPFS_URL, INFURA_PROJECT_ID, PINATA_API_KEY env vars
213
+ */
214
+ export function createIPFSClient(): IPFSClient {
215
+ // Check for Infura credentials
216
+ if (process.env.INFURA_PROJECT_ID && process.env.INFURA_PROJECT_SECRET) {
217
+ return new IPFSHTTPClientImpl({
218
+ url: 'https://ipfs.infura.io:5001/api/v0',
219
+ auth: {
220
+ username: process.env.INFURA_PROJECT_ID,
221
+ password: process.env.INFURA_PROJECT_SECRET
222
+ }
223
+ });
224
+ }
225
+
226
+ // Check for Pinata credentials
227
+ if (process.env.PINATA_API_KEY && process.env.PINATA_SECRET_API_KEY) {
228
+ return new IPFSHTTPClientImpl({
229
+ url: 'https://api.pinata.cloud',
230
+ headers: {
231
+ pinata_api_key: process.env.PINATA_API_KEY,
232
+ pinata_secret_api_key: process.env.PINATA_SECRET_API_KEY
233
+ }
234
+ });
235
+ }
236
+
237
+ // Check for custom IPFS URL
238
+ if (process.env.IPFS_URL) {
239
+ return new IPFSHTTPClientImpl({
240
+ url: process.env.IPFS_URL
241
+ });
242
+ }
243
+
244
+ // Default to local IPFS daemon
245
+ return new IPFSHTTPClientImpl({
246
+ url: 'http://localhost:5001'
247
+ });
248
+ }