Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/contracts-api/attestationAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,8 +216,8 @@ export class AttestationAction extends Action {
/**
* List attestation metadata with optional filtering
*
* This returns metadata for attestations, optionally filtered by requester address or request transaction ID.
* Supports pagination and sorting.
* This returns metadata for attestations, optionally filtered by requester address, request transaction ID,
* attestation hash, or result canonical bytes. Supports pagination and sorting.
*
* @param input - Filter and pagination parameters
* @returns Promise resolving to array of attestation metadata
Expand Down Expand Up @@ -254,6 +254,8 @@ export class AttestationAction extends Action {
const params: Types.NamedParams = {
$requester: input.requester ?? new Uint8Array(0),
$request_tx_id: input.requestTxId ?? null,
$attestation_hash: input.attestationHash ?? new Uint8Array(0),
$result_canonical: input.resultCanonical ?? new Uint8Array(0),
$limit: limit,
$offset: offset,
$order_by: input.orderBy ?? null,
Expand Down
31 changes: 31 additions & 0 deletions src/types/attestation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ export interface ListAttestationsInput {
*/
requestTxId?: string;

/**
* Optional: Filter by attestation hash
* If provided, only returns attestations matching this hash
*/
attestationHash?: Uint8Array;

/**
* Optional: Filter by result canonical bytes
* If provided, only returns attestations matching this canonical result
*/
resultCanonical?: Uint8Array;

/**
* Optional: Maximum number of results (1-5000, default 5000)
*/
Expand Down Expand Up @@ -451,6 +463,9 @@ if (import.meta.vitest) {
expect(() =>
validateListAttestationsInput({
requester: new Uint8Array(20),
requestTxId: 'abc123',
attestationHash: new Uint8Array(32),
resultCanonical: new Uint8Array(100),
limit: 100,
offset: 0,
orderBy: 'created_height desc',
Expand All @@ -462,6 +477,22 @@ if (import.meta.vitest) {
expect(() => validateListAttestationsInput({})).not.toThrow();
});

it('should accept attestationHash filter', () => {
expect(() =>
validateListAttestationsInput({
attestationHash: new Uint8Array(32),
})
).not.toThrow();
});

it('should accept resultCanonical filter', () => {
expect(() =>
validateListAttestationsInput({
resultCanonical: new Uint8Array(64),
})
).not.toThrow();
});

it('should reject limit < 1', () => {
expect(() =>
validateListAttestationsInput({
Expand Down
101 changes: 101 additions & 0 deletions tests/integration/attestation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -408,5 +408,106 @@ describe.skip.sequential(
console.log(`Filtered by request_tx_id: found ${filtered.length} attestation(s) with TX ID ${filtered[0].requestTxId}`);
}
);

testWithDefaultWallet(
"should filter attestations by attestation hash",
async ({ defaultClient }) => {
const attestationAction = defaultClient.loadAttestationAction();

// First, list attestations to get an attestation hash to filter by
const allAttestations = await attestationAction.listAttestations({
limit: 1,
});

// If we have at least one attestation, test filtering by its hash
if (allAttestations.length > 0) {
const targetAttestation = allAttestations[0];
console.log(`Filtering by attestation hash: ${Buffer.from(targetAttestation.attestationHash).toString('hex')}`);

const filtered = await attestationAction.listAttestations({
attestationHash: targetAttestation.attestationHash,
});

// Should return array with at least 1 result
expect(Array.isArray(filtered)).toBe(true);
expect(filtered.length).toBeGreaterThan(0);

// All returned attestations should match the attestation hash
filtered.forEach((att) => {
expect(Buffer.from(att.attestationHash).toString('hex')).toBe(
Buffer.from(targetAttestation.attestationHash).toString('hex')
);
});

console.log(`Filtered by attestation_hash: found ${filtered.length} attestation(s)`);
} else {
console.log("No attestations available to test attestation_hash filter");
}
}
);

testWithDefaultWallet(
"should filter attestations by result canonical",
async ({ defaultClient }) => {
const attestationAction = defaultClient.loadAttestationAction();

// Create an attestation and wait for it to be signed
const dataProvider = "0x4710a8d8f0d845da110086812a32de6d90d7ff5c";
const streamId = "stai0000000000000000000000000000";
const now = Math.floor(Date.now() / 1000);
const weekAgo = now - 7 * 24 * 60 * 60;
const weekAgoOffset = weekAgo + 14400; // Add 4 hours offset for uniqueness

const requestResult = await attestationAction.requestAttestation({
dataProvider,
streamId,
actionName: "get_record",
args: [dataProvider, streamId, weekAgoOffset, now, null, false],
encryptSig: false,
maxFee: '50000000000000000000', // 50 TRUF (attestation fee is 40 TRUF)
});

// Wait for transaction to be mined
await defaultClient.waitForTx(requestResult.requestTxId, 30000);

// Get the signed attestation to extract result_canonical
// Wait for signature first
let signed = null;
for (let i = 0; i < 10; i++) {
try {
signed = await attestationAction.getSignedAttestation({
requestTxId: requestResult.requestTxId,
});
if (signed.payload && signed.payload.length > 65) break;
} catch (e) {
await new Promise((resolve) => setTimeout(resolve, 2000));
}
}

if (signed && signed.payload) {
// Extract result_canonical from payload (payload = canonical + signature)
// The canonical portion is everything except the last 65 bytes
const resultCanonical = signed.payload.slice(0, -65);

console.log(`Testing result_canonical filter (${resultCanonical.length} bytes)`);

// Filter by result_canonical
const filtered = await attestationAction.listAttestations({
resultCanonical: resultCanonical,
});

expect(Array.isArray(filtered)).toBe(true);
expect(filtered.length).toBeGreaterThan(0);

// Verify that the filtered attestation matches our request
const matchingAttestation = filtered.find(att => att.requestTxId === requestResult.requestTxId);
expect(matchingAttestation).toBeTruthy();

console.log(`Result canonical filter test completed: found ${filtered.length} attestation(s)`);
} else {
console.log("Attestation not signed yet, skipping result_canonical filter test");
}
}
);
}
);
Loading