Skip to content

Commit 1cd1767

Browse files
committed
fix(axfer): convert asset amount to undefined
closes #24
1 parent fac3cf1 commit 1cd1767

File tree

5 files changed

+295
-4
lines changed

5 files changed

+295
-4
lines changed

.github/workflows/CI.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ jobs:
99
matrix:
1010
node-version: [ 18.x, 20.x ]
1111
steps:
12+
- name: Install algokit
13+
run: pipx install algokit
14+
- name: Start LocalNet
15+
run: algokit localnet start
1216
-
1317
name: Checkout
1418
uses: actions/checkout@v4.1.0

lib/algorand.transaction.axfer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,9 @@ export class AssetTransferTxBuilder implements IAssetTransferTxBuilder {
110110
return this
111111
}
112112
addAssetAmount(aamt: number | bigint): IAssetTransferTxBuilder {
113-
this.tx.aamt = AlgorandEncoder.safeCastBigInt(aamt)
113+
if(BigInt(aamt) !== 0n) {
114+
this.tx.aamt = AlgorandEncoder.safeCastBigInt(aamt)
115+
}
114116
return this
115117
}
116118
addAssetSender(asnd: string): IAssetTransferTxBuilder {

lib/e2e.spec.ts

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import {algo, AlgorandClient, waitForConfirmation} from '@algorandfoundation/algokit-utils'
2+
import {AlgorandTransactionCrafter, AssetConfigTransaction, AssetParamsBuilder} from "./index";
3+
import {Address, SuggestedParams} from "algosdk";
4+
import {SigningAccount, TransactionSignerAccount} from "@algorandfoundation/algokit-utils/types/account";
5+
import {AlgoAmount} from "@algorandfoundation/algokit-utils/types/amount";
6+
import {encode} from "hi-base32";
7+
import {sha512_256} from "js-sha512";
8+
9+
export type KeyPairRecord = {
10+
id: string
11+
keyPair: CryptoKeyPair
12+
}
13+
14+
const HASH_BYTES_LENGTH = 32;
15+
const ALGORAND_CHECKSUM_BYTE_LENGTH = 4;
16+
const ALGORAND_ADDRESS_LENGTH = 58;
17+
18+
export async function sign(txn: Uint8Array, kpr: KeyPairRecord){
19+
const sig = await crypto.subtle.sign(
20+
{name: 'Ed25519'},
21+
kpr.keyPair.privateKey,
22+
txn,
23+
);
24+
return new Uint8Array(sig)
25+
}
26+
/**
27+
* Get the address of a key pair
28+
* @param keyPair
29+
*/
30+
export async function getAddress(keyPair: CryptoKeyPair){
31+
const key= new Uint8Array(await crypto.subtle.exportKey("raw", keyPair.publicKey))
32+
return encodeAddress(key)
33+
}
34+
/**
35+
* Create a new key pair
36+
* @param extractable
37+
* @param keyUsages
38+
*/
39+
export async function generateKey(extractable: boolean = false, keyUsages: KeyUsage[] = ["sign"]){
40+
const keyPair = (await crypto.subtle.generateKey({name: 'Ed25519'}, extractable, keyUsages) as CryptoKeyPair)
41+
return {keyPair, id: await getAddress(keyPair)} as KeyPairRecord
42+
}
43+
/**
44+
* Encode a public key to an address
45+
* @param publicKey
46+
*/
47+
export function encodeAddress(publicKey: Uint8Array) {
48+
// Concatenate the publicKey and the checksum, remove the extra '====' and return the base32 encoding
49+
return `${encode([...publicKey, ...generateChecksum(publicKey)])}`.slice(0, ALGORAND_ADDRESS_LENGTH);
50+
}
51+
52+
/**
53+
* Generate the checksum of a public key
54+
* @param publicKey
55+
*/
56+
export function generateChecksum(publicKey: Uint8Array){
57+
return sha512_256.array(publicKey)
58+
.slice(
59+
HASH_BYTES_LENGTH - ALGORAND_CHECKSUM_BYTE_LENGTH,
60+
HASH_BYTES_LENGTH
61+
);
62+
}
63+
64+
65+
66+
describe('Algorand Transaction Crafter', () => {
67+
let algorand: AlgorandClient
68+
let deployer: Address & TransactionSignerAccount & {
69+
account: SigningAccount;
70+
}
71+
let params: SuggestedParams
72+
let algorandCrafter: AlgorandTransactionCrafter
73+
let masterKeyPair: KeyPairRecord
74+
let secondaryKeyPair: KeyPairRecord
75+
76+
77+
beforeAll(async () => {
78+
algorand = AlgorandClient.fromEnvironment()
79+
deployer = await algorand.account.fromEnvironment('DEPLOYER')
80+
81+
masterKeyPair = await generateKey()
82+
secondaryKeyPair = await generateKey()
83+
84+
await algorand.account.ensureFunded(masterKeyPair.id, deployer, new AlgoAmount({algos: 10}))
85+
await algorand.account.ensureFunded(secondaryKeyPair.id, deployer, new AlgoAmount({algos: 10}))
86+
87+
params = await algorand.getSuggestedParams()
88+
algorandCrafter = new AlgorandTransactionCrafter(params.genesisID as string, Buffer.from(params.genesisHash as Uint8Array).toString('base64'))
89+
}, 10000)
90+
91+
it("(OK) Pay Transaction", async () => {
92+
const txn = algorandCrafter
93+
.pay(1000, masterKeyPair.id, masterKeyPair.id)
94+
.addFirstValidRound(params.firstValid)
95+
.addLastValidRound(params.lastValid)
96+
.addFee(Number(params.fee) < 1000 ? 1000 : params.fee)
97+
.get()
98+
99+
const encodedTxn = txn.encode()
100+
const signature = await sign(encodedTxn, masterKeyPair)
101+
const signed = algorandCrafter.addSignature(encodedTxn, signature)
102+
const {txid} = await algorand.client.algod.sendRawTransaction(signed).do()
103+
await waitForConfirmation(txid, 20, algorand.client.algod)
104+
})
105+
it("(OK) KeyReg Online/Offline Transaction", async () => {
106+
let account = await algorand.client.algod.accountInformation(masterKeyPair.id).do()
107+
expect(account.status).toEqual("Offline")
108+
const onlineTxn = algorandCrafter
109+
.changeOnline(
110+
masterKeyPair.id,
111+
// Vote Key
112+
"CR3Bf/IJqzHC1TORQe83QnAkcB+JLyb+opP8f8q3ke0=",
113+
// Selection Key
114+
"CAdOmC4N+IqY40TkxNmGfzbuKXFCJr+ZUalwminLqAY=",
115+
// State Key
116+
"NhHc7r4U+lZhZCVVGD2LNnnyCx5ZxuHlEwyKQ3n4/41hEsQdZu/DSFeJiVMmKT/JmOiO8K/hWbb1865iaJmLlQ==",
117+
params.firstValid,
118+
params.lastValid,
119+
100,
120+
)
121+
.addFirstValidRound(params.firstValid)
122+
.addLastValidRound(params.lastValid)
123+
.addFee(Number(params.fee) < 1000 ? 1000 : params.fee)
124+
.get()
125+
const onlineEncodedTxn = onlineTxn.encode()
126+
const onlineSignature = await sign(onlineEncodedTxn, masterKeyPair)
127+
const onlineSigned = algorandCrafter.addSignature(onlineEncodedTxn, onlineSignature)
128+
const onlineStatus = await algorand.client.algod.sendRawTransaction(onlineSigned).do()
129+
await waitForConfirmation(onlineStatus.txid, 20, algorand.client.algod)
130+
account = await algorand.client.algod.accountInformation(masterKeyPair.id).do()
131+
expect(account.status).toEqual("Online")
132+
133+
// create keyreg transaction
134+
const offlineTxn = algorandCrafter
135+
.changeOffline(masterKeyPair.id)
136+
.addFirstValidRound(params.firstValid)
137+
.addLastValidRound(params.lastValid)
138+
.addFee(Number(params.fee) < 1000 ? 1000 : params.fee)
139+
.get()
140+
141+
const offlineEncodedTxn = offlineTxn.encode()
142+
const offlineSignature = await sign(offlineEncodedTxn, masterKeyPair)
143+
const offlineSigned = algorandCrafter.addSignature(offlineEncodedTxn, offlineSignature)
144+
const offlineStatus = await algorand.client.algod.sendRawTransaction(offlineSigned).do()
145+
await waitForConfirmation(offlineStatus.txid, 20, algorand.client.algod)
146+
})
147+
it("(OK) Non-participation Transactions", async () => {
148+
const txn = algorandCrafter
149+
.markNonParticipation(masterKeyPair.id)
150+
.addFirstValidRound(params.firstValid)
151+
.addLastValidRound(params.lastValid)
152+
.addFee(Number(params.fee) < 1000 ? 1000 : params.fee)
153+
.get()
154+
const encodedTxn = txn.encode()
155+
const signature = await sign(encodedTxn, masterKeyPair)
156+
const signed = algorandCrafter.addSignature(encodedTxn, signature)
157+
const {txid} = await algorand.client.algod.sendRawTransaction(signed).do()
158+
await waitForConfirmation(txid, 20, algorand.client.algod)
159+
})
160+
161+
it("(OK) Asset Config Create/OptIn/Transfer/Destroy", async ()=>{
162+
const assetParams = new AssetParamsBuilder()
163+
.addTotal(1000)
164+
.addDecimals(1)
165+
.addManagerAddress(masterKeyPair.id)
166+
.addAssetName("Big Yeetus")
167+
.addUnitName("YEET")
168+
.get()
169+
170+
// create asset transaction
171+
const createTxn: AssetConfigTransaction = algorandCrafter
172+
.createAsset(masterKeyPair.id, assetParams)
173+
.addFirstValidRound(params.firstValid)
174+
.addLastValidRound(params.lastValid)
175+
.addFee(Number(params.fee) < 1000 ? 1000 : params.fee)
176+
.get()
177+
178+
const createEncodedTxn = createTxn.encode()
179+
const createSignature = await sign(createEncodedTxn, masterKeyPair)
180+
const createSigned = algorandCrafter.addSignature(createEncodedTxn, createSignature)
181+
const createResult = await algorand.client.algod.sendRawTransaction(createSigned).do()
182+
const {assetIndex} = await waitForConfirmation(createResult.txid, 20, algorand.client.algod)
183+
184+
185+
const optInTxn = algorandCrafter
186+
.transferAsset(secondaryKeyPair.id, assetIndex as bigint, secondaryKeyPair.id, 0)
187+
.addFirstValidRound(params.firstValid)
188+
.addLastValidRound(params.lastValid)
189+
.addFee(Number(params.fee) < 1000 ? 1000 : params.fee)
190+
.get()
191+
192+
193+
const optInEncodedTxn = optInTxn.encode()
194+
const optInSignature = await sign(optInEncodedTxn, secondaryKeyPair)
195+
const optInSigned = algorandCrafter.addSignature(optInEncodedTxn, optInSignature)
196+
const optInResult = await algorand.client.algod.sendRawTransaction(optInSigned).do()
197+
await waitForConfirmation(optInResult.txid, 20 , algorand.client.algod)
198+
199+
200+
201+
const transferTxn = algorandCrafter
202+
.transferAsset(masterKeyPair.id, assetIndex as bigint, secondaryKeyPair.id, 10)
203+
.addFirstValidRound(params.firstValid)
204+
.addLastValidRound(params.lastValid)
205+
.addFee(Number(params.fee) < 1000 ? 1000 : params.fee)
206+
.get()
207+
208+
const transferEncodedTxn = transferTxn.encode()
209+
const transferSignature = await sign(transferEncodedTxn, masterKeyPair)
210+
const transferSigned = algorandCrafter.addSignature(transferEncodedTxn, transferSignature)
211+
const transferResult = await algorand.client.algod.sendRawTransaction(transferSigned).do()
212+
await waitForConfirmation(transferResult.txid, 20 , algorand.client.algod)
213+
214+
const closeOutTxn = algorandCrafter
215+
.transferAsset(secondaryKeyPair.id, assetIndex as bigint, secondaryKeyPair.id, 0)
216+
.addAssetCloseTo(masterKeyPair.id)
217+
.addFirstValidRound(params.firstValid)
218+
.addLastValidRound(params.lastValid)
219+
.addFee(Number(params.fee) < 1000 ? 1000 : params.fee)
220+
.get()
221+
222+
223+
const closeOutEncodedTxn = closeOutTxn.encode()
224+
const closeOutSignature = await sign(closeOutEncodedTxn, secondaryKeyPair)
225+
const closeOutSigned = algorandCrafter.addSignature(closeOutEncodedTxn, closeOutSignature)
226+
const closeOutResult = await algorand.client.algod.sendRawTransaction(closeOutSigned).do()
227+
await waitForConfirmation(closeOutResult.txid, 20 , algorand.client.algod)
228+
229+
const destroyTxn: AssetConfigTransaction = algorandCrafter
230+
.destroyAsset(masterKeyPair.id, assetIndex as bigint)
231+
.addFirstValidRound(params.firstValid)
232+
.addLastValidRound(params.lastValid)
233+
.addFee(Number(params.fee) < 1000 ? 1000 : params.fee)
234+
.get()
235+
236+
const destroyEncodedTxn = destroyTxn.encode()
237+
const destroySignature = await sign(destroyEncodedTxn, masterKeyPair)
238+
const destroySigned = algorandCrafter.addSignature(destroyEncodedTxn, destroySignature)
239+
const destroyResult = await algorand.client.algod.sendRawTransaction(destroySigned).do()
240+
await waitForConfirmation(destroyResult.txid, 20, algorand.client.algod)
241+
})
242+
})

package-lock.json

Lines changed: 44 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"author": "",
2525
"license": "AGPL-3.0-or-later",
2626
"devDependencies": {
27+
"@algorandfoundation/algokit-utils": "^8.2.2",
2728
"@commitlint/cli": "^17.8.0",
2829
"@commitlint/config-conventional": "^17.8.0",
2930
"@semantic-release/changelog": "^6.0.3",
@@ -34,9 +35,9 @@
3435
"@types/jest": "^29.5.5",
3536
"@types/node": "^20.7.1",
3637
"algosdk": "^3.1.0",
37-
"jest": "^29.7.0",
3838
"commitizen": "^4.3.0",
3939
"husky": "^8.0.0",
40+
"jest": "^29.7.0",
4041
"lint-staged": "^15.0.2",
4142
"semantic-release": "^22.0.5",
4243
"ts-jest": "^29.1.1",

0 commit comments

Comments
 (0)