@goplausible/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 +41 -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,126 @@
|
|
|
1
|
+
# ARC-4 Implementation (TypeScript)
|
|
2
|
+
|
|
3
|
+
This reference covers implementing and calling ARC-4 methods in Algorand TypeScript.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Using ARC-4 Types in Contracts](#using-arc-4-types-in-contracts)
|
|
8
|
+
- [Calling ARC-4 Methods from Clients](#calling-arc-4-methods-from-clients)
|
|
9
|
+
- [Common ARC-4 Patterns](#common-arc-4-patterns)
|
|
10
|
+
- [Structs](#structs)
|
|
11
|
+
- [Arrays](#arrays)
|
|
12
|
+
- [Bare Methods](#bare-methods)
|
|
13
|
+
|
|
14
|
+
## Using ARC-4 Types in Contracts
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { Contract, Account, Asset, Application, Global } from '@algorandfoundation/algorand-typescript'
|
|
18
|
+
import { abimethod, UInt64, Bool, Str, DynamicBytes, Address } from '@algorandfoundation/algorand-typescript/arc4'
|
|
19
|
+
import { PaymentTxn } from '@algorandfoundation/algorand-typescript/gtxn'
|
|
20
|
+
|
|
21
|
+
class MyContract extends Contract {
|
|
22
|
+
@abimethod()
|
|
23
|
+
demoTypes(
|
|
24
|
+
// Primitive types
|
|
25
|
+
amount: UInt64,
|
|
26
|
+
flag: Bool,
|
|
27
|
+
name: Str,
|
|
28
|
+
|
|
29
|
+
// Reference types
|
|
30
|
+
user: Account,
|
|
31
|
+
token: Asset,
|
|
32
|
+
app: Application,
|
|
33
|
+
|
|
34
|
+
// Complex types
|
|
35
|
+
data: DynamicBytes,
|
|
36
|
+
addr: Address,
|
|
37
|
+
): Str {
|
|
38
|
+
return new Str('Success')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@abimethod()
|
|
42
|
+
withTransaction(
|
|
43
|
+
payment: PaymentTxn, // Preceding payment in group
|
|
44
|
+
amount: UInt64,
|
|
45
|
+
): void {
|
|
46
|
+
assert(payment.receiver === Global.currentApplicationAddress)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Calling ARC-4 Methods from Clients
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Using AlgoKit Utils typed client
|
|
55
|
+
const result = await client.send.add({
|
|
56
|
+
args: { a: 10n, b: 20n }
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Access return value
|
|
60
|
+
const sum = result.return // BigInt
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Common ARC-4 Patterns
|
|
64
|
+
|
|
65
|
+
### Structs
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { Contract } from '@algorandfoundation/algorand-typescript'
|
|
69
|
+
import { abimethod, Struct, UInt64, Bool, Str, Address } from '@algorandfoundation/algorand-typescript/arc4'
|
|
70
|
+
|
|
71
|
+
class UserInfo extends Struct<{
|
|
72
|
+
name: Str
|
|
73
|
+
balance: UInt64
|
|
74
|
+
active: Bool
|
|
75
|
+
}> {}
|
|
76
|
+
|
|
77
|
+
class MyContract extends Contract {
|
|
78
|
+
@abimethod()
|
|
79
|
+
getUser(addr: Address): UserInfo {
|
|
80
|
+
return new UserInfo({
|
|
81
|
+
name: new Str('Alice'),
|
|
82
|
+
balance: new UInt64(1000),
|
|
83
|
+
active: new Bool(true),
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Arrays
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { Contract } from '@algorandfoundation/algorand-typescript'
|
|
93
|
+
import { abimethod, UInt64, DynamicArray, StaticArray } from '@algorandfoundation/algorand-typescript/arc4'
|
|
94
|
+
|
|
95
|
+
class MyContract extends Contract {
|
|
96
|
+
@abimethod()
|
|
97
|
+
processList(items: DynamicArray<UInt64>): UInt64 {
|
|
98
|
+
let total = 0n
|
|
99
|
+
for (const item of items) {
|
|
100
|
+
total += item.native
|
|
101
|
+
}
|
|
102
|
+
return new UInt64(total)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Bare Methods
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { Contract } from '@algorandfoundation/algorand-typescript'
|
|
111
|
+
import { baremethod } from '@algorandfoundation/algorand-typescript/arc4'
|
|
112
|
+
|
|
113
|
+
class MyContract extends Contract {
|
|
114
|
+
@baremethod({ create: 'require' })
|
|
115
|
+
create(): void {
|
|
116
|
+
// Called on app creation with no args
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
@baremethod({ allowActions: ['OptIn'] })
|
|
120
|
+
optIn(): void {
|
|
121
|
+
// Called on OptIn with no args
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Bare calls are identified by `NumAppArgs == 0`.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Implement ARC Standards (TypeScript)
|
|
2
|
+
|
|
3
|
+
This reference covers implementing ARC standards in Algorand TypeScript smart contracts and clients. ARC-4 defines the ABI for method calls and type encoding, while ARC-56 provides the modern application specification format for typed client generation.
|
|
4
|
+
|
|
5
|
+
## ARC-4 Contract Method Example
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { Contract } from '@algorandfoundation/algorand-typescript'
|
|
9
|
+
import { abimethod, UInt64, UInt128 } from '@algorandfoundation/algorand-typescript/arc4'
|
|
10
|
+
|
|
11
|
+
class Calculator extends Contract {
|
|
12
|
+
@abimethod()
|
|
13
|
+
add(a: UInt64, b: UInt64): UInt128 {
|
|
14
|
+
return new UInt128(a.native + b.native)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Using Typed Clients with ARC-56
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { AlgorandClient } from '@algorandfoundation/algokit-utils'
|
|
23
|
+
import { CalculatorFactory } from './clients/Calculator'
|
|
24
|
+
|
|
25
|
+
const algorand = AlgorandClient.defaultLocalNet()
|
|
26
|
+
|
|
27
|
+
// Deploy new contract
|
|
28
|
+
const factory = algorand.client.getTypedAppFactory(CalculatorFactory)
|
|
29
|
+
const { appClient } = await factory.deploy({
|
|
30
|
+
onSchemaBreak: 'replace',
|
|
31
|
+
onUpdate: 'update',
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
// Call methods with full type safety
|
|
35
|
+
const result = await appClient.send.add({
|
|
36
|
+
args: { a: 10n, b: 20n }
|
|
37
|
+
})
|
|
38
|
+
console.log(result.return) // BigInt: 30n
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## References
|
|
42
|
+
|
|
43
|
+
- [TypeScript ARC-4 Implementation](./implement-arc-standards-arc4.md) - ARC-4 types, contracts, and client calls
|
|
44
|
+
- [TypeScript ARC-32/56 Client Usage](./implement-arc-standards-arc32-arc56.md) - App specs, typed clients, and state access
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
# Canonical Test Examples
|
|
2
|
+
|
|
3
|
+
Real-world examples from [algorandfoundation/devportal-code-examples](https://github.com/algorandfoundation/devportal-code-examples/tree/main/projects/typescript-examples/contracts).
|
|
4
|
+
|
|
5
|
+
## HelloWorld - Basic Contract
|
|
6
|
+
|
|
7
|
+
**Source:** [HelloWorld/contract.algo.e2e.spec.ts](https://github.com/algorandfoundation/devportal-code-examples/blob/main/projects/typescript-examples/contracts/HelloWorld/contract.algo.e2e.spec.ts)
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { Config } from '@algorandfoundation/algokit-utils'
|
|
11
|
+
import { algorandFixture } from '@algorandfoundation/algokit-utils/testing'
|
|
12
|
+
import { Address } from 'algosdk'
|
|
13
|
+
import { beforeAll, beforeEach, describe, expect, test } from 'vitest'
|
|
14
|
+
import { HelloWorldFactory } from '../artifacts/clients/HelloWorld/HelloWorldClient'
|
|
15
|
+
|
|
16
|
+
describe('HelloWorld contract', () => {
|
|
17
|
+
const localnet = algorandFixture()
|
|
18
|
+
|
|
19
|
+
beforeAll(() => {
|
|
20
|
+
Config.configure({ debug: true })
|
|
21
|
+
})
|
|
22
|
+
beforeEach(localnet.newScope)
|
|
23
|
+
|
|
24
|
+
const deploy = async (account: Address) => {
|
|
25
|
+
const factory = localnet.algorand.client.getTypedAppFactory(HelloWorldFactory, {
|
|
26
|
+
defaultSender: account,
|
|
27
|
+
})
|
|
28
|
+
const { appClient } = await factory.deploy({
|
|
29
|
+
onUpdate: 'append',
|
|
30
|
+
onSchemaBreak: 'append',
|
|
31
|
+
suppressLog: true,
|
|
32
|
+
})
|
|
33
|
+
return { client: appClient }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test('say hello', async () => {
|
|
37
|
+
const { testAccount } = localnet.context
|
|
38
|
+
const { client } = await deploy(testAccount)
|
|
39
|
+
|
|
40
|
+
const result = await client
|
|
41
|
+
.newGroup()
|
|
42
|
+
.sayHello({ args: { firstName: 'Silvio', lastName: 'Micali' } })
|
|
43
|
+
.simulate()
|
|
44
|
+
|
|
45
|
+
expect(result.returns[0]).toBe('Hello Silvio Micali')
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## BoxStorage - Boxes and BoxMaps
|
|
51
|
+
|
|
52
|
+
**Source:** [BoxStorage/contract.algo.e2e.test.ts](https://github.com/algorandfoundation/devportal-code-examples/blob/main/projects/typescript-examples/contracts/BoxStorage/contract.algo.e2e.test.ts)
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
import { Config } from '@algorandfoundation/algokit-utils'
|
|
56
|
+
import { algorandFixture } from '@algorandfoundation/algokit-utils/testing'
|
|
57
|
+
import { Address, ABIUintType } from 'algosdk'
|
|
58
|
+
import { beforeAll, beforeEach, describe, expect, test } from 'vitest'
|
|
59
|
+
import { BoxStorageFactory } from '../artifacts/clients/BoxStorage/BoxStorageClient'
|
|
60
|
+
|
|
61
|
+
describe('BoxStorage contract', () => {
|
|
62
|
+
const localnet = algorandFixture()
|
|
63
|
+
|
|
64
|
+
beforeAll(() => {
|
|
65
|
+
Config.configure({ debug: true })
|
|
66
|
+
})
|
|
67
|
+
beforeEach(localnet.newScope)
|
|
68
|
+
|
|
69
|
+
const deploy = async (account: Address) => {
|
|
70
|
+
const factory = localnet.algorand.client.getTypedAppFactory(BoxStorageFactory, {
|
|
71
|
+
defaultSender: account,
|
|
72
|
+
})
|
|
73
|
+
const { appClient } = await factory.deploy({
|
|
74
|
+
onUpdate: 'append',
|
|
75
|
+
onSchemaBreak: 'append',
|
|
76
|
+
suppressLog: true,
|
|
77
|
+
})
|
|
78
|
+
return { client: appClient }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// CRITICAL: Fund app account before any box operations
|
|
82
|
+
const fundContract = async (sender: Address, receiver: Address) => {
|
|
83
|
+
await localnet.algorand.send.payment({
|
|
84
|
+
amount: (1).algo(),
|
|
85
|
+
sender,
|
|
86
|
+
receiver,
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Helper for BoxMap references with uint64 keys
|
|
91
|
+
function createBoxReference(appId: bigint, prefix: string, key: bigint) {
|
|
92
|
+
const uint64Type = new ABIUintType(64)
|
|
93
|
+
const encodedKey = uint64Type.encode(key)
|
|
94
|
+
const boxName = new Uint8Array([...new TextEncoder().encode(prefix), ...encodedKey])
|
|
95
|
+
return { appId, name: boxName }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
test('set and read box value', async () => {
|
|
99
|
+
const { testAccount } = localnet.context
|
|
100
|
+
const { client } = await deploy(testAccount)
|
|
101
|
+
|
|
102
|
+
// Fund BEFORE box operations
|
|
103
|
+
await fundContract(testAccount, client.appAddress)
|
|
104
|
+
|
|
105
|
+
await client
|
|
106
|
+
.newGroup()
|
|
107
|
+
.setBox({ args: { valueInt: 42n }, boxReferences: ['boxInt'] })
|
|
108
|
+
.send()
|
|
109
|
+
|
|
110
|
+
const boxValue = await client.state.box.boxInt()
|
|
111
|
+
expect(boxValue).toBe(42n)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('set and read BoxMap', async () => {
|
|
115
|
+
const { testAccount } = localnet.context
|
|
116
|
+
const { client } = await deploy(testAccount)
|
|
117
|
+
|
|
118
|
+
await fundContract(testAccount, client.appAddress)
|
|
119
|
+
|
|
120
|
+
await client
|
|
121
|
+
.newGroup()
|
|
122
|
+
.setBoxMap({
|
|
123
|
+
args: { key: 1n, value: 'Hello' },
|
|
124
|
+
boxReferences: [createBoxReference(client.appId, 'boxMap', 1n)],
|
|
125
|
+
})
|
|
126
|
+
.send()
|
|
127
|
+
|
|
128
|
+
const value = await client.getBoxMap({ args: { key: 1n } })
|
|
129
|
+
expect(value).toBe('Hello')
|
|
130
|
+
})
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## LocalStorage - Opt-in and Local State
|
|
135
|
+
|
|
136
|
+
**Source:** [LocalStorage/contract.algo.e2e.spec.ts](https://github.com/algorandfoundation/devportal-code-examples/blob/main/projects/typescript-examples/contracts/LocalStorage/contract.algo.e2e.spec.ts)
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { Config } from '@algorandfoundation/algokit-utils'
|
|
140
|
+
import { algorandFixture } from '@algorandfoundation/algokit-utils/testing'
|
|
141
|
+
import { Address } from 'algosdk'
|
|
142
|
+
import { beforeAll, beforeEach, describe, expect, test } from 'vitest'
|
|
143
|
+
import { LocalStorageFactory } from '../artifacts/clients/LocalStorage/LocalStorageClient'
|
|
144
|
+
|
|
145
|
+
describe('LocalStorage contract', () => {
|
|
146
|
+
const localnet = algorandFixture()
|
|
147
|
+
|
|
148
|
+
beforeAll(() => {
|
|
149
|
+
Config.configure({ debug: true })
|
|
150
|
+
})
|
|
151
|
+
beforeEach(localnet.newScope)
|
|
152
|
+
|
|
153
|
+
const deploy = async (account: Address) => {
|
|
154
|
+
const factory = localnet.algorand.client.getTypedAppFactory(LocalStorageFactory, {
|
|
155
|
+
defaultSender: account,
|
|
156
|
+
})
|
|
157
|
+
const { appClient } = await factory.deploy({
|
|
158
|
+
onUpdate: 'append',
|
|
159
|
+
onSchemaBreak: 'append',
|
|
160
|
+
suppressLog: true,
|
|
161
|
+
})
|
|
162
|
+
return { client: appClient }
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
test('opt in and read local state', async () => {
|
|
166
|
+
const { testAccount } = localnet.context
|
|
167
|
+
const { client } = await deploy(testAccount)
|
|
168
|
+
|
|
169
|
+
// MUST opt in before accessing local state
|
|
170
|
+
await client.newGroup().optIn.optInToApplication().send()
|
|
171
|
+
|
|
172
|
+
const result = await client.newGroup().readLocalState().simulate()
|
|
173
|
+
expect(result.returns![0]).toBeDefined()
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
test('write and read local state', async () => {
|
|
177
|
+
const { testAccount } = localnet.context
|
|
178
|
+
const { client } = await deploy(testAccount)
|
|
179
|
+
|
|
180
|
+
await client.newGroup().optIn.optInToApplication().send()
|
|
181
|
+
|
|
182
|
+
await client
|
|
183
|
+
.newGroup()
|
|
184
|
+
.writeLocalState({
|
|
185
|
+
args: {
|
|
186
|
+
valueString: 'Hello',
|
|
187
|
+
valueBool: true,
|
|
188
|
+
valueAccount: testAccount.addr.toString(),
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
.send()
|
|
192
|
+
|
|
193
|
+
const result = await client.newGroup().readLocalState().simulate()
|
|
194
|
+
expect(result.returns![0]![3]).toBe('Hello')
|
|
195
|
+
})
|
|
196
|
+
})
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## StructInBox - Struct Returns as Tuples
|
|
200
|
+
|
|
201
|
+
**Source:** [StructInBox/contract.algo.e2e.spec.ts](https://github.com/algorandfoundation/devportal-code-examples/blob/main/projects/typescript-examples/contracts/StructInBox/contract.algo.e2e.spec.ts)
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
test('create and get user struct', async () => {
|
|
205
|
+
const { testAccount } = localnet.context
|
|
206
|
+
const { client } = await deploy(testAccount)
|
|
207
|
+
|
|
208
|
+
await fundContract(testAccount, client.appAddress)
|
|
209
|
+
|
|
210
|
+
const testUser = {
|
|
211
|
+
id: 1n,
|
|
212
|
+
name: 'TestUser',
|
|
213
|
+
age: 25n,
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
await client.send.createNewUser({
|
|
217
|
+
args: { id: 1n, user: testUser },
|
|
218
|
+
boxReferences: [createBoxReference(client.appId, 'users', 1n)],
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
const { returns } = await client.send.getUser({
|
|
222
|
+
args: { id: 1n },
|
|
223
|
+
boxReferences: [createBoxReference(client.appId, 'users', 1n)],
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// CRITICAL: Struct returns are tuples, access by index
|
|
227
|
+
const [id, name, age] = returns?.[0]?.returnValue as [bigint, string, bigint]
|
|
228
|
+
expect(id).toBe(testUser.id)
|
|
229
|
+
expect(name).toBe(testUser.name)
|
|
230
|
+
expect(age).toBe(testUser.age)
|
|
231
|
+
})
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Key Patterns Summary
|
|
235
|
+
|
|
236
|
+
| Pattern | Code |
|
|
237
|
+
|---------|------|
|
|
238
|
+
| Deploy | `const { appClient } = await factory.deploy({ onUpdate: 'append', onSchemaBreak: 'append' })` |
|
|
239
|
+
| Send method | `await client.send.methodName({ args: { ... } })` |
|
|
240
|
+
| Chain methods | `await client.newGroup().method1().method2().send()` |
|
|
241
|
+
| Simulate | `await client.newGroup().method().simulate()` |
|
|
242
|
+
| Fund app | `await localnet.algorand.send.payment({ amount: (1).algo(), sender, receiver: client.appAddress })` |
|
|
243
|
+
| Opt-in | `await client.newGroup().optIn.optInToApplication().send()` |
|
|
244
|
+
| Box reference | `boxReferences: ['boxName']` or `boxReferences: [createBoxReference(...)]` |
|
|
245
|
+
| Struct return | `const [field1, field2] = result.return as [Type1, Type2]` |
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Unit Testing Guide
|
|
2
|
+
|
|
3
|
+
**Only use unit tests if the user explicitly requests them.** Integration tests (E2E) are the default and recommended approach.
|
|
4
|
+
|
|
5
|
+
## When to use unit tests
|
|
6
|
+
|
|
7
|
+
- User explicitly says "unit test" or "offline test"
|
|
8
|
+
- Testing pure contract logic without network interaction
|
|
9
|
+
- Fast iteration during contract development
|
|
10
|
+
|
|
11
|
+
## File naming
|
|
12
|
+
|
|
13
|
+
- Unit tests: `contract.algo.spec.ts` (no `.e2e.` in the name)
|
|
14
|
+
|
|
15
|
+
## Basic Setup
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing'
|
|
19
|
+
import { describe, expect, it, afterEach } from 'vitest'
|
|
20
|
+
import MyContract from './contract.algo'
|
|
21
|
+
|
|
22
|
+
describe('MyContract unit tests', () => {
|
|
23
|
+
const ctx = new TestExecutionContext()
|
|
24
|
+
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
ctx.reset()
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('should call method directly', () => {
|
|
30
|
+
const contract = ctx.contract.create(MyContract)
|
|
31
|
+
|
|
32
|
+
// Call methods directly - no ABI encoding
|
|
33
|
+
const result = contract.myMethod('arg1', 42n)
|
|
34
|
+
|
|
35
|
+
expect(result).toBe('expected value')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Key Differences from E2E Tests
|
|
41
|
+
|
|
42
|
+
| Aspect | E2E Tests | Unit Tests |
|
|
43
|
+
|--------|-----------|------------|
|
|
44
|
+
| **Framework** | `algorandFixture` | `TestExecutionContext` |
|
|
45
|
+
| **Network** | LocalNet | Emulated AVM |
|
|
46
|
+
| **Contract access** | Via typed client | Direct instance |
|
|
47
|
+
| **Method calls** | `client.send.method({ args: {...} })` | `contract.method(arg1, arg2)` |
|
|
48
|
+
| **Struct returns** | Tuples `[field1, field2]` | Object properties `result.field1` |
|
|
49
|
+
| **File naming** | `*.e2e.spec.ts` | `*.spec.ts` |
|
|
50
|
+
|
|
51
|
+
## Canonical Example
|
|
52
|
+
|
|
53
|
+
**Source:** [HelloWorld/contract.algo.spec.ts](https://github.com/algorandfoundation/devportal-code-examples/blob/main/projects/typescript-examples/contracts/HelloWorld/contract.algo.spec.ts)
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing'
|
|
57
|
+
import { describe, expect, it } from 'vitest'
|
|
58
|
+
import HelloWorld from './contract.algo'
|
|
59
|
+
|
|
60
|
+
describe('HelloWorld unit tests', () => {
|
|
61
|
+
const ctx = new TestExecutionContext()
|
|
62
|
+
|
|
63
|
+
it('returns greeting', () => {
|
|
64
|
+
const contract = ctx.contract.create(HelloWorld)
|
|
65
|
+
|
|
66
|
+
const result = contract.sayHello('Sally', 'Jones')
|
|
67
|
+
|
|
68
|
+
expect(result).toBe('Hello Sally Jones')
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('returns bananas', () => {
|
|
72
|
+
const contract = ctx.contract.create(HelloWorld)
|
|
73
|
+
|
|
74
|
+
const result = contract.sayBananas()
|
|
75
|
+
|
|
76
|
+
expect(result).toBe('Bananas')
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Testing with State
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { TestExecutionContext } from '@algorandfoundation/algorand-typescript-testing'
|
|
85
|
+
import { Uint64 } from '@algorandfoundation/algorand-typescript'
|
|
86
|
+
import { describe, expect, it, afterEach } from 'vitest'
|
|
87
|
+
import CounterContract from './contract.algo'
|
|
88
|
+
|
|
89
|
+
describe('Counter unit tests', () => {
|
|
90
|
+
const ctx = new TestExecutionContext()
|
|
91
|
+
|
|
92
|
+
afterEach(() => {
|
|
93
|
+
ctx.reset()
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('increments counter', () => {
|
|
97
|
+
const contract = ctx.contract.create(CounterContract)
|
|
98
|
+
|
|
99
|
+
contract.increment()
|
|
100
|
+
contract.increment()
|
|
101
|
+
|
|
102
|
+
// Access state directly on contract instance
|
|
103
|
+
expect(contract.counter.value).toBe(Uint64(2))
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Testing with Multiple Accounts
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
it('supports multiple accounts', () => {
|
|
112
|
+
const contract = ctx.contract.create(MyContract)
|
|
113
|
+
|
|
114
|
+
// Create test accounts
|
|
115
|
+
const account1 = ctx.any.account()
|
|
116
|
+
const account2 = ctx.any.account()
|
|
117
|
+
|
|
118
|
+
// Change sender for subsequent calls
|
|
119
|
+
ctx.txn.sender = account1
|
|
120
|
+
contract.doSomething()
|
|
121
|
+
|
|
122
|
+
ctx.txn.sender = account2
|
|
123
|
+
contract.doSomethingElse()
|
|
124
|
+
})
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Testing Boxes
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
it('sets box value', () => {
|
|
131
|
+
const contract = ctx.contract.create(BoxContract)
|
|
132
|
+
const key = Bytes('myKey')
|
|
133
|
+
const value = Bytes('myValue')
|
|
134
|
+
|
|
135
|
+
contract.setBox(key, value)
|
|
136
|
+
|
|
137
|
+
// Access box via ledger context
|
|
138
|
+
const storedValue = ctx.ledger.getBox(contract, key)
|
|
139
|
+
expect(storedValue).toEqual(value)
|
|
140
|
+
})
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Documentation
|
|
144
|
+
|
|
145
|
+
- [TypeScript Unit Testing Guide](https://dev.algorand.co/algokit/unit-testing/typescript/overview)
|
|
146
|
+
- [TestExecutionContext API](https://dev.algorand.co/algokit/unit-testing/typescript/concepts)
|
|
147
|
+
- [State Management in Tests](https://dev.algorand.co/algokit/unit-testing/typescript/state-management)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
|
|
2
|
+
# Testing Smart Contracts
|
|
3
|
+
|
|
4
|
+
Write integration tests for Algorand smart contracts using the `algorandFixture` and generated typed clients.
|
|
5
|
+
|
|
6
|
+
## Default: Integration Tests (E2E)
|
|
7
|
+
|
|
8
|
+
**Always write integration tests unless the user explicitly requests unit tests.** Integration tests run against LocalNet and test real contract behavior.
|
|
9
|
+
|
|
10
|
+
**Test Framework**: Both Vitest (default) and Jest are supported. The examples below use Vitest syntax, but Jest equivalents work identically.
|
|
11
|
+
|
|
12
|
+
### File naming
|
|
13
|
+
|
|
14
|
+
- Integration tests: `contract.algo.e2e.spec.ts`
|
|
15
|
+
- Unit tests (only if requested): `contract.algo.spec.ts`
|
|
16
|
+
|
|
17
|
+
### Canonical Example
|
|
18
|
+
|
|
19
|
+
Study and adapt from: [devportal-code-examples/contracts/HelloWorld](https://github.com/algorandfoundation/devportal-code-examples/tree/main/projects/typescript-examples/contracts/HelloWorld)
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { Config } from '@algorandfoundation/algokit-utils'
|
|
23
|
+
import { algorandFixture } from '@algorandfoundation/algokit-utils/testing'
|
|
24
|
+
import { Address } from 'algosdk'
|
|
25
|
+
import { beforeAll, beforeEach, describe, expect, test } from 'vitest'
|
|
26
|
+
import { MyContractFactory } from '../artifacts/clients/MyContract/MyContractClient'
|
|
27
|
+
|
|
28
|
+
describe('MyContract', () => {
|
|
29
|
+
const localnet = algorandFixture()
|
|
30
|
+
|
|
31
|
+
beforeAll(() => {
|
|
32
|
+
Config.configure({ debug: true })
|
|
33
|
+
})
|
|
34
|
+
// 10-second timeout: LocalNet needs time to process transactions and confirm blocks
|
|
35
|
+
beforeEach(localnet.newScope, 10_000)
|
|
36
|
+
|
|
37
|
+
const deploy = async (account: Address) => {
|
|
38
|
+
const factory = localnet.algorand.client.getTypedAppFactory(MyContractFactory, {
|
|
39
|
+
defaultSender: account,
|
|
40
|
+
})
|
|
41
|
+
const { appClient } = await factory.deploy({
|
|
42
|
+
onUpdate: 'append',
|
|
43
|
+
onSchemaBreak: 'append',
|
|
44
|
+
suppressLog: true,
|
|
45
|
+
})
|
|
46
|
+
return { client: appClient }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
test('should call method and verify result', async () => {
|
|
50
|
+
const { testAccount } = localnet.context
|
|
51
|
+
const { client } = await deploy(testAccount)
|
|
52
|
+
|
|
53
|
+
const result = await client.send.myMethod({ args: { value: 42n } })
|
|
54
|
+
expect(result.return).toBe(42n)
|
|
55
|
+
})
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## How to proceed
|
|
60
|
+
|
|
61
|
+
1. **Locate the generated client** in `artifacts/clients/<ContractName>/<ContractName>Client.ts`
|
|
62
|
+
2. **Import the Factory** (e.g., `MyContractFactory`) - NOT the Client directly
|
|
63
|
+
3. **Use the deploy helper pattern** shown above
|
|
64
|
+
4. **Call methods via `client.send.methodName()`** or `client.newGroup().methodName().send()`
|
|
65
|
+
|
|
66
|
+
## Critical Rules
|
|
67
|
+
|
|
68
|
+
| Rule | Details |
|
|
69
|
+
|------|---------|
|
|
70
|
+
| **Use newGroup() for chaining** | `client.newGroup().method1().method2().send()` |
|
|
71
|
+
| **Struct returns are tuples** | `const [id, name] = result.return as [bigint, string]` |
|
|
72
|
+
| **Fund app for BoxMap** | Send payment to `client.appAddress` before box operations |
|
|
73
|
+
| **Opt-in before local state** | `await client.newGroup().optIn.optInToApplication().send()` |
|
|
74
|
+
|
|
75
|
+
## Common Patterns
|
|
76
|
+
|
|
77
|
+
### Fund contract for box storage
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
await localnet.algorand.send.payment({
|
|
81
|
+
amount: (1).algo(),
|
|
82
|
+
sender: testAccount,
|
|
83
|
+
receiver: client.appAddress,
|
|
84
|
+
})
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Multiple users on same contract
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// Create and fund second user
|
|
91
|
+
const user2 = localnet.algorand.account.random()
|
|
92
|
+
await localnet.algorand.send.payment({
|
|
93
|
+
amount: (5).algo(),
|
|
94
|
+
sender: testAccount,
|
|
95
|
+
receiver: user2.addr,
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
// Get client for same app with different sender
|
|
99
|
+
const client2 = factory.getAppClientById({
|
|
100
|
+
appId: client.appId,
|
|
101
|
+
defaultSender: user2.addr,
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Box references
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { ABIUintType } from 'algosdk'
|
|
109
|
+
|
|
110
|
+
function createBoxReference(appId: bigint, prefix: string, key: bigint) {
|
|
111
|
+
const uint64Type = new ABIUintType(64)
|
|
112
|
+
const encodedKey = uint64Type.encode(key)
|
|
113
|
+
const boxName = new Uint8Array([...new TextEncoder().encode(prefix), ...encodedKey])
|
|
114
|
+
return { appId, name: boxName }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
await client.send.setBoxMap({
|
|
118
|
+
args: { key: 1n, value: 'hello' },
|
|
119
|
+
boxReferences: [createBoxReference(client.appId, 'boxMap', 1n)],
|
|
120
|
+
})
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## References
|
|
124
|
+
|
|
125
|
+
- [Canonical Examples](./test-smart-contracts-examples.md) - Complete patterns from algorandfoundation repos
|
|
126
|
+
- [Unit Testing Guide](./test-smart-contracts-unit-tests.md) - Only use if user requests unit tests
|
|
127
|
+
- [devportal-code-examples](https://github.com/algorandfoundation/devportal-code-examples/tree/main/projects/typescript-examples/contracts)
|