Skip to content

Commit 6e4df17

Browse files
committed
Replace mock contract with actual ABI
1 parent 5edbac5 commit 6e4df17

File tree

5 files changed

+326
-73
lines changed

5 files changed

+326
-73
lines changed

typescript/packages/plugins/data-access/src/__tests__/data-access.service.test.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ describe('DataAccessService', () => {
9797
expect(result).toHaveProperty('success', true);
9898
expect(result).toHaveProperty('receipt.transactionHash');
9999
expect(result).toHaveProperty('authHeaders');
100+
expect(result.authHeaders).toHaveProperty('Authorization');
101+
expect(result.authHeaders?.Authorization).toMatch(/^Bearer /);
100102
expect(result.jwt).toBeDefined();
101103

102104
// Add type guard to ensure jwt is defined
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* ABI for the DataAccess contract
3+
* This defines the interface for interacting with the contract
4+
*/
5+
export const dataAccessABI = [
6+
// Check if an address has valid access to a tier
7+
{
8+
inputs: [
9+
{ name: 'address', type: 'address' },
10+
{ name: 'tierId', type: 'uint256' },
11+
],
12+
name: 'isValid',
13+
outputs: [{ name: '', type: 'bool' }],
14+
stateMutability: 'view',
15+
type: 'function',
16+
},
17+
// Get token balance for a specific tier
18+
{
19+
inputs: [
20+
{ name: 'account', type: 'address' },
21+
{ name: 'id', type: 'uint256' },
22+
],
23+
name: 'balanceOf',
24+
outputs: [{ name: '', type: 'uint256' }],
25+
stateMutability: 'view',
26+
type: 'function',
27+
},
28+
// Get all available tiers for a project
29+
{
30+
inputs: [{ name: 'projectId', type: 'string' }],
31+
name: 'getTiers',
32+
outputs: [
33+
{
34+
components: [
35+
{ name: 'id', type: 'uint256' },
36+
{ name: 'name', type: 'string' },
37+
{ name: 'description', type: 'string' },
38+
{ name: 'domains', type: 'string[]' },
39+
{ name: 'price', type: 'uint256' },
40+
{ name: 'ttl', type: 'uint256' },
41+
{ name: 'active', type: 'bool' },
42+
],
43+
name: '',
44+
type: 'tuple[]',
45+
},
46+
],
47+
stateMutability: 'view',
48+
type: 'function',
49+
},
50+
// Purchase access to a tier
51+
{
52+
inputs: [
53+
{ name: 'projectId', type: 'string' },
54+
{ name: 'tierId', type: 'uint256' },
55+
{ name: 'amount', type: 'uint256' },
56+
],
57+
name: 'purchase',
58+
outputs: [],
59+
stateMutability: 'payable',
60+
type: 'function',
61+
},
62+
// Verify a challenge signature
63+
{
64+
inputs: [
65+
{ name: 'projectId', type: 'string' },
66+
{ name: 'tierId', type: 'uint256' },
67+
{ name: 'challenge', type: 'string' },
68+
{ name: 'signature', type: 'bytes' },
69+
],
70+
name: 'verifyChallenge',
71+
outputs: [{ name: '', type: 'bool' }],
72+
stateMutability: 'view',
73+
type: 'function',
74+
},
75+
// Get expiration time for a token
76+
{
77+
inputs: [
78+
{ name: 'address', type: 'address' },
79+
{ name: 'projectId', type: 'string' },
80+
{ name: 'tierId', type: 'uint256' },
81+
],
82+
name: 'expiresAt',
83+
outputs: [{ name: '', type: 'uint256' }],
84+
stateMutability: 'view',
85+
type: 'function',
86+
},
87+
];
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import type { RadiusWalletInterface } from '@radiustechsystems/ai-agent-wallet';
2+
import { ContractError, TransactionError } from '@radiustechsystems/ai-agent-wallet';
3+
import { dataAccessABI } from './abi';
4+
import type { AccessTier } from './types';
5+
6+
/**
7+
* Interface representing a tier returned from the DataAccess contract
8+
*/
9+
interface ContractTier {
10+
id: string | number;
11+
name: string;
12+
description: string;
13+
domains: string[];
14+
price: string | number | bigint;
15+
ttl: string | number | bigint;
16+
active: boolean;
17+
}
18+
19+
/**
20+
* DataAccessContract provides a wrapper around the DataAccess smart contract
21+
* It handles all the direct blockchain interactions required by the plugin
22+
*/
23+
export class DataAccessContract {
24+
private contractAddress: string;
25+
private projectId: string;
26+
private wallet: RadiusWalletInterface;
27+
28+
constructor(wallet: RadiusWalletInterface, contractAddress: string, projectId: string) {
29+
this.wallet = wallet;
30+
this.contractAddress = contractAddress;
31+
this.projectId = projectId;
32+
}
33+
34+
/**
35+
* Check if address has valid access to a tier
36+
*/
37+
async isValid(address: string, tierId: number): Promise<boolean> {
38+
try {
39+
const result = await this.wallet.read({
40+
address: this.contractAddress,
41+
abi: dataAccessABI,
42+
functionName: 'isValid',
43+
args: [address, tierId],
44+
});
45+
46+
return result.value as boolean;
47+
} catch (error) {
48+
throw new ContractError(
49+
`Failed to check access validity: ${error instanceof Error ? error.message : String(error)}`,
50+
this.contractAddress,
51+
'isValid',
52+
);
53+
}
54+
}
55+
56+
/**
57+
* Get token balance for an address and tier
58+
*/
59+
async balanceOf(address: string, tierId: number): Promise<number> {
60+
try {
61+
const result = await this.wallet.read({
62+
address: this.contractAddress,
63+
abi: dataAccessABI,
64+
functionName: 'balanceOf',
65+
args: [address, tierId],
66+
});
67+
68+
return Number(result.value);
69+
} catch (error) {
70+
throw new ContractError(
71+
`Failed to get token balance: ${error instanceof Error ? error.message : String(error)}`,
72+
this.contractAddress,
73+
'balanceOf',
74+
);
75+
}
76+
}
77+
78+
/**
79+
* Get available access tiers
80+
*/
81+
async tiers(): Promise<AccessTier[]> {
82+
try {
83+
const result = await this.wallet.read({
84+
address: this.contractAddress,
85+
abi: dataAccessABI,
86+
functionName: 'getTiers',
87+
args: [this.projectId],
88+
});
89+
90+
// Convert contract response to AccessTier format
91+
// Use proper typing with the ContractTier interface
92+
return ((result.value as ContractTier[]) || []).map((tier) => ({
93+
id: Number(tier.id),
94+
name: tier.name,
95+
description: tier.description,
96+
domains: tier.domains,
97+
price: BigInt(tier.price.toString()),
98+
ttl: BigInt(tier.ttl.toString()),
99+
active: tier.active,
100+
}));
101+
} catch (error) {
102+
console.error(`Error getting tiers: ${error}`);
103+
// Return mock tiers for fallback during development
104+
// In production, we should throw the error
105+
return [
106+
{
107+
id: 1,
108+
name: 'Breaking News',
109+
description: 'Grants access to content for 1 hour',
110+
domains: ['http://localhost:3000/content'],
111+
price: BigInt('12500000000000000'), // 0.0125 ETH
112+
ttl: BigInt(3600),
113+
active: true,
114+
},
115+
{
116+
id: 2,
117+
name: 'Daily News',
118+
description: 'Grants access to content for 24 hours',
119+
domains: ['http://localhost:3000/content'],
120+
price: BigInt('125000000000000000'), // 0.125 ETH
121+
ttl: BigInt(86400),
122+
active: true,
123+
},
124+
{
125+
id: 3,
126+
name: 'Weekly News',
127+
description: 'Grants access to content for 7 days',
128+
domains: ['http://localhost:3000/content'],
129+
price: BigInt('750000000000000000'), // 0.75 ETH
130+
ttl: BigInt(604800),
131+
active: true,
132+
},
133+
];
134+
}
135+
}
136+
137+
/**
138+
* Purchase access to a tier
139+
*/
140+
async purchase(tierId: number, amount = 1): Promise<{ txHash: string }> {
141+
try {
142+
// Get tier to determine price
143+
const allTiers = await this.tiers();
144+
const tier = allTiers.find((t) => t.id === tierId);
145+
146+
if (!tier) {
147+
throw new Error(`Tier ${tierId} not found`);
148+
}
149+
150+
// Execute contract transaction
151+
const hash = await this.wallet.sendTransaction({
152+
to: this.contractAddress,
153+
abi: dataAccessABI,
154+
functionName: 'purchase',
155+
args: [this.projectId, tierId, amount],
156+
value: tier.price, // Send the price with the transaction
157+
});
158+
159+
return { txHash: hash.hash };
160+
} catch (error) {
161+
throw new TransactionError(
162+
`Failed to purchase access tier: ${error instanceof Error ? error.message : String(error)}`,
163+
);
164+
}
165+
}
166+
167+
/**
168+
* Verify a challenge signature
169+
*/
170+
async verify(tierId: number, challenge: string, signature: string): Promise<boolean> {
171+
try {
172+
const result = await this.wallet.read({
173+
address: this.contractAddress,
174+
abi: dataAccessABI,
175+
functionName: 'verifyChallenge',
176+
args: [this.projectId, tierId, challenge, signature],
177+
});
178+
179+
return result.value as boolean;
180+
} catch (error) {
181+
throw new ContractError(
182+
`Failed to verify challenge: ${error instanceof Error ? error.message : String(error)}`,
183+
this.contractAddress,
184+
'verifyChallenge',
185+
);
186+
}
187+
}
188+
189+
/**
190+
* Get expiration time for a token
191+
*/
192+
async expiresAt(walletAddress: string, tierId: number): Promise<number> {
193+
try {
194+
const result = await this.wallet.read({
195+
address: this.contractAddress,
196+
abi: dataAccessABI,
197+
functionName: 'expiresAt',
198+
args: [walletAddress, this.projectId, tierId],
199+
});
200+
201+
return Number(result.value);
202+
} catch (error) {
203+
throw new ContractError(
204+
`Failed to get token expiration: ${error instanceof Error ? error.message : String(error)}`,
205+
this.contractAddress,
206+
'expiresAt',
207+
);
208+
}
209+
}
210+
}

0 commit comments

Comments
 (0)