@ophirai/sdk 0.1.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.
- package/README.md +139 -0
- package/dist/__tests__/buyer.test.d.ts +1 -0
- package/dist/__tests__/buyer.test.js +664 -0
- package/dist/__tests__/discovery.test.d.ts +1 -0
- package/dist/__tests__/discovery.test.js +188 -0
- package/dist/__tests__/escrow.test.d.ts +1 -0
- package/dist/__tests__/escrow.test.js +385 -0
- package/dist/__tests__/identity.test.d.ts +1 -0
- package/dist/__tests__/identity.test.js +222 -0
- package/dist/__tests__/integration.test.d.ts +1 -0
- package/dist/__tests__/integration.test.js +681 -0
- package/dist/__tests__/lockstep.test.d.ts +1 -0
- package/dist/__tests__/lockstep.test.js +320 -0
- package/dist/__tests__/messages.test.d.ts +1 -0
- package/dist/__tests__/messages.test.js +976 -0
- package/dist/__tests__/negotiation.test.d.ts +1 -0
- package/dist/__tests__/negotiation.test.js +667 -0
- package/dist/__tests__/seller.test.d.ts +1 -0
- package/dist/__tests__/seller.test.js +767 -0
- package/dist/__tests__/server.test.d.ts +1 -0
- package/dist/__tests__/server.test.js +239 -0
- package/dist/__tests__/signing.test.d.ts +1 -0
- package/dist/__tests__/signing.test.js +713 -0
- package/dist/__tests__/sla.test.d.ts +1 -0
- package/dist/__tests__/sla.test.js +342 -0
- package/dist/__tests__/transport.test.d.ts +1 -0
- package/dist/__tests__/transport.test.js +197 -0
- package/dist/__tests__/x402.test.d.ts +1 -0
- package/dist/__tests__/x402.test.js +141 -0
- package/dist/buyer.d.ts +190 -0
- package/dist/buyer.js +555 -0
- package/dist/discovery.d.ts +47 -0
- package/dist/discovery.js +51 -0
- package/dist/escrow.d.ts +177 -0
- package/dist/escrow.js +434 -0
- package/dist/identity.d.ts +60 -0
- package/dist/identity.js +108 -0
- package/dist/index.d.ts +122 -0
- package/dist/index.js +43 -0
- package/dist/lockstep.d.ts +94 -0
- package/dist/lockstep.js +127 -0
- package/dist/messages.d.ts +172 -0
- package/dist/messages.js +262 -0
- package/dist/negotiation.d.ts +113 -0
- package/dist/negotiation.js +214 -0
- package/dist/seller.d.ts +127 -0
- package/dist/seller.js +395 -0
- package/dist/server.d.ts +52 -0
- package/dist/server.js +149 -0
- package/dist/signing.d.ts +98 -0
- package/dist/signing.js +165 -0
- package/dist/sla.d.ts +95 -0
- package/dist/sla.js +187 -0
- package/dist/transport.d.ts +41 -0
- package/dist/transport.js +127 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.js +1 -0
- package/dist/x402.d.ts +25 -0
- package/dist/x402.js +54 -0
- package/package.json +40 -0
package/dist/escrow.d.ts
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import type { Agreement } from './types.js';
|
|
2
|
+
/** Escrow account status as stored on-chain. */
|
|
3
|
+
export type EscrowStatus = 'Active' | 'Released' | 'Disputed' | 'Cancelled';
|
|
4
|
+
/** On-chain escrow account data. */
|
|
5
|
+
export interface EscrowAccountData {
|
|
6
|
+
buyer: string;
|
|
7
|
+
seller: string;
|
|
8
|
+
mint: string;
|
|
9
|
+
agreementHash: string;
|
|
10
|
+
depositAmount: bigint;
|
|
11
|
+
penaltyRateBps: number;
|
|
12
|
+
createdAt: number;
|
|
13
|
+
timeoutSlot: bigint;
|
|
14
|
+
status: EscrowStatus;
|
|
15
|
+
bump: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Manages Solana escrow PDAs for agreement payments and dispute resolution.
|
|
19
|
+
*
|
|
20
|
+
* Derives deterministic PDA addresses from buyer pubkey and agreement hash,
|
|
21
|
+
* ensuring collision-resistant escrow accounts tied to specific agreements.
|
|
22
|
+
* Builds and submits Solana transactions for all escrow lifecycle operations.
|
|
23
|
+
*/
|
|
24
|
+
export declare class EscrowManager {
|
|
25
|
+
private rpcUrl;
|
|
26
|
+
private programId;
|
|
27
|
+
constructor(config?: {
|
|
28
|
+
rpcUrl?: string;
|
|
29
|
+
programId?: string;
|
|
30
|
+
});
|
|
31
|
+
/**
|
|
32
|
+
* Derive the deterministic escrow PDA address from buyer pubkey and agreement hash.
|
|
33
|
+
* Seeds: ["escrow", buyer_pubkey, agreement_hash]
|
|
34
|
+
*
|
|
35
|
+
* @param buyerPublicKey - 32-byte Ed25519 public key of the buyer
|
|
36
|
+
* @param agreementHash - SHA-256 hash of the canonicalized agreement terms (32 bytes)
|
|
37
|
+
* @returns The base58-encoded PDA address and its bump seed
|
|
38
|
+
* @throws {OphirError} INVALID_MESSAGE if buyerPublicKey is not 32 bytes
|
|
39
|
+
*/
|
|
40
|
+
deriveEscrowAddress(buyerPublicKey: Uint8Array, agreementHash: Uint8Array): {
|
|
41
|
+
address: string;
|
|
42
|
+
bump: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Derive the vault token account PDA from an escrow address.
|
|
46
|
+
* Seeds: ["vault", escrow_pubkey]
|
|
47
|
+
*
|
|
48
|
+
* @param escrowPublicKey - 32-byte public key of the escrow PDA
|
|
49
|
+
* @returns The base58-encoded vault PDA address and its bump seed
|
|
50
|
+
*/
|
|
51
|
+
deriveVaultAddress(escrowPublicKey: Uint8Array): {
|
|
52
|
+
address: string;
|
|
53
|
+
bump: number;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Create an escrow account and deposit tokens into the PDA-controlled vault.
|
|
57
|
+
*
|
|
58
|
+
* Builds a `make_escrow` instruction with the buyer's token account as the
|
|
59
|
+
* funding source, initializes the escrow PDA and vault, and submits the
|
|
60
|
+
* transaction to the Solana network.
|
|
61
|
+
*
|
|
62
|
+
* @param params.agreement - The signed agreement containing the hash to escrow
|
|
63
|
+
* @param params.buyerKeypair - Buyer's Ed25519 keypair (signs the transaction)
|
|
64
|
+
* @param params.sellerPublicKey - Seller's 32-byte public key
|
|
65
|
+
* @param params.depositAmount - Amount in smallest token units (e.g., USDC has 6 decimals)
|
|
66
|
+
* @param params.mintAddress - SPL token mint address (e.g., USDC mint)
|
|
67
|
+
* @param params.buyerTokenAccount - Buyer's associated token account address
|
|
68
|
+
* @param params.timeoutSlots - Slots before escrow can be cancelled (default: ~24h)
|
|
69
|
+
* @param params.penaltyRateBps - Max penalty in basis points (default: 500 = 5%)
|
|
70
|
+
* @returns The escrow PDA address, vault address, and transaction signature
|
|
71
|
+
* @throws {OphirError} ESCROW_CREATION_FAILED if the transaction fails
|
|
72
|
+
* @throws {OphirError} INVALID_MESSAGE if inputs are invalid
|
|
73
|
+
*/
|
|
74
|
+
createEscrow(params: {
|
|
75
|
+
agreement: Agreement;
|
|
76
|
+
buyerKeypair: {
|
|
77
|
+
publicKey: Uint8Array;
|
|
78
|
+
secretKey: Uint8Array;
|
|
79
|
+
};
|
|
80
|
+
sellerPublicKey: Uint8Array;
|
|
81
|
+
depositAmount: bigint;
|
|
82
|
+
mintAddress: string;
|
|
83
|
+
buyerTokenAccount: string;
|
|
84
|
+
timeoutSlots?: number;
|
|
85
|
+
penaltyRateBps?: number;
|
|
86
|
+
}): Promise<{
|
|
87
|
+
escrowAddress: string;
|
|
88
|
+
vaultAddress: string;
|
|
89
|
+
txSignature: string;
|
|
90
|
+
}>;
|
|
91
|
+
/**
|
|
92
|
+
* Release escrowed funds to the seller after successful job completion.
|
|
93
|
+
*
|
|
94
|
+
* Only the seller can call this instruction. Transfers the entire vault
|
|
95
|
+
* balance to the seller's token account and marks the escrow as Released.
|
|
96
|
+
*
|
|
97
|
+
* @param params.escrowAddress - Base58-encoded escrow PDA address
|
|
98
|
+
* @param params.sellerKeypair - Seller's Ed25519 keypair (must match escrow.seller)
|
|
99
|
+
* @param params.sellerTokenAccount - Seller's token account to receive funds
|
|
100
|
+
* @param params.verificationHash - Optional 32-byte proof of service delivery
|
|
101
|
+
* @returns The transaction signature
|
|
102
|
+
* @throws {OphirError} ESCROW_ALREADY_RELEASED if the escrow is not Active
|
|
103
|
+
*/
|
|
104
|
+
releaseEscrow(params: {
|
|
105
|
+
escrowAddress: string;
|
|
106
|
+
sellerKeypair: {
|
|
107
|
+
publicKey: Uint8Array;
|
|
108
|
+
secretKey: Uint8Array;
|
|
109
|
+
};
|
|
110
|
+
sellerTokenAccount: string;
|
|
111
|
+
verificationHash?: Uint8Array;
|
|
112
|
+
}): Promise<{
|
|
113
|
+
txSignature: string;
|
|
114
|
+
}>;
|
|
115
|
+
/**
|
|
116
|
+
* File an on-chain dispute, splitting funds between buyer (penalty) and seller (remainder).
|
|
117
|
+
*
|
|
118
|
+
* Only the buyer can initiate a dispute. The penalty amount must not exceed
|
|
119
|
+
* `deposit_amount * penalty_rate_bps / 10000`. The penalty is returned to
|
|
120
|
+
* the buyer and the remainder goes to the seller.
|
|
121
|
+
*
|
|
122
|
+
* @param params.escrowAddress - Base58-encoded escrow PDA address
|
|
123
|
+
* @param params.buyerKeypair - Buyer's Ed25519 keypair (must match escrow.buyer)
|
|
124
|
+
* @param params.buyerTokenAccount - Buyer's token account for penalty refund
|
|
125
|
+
* @param params.sellerTokenAccount - Seller's token account for remainder
|
|
126
|
+
* @param params.evidenceHash - 32-byte SHA-256 hash of the violation evidence
|
|
127
|
+
* @param params.penaltyAmount - Penalty in smallest token units
|
|
128
|
+
* @returns The transaction signature
|
|
129
|
+
* @throws {OphirError} ESCROW_VERIFICATION_FAILED if penalty exceeds max allowed
|
|
130
|
+
*/
|
|
131
|
+
disputeEscrow(params: {
|
|
132
|
+
escrowAddress: string;
|
|
133
|
+
buyerKeypair: {
|
|
134
|
+
publicKey: Uint8Array;
|
|
135
|
+
secretKey: Uint8Array;
|
|
136
|
+
};
|
|
137
|
+
buyerTokenAccount: string;
|
|
138
|
+
sellerTokenAccount: string;
|
|
139
|
+
evidenceHash: Uint8Array;
|
|
140
|
+
penaltyAmount: bigint;
|
|
141
|
+
}): Promise<{
|
|
142
|
+
txSignature: string;
|
|
143
|
+
}>;
|
|
144
|
+
/**
|
|
145
|
+
* Cancel an escrow after the timeout slot has passed, returning all funds to the buyer.
|
|
146
|
+
*
|
|
147
|
+
* Only the buyer can cancel, and only after the escrow's timeout_slot has been reached.
|
|
148
|
+
* Transfers the entire vault balance back to the buyer's token account.
|
|
149
|
+
*
|
|
150
|
+
* @param params.escrowAddress - Base58-encoded escrow PDA address
|
|
151
|
+
* @param params.buyerKeypair - Buyer's Ed25519 keypair (must match escrow.buyer)
|
|
152
|
+
* @param params.buyerTokenAccount - Buyer's token account for refund
|
|
153
|
+
* @returns The transaction signature
|
|
154
|
+
* @throws {OphirError} ESCROW_TIMEOUT_NOT_REACHED if timeout has not elapsed
|
|
155
|
+
*/
|
|
156
|
+
cancelEscrow(params: {
|
|
157
|
+
escrowAddress: string;
|
|
158
|
+
buyerKeypair: {
|
|
159
|
+
publicKey: Uint8Array;
|
|
160
|
+
secretKey: Uint8Array;
|
|
161
|
+
};
|
|
162
|
+
buyerTokenAccount: string;
|
|
163
|
+
}): Promise<{
|
|
164
|
+
txSignature: string;
|
|
165
|
+
}>;
|
|
166
|
+
/**
|
|
167
|
+
* Fetch and deserialize escrow account data from Solana.
|
|
168
|
+
*
|
|
169
|
+
* Connects to the configured Solana RPC endpoint, fetches the raw account
|
|
170
|
+
* data at the given address, and deserializes it into a typed EscrowAccountData.
|
|
171
|
+
*
|
|
172
|
+
* @param escrowAddress - Base58-encoded escrow PDA address
|
|
173
|
+
* @returns The deserialized escrow account data
|
|
174
|
+
* @throws {OphirError} SOLANA_RPC_ERROR if the account doesn't exist or deserialization fails
|
|
175
|
+
*/
|
|
176
|
+
getEscrowStatus(escrowAddress: string): Promise<EscrowAccountData>;
|
|
177
|
+
}
|
package/dist/escrow.js
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { Connection, Keypair, PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, Transaction, TransactionInstruction, sendAndConfirmTransaction, } from '@solana/web3.js';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { OphirError, OphirErrorCode, DEFAULT_CONFIG, ESCROW_PROGRAM_ID } from '@ophirai/protocol';
|
|
4
|
+
const DEFAULT_RPC_URL = DEFAULT_CONFIG.solana_rpc;
|
|
5
|
+
const DEFAULT_PROGRAM_ID = ESCROW_PROGRAM_ID;
|
|
6
|
+
const DEFAULT_TIMEOUT_SLOTS = 216_000; // ~24h at 400ms slots
|
|
7
|
+
const DEFAULT_PENALTY_RATE_BPS = 500; // 5%
|
|
8
|
+
const SPL_TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
|
|
9
|
+
/** Numeric status discriminant matching the Anchor program's EscrowStatus enum. */
|
|
10
|
+
const ESCROW_STATUS_MAP = {
|
|
11
|
+
0: 'Active',
|
|
12
|
+
1: 'Released',
|
|
13
|
+
2: 'Disputed',
|
|
14
|
+
3: 'Cancelled',
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Compute the Anchor instruction discriminator for a given instruction name.
|
|
18
|
+
* Anchor uses sha256("global:<name>")[0..8] as the 8-byte discriminator.
|
|
19
|
+
*/
|
|
20
|
+
function anchorDiscriminator(name) {
|
|
21
|
+
const hash = createHash('sha256').update(`global:${name}`).digest();
|
|
22
|
+
return Buffer.from(hash.subarray(0, 8));
|
|
23
|
+
}
|
|
24
|
+
/** Anchor discriminators for each escrow instruction. */
|
|
25
|
+
const DISCRIMINATORS = {
|
|
26
|
+
makeEscrow: anchorDiscriminator('make_escrow'),
|
|
27
|
+
releaseEscrow: anchorDiscriminator('release_escrow'),
|
|
28
|
+
disputeEscrow: anchorDiscriminator('dispute_escrow'),
|
|
29
|
+
cancelEscrow: anchorDiscriminator('cancel_escrow'),
|
|
30
|
+
};
|
|
31
|
+
/** Anchor account discriminator for EscrowAccount (sha256("account:EscrowAccount")[0..8]). */
|
|
32
|
+
const ACCOUNT_DISCRIMINATOR = createHash('sha256')
|
|
33
|
+
.update('account:EscrowAccount')
|
|
34
|
+
.digest()
|
|
35
|
+
.subarray(0, 8);
|
|
36
|
+
/** Size of the EscrowAccount: 8 (discriminator) + 32*3 + 32 + 8 + 2 + 8 + 8 + 1 + 1 = 164 bytes. */
|
|
37
|
+
const ESCROW_ACCOUNT_SIZE = 8 + 32 + 32 + 32 + 32 + 8 + 2 + 8 + 8 + 1 + 1;
|
|
38
|
+
/**
|
|
39
|
+
* Serialize make_escrow instruction data in Borsh format.
|
|
40
|
+
* Layout: discriminator(8) + agreement_hash(32) + deposit_amount(u64) + timeout_slots(u64) + penalty_rate_bps(u16)
|
|
41
|
+
*/
|
|
42
|
+
function serializeMakeEscrow(agreementHash, depositAmount, timeoutSlots, penaltyRateBps) {
|
|
43
|
+
const buf = Buffer.alloc(8 + 32 + 8 + 8 + 2);
|
|
44
|
+
let offset = 0;
|
|
45
|
+
DISCRIMINATORS.makeEscrow.copy(buf, offset);
|
|
46
|
+
offset += 8;
|
|
47
|
+
Buffer.from(agreementHash).copy(buf, offset);
|
|
48
|
+
offset += 32;
|
|
49
|
+
buf.writeBigUInt64LE(depositAmount, offset);
|
|
50
|
+
offset += 8;
|
|
51
|
+
buf.writeBigUInt64LE(timeoutSlots, offset);
|
|
52
|
+
offset += 8;
|
|
53
|
+
buf.writeUInt16LE(penaltyRateBps, offset);
|
|
54
|
+
return buf;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Serialize release_escrow instruction data in Borsh format.
|
|
58
|
+
* Layout: discriminator(8) + option_flag(1) + [verification_hash(32) if Some]
|
|
59
|
+
*/
|
|
60
|
+
function serializeReleaseEscrow(verificationHash) {
|
|
61
|
+
if (verificationHash) {
|
|
62
|
+
const buf = Buffer.alloc(8 + 1 + 32);
|
|
63
|
+
DISCRIMINATORS.releaseEscrow.copy(buf, 0);
|
|
64
|
+
buf.writeUInt8(1, 8); // Some
|
|
65
|
+
Buffer.from(verificationHash).copy(buf, 9);
|
|
66
|
+
return buf;
|
|
67
|
+
}
|
|
68
|
+
const buf = Buffer.alloc(8 + 1);
|
|
69
|
+
DISCRIMINATORS.releaseEscrow.copy(buf, 0);
|
|
70
|
+
buf.writeUInt8(0, 8); // None
|
|
71
|
+
return buf;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Serialize dispute_escrow instruction data in Borsh format.
|
|
75
|
+
* Layout: discriminator(8) + violation_evidence_hash(32) + penalty_amount(u64)
|
|
76
|
+
*/
|
|
77
|
+
function serializeDisputeEscrow(evidenceHash, penaltyAmount) {
|
|
78
|
+
const buf = Buffer.alloc(8 + 32 + 8);
|
|
79
|
+
DISCRIMINATORS.disputeEscrow.copy(buf, 0);
|
|
80
|
+
Buffer.from(evidenceHash).copy(buf, 8);
|
|
81
|
+
buf.writeBigUInt64LE(penaltyAmount, 40);
|
|
82
|
+
return buf;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Serialize cancel_escrow instruction data in Borsh format.
|
|
86
|
+
* Layout: discriminator(8) only — no additional args.
|
|
87
|
+
*/
|
|
88
|
+
function serializeCancelEscrow() {
|
|
89
|
+
const buf = Buffer.alloc(8);
|
|
90
|
+
DISCRIMINATORS.cancelEscrow.copy(buf, 0);
|
|
91
|
+
return buf;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Deserialize on-chain EscrowAccount data from raw account bytes.
|
|
95
|
+
*
|
|
96
|
+
* Layout (after 8-byte Anchor discriminator):
|
|
97
|
+
* buyer: Pubkey(32) + seller: Pubkey(32) + mint: Pubkey(32) +
|
|
98
|
+
* agreement_hash: [u8;32](32) + deposit_amount: u64(8) +
|
|
99
|
+
* penalty_rate_bps: u16(2) + created_at: i64(8) + timeout_slot: u64(8) +
|
|
100
|
+
* status: u8(1) + bump: u8(1)
|
|
101
|
+
*/
|
|
102
|
+
function deserializeEscrowAccount(data) {
|
|
103
|
+
if (data.length < ESCROW_ACCOUNT_SIZE) {
|
|
104
|
+
throw new OphirError(OphirErrorCode.SOLANA_RPC_ERROR, `Invalid escrow account data: expected at least ${ESCROW_ACCOUNT_SIZE} bytes, got ${data.length}`);
|
|
105
|
+
}
|
|
106
|
+
// Verify Anchor discriminator
|
|
107
|
+
const disc = data.subarray(0, 8);
|
|
108
|
+
if (!disc.equals(Buffer.from(ACCOUNT_DISCRIMINATOR))) {
|
|
109
|
+
throw new OphirError(OphirErrorCode.SOLANA_RPC_ERROR, 'Invalid escrow account discriminator — account does not belong to the Ophir escrow program');
|
|
110
|
+
}
|
|
111
|
+
let offset = 8;
|
|
112
|
+
const buyer = new PublicKey(data.subarray(offset, offset + 32)).toBase58();
|
|
113
|
+
offset += 32;
|
|
114
|
+
const seller = new PublicKey(data.subarray(offset, offset + 32)).toBase58();
|
|
115
|
+
offset += 32;
|
|
116
|
+
const mint = new PublicKey(data.subarray(offset, offset + 32)).toBase58();
|
|
117
|
+
offset += 32;
|
|
118
|
+
const agreementHash = Buffer.from(data.subarray(offset, offset + 32)).toString('hex');
|
|
119
|
+
offset += 32;
|
|
120
|
+
const depositAmount = data.readBigUInt64LE(offset);
|
|
121
|
+
offset += 8;
|
|
122
|
+
const penaltyRateBps = data.readUInt16LE(offset);
|
|
123
|
+
offset += 2;
|
|
124
|
+
const createdAt = Number(data.readBigInt64LE(offset));
|
|
125
|
+
offset += 8;
|
|
126
|
+
const timeoutSlot = data.readBigUInt64LE(offset);
|
|
127
|
+
offset += 8;
|
|
128
|
+
const statusByte = data.readUInt8(offset);
|
|
129
|
+
offset += 1;
|
|
130
|
+
const bump = data.readUInt8(offset);
|
|
131
|
+
const status = ESCROW_STATUS_MAP[statusByte];
|
|
132
|
+
if (!status) {
|
|
133
|
+
throw new OphirError(OphirErrorCode.SOLANA_RPC_ERROR, `Unknown escrow status byte: ${statusByte}`);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
buyer,
|
|
137
|
+
seller,
|
|
138
|
+
mint,
|
|
139
|
+
agreementHash,
|
|
140
|
+
depositAmount,
|
|
141
|
+
penaltyRateBps,
|
|
142
|
+
createdAt,
|
|
143
|
+
timeoutSlot,
|
|
144
|
+
status,
|
|
145
|
+
bump,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Manages Solana escrow PDAs for agreement payments and dispute resolution.
|
|
150
|
+
*
|
|
151
|
+
* Derives deterministic PDA addresses from buyer pubkey and agreement hash,
|
|
152
|
+
* ensuring collision-resistant escrow accounts tied to specific agreements.
|
|
153
|
+
* Builds and submits Solana transactions for all escrow lifecycle operations.
|
|
154
|
+
*/
|
|
155
|
+
export class EscrowManager {
|
|
156
|
+
rpcUrl;
|
|
157
|
+
programId;
|
|
158
|
+
constructor(config) {
|
|
159
|
+
this.rpcUrl = config?.rpcUrl ?? DEFAULT_RPC_URL;
|
|
160
|
+
this.programId = new PublicKey(config?.programId ?? DEFAULT_PROGRAM_ID);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Derive the deterministic escrow PDA address from buyer pubkey and agreement hash.
|
|
164
|
+
* Seeds: ["escrow", buyer_pubkey, agreement_hash]
|
|
165
|
+
*
|
|
166
|
+
* @param buyerPublicKey - 32-byte Ed25519 public key of the buyer
|
|
167
|
+
* @param agreementHash - SHA-256 hash of the canonicalized agreement terms (32 bytes)
|
|
168
|
+
* @returns The base58-encoded PDA address and its bump seed
|
|
169
|
+
* @throws {OphirError} INVALID_MESSAGE if buyerPublicKey is not 32 bytes
|
|
170
|
+
*/
|
|
171
|
+
deriveEscrowAddress(buyerPublicKey, agreementHash) {
|
|
172
|
+
if (buyerPublicKey.length !== 32) {
|
|
173
|
+
throw new OphirError(OphirErrorCode.INVALID_MESSAGE, `Invalid buyer public key length: expected 32, got ${buyerPublicKey.length}`);
|
|
174
|
+
}
|
|
175
|
+
if (agreementHash.length !== 32) {
|
|
176
|
+
throw new OphirError(OphirErrorCode.INVALID_MESSAGE, `Invalid agreement hash length: expected 32, got ${agreementHash.length}`);
|
|
177
|
+
}
|
|
178
|
+
const [pda, bump] = PublicKey.findProgramAddressSync([Buffer.from('escrow'), buyerPublicKey, agreementHash], this.programId);
|
|
179
|
+
return { address: pda.toBase58(), bump };
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Derive the vault token account PDA from an escrow address.
|
|
183
|
+
* Seeds: ["vault", escrow_pubkey]
|
|
184
|
+
*
|
|
185
|
+
* @param escrowPublicKey - 32-byte public key of the escrow PDA
|
|
186
|
+
* @returns The base58-encoded vault PDA address and its bump seed
|
|
187
|
+
*/
|
|
188
|
+
deriveVaultAddress(escrowPublicKey) {
|
|
189
|
+
const [pda, bump] = PublicKey.findProgramAddressSync([Buffer.from('vault'), escrowPublicKey], this.programId);
|
|
190
|
+
return { address: pda.toBase58(), bump };
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Create an escrow account and deposit tokens into the PDA-controlled vault.
|
|
194
|
+
*
|
|
195
|
+
* Builds a `make_escrow` instruction with the buyer's token account as the
|
|
196
|
+
* funding source, initializes the escrow PDA and vault, and submits the
|
|
197
|
+
* transaction to the Solana network.
|
|
198
|
+
*
|
|
199
|
+
* @param params.agreement - The signed agreement containing the hash to escrow
|
|
200
|
+
* @param params.buyerKeypair - Buyer's Ed25519 keypair (signs the transaction)
|
|
201
|
+
* @param params.sellerPublicKey - Seller's 32-byte public key
|
|
202
|
+
* @param params.depositAmount - Amount in smallest token units (e.g., USDC has 6 decimals)
|
|
203
|
+
* @param params.mintAddress - SPL token mint address (e.g., USDC mint)
|
|
204
|
+
* @param params.buyerTokenAccount - Buyer's associated token account address
|
|
205
|
+
* @param params.timeoutSlots - Slots before escrow can be cancelled (default: ~24h)
|
|
206
|
+
* @param params.penaltyRateBps - Max penalty in basis points (default: 500 = 5%)
|
|
207
|
+
* @returns The escrow PDA address, vault address, and transaction signature
|
|
208
|
+
* @throws {OphirError} ESCROW_CREATION_FAILED if the transaction fails
|
|
209
|
+
* @throws {OphirError} INVALID_MESSAGE if inputs are invalid
|
|
210
|
+
*/
|
|
211
|
+
async createEscrow(params) {
|
|
212
|
+
if (params.depositAmount <= 0n) {
|
|
213
|
+
throw new OphirError(OphirErrorCode.ESCROW_CREATION_FAILED, 'Deposit amount must be greater than zero');
|
|
214
|
+
}
|
|
215
|
+
if (params.sellerPublicKey.length !== 32) {
|
|
216
|
+
throw new OphirError(OphirErrorCode.INVALID_MESSAGE, `Invalid seller public key length: expected 32, got ${params.sellerPublicKey.length}`);
|
|
217
|
+
}
|
|
218
|
+
const timeoutSlots = params.timeoutSlots ?? DEFAULT_TIMEOUT_SLOTS;
|
|
219
|
+
const penaltyRateBps = params.penaltyRateBps ?? DEFAULT_PENALTY_RATE_BPS;
|
|
220
|
+
if (penaltyRateBps > 10000) {
|
|
221
|
+
throw new OphirError(OphirErrorCode.ESCROW_CREATION_FAILED, `Penalty rate ${penaltyRateBps} exceeds maximum 10000 basis points`);
|
|
222
|
+
}
|
|
223
|
+
const hashBytes = Buffer.from(params.agreement.agreement_hash, 'hex');
|
|
224
|
+
if (hashBytes.length !== 32) {
|
|
225
|
+
throw new OphirError(OphirErrorCode.INVALID_MESSAGE, `Invalid agreement hash: expected 64 hex chars (32 bytes), got ${params.agreement.agreement_hash.length} chars`);
|
|
226
|
+
}
|
|
227
|
+
const buyerPubkey = new PublicKey(params.buyerKeypair.publicKey);
|
|
228
|
+
const sellerPubkey = new PublicKey(params.sellerPublicKey);
|
|
229
|
+
const mintPubkey = new PublicKey(params.mintAddress);
|
|
230
|
+
const buyerTokenPubkey = new PublicKey(params.buyerTokenAccount);
|
|
231
|
+
const { address: escrowAddress, bump: _escrowBump } = this.deriveEscrowAddress(params.buyerKeypair.publicKey, hashBytes);
|
|
232
|
+
const escrowPubkey = new PublicKey(escrowAddress);
|
|
233
|
+
const { address: vaultAddress } = this.deriveVaultAddress(escrowPubkey.toBytes());
|
|
234
|
+
const vaultPubkey = new PublicKey(vaultAddress);
|
|
235
|
+
const instructionData = serializeMakeEscrow(hashBytes, params.depositAmount, BigInt(timeoutSlots), penaltyRateBps);
|
|
236
|
+
const instruction = new TransactionInstruction({
|
|
237
|
+
keys: [
|
|
238
|
+
{ pubkey: buyerPubkey, isSigner: true, isWritable: true },
|
|
239
|
+
{ pubkey: sellerPubkey, isSigner: false, isWritable: false },
|
|
240
|
+
{ pubkey: escrowPubkey, isSigner: false, isWritable: true },
|
|
241
|
+
{ pubkey: vaultPubkey, isSigner: false, isWritable: true },
|
|
242
|
+
{ pubkey: buyerTokenPubkey, isSigner: false, isWritable: true },
|
|
243
|
+
{ pubkey: mintPubkey, isSigner: false, isWritable: false },
|
|
244
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
245
|
+
{ pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
246
|
+
{ pubkey: SYSVAR_RENT_PUBKEY, isSigner: false, isWritable: false },
|
|
247
|
+
],
|
|
248
|
+
programId: this.programId,
|
|
249
|
+
data: instructionData,
|
|
250
|
+
});
|
|
251
|
+
const connection = new Connection(this.rpcUrl, 'confirmed');
|
|
252
|
+
const buyerSigner = Keypair.fromSecretKey(params.buyerKeypair.secretKey);
|
|
253
|
+
const transaction = new Transaction().add(instruction);
|
|
254
|
+
try {
|
|
255
|
+
const txSignature = await sendAndConfirmTransaction(connection, transaction, [buyerSigner]);
|
|
256
|
+
return { escrowAddress, vaultAddress, txSignature };
|
|
257
|
+
}
|
|
258
|
+
catch (err) {
|
|
259
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
260
|
+
throw new OphirError(OphirErrorCode.ESCROW_CREATION_FAILED, `Failed to create escrow: ${message}`, { escrowAddress, vaultAddress });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Release escrowed funds to the seller after successful job completion.
|
|
265
|
+
*
|
|
266
|
+
* Only the seller can call this instruction. Transfers the entire vault
|
|
267
|
+
* balance to the seller's token account and marks the escrow as Released.
|
|
268
|
+
*
|
|
269
|
+
* @param params.escrowAddress - Base58-encoded escrow PDA address
|
|
270
|
+
* @param params.sellerKeypair - Seller's Ed25519 keypair (must match escrow.seller)
|
|
271
|
+
* @param params.sellerTokenAccount - Seller's token account to receive funds
|
|
272
|
+
* @param params.verificationHash - Optional 32-byte proof of service delivery
|
|
273
|
+
* @returns The transaction signature
|
|
274
|
+
* @throws {OphirError} ESCROW_ALREADY_RELEASED if the escrow is not Active
|
|
275
|
+
*/
|
|
276
|
+
async releaseEscrow(params) {
|
|
277
|
+
if (params.verificationHash && params.verificationHash.length !== 32) {
|
|
278
|
+
throw new OphirError(OphirErrorCode.INVALID_MESSAGE, `Invalid verification hash length: expected 32, got ${params.verificationHash.length}`);
|
|
279
|
+
}
|
|
280
|
+
const escrowPubkey = new PublicKey(params.escrowAddress);
|
|
281
|
+
const sellerPubkey = new PublicKey(params.sellerKeypair.publicKey);
|
|
282
|
+
const sellerTokenPubkey = new PublicKey(params.sellerTokenAccount);
|
|
283
|
+
const { address: vaultAddress } = this.deriveVaultAddress(escrowPubkey.toBytes());
|
|
284
|
+
const vaultPubkey = new PublicKey(vaultAddress);
|
|
285
|
+
const instructionData = serializeReleaseEscrow(params.verificationHash);
|
|
286
|
+
const instruction = new TransactionInstruction({
|
|
287
|
+
keys: [
|
|
288
|
+
{ pubkey: sellerPubkey, isSigner: true, isWritable: true },
|
|
289
|
+
{ pubkey: escrowPubkey, isSigner: false, isWritable: true },
|
|
290
|
+
{ pubkey: vaultPubkey, isSigner: false, isWritable: true },
|
|
291
|
+
{ pubkey: sellerTokenPubkey, isSigner: false, isWritable: true },
|
|
292
|
+
{ pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
293
|
+
],
|
|
294
|
+
programId: this.programId,
|
|
295
|
+
data: instructionData,
|
|
296
|
+
});
|
|
297
|
+
const connection = new Connection(this.rpcUrl, 'confirmed');
|
|
298
|
+
const sellerSigner = Keypair.fromSecretKey(params.sellerKeypair.secretKey);
|
|
299
|
+
const transaction = new Transaction().add(instruction);
|
|
300
|
+
try {
|
|
301
|
+
const txSignature = await sendAndConfirmTransaction(connection, transaction, [sellerSigner]);
|
|
302
|
+
return { txSignature };
|
|
303
|
+
}
|
|
304
|
+
catch (err) {
|
|
305
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
306
|
+
throw new OphirError(OphirErrorCode.ESCROW_ALREADY_RELEASED, `Failed to release escrow: ${message}`, { escrowAddress: params.escrowAddress });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* File an on-chain dispute, splitting funds between buyer (penalty) and seller (remainder).
|
|
311
|
+
*
|
|
312
|
+
* Only the buyer can initiate a dispute. The penalty amount must not exceed
|
|
313
|
+
* `deposit_amount * penalty_rate_bps / 10000`. The penalty is returned to
|
|
314
|
+
* the buyer and the remainder goes to the seller.
|
|
315
|
+
*
|
|
316
|
+
* @param params.escrowAddress - Base58-encoded escrow PDA address
|
|
317
|
+
* @param params.buyerKeypair - Buyer's Ed25519 keypair (must match escrow.buyer)
|
|
318
|
+
* @param params.buyerTokenAccount - Buyer's token account for penalty refund
|
|
319
|
+
* @param params.sellerTokenAccount - Seller's token account for remainder
|
|
320
|
+
* @param params.evidenceHash - 32-byte SHA-256 hash of the violation evidence
|
|
321
|
+
* @param params.penaltyAmount - Penalty in smallest token units
|
|
322
|
+
* @returns The transaction signature
|
|
323
|
+
* @throws {OphirError} ESCROW_VERIFICATION_FAILED if penalty exceeds max allowed
|
|
324
|
+
*/
|
|
325
|
+
async disputeEscrow(params) {
|
|
326
|
+
if (params.evidenceHash.length !== 32) {
|
|
327
|
+
throw new OphirError(OphirErrorCode.DISPUTE_INVALID_EVIDENCE, `Invalid evidence hash length: expected 32, got ${params.evidenceHash.length}`);
|
|
328
|
+
}
|
|
329
|
+
if (params.penaltyAmount < 0n) {
|
|
330
|
+
throw new OphirError(OphirErrorCode.ESCROW_VERIFICATION_FAILED, 'Penalty amount cannot be negative');
|
|
331
|
+
}
|
|
332
|
+
const escrowPubkey = new PublicKey(params.escrowAddress);
|
|
333
|
+
const buyerPubkey = new PublicKey(params.buyerKeypair.publicKey);
|
|
334
|
+
const buyerTokenPubkey = new PublicKey(params.buyerTokenAccount);
|
|
335
|
+
const sellerTokenPubkey = new PublicKey(params.sellerTokenAccount);
|
|
336
|
+
const { address: vaultAddress } = this.deriveVaultAddress(escrowPubkey.toBytes());
|
|
337
|
+
const vaultPubkey = new PublicKey(vaultAddress);
|
|
338
|
+
const instructionData = serializeDisputeEscrow(params.evidenceHash, params.penaltyAmount);
|
|
339
|
+
const instruction = new TransactionInstruction({
|
|
340
|
+
keys: [
|
|
341
|
+
{ pubkey: buyerPubkey, isSigner: true, isWritable: true },
|
|
342
|
+
{ pubkey: escrowPubkey, isSigner: false, isWritable: true },
|
|
343
|
+
{ pubkey: vaultPubkey, isSigner: false, isWritable: true },
|
|
344
|
+
{ pubkey: buyerTokenPubkey, isSigner: false, isWritable: true },
|
|
345
|
+
{ pubkey: sellerTokenPubkey, isSigner: false, isWritable: true },
|
|
346
|
+
{ pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
347
|
+
],
|
|
348
|
+
programId: this.programId,
|
|
349
|
+
data: instructionData,
|
|
350
|
+
});
|
|
351
|
+
const connection = new Connection(this.rpcUrl, 'confirmed');
|
|
352
|
+
const buyerSigner = Keypair.fromSecretKey(params.buyerKeypair.secretKey);
|
|
353
|
+
const transaction = new Transaction().add(instruction);
|
|
354
|
+
try {
|
|
355
|
+
const txSignature = await sendAndConfirmTransaction(connection, transaction, [buyerSigner]);
|
|
356
|
+
return { txSignature };
|
|
357
|
+
}
|
|
358
|
+
catch (err) {
|
|
359
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
360
|
+
throw new OphirError(OphirErrorCode.ESCROW_VERIFICATION_FAILED, `Failed to dispute escrow: ${message}`, { escrowAddress: params.escrowAddress });
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Cancel an escrow after the timeout slot has passed, returning all funds to the buyer.
|
|
365
|
+
*
|
|
366
|
+
* Only the buyer can cancel, and only after the escrow's timeout_slot has been reached.
|
|
367
|
+
* Transfers the entire vault balance back to the buyer's token account.
|
|
368
|
+
*
|
|
369
|
+
* @param params.escrowAddress - Base58-encoded escrow PDA address
|
|
370
|
+
* @param params.buyerKeypair - Buyer's Ed25519 keypair (must match escrow.buyer)
|
|
371
|
+
* @param params.buyerTokenAccount - Buyer's token account for refund
|
|
372
|
+
* @returns The transaction signature
|
|
373
|
+
* @throws {OphirError} ESCROW_TIMEOUT_NOT_REACHED if timeout has not elapsed
|
|
374
|
+
*/
|
|
375
|
+
async cancelEscrow(params) {
|
|
376
|
+
const escrowPubkey = new PublicKey(params.escrowAddress);
|
|
377
|
+
const buyerPubkey = new PublicKey(params.buyerKeypair.publicKey);
|
|
378
|
+
const buyerTokenPubkey = new PublicKey(params.buyerTokenAccount);
|
|
379
|
+
const { address: vaultAddress } = this.deriveVaultAddress(escrowPubkey.toBytes());
|
|
380
|
+
const vaultPubkey = new PublicKey(vaultAddress);
|
|
381
|
+
const instructionData = serializeCancelEscrow();
|
|
382
|
+
const instruction = new TransactionInstruction({
|
|
383
|
+
keys: [
|
|
384
|
+
{ pubkey: buyerPubkey, isSigner: true, isWritable: true },
|
|
385
|
+
{ pubkey: escrowPubkey, isSigner: false, isWritable: true },
|
|
386
|
+
{ pubkey: vaultPubkey, isSigner: false, isWritable: true },
|
|
387
|
+
{ pubkey: buyerTokenPubkey, isSigner: false, isWritable: true },
|
|
388
|
+
{ pubkey: SPL_TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
|
|
389
|
+
],
|
|
390
|
+
programId: this.programId,
|
|
391
|
+
data: instructionData,
|
|
392
|
+
});
|
|
393
|
+
const connection = new Connection(this.rpcUrl, 'confirmed');
|
|
394
|
+
const buyerSigner = Keypair.fromSecretKey(params.buyerKeypair.secretKey);
|
|
395
|
+
const transaction = new Transaction().add(instruction);
|
|
396
|
+
try {
|
|
397
|
+
const txSignature = await sendAndConfirmTransaction(connection, transaction, [buyerSigner]);
|
|
398
|
+
return { txSignature };
|
|
399
|
+
}
|
|
400
|
+
catch (err) {
|
|
401
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
402
|
+
throw new OphirError(OphirErrorCode.ESCROW_TIMEOUT_NOT_REACHED, `Failed to cancel escrow: ${message}`, { escrowAddress: params.escrowAddress });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Fetch and deserialize escrow account data from Solana.
|
|
407
|
+
*
|
|
408
|
+
* Connects to the configured Solana RPC endpoint, fetches the raw account
|
|
409
|
+
* data at the given address, and deserializes it into a typed EscrowAccountData.
|
|
410
|
+
*
|
|
411
|
+
* @param escrowAddress - Base58-encoded escrow PDA address
|
|
412
|
+
* @returns The deserialized escrow account data
|
|
413
|
+
* @throws {OphirError} SOLANA_RPC_ERROR if the account doesn't exist or deserialization fails
|
|
414
|
+
*/
|
|
415
|
+
async getEscrowStatus(escrowAddress) {
|
|
416
|
+
const connection = new Connection(this.rpcUrl, 'confirmed');
|
|
417
|
+
const pubkey = new PublicKey(escrowAddress);
|
|
418
|
+
let accountInfo;
|
|
419
|
+
try {
|
|
420
|
+
accountInfo = await connection.getAccountInfo(pubkey);
|
|
421
|
+
}
|
|
422
|
+
catch (err) {
|
|
423
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
424
|
+
throw new OphirError(OphirErrorCode.SOLANA_RPC_ERROR, `Failed to fetch escrow account: ${message}`, { escrowAddress });
|
|
425
|
+
}
|
|
426
|
+
if (!accountInfo) {
|
|
427
|
+
throw new OphirError(OphirErrorCode.SOLANA_RPC_ERROR, `Escrow account not found at address: ${escrowAddress}`, { escrowAddress });
|
|
428
|
+
}
|
|
429
|
+
if (!accountInfo.owner.equals(this.programId)) {
|
|
430
|
+
throw new OphirError(OphirErrorCode.SOLANA_RPC_ERROR, `Account at ${escrowAddress} is not owned by the Ophir escrow program`, { escrowAddress, owner: accountInfo.owner.toBase58() });
|
|
431
|
+
}
|
|
432
|
+
return deserializeEscrowAccount(Buffer.from(accountInfo.data));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate an Ed25519 keypair for agent identity.
|
|
3
|
+
*
|
|
4
|
+
* @throws {OphirError} if the generated keypair has unexpected key lengths.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* const { publicKey, secretKey } = generateKeyPair();
|
|
9
|
+
* // publicKey: Uint8Array(32), secretKey: Uint8Array(64)
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare function generateKeyPair(): {
|
|
13
|
+
publicKey: Uint8Array;
|
|
14
|
+
secretKey: Uint8Array;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Convert Ed25519 public key to did:key:z6Mk... format.
|
|
18
|
+
* Prepends multicodec prefix (0xed01) then base58-btc encodes with 'z' prefix.
|
|
19
|
+
*
|
|
20
|
+
* @throws {OphirError} if publicKey is not exactly 32 bytes.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const did = publicKeyToDid(keypair.publicKey);
|
|
25
|
+
* // 'did:key:z6Mk...'
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function publicKeyToDid(publicKey: Uint8Array): string;
|
|
29
|
+
/**
|
|
30
|
+
* Extract Ed25519 public key from did:key string.
|
|
31
|
+
*
|
|
32
|
+
* @throws {OphirError} if input is empty, DID format is invalid, or extracted key is not 32 bytes.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const publicKey = didToPublicKey('did:key:z6Mk...');
|
|
37
|
+
* // Uint8Array(32)
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function didToPublicKey(did: string): Uint8Array;
|
|
41
|
+
/**
|
|
42
|
+
* Generate a complete agent identity bundle.
|
|
43
|
+
*
|
|
44
|
+
* @param endpoint - The HTTPS endpoint URL for the agent.
|
|
45
|
+
* @throws {OphirError} if endpoint is not a valid URL with http or https protocol.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const identity = generateAgentIdentity('https://agent.example.com');
|
|
50
|
+
* // { agentId: 'did:key:z6Mk...', keypair: { publicKey, secretKey }, endpoint: 'https://...' }
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export declare function generateAgentIdentity(endpoint: string): {
|
|
54
|
+
agentId: string;
|
|
55
|
+
keypair: {
|
|
56
|
+
publicKey: Uint8Array;
|
|
57
|
+
secretKey: Uint8Array;
|
|
58
|
+
};
|
|
59
|
+
endpoint: string;
|
|
60
|
+
};
|