openclaw-algorand-plugin 0.5.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/LICENSE +21 -0
- package/README.md +112 -0
- package/index.ts +361 -0
- package/lib/mcp-servers.ts +14 -0
- package/lib/x402-fetch.ts +213 -0
- package/memory/algorand-plugin.md +82 -0
- package/openclaw.plugin.json +30 -0
- package/package.json +38 -0
- package/setup.ts +80 -0
- package/skills/algorand-development/SKILL.md +90 -0
- package/skills/algorand-development/references/build-smart-contracts-reference.md +79 -0
- package/skills/algorand-development/references/build-smart-contracts.md +52 -0
- package/skills/algorand-development/references/create-project-reference.md +86 -0
- package/skills/algorand-development/references/create-project.md +89 -0
- package/skills/algorand-development/references/implement-arc-standards-arc32-arc56.md +396 -0
- package/skills/algorand-development/references/implement-arc-standards-arc4.md +265 -0
- package/skills/algorand-development/references/implement-arc-standards.md +92 -0
- package/skills/algorand-development/references/search-algorand-examples-reference.md +119 -0
- package/skills/algorand-development/references/search-algorand-examples.md +89 -0
- package/skills/algorand-development/references/troubleshoot-errors-contract.md +373 -0
- package/skills/algorand-development/references/troubleshoot-errors-transaction.md +599 -0
- package/skills/algorand-development/references/troubleshoot-errors.md +105 -0
- package/skills/algorand-development/references/use-algokit-cli-reference.md +228 -0
- package/skills/algorand-development/references/use-algokit-cli.md +64 -0
- package/skills/algorand-interaction/SKILL.md +223 -0
- package/skills/algorand-interaction/references/algorand-mcp.md +743 -0
- package/skills/algorand-interaction/references/examples-algorand-mcp.md +647 -0
- package/skills/algorand-python/SKILL.md +95 -0
- package/skills/algorand-python/references/build-smart-contracts-decorators.md +413 -0
- package/skills/algorand-python/references/build-smart-contracts-reference.md +55 -0
- package/skills/algorand-python/references/build-smart-contracts-storage.md +452 -0
- package/skills/algorand-python/references/build-smart-contracts-transactions.md +445 -0
- package/skills/algorand-python/references/build-smart-contracts-types.md +438 -0
- package/skills/algorand-python/references/build-smart-contracts.md +82 -0
- package/skills/algorand-python/references/create-project-reference.md +55 -0
- package/skills/algorand-python/references/create-project.md +75 -0
- package/skills/algorand-python/references/implement-arc-standards-arc32-arc56.md +101 -0
- package/skills/algorand-python/references/implement-arc-standards-arc4.md +154 -0
- package/skills/algorand-python/references/implement-arc-standards.md +39 -0
- package/skills/algorand-python/references/troubleshoot-errors-contract.md +355 -0
- package/skills/algorand-python/references/troubleshoot-errors-transaction.md +430 -0
- package/skills/algorand-python/references/troubleshoot-errors.md +46 -0
- package/skills/algorand-python/references/use-algokit-utils-reference.md +350 -0
- package/skills/algorand-python/references/use-algokit-utils.md +76 -0
- package/skills/algorand-typescript/SKILL.md +131 -0
- package/skills/algorand-typescript/references/algorand-ts-migration-from-beta.md +448 -0
- package/skills/algorand-typescript/references/algorand-ts-migration-from-tealscript.md +487 -0
- package/skills/algorand-typescript/references/algorand-ts-migration.md +102 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-methods-and-abi.md +134 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-reference.md +58 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-storage.md +154 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-transactions.md +187 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax-types-and-values.md +150 -0
- package/skills/algorand-typescript/references/algorand-typescript-syntax.md +84 -0
- package/skills/algorand-typescript/references/build-smart-contracts-reference.md +52 -0
- package/skills/algorand-typescript/references/build-smart-contracts.md +74 -0
- package/skills/algorand-typescript/references/call-smart-contracts-reference.md +237 -0
- package/skills/algorand-typescript/references/call-smart-contracts.md +183 -0
- package/skills/algorand-typescript/references/create-project-reference.md +53 -0
- package/skills/algorand-typescript/references/create-project.md +86 -0
- package/skills/algorand-typescript/references/deploy-react-frontend-examples.md +527 -0
- package/skills/algorand-typescript/references/deploy-react-frontend-reference.md +412 -0
- package/skills/algorand-typescript/references/deploy-react-frontend.md +239 -0
- package/skills/algorand-typescript/references/implement-arc-standards-arc32-arc56.md +73 -0
- package/skills/algorand-typescript/references/implement-arc-standards-arc4.md +126 -0
- package/skills/algorand-typescript/references/implement-arc-standards.md +44 -0
- package/skills/algorand-typescript/references/test-smart-contracts-examples.md +245 -0
- package/skills/algorand-typescript/references/test-smart-contracts-unit-tests.md +147 -0
- package/skills/algorand-typescript/references/test-smart-contracts.md +127 -0
- package/skills/algorand-typescript/references/troubleshoot-errors-contract.md +296 -0
- package/skills/algorand-typescript/references/troubleshoot-errors-transaction.md +438 -0
- package/skills/algorand-typescript/references/troubleshoot-errors.md +56 -0
- package/skills/algorand-typescript/references/use-algokit-utils-reference.md +342 -0
- package/skills/algorand-typescript/references/use-algokit-utils.md +74 -0
- package/skills/algorand-x402-python/SKILL.md +113 -0
- package/skills/algorand-x402-python/references/create-python-x402-client-examples.md +469 -0
- package/skills/algorand-x402-python/references/create-python-x402-client-reference.md +313 -0
- package/skills/algorand-x402-python/references/create-python-x402-client.md +207 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator-examples.md +924 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator-reference.md +629 -0
- package/skills/algorand-x402-python/references/create-python-x402-facilitator.md +408 -0
- package/skills/algorand-x402-python/references/create-python-x402-server-examples.md +703 -0
- package/skills/algorand-x402-python/references/create-python-x402-server-reference.md +303 -0
- package/skills/algorand-x402-python/references/create-python-x402-server.md +221 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python-examples.md +605 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python-reference.md +315 -0
- package/skills/algorand-x402-python/references/explain-algorand-x402-python.md +167 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm-examples.md +554 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm-reference.md +278 -0
- package/skills/algorand-x402-python/references/use-python-x402-core-avm.md +166 -0
- package/skills/algorand-x402-typescript/SKILL.md +129 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client-examples.md +879 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client-reference.md +371 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-client.md +236 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-examples.md +875 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator-reference.md +461 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-facilitator.md +270 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs-examples.md +1181 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs-reference.md +360 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-nextjs.md +251 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall-examples.md +870 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall-reference.md +323 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-paywall.md +281 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server-examples.md +1135 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server-reference.md +382 -0
- package/skills/algorand-x402-typescript/references/create-typescript-x402-server.md +216 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript-examples.md +616 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript-reference.md +323 -0
- package/skills/algorand-x402-typescript/references/explain-algorand-x402-typescript.md +232 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm-examples.md +1417 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm-reference.md +504 -0
- package/skills/algorand-x402-typescript/references/use-typescript-x402-core-avm.md +158 -0
|
@@ -0,0 +1,1417 @@
|
|
|
1
|
+
# @x402-avm/core and @x402-avm/avm Examples
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @x402-avm/core @x402-avm/avm algosdk
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
For browser wallet integration:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @x402-avm/avm algosdk @txnlab/use-wallet
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Network Identifiers
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import type { Network } from "@x402-avm/core/types";
|
|
21
|
+
import {
|
|
22
|
+
ALGORAND_TESTNET_CAIP2,
|
|
23
|
+
ALGORAND_MAINNET_CAIP2,
|
|
24
|
+
V1_ALGORAND_TESTNET,
|
|
25
|
+
V1_ALGORAND_MAINNET,
|
|
26
|
+
V1_TO_CAIP2,
|
|
27
|
+
CAIP2_TO_V1,
|
|
28
|
+
} from "@x402-avm/avm";
|
|
29
|
+
|
|
30
|
+
const testnet: Network = ALGORAND_TESTNET_CAIP2;
|
|
31
|
+
// => "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="
|
|
32
|
+
|
|
33
|
+
const mainnet: Network = ALGORAND_MAINNET_CAIP2;
|
|
34
|
+
// => "algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8="
|
|
35
|
+
|
|
36
|
+
const testnetV1 = V1_ALGORAND_TESTNET; // => "algorand-testnet"
|
|
37
|
+
const mainnetV1 = V1_ALGORAND_MAINNET; // => "algorand-mainnet"
|
|
38
|
+
|
|
39
|
+
const caip2 = V1_TO_CAIP2["algorand-testnet"];
|
|
40
|
+
// => "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="
|
|
41
|
+
|
|
42
|
+
const v1Name = CAIP2_TO_V1[ALGORAND_TESTNET_CAIP2];
|
|
43
|
+
// => "algorand-testnet"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## PaymentRequirements (V2)
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import type { PaymentRequirements } from "@x402-avm/core/types";
|
|
52
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
53
|
+
|
|
54
|
+
const requirements: PaymentRequirements = {
|
|
55
|
+
scheme: "exact",
|
|
56
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
57
|
+
maxAmountRequired: "1000000",
|
|
58
|
+
resource: "https://api.example.com/premium/data",
|
|
59
|
+
description: "Access to premium API endpoint",
|
|
60
|
+
mimeType: "application/json",
|
|
61
|
+
payTo: "RECEIVER_ALGORAND_ADDRESS_58_CHARS_AAAAAAAAAAAAAAAAAAA",
|
|
62
|
+
maxTimeoutSeconds: 60,
|
|
63
|
+
asset: USDC_TESTNET_ASA_ID,
|
|
64
|
+
outputSchema: undefined,
|
|
65
|
+
extra: {
|
|
66
|
+
name: "USDC",
|
|
67
|
+
decimals: 6,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const algoRequirements: PaymentRequirements = {
|
|
72
|
+
scheme: "exact",
|
|
73
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
74
|
+
maxAmountRequired: "1000000",
|
|
75
|
+
resource: "https://api.example.com/premium/data",
|
|
76
|
+
description: "Access to premium data",
|
|
77
|
+
mimeType: "application/json",
|
|
78
|
+
payTo: "RECEIVER_ALGORAND_ADDRESS_58_CHARS_AAAAAAAAAAAAAAAAAAA",
|
|
79
|
+
maxTimeoutSeconds: 60,
|
|
80
|
+
asset: "0",
|
|
81
|
+
outputSchema: undefined,
|
|
82
|
+
extra: {
|
|
83
|
+
name: "ALGO",
|
|
84
|
+
decimals: 6,
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## PaymentRequirements (V1 Legacy)
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import type { PaymentRequirementsV1 } from "@x402-avm/core/types";
|
|
95
|
+
|
|
96
|
+
const requirementsV1: PaymentRequirementsV1 = {
|
|
97
|
+
scheme: "exact",
|
|
98
|
+
network: "algorand-testnet",
|
|
99
|
+
maxAmountRequired: "1000000",
|
|
100
|
+
resource: "https://api.example.com/premium/data",
|
|
101
|
+
description: "Premium data access",
|
|
102
|
+
payTo: "RECEIVER_ALGORAND_ADDRESS_58_CHARS_AAAAAAAAAAAAAAAAAAA",
|
|
103
|
+
maxTimeoutSeconds: 60,
|
|
104
|
+
asset: "10458941",
|
|
105
|
+
outputSchema: null,
|
|
106
|
+
extra: {
|
|
107
|
+
name: "USDC",
|
|
108
|
+
decimals: 6,
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## PaymentPayload
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import type { PaymentPayload } from "@x402-avm/core/types";
|
|
119
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
120
|
+
|
|
121
|
+
const payload: PaymentPayload = {
|
|
122
|
+
x402Version: 2,
|
|
123
|
+
scheme: "exact",
|
|
124
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
125
|
+
payload: {
|
|
126
|
+
paymentGroup: [
|
|
127
|
+
"iaNhbXTOAAGGoKNmZWXNA...",
|
|
128
|
+
"iaNhbXTOAAGGoKNmZWXNA...",
|
|
129
|
+
],
|
|
130
|
+
paymentIndex: 0,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## PaymentRequired Response
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
import type { PaymentRequired } from "@x402-avm/core/types";
|
|
141
|
+
|
|
142
|
+
const paymentRequired: PaymentRequired = {
|
|
143
|
+
x402Version: 2,
|
|
144
|
+
resource: {
|
|
145
|
+
url: "https://api.example.com/premium/data",
|
|
146
|
+
description: "Premium data endpoint",
|
|
147
|
+
mimeType: "application/json",
|
|
148
|
+
},
|
|
149
|
+
accepts: [
|
|
150
|
+
{
|
|
151
|
+
scheme: "exact",
|
|
152
|
+
network: "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=",
|
|
153
|
+
maxAmountRequired: "1000000",
|
|
154
|
+
resource: "https://api.example.com/premium/data",
|
|
155
|
+
description: "Premium data endpoint",
|
|
156
|
+
mimeType: "application/json",
|
|
157
|
+
payTo: "RECEIVER_ALGORAND_ADDRESS_58_CHARS_AAAAAAAAAAAAAAAAAAA",
|
|
158
|
+
maxTimeoutSeconds: 60,
|
|
159
|
+
asset: "10458941",
|
|
160
|
+
outputSchema: undefined,
|
|
161
|
+
extra: { name: "USDC", decimals: 6 },
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
error: "Payment required to access this resource",
|
|
165
|
+
};
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
170
|
+
## Client with Private Key (Server-Side)
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
import { x402Client } from "@x402-avm/core/client";
|
|
174
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/client";
|
|
175
|
+
import type { ClientAvmSigner } from "@x402-avm/avm";
|
|
176
|
+
import algosdk from "algosdk";
|
|
177
|
+
|
|
178
|
+
const secretKey = Buffer.from(process.env.AVM_PRIVATE_KEY!, "base64");
|
|
179
|
+
const address = algosdk.encodeAddress(secretKey.slice(32));
|
|
180
|
+
|
|
181
|
+
const signer: ClientAvmSigner = {
|
|
182
|
+
address,
|
|
183
|
+
signTransactions: async (txns, indexesToSign) => {
|
|
184
|
+
return txns.map((txn, i) => {
|
|
185
|
+
if (indexesToSign && !indexesToSign.includes(i)) return null;
|
|
186
|
+
const decoded = algosdk.decodeUnsignedTransaction(txn);
|
|
187
|
+
const signed = algosdk.signTransaction(decoded, secretKey);
|
|
188
|
+
return signed.blob;
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const client = new x402Client({ schemes: [] });
|
|
194
|
+
|
|
195
|
+
registerExactAvmScheme(client, {
|
|
196
|
+
signer,
|
|
197
|
+
algodConfig: {
|
|
198
|
+
algodUrl: "https://testnet-api.algonode.cloud",
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
async function accessPaidResource() {
|
|
203
|
+
const response = await client.fetch(
|
|
204
|
+
"https://api.example.com/premium/data"
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
if (response.ok) {
|
|
208
|
+
const data = await response.json();
|
|
209
|
+
console.log("Received:", data);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Client with @txnlab/use-wallet (Browser)
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
import type { ClientAvmSigner } from "@x402-avm/avm";
|
|
220
|
+
import { useWallet } from "@txnlab/use-wallet";
|
|
221
|
+
|
|
222
|
+
function PaymentComponent() {
|
|
223
|
+
const { activeAccount, signTransactions } = useWallet();
|
|
224
|
+
|
|
225
|
+
const signer: ClientAvmSigner | null = activeAccount
|
|
226
|
+
? {
|
|
227
|
+
address: activeAccount.address,
|
|
228
|
+
signTransactions: async (txns, indexesToSign) => {
|
|
229
|
+
return signTransactions(txns, indexesToSign);
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
: null;
|
|
233
|
+
|
|
234
|
+
return signer ? <PaidContent signer={signer} /> : <ConnectWallet />;
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Full React Browser Client
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import React, { useState, useCallback } from "react";
|
|
244
|
+
import { x402Client } from "@x402-avm/core/client";
|
|
245
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/client";
|
|
246
|
+
import type { ClientAvmSigner } from "@x402-avm/avm";
|
|
247
|
+
import {
|
|
248
|
+
WalletProvider,
|
|
249
|
+
useWallet,
|
|
250
|
+
WalletId,
|
|
251
|
+
} from "@txnlab/use-wallet-react";
|
|
252
|
+
|
|
253
|
+
const walletProviders = {
|
|
254
|
+
wallets: [WalletId.PERA, WalletId.DEFLY],
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
function PayForWeather() {
|
|
258
|
+
const { activeAccount, signTransactions, connect, disconnect } = useWallet();
|
|
259
|
+
const [weather, setWeather] = useState<string | null>(null);
|
|
260
|
+
const [loading, setLoading] = useState(false);
|
|
261
|
+
|
|
262
|
+
const fetchWeather = useCallback(async () => {
|
|
263
|
+
if (!activeAccount) return;
|
|
264
|
+
setLoading(true);
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const signer: ClientAvmSigner = {
|
|
268
|
+
address: activeAccount.address,
|
|
269
|
+
signTransactions: async (txns, indexes) =>
|
|
270
|
+
signTransactions(txns, indexes),
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const client = new x402Client({ schemes: [] });
|
|
274
|
+
registerExactAvmScheme(client, { signer });
|
|
275
|
+
|
|
276
|
+
const response = await client.fetch(
|
|
277
|
+
"https://api.example.com/weather"
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
if (response.ok) {
|
|
281
|
+
const data = await response.json();
|
|
282
|
+
setWeather(JSON.stringify(data, null, 2));
|
|
283
|
+
} else {
|
|
284
|
+
setWeather(`Error: ${response.status} ${response.statusText}`);
|
|
285
|
+
}
|
|
286
|
+
} catch (err) {
|
|
287
|
+
setWeather(`Error: ${(err as Error).message}`);
|
|
288
|
+
} finally {
|
|
289
|
+
setLoading(false);
|
|
290
|
+
}
|
|
291
|
+
}, [activeAccount, signTransactions]);
|
|
292
|
+
|
|
293
|
+
return (
|
|
294
|
+
<div>
|
|
295
|
+
<h1>Weather API (Paid with USDC on Algorand)</h1>
|
|
296
|
+
|
|
297
|
+
{!activeAccount ? (
|
|
298
|
+
<button onClick={() => connect(WalletId.PERA)}>
|
|
299
|
+
Connect Pera Wallet
|
|
300
|
+
</button>
|
|
301
|
+
) : (
|
|
302
|
+
<div>
|
|
303
|
+
<p>Connected: {activeAccount.address.slice(0, 8)}...</p>
|
|
304
|
+
<button onClick={fetchWeather} disabled={loading}>
|
|
305
|
+
{loading ? "Paying..." : "Get Weather (0.10 USDC)"}
|
|
306
|
+
</button>
|
|
307
|
+
<button onClick={disconnect}>Disconnect</button>
|
|
308
|
+
</div>
|
|
309
|
+
)}
|
|
310
|
+
|
|
311
|
+
{weather && (
|
|
312
|
+
<pre>{weather}</pre>
|
|
313
|
+
)}
|
|
314
|
+
</div>
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export default function App() {
|
|
319
|
+
return (
|
|
320
|
+
<WalletProvider value={walletProviders}>
|
|
321
|
+
<PayForWeather />
|
|
322
|
+
</WalletProvider>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## Payment Policies
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { x402Client, PaymentPolicy } from "@x402-avm/core/client";
|
|
333
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/client";
|
|
334
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
335
|
+
|
|
336
|
+
const preferTestnet: PaymentPolicy = (version, requirements) => {
|
|
337
|
+
return requirements.filter(r => r.network === ALGORAND_TESTNET_CAIP2);
|
|
338
|
+
};
|
|
339
|
+
|
|
340
|
+
const maxAmount: PaymentPolicy = (version, requirements) => {
|
|
341
|
+
const MAX_USDC = 5_000_000;
|
|
342
|
+
return requirements.filter(r => {
|
|
343
|
+
const amount = parseInt(r.maxAmountRequired, 10);
|
|
344
|
+
return amount <= MAX_USDC;
|
|
345
|
+
});
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const preferAlgorand: PaymentPolicy = (version, requirements) => {
|
|
349
|
+
const algorandOptions = requirements.filter(r =>
|
|
350
|
+
r.network.startsWith("algorand:")
|
|
351
|
+
);
|
|
352
|
+
return algorandOptions.length > 0 ? algorandOptions : requirements;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const client = new x402Client({ schemes: [] });
|
|
356
|
+
registerExactAvmScheme(client, {
|
|
357
|
+
signer,
|
|
358
|
+
policies: [preferTestnet, maxAmount],
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
client.registerPolicy(preferAlgorand);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
---
|
|
365
|
+
|
|
366
|
+
## Resource Server (Transport-Agnostic)
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import { x402ResourceServer, ResourceConfig } from "@x402-avm/core/server";
|
|
370
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
371
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
372
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
373
|
+
|
|
374
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
375
|
+
url: "https://facilitator.example.com",
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const server = new x402ResourceServer(facilitatorClient);
|
|
379
|
+
registerExactAvmScheme(server);
|
|
380
|
+
|
|
381
|
+
const resourceConfig: ResourceConfig = {
|
|
382
|
+
scheme: "exact",
|
|
383
|
+
payTo: "RECEIVER_ALGORAND_ADDRESS_58_CHARS_AAAAAAAAAAAAAAAAAAA",
|
|
384
|
+
price: {
|
|
385
|
+
asset: USDC_TESTNET_ASA_ID,
|
|
386
|
+
amount: "100000",
|
|
387
|
+
extra: { name: "USDC", decimals: 6 },
|
|
388
|
+
},
|
|
389
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
390
|
+
maxTimeoutSeconds: 60,
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
async function handleRequest(url: string, xPaymentHeader?: string) {
|
|
394
|
+
if (!xPaymentHeader) {
|
|
395
|
+
const paymentRequired = server.createPaymentRequired(
|
|
396
|
+
{ url, description: "Premium API", mimeType: "application/json" },
|
|
397
|
+
[resourceConfig]
|
|
398
|
+
);
|
|
399
|
+
return { status: 402, body: paymentRequired };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const result = await server.processPayment(xPaymentHeader, resourceConfig);
|
|
403
|
+
if (result.verified) {
|
|
404
|
+
return { status: 200, body: { data: "premium content" } };
|
|
405
|
+
}
|
|
406
|
+
return { status: 402, body: result.error };
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
---
|
|
411
|
+
|
|
412
|
+
## HTTP Resource Server with Routes
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
import {
|
|
416
|
+
x402HTTPResourceServer,
|
|
417
|
+
HTTPFacilitatorClient,
|
|
418
|
+
RouteConfig,
|
|
419
|
+
} from "@x402-avm/core/server";
|
|
420
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
421
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
422
|
+
|
|
423
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
424
|
+
url: "https://facilitator.example.com",
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
const routes: RouteConfig[] = [
|
|
428
|
+
{
|
|
429
|
+
path: "/api/premium/*",
|
|
430
|
+
config: {
|
|
431
|
+
scheme: "exact",
|
|
432
|
+
payTo: "RECEIVER_ALGORAND_ADDRESS_58_CHARS_AAAAAAAAAAAAAAAAAAA",
|
|
433
|
+
price: {
|
|
434
|
+
asset: USDC_TESTNET_ASA_ID,
|
|
435
|
+
amount: "100000",
|
|
436
|
+
extra: { name: "USDC", decimals: 6 },
|
|
437
|
+
},
|
|
438
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
439
|
+
maxTimeoutSeconds: 60,
|
|
440
|
+
},
|
|
441
|
+
description: "Premium API endpoints",
|
|
442
|
+
mimeType: "application/json",
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
path: "/api/expensive-analysis",
|
|
446
|
+
config: {
|
|
447
|
+
scheme: "exact",
|
|
448
|
+
payTo: "RECEIVER_ALGORAND_ADDRESS_58_CHARS_AAAAAAAAAAAAAAAAAAA",
|
|
449
|
+
price: {
|
|
450
|
+
asset: USDC_TESTNET_ASA_ID,
|
|
451
|
+
amount: "5000000",
|
|
452
|
+
extra: { name: "USDC", decimals: 6 },
|
|
453
|
+
},
|
|
454
|
+
network: ALGORAND_TESTNET_CAIP2,
|
|
455
|
+
maxTimeoutSeconds: 120,
|
|
456
|
+
},
|
|
457
|
+
description: "Expensive analysis endpoint",
|
|
458
|
+
mimeType: "application/json",
|
|
459
|
+
},
|
|
460
|
+
];
|
|
461
|
+
|
|
462
|
+
const httpServer = new x402HTTPResourceServer(facilitatorClient, { routes });
|
|
463
|
+
registerExactAvmScheme(httpServer.resourceServer);
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Facilitator
|
|
469
|
+
|
|
470
|
+
```typescript
|
|
471
|
+
import { x402Facilitator } from "@x402-avm/core/facilitator";
|
|
472
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/facilitator";
|
|
473
|
+
import type { FacilitatorAvmSigner } from "@x402-avm/avm";
|
|
474
|
+
import { ALGORAND_TESTNET_CAIP2 } from "@x402-avm/avm";
|
|
475
|
+
import algosdk from "algosdk";
|
|
476
|
+
|
|
477
|
+
const secretKey = Buffer.from(process.env.AVM_PRIVATE_KEY!, "base64");
|
|
478
|
+
const address = algosdk.encodeAddress(secretKey.slice(32));
|
|
479
|
+
const algodClient = new algosdk.Algodv2("", "https://testnet-api.algonode.cloud", "");
|
|
480
|
+
|
|
481
|
+
const facilitatorSigner: FacilitatorAvmSigner = {
|
|
482
|
+
getAddresses: () => [address],
|
|
483
|
+
signTransaction: async (txn, senderAddress) => {
|
|
484
|
+
const decoded = algosdk.decodeUnsignedTransaction(txn);
|
|
485
|
+
const signed = algosdk.signTransaction(decoded, secretKey);
|
|
486
|
+
return signed.blob;
|
|
487
|
+
},
|
|
488
|
+
getAlgodClient: (network) => algodClient,
|
|
489
|
+
simulateTransactions: async (txns, network) => {
|
|
490
|
+
const stxns = txns.map((txnBytes) => {
|
|
491
|
+
try {
|
|
492
|
+
return algosdk.decodeSignedTransaction(txnBytes);
|
|
493
|
+
} catch {
|
|
494
|
+
const txn = algosdk.decodeUnsignedTransaction(txnBytes);
|
|
495
|
+
return new algosdk.SignedTransaction({ txn });
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
const request = new algosdk.modelsv2.SimulateRequest({
|
|
499
|
+
txnGroups: [new algosdk.modelsv2.SimulateRequestTransactionGroup({ txns: stxns })],
|
|
500
|
+
allowEmptySignatures: true,
|
|
501
|
+
});
|
|
502
|
+
return algodClient.simulateTransactions(request).do();
|
|
503
|
+
},
|
|
504
|
+
sendTransactions: async (signedTxns, network) => {
|
|
505
|
+
const combined = Buffer.concat(signedTxns.map(t => Buffer.from(t)));
|
|
506
|
+
const { txId } = await algodClient.sendRawTransaction(combined).do();
|
|
507
|
+
return txId;
|
|
508
|
+
},
|
|
509
|
+
waitForConfirmation: async (txId, network, waitRounds = 4) => {
|
|
510
|
+
return algosdk.waitForConfirmation(algodClient, txId, waitRounds);
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const facilitator = new x402Facilitator();
|
|
515
|
+
|
|
516
|
+
registerExactAvmScheme(facilitator, {
|
|
517
|
+
signer: facilitatorSigner,
|
|
518
|
+
networks: ALGORAND_TESTNET_CAIP2,
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
async function handlePaymentVerification(paymentPayload: any, requirements: any) {
|
|
522
|
+
const verifyResult = await facilitator.verify(paymentPayload, requirements);
|
|
523
|
+
|
|
524
|
+
if (verifyResult.isValid) {
|
|
525
|
+
const settleResult = await facilitator.settle(paymentPayload, requirements);
|
|
526
|
+
return settleResult;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return { success: false, error: verifyResult.error };
|
|
530
|
+
}
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
535
|
+
## HTTPFacilitatorClient
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
import { HTTPFacilitatorClient } from "@x402-avm/core/server";
|
|
539
|
+
|
|
540
|
+
const facilitatorClient = new HTTPFacilitatorClient({
|
|
541
|
+
url: "https://facilitator.example.com",
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const authenticatedClient = new HTTPFacilitatorClient({
|
|
545
|
+
url: "https://facilitator.example.com",
|
|
546
|
+
headers: {
|
|
547
|
+
Authorization: `Bearer ${process.env.FACILITATOR_API_KEY}`,
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
const supported = await facilitatorClient.supported();
|
|
552
|
+
console.log("Supported networks:", supported.networks);
|
|
553
|
+
|
|
554
|
+
const verifyResult = await facilitatorClient.verify({
|
|
555
|
+
paymentPayload,
|
|
556
|
+
paymentRequirements,
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
const settleResult = await facilitatorClient.settle({
|
|
560
|
+
paymentPayload,
|
|
561
|
+
paymentRequirements,
|
|
562
|
+
});
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
567
|
+
## ClientAvmSigner Interface
|
|
568
|
+
|
|
569
|
+
```typescript
|
|
570
|
+
import type { ClientAvmSigner } from "@x402-avm/avm";
|
|
571
|
+
|
|
572
|
+
interface ClientAvmSigner {
|
|
573
|
+
address: string;
|
|
574
|
+
signTransactions(
|
|
575
|
+
txns: Uint8Array[],
|
|
576
|
+
indexesToSign?: number[],
|
|
577
|
+
): Promise<(Uint8Array | null)[]>;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
import { isAvmSignerWallet } from "@x402-avm/avm";
|
|
581
|
+
|
|
582
|
+
function checkWallet(wallet: unknown) {
|
|
583
|
+
if (isAvmSignerWallet(wallet)) {
|
|
584
|
+
console.log("Address:", wallet.address);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
---
|
|
590
|
+
|
|
591
|
+
## ClientAvmSigner from Private Key
|
|
592
|
+
|
|
593
|
+
```typescript
|
|
594
|
+
import type { ClientAvmSigner } from "@x402-avm/avm";
|
|
595
|
+
import algosdk from "algosdk";
|
|
596
|
+
|
|
597
|
+
function createPrivateKeySigner(privateKeyBase64: string): ClientAvmSigner {
|
|
598
|
+
const secretKey = Buffer.from(privateKeyBase64, "base64");
|
|
599
|
+
|
|
600
|
+
if (secretKey.length !== 64) {
|
|
601
|
+
throw new Error(`Invalid key length: expected 64, got ${secretKey.length}`);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const address = algosdk.encodeAddress(secretKey.slice(32));
|
|
605
|
+
|
|
606
|
+
return {
|
|
607
|
+
address,
|
|
608
|
+
signTransactions: async (
|
|
609
|
+
txns: Uint8Array[],
|
|
610
|
+
indexesToSign?: number[],
|
|
611
|
+
): Promise<(Uint8Array | null)[]> => {
|
|
612
|
+
return txns.map((txnBytes, i) => {
|
|
613
|
+
if (indexesToSign && !indexesToSign.includes(i)) {
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
const decoded = algosdk.decodeUnsignedTransaction(txnBytes);
|
|
617
|
+
const signed = algosdk.signTransaction(decoded, secretKey);
|
|
618
|
+
return signed.blob;
|
|
619
|
+
});
|
|
620
|
+
},
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
const signer = createPrivateKeySigner(process.env.AVM_PRIVATE_KEY!);
|
|
625
|
+
console.log("Signer address:", signer.address);
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
## FacilitatorAvmSigner Interface
|
|
631
|
+
|
|
632
|
+
```typescript
|
|
633
|
+
import type { FacilitatorAvmSigner } from "@x402-avm/avm";
|
|
634
|
+
import type { Network } from "@x402-avm/core/types";
|
|
635
|
+
|
|
636
|
+
interface FacilitatorAvmSigner {
|
|
637
|
+
getAddresses(): readonly string[];
|
|
638
|
+
signTransaction(txn: Uint8Array, senderAddress: string): Promise<Uint8Array>;
|
|
639
|
+
getAlgodClient(network: Network): unknown;
|
|
640
|
+
simulateTransactions(txns: Uint8Array[], network: Network): Promise<unknown>;
|
|
641
|
+
sendTransactions(signedTxns: Uint8Array[], network: Network): Promise<string>;
|
|
642
|
+
waitForConfirmation(
|
|
643
|
+
txId: string,
|
|
644
|
+
network: Network,
|
|
645
|
+
waitRounds?: number,
|
|
646
|
+
): Promise<unknown>;
|
|
647
|
+
}
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
---
|
|
651
|
+
|
|
652
|
+
## Production FacilitatorAvmSigner
|
|
653
|
+
|
|
654
|
+
```typescript
|
|
655
|
+
import type { FacilitatorAvmSigner, FacilitatorAvmSignerConfig } from "@x402-avm/avm";
|
|
656
|
+
import type { Network } from "@x402-avm/core/types";
|
|
657
|
+
import {
|
|
658
|
+
ALGORAND_TESTNET_CAIP2,
|
|
659
|
+
ALGORAND_MAINNET_CAIP2,
|
|
660
|
+
createAlgodClient,
|
|
661
|
+
isAlgorandNetwork,
|
|
662
|
+
isTestnetNetwork,
|
|
663
|
+
} from "@x402-avm/avm";
|
|
664
|
+
import algosdk from "algosdk";
|
|
665
|
+
|
|
666
|
+
function createFacilitatorSigner(
|
|
667
|
+
privateKeyBase64: string,
|
|
668
|
+
config?: FacilitatorAvmSignerConfig,
|
|
669
|
+
): FacilitatorAvmSigner {
|
|
670
|
+
const secretKey = Buffer.from(privateKeyBase64, "base64");
|
|
671
|
+
const address = algosdk.encodeAddress(secretKey.slice(32));
|
|
672
|
+
|
|
673
|
+
const clients: Record<string, algosdk.Algodv2> = {};
|
|
674
|
+
|
|
675
|
+
function getClient(network: Network): algosdk.Algodv2 {
|
|
676
|
+
if (!clients[network]) {
|
|
677
|
+
clients[network] = createAlgodClient(
|
|
678
|
+
network,
|
|
679
|
+
isTestnetNetwork(network) ? config?.testnetUrl : config?.mainnetUrl,
|
|
680
|
+
config?.algodToken,
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
return clients[network];
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
return {
|
|
687
|
+
getAddresses: () => [address],
|
|
688
|
+
|
|
689
|
+
signTransaction: async (txn: Uint8Array, _senderAddress: string) => {
|
|
690
|
+
const decoded = algosdk.decodeUnsignedTransaction(txn);
|
|
691
|
+
const signed = algosdk.signTransaction(decoded, secretKey);
|
|
692
|
+
return signed.blob;
|
|
693
|
+
},
|
|
694
|
+
|
|
695
|
+
getAlgodClient: (network: Network) => getClient(network),
|
|
696
|
+
|
|
697
|
+
simulateTransactions: async (txns: Uint8Array[], network: Network) => {
|
|
698
|
+
const client = getClient(network);
|
|
699
|
+
|
|
700
|
+
const signedTxns = txns.map((txnBytes) => {
|
|
701
|
+
try {
|
|
702
|
+
return algosdk.decodeSignedTransaction(txnBytes);
|
|
703
|
+
} catch {
|
|
704
|
+
const txn = algosdk.decodeUnsignedTransaction(txnBytes);
|
|
705
|
+
return new algosdk.SignedTransaction({ txn });
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
const request = new algosdk.modelsv2.SimulateRequest({
|
|
710
|
+
txnGroups: [
|
|
711
|
+
new algosdk.modelsv2.SimulateRequestTransactionGroup({
|
|
712
|
+
txns: signedTxns,
|
|
713
|
+
}),
|
|
714
|
+
],
|
|
715
|
+
allowEmptySignatures: true,
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
const result = await client.simulateTransactions(request).do();
|
|
719
|
+
|
|
720
|
+
for (const group of result.txnGroups || []) {
|
|
721
|
+
if (group.failureMessage) {
|
|
722
|
+
throw new Error(`Simulation failed: ${group.failureMessage}`);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return result;
|
|
727
|
+
},
|
|
728
|
+
|
|
729
|
+
sendTransactions: async (signedTxns: Uint8Array[], network: Network) => {
|
|
730
|
+
const client = getClient(network);
|
|
731
|
+
const combined = Buffer.concat(signedTxns.map((t) => Buffer.from(t)));
|
|
732
|
+
const { txId } = await client.sendRawTransaction(combined).do();
|
|
733
|
+
return txId;
|
|
734
|
+
},
|
|
735
|
+
|
|
736
|
+
waitForConfirmation: async (
|
|
737
|
+
txId: string,
|
|
738
|
+
network: Network,
|
|
739
|
+
waitRounds: number = 4,
|
|
740
|
+
) => {
|
|
741
|
+
const client = getClient(network);
|
|
742
|
+
return algosdk.waitForConfirmation(client, txId, waitRounds);
|
|
743
|
+
},
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
const facilitatorSigner = createFacilitatorSigner(
|
|
748
|
+
process.env.AVM_PRIVATE_KEY!,
|
|
749
|
+
{
|
|
750
|
+
testnetUrl: "https://testnet-api.algonode.cloud",
|
|
751
|
+
mainnetUrl: "https://mainnet-api.algonode.cloud",
|
|
752
|
+
},
|
|
753
|
+
);
|
|
754
|
+
|
|
755
|
+
console.log("Fee payer addresses:", facilitatorSigner.getAddresses());
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
---
|
|
759
|
+
|
|
760
|
+
## Constants: Network Identifiers
|
|
761
|
+
|
|
762
|
+
```typescript
|
|
763
|
+
import {
|
|
764
|
+
ALGORAND_MAINNET_CAIP2,
|
|
765
|
+
ALGORAND_TESTNET_CAIP2,
|
|
766
|
+
CAIP2_NETWORKS,
|
|
767
|
+
ALGORAND_MAINNET_GENESIS_HASH,
|
|
768
|
+
ALGORAND_TESTNET_GENESIS_HASH,
|
|
769
|
+
V1_ALGORAND_MAINNET,
|
|
770
|
+
V1_ALGORAND_TESTNET,
|
|
771
|
+
V1_NETWORKS,
|
|
772
|
+
V1_TO_CAIP2,
|
|
773
|
+
CAIP2_TO_V1,
|
|
774
|
+
} from "@x402-avm/avm";
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
---
|
|
778
|
+
|
|
779
|
+
## Constants: USDC Configuration
|
|
780
|
+
|
|
781
|
+
```typescript
|
|
782
|
+
import {
|
|
783
|
+
USDC_MAINNET_ASA_ID,
|
|
784
|
+
USDC_TESTNET_ASA_ID,
|
|
785
|
+
USDC_DECIMALS,
|
|
786
|
+
USDC_CONFIG,
|
|
787
|
+
} from "@x402-avm/avm";
|
|
788
|
+
|
|
789
|
+
const testnetUsdc = USDC_CONFIG[ALGORAND_TESTNET_CAIP2];
|
|
790
|
+
console.log(testnetUsdc);
|
|
791
|
+
// { asaId: "10458941", name: "USDC", decimals: 6 }
|
|
792
|
+
```
|
|
793
|
+
|
|
794
|
+
---
|
|
795
|
+
|
|
796
|
+
## Constants: Algod Endpoints
|
|
797
|
+
|
|
798
|
+
```typescript
|
|
799
|
+
import {
|
|
800
|
+
DEFAULT_ALGOD_MAINNET,
|
|
801
|
+
DEFAULT_ALGOD_TESTNET,
|
|
802
|
+
NETWORK_TO_ALGOD,
|
|
803
|
+
FALLBACK_ALGOD_MAINNET,
|
|
804
|
+
FALLBACK_ALGOD_TESTNET,
|
|
805
|
+
} from "@x402-avm/avm";
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
---
|
|
809
|
+
|
|
810
|
+
## Constants: Transaction Limits and Address Validation
|
|
811
|
+
|
|
812
|
+
```typescript
|
|
813
|
+
import {
|
|
814
|
+
MAX_ATOMIC_GROUP_SIZE,
|
|
815
|
+
MIN_TXN_FEE,
|
|
816
|
+
MAX_REASONABLE_FEE,
|
|
817
|
+
ALGORAND_ADDRESS_REGEX,
|
|
818
|
+
ALGORAND_ADDRESS_LENGTH,
|
|
819
|
+
} from "@x402-avm/avm";
|
|
820
|
+
|
|
821
|
+
const isValid = ALGORAND_ADDRESS_REGEX.test(someAddress);
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
---
|
|
825
|
+
|
|
826
|
+
## Utility: Address Validation
|
|
827
|
+
|
|
828
|
+
```typescript
|
|
829
|
+
import { isValidAlgorandAddress } from "@x402-avm/avm";
|
|
830
|
+
|
|
831
|
+
isValidAlgorandAddress("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
|
|
832
|
+
// => true
|
|
833
|
+
|
|
834
|
+
isValidAlgorandAddress("invalid");
|
|
835
|
+
// => false
|
|
836
|
+
```
|
|
837
|
+
|
|
838
|
+
---
|
|
839
|
+
|
|
840
|
+
## Utility: Amount Conversion
|
|
841
|
+
|
|
842
|
+
```typescript
|
|
843
|
+
import { convertToTokenAmount, convertFromTokenAmount } from "@x402-avm/avm";
|
|
844
|
+
|
|
845
|
+
convertToTokenAmount("1.50", 6); // => "1500000"
|
|
846
|
+
convertToTokenAmount("0.10", 6); // => "100000"
|
|
847
|
+
convertToTokenAmount("100", 6); // => "100000000"
|
|
848
|
+
convertToTokenAmount("0.000001", 6); // => "1"
|
|
849
|
+
|
|
850
|
+
convertFromTokenAmount("1500000", 6); // => "1.5"
|
|
851
|
+
convertFromTokenAmount("100000", 6); // => "0.1"
|
|
852
|
+
convertFromTokenAmount("1", 6); // => "0.000001"
|
|
853
|
+
convertFromTokenAmount("100000000", 6); // => "100"
|
|
854
|
+
```
|
|
855
|
+
|
|
856
|
+
---
|
|
857
|
+
|
|
858
|
+
## Utility: Transaction Encoding/Decoding
|
|
859
|
+
|
|
860
|
+
```typescript
|
|
861
|
+
import {
|
|
862
|
+
encodeTransaction,
|
|
863
|
+
decodeTransaction,
|
|
864
|
+
decodeSignedTransaction,
|
|
865
|
+
decodeUnsignedTransaction,
|
|
866
|
+
} from "@x402-avm/avm";
|
|
867
|
+
|
|
868
|
+
const base64Str = encodeTransaction(txnBytes);
|
|
869
|
+
// => "iaNhbXTOAAGGoKNm..."
|
|
870
|
+
|
|
871
|
+
const bytes = decodeTransaction(base64Str);
|
|
872
|
+
// => Uint8Array [...]
|
|
873
|
+
|
|
874
|
+
const signedTxn = decodeSignedTransaction(base64Str);
|
|
875
|
+
// => algosdk.SignedTransaction
|
|
876
|
+
|
|
877
|
+
const unsignedTxn = decodeUnsignedTransaction(base64Str);
|
|
878
|
+
// => algosdk.Transaction
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
---
|
|
882
|
+
|
|
883
|
+
## Utility: Network Functions
|
|
884
|
+
|
|
885
|
+
```typescript
|
|
886
|
+
import {
|
|
887
|
+
getNetworkFromCaip2,
|
|
888
|
+
isAlgorandNetwork,
|
|
889
|
+
isTestnetNetwork,
|
|
890
|
+
v1ToCaip2,
|
|
891
|
+
caip2ToV1,
|
|
892
|
+
createAlgodClient,
|
|
893
|
+
} from "@x402-avm/avm";
|
|
894
|
+
|
|
895
|
+
getNetworkFromCaip2("algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=");
|
|
896
|
+
// => "testnet"
|
|
897
|
+
|
|
898
|
+
getNetworkFromCaip2("algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=");
|
|
899
|
+
// => "mainnet"
|
|
900
|
+
|
|
901
|
+
getNetworkFromCaip2("eip155:8453");
|
|
902
|
+
// => null
|
|
903
|
+
|
|
904
|
+
isAlgorandNetwork("algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=");
|
|
905
|
+
// => true
|
|
906
|
+
|
|
907
|
+
isAlgorandNetwork("algorand-testnet");
|
|
908
|
+
// => true
|
|
909
|
+
|
|
910
|
+
isAlgorandNetwork("eip155:8453");
|
|
911
|
+
// => false
|
|
912
|
+
|
|
913
|
+
isTestnetNetwork("algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=");
|
|
914
|
+
// => true
|
|
915
|
+
|
|
916
|
+
v1ToCaip2("algorand-testnet");
|
|
917
|
+
// => "algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="
|
|
918
|
+
|
|
919
|
+
caip2ToV1("algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=");
|
|
920
|
+
// => "algorand-testnet"
|
|
921
|
+
|
|
922
|
+
const algod = createAlgodClient("algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=");
|
|
923
|
+
|
|
924
|
+
const customAlgod = createAlgodClient(
|
|
925
|
+
"algorand:wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
|
|
926
|
+
"https://my-custom-node.example.com",
|
|
927
|
+
"my-api-token",
|
|
928
|
+
);
|
|
929
|
+
```
|
|
930
|
+
|
|
931
|
+
---
|
|
932
|
+
|
|
933
|
+
## Utility: Transaction Inspection
|
|
934
|
+
|
|
935
|
+
```typescript
|
|
936
|
+
import {
|
|
937
|
+
getSenderFromTransaction,
|
|
938
|
+
getTransactionId,
|
|
939
|
+
hasSignature,
|
|
940
|
+
getGenesisHashFromTransaction,
|
|
941
|
+
validateGroupId,
|
|
942
|
+
assignGroupId,
|
|
943
|
+
} from "@x402-avm/avm";
|
|
944
|
+
|
|
945
|
+
const sender = getSenderFromTransaction(signedTxnBytes, true);
|
|
946
|
+
const senderUnsigned = getSenderFromTransaction(unsignedTxnBytes, false);
|
|
947
|
+
|
|
948
|
+
const txId = getTransactionId(signedTxnBytes);
|
|
949
|
+
|
|
950
|
+
const signed = hasSignature(txnBytes);
|
|
951
|
+
|
|
952
|
+
const allMatch = validateGroupId([txn1Bytes, txn2Bytes, txn3Bytes]);
|
|
953
|
+
|
|
954
|
+
const groupedTxns = assignGroupId([txn1, txn2, txn3]);
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
---
|
|
958
|
+
|
|
959
|
+
## Simple Payment Group
|
|
960
|
+
|
|
961
|
+
```typescript
|
|
962
|
+
import algosdk from "algosdk";
|
|
963
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID, createAlgodClient } from "@x402-avm/avm";
|
|
964
|
+
|
|
965
|
+
async function createSimplePayment(
|
|
966
|
+
senderAddress: string,
|
|
967
|
+
receiverAddress: string,
|
|
968
|
+
amount: number,
|
|
969
|
+
) {
|
|
970
|
+
const algod = createAlgodClient(ALGORAND_TESTNET_CAIP2);
|
|
971
|
+
const params = await algod.getTransactionParams().do();
|
|
972
|
+
|
|
973
|
+
const txn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({
|
|
974
|
+
from: senderAddress,
|
|
975
|
+
to: receiverAddress,
|
|
976
|
+
amount,
|
|
977
|
+
assetIndex: parseInt(USDC_TESTNET_ASA_ID, 10),
|
|
978
|
+
suggestedParams: params,
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
return [txn.toByte()];
|
|
982
|
+
}
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
---
|
|
986
|
+
|
|
987
|
+
## Fee-Abstracted Payment Group
|
|
988
|
+
|
|
989
|
+
```typescript
|
|
990
|
+
import algosdk from "algosdk";
|
|
991
|
+
import {
|
|
992
|
+
ALGORAND_TESTNET_CAIP2,
|
|
993
|
+
USDC_TESTNET_ASA_ID,
|
|
994
|
+
MIN_TXN_FEE,
|
|
995
|
+
createAlgodClient,
|
|
996
|
+
encodeTransaction,
|
|
997
|
+
} from "@x402-avm/avm";
|
|
998
|
+
|
|
999
|
+
async function createFeeAbstractedPayment(
|
|
1000
|
+
senderAddress: string,
|
|
1001
|
+
receiverAddress: string,
|
|
1002
|
+
feePayerAddress: string,
|
|
1003
|
+
amount: number,
|
|
1004
|
+
) {
|
|
1005
|
+
const algod = createAlgodClient(ALGORAND_TESTNET_CAIP2);
|
|
1006
|
+
const params = await algod.getTransactionParams().do();
|
|
1007
|
+
|
|
1008
|
+
const paymentTxn = algosdk.makeAssetTransferTxnWithSuggestedParamsFromObject({
|
|
1009
|
+
from: senderAddress,
|
|
1010
|
+
to: receiverAddress,
|
|
1011
|
+
amount,
|
|
1012
|
+
assetIndex: parseInt(USDC_TESTNET_ASA_ID, 10),
|
|
1013
|
+
suggestedParams: {
|
|
1014
|
+
...params,
|
|
1015
|
+
fee: 0,
|
|
1016
|
+
flatFee: true,
|
|
1017
|
+
},
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
const feePayerTxn = algosdk.makePaymentTxnWithSuggestedParamsFromObject({
|
|
1021
|
+
from: feePayerAddress,
|
|
1022
|
+
to: feePayerAddress,
|
|
1023
|
+
amount: 0,
|
|
1024
|
+
suggestedParams: {
|
|
1025
|
+
...params,
|
|
1026
|
+
fee: MIN_TXN_FEE * 2,
|
|
1027
|
+
flatFee: true,
|
|
1028
|
+
},
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
const grouped = algosdk.assignGroupID([paymentTxn, feePayerTxn]);
|
|
1032
|
+
|
|
1033
|
+
const paymentBytes = grouped[0].toByte();
|
|
1034
|
+
const feePayerBytes = grouped[1].toByte();
|
|
1035
|
+
|
|
1036
|
+
return {
|
|
1037
|
+
paymentGroup: [
|
|
1038
|
+
encodeTransaction(paymentBytes),
|
|
1039
|
+
encodeTransaction(feePayerBytes),
|
|
1040
|
+
],
|
|
1041
|
+
paymentIndex: 0,
|
|
1042
|
+
rawBytes: [paymentBytes, feePayerBytes],
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
---
|
|
1048
|
+
|
|
1049
|
+
## Fee Abstraction with x402Client
|
|
1050
|
+
|
|
1051
|
+
```typescript
|
|
1052
|
+
import { x402Client } from "@x402-avm/core/client";
|
|
1053
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/client";
|
|
1054
|
+
import type { ClientAvmSigner } from "@x402-avm/avm";
|
|
1055
|
+
import algosdk from "algosdk";
|
|
1056
|
+
|
|
1057
|
+
const signer: ClientAvmSigner = {
|
|
1058
|
+
address: myAddress,
|
|
1059
|
+
signTransactions: async (txns, indexesToSign) => {
|
|
1060
|
+
return txns.map((txn, i) => {
|
|
1061
|
+
if (indexesToSign && !indexesToSign.includes(i)) return null;
|
|
1062
|
+
const decoded = algosdk.decodeUnsignedTransaction(txn);
|
|
1063
|
+
return algosdk.signTransaction(decoded, secretKey).blob;
|
|
1064
|
+
});
|
|
1065
|
+
},
|
|
1066
|
+
};
|
|
1067
|
+
|
|
1068
|
+
const client = new x402Client({ schemes: [] });
|
|
1069
|
+
registerExactAvmScheme(client, { signer });
|
|
1070
|
+
|
|
1071
|
+
// Fee abstraction is automatic when PaymentRequirements include a feePayer
|
|
1072
|
+
const response = await client.fetch("https://api.example.com/paid-resource");
|
|
1073
|
+
```
|
|
1074
|
+
|
|
1075
|
+
---
|
|
1076
|
+
|
|
1077
|
+
## ExactAvmScheme Registration: Client
|
|
1078
|
+
|
|
1079
|
+
```typescript
|
|
1080
|
+
import { x402Client } from "@x402-avm/core/client";
|
|
1081
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/client";
|
|
1082
|
+
|
|
1083
|
+
const client = new x402Client({ schemes: [] });
|
|
1084
|
+
|
|
1085
|
+
registerExactAvmScheme(client, {
|
|
1086
|
+
signer: myClientSigner,
|
|
1087
|
+
algodConfig: {
|
|
1088
|
+
algodUrl: "https://testnet-api.algonode.cloud",
|
|
1089
|
+
},
|
|
1090
|
+
networks: ["algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="],
|
|
1091
|
+
policies: [preferTestnetPolicy],
|
|
1092
|
+
});
|
|
1093
|
+
```
|
|
1094
|
+
|
|
1095
|
+
---
|
|
1096
|
+
|
|
1097
|
+
## ExactAvmScheme Registration: Server
|
|
1098
|
+
|
|
1099
|
+
```typescript
|
|
1100
|
+
import { x402ResourceServer } from "@x402-avm/core/server";
|
|
1101
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/server";
|
|
1102
|
+
|
|
1103
|
+
const server = new x402ResourceServer(facilitatorClient);
|
|
1104
|
+
|
|
1105
|
+
registerExactAvmScheme(server, {
|
|
1106
|
+
networks: ["algorand:SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI="],
|
|
1107
|
+
});
|
|
1108
|
+
|
|
1109
|
+
// Or with wildcard (default)
|
|
1110
|
+
registerExactAvmScheme(server);
|
|
1111
|
+
```
|
|
1112
|
+
|
|
1113
|
+
---
|
|
1114
|
+
|
|
1115
|
+
## ExactAvmScheme Registration: Facilitator
|
|
1116
|
+
|
|
1117
|
+
```typescript
|
|
1118
|
+
import { x402Facilitator } from "@x402-avm/core/facilitator";
|
|
1119
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/facilitator";
|
|
1120
|
+
import { ALGORAND_TESTNET_CAIP2, ALGORAND_MAINNET_CAIP2 } from "@x402-avm/avm";
|
|
1121
|
+
|
|
1122
|
+
const facilitator = new x402Facilitator();
|
|
1123
|
+
|
|
1124
|
+
registerExactAvmScheme(facilitator, {
|
|
1125
|
+
signer: myFacilitatorSigner,
|
|
1126
|
+
networks: ALGORAND_TESTNET_CAIP2,
|
|
1127
|
+
});
|
|
1128
|
+
|
|
1129
|
+
registerExactAvmScheme(facilitator, {
|
|
1130
|
+
signer: myFacilitatorSigner,
|
|
1131
|
+
networks: [ALGORAND_TESTNET_CAIP2, ALGORAND_MAINNET_CAIP2],
|
|
1132
|
+
});
|
|
1133
|
+
```
|
|
1134
|
+
|
|
1135
|
+
---
|
|
1136
|
+
|
|
1137
|
+
## Complete End-to-End: Full Stack
|
|
1138
|
+
|
|
1139
|
+
```typescript
|
|
1140
|
+
// ============================================================
|
|
1141
|
+
// shared/config.ts
|
|
1142
|
+
// ============================================================
|
|
1143
|
+
import { ALGORAND_TESTNET_CAIP2, USDC_TESTNET_ASA_ID } from "@x402-avm/avm";
|
|
1144
|
+
|
|
1145
|
+
export const NETWORK = ALGORAND_TESTNET_CAIP2;
|
|
1146
|
+
export const USDC_ASA = USDC_TESTNET_ASA_ID;
|
|
1147
|
+
export const RESOURCE_WALLET = "RECEIVER_ALGORAND_ADDRESS_58_CHARS_AAAAAAAAAAAAAAAAAAA";
|
|
1148
|
+
export const FACILITATOR_URL = "http://localhost:4000";
|
|
1149
|
+
export const RESOURCE_SERVER_URL = "http://localhost:3000";
|
|
1150
|
+
|
|
1151
|
+
// ============================================================
|
|
1152
|
+
// facilitator/index.ts
|
|
1153
|
+
// ============================================================
|
|
1154
|
+
import express from "express";
|
|
1155
|
+
import { x402Facilitator } from "@x402-avm/core/facilitator";
|
|
1156
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/facilitator";
|
|
1157
|
+
import type { FacilitatorAvmSigner } from "@x402-avm/avm";
|
|
1158
|
+
import algosdk from "algosdk";
|
|
1159
|
+
import { NETWORK } from "../shared/config";
|
|
1160
|
+
|
|
1161
|
+
const secretKey = Buffer.from(process.env.AVM_PRIVATE_KEY!, "base64");
|
|
1162
|
+
const address = algosdk.encodeAddress(secretKey.slice(32));
|
|
1163
|
+
const algodClient = new algosdk.Algodv2("", "https://testnet-api.algonode.cloud", "");
|
|
1164
|
+
|
|
1165
|
+
const signer: FacilitatorAvmSigner = {
|
|
1166
|
+
getAddresses: () => [address],
|
|
1167
|
+
signTransaction: async (txn, _addr) => {
|
|
1168
|
+
const decoded = algosdk.decodeUnsignedTransaction(txn);
|
|
1169
|
+
return algosdk.signTransaction(decoded, secretKey).blob;
|
|
1170
|
+
},
|
|
1171
|
+
getAlgodClient: () => algodClient,
|
|
1172
|
+
simulateTransactions: async (txns) => {
|
|
1173
|
+
const stxns = txns.map((t) => {
|
|
1174
|
+
try { return algosdk.decodeSignedTransaction(t); }
|
|
1175
|
+
catch { return new algosdk.SignedTransaction({ txn: algosdk.decodeUnsignedTransaction(t) }); }
|
|
1176
|
+
});
|
|
1177
|
+
const req = new algosdk.modelsv2.SimulateRequest({
|
|
1178
|
+
txnGroups: [new algosdk.modelsv2.SimulateRequestTransactionGroup({ txns: stxns })],
|
|
1179
|
+
allowEmptySignatures: true,
|
|
1180
|
+
});
|
|
1181
|
+
return algodClient.simulateTransactions(req).do();
|
|
1182
|
+
},
|
|
1183
|
+
sendTransactions: async (signedTxns) => {
|
|
1184
|
+
const combined = Buffer.concat(signedTxns.map((t) => Buffer.from(t)));
|
|
1185
|
+
const { txId } = await algodClient.sendRawTransaction(combined).do();
|
|
1186
|
+
return txId;
|
|
1187
|
+
},
|
|
1188
|
+
waitForConfirmation: async (txId, _net, rounds = 4) => {
|
|
1189
|
+
return algosdk.waitForConfirmation(algodClient, txId, rounds);
|
|
1190
|
+
},
|
|
1191
|
+
};
|
|
1192
|
+
|
|
1193
|
+
const facilitator = new x402Facilitator();
|
|
1194
|
+
registerExactAvmScheme(facilitator, { signer, networks: NETWORK });
|
|
1195
|
+
|
|
1196
|
+
const app = express();
|
|
1197
|
+
app.use(express.json());
|
|
1198
|
+
|
|
1199
|
+
app.get("/supported", async (_req, res) => {
|
|
1200
|
+
const supported = facilitator.getSupportedNetworks();
|
|
1201
|
+
res.json(supported);
|
|
1202
|
+
});
|
|
1203
|
+
|
|
1204
|
+
app.post("/verify", async (req, res) => {
|
|
1205
|
+
const { paymentPayload, paymentRequirements } = req.body;
|
|
1206
|
+
const result = await facilitator.verify(paymentPayload, paymentRequirements);
|
|
1207
|
+
res.json(result);
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
app.post("/settle", async (req, res) => {
|
|
1211
|
+
const { paymentPayload, paymentRequirements } = req.body;
|
|
1212
|
+
const result = await facilitator.settle(paymentPayload, paymentRequirements);
|
|
1213
|
+
res.json(result);
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
app.listen(4000, () => console.log("Facilitator running on :4000"));
|
|
1217
|
+
|
|
1218
|
+
// ============================================================
|
|
1219
|
+
// server/index.ts
|
|
1220
|
+
// ============================================================
|
|
1221
|
+
import express from "express";
|
|
1222
|
+
import {
|
|
1223
|
+
x402HTTPResourceServer,
|
|
1224
|
+
HTTPFacilitatorClient,
|
|
1225
|
+
} from "@x402-avm/core/server";
|
|
1226
|
+
import { registerExactAvmScheme as registerServerScheme } from "@x402-avm/avm/exact/server";
|
|
1227
|
+
import {
|
|
1228
|
+
NETWORK,
|
|
1229
|
+
USDC_ASA,
|
|
1230
|
+
RESOURCE_WALLET,
|
|
1231
|
+
FACILITATOR_URL,
|
|
1232
|
+
} from "../shared/config";
|
|
1233
|
+
|
|
1234
|
+
const facilitatorClient = new HTTPFacilitatorClient({ url: FACILITATOR_URL });
|
|
1235
|
+
|
|
1236
|
+
const httpServer = new x402HTTPResourceServer(facilitatorClient, {
|
|
1237
|
+
routes: [
|
|
1238
|
+
{
|
|
1239
|
+
path: "/api/weather",
|
|
1240
|
+
config: {
|
|
1241
|
+
scheme: "exact",
|
|
1242
|
+
payTo: RESOURCE_WALLET,
|
|
1243
|
+
price: { asset: USDC_ASA, amount: "10000", extra: { name: "USDC", decimals: 6 } },
|
|
1244
|
+
network: NETWORK,
|
|
1245
|
+
maxTimeoutSeconds: 60,
|
|
1246
|
+
},
|
|
1247
|
+
description: "Weather data API",
|
|
1248
|
+
mimeType: "application/json",
|
|
1249
|
+
},
|
|
1250
|
+
],
|
|
1251
|
+
});
|
|
1252
|
+
registerServerScheme(httpServer.resourceServer);
|
|
1253
|
+
|
|
1254
|
+
const app = express();
|
|
1255
|
+
|
|
1256
|
+
app.get("/api/weather", async (req, res) => {
|
|
1257
|
+
const result = await httpServer.processRequest({
|
|
1258
|
+
url: req.url,
|
|
1259
|
+
method: req.method,
|
|
1260
|
+
headers: req.headers,
|
|
1261
|
+
adapter: {
|
|
1262
|
+
getHeader: (name: string) => req.headers[name.toLowerCase()] as string,
|
|
1263
|
+
},
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
if (result.status === 402) {
|
|
1267
|
+
return res.status(402).json(result.body);
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
res.json({
|
|
1271
|
+
temperature: 72,
|
|
1272
|
+
condition: "Sunny",
|
|
1273
|
+
location: "San Francisco",
|
|
1274
|
+
});
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
app.listen(3000, () => console.log("Resource server running on :3000"));
|
|
1278
|
+
|
|
1279
|
+
// ============================================================
|
|
1280
|
+
// client/index.ts
|
|
1281
|
+
// ============================================================
|
|
1282
|
+
import { x402Client } from "@x402-avm/core/client";
|
|
1283
|
+
import { registerExactAvmScheme as registerClientScheme } from "@x402-avm/avm/exact/client";
|
|
1284
|
+
import type { ClientAvmSigner } from "@x402-avm/avm";
|
|
1285
|
+
import algosdk from "algosdk";
|
|
1286
|
+
import { RESOURCE_SERVER_URL } from "../shared/config";
|
|
1287
|
+
|
|
1288
|
+
const clientSecretKey = Buffer.from(process.env.CLIENT_AVM_PRIVATE_KEY!, "base64");
|
|
1289
|
+
const clientAddress = algosdk.encodeAddress(clientSecretKey.slice(32));
|
|
1290
|
+
|
|
1291
|
+
const clientSigner: ClientAvmSigner = {
|
|
1292
|
+
address: clientAddress,
|
|
1293
|
+
signTransactions: async (txns, indexesToSign) => {
|
|
1294
|
+
return txns.map((txn, i) => {
|
|
1295
|
+
if (indexesToSign && !indexesToSign.includes(i)) return null;
|
|
1296
|
+
const decoded = algosdk.decodeUnsignedTransaction(txn);
|
|
1297
|
+
return algosdk.signTransaction(decoded, clientSecretKey).blob;
|
|
1298
|
+
});
|
|
1299
|
+
},
|
|
1300
|
+
};
|
|
1301
|
+
|
|
1302
|
+
const client = new x402Client({ schemes: [] });
|
|
1303
|
+
registerClientScheme(client, { signer: clientSigner });
|
|
1304
|
+
|
|
1305
|
+
async function getWeather() {
|
|
1306
|
+
const response = await client.fetch(`${RESOURCE_SERVER_URL}/api/weather`);
|
|
1307
|
+
|
|
1308
|
+
if (response.ok) {
|
|
1309
|
+
const weather = await response.json();
|
|
1310
|
+
console.log("Weather:", weather);
|
|
1311
|
+
} else {
|
|
1312
|
+
console.error("Failed:", response.status, response.statusText);
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
getWeather();
|
|
1317
|
+
```
|
|
1318
|
+
|
|
1319
|
+
---
|
|
1320
|
+
|
|
1321
|
+
## Node.js Facilitator Service
|
|
1322
|
+
|
|
1323
|
+
```typescript
|
|
1324
|
+
import express from "express";
|
|
1325
|
+
import { x402Facilitator } from "@x402-avm/core/facilitator";
|
|
1326
|
+
import { registerExactAvmScheme } from "@x402-avm/avm/exact/facilitator";
|
|
1327
|
+
import { ALGORAND_TESTNET_CAIP2, ALGORAND_MAINNET_CAIP2 } from "@x402-avm/avm";
|
|
1328
|
+
import algosdk from "algosdk";
|
|
1329
|
+
|
|
1330
|
+
const secretKey = Buffer.from(process.env.AVM_PRIVATE_KEY!, "base64");
|
|
1331
|
+
const address = algosdk.encodeAddress(secretKey.slice(32));
|
|
1332
|
+
|
|
1333
|
+
const algodTestnet = new algosdk.Algodv2("", "https://testnet-api.algonode.cloud", "");
|
|
1334
|
+
const algodMainnet = new algosdk.Algodv2("", "https://mainnet-api.algonode.cloud", "");
|
|
1335
|
+
|
|
1336
|
+
function getClient(network: string) {
|
|
1337
|
+
return network === ALGORAND_MAINNET_CAIP2 ? algodMainnet : algodTestnet;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
const signer = {
|
|
1341
|
+
getAddresses: () => [address] as const,
|
|
1342
|
+
signTransaction: async (txn: Uint8Array) => {
|
|
1343
|
+
const decoded = algosdk.decodeUnsignedTransaction(txn);
|
|
1344
|
+
return algosdk.signTransaction(decoded, secretKey).blob;
|
|
1345
|
+
},
|
|
1346
|
+
getAlgodClient: (network: string) => getClient(network),
|
|
1347
|
+
simulateTransactions: async (txns: Uint8Array[], network: string) => {
|
|
1348
|
+
const client = getClient(network);
|
|
1349
|
+
const stxns = txns.map((t) => {
|
|
1350
|
+
try { return algosdk.decodeSignedTransaction(t); }
|
|
1351
|
+
catch { return new algosdk.SignedTransaction({ txn: algosdk.decodeUnsignedTransaction(t) }); }
|
|
1352
|
+
});
|
|
1353
|
+
const req = new algosdk.modelsv2.SimulateRequest({
|
|
1354
|
+
txnGroups: [new algosdk.modelsv2.SimulateRequestTransactionGroup({ txns: stxns })],
|
|
1355
|
+
allowEmptySignatures: true,
|
|
1356
|
+
});
|
|
1357
|
+
const result = await client.simulateTransactions(req).do();
|
|
1358
|
+
for (const g of result.txnGroups || []) {
|
|
1359
|
+
if (g.failureMessage) throw new Error(`Simulation failed: ${g.failureMessage}`);
|
|
1360
|
+
}
|
|
1361
|
+
return result;
|
|
1362
|
+
},
|
|
1363
|
+
sendTransactions: async (signedTxns: Uint8Array[], network: string) => {
|
|
1364
|
+
const client = getClient(network);
|
|
1365
|
+
const combined = Buffer.concat(signedTxns.map((t) => Buffer.from(t)));
|
|
1366
|
+
const { txId } = await client.sendRawTransaction(combined).do();
|
|
1367
|
+
return txId;
|
|
1368
|
+
},
|
|
1369
|
+
waitForConfirmation: async (txId: string, network: string, rounds = 4) => {
|
|
1370
|
+
return algosdk.waitForConfirmation(getClient(network), txId, rounds);
|
|
1371
|
+
},
|
|
1372
|
+
};
|
|
1373
|
+
|
|
1374
|
+
const facilitator = new x402Facilitator();
|
|
1375
|
+
registerExactAvmScheme(facilitator, {
|
|
1376
|
+
signer,
|
|
1377
|
+
networks: [ALGORAND_TESTNET_CAIP2, ALGORAND_MAINNET_CAIP2],
|
|
1378
|
+
});
|
|
1379
|
+
|
|
1380
|
+
const app = express();
|
|
1381
|
+
app.use(express.json({ limit: "1mb" }));
|
|
1382
|
+
|
|
1383
|
+
app.get("/supported", async (_req, res) => {
|
|
1384
|
+
try {
|
|
1385
|
+
res.json(facilitator.getSupportedNetworks());
|
|
1386
|
+
} catch (err) {
|
|
1387
|
+
res.status(500).json({ error: (err as Error).message });
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1390
|
+
|
|
1391
|
+
app.post("/verify", async (req, res) => {
|
|
1392
|
+
try {
|
|
1393
|
+
const { paymentPayload, paymentRequirements } = req.body;
|
|
1394
|
+
const result = await facilitator.verify(paymentPayload, paymentRequirements);
|
|
1395
|
+
res.json(result);
|
|
1396
|
+
} catch (err) {
|
|
1397
|
+
res.status(500).json({ error: (err as Error).message });
|
|
1398
|
+
}
|
|
1399
|
+
});
|
|
1400
|
+
|
|
1401
|
+
app.post("/settle", async (req, res) => {
|
|
1402
|
+
try {
|
|
1403
|
+
const { paymentPayload, paymentRequirements } = req.body;
|
|
1404
|
+
const result = await facilitator.settle(paymentPayload, paymentRequirements);
|
|
1405
|
+
res.json(result);
|
|
1406
|
+
} catch (err) {
|
|
1407
|
+
res.status(500).json({ error: (err as Error).message });
|
|
1408
|
+
}
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
const PORT = parseInt(process.env.PORT || "4000", 10);
|
|
1412
|
+
app.listen(PORT, () => {
|
|
1413
|
+
console.log(`Facilitator service running on port ${PORT}`);
|
|
1414
|
+
console.log(`Fee payer address: ${address}`);
|
|
1415
|
+
console.log(`Networks: Testnet + Mainnet`);
|
|
1416
|
+
});
|
|
1417
|
+
```
|