From a935089a4bb5e07ca223a17f28114e179be94243 Mon Sep 17 00:00:00 2001 From: shiqizng <80276844+shiqizng@users.noreply.github.com> Date: Tue, 3 Jan 2023 17:24:27 -0500 Subject: [PATCH 01/16] enhancement: add genesis type (#443) * adding genesis type --- types/genesis.go | 126 +++ types/genesis_test.go | 155 ++++ types/test_resource/mainnet_genesis.json | 946 +++++++++++++++++++++++ 3 files changed, 1227 insertions(+) create mode 100644 types/genesis.go create mode 100644 types/genesis_test.go create mode 100644 types/test_resource/mainnet_genesis.json diff --git a/types/genesis.go b/types/genesis.go new file mode 100644 index 00000000..f7f95cd6 --- /dev/null +++ b/types/genesis.go @@ -0,0 +1,126 @@ +package types + +import ( + "crypto/sha512" + "fmt" + + "github.com/algorand/go-algorand-sdk/v2/encoding/msgpack" +) + +// GenesisHashID is the Genesis HashID defined in go-algorand/protocol/hash.go +var GenesisHashID = "GE" + +// A Genesis object defines an Algorand "universe" +type Genesis struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + // The SchemaID allows nodes to store data specific to a particular + // universe + SchemaID string `codec:"id"` + + // Network identifies the unique algorand network for which the ledger + // is valid. + Network string `codec:"network"` + + // Proto is the consensus protocol in use at the genesis block. + Proto string `codec:"proto"` + + // Allocation determines the initial accounts and their state. + Allocation []GenesisAllocation `codec:"alloc"` + + // RewardsPool is the address of the rewards pool. + RewardsPool string `codec:"rwd"` + + // FeeSink is the address of the fee sink. + FeeSink string `codec:"fees"` + + // Timestamp for the genesis block + Timestamp int64 `codec:"timestamp"` + + // Arbitrary genesis comment string - will be excluded from file if empty + Comment string `codec:"comment"` + + // DevMode defines whether this network operates in a developer mode or not. Developer mode networks + // are a single node network, that operates without the agreement service being active. In liue of the + // agreement service, a new block is generated each time a node receives a transaction group. The + // default value for this field is "false", which makes this field empty from it's encoding, and + // therefore backward compatible. + DevMode bool `codec:"devmode"` +} + +type GenesisAllocation struct { + _struct struct{} `codec:""` + + Address string `codec:"addr"` + Comment string `codec:"comment"` + State Account `codec:"state"` +} + +type Account struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Status byte `codec:"onl"` + MicroAlgos uint64 `codec:"algo"` + VoteID [32]byte `codec:"vote"` + SelectionID [32]byte `codec:"sel"` + StateProofID [64]byte `codec:"stprf"` + VoteLastValid uint64 `codec:"voteLst"` + VoteKeyDilution uint64 `codec:"voteKD"` +} + +// ID is the effective Genesis identifier - the combination +// of the network and the ledger schema version +func (genesis Genesis) ID() string { + return string(genesis.Network) + "-" + genesis.SchemaID +} + +// Hash is the genesis hash. +func (genesis Genesis) Hash() Digest { + hashRep := []byte(GenesisHashID) + data := msgpack.Encode(genesis) + hashRep = append(hashRep, data...) + return sha512.Sum512_256(hashRep) +} + +// GenesisBalances contains the information needed to generate a new ledger +type GenesisBalances struct { + Balances map[Address]Account + FeeSink Address + RewardsPool Address + Timestamp int64 +} + +// Balances returns the genesis account balances. +func (genesis Genesis) Balances() (GenesisBalances, error) { + genalloc := make(map[Address]Account) + for _, entry := range genesis.Allocation { + addr, err := DecodeAddress(entry.Address) + if err != nil { + return GenesisBalances{}, fmt.Errorf("cannot parse genesis addr %s: %w", entry.Address, err) + } + + _, present := genalloc[addr] + if present { + return GenesisBalances{}, fmt.Errorf("repeated allocation to %s", entry.Address) + } + + genalloc[addr] = entry.State + } + + feeSink, err := DecodeAddress(genesis.FeeSink) + if err != nil { + return GenesisBalances{}, fmt.Errorf("cannot parse fee sink addr %s: %w", genesis.FeeSink, err) + } + + rewardsPool, err := DecodeAddress(genesis.RewardsPool) + if err != nil { + return GenesisBalances{}, fmt.Errorf("cannot parse rewards pool addr %s: %w", genesis.RewardsPool, err) + } + + return MakeTimestampedGenesisBalances(genalloc, feeSink, rewardsPool, genesis.Timestamp), nil +} + +// MakeTimestampedGenesisBalances returns the information needed to bootstrap the ledger based on a given time +func MakeTimestampedGenesisBalances(balances map[Address]Account, feeSink, rewardsPool Address, timestamp int64) GenesisBalances { + return GenesisBalances{Balances: balances, FeeSink: feeSink, RewardsPool: rewardsPool, Timestamp: timestamp} +} diff --git a/types/genesis_test.go b/types/genesis_test.go new file mode 100644 index 00000000..e6537d7c --- /dev/null +++ b/types/genesis_test.go @@ -0,0 +1,155 @@ +package types + +import ( + "encoding/base64" + "fmt" + "io/ioutil" + "testing" + + "github.com/algorand/go-algorand-sdk/v2/encoding/json" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestEncodeDecodeGenesis(t *testing.T) { + genesisStr, err := ioutil.ReadFile("test_resource/mainnet_genesis.json") + require.NoError(t, err) + + var genesis Genesis + err = json.Decode(genesisStr, &genesis) + require.NoError(t, err) + assert.Equal(t, base64.StdEncoding.EncodeToString(json.Encode(genesis)), base64.StdEncoding.EncodeToString(genesisStr)) + // hash value taken from https://developer.algorand.org/docs/get-details/algorand-networks/mainnet/#genesis-hash + expectedHash := "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=" + gh := genesis.Hash() + b := gh[:] + assert.Equal(t, base64.StdEncoding.EncodeToString(b), expectedHash) +} + +func TestGenesis_Balances(t *testing.T) { + containsErrorFunc := func(str string) assert.ErrorAssertionFunc { + return func(_ assert.TestingT, err error, i ...interface{}) bool { + require.ErrorContains(t, err, str) + return true + } + } + mustAddr := func(addr string) Address { + address, err := DecodeAddress(addr) + require.NoError(t, err) + return address + } + makeAddr := func(addr uint64) Address { + var address Address + address[0] = byte(addr) + return address + } + acctWith := func(algos uint64, addr string) GenesisAllocation { + return GenesisAllocation{ + _struct: struct{}{}, + Address: addr, + Comment: "", + State: Account{ + MicroAlgos: algos, + }, + } + } + goodAddr := makeAddr(100) + allocation1 := acctWith(1000, makeAddr(1).String()) + allocation2 := acctWith(2000, makeAddr(2).String()) + badAllocation := acctWith(1234, "El Toro Loco") + type fields struct { + Allocation []GenesisAllocation + FeeSink string + RewardsPool string + } + tests := []struct { + name string + fields fields + want GenesisBalances + wantErr assert.ErrorAssertionFunc + }{ + { + name: "basic test", + fields: fields{ + Allocation: []GenesisAllocation{allocation1}, + FeeSink: goodAddr.String(), + RewardsPool: goodAddr.String(), + }, + want: GenesisBalances{ + Balances: map[Address]Account{ + mustAddr(allocation1.Address): allocation1.State, + }, + FeeSink: goodAddr, + RewardsPool: goodAddr, + Timestamp: 0, + }, + wantErr: assert.NoError, + }, + { + name: "two test", + fields: fields{ + Allocation: []GenesisAllocation{allocation1, allocation2}, + FeeSink: goodAddr.String(), + RewardsPool: goodAddr.String(), + }, + want: GenesisBalances{ + Balances: map[Address]Account{ + mustAddr(allocation1.Address): allocation1.State, + mustAddr(allocation2.Address): allocation2.State, + }, + FeeSink: goodAddr, + RewardsPool: goodAddr, + Timestamp: 0, + }, + wantErr: assert.NoError, + }, + { + name: "bad fee sink", + fields: fields{ + Allocation: []GenesisAllocation{allocation1, allocation2}, + RewardsPool: goodAddr.String(), + }, + wantErr: containsErrorFunc("cannot parse fee sink addr"), + }, + { + name: "bad rewards pool", + fields: fields{ + Allocation: []GenesisAllocation{allocation1, allocation2}, + FeeSink: goodAddr.String(), + }, + wantErr: containsErrorFunc("cannot parse rewards pool addr"), + }, + { + name: "bad genesis addr", + fields: fields{ + Allocation: []GenesisAllocation{badAllocation}, + FeeSink: goodAddr.String(), + RewardsPool: goodAddr.String(), + }, + wantErr: containsErrorFunc("cannot parse genesis addr"), + }, + { + name: "repeat address", + fields: fields{ + Allocation: []GenesisAllocation{allocation1, allocation1}, + FeeSink: goodAddr.String(), + RewardsPool: goodAddr.String(), + }, + wantErr: containsErrorFunc("repeated allocation to"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + genesis := Genesis{ + Allocation: tt.fields.Allocation, + FeeSink: tt.fields.FeeSink, + RewardsPool: tt.fields.RewardsPool, + } + got, err := genesis.Balances() + if tt.wantErr(t, err, fmt.Sprintf("Balances()")) { + return + } + assert.Equalf(t, tt.want, got, "Balances()") + }) + } +} diff --git a/types/test_resource/mainnet_genesis.json b/types/test_resource/mainnet_genesis.json new file mode 100644 index 00000000..987a290b --- /dev/null +++ b/types/test_resource/mainnet_genesis.json @@ -0,0 +1,946 @@ +{ + "alloc": [ + { + "addr": "737777777777777777777777777777777777777777777777777UFEJ2CI", + "comment": "RewardsPool", + "state": { + "algo": 10000000000000, + "onl": 2 + } + }, + { + "addr": "Y76M3MSY6DKBRHBL7C3NNDXGS5IIMQVQVUAB6MP4XEMMGVF2QWNPL226CA", + "comment": "FeeSink", + "state": { + "algo": 1000000, + "onl": 2 + } + }, + { + "addr": "ALGORANDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIN5DNAU", + "comment": "A BIT DOES E NOT BUT E STARTS EVERYTHING LIFE A MANY FORTUNE R BUILD SIMPLER BE THE STARTS PERSEVERES FAVORS A ENOUGH RIPROVANDO POSSIBLE JOURNEY VICTORIA HE BOLD U WITHOUT MEN A K OF BORDERS WHO HE E RACES TOMORROW BUT WHO SINGLE PURPOSE GEOGRAPHICAL PROVANDO A KNOW SUFFOCATES NOT SCIENCE STEP MATHEMATICS OF OR A BRIDGES WALLS TECHNOLOGY TODAY AND WITH AS ET MILES OF THOUSAND VITA SIMPLE TOO MUST AS NOT MADE NOT", + "state": { + "algo": 1000000, + "onl": 2 + } + }, + { + "addr": "XQJEJECPWUOXSKMIC5TCSARPVGHQJIIOKHO7WTKEPPLJMKG3D7VWWID66E", + "comment": "AlgorandCommunityAnnouncement", + "state": { + "algo": 10000000, + "onl": 2 + } + }, + { + "addr": "VCINCVUX2DBKQ6WP63NOGPEAQAYGHGSGQX7TSH4M5LI5NBPVAGIHJPMIPM", + "comment": "AuctionsMaster", + "state": { + "algo": 1000000000, + "onl": 2 + } + }, + { + "addr": "OGP6KK5KCMHT4GOEQXJ4LLNJ7D6P6IH7MV5WZ5EX4ZWACHP75ID5PPEE5E", + "comment": "", + "state": { + "algo": 300000000000000, + "onl": 2 + } + }, + { + "addr": "AYBHAG2DAIOG26QEV35HKUBGWPMPOCCQ44MQEY32UOW3EXEMSZEIS37M2U", + "comment": "", + "state": { + "algo": 300000000000000, + "onl": 2 + } + }, + { + "addr": "2XKK2L6HOBCYHGIGBS3N365FJKHS733QOX42HIYLSBARUIJHMGQZYAQDRY", + "comment": "", + "state": { + "algo": 300000000000000, + "onl": 2 + } + }, + { + "addr": "ZBSPQQG7O5TR5MHPG3D5RS2TIFFD5NMOPR77VUKURMN6HV2BSN224ZHKGU", + "comment": "", + "state": { + "algo": 300000000000000, + "onl": 2 + } + }, + { + "addr": "7NQED6NJ4NZU7B5HGGFU2ZEC2UZQYU2SA5S4QOE2EXBVAR4CNAHIXV2XYY", + "comment": "", + "state": { + "algo": 300000000000000, + "onl": 2 + } + }, + { + "addr": "RX2ZKVJ43GNYDJNIOB6TIX26U7UEQFUQY46OMHX6CXLMMBHENJIH4YVLUQ", + "comment": "", + "state": { + "algo": 300000000000000, + "onl": 2 + } + }, + { + "addr": "RHSKYCCZYYQ2BL6Z63626YUETJMLFGVVV47ED5D55EKIK4YFJ5DQT5CV4A", + "comment": "", + "state": { + "algo": 300000000000000, + "onl": 2 + } + }, + { + "addr": "RJS6FDZ46ZZJIONLMMCKDJHYSJNHHAXNABMAVSGH23ULJSEAHZC6AQ6ALE", + "comment": "", + "state": { + "algo": 300000000000000, + "onl": 2 + } + }, + { + "addr": "AZ2KKAHF2PJMEEUVN4E2ILMNJCSZLJJYVLBIA7HOY3BQ7AENOVVTXMGN3I", + "comment": "", + "state": { + "algo": 300000000000000, + "onl": 2 + } + }, + { + "addr": "CGUKRKXNMEEOK7SJKOGXLRWEZESF24ELG3TAW6LUF43XRT2LX4OVQLU4BQ", + "comment": "", + "state": { + "algo": 300000000000000, + "onl": 2 + } + }, + { + "addr": "VVW6BVYHBG7MZQXKAR3OSPUZVAZ66JMQHOBMIBJG6YSPR7SLMNAPA7UWGY", + "comment": "", + "state": { + "algo": 250000000000000, + "onl": 2 + } + }, + { + "addr": "N5BGWISAJSYT7MVW2BDTTEHOXFQF4QQH4VKSMKJEOA4PHPYND43D6WWTIU", + "comment": "", + "state": { + "algo": 1740000000000000, + "onl": 2 + } + }, + { + "addr": "MKT3JAP2CEI5C4IX73U7QKRUF6JR7KPKE2YD6BLURFVPW6N7CYXVBSJPEQ", + "comment": "", + "state": { + "algo": 158000000000000, + "onl": 2 + } + }, + { + "addr": "GVCPSWDNSL54426YL76DZFVIZI5OIDC7WEYSJLBFFEQYPXM7LTGSDGC4SA", + "comment": "", + "state": { + "algo": 49998988000000, + "onl": 1, + "sel": "lZ9z6g0oSlis/8ZlEyOMiGfX0XDUcObfpJEg5KjU0OA=", + "vote": "Kk+5CcpHWIXSMO9GiAvnfe+eNSeRtpDb2telHb6I1EE=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "M7XKTBQXVQARLS7IVS6NVDHNLJFIAXR2CGGZTUDEKRIHRVLWL5TJFJOL5U", + "comment": "", + "state": { + "algo": 50000000000000, + "onl": 1, + "sel": "Z5gE/m2E/WSuaS5E8aYzO2DugTdSWQdc5W5BroCJdms=", + "vote": "QHHw03LnZQhKvjjIxVj3+qwgohOij2j3TBDMy7V9JMk=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "QFYWTHPNZBKKZ4XG2OWVNEX6ETBISD2VJZTCMODIZKT3QHQ4TIRJVEDVV4", + "comment": "", + "state": { + "algo": 50000000000000, + "onl": 1, + "sel": "NthIIUyiiRVnU/W13ajFFV4EhTvT5EZR/9N6ZRD/Z7U=", + "vote": "3KtiTLYvHJqa+qkGFj2RcZC77bz9yUYKxBZt8B24Z+c=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "DPOZQ6HRYLNNWVQL3I4XV4LMK5UZVROKGJBRIYIRNZUBMVHCU4DZWDBHYE", + "comment": "", + "state": { + "algo": 50000000000000, + "onl": 1, + "sel": "PBZ/agWgmwMdmWgt/W0NvdTN/XSTrVhPvRSMjmP5j90=", + "vote": "FDONnMcq1acmIBjJr3vz4kx4Q8ZRZ8oIH8xXRV5c4L8=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "42GALMKS3HMDB24ZPOR237WQ5QDHL5NIRC3KIA4PCKENJZAD5RP5QPBFO4", + "comment": "", + "state": { + "algo": 50000000000000, + "onl": 1, + "sel": "p7axjoy3Wn/clD7IKoTK2Zahc5ZU+Qkt2POVHKugQU4=", + "vote": "PItHHw+b01XplxRBFmZniqmdm+RyJFYd0fDz+OP4D6o=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "OXWIMTRZA5TVPABJF534EBBERJG367OLAB6VFN4RAW5P6CQEMXEX7VVDV4", + "comment": "", + "state": { + "algo": 50000000000000, + "onl": 1, + "sel": "RSOWYRM6/LD7MYxlZGvvF+WFGmBZg7UUutdkaWql0Xo=", + "vote": "sYSYFRL7AMJ61egushOYD5ABh9p06C4ZRV/OUSx7o3g=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "AICDUO6E46YBJRLM4DFJPVRVZGOFTRNPF7UPQXWEPPYRPVGIMQMLY5HLFM", + "comment": "", + "state": { + "algo": 50000000000000, + "onl": 1, + "sel": "0vxjPZqEreAhUt9PHJU2Eerb7gBhMU+PgyEXYLmbifg=", + "vote": "fuc0z/tpiZXBWARCJa4jPdmDvSmun4ShQLFiAxQkOFI=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "DYATVHCICZA7VVOWZN6OLFFSKUAZ64TZ7WZWCJQBFWL3JL4VBBV6R7Z6IE", + "comment": "", + "state": { + "algo": 50000000000000, + "onl": 1, + "sel": "KO2035CRpp1XmVPOTOF6ICWCw/0I6FgelKxdwPq+gMY=", + "vote": "rlcoayAuud0suR3bvvI0+psi/NzxvAJUFlp+I4ntzkM=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "6XJH2PJMAXWS4RGE6NBYIS3OZFOPU3LOHYC6MADBFUAALSWNFHMPJUWVSE", + "comment": "", + "state": { + "algo": 50000000000000, + "onl": 1, + "sel": "PgW1dncjs9chAVM89SB0FD4lIrygxrf+uqsAeZw8Qts=", + "vote": "pA4NJqjTAtHGGvZWET9kliq24Go5kEW8w7f1BGAWmKY=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "EYOZMULFFZZ5QDDMWQ64HKIMUPPNEL3WJMNGAFD43L52ZXTPESBEVJPEZU", + "comment": "", + "state": { + "algo": 50000000000000, + "onl": 1, + "sel": "sfebD2noAbrn1vblMmeCIeGB3BxLGKQDTG4sKSNibFs=", + "vote": "Cuz3REj26J+JhOpf91u6PO6MV5ov5b1K/ii1U1uPD/g=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "I3345FUQQ2GRBHFZQPLYQQX5HJMMRZMABCHRLWV6RCJYC6OO4MOLEUBEGU", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "MkH9KsdwiFgYtFFWFu48CeejEop1vsyGFG4/kqPIOFg=", + "vote": "RcntidhQqXQIvYjLFtc6HuL335rMnNX92roa2LcC+qQ=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "6LQH42A4QJ3Y27FGKJWERY3MD65SXM4QQCJJR2HRJYNB427IQ73YBI3YFY", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "nF3mu9Bu0Ad5MIrT31NgTxxrsZOXc4u1+WCvaPQTYEQ=", + "vote": "NaqWR/7FzOq/MiHb3adO6+J+kvnQKat8NSqEmoEkVfE=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "3V2MC7WJGAFU2EHWBHEETIMJVFJNAT4KKWVPOMJFJIM6ZPWEJRJ4POTXGI", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "3i4K8zdmnf1kxwgcNmI3x50iIwAxDmLMvoQEhjzhado=", + "vote": "wfJWa0kby76rqX2yvCD/aCfJdNt+qItylDPQiuAWFkQ=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "FTXSKED23VEXNW442T2JKNPPNUC2WKFNRWBVQTFMT7HYX365IVLZXYILAI", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "icuL7ehcGonAcJ02Zy4MIHqcT+Sp1R1UURNCYJQHmo4=", + "vote": "tmFcj3v7X5DDxKI1IDbGdhXh3a5f0Ab1ftltM7TgIDE=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "IAOW7PXLCDGLKMIQF26IXFF4THSQMU662MUU6W5KPOXHIVKHYFLYRWOUT4", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "zTn9rl/8Y2gokMdFyFP/pKg4eP02arkxlrBZIS94vPI=", + "vote": "a0pX68GgY7u8bd2Z3311+Mtc6yDnESZmi9k8zJ0oHzY=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "4NRNE5RIGC2UGOMGMDR6L5YMQUV3Q76TPOR7TDU3WEMJLMC6BSBEKPJ2SY", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "orSV2VHPY8m5ckEHGwK0r+SM9jq4BujAICXegAUAecI=", + "vote": "NJ9tisH+7+S29m/uMymFTD8X02/PKU0JUX1ghnLCzkw=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "E2EIMPLDISONNZLXONGMC33VBYOIBC2R7LVOS4SYIEZYJQK6PYSAPQL7LQ", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "XM2iW9wg9G5TyOfVu9kTS80LDIqcEPkJsgxaZll3SWA=", + "vote": "p/opFfDOsIomj5j7pAYU+G/CNUIwvD2XdEer6dhGquQ=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "APDO5T76FB57LNURPHTLAGLQOHUQZXYHH2ZKR4DPQRKK76FB4IAOBVBXHQ", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "5k2vclbUQBE6zBl45F3kGSv1PYhE2k9wZjxyxoPlnwA=", + "vote": "3dcLRSckm3wd9KB0FBRxub3meIgT6lMZnv5F08GJgEo=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "3KJTYHNHK37G2JDZJPV55IHBADU22TX2FPJZJH43MY64IFWKVNMP2F4JZE", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "o5e9VLqMdmJas5wRovfYFHgQ+Z6sQoATf3a6j0HeIXU=", + "vote": "rG7J8pPAW+Xtu5pqMIJOG9Hxdlyewtf9zPHEKR2Q6OE=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "IVKDCE6MS44YVGMQQFVXCDABW2HKULKIXMLDS2AEOIA6P2OGMVHVJ64MZI", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "XgUrwumD7oin/rG3NKwywBSsTETg/aWg9MjCDG61Ybg=", + "vote": "sBPEGGrEqcQMdT+iq2ududNxCa/1HcluvsosO1SkE/k=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "2WDM5XFF7ONWFANPE5PBMPJLVWOEN2BBRLSKJ37PQYW5WWIHEFT3FV6N5Y", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "Lze5dARJdb1+Gg6ui8ySIi+LAOM3P9dKiHKB9HpMM6A=", + "vote": "ys4FsqUNQiv+N0RFtr0Hh9OnzVcxXS6cRVD/XrLgW84=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "EOZWAIPQEI23ATBWQ5J57FUMRMXADS764XLMBTSOLVKPMK5MK5DBIS3PCY", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "jtmLcJhaAknJtA1cS5JPZil4SQ5SKh8P0w1fUw3X0CE=", + "vote": "pyEtTxJAas/j+zi/N13b/3LB4UoCar1gfcTESl0SI2I=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "REMF542E5ZFKS7SGSNHTYB255AUITEKHLAATWVPK3CY7TAFPT6GNNCHH6M", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "8ggWPvRpSkyrjxoh1SVS9PiSjff2azWtH0HFadwI9Ck=", + "vote": "Ej/dSkWbzRf09RAuWZfC4luRPNuqkLFCSGYXDcOtwic=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "T4UBSEAKK7JHT7RNLXVHDRW72KKFJITITR54J464CAGE5FGAZFI3SQH3TI", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "eIB8MKaG2lyJyM9spk+b/Ap/bkbo9bHfvF9f8T51OQk=", + "vote": "7xuBsE5mJaaRAdm5wnINVwm4SgPqKwJTAS1QBQV3sEc=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "YUDNQMOHAXC4B3BAMRMMQNFDFZ7GYO2HUTBIMNIP7YQ4BL57HZ5VM3AFYU", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "CSTCDvvtsJB0VYUcl3oRXyiJfhm3CtqvRIuFYZ69Z68=", + "vote": "uBK1TH4xKdWfv5nnnHkvYssI0tyhWRFZRLHgVt9TE1k=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "4SZTEUQIURTRT37FCI3TRMHSYT5IKLUPXUI7GWC5DZFXN2DGTATFJY5ABY", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "THGOlrqElX13xMqeLUPy6kooTbXjiyrUoZfVccnHrfI=", + "vote": "k4hde2Q3Zl++sQobo01U8heZd/X0GIX1nyqM8aI/hCY=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "UEDD34QFEMWRGYCBLKZIEHPKSTNBFSRMFBHRJPY3O2JPGKHQCXH4IY6XRI", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "jE+AUFvtp2NJsfNeUZeXdWt0X6I58YOgY+z/HB17GDs=", + "vote": "lmnYTjg1FhRNAR9TwVmOahVr5Z+7H1GO6McmvOZZRTQ=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "HHZQOGQKMQDLBEL3HXMDX7AGTNORYVZ4JFDWVSL5QLWMD3EXOIAHDI5L7M", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "Hajdvzem2rR2GjLmCG+98clHZFY5Etlp0n+x/gQTGj0=", + "vote": "2+Ie4MDWC6o/SfFSqev1A7UAkzvKRESI42b4NKS6Iw8=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "XRTBXPKH3DXDJ5OLQSYXOGX3DJ3U5NR6Y3LIVIWMK7TY33YW4I2NJZOTVE", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "5qe7rVoQfGdIUuDbhP2ABWivCoCstKbUsjdmYY76akA=", + "vote": "3J3O9DyJMWKvACubUK9QvmCiArtZR7yFHWG7k7+apdQ=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "JJFGCPCZPYRLOUYBZVC4F7GRPZ5CLB6BMTVRGNDP7GRGXL6GG4JEN7DL54", + "comment": "", + "state": { + "algo": 24000000000000, + "onl": 1, + "sel": "YoRFAcTiOgJcLudNScYstbaKJ8anrrHwQMZAffWMqYE=", + "vote": "VQFKlDdxRqqqPUQ/mVoF8xZS9BGxUtTnPUjYyKnOVRA=", + "voteKD": 10000, + "voteLst": 3000000 + } + }, + { + "addr": "4VNSA2GZVUD5ZNO62OVVNP4NEL2LIEE5N3MZEK4BKH62KGKRLVINFZYTZM", + "comment": "", + "state": { + "algo": 100000000000000, + "onl": 2 + } + }, + { + "addr": "IVCEEIH2Q32DZNRTS5XFVEFFAQGERNZHHQT6S4UPY7ORJMHIQDSTX7YM4E", + "comment": "", + "state": { + "algo": 408400000000000, + "onl": 2 + } + }, + { + "addr": "PLFHBIRGM3ZWGAMCXTREX2N537TWOMFIQXHFO2ZGQOEPZU473SYBVGVA5M", + "comment": "", + "state": { + "algo": 1011600000000000, + "onl": 2 + } + }, + { + "addr": "KF7X4ZABZUQU7IFMHSKLDKWCS4F3GZLOLJRDAK5KMEMDAGU32CX36CJQ5M", + "comment": "", + "state": { + "algo": 10000000000000, + "onl": 2 + } + }, + { + "addr": "BTEESEYQMFLWZKULSKLNDELYJTOOQK6ZT4FBCW3TOZQ55NZYLOO6BRQ5K4", + "comment": "", + "state": { + "algo": 36199095000000, + "onl": 2 + } + }, + { + "addr": "E36JOZVSZZDXKSERASLAWQE4NU67HC7Q6YDOCG7P7IRRWCPSWXOI245DPA", + "comment": "", + "state": { + "algo": 20000000000000, + "onl": 2 + } + }, + { + "addr": "I5Q6RRN44OZWYMX6YLWHBGEVPL7S3GBUCMHZCOOLJ245TONH7PERHJXE4A", + "comment": "", + "state": { + "algo": 20000000000000, + "onl": 2 + } + }, + { + "addr": "2GYS272T3W2AP4N2VX5BFBASVNLWN44CNVZVKLWMMVPZPHVJ52SJPPFQ2I", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "D5LSV2UGT4JJNSLJ5XNIF52WP4IHRZN46ZGWH6F4QEF4L2FLDYS6I6R35Y", + "comment": "", + "state": { + "algo": 20000000000000, + "onl": 2 + } + }, + { + "addr": "UWMSBIP2CGCGR3GYVUIOW3YOMWEN5A2WRTTBH6Y23KE3MOVFRHNXBP6IOE", + "comment": "", + "state": { + "algo": 20000000000000, + "onl": 2 + } + }, + { + "addr": "OF3MKZZ3L5ZN7AZ46K7AXJUI4UWJI3WBRRVNTDKYVZUHZAOBXPVR3DHINE", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "2PPWE36YUMWUVIFTV2A6U4MLZLGROW4GHYIRVHMUCHDH6HCNVPUKPQ53NY", + "comment": "", + "state": { + "algo": 440343426000000, + "onl": 2 + } + }, + { + "addr": "JRGRGRW4HYBNAAHR7KQLLBAGRSPOYY6TRSINKYB3LI5S4AN247TANH5IQY", + "comment": "", + "state": { + "algo": 362684706000000, + "onl": 2 + } + }, + { + "addr": "D7YVVQJXJEFOZYUHJLIJBW3ATCAW46ML62VYRJ3SMGLOHMWYH4OS3KNHTU", + "comment": "", + "state": { + "algo": 10000000000000, + "onl": 2 + } + }, + { + "addr": "PZJKH2ILW2YDZNUIYQVJZ2MANRSMK6LCHAFSAPYT6R3L3ZCWKYRDZXRVY4", + "comment": "", + "state": { + "algo": 10000000000000, + "onl": 2 + } + }, + { + "addr": "3MODEFJVPGUZH3HDIQ6L2MO3WLJV3FK3XSWKFBHUGZDCHXQMUKD4B7XLMI", + "comment": "", + "state": { + "algo": 130000000000000, + "onl": 2 + } + }, + { + "addr": "WNSA5P6C5IIH2UJPQWJX6FRNPHXY7XZZHOWLSW5ZWHOEHBUW4AD2H6TZGM", + "comment": "", + "state": { + "algo": 130000000000000, + "onl": 2 + } + }, + { + "addr": "OO65J5AIFDS6255WL3AESTUGJD5SUV47RTUDOUGYHEIME327GX7K2BGC6U", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "DM6A24ZWHRZRM2HWXUHAUDSAACO7VKEZAOC2THWDXH4DX5L7LSO3VF2OPU", + "comment": "", + "state": { + "algo": 20000000000000, + "onl": 2 + } + }, + { + "addr": "NTJJSFM75RADUOUGOBHZB7IJGO7NLVBWA66EYOOPU67H7LYIXVSPSI7BTA", + "comment": "", + "state": { + "algo": 18099548000000, + "onl": 2 + } + }, + { + "addr": "DAV2AWBBW4HBGIL2Z6AAAWDWRJPTOQD6BSKU2CFXZQCOBFEVFEJ632I2LY", + "comment": "", + "state": { + "algo": 1000000000000, + "onl": 2 + } + }, + { + "addr": "M5VIY6QPSMALVVPVG5LVH35NBMH6XJMXNWKWTARGGTEEQNQ3BHPQGYP5XU", + "comment": "", + "state": { + "algo": 20000000000000, + "onl": 2 + } + }, + { + "addr": "WZZLVKMCXJG3ICVZSVOVAGCCN755VHJKZWVSVQ6JPSRQ2H2OSPOOZKW6DQ", + "comment": "", + "state": { + "algo": 45248869000000, + "onl": 2 + } + }, + { + "addr": "XEJLJUZRQOLBHHSOJJUE4IWI3EZOM44P646UDKHS4AV2JW7ZWBWNFGY6BU", + "comment": "", + "state": { + "algo": 20000000000000, + "onl": 2 + } + }, + { + "addr": "OGIPDCRJJPNVZ6X6NBQHMTEVKJVF74QHZIXVLABMGUKZWNMEH7MNXZIJ7Q", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "G47R73USFN6FJJQTI3JMYQXO7F6H4LRPBCTTAD5EZWPWY2WCG64AVPCYG4", + "comment": "", + "state": { + "algo": 10000000000000, + "onl": 2 + } + }, + { + "addr": "PQ5T65QB564NMIY6HXNYZXTFRSTESUEFIF2C26ZZKIZE6Q4R4XFP5UYYWI", + "comment": "", + "state": { + "algo": 5000000000000, + "onl": 2 + } + }, + { + "addr": "R6S7TRMZCHNQPKP2PGEEJ6WYUKMTURNMM527ZQXABTHFT5GBVMF6AZAL54", + "comment": "", + "state": { + "algo": 1000000000000, + "onl": 2 + } + }, + { + "addr": "36LZKCBDUR5EHJ74Q6UWWNADLVJOHGCPBBQ5UTUM3ILRTQLA6RYYU4PUWQ", + "comment": "", + "state": { + "algo": 5000000000000, + "onl": 2 + } + }, + { + "addr": "JRHPFMSJLU42V75NTGFRQIALCK6RHTEK26QKLWCH2AEEAFNAVEXWDTA5AM", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "64VZVS2LFZXWA5W3S657W36LWGP34B7XLMDIF4ROXBTPADD7SR5WNUUYJE", + "comment": "", + "state": { + "algo": 171945701000000, + "onl": 2 + } + }, + { + "addr": "TXDBSEZPFP2UB6BDNFCHCZBTPONIIQVZGABM4UBRHVAAPR5NE24QBL6H2A", + "comment": "", + "state": { + "algo": 60000000000000, + "onl": 2 + } + }, + { + "addr": "XI5TYT4XPWUHE4AMDDZCCU6M4AP4CAI4VTCMXXUNS46I36O7IYBQ7SL3D4", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "Y6ZPKPXF2QHF6ULYQXVHM7NPI3L76SP6QHJHK7XTNPHNXDEUTJPRKUZBNE", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "6LY2PGUJLCK4Q75JU4IX5VWVJVU22VGJBWPZOFP3752UEBIUBQRNGJWIEA", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "L7AGFNAFJ6Z2FYCX3LXE4ZSERM2VOJF4KPF7OUCMGK6GWFXXDNHZJBEC2E", + "comment": "", + "state": { + "algo": 10000000000000, + "onl": 2 + } + }, + { + "addr": "RYXX5U2HMWGTPBG2UDLDT6OXDDRCK2YGL7LFAKYNBLRGZGYEJLRMGYLSVU", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "S263NYHFQWZYLINTBELLMIRMAJX6J5CUMHTECTGGVZUKUN2XY6ND2QBZVY", + "comment": "", + "state": { + "algo": 21647524000000, + "onl": 2 + } + }, + { + "addr": "AERTZIYYGK3Q364M6DXPKSRRNSQITWYEDGAHXC6QXFCF4GPSCCSISAGCBY", + "comment": "", + "state": { + "algo": 19306244000000, + "onl": 2 + } + }, + { + "addr": "34UYPXOJA6WRTWRNH5722LFDLWT23OM2ZZTCFQ62EHQI6MM3AJIAKOWDVQ", + "comment": "", + "state": { + "algo": 10000000000000, + "onl": 2 + } + }, + { + "addr": "EDVGNQL6APUFTIGFZHASIEWGJRZNWGIKJE64B72V36IQM2SJPOAG2MJNQE", + "comment": "", + "state": { + "algo": 20000000000000, + "onl": 2 + } + }, + { + "addr": "RKKLUIIGR75DFWGQOMJB5ZESPT7URDPC7QHGYKM4MAJ4OEL2J5WAQF6Z2Q", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "M4TNVJLDZZFAOH2M24BE7IU72KUX3P6M2D4JN4WZXW7WXH3C5QSHULJOU4", + "comment": "", + "state": { + "algo": 10000000000000, + "onl": 2 + } + }, + { + "addr": "WQL6MQS5SPK3CR3XUPYMGOUSCUC5PNW5YQPLGEXGKVRK3KFKSAZ6JK4HXQ", + "comment": "", + "state": { + "algo": 10000000000000, + "onl": 2 + } + }, + { + "addr": "36JTK4PKUBJGVCWKXZTAG6VLJRXWZXQVPQQSYODSN6WEGVHOWSVK6O54YU", + "comment": "", + "state": { + "algo": 10000000000000, + "onl": 2 + } + }, + { + "addr": "YFOAYI4SNXJR2DBEZ3O6FJOFSEQHWD7TYROCNDWF6VLBGLNJMRRHDXXZUI", + "comment": "", + "state": { + "algo": 30000000000000, + "onl": 2 + } + }, + { + "addr": "XASOPHD3KK3NNI5IF2I7S7U55RGF22SG6OEICVRMCTMMGHT3IBOJG7QWBU", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "H2AUGBLVQFHHFLFEPJ6GGJ7PBQITEN2GE6T7JZCALBKNU7Q52AVJM5HOYU", + "comment": "", + "state": { + "algo": 10000000000000, + "onl": 2 + } + }, + { + "addr": "GX3XLHSRMFTADVKJBBQBTZ6BKINW6ZO5JHXWGCWB4CPDNPDQ2PIYN4AVHQ", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "VBJBJ4VC3IHUTLVLWMBON36Y5MPAMPV4DNGW5FQ47GRLPT7JR5PQOUST2E", + "comment": "", + "state": { + "algo": 4524887000000, + "onl": 2 + } + }, + { + "addr": "7AQVTOMB5DJRSUM4LPLVF6PY3Y5EBDF4RZNDIWNW4Z63JYTAQCPQ62IZFE", + "comment": "", + "state": { + "algo": 50000000000000, + "onl": 2 + } + }, + { + "addr": "B4ZIHKD4VYLA4BAFEP7KUHZD7PNWXW4QLCHCNKWRENJ2LYVEOIYA3ZX6IA", + "comment": "", + "state": { + "algo": 40000000000000, + "onl": 2 + } + }, + { + "addr": "G5RGT3EENES7UVIQUHXMJ5APMOGSW6W6RBC534JC6U2TZA4JWC7U27RADE", + "comment": "", + "state": { + "algo": 10000000000000, + "onl": 2 + } + }, + { + "addr": "5AHJFDLAXVINK34IGSI3JA5OVRVMPCWLFEZ6TA4I7XUZ7I6M34Q56DUYIM", + "comment": "", + "state": { + "algo": 20000000000000, + "onl": 2 + } + } + ], + "fees": "Y76M3MSY6DKBRHBL7C3NNDXGS5IIMQVQVUAB6MP4XEMMGVF2QWNPL226CA", + "id": "v1.0", + "network": "mainnet", + "proto": "https://github.com/algorandfoundation/specs/tree/5615adc36bad610c7f165fa2967f4ecfa75125f0", + "rwd": "737777777777777777777777777777777777777777777777777UFEJ2CI", + "timestamp": 1560211200 +} \ No newline at end of file From 1869a3d69c761f2b6954a7b6abc15ca8205c080e Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Tue, 24 Jan 2023 11:53:56 -0500 Subject: [PATCH 02/16] docs: Update README.md (#460) * Update README.md * Update README.md Co-authored-by: Jason Paulos Co-authored-by: Jason Paulos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 17ff328c..7ffd6765 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The Algorand golang SDK provides: # Documentation -Full documentation is available [on godoc](https://godoc.org/github.com/algorand/go-algorand-sdk). You can also self-host the documentation by running `godoc -http=:8099` and visiting `http://localhost:8099/pkg/github.com/algorand/go-algorand-sdk` in your web browser. +Full documentation is available [on pkg.go.dev](https://pkg.go.dev/github.com/algorand/go-algorand-sdk/v2). You can also self-host the documentation by running `godoc -http=:8099` and visiting `http://localhost:8099/pkg/github.com/algorand/go-algorand-sdk` in your web browser. Additional developer documentation and examples can be found on [developer.algorand.org](https://developer.algorand.org/docs/sdks/go/) @@ -21,7 +21,7 @@ In `client/`, the `kmd` packages provide HTTP clients for the Key Management Dae In `client/v2` the `algod` package contains a client for the Algorand protocol daemon HTTP API. You can use it to check the status of the blockchain, read a block, look at transactions, or submit a signed transaction. In `client/v2` the `indexer` package contains a client for the Algorand Indexer API. You can use it to query historical transactions or make queries about the current state of the chain. -`future` package contains Transaction building utility functions. +`transaction` package contains Transaction building utility functions. `types` contains the data structures you'll use when interacting with the network, including addresses, transactions, multisig signatures, etc. From 9cb13b45e40a38add8caf2aa354298c27ed25a40 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Wed, 25 Jan 2023 11:48:57 -0500 Subject: [PATCH 03/16] Fix bad response error type. (#461) --- client/v2/common/common.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/v2/common/common.go b/client/v2/common/common.go index 0851848b..88357347 100644 --- a/client/v2/common/common.go +++ b/client/v2/common/common.go @@ -181,7 +181,7 @@ func (client *Client) submitForm(ctx context.Context, response interface{}, path // The caller wants a string if strResponse, ok := response.(*string); ok { *strResponse = string(bodyBytes) - return err + return responseErr } // Attempt to unmarshal a response regardless of whether or not there was an error. From c0cdbec79cbebc3c3c9517c3e1400fcaff995a1a Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Thu, 26 Jan 2023 14:59:02 -0500 Subject: [PATCH 04/16] Add disassembly test for Go SDK (#462) --- test/integration.tags | 1 + test/steps_test.go | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/test/integration.tags b/test/integration.tags index 91dace71..f8b49f9f 100644 --- a/test/integration.tags +++ b/test/integration.tags @@ -6,6 +6,7 @@ @auction @c2c @compile +@compile.disassemble @compile.sourcemap @dryrun @kmd diff --git a/test/steps_test.go b/test/steps_test.go index 7945182a..072943b1 100644 --- a/test/steps_test.go +++ b/test/steps_test.go @@ -10,7 +10,6 @@ import ( "encoding/json" "flag" "fmt" - "github.com/algorand/go-algorand-sdk/v2/transaction" "os" "path" "reflect" @@ -19,6 +18,8 @@ import ( "testing" "time" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "golang.org/x/crypto/ed25519" "github.com/algorand/go-algorand-sdk/v2/abi" @@ -352,6 +353,7 @@ func FeatureContext(s *godog.Suite) { s.Step(`^a base64 encoded program bytes for heuristic sanity check "([^"]*)"$`, takeB64encodedBytes) s.Step(`^I start heuristic sanity check over the bytes$`, heuristicCheckOverBytes) s.Step(`^if the heuristic sanity check throws an error, the error contains "([^"]*)"$`, checkErrorIfMatching) + s.Step(`^disassembly of "([^"]*)" matches "([^"]*)"$`, disassemblyMatches) s.BeforeScenario(func(interface{}) { stxObj = types.SignedTxn{} @@ -2559,3 +2561,23 @@ func checkErrorIfMatching(errMsg string) error { } return nil } + +func disassemblyMatches(bytecodeFilename, sourceFilename string) error { + disassembledBytes, err := loadResource(bytecodeFilename) + if err != nil { + return err + } + actualResult, err := aclv2.TealDisassemble(disassembledBytes).Do(context.Background()) + if err != nil { + return err + } + expectedBytes, err := loadResource(sourceFilename) + if err != nil { + return err + } + expectedResult := string(expectedBytes) + if actualResult.Result != expectedResult { + return fmt.Errorf("Actual program does not match expected: %s != %s", actualResult.Result, expectedResult) + } + return nil +} From 8deeaedcc7f05c125cd2fb6eb65378e748739d0b Mon Sep 17 00:00:00 2001 From: Will Winder Date: Wed, 1 Feb 2023 10:30:52 -0500 Subject: [PATCH 05/16] marshaling: Lenient Address and BlockHash go-codec unmarshalers. (#464) --- types/address.go | 29 +++++++++++++++++++ types/address_test.go | 56 +++++++++++++++++++++++++++++++++++++ types/basics.go | 17 +++++++++++ types/blockhash.go | 40 ++++++++++++++++++++++++++ types/blockhash_test.go | 62 +++++++++++++++++++++++++++++++++++++++++ types/errors.go | 1 + 6 files changed, 205 insertions(+) create mode 100644 types/blockhash.go create mode 100644 types/blockhash_test.go diff --git a/types/address.go b/types/address.go index 7f26b290..1ac5a88c 100644 --- a/types/address.go +++ b/types/address.go @@ -4,6 +4,7 @@ import ( "bytes" "crypto/sha512" "encoding/base32" + "encoding/base64" ) const ( @@ -34,6 +35,34 @@ func (a Address) IsZero() bool { return a == ZeroAddress } +// MarshalText returns the address string as an array of bytes +func (addr *Address) MarshalText() ([]byte, error) { + result := base64.StdEncoding.EncodeToString(addr[:]) + return []byte(result), nil +} + +// UnmarshalText initializes the Address from an array of bytes. +// The bytes may be in the base32 checksum format, or the raw bytes base64 encoded. +func (addr *Address) UnmarshalText(text []byte) error { + address, err := DecodeAddress(string(text)) + if err == nil { + *addr = address + return nil + } + // ignore the DecodeAddress error because it isn't the native MarshalText format. + + // Check if its b64 encoded + data, err := base64.StdEncoding.DecodeString(string(text)) + if err == nil { + if len(data) != len(addr[:]) { + return errWrongAddressLen + } + copy(addr[:], data[:]) + return nil + } + return err +} + // DecodeAddress turns a checksum address string into an Address object. It // checks that the checksum is correct, and returns an error if it's not. func DecodeAddress(addr string) (a Address, err error) { diff --git a/types/address_test.go b/types/address_test.go index cbc6f17f..a03e71b6 100644 --- a/types/address_test.go +++ b/types/address_test.go @@ -2,9 +2,12 @@ package types import ( "crypto/rand" + "fmt" "testing" "github.com/stretchr/testify/require" + + json2 "github.com/algorand/go-algorand-sdk/v2/encoding/json" ) func randomBytes(s []byte) { @@ -37,3 +40,56 @@ func TestGoldenValues(t *testing.T) { } require.Equal(t, golden, a.String()) } + +func TestUnmarshalAddress(t *testing.T) { + testcases := []struct { + name string + input string + str string + output string + err string + }{ + { + name: "B32+Checksum", + input: "7HJBGRIWI7GDL42SOJNIAZ7LJ7EBEGKGE5S52QZXAWDXOHDKMDFR6AUXDE", + str: "7HJBGRIWI7GDL42SOJNIAZ7LJ7EBEGKGE5S52QZXAWDXOHDKMDFR6AUXDE", + output: "+dITRRZHzDXzUnJagGfrT8gSGUYnZd1DNwWHdxxqYMs=", + }, { + name: "B64", + input: "+dITRRZHzDXzUnJagGfrT8gSGUYnZd1DNwWHdxxqYMs=", + str: "7HJBGRIWI7GDL42SOJNIAZ7LJ7EBEGKGE5S52QZXAWDXOHDKMDFR6AUXDE", + output: "+dITRRZHzDXzUnJagGfrT8gSGUYnZd1DNwWHdxxqYMs=", + }, { + name: "B64-err-length", + input: "AAE=", + err: "decoded address is the wrong length", + }, { + name: "B64-err-illegal", + input: "bogus", + err: "illegal base64 data at input byte 4", + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + // wrap in quotes so that it is valid JSON + data := []byte(fmt.Sprintf("\"%s\"", tc.input)) + + var addr Address + err := json2.Decode(data, &addr) + if tc.err != "" { + require.Error(t, err) + require.ErrorContains(t, err, tc.err) + return + } + require.NoError(t, err) + require.Equal(t, tc.str, addr.String()) + actual, err := addr.MarshalText() + require.NoError(t, err) + require.Equal(t, tc.output, string(actual)) + fmt.Println(string(actual)) + }) + } +} diff --git a/types/basics.go b/types/basics.go index 37ff4672..33c40eb1 100644 --- a/types/basics.go +++ b/types/basics.go @@ -1,7 +1,10 @@ package types import ( + "encoding/base32" "encoding/base64" + "errors" + "fmt" "math" "github.com/algorand/go-algorand-sdk/v2/encoding/msgpack" @@ -99,3 +102,17 @@ func (block *Block) FromBase64String(b64string string) error { } return nil } + +// DigestFromString converts a string to a Digest +func DigestFromString(str string) (d Digest, err error) { + decoded, err := base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(str) + if err != nil { + return d, err + } + if len(decoded) != len(d) { + msg := fmt.Sprintf(`Attempted to decode a string which was not a Digest: "%v"`, str) + return d, errors.New(msg) + } + copy(d[:], decoded[:]) + return d, err +} diff --git a/types/blockhash.go b/types/blockhash.go new file mode 100644 index 00000000..a38cd259 --- /dev/null +++ b/types/blockhash.go @@ -0,0 +1,40 @@ +package types + +import ( + "encoding/base64" + "strings" +) + +// MarshalText returns the BlockHash string as an array of bytes +func (b *BlockHash) MarshalText() ([]byte, error) { + result := base64.StdEncoding.EncodeToString(b[:]) + return []byte(result), nil +} + +// UnmarshalText initializes the BlockHash from an array of bytes. +func (b *BlockHash) UnmarshalText(text []byte) error { + // Remove the blk- prefix if it is present to allow decoding either format. + if strings.HasPrefix(string(text), "blk-") { + text = text[4:] + } + + // Attempt to decode base32 format + d, err := DigestFromString(string(text)) + if err == nil { + *b = BlockHash(d) + return nil + } + // ignore the DigestFromString error because it isn't the native MarshalText format. + + // Attempt to decode base64 format + var data BlockHash + n, err := base64.StdEncoding.Decode(data[:], text) + if err == nil { + if n != len(b[:]) { + return errWrongBlockHashLen + } + copy(b[:], data[:]) + return nil + } + return err +} diff --git a/types/blockhash_test.go b/types/blockhash_test.go new file mode 100644 index 00000000..47a2c6b0 --- /dev/null +++ b/types/blockhash_test.go @@ -0,0 +1,62 @@ +package types + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + json2 "github.com/algorand/go-algorand-sdk/v2/encoding/json" +) + +func TestUnmarshalBlockHash(t *testing.T) { + testcases := []struct { + name string + input string + outputB64 string + err string + }{ + { + name: "blk-B32", + input: "blk-PEMLJXJYPLIFJ7DGVGTSEGUHUFR3M6Y67UZW3AC4LLNLF26XIXQA", + outputB64: "eRi03Th60FT8ZqmnIhqHoWO2ex79M22AXFrasuvXReA=", + }, { + name: "B32", + input: "PEMLJXJYPLIFJ7DGVGTSEGUHUFR3M6Y67UZW3AC4LLNLF26XIXQA", + outputB64: "eRi03Th60FT8ZqmnIhqHoWO2ex79M22AXFrasuvXReA=", + }, { + name: "B64", + input: "eRi03Th60FT8ZqmnIhqHoWO2ex79M22AXFrasuvXReA=", + outputB64: "eRi03Th60FT8ZqmnIhqHoWO2ex79M22AXFrasuvXReA=", + }, { + name: "B64-err-length", + input: "AAE=", + err: "decoded block hash is the wrong length", + }, { + name: "B64-err-illegal", + input: "bogus", + err: "illegal base64 data at input byte 4", + }, + } + + for _, tc := range testcases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + // wrap in quotes so that it is valid JSON + data := []byte(fmt.Sprintf("\"%s\"", tc.input)) + + var hash BlockHash + err := json2.Decode(data, &hash) + if tc.err != "" { + require.Error(t, err) + require.ErrorContains(t, err, tc.err) + return + } + require.NoError(t, err) + actual, err := hash.MarshalText() + require.NoError(t, err) + require.Equal(t, tc.outputB64, string(actual)) + }) + } +} diff --git a/types/errors.go b/types/errors.go index 551d5854..7a345d0f 100644 --- a/types/errors.go +++ b/types/errors.go @@ -7,3 +7,4 @@ import ( var errWrongAddressByteLen = fmt.Errorf("encoding address is the wrong length, should be %d bytes", hashLenBytes) var errWrongAddressLen = fmt.Errorf("decoded address is the wrong length, should be %d bytes", hashLenBytes+checksumLenBytes) var errWrongChecksum = fmt.Errorf("address checksum is incorrect, did you copy the address correctly?") +var errWrongBlockHashLen = fmt.Errorf("decoded block hash is the wrong length, should be %d bytes", Sha512_256Size) From 073239f9dca8d46c82edbcd2a3726f0fd259a7da Mon Sep 17 00:00:00 2001 From: Will Winder Date: Wed, 1 Feb 2023 10:51:49 -0500 Subject: [PATCH 06/16] Remove debug output. (#465) --- types/address_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/types/address_test.go b/types/address_test.go index a03e71b6..dee6c278 100644 --- a/types/address_test.go +++ b/types/address_test.go @@ -89,7 +89,6 @@ func TestUnmarshalAddress(t *testing.T) { actual, err := addr.MarshalText() require.NoError(t, err) require.Equal(t, tc.output, string(actual)) - fmt.Println(string(actual)) }) } } From 5b8c99b1412c4cfbba614b7be7b07ea326aacf16 Mon Sep 17 00:00:00 2001 From: Will Winder Date: Tue, 28 Feb 2023 15:18:05 -0500 Subject: [PATCH 07/16] API: Add types for ledgercore.StateDelta. (#467) --- .../v2/algod/accountApplicationInformation.go | 3 +- client/v2/algod/accountAssetInformation.go | 3 +- client/v2/algod/accountInformation.go | 3 +- client/v2/algod/algod.go | 4 - client/v2/algod/getBlock.go | 3 +- client/v2/algod/getLedgerStateDelta.go | 22 -- client/v2/algod/getPendingTransactions.go | 3 +- .../algod/getPendingTransactionsByAddress.go | 3 +- client/v2/algod/getTransactionProof.go | 3 +- .../v2/algod/pendingTransactionInformation.go | 3 +- client/v2/algod/rawTransaction.go | 3 +- .../common/models/account_balance_record.go | 10 - client/v2/common/models/account_deltas.go | 13 - client/v2/common/models/account_totals.go | 16 - .../v2/common/models/app_resource_record.go | 22 -- .../v2/common/models/asset_resource_record.go | 22 -- client/v2/common/models/ledger_state_delta.go | 28 -- client/v2/common/models/modified_app.go | 13 - client/v2/common/models/modified_asset.go | 13 - .../v2/common/models/node_status_response.go | 24 ++ client/v2/common/models/tx_lease.go | 13 - client/v2/indexer/indexer.go | 5 + ....go => lookupApplicationBoxByIDAndName.go} | 0 encoding/json/json.go | 22 ++ encoding/json/json_test.go | 34 +- test/algodclientv2_test.go | 10 - test/responses_unit_test.go | 5 +- test/unit.tags | 3 - test/utilities.go | 64 +++- types/blockhash.go | 6 +- types/blockhash_test.go | 4 + types/statedelta.go | 327 ++++++++++++++++++ 32 files changed, 489 insertions(+), 218 deletions(-) delete mode 100644 client/v2/algod/getLedgerStateDelta.go delete mode 100644 client/v2/common/models/account_balance_record.go delete mode 100644 client/v2/common/models/account_deltas.go delete mode 100644 client/v2/common/models/account_totals.go delete mode 100644 client/v2/common/models/app_resource_record.go delete mode 100644 client/v2/common/models/asset_resource_record.go delete mode 100644 client/v2/common/models/ledger_state_delta.go delete mode 100644 client/v2/common/models/modified_app.go delete mode 100644 client/v2/common/models/modified_asset.go delete mode 100644 client/v2/common/models/tx_lease.go rename client/v2/indexer/{lookupApplicationBoxByIDandName.go => lookupApplicationBoxByIDAndName.go} (100%) create mode 100644 types/statedelta.go diff --git a/client/v2/algod/accountApplicationInformation.go b/client/v2/algod/accountApplicationInformation.go index 6988ee50..a6f93d5e 100644 --- a/client/v2/algod/accountApplicationInformation.go +++ b/client/v2/algod/accountApplicationInformation.go @@ -11,7 +11,8 @@ import ( // AccountApplicationInformationParams contains all of the query parameters for url serialization. type AccountApplicationInformationParams struct { - // Format configures whether the response object is JSON or MessagePack encoded. + // Format configures whether the response object is JSON or MessagePack encoded. If + // not provided, defaults to JSON. Format string `url:"format,omitempty"` } diff --git a/client/v2/algod/accountAssetInformation.go b/client/v2/algod/accountAssetInformation.go index 997c978b..2a28d78c 100644 --- a/client/v2/algod/accountAssetInformation.go +++ b/client/v2/algod/accountAssetInformation.go @@ -11,7 +11,8 @@ import ( // AccountAssetInformationParams contains all of the query parameters for url serialization. type AccountAssetInformationParams struct { - // Format configures whether the response object is JSON or MessagePack encoded. + // Format configures whether the response object is JSON or MessagePack encoded. If + // not provided, defaults to JSON. Format string `url:"format,omitempty"` } diff --git a/client/v2/algod/accountInformation.go b/client/v2/algod/accountInformation.go index f601bb76..4096cded 100644 --- a/client/v2/algod/accountInformation.go +++ b/client/v2/algod/accountInformation.go @@ -16,7 +16,8 @@ type AccountInformationParams struct { // `none`. Exclude string `url:"exclude,omitempty"` - // Format configures whether the response object is JSON or MessagePack encoded. + // Format configures whether the response object is JSON or MessagePack encoded. If + // not provided, defaults to JSON. Format string `url:"format,omitempty"` } diff --git a/client/v2/algod/algod.go b/client/v2/algod/algod.go index 73cb5f30..fe03e39e 100644 --- a/client/v2/algod/algod.go +++ b/client/v2/algod/algod.go @@ -121,10 +121,6 @@ func (c *Client) PendingTransactionInformation(txid string) *PendingTransactionI return &PendingTransactionInformation{c: c, txid: txid} } -func (c *Client) GetLedgerStateDelta(round uint64) *GetLedgerStateDelta { - return &GetLedgerStateDelta{c: c, round: round} -} - func (c *Client) GetStateProof(round uint64) *GetStateProof { return &GetStateProof{c: c, round: round} } diff --git a/client/v2/algod/getBlock.go b/client/v2/algod/getBlock.go index 9ae92460..40f0f1f5 100644 --- a/client/v2/algod/getBlock.go +++ b/client/v2/algod/getBlock.go @@ -12,7 +12,8 @@ import ( // BlockParams contains all of the query parameters for url serialization. type BlockParams struct { - // Format configures whether the response object is JSON or MessagePack encoded. + // Format configures whether the response object is JSON or MessagePack encoded. If + // not provided, defaults to JSON. Format string `url:"format,omitempty"` } diff --git a/client/v2/algod/getLedgerStateDelta.go b/client/v2/algod/getLedgerStateDelta.go deleted file mode 100644 index f10040d6..00000000 --- a/client/v2/algod/getLedgerStateDelta.go +++ /dev/null @@ -1,22 +0,0 @@ -package algod - -import ( - "context" - "fmt" - - "github.com/algorand/go-algorand-sdk/v2/client/v2/common" - "github.com/algorand/go-algorand-sdk/v2/client/v2/common/models" -) - -// GetLedgerStateDelta get ledger deltas for a round. -type GetLedgerStateDelta struct { - c *Client - - round uint64 -} - -// Do performs the HTTP request -func (s *GetLedgerStateDelta) Do(ctx context.Context, headers ...*common.Header) (response models.LedgerStateDelta, err error) { - err = s.c.get(ctx, &response, fmt.Sprintf("/v2/deltas/%s", common.EscapeParams(s.round)...), nil, headers) - return -} diff --git a/client/v2/algod/getPendingTransactions.go b/client/v2/algod/getPendingTransactions.go index 12fb19e8..9b0e67b9 100644 --- a/client/v2/algod/getPendingTransactions.go +++ b/client/v2/algod/getPendingTransactions.go @@ -11,7 +11,8 @@ import ( // PendingTransactionsParams contains all of the query parameters for url serialization. type PendingTransactionsParams struct { - // Format configures whether the response object is JSON or MessagePack encoded. + // Format configures whether the response object is JSON or MessagePack encoded. If + // not provided, defaults to JSON. Format string `url:"format,omitempty"` // Max truncated number of transactions to display. If max=0, returns all pending diff --git a/client/v2/algod/getPendingTransactionsByAddress.go b/client/v2/algod/getPendingTransactionsByAddress.go index 8f2f80e2..fd714323 100644 --- a/client/v2/algod/getPendingTransactionsByAddress.go +++ b/client/v2/algod/getPendingTransactionsByAddress.go @@ -12,7 +12,8 @@ import ( // PendingTransactionsByAddressParams contains all of the query parameters for url serialization. type PendingTransactionsByAddressParams struct { - // Format configures whether the response object is JSON or MessagePack encoded. + // Format configures whether the response object is JSON or MessagePack encoded. If + // not provided, defaults to JSON. Format string `url:"format,omitempty"` // Max truncated number of transactions to display. If max=0, returns all pending diff --git a/client/v2/algod/getTransactionProof.go b/client/v2/algod/getTransactionProof.go index d3a5fad5..db65720e 100644 --- a/client/v2/algod/getTransactionProof.go +++ b/client/v2/algod/getTransactionProof.go @@ -11,7 +11,8 @@ import ( // GetTransactionProofParams contains all of the query parameters for url serialization. type GetTransactionProofParams struct { - // Format configures whether the response object is JSON or MessagePack encoded. + // Format configures whether the response object is JSON or MessagePack encoded. If + // not provided, defaults to JSON. Format string `url:"format,omitempty"` // Hashtype the type of hash function used to create the proof, must be one of: diff --git a/client/v2/algod/pendingTransactionInformation.go b/client/v2/algod/pendingTransactionInformation.go index c6212e45..671a4474 100644 --- a/client/v2/algod/pendingTransactionInformation.go +++ b/client/v2/algod/pendingTransactionInformation.go @@ -12,7 +12,8 @@ import ( // PendingTransactionInformationParams contains all of the query parameters for url serialization. type PendingTransactionInformationParams struct { - // Format configures whether the response object is JSON or MessagePack encoded. + // Format configures whether the response object is JSON or MessagePack encoded. If + // not provided, defaults to JSON. Format string `url:"format,omitempty"` } diff --git a/client/v2/algod/rawTransaction.go b/client/v2/algod/rawTransaction.go index 1c8ed7a4..bbdc3435 100644 --- a/client/v2/algod/rawTransaction.go +++ b/client/v2/algod/rawTransaction.go @@ -8,7 +8,8 @@ import ( "github.com/algorand/go-algorand-sdk/v2/client/v2/common/models" ) -// SendRawTransaction broadcasts a raw transaction to the network. +// SendRawTransaction broadcasts a raw transaction or transaction group to the +// network. type SendRawTransaction struct { c *Client diff --git a/client/v2/common/models/account_balance_record.go b/client/v2/common/models/account_balance_record.go deleted file mode 100644 index dffe48bf..00000000 --- a/client/v2/common/models/account_balance_record.go +++ /dev/null @@ -1,10 +0,0 @@ -package models - -// AccountBalanceRecord account and its address -type AccountBalanceRecord struct { - // AccountData updated account data. - AccountData Account `json:"account-data"` - - // Address address of the updated account. - Address string `json:"address"` -} diff --git a/client/v2/common/models/account_deltas.go b/client/v2/common/models/account_deltas.go deleted file mode 100644 index d23a4dcc..00000000 --- a/client/v2/common/models/account_deltas.go +++ /dev/null @@ -1,13 +0,0 @@ -package models - -// AccountDeltas exposes deltas for account based resources in a single round -type AccountDeltas struct { - // Accounts array of Account updates for the round - Accounts []AccountBalanceRecord `json:"accounts,omitempty"` - - // Apps array of App updates for the round. - Apps []AppResourceRecord `json:"apps,omitempty"` - - // Assets array of Asset updates for the round. - Assets []AssetResourceRecord `json:"assets,omitempty"` -} diff --git a/client/v2/common/models/account_totals.go b/client/v2/common/models/account_totals.go deleted file mode 100644 index ab0afd74..00000000 --- a/client/v2/common/models/account_totals.go +++ /dev/null @@ -1,16 +0,0 @@ -package models - -// AccountTotals total Algos in the system grouped by account status -type AccountTotals struct { - // NotParticipating amount of stake in non-participating accounts - NotParticipating uint64 `json:"not-participating"` - - // Offline amount of stake in offline accounts - Offline uint64 `json:"offline"` - - // Online amount of stake in online accounts - Online uint64 `json:"online"` - - // RewardsLevel total number of algos received per reward unit since genesis - RewardsLevel uint64 `json:"rewards-level"` -} diff --git a/client/v2/common/models/app_resource_record.go b/client/v2/common/models/app_resource_record.go deleted file mode 100644 index f1212b33..00000000 --- a/client/v2/common/models/app_resource_record.go +++ /dev/null @@ -1,22 +0,0 @@ -package models - -// AppResourceRecord represents AppParams and AppLocalStateDelta in deltas -type AppResourceRecord struct { - // Address app account address - Address string `json:"address"` - - // AppDeleted whether the app was deleted - AppDeleted bool `json:"app-deleted"` - - // AppIndex app index - AppIndex uint64 `json:"app-index"` - - // AppLocalState app local state - AppLocalState ApplicationLocalState `json:"app-local-state,omitempty"` - - // AppLocalStateDeleted whether the app local state was deleted - AppLocalStateDeleted bool `json:"app-local-state-deleted"` - - // AppParams app params - AppParams ApplicationParams `json:"app-params,omitempty"` -} diff --git a/client/v2/common/models/asset_resource_record.go b/client/v2/common/models/asset_resource_record.go deleted file mode 100644 index fcf008e2..00000000 --- a/client/v2/common/models/asset_resource_record.go +++ /dev/null @@ -1,22 +0,0 @@ -package models - -// AssetResourceRecord represents AssetParams and AssetHolding in deltas -type AssetResourceRecord struct { - // Address account address of the asset - Address string `json:"address"` - - // AssetDeleted whether the asset was deleted - AssetDeleted bool `json:"asset-deleted"` - - // AssetHolding the asset holding - AssetHolding AssetHolding `json:"asset-holding,omitempty"` - - // AssetHoldingDeleted whether the asset holding was deleted - AssetHoldingDeleted bool `json:"asset-holding-deleted"` - - // AssetIndex index of the asset - AssetIndex uint64 `json:"asset-index"` - - // AssetParams asset params - AssetParams AssetParams `json:"asset-params,omitempty"` -} diff --git a/client/v2/common/models/ledger_state_delta.go b/client/v2/common/models/ledger_state_delta.go deleted file mode 100644 index d63dc849..00000000 --- a/client/v2/common/models/ledger_state_delta.go +++ /dev/null @@ -1,28 +0,0 @@ -package models - -// LedgerStateDelta contains ledger updates. -type LedgerStateDelta struct { - // Accts accountDeltas object - Accts AccountDeltas `json:"accts,omitempty"` - - // KvMods array of KV Deltas - KvMods []KvDelta `json:"kv-mods,omitempty"` - - // ModifiedApps list of modified Apps - ModifiedApps []ModifiedApp `json:"modified-apps,omitempty"` - - // ModifiedAssets list of modified Assets - ModifiedAssets []ModifiedAsset `json:"modified-assets,omitempty"` - - // PrevTimestamp previous block timestamp - PrevTimestamp uint64 `json:"prev-timestamp,omitempty"` - - // StateProofNext next round for which we expect a state proof - StateProofNext uint64 `json:"state-proof-next,omitempty"` - - // Totals account Totals - Totals AccountTotals `json:"totals,omitempty"` - - // TxLeases list of transaction leases - TxLeases []TxLease `json:"tx-leases,omitempty"` -} diff --git a/client/v2/common/models/modified_app.go b/client/v2/common/models/modified_app.go deleted file mode 100644 index bc58ff62..00000000 --- a/client/v2/common/models/modified_app.go +++ /dev/null @@ -1,13 +0,0 @@ -package models - -// ModifiedApp app which was created or deleted. -type ModifiedApp struct { - // Created created if true, deleted if false - Created bool `json:"created"` - - // Creator address of the creator. - Creator string `json:"creator"` - - // Id app Id - Id uint64 `json:"id"` -} diff --git a/client/v2/common/models/modified_asset.go b/client/v2/common/models/modified_asset.go deleted file mode 100644 index 684c848e..00000000 --- a/client/v2/common/models/modified_asset.go +++ /dev/null @@ -1,13 +0,0 @@ -package models - -// ModifiedAsset asset which was created or deleted. -type ModifiedAsset struct { - // Created created if true, deleted if false - Created bool `json:"created"` - - // Creator address of the creator. - Creator string `json:"creator"` - - // Id asset Id - Id uint64 `json:"id"` -} diff --git a/client/v2/common/models/node_status_response.go b/client/v2/common/models/node_status_response.go index 8f7f2b6d..00a820f3 100644 --- a/client/v2/common/models/node_status_response.go +++ b/client/v2/common/models/node_status_response.go @@ -66,4 +66,28 @@ type NodeStatusResponse struct { // TimeSinceLastRound timeSinceLastRound in nanoseconds TimeSinceLastRound uint64 `json:"time-since-last-round"` + + // UpgradeDelay upgrade delay + UpgradeDelay uint64 `json:"upgrade-delay,omitempty"` + + // UpgradeNextProtocolVoteBefore next protocol round + UpgradeNextProtocolVoteBefore uint64 `json:"upgrade-next-protocol-vote-before,omitempty"` + + // UpgradeNoVotes no votes cast for consensus upgrade + UpgradeNoVotes uint64 `json:"upgrade-no-votes,omitempty"` + + // UpgradeNodeVote this node's upgrade vote + UpgradeNodeVote bool `json:"upgrade-node-vote,omitempty"` + + // UpgradeVoteRounds total voting ounds for current upgrade + UpgradeVoteRounds uint64 `json:"upgrade-vote-rounds,omitempty"` + + // UpgradeVotes total votes cast for consensus upgrade + UpgradeVotes uint64 `json:"upgrade-votes,omitempty"` + + // UpgradeVotesRequired yes votes required for consensus upgrade + UpgradeVotesRequired uint64 `json:"upgrade-votes-required,omitempty"` + + // UpgradeYesVotes yes votes cast for consensus upgrade + UpgradeYesVotes uint64 `json:"upgrade-yes-votes,omitempty"` } diff --git a/client/v2/common/models/tx_lease.go b/client/v2/common/models/tx_lease.go deleted file mode 100644 index cdc25755..00000000 --- a/client/v2/common/models/tx_lease.go +++ /dev/null @@ -1,13 +0,0 @@ -package models - -// TxLease -type TxLease struct { - // Expiration round that the lease expires - Expiration uint64 `json:"expiration"` - - // Lease lease data - Lease []byte `json:"lease"` - - // Sender address of the lease sender - Sender string `json:"sender"` -} diff --git a/client/v2/indexer/indexer.go b/client/v2/indexer/indexer.go index a3d9f9fa..0ead745f 100644 --- a/client/v2/indexer/indexer.go +++ b/client/v2/indexer/indexer.go @@ -10,6 +10,11 @@ const authHeader = "X-Indexer-API-Token" type Client common.Client +// delete performs a DELETE request to the specific path against the server, assumes JSON response +func (c *Client) delete(ctx context.Context, response interface{}, path string, body interface{}, headers []*common.Header) error { + return (*common.Client)(c).Delete(ctx, response, path, body, headers) +} + // get performs a GET request to the specific path against the server, assumes JSON response func (c *Client) get(ctx context.Context, response interface{}, path string, body interface{}, headers []*common.Header) error { return (*common.Client)(c).Get(ctx, response, path, body, headers) diff --git a/client/v2/indexer/lookupApplicationBoxByIDandName.go b/client/v2/indexer/lookupApplicationBoxByIDAndName.go similarity index 100% rename from client/v2/indexer/lookupApplicationBoxByIDandName.go rename to client/v2/indexer/lookupApplicationBoxByIDAndName.go diff --git a/encoding/json/json.go b/encoding/json/json.go index a150694e..3ec6fe5c 100644 --- a/encoding/json/json.go +++ b/encoding/json/json.go @@ -13,6 +13,10 @@ var CodecHandle *codec.JsonHandle // LenientCodecHandle is used to instantiate msgpack encoders for the REST API. var LenientCodecHandle *codec.JsonHandle +// JSONStrictHandle is the same as CodecHandle but with MapKeyAsString=true +// for correct maps[int]interface{} encoding +var JSONStrictHandle *codec.JsonHandle + // init configures our json encoder and decoder func init() { CodecHandle = new(codec.JsonHandle) @@ -30,6 +34,15 @@ func init() { LenientCodecHandle.RecursiveEmptyCheck = true LenientCodecHandle.Indent = 2 LenientCodecHandle.HTMLCharsAsIs = true + + JSONStrictHandle = new(codec.JsonHandle) + JSONStrictHandle.ErrorIfNoField = CodecHandle.ErrorIfNoField + JSONStrictHandle.ErrorIfNoArrayExpand = CodecHandle.ErrorIfNoArrayExpand + JSONStrictHandle.Canonical = CodecHandle.Canonical + JSONStrictHandle.RecursiveEmptyCheck = CodecHandle.RecursiveEmptyCheck + JSONStrictHandle.Indent = CodecHandle.Indent + JSONStrictHandle.HTMLCharsAsIs = CodecHandle.HTMLCharsAsIs + JSONStrictHandle.MapKeyAsString = true } // Encode returns a json-encoded byte buffer for a given object @@ -40,6 +53,15 @@ func Encode(obj interface{}) []byte { return b } +// EncodeStrict returns a JSON-encoded byte buffer for a given object +// It is the same Encode but encodes map's int keys as strings +func EncodeStrict(obj interface{}) []byte { + var b []byte + enc := codec.NewEncoderBytes(&b, JSONStrictHandle) + enc.MustEncode(obj) + return b +} + // Decode attempts to decode a json-encoded byte buffer into an // object instance pointed to by objptr func Decode(b []byte, objptr interface{}) error { diff --git a/encoding/json/json_test.go b/encoding/json/json_test.go index 8a677716..750408d4 100644 --- a/encoding/json/json_test.go +++ b/encoding/json/json_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) type object struct { @@ -27,7 +28,7 @@ func TestDecode(t *testing.T) { // basic encode/decode test. var decoded object err := Decode(encodedOb, &decoded) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, obj, decoded) }) @@ -36,7 +37,7 @@ func TestDecode(t *testing.T) { decoder := NewDecoder(bytes.NewReader(encodedOb)) var decoded object err := decoder.Decode(&decoded) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, obj, decoded) }) @@ -45,7 +46,7 @@ func TestDecode(t *testing.T) { decoder := NewDecoder(bytes.NewReader(encodedOb)) var decoded subsetObject err := decoder.Decode(&decoded) - assert.Error(t, err) + require.Error(t, err) assert.Contains(t, err.Error(), "no matching struct field found when decoding stream map with key name") }) @@ -54,7 +55,32 @@ func TestDecode(t *testing.T) { decoder := NewLenientDecoder(bytes.NewReader(encodedOb)) var decoded subsetObject err := decoder.Decode(&decoded) - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, obj.subsetObject, decoded) }) + + t.Run("original encode map key as string", func(t *testing.T) { + intMap := map[int]string{ + 0: "int key", + } + data := string(Encode(intMap)) + assert.NotContains(t, data, "\"0\":") + }) + + t.Run("strict encode map key as string", func(t *testing.T) { + intMap := map[int]string{ + 0: "int key", + } + data := string(EncodeStrict(intMap)) + assert.NotContains(t, data, "0:") + }) + + t.Run("strict encode map interface key as string", func(t *testing.T) { + t.Skip("There is a bug in go-codec with MapKeyAsString = true and Canonical = true") + intMap := map[interface{}]interface{}{ + 0: "int key", + } + data := string(EncodeStrict(intMap)) + assert.NotContains(t, data, "0:") + }) } diff --git a/test/algodclientv2_test.go b/test/algodclientv2_test.go index 030f2ce5..350453de 100644 --- a/test/algodclientv2_test.go +++ b/test/algodclientv2_test.go @@ -55,7 +55,6 @@ func AlgodClientV2Context(s *godog.Suite) { s.Step(`^we make a GetStateProof call for round (\d+)$`, weMakeAGetStateProofCallForRound) s.Step(`^we make a GetTransactionProof call for round (\d+) txid "([^"]*)" and hashtype "([^"]*)"$`, weMakeAGetTransactionProofCallForRoundTxidAndHashtype) s.Step(`^we make a Lookup Block Hash call against round (\d+)$`, weMakeALookupBlockHashCallAgainstRound) - s.Step(`^we make a GetLedgerStateDelta call against round (\d+)$`, weMakeAGetLedgerStateDeltaCallAgainstRound) s.Step(`^we make a SetSyncRound call against round (\d+)$`, weMakeASetSyncRoundCallAgainstRound) s.Step(`^we make a GetSyncRound call$`, weMakeAGetSyncRoundCall) s.Step(`^we make a UnsetSyncRound call$`, weMakeAUnsetSyncRoundCall) @@ -310,15 +309,6 @@ func weMakeALookupBlockHashCallAgainstRound(round int) error { return nil } -func weMakeAGetLedgerStateDeltaCallAgainstRound(round int) error { - algodClient, err := algod.MakeClient(mockServer.URL, "") - if err != nil { - return err - } - algodClient.GetLedgerStateDelta(uint64(round)).Do(context.Background()) - return nil -} - func weMakeASetSyncRoundCallAgainstRound(round int) error { algodClient, err := algod.MakeClient(mockServer.URL, "") if err != nil { diff --git a/test/responses_unit_test.go b/test/responses_unit_test.go index c98c234b..f1b70d4a 100644 --- a/test/responses_unit_test.go +++ b/test/responses_unit_test.go @@ -172,9 +172,6 @@ func weMakeAnyCallTo(client /* algod/indexer */, endpoint string) (err error) { case "GetBlockHash": response, err = algodC.GetBlockHash(123).Do(context.Background()) - case "GetLedgerStateDelta": - response, err = - algodC.GetLedgerStateDelta(123).Do(context.Background()) case "UnsetSyncRound": response, err = algodC.UnsetSyncRound().Do(context.Background()) @@ -216,7 +213,7 @@ func theParsedResponseShouldEqualTheMockResponse() error { if responseStr, ok := response.(string); ok { responseJson = responseStr } else { - responseJson = string(json.Encode(response)) + responseJson = string(json.EncodeStrict(response)) } } diff --git a/test/unit.tags b/test/unit.tags index cb55c5bf..6d0ef444 100644 --- a/test/unit.tags +++ b/test/unit.tags @@ -24,12 +24,9 @@ @unit.responses.messagepack.231 @unit.responses.participationupdates @unit.responses.unlimited_assets -@unit.responses.statedelta @unit.sourcemap @unit.stateproof.paths @unit.stateproof.responses -@unit.stateproof.responses.msgp -@unit.statedelta @unit.tealsign @unit.transactions @unit.transactions.keyreg diff --git a/test/utilities.go b/test/utilities.go index 6f64822c..26fda1b9 100644 --- a/test/utilities.go +++ b/test/utilities.go @@ -15,6 +15,7 @@ import ( sdk_json "github.com/algorand/go-algorand-sdk/v2/encoding/json" "github.com/algorand/go-algorand-sdk/v2/encoding/msgpack" + "github.com/algorand/go-algorand-sdk/v2/types" ) func VerifyResponse(expectedFile string, actual string) error { @@ -43,7 +44,7 @@ func VerifyResponse(expectedFile string, actual string) error { if err != nil { return fmt.Errorf("failed to decode '%s' from message pack: %v", expectedFile, err) } - expectedString = string(sdk_json.Encode(generic)) + expectedString = string(sdk_json.EncodeStrict(generic)) } err = EqualJson2(expectedString, actual) @@ -60,10 +61,16 @@ func VerifyResponse(expectedFile string, actual string) error { // For reference: j1 is the baseline, j2 is the test func EqualJson2(j1, j2 string) (err error) { var expected map[string]interface{} - json.Unmarshal([]byte(j1), &expected) + err = json.Unmarshal([]byte(j1), &expected) + if err != nil { + return fmt.Errorf("failed to unmarshal expected: %w", err) + } var actual map[string]interface{} - json.Unmarshal([]byte(j2), &actual) + err = json.Unmarshal([]byte(j2), &actual) + if err != nil { + return fmt.Errorf("failed to unmarshal actual: %w", err) + } err = recursiveCompare("root", expected, actual) @@ -113,18 +120,57 @@ func getType(val interface{}) ValueType { // The decoding process doesn't seem to distinguish between string and binary, but the encoding process // does. So sometimes the string will be base64 encoded and need to compare against the decoded string // value. +// There are some discrepancies in different algod / SDK types that causes +// encodings that need special handling: +// * Address is sometimes B32 encoded and sometimes B64 encoded. +// * BlockHash is sometimes B32 encoded (with a blk prefix) and sometimes B64 encoded. func binaryOrStringEqual(s1, s2 string) bool { if s1 == s2 { return true } - if val, err := base64.StdEncoding.DecodeString(s1); err == nil { - if string(val) == s2 { - return true + // S1 convert to S2 + { + if val, err := base64.StdEncoding.DecodeString(s1); err == nil { + if string(val) == s2 { + return true + } + var addr types.Address + if len(val) == len(addr[:]) { + copy(addr[:], val) + if addr.String() == s2 { + return true + } + } + + } + // parse blockhash + var bh types.BlockHash + if bh.UnmarshalText([]byte(s1)) == nil { + if base64.StdEncoding.EncodeToString(bh[:]) == s2 { + return true + } } } - if val, err := base64.StdEncoding.DecodeString(s2); err == nil { - if string(val) == s1 { - return true + + // S2 convert to S1 + { + if val, err := base64.StdEncoding.DecodeString(s2); err == nil { + if string(val) == s1 { + return true + } + var addr types.Address + if len(val) == len(addr[:]) { + copy(addr[:], val) + if addr.String() == s1 { + return true + } + } + } + var bh types.BlockHash + if bh.UnmarshalText([]byte(s2)) == nil { + if base64.StdEncoding.EncodeToString(bh[:]) == s1 { + return true + } } } return false diff --git a/types/blockhash.go b/types/blockhash.go index a38cd259..3bd2b97a 100644 --- a/types/blockhash.go +++ b/types/blockhash.go @@ -24,13 +24,13 @@ func (b *BlockHash) UnmarshalText(text []byte) error { *b = BlockHash(d) return nil } + // ignore the DigestFromString error because it isn't the native MarshalText format. // Attempt to decode base64 format - var data BlockHash - n, err := base64.StdEncoding.Decode(data[:], text) + data, err := base64.StdEncoding.DecodeString(string(text)) if err == nil { - if n != len(b[:]) { + if len(data) != len(b[:]) { return errWrongBlockHashLen } copy(b[:], data[:]) diff --git a/types/blockhash_test.go b/types/blockhash_test.go index 47a2c6b0..dc9a36f7 100644 --- a/types/blockhash_test.go +++ b/types/blockhash_test.go @@ -36,6 +36,10 @@ func TestUnmarshalBlockHash(t *testing.T) { name: "B64-err-illegal", input: "bogus", err: "illegal base64 data at input byte 4", + }, { + name: "Overflow does not panic", + input: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY5HFKQ", + err: "illegal base64 data at input byte 56", }, } diff --git a/types/statedelta.go b/types/statedelta.go new file mode 100644 index 00000000..22a1b45f --- /dev/null +++ b/types/statedelta.go @@ -0,0 +1,327 @@ +package types + +// TealType is an enum of the types in a TEAL program: Bytes and Uint +type TealType uint64 + +const ( + // TealBytesType represents the type of a byte slice in a TEAL program + TealBytesType TealType = 1 + + // TealUintType represents the type of a uint in a TEAL program + TealUintType TealType = 2 +) + +// Status is the delegation status of an account's MicroAlgos +type Status byte + +const ( + // Offline indicates that the associated account receives rewards but does not participate in the consensus. + Offline Status = iota + // Online indicates that the associated account participates in the consensus and receive rewards. + Online + // NotParticipating indicates that the associated account neither participates in the consensus, nor receives rewards. + // Accounts that are marked as NotParticipating cannot change their status, but can receive and send Algos to other accounts. + // Two special accounts that are defined as NotParticipating are the incentive pool (also know as rewards pool) and the fee sink. + // These two accounts also have additional Algo transfer restrictions. + NotParticipating +) + +// TealValue contains type information and a value, representing a value in a +// TEAL program +type TealValue struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Type TealType `codec:"tt"` + Bytes string `codec:"tb"` + Uint uint64 `codec:"ui"` +} + +// TealKeyValue represents a key/value store for use in an application's +// LocalState or GlobalState +//msgp:allocbound TealKeyValue EncodedMaxKeyValueEntries +type TealKeyValue map[string]TealValue + +// StateSchemas is a thin wrapper around the LocalStateSchema and the +// GlobalStateSchema, since they are often needed together +type StateSchemas struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + LocalStateSchema StateSchema `codec:"lsch"` + GlobalStateSchema StateSchema `codec:"gsch"` +} + +// AppParams stores the global information associated with an application +type AppParams struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + ApprovalProgram []byte `codec:"approv,allocbound=config.MaxAvailableAppProgramLen"` + ClearStateProgram []byte `codec:"clearp,allocbound=config.MaxAvailableAppProgramLen"` + GlobalState TealKeyValue `codec:"gs"` + StateSchemas + ExtraProgramPages uint32 `codec:"epp"` +} + +// AppLocalState stores the LocalState associated with an application. It also +// stores a cached copy of the application's LocalStateSchema so that +// MinBalance requirements may be computed 1. without looking up the +// AppParams and 2. even if the application has been deleted +type AppLocalState struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Schema StateSchema `codec:"hsch"` + KeyValue TealKeyValue `codec:"tkv"` +} + +// AppLocalStateDelta tracks a changed AppLocalState, and whether it was deleted +type AppLocalStateDelta struct { + LocalState *AppLocalState + Deleted bool +} + +// AppParamsDelta tracks a changed AppParams, and whether it was deleted +type AppParamsDelta struct { + Params *AppParams + Deleted bool +} + +// AppResourceRecord represents AppParams and AppLocalState in deltas +type AppResourceRecord struct { + Aidx AppIndex + Addr Address + Params AppParamsDelta + State AppLocalStateDelta +} + +// AssetHolding describes an asset held by an account. +type AssetHolding struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Amount uint64 `codec:"a"` + Frozen bool `codec:"f"` +} + +// AssetHoldingDelta records a changed AssetHolding, and whether it was deleted +type AssetHoldingDelta struct { + Holding *AssetHolding + Deleted bool +} + +// AssetParamsDelta tracks a changed AssetParams, and whether it was deleted +type AssetParamsDelta struct { + Params *AssetParams + Deleted bool +} + +// AssetResourceRecord represents AssetParams and AssetHolding in deltas +type AssetResourceRecord struct { + Aidx AssetIndex + Addr Address + Params AssetParamsDelta + Holding AssetHoldingDelta +} + +// AccountAsset is used as a map key. +type AccountAsset struct { + Address Address + Asset AssetIndex +} + +// AccountApp is used as a map key. +type AccountApp struct { + Address Address + App AppIndex +} + +// A OneTimeSignatureVerifier is used to identify the holder of +// OneTimeSignatureSecrets and prove the authenticity of OneTimeSignatures +// against some OneTimeSignatureIdentifier. +type OneTimeSignatureVerifier [32]byte + +// VRFVerifier is a deprecated name for VrfPubkey +type VRFVerifier [32]byte + +// VotingData holds participation information +type VotingData struct { + VoteID OneTimeSignatureVerifier + SelectionID VRFVerifier + StateProofID Commitment + + VoteFirstValid Round + VoteLastValid Round + VoteKeyDilution uint64 +} + +// AccountBaseData contains base account info like balance, status and total number of resources +type AccountBaseData struct { + Status Status + MicroAlgos MicroAlgos + RewardsBase uint64 + RewardedMicroAlgos MicroAlgos + AuthAddr Address + + TotalAppSchema StateSchema // Totals across created globals, and opted in locals. + TotalExtraAppPages uint32 // Total number of extra pages across all created apps + TotalAppParams uint64 // Total number of apps this account has created + TotalAppLocalStates uint64 // Total number of apps this account is opted into. + TotalAssetParams uint64 // Total number of assets created by this account + TotalAssets uint64 // Total of asset creations and optins (i.e. number of holdings) + TotalBoxes uint64 // Total number of boxes associated to this account + TotalBoxBytes uint64 // Total bytes for this account's boxes. keys _and_ values count +} + +// AccountData provides users of the Balances interface per-account data (like basics.AccountData) +// but without any maps containing AppParams, AppLocalState, AssetHolding, or AssetParams. This +// ensures that transaction evaluation must retrieve and mutate account, asset, and application data +// separately, to better support on-disk and in-memory schemas that do not store them together. +type AccountData struct { + AccountBaseData + VotingData +} + +// BalanceRecord is similar to basics.BalanceRecord but with decoupled base and voting data +type BalanceRecord struct { + Addr Address + AccountData +} + +// AccountDeltas stores ordered accounts and allows fast lookup by address +// One key design aspect here was to ensure that we're able to access the written +// deltas in a deterministic order, while maintaining O(1) lookup. In order to +// do that, each of the arrays here is constructed as a pair of (slice, map). +// The map would point the address/address+creatable id onto the index of the +// element within the slice. +// If adding fields make sure to add them to the .reset() method to avoid dirty state +type AccountDeltas struct { + // Actual data. If an account is deleted, `Accts` contains the BalanceRecord + // with an empty `AccountData` and a populated `Addr`. + Accts []BalanceRecord + // cache for addr to deltas index resolution + acctsCache map[Address]int + + // AppResources deltas. If app params or local state is deleted, there is a nil value in AppResources.Params or AppResources.State and Deleted flag set + AppResources []AppResourceRecord + // caches for {addr, app id} to app params delta resolution + // not preallocated - use UpsertAppResource instead of inserting directly + appResourcesCache map[AccountApp]int + + AssetResources []AssetResourceRecord + // not preallocated - use UpsertAssertResource instead of inserting directly + assetResourcesCache map[AccountAsset]int +} + +// A KvValueDelta shows how the Data associated with a key in the kvstore has +// changed. However, OldData is elided during evaluation, and only filled in at +// the conclusion of a block during the called to roundCowState.deltas() +type KvValueDelta struct { + // Data stores the most recent value (nil == deleted) + Data []byte + + // OldData stores the previous vlaue (nil == didn't exist) + OldData []byte +} + +// IncludedTransactions defines the transactions included in a block, their index and last valid round. +type IncludedTransactions struct { + LastValid Round + Intra uint64 // the index of the transaction in the block +} + +// Txid is a hash used to uniquely identify individual transactions +type Txid Digest + +// A Txlease is a transaction (sender, lease) pair which uniquely specifies a +// transaction lease. +type Txlease struct { + Sender Address + Lease [32]byte +} + +// CreatableIndex represents either an AssetIndex or AppIndex, which come from +// the same namespace of indices as each other (both assets and apps are +// "creatables") +type CreatableIndex uint64 + +// CreatableType is an enum representing whether or not a given creatable is an +// application or an asset +type CreatableType uint64 + +// ModifiedCreatable defines the changes to a single single creatable state +type ModifiedCreatable struct { + // Type of the creatable: app or asset + Ctype CreatableType + + // Created if true, deleted if false + Created bool + + // creator of the app/asset + Creator Address + + // Keeps track of how many times this app/asset appears in + // accountUpdates.creatableDeltas + Ndeltas int +} + +// AlgoCount represents a total of algos of a certain class +// of accounts (split up by their Status value). +type AlgoCount struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + // Sum of algos of all accounts in this class. + Money MicroAlgos `codec:"mon"` + + // Total number of whole reward units in accounts. + RewardUnits uint64 `codec:"rwd"` +} + +// AccountTotals represents the totals of algos in the system +// grouped by different account status values. +type AccountTotals struct { + _struct struct{} `codec:",omitempty,omitemptyarray"` + + Online AlgoCount `codec:"online"` + Offline AlgoCount `codec:"offline"` + NotParticipating AlgoCount `codec:"notpart"` + + // Total number of algos received per reward unit since genesis + RewardsLevel uint64 `codec:"rwdlvl"` +} + +// LedgerStateDelta describes the delta between a given round to the previous round +// If adding a new field not explicitly allocated by PopulateStateDelta, make sure to reset +// it in .ReuseStateDelta to avoid dirty memory errors. +// If adding fields make sure to add them to the .Reset() method to avoid dirty state +type LedgerStateDelta struct { + // modified new accounts + Accts AccountDeltas + + // modified kv pairs (nil == delete) + // not preallocated use .AddKvMod to insert instead of direct assignment + KvMods map[string]KvValueDelta + + // new Txids for the txtail and TxnCounter, mapped to txn.LastValid + Txids map[Txid]IncludedTransactions + + // new txleases for the txtail mapped to expiration + // not pre-allocated so use .AddTxLease to insert instead of direct assignment + Txleases map[Txlease]Round + + // new creatables creator lookup table + // not pre-allocated so use .AddCreatable to insert instead of direct assignment + Creatables map[CreatableIndex]ModifiedCreatable + + // new block header; read-only + Hdr *BlockHeader + + // next round for which we expect a state proof. + // zero if no state proof is expected. + StateProofNext Round + + // previous block timestamp + PrevTimestamp int64 + + // initial hint for allocating data structures for StateDelta + initialHint int + + // The account totals reflecting the changes in this StateDelta object. + Totals AccountTotals +} From 9aefe062580c0a152e79f47b3f15763bf671a26f Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Thu, 16 Mar 2023 12:16:38 -0400 Subject: [PATCH 08/16] docs: Create runnable examples to be pulled into docs (#480) * Initial examples setup, algod and indexer * account and kmd examples for docs * gofmt * Participation code. Only includes marking online atm * adding more files * adding atomic transfer example * Adding asa examples * Adding atc * Adding app txns * adding lsig examples * Adding codec and debug examples --------- Co-authored-by: nullun --- _examples/account.go | 83 ++++ _examples/application/approval.teal | 100 ++++ .../application/approval_refactored.teal | 107 +++++ _examples/application/clear.teal | 3 + _examples/apps.go | 446 ++++++++++++++++++ _examples/asa.go | 337 +++++++++++++ _examples/atc.go | 112 +++++ _examples/atomic_transfer.go | 81 ++++ _examples/calculator/approval.teal | 181 +++++++ _examples/calculator/clear.teal | 3 + _examples/calculator/contract.json | 74 +++ _examples/codec.go | 88 ++++ _examples/debug.go | 81 ++++ _examples/indexer.go | 135 ++++++ _examples/kmd.go | 229 +++++++++ _examples/lsig.go | 130 +++++ _examples/lsig/sample_arg.teal | 5 + _examples/lsig/simple.teal | 3 + _examples/overview.go | 70 +++ _examples/participation.go | 67 +++ _examples/util.go | 182 +++++++ 21 files changed, 2517 insertions(+) create mode 100644 _examples/account.go create mode 100644 _examples/application/approval.teal create mode 100644 _examples/application/approval_refactored.teal create mode 100644 _examples/application/clear.teal create mode 100644 _examples/apps.go create mode 100644 _examples/asa.go create mode 100644 _examples/atc.go create mode 100644 _examples/atomic_transfer.go create mode 100644 _examples/calculator/approval.teal create mode 100644 _examples/calculator/clear.teal create mode 100644 _examples/calculator/contract.json create mode 100644 _examples/codec.go create mode 100644 _examples/debug.go create mode 100644 _examples/indexer.go create mode 100644 _examples/kmd.go create mode 100644 _examples/lsig.go create mode 100644 _examples/lsig/sample_arg.teal create mode 100644 _examples/lsig/simple.teal create mode 100644 _examples/overview.go create mode 100644 _examples/participation.go create mode 100644 _examples/util.go diff --git a/_examples/account.go b/_examples/account.go new file mode 100644 index 00000000..146d7c57 --- /dev/null +++ b/_examples/account.go @@ -0,0 +1,83 @@ +package main + +import ( + "context" + "fmt" + "log" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/mnemonic" + "github.com/algorand/go-algorand-sdk/v2/transaction" +) + +func main() { + // example: ACCOUNT_GENERATE + account := crypto.GenerateAccount() + mn, err := mnemonic.FromPrivateKey(account.PrivateKey) + + if err != nil { + log.Fatalf("failed to generate account: %s", err) + } + + log.Printf("Address: %s\n", account.Address) + log.Printf("Mnemonic: %s\n", mn) + // example: ACCOUNT_GENERATE + + // example: ACCOUNT_RECOVER_MNEMONIC + k, err := mnemonic.ToPrivateKey(mn) + if err != nil { + log.Fatalf("failed to parse mnemonic: %s", err) + } + + recovered, err := crypto.AccountFromPrivateKey(k) + if err != nil { + log.Fatalf("failed to recover account from key: %s", err) + } + + log.Printf("%+v", recovered) + // example: ACCOUNT_RECOVER_MNEMONIC + accts, err := getSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + rekeyAccount(getAlgodClient(), accts[0], accts[1]) +} + +func rekeyAccount(algodClient *algod.Client, acct crypto.Account, rekeyTarget crypto.Account) { + // example: ACCOUNT_REKEY + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("failed to get suggested params: %s", err) + } + + addr := acct.Address.String() + // here we create a payment transaction but rekey is valid + // on any transaction type + rktxn, err := transaction.MakePaymentTxn(addr, addr, 0, nil, "", sp) + if err != nil { + log.Fatalf("failed to creating transaction: %s\n", err) + } + // Set the rekey parameter + rktxn.RekeyTo = rekeyTarget.Address + + _, stxn, err := crypto.SignTransaction(acct.PrivateKey, rktxn) + if err != nil { + fmt.Printf("Failed to sign transaction: %s\n", err) + } + + txID, err := algodClient.SendRawTransaction(stxn).Do(context.Background()) + if err != nil { + fmt.Printf("failed to send transaction: %s\n", err) + return + } + + result, err := transaction.WaitForConfirmation(algodClient, txID, 4, context.Background()) + if err != nil { + fmt.Printf("Error waiting for confirmation on txID: %s\n", txID) + return + } + + fmt.Printf("Confirmed Transaction: %s in Round %d\n", txID, result.ConfirmedRound) + // example: ACCOUNT_REKEY +} diff --git a/_examples/application/approval.teal b/_examples/application/approval.teal new file mode 100644 index 00000000..eabde624 --- /dev/null +++ b/_examples/application/approval.teal @@ -0,0 +1,100 @@ +#pragma version 4 +// Handle each possible OnCompletion type. We don't have to worry about +// handling ClearState, because the ClearStateProgram will execute in that +// case, not the ApprovalProgram. +txn ApplicationID +int 0 +== +bnz handle_approve + +txn OnCompletion +int NoOp +== +bnz handle_noop + +txn OnCompletion +int OptIn +== +bnz handle_approve + +txn OnCompletion +int CloseOut +== +bnz handle_closeout + +txn OnCompletion +int UpdateApplication +== +bnz handle_updateapp + +txn OnCompletion +int DeleteApplication +== +bnz handle_deleteapp + +// Unexpected OnCompletion value. Should be unreachable. +err + +handle_noop: +// Handle NoOp + +// read global state +byte "counter" +dup +app_global_get + +// increment the value +int 1 ++ + +// store to scratch space +dup +store 0 + +// update global state +app_global_put + +// read local state for sender +int 0 +byte "counter" +app_local_get + +// increment the value +int 1 ++ +store 1 + +// update local state for sender +int 0 +byte "counter" +load 1 +app_local_put + +// load return value as approval +load 0 +return + + +handle_closeout: +// Handle CloseOut +//approval +int 1 +return + +handle_deleteapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_updateapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_approve: +int 1 +return diff --git a/_examples/application/approval_refactored.teal b/_examples/application/approval_refactored.teal new file mode 100644 index 00000000..1af5a7eb --- /dev/null +++ b/_examples/application/approval_refactored.teal @@ -0,0 +1,107 @@ +#pragma version 4 +// Handle each possible OnCompletion type. We don't have to worry about +// handling ClearState, because the ClearStateProgram will execute in that +// case, not the ApprovalProgram. + +txn ApplicationID +int 0 +== +bnz handle_approve + +txn OnCompletion +int NoOp +== +bnz handle_noop + +txn OnCompletion +int OptIn +== +bnz handle_approve + +txn OnCompletion +int CloseOut +== +bnz handle_closeout + +txn OnCompletion +int UpdateApplication +== +bnz handle_updateapp + +txn OnCompletion +int DeleteApplication +== +bnz handle_deleteapp + +// Unexpected OnCompletion value. Should be unreachable. +err + +handle_noop: +// Handle NoOp + +// read global state +byte "counter" +dup +app_global_get + +// increment the value +int 1 ++ + +// store to scratch space +dup +store 0 + +// update global state +app_global_put + +// read local state for sender +int 0 +byte "counter" +app_local_get + +// increment the value +int 1 ++ +store 1 + +// update local state for sender +// update "counter" +int 0 +byte "counter" +load 1 +app_local_put + +// update "timestamp" +int 0 +byte "timestamp" +txn ApplicationArgs 0 +app_local_put + +// load return value as approval +load 0 +return + +handle_closeout: +// Handle CloseOut +//approval +int 1 +return + +handle_deleteapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_updateapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_approve: +int 1 +return \ No newline at end of file diff --git a/_examples/application/clear.teal b/_examples/application/clear.teal new file mode 100644 index 00000000..d793651c --- /dev/null +++ b/_examples/application/clear.teal @@ -0,0 +1,3 @@ +#pragma version 4 +int 1 +return \ No newline at end of file diff --git a/_examples/apps.go b/_examples/apps.go new file mode 100644 index 00000000..f996a15a --- /dev/null +++ b/_examples/apps.go @@ -0,0 +1,446 @@ +package main + +import ( + "context" + "encoding/base64" + "io/ioutil" + "log" + "time" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + + algodClient := getAlgodClient() + accts, err := getSandboxAccounts() + if err != nil { + log.Fatal("failed to get sandbox accounts: %s", err) + } + acct1 := accts[0] + appID := appCreate(algodClient, acct1) + appOptIn(algodClient, appID, acct1) + + // example: APP_READ_STATE + // grab global state and config of application + appInfo, err := algodClient.GetApplicationByID(appID).Do(context.Background()) + if err != nil { + log.Fatalf("failed to get app info: %s", err) + } + log.Printf("app info: %+v", appInfo) + + // grab local state for an app id for a single account + acctInfo, err := algodClient.AccountApplicationInformation( + acct1.Address.String(), appID, + ).Do(context.Background()) + if err != nil { + log.Fatalf("failed to get app info: %s", err) + } + log.Printf("app info: %+v", acctInfo) + // example: APP_READ_STATE + + appNoOp(algodClient, appID, acct1) + appUpdate(algodClient, appID, acct1) + appCall(algodClient, appID, acct1) + appCloseOut(algodClient, appID, acct1) + appDelete(algodClient, appID, acct1) + +} + +func appCreate(algodClient *algod.Client, creator crypto.Account) uint64 { + // example: APP_SCHEMA + // declare application state storage (immutable) + var ( + localInts uint64 = 1 + localBytes uint64 = 1 + globalInts uint64 = 1 + globalBytes uint64 = 0 + ) + + // define schema + globalSchema := types.StateSchema{NumUint: globalInts, NumByteSlice: globalBytes} + localSchema := types.StateSchema{NumUint: localInts, NumByteSlice: localBytes} + // example: APP_SCHEMA + + // example: APP_SOURCE + approvalTeal, err := ioutil.ReadFile("application/approval.teal") + if err != nil { + log.Fatalf("failed to read approval program: %s", err) + } + clearTeal, err := ioutil.ReadFile("application/clear.teal") + if err != nil { + log.Fatalf("failed to read clear program: %s", err) + } + // example: APP_SOURCE + + // example: APP_COMPILE + approvalResult, err := algodClient.TealCompile(approvalTeal).Do(context.Background()) + if err != nil { + log.Fatalf("failed to compile program: %s", err) + } + + approvalBinary, err := base64.StdEncoding.DecodeString(approvalResult.Result) + if err != nil { + log.Fatalf("failed to decode compiled program: %s", err) + } + + clearResult, err := algodClient.TealCompile(clearTeal).Do(context.Background()) + if err != nil { + log.Fatalf("failed to compile program: %s", err) + } + + clearBinary, err := base64.StdEncoding.DecodeString(clearResult.Result) + if err != nil { + log.Fatalf("failed to decode compiled program: %s", err) + } + // example: APP_COMPILE + + // example: APP_CREATE + // Create application + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakeApplicationCreateTx( + false, approvalBinary, clearBinary, globalSchema, localSchema, + nil, nil, nil, nil, sp, creator.Address, nil, + types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + appID := confirmedTxn.ApplicationIndex + log.Printf("Created app with id: %d", appID) + // example: APP_CREATE + return appID +} + +func appOptIn(algodClient *algod.Client, appID uint64, caller crypto.Account) { + // example: APP_OPTIN + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + // Create a new clawback transaction with the target of the user address and the recipient as the creator + // address, being sent from the address marked as `clawback` on the asset, in this case the same as creator + txn, err := transaction.MakeApplicationOptInTx( + appID, nil, nil, nil, nil, sp, + caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("OptIn Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_OPTIN +} + +func appNoOp(algodClient *algod.Client, appID uint64, caller crypto.Account) { + // example: APP_NOOP + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + var ( + appArgs [][]byte + accts []string + apps []uint64 + assets []uint64 + ) + + // Add an arg to our app call + appArgs = append(appArgs, []byte("arg0")) + + txn, err := transaction.MakeApplicationNoOpTx( + appID, appArgs, accts, apps, assets, sp, + caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("NoOp Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_NOOP +} + +func appUpdate(algodClient *algod.Client, appID uint64, caller crypto.Account) { + approvalBinary := compileTeal(algodClient, "application/approval_refactored.teal") + clearBinary := compileTeal(algodClient, "application/clear.teal") + + // example: APP_UPDATE + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + var ( + appArgs [][]byte + accts []string + apps []uint64 + assets []uint64 + ) + + txn, err := transaction.MakeApplicationUpdateTx( + appID, appArgs, accts, apps, assets, approvalBinary, clearBinary, + sp, caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Update Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_UPDATE +} + +func appCloseOut(algodClient *algod.Client, appID uint64, caller crypto.Account) { + // example: APP_CLOSEOUT + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + var ( + appArgs [][]byte + accts []string + apps []uint64 + assets []uint64 + ) + + txn, err := transaction.MakeApplicationCloseOutTx( + appID, appArgs, accts, apps, assets, sp, + caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Closeout Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_CLOSEOUT +} + +func appClearState(algodClient *algod.Client, appID uint64, caller crypto.Account) { + // example: APP_CLEAR + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + var ( + appArgs [][]byte + accts []string + apps []uint64 + assets []uint64 + ) + + txn, err := transaction.MakeApplicationClearStateTx( + appID, appArgs, accts, apps, assets, sp, + caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("ClearState Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_CLEAR +} + +func appCall(algodClient *algod.Client, appID uint64, caller crypto.Account) { + // example: APP_CALL + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + var ( + appArgs [][]byte + accts []string + apps []uint64 + assets []uint64 + ) + + datetime := time.Now().Format("2006-01-02 at 15:04:05") + appArgs = append(appArgs, []byte(datetime)) + + txn, err := transaction.MakeApplicationNoOpTx( + appID, appArgs, accts, apps, assets, sp, + caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("NoOp Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_CALL +} + +func appDelete(algodClient *algod.Client, appID uint64, caller crypto.Account) { + // example: APP_DELETE + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + var ( + appArgs [][]byte + accts []string + apps []uint64 + assets []uint64 + ) + + txn, err := transaction.MakeApplicationDeleteTx( + appID, appArgs, accts, apps, assets, sp, + caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Delete Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_DELETE +} diff --git a/_examples/asa.go b/_examples/asa.go new file mode 100644 index 00000000..e8e28d36 --- /dev/null +++ b/_examples/asa.go @@ -0,0 +1,337 @@ +package main + +import ( + "context" + "log" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/transaction" +) + +func main() { + + algodClient := getAlgodClient() + accts, err := getSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + + creator := accts[0] + user := accts[1] + + assetID := createAsset(algodClient, creator) + // example: ASSET_INFO + info, err := algodClient.GetAssetByID(assetID).Do(context.Background()) + if err != nil { + log.Fatalf("failed to get asset info: %s", err) + } + log.Printf("Asset info for %d: %+v", assetID, info) + // example: ASSET_INFO + + configureAsset(algodClient, assetID, creator) + optInAsset(algodClient, assetID, user) + xferAsset(algodClient, assetID, creator, user) + freezeAsset(algodClient, assetID, creator, user) + clawbackAsset(algodClient, assetID, creator, user) + deleteAsset(algodClient, assetID, creator) +} + +func createAsset(algodClient *algod.Client, creator crypto.Account) uint64 { + // example: ASSET_CREATE + // Configure parameters for asset creation + var ( + creatorAddr = creator.Address.String() + assetName = "Really Useful Gift" + unitName = "rug" + assetURL = "https://path/to/my/asset/details" + assetMetadataHash = "thisIsSomeLength32HashCommitment" + defaultFrozen = false + decimals = uint32(0) + totalIssuance = uint64(1000) + + manager = creatorAddr + reserve = creatorAddr + freeze = creatorAddr + clawback = creatorAddr + + note []byte + ) + + // Get network-related transaction parameters and assign + txParams, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + // Construct the transaction + txn, err := transaction.MakeAssetCreateTxn( + creatorAddr, note, txParams, totalIssuance, decimals, + defaultFrozen, manager, reserve, freeze, clawback, + unitName, assetName, assetURL, assetMetadataHash, + ) + + if err != nil { + log.Fatalf("failed to make transaction: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Create Transaction: %s confirmed in Round %d with new asset id: %d\n", + txid, confirmedTxn.ConfirmedRound, confirmedTxn.AssetIndex) + // example: ASSET_CREATE + return confirmedTxn.AssetIndex +} + +func configureAsset(algodClient *algod.Client, assetID uint64, creator crypto.Account) { + // example: ASSET_CONFIG + creatorAddr := creator.Address.String() + var ( + newManager = creatorAddr + newFreeze = creatorAddr + newClawback = creatorAddr + newReserve = "" + + strictAddrCheck = false + note []byte + ) + + // Get network-related transaction parameters and assign + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakeAssetConfigTxn(creatorAddr, note, sp, assetID, newManager, newReserve, newFreeze, newClawback, strictAddrCheck) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Asset Config Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_CONFIG +} + +func optInAsset(algodClient *algod.Client, assetID uint64, user crypto.Account) { + // example: ASSET_OPTIN + userAddr := user.Address.String() + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakeAssetAcceptanceTxn(userAddr, nil, sp, assetID) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(user.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("OptIn Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_OPTIN +} + +func xferAsset(algodClient *algod.Client, assetID uint64, creator crypto.Account, user crypto.Account) { + // example: ASSET_XFER + var ( + creatorAddr = creator.Address.String() + userAddr = user.Address.String() + ) + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakeAssetTransferTxn(creatorAddr, userAddr, 1, nil, sp, "", assetID) + if err != nil { + log.Fatalf("failed to make asset txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Asset Transfer Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_XFER +} + +func freezeAsset(algodClient *algod.Client, assetID uint64, creator crypto.Account, user crypto.Account) { + // example: ASSET_FREEZE + var ( + creatorAddr = creator.Address.String() + userAddr = user.Address.String() + ) + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + // Create a freeze asset transaction with the target of the user address + // and the new freeze setting of `true` + txn, err := transaction.MakeAssetFreezeTxn(creatorAddr, nil, sp, assetID, userAddr, true) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Freeze Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_FREEZE +} + +func clawbackAsset(algodClient *algod.Client, assetID uint64, creator crypto.Account, user crypto.Account) { + // example: ASSET_CLAWBACK + var ( + creatorAddr = creator.Address.String() + userAddr = user.Address.String() + ) + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + // Create a new clawback transaction with the target of the user address and the recipient as the creator + // address, being sent from the address marked as `clawback` on the asset, in this case the same as creator + txn, err := transaction.MakeAssetRevocationTxn(creatorAddr, userAddr, 1, creatorAddr, nil, sp, assetID) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Clawback Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_CLAWBACK +} + +func deleteAsset(algodClient *algod.Client, assetID uint64, creator crypto.Account) { + // example: ASSET_DELETE + var ( + creatorAddr = creator.Address.String() + ) + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + // Create a new clawback transaction with the target of the user address and the recipient as the creator + // address, being sent from the address marked as `clawback` on the asset, in this case the same as creator + txn, err := transaction.MakeAssetDestroyTxn(creatorAddr, nil, sp, assetID) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Destroy Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_DELETE +} diff --git a/_examples/atc.go b/_examples/atc.go new file mode 100644 index 00000000..d4d2cac2 --- /dev/null +++ b/_examples/atc.go @@ -0,0 +1,112 @@ +package main + +import ( + "context" + "encoding/json" + "io/ioutil" + "log" + + "github.com/algorand/go-algorand-sdk/v2/abi" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + algodClient := getAlgodClient() + accts, err := getSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + + acct1 := accts[0] + + appID := deployApp(algodClient, acct1) + log.Printf("%d", appID) + + // example: ATC_CONTRACT_INIT + b, err := ioutil.ReadFile("calculator/contract.json") + if err != nil { + log.Fatalf("failed to read contract file: %s", err) + } + + contract := &abi.Contract{} + if err := json.Unmarshal(b, contract); err != nil { + log.Fatalf("failed to unmarshal contract: %s", err) + } + // example: ATC_CONTRACT_INIT + + // example: ATC_CREATE + // Create the atc we'll use to compose our transaction group + var atc = transaction.AtomicTransactionComposer{} + // example: ATC_CREATE + + // example: ATC_ADD_TRANSACTION + // Get suggested params and make a transaction as usual + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakePaymentTxn(acct1.Address.String(), acct1.Address.String(), 10000, nil, "", sp) + if err != nil { + log.Fatalf("failed to make transaction: %s", err) + } + + // Construct a TransactionWithSigner and pass it to the atc + signer := transaction.BasicAccountTransactionSigner{Account: acct1} + atc.AddTransaction(transaction.TransactionWithSigner{Txn: txn, Signer: signer}) + // example: ATC_ADD_TRANSACTION + + // example: ATC_ADD_METHOD_CALL + // Grab the method from out contract object + addMethod, err := contract.GetMethodByName("add") + if err != nil { + log.Fatalf("failed to get add method: %s", err) + } + + // Set up method call params + mcp := transaction.AddMethodCallParams{ + AppID: appID, + Sender: acct1.Address, + SuggestedParams: sp, + OnComplete: types.NoOpOC, + Signer: signer, + Method: addMethod, + MethodArgs: []interface{}{1, 1}, + } + if err := atc.AddMethodCall(mcp); err != nil { + log.Fatalf("failed to add method call: %s", err) + } + // example: ATC_ADD_METHOD_CALL + + // example: ATC_RESULTS + result, err := atc.Execute(algodClient, context.Background(), 4) + if err != nil { + log.Fatalf("failed to get add method: %s", err) + } + + for _, r := range result.MethodResults { + log.Printf("%s => %v", r.Method.Name, r.ReturnValue) + } + // example: ATC_RESULTS + + // example: ATC_BOX_REF + boxName := "coolBoxName" + mcp = transaction.AddMethodCallParams{ + AppID: appID, + Sender: acct1.Address, + SuggestedParams: sp, + OnComplete: types.NoOpOC, + Signer: signer, + Method: addMethod, + MethodArgs: []interface{}{1, 1}, + // Here we're passing a box reference so our app + // can reference it during evaluation + BoxReferences: []types.AppBoxReference{ + {AppID: appID, Name: []byte(boxName)}, + }, + } + // ... + // example: ATC_BOX_REF + +} diff --git a/_examples/atomic_transfer.go b/_examples/atomic_transfer.go new file mode 100644 index 00000000..72c989da --- /dev/null +++ b/_examples/atomic_transfer.go @@ -0,0 +1,81 @@ +package main + +import ( + "context" + "fmt" + "log" + + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + algodClient := getAlgodClient() + accts, err := getSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + + acct1 := accts[0] + acct2 := accts[1] + + // example: ATOMIC_CREATE_TXNS + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("failed to get suggested params: %s", err) + } + + tx1, err := transaction.MakePaymentTxn(acct1.Address.String(), acct2.Address.String(), 100000, nil, "", sp) + if err != nil { + log.Fatalf("failed creating transaction: %s", err) + } + + // from account 2 to account 1 + tx2, err := transaction.MakePaymentTxn(acct2.Address.String(), acct1.Address.String(), 100000, nil, "", sp) + if err != nil { + log.Fatalf("failed creating transaction: %s", err) + } + // example: ATOMIC_CREATE_TXNS + + // example: ATOMIC_GROUP_TXNS + // compute group id and put it into each transaction + gid, err := crypto.ComputeGroupID([]types.Transaction{tx1, tx2}) + tx1.Group = gid + tx2.Group = gid + // example: ATOMIC_GROUP_TXNS + + // example: ATOMIC_GROUP_SIGN + _, stx1, err := crypto.SignTransaction(acct1.PrivateKey, tx1) + if err != nil { + fmt.Printf("Failed to sign transaction: %s\n", err) + return + } + _, stx2, err := crypto.SignTransaction(acct2.PrivateKey, tx2) + if err != nil { + fmt.Printf("Failed to sign transaction: %s\n", err) + } + // example: ATOMIC_GROUP_SIGN + + // example: ATOMIC_GROUP_ASSEMBLE + var signedGroup []byte + signedGroup = append(signedGroup, stx1...) + signedGroup = append(signedGroup, stx2...) + + // example: ATOMIC_GROUP_ASSEMBLE + + // example: ATOMIC_GROUP_SEND + pendingTxID, err := algodClient.SendRawTransaction(signedGroup).Do(context.Background()) + if err != nil { + fmt.Printf("failed to send transaction: %s\n", err) + return + } + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, pendingTxID, 4, context.Background()) + if err != nil { + fmt.Printf("Error waiting for confirmation on txID: %s\n", pendingTxID) + return + } + fmt.Printf("Confirmed Transaction: %s in Round %d\n", pendingTxID, confirmedTxn.ConfirmedRound) + // example: ATOMIC_GROUP_SEND + +} diff --git a/_examples/calculator/approval.teal b/_examples/calculator/approval.teal new file mode 100644 index 00000000..32acbb84 --- /dev/null +++ b/_examples/calculator/approval.teal @@ -0,0 +1,181 @@ +#pragma version 8 +intcblock 0 1 +bytecblock 0x151f7c75 +txn NumAppArgs +intc_0 // 0 +== +bnz main_l10 +txna ApplicationArgs 0 +pushbytes 0xfe6bdf69 // "add(uint64,uint64)uint64" +== +bnz main_l9 +txna ApplicationArgs 0 +pushbytes 0xe2f188c5 // "mul(uint64,uint64)uint64" +== +bnz main_l8 +txna ApplicationArgs 0 +pushbytes 0x78b488b7 // "sub(uint64,uint64)uint64" +== +bnz main_l7 +txna ApplicationArgs 0 +pushbytes 0x16e80f08 // "div(uint64,uint64)uint64" +== +bnz main_l6 +err +main_l6: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 9 +txna ApplicationArgs 2 +btoi +store 10 +load 9 +load 10 +callsub div_3 +store 11 +bytec_0 // 0x151f7c75 +load 11 +itob +concat +log +intc_1 // 1 +return +main_l7: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 6 +txna ApplicationArgs 2 +btoi +store 7 +load 6 +load 7 +callsub sub_2 +store 8 +bytec_0 // 0x151f7c75 +load 8 +itob +concat +log +intc_1 // 1 +return +main_l8: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 3 +txna ApplicationArgs 2 +btoi +store 4 +load 3 +load 4 +callsub mul_1 +store 5 +bytec_0 // 0x151f7c75 +load 5 +itob +concat +log +intc_1 // 1 +return +main_l9: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 0 +txna ApplicationArgs 2 +btoi +store 1 +load 0 +load 1 +callsub add_0 +store 2 +bytec_0 // 0x151f7c75 +load 2 +itob +concat +log +intc_1 // 1 +return +main_l10: +txn OnCompletion +intc_0 // NoOp +== +bnz main_l12 +err +main_l12: +txn ApplicationID +intc_0 // 0 +== +assert +intc_1 // 1 +return + +// add +add_0: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 ++ +frame_bury 0 +retsub + +// mul +mul_1: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 +* +frame_bury 0 +retsub + +// sub +sub_2: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 +- +frame_bury 0 +retsub + +// div +div_3: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 +/ +frame_bury 0 +retsub \ No newline at end of file diff --git a/_examples/calculator/clear.teal b/_examples/calculator/clear.teal new file mode 100644 index 00000000..e741f0e5 --- /dev/null +++ b/_examples/calculator/clear.teal @@ -0,0 +1,3 @@ +#pragma version 8 +pushint 0 // 0 +return \ No newline at end of file diff --git a/_examples/calculator/contract.json b/_examples/calculator/contract.json new file mode 100644 index 00000000..4b23fa17 --- /dev/null +++ b/_examples/calculator/contract.json @@ -0,0 +1,74 @@ +{ + "name": "Calculator", + "methods": [ + { + "name": "add", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Add a and b, return the result" + }, + { + "name": "mul", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Multiply a and b, return the result" + }, + { + "name": "sub", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Subtract b from a, return the result" + }, + { + "name": "div", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Divide a by b, return the result" + } + ], + "networks": {} +} \ No newline at end of file diff --git a/_examples/codec.go b/_examples/codec.go new file mode 100644 index 00000000..b0c7159c --- /dev/null +++ b/_examples/codec.go @@ -0,0 +1,88 @@ +package main + +import ( + "context" + "encoding/base64" + "encoding/binary" + "log" + "os" + + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/encoding/msgpack" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + algodClient := getAlgodClient() + accts, err := getSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + + acct1 := accts[0] + + // example: CODEC_TRANSACTION_UNSIGNED + // Error handling omitted for brevity + sp, _ := algodClient.SuggestedParams().Do(context.Background()) + ptxn, _ := transaction.MakePaymentTxn( + acct1.Address.String(), acct1.Address.String(), 10000, nil, "", sp, + ) + + // Encode the txn as bytes, + // if sending over the wire (like to a frontend) it should also be b64 encoded + encodedTxn := msgpack.Encode(ptxn) + os.WriteFile("pay.txn", encodedTxn, 0655) + + var recoveredPayTxn = types.Transaction{} + + msgpack.Decode(encodedTxn, &recoveredPayTxn) + log.Printf("%+v", recoveredPayTxn) + // example: CODEC_TRANSACTION_UNSIGNED + + // example: CODEC_TRANSACTION_SIGNED + // Assuming we already have a pay transaction `ptxn` + + // Sign the transaction + _, signedTxn, err := crypto.SignTransaction(acct1.PrivateKey, ptxn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Save the signed transaction to file + os.WriteFile("pay.stxn", signedTxn, 0644) + + signedPayTxn := types.SignedTxn{} + err = msgpack.Decode(signedTxn, &signedPayTxn) + if err != nil { + log.Fatalf("failed to decode signed transaction: %s", err) + } + // example: CODEC_TRANSACTION_SIGNED + + // example: CODEC_ADDRESS + address := "4H5UNRBJ2Q6JENAXQ6HNTGKLKINP4J4VTQBEPK5F3I6RDICMZBPGNH6KD4" + pk, _ := types.DecodeAddress(address) + addr := pk.String() + // example: CODEC_ADDRESS + _ = addr + + // example: CODEC_BASE64 + encoded := "SGksIEknbSBkZWNvZGVkIGZyb20gYmFzZTY0" + decoded, _ := base64.StdEncoding.DecodeString(encoded) + reencoded := base64.StdEncoding.EncodeToString(decoded) + // example: CODEC_BASE64 + _ = reencoded + + // example: CODEC_UINT64 + val := 1337 + encodedInt := make([]byte, 8) + binary.BigEndian.PutUint64(encodedInt, uint64(val)) + + decodedInt := binary.BigEndian.Uint64(encodedInt) + // decodedInt == val + // example: CODEC_UINT64 + _ = decodedInt + + // example: CODEC_BLOCK + // example: CODEC_BLOCK +} diff --git a/_examples/debug.go b/_examples/debug.go new file mode 100644 index 00000000..07f506ef --- /dev/null +++ b/_examples/debug.go @@ -0,0 +1,81 @@ +package main + +import ( + "context" + "log" + "os" + + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/encoding/msgpack" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + + algodClient := getAlgodClient() + accts, err := getSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + + acct1 := accts[0] + + appID := deployApp(algodClient, acct1) + + // example: DEBUG_DRYRUN_DUMP + var ( + args [][]byte + accounts []string + apps []uint64 + assets []uint64 + ) + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("failed to get suggested params: %s", err) + } + + appCallTxn, err := transaction.MakeApplicationNoOpTx( + appID, args, accounts, apps, assets, sp, acct1.Address, + nil, types.Digest{}, [32]byte{}, types.Address{}, + ) + if err != nil { + log.Fatalf("Failed to create app call txn: %+v", err) + } + + _, stxn, err := crypto.SignTransaction(acct1.PrivateKey, appCallTxn) + if err != nil { + log.Fatalf("Failed to sign app txn: %+v", err) + } + + signedAppCallTxn := types.SignedTxn{} + msgpack.Decode(stxn, &signedAppCallTxn) + + drr, err := transaction.CreateDryrun(algodClient, []types.SignedTxn{signedAppCallTxn}, nil, context.Background()) + if err != nil { + log.Fatalf("Failed to create dryrun: %+v", err) + } + + os.WriteFile("dryrun.msgp", msgpack.Encode(drr), 0666) + // example: DEBUG_DRYRUN_DUMP + + // example: DEBUG_DRYRUN_SUBMIT + // Create the dryrun request object + drReq, err := transaction.CreateDryrun(algodClient, []types.SignedTxn{signedAppCallTxn}, nil, context.Background()) + if err != nil { + log.Fatalf("Failed to create dryrun: %+v", err) + } + + // Pass dryrun request to algod server + dryrunResponse, err := algodClient.TealDryrun(drReq).Do(context.Background()) + if err != nil { + log.Fatalf("failed to dryrun request: %s", err) + } + + // Inspect the response to check result + for _, txn := range dryrunResponse.Txns { + log.Printf("%+v", txn.AppCallTrace) + } + // example: DEBUG_DRYRUN_SUBMIT +} diff --git a/_examples/indexer.go b/_examples/indexer.go new file mode 100644 index 00000000..238550f1 --- /dev/null +++ b/_examples/indexer.go @@ -0,0 +1,135 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/common" + "github.com/algorand/go-algorand-sdk/v2/client/v2/indexer" +) + +func main() { + // example: CREATE_INDEXER_CLIENT + // Create a new indexer client, configured to connect to out local sandbox + var indexerAddress = "http://localhost:8980" + var indexerToken = strings.Repeat("a", 64) + indexerClient, err := indexer.MakeClient( + indexerAddress, + indexerToken, + ) + + // Or, if necessary, pass alternate headers + + var indexerHeader common.Header + indexerHeader.Key = "X-API-Key" + indexerHeader.Value = indexerToken + indexerClientWithHeaders, err := indexer.MakeClientWithHeaders( + indexerAddress, + indexerToken, + []*common.Header{&indexerHeader}, + ) + // example: CREATE_INDEXER_CLIENT + + // Suppress `indexerClientWithHeaders declared but not used` + _ = indexerClientWithHeaders + + if err != nil { + fmt.Printf("failed to make indexer client: %s\n", err) + return + } + + indexerHealth, err := indexerClient.HealthCheck().Do(context.Background()) + if err != nil { + fmt.Printf("Failed to get status: %s\n", err) + return + } + + fmt.Printf("Indexer Round: %d\n", indexerHealth.Round) + + // example: INDEXER_LOOKUP_ASSET + // query parameters + var assetId uint64 = 2044572 + var minBalance uint64 = 50 + + // Lookup accounts with minimum balance of asset + assetResult, err := indexerClient. + LookupAssetBalances(assetId). + CurrencyGreaterThan(minBalance). + Do(context.Background()) + + // Print the results + assetJson, err := json.MarshalIndent(assetResult, "", "\t") + fmt.Printf(string(assetJson) + "\n") + // example: INDEXER_LOOKUP_ASSET + + assetJson = nil + + // example: INDEXER_SEARCH_MIN_AMOUNT + // query parameters + var transactionMinAmount uint64 = 10 + + // Query + transactionResult, err := indexerClient. + SearchForTransactions(). + CurrencyGreaterThan(transactionMinAmount). + Do(context.Background()) + + // Print results + transactionJson, err := json.MarshalIndent(transactionResult, "", "\t") + fmt.Printf(string(transactionJson) + "\n") + // example: INDEXER_SEARCH_MIN_AMOUNT + + // example: INDEXER_PAGINATE_RESULTS + var nextToken = "" + var numTx = 1 + var numPages = 1 + var pagedMinAmount uint64 = 10 + var limit uint64 = 1 + + for numTx > 0 { + // Query + pagedResults, err := indexerClient. + SearchForTransactions(). + CurrencyGreaterThan(pagedMinAmount). + Limit(limit). + NextToken(nextToken). + Do(context.Background()) + if err != nil { + return + } + pagedTransactions := pagedResults.Transactions + numTx = len(pagedTransactions) + nextToken = pagedResults.NextToken + + if numTx > 0 { + // Print results + pagedJson, err := json.MarshalIndent(pagedTransactions, "", "\t") + if err != nil { + return + } + fmt.Printf(string(pagedJson) + "\n") + fmt.Println("End of page : ", numPages) + fmt.Println("Transaction printed : ", len(pagedTransactions)) + fmt.Println("Next Token : ", nextToken) + numPages++ + } + } + // example: INDEXER_PAGINATE_RESULTS + + // example: INDEXER_PREFIX_SEARCH + // Parameters + var notePrefix = "showing prefix" + + // Query + prefixResult, err := indexerClient. + SearchForTransactions(). + NotePrefix([]byte(notePrefix)). + Do(context.Background()) + + // Print results + prefixJson, err := json.MarshalIndent(prefixResult, "", "\t") + fmt.Printf(string(prefixJson) + "\n") + // example: INDEXER_PREFIX_SEARCH +} diff --git a/_examples/kmd.go b/_examples/kmd.go new file mode 100644 index 00000000..6fa724df --- /dev/null +++ b/_examples/kmd.go @@ -0,0 +1,229 @@ +package main + +import ( + "crypto/ed25519" + "fmt" + "strings" + + "github.com/algorand/go-algorand-sdk/v2/client/kmd" + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/mnemonic" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + var ( + exampleWalletID string + exampleWalletHandleToken string + genResponse kmd.GenerateKeyResponse + initResponse kmd.InitWalletHandleResponse + ) + + // example: KMD_CREATE_CLIENT + // Create a new kmd client, configured to connect to out local sandbox + var kmdAddress = "http://localhost:4002" + var kmdToken = strings.Repeat("a", 64) + kmdClient, err := kmd.MakeClient( + kmdAddress, + kmdToken, + ) + // example: KMD_CREATE_CLIENT + + if err != nil { + fmt.Printf("failed to make kmd client: %s\n", err) + return + } + + // example: KMD_CREATE_WALLET + // Create the example wallet, if it doesn't already exist + createResponse, err := kmdClient.CreateWallet( + "DemoWallet", + "password", + kmd.DefaultWalletDriver, + types.MasterDerivationKey{}, + ) + if err != nil { + fmt.Printf("error creating wallet: %s\n", err) + return + } + + // We need the wallet ID in order to get a wallet handle, so we can add accounts + exampleWalletID = createResponse.Wallet.ID + fmt.Printf("Created wallet '%s' with ID: %s\n", createResponse.Wallet.Name, exampleWalletID) + // example: KMD_CREATE_WALLET + + // example: KMD_CREATE_ACCOUNT + // Get a wallet handle. + initResponse, _ = kmdClient.InitWalletHandle( + exampleWalletID, + "password", + ) + exampleWalletHandleToken = initResponse.WalletHandleToken + + // Generate a new address from the wallet handle + genResponse, err = kmdClient.GenerateKey(exampleWalletHandleToken) + if err != nil { + fmt.Printf("Error generating key: %s\n", err) + return + } + accountAddress := genResponse.Address + fmt.Printf("New Account: %s\n", accountAddress) + // example: KMD_CREATE_ACCOUNT + + // example: KMD_EXPORT_ACCOUNT + // Extract the account sk + accountKeyResponse, err := kmdClient.ExportKey( + exampleWalletHandleToken, + "password", + accountAddress, + ) + accountKey := accountKeyResponse.PrivateKey + // Convert sk to mnemonic + mn, err := mnemonic.FromPrivateKey(accountKey) + if err != nil { + fmt.Printf("Error getting backup phrase: %s\n", err) + return + } + fmt.Printf("Account Mnemonic: %v ", mn) + // example: KMD_EXPORT_ACCOUNT + + // example: KMD_IMPORT_ACCOUNT + account := crypto.GenerateAccount() + fmt.Println("Account Address: ", account.Address) + mn, err = mnemonic.FromPrivateKey(account.PrivateKey) + if err != nil { + fmt.Printf("Error getting backup phrase: %s\n", err) + return + } + fmt.Printf("Account Mnemonic: %s\n", mn) + importedAccount, err := kmdClient.ImportKey( + exampleWalletHandleToken, + account.PrivateKey, + ) + fmt.Println("Account Successfully Imported: ", importedAccount.Address) + // example: KMD_IMPORT_ACCOUNT + + // Get the MDK for Recovery example + backupResponse, err := kmdClient.ExportMasterDerivationKey(exampleWalletHandleToken, "password") + if err != nil { + fmt.Printf("error exporting mdk: %s\n", err) + return + } + backupPhrase, _ := mnemonic.FromMasterDerivationKey(backupResponse.MasterDerivationKey) + fmt.Printf("Backup: %s\n", backupPhrase) + + // example: KMD_RECOVER_WALLET + keyBytes, err := mnemonic.ToKey(backupPhrase) + if err != nil { + fmt.Printf("failed to get key: %s\n", err) + return + } + + var mdk types.MasterDerivationKey + copy(mdk[:], keyBytes) + recoverResponse, err := kmdClient.CreateWallet( + "RecoveryWallet", + "password", + kmd.DefaultWalletDriver, + mdk, + ) + if err != nil { + fmt.Printf("error creating wallet: %s\n", err) + return + } + + // We need the wallet ID in order to get a wallet handle, so we can add accounts + exampleWalletID = recoverResponse.Wallet.ID + fmt.Printf("Created wallet '%s' with ID: %s\n", recoverResponse.Wallet.Name, exampleWalletID) + + // Get a wallet handle. The wallet handle is used for things like signing transactions + // and creating accounts. Wallet handles do expire, but they can be renewed + initResponse, err = kmdClient.InitWalletHandle( + exampleWalletID, + "password", + ) + if err != nil { + fmt.Printf("Error initializing wallet handle: %s\n", err) + return + } + + // Extract the wallet handle + exampleWalletHandleToken = initResponse.WalletHandleToken + fmt.Printf("Got wallet handle: '%s'\n", exampleWalletHandleToken) + + // Generate a new address from the wallet handle + genResponse, err = kmdClient.GenerateKey(exampleWalletHandleToken) + if err != nil { + fmt.Printf("Error generating key: %s\n", err) + return + } + fmt.Printf("Recovered address %s\n", genResponse.Address) + // example: KMD_RECOVER_WALLET + + // example: ACCOUNT_GENERATE + newAccount := crypto.GenerateAccount() + passphrase, err := mnemonic.FromPrivateKey(newAccount.PrivateKey) + + if err != nil { + fmt.Printf("Error creating transaction: %s\n", err) + } else { + fmt.Printf("My address: %s\n", newAccount.Address) + fmt.Printf("My passphrase: %s\n", passphrase) + } + // example: ACCOUNT_GENERATE + + // example: MULTISIG_CREATE + // Get pre-defined set of keys for example + _, pks := loadAccounts() + addr1, _ := types.DecodeAddress(pks[1]) + addr2, _ := types.DecodeAddress(pks[2]) + addr3, _ := types.DecodeAddress(pks[3]) + + ma, err := crypto.MultisigAccountWithParams(1, 2, []types.Address{ + addr1, + addr2, + addr3, + }) + + if err != nil { + panic("invalid multisig parameters") + } + fromAddr, _ := ma.Address() + // Print multisig account + fmt.Printf("Multisig address : %s \n", fromAddr) + // example: MULTISIG_CREATE +} + +// Accounts to be used through examples +func loadAccounts() (map[int][]byte, map[int]string) { + // Shown for demonstration purposes. NEVER reveal secret mnemonics in practice. + // Change these values to use the accounts created previously. + // Paste in mnemonic phrases for all three accounts + acc1 := crypto.GenerateAccount() + mnemonic1, _ := mnemonic.FromPrivateKey(acc1.PrivateKey) + acc2 := crypto.GenerateAccount() + mnemonic2, _ := mnemonic.FromPrivateKey(acc2.PrivateKey) + acc3 := crypto.GenerateAccount() + mnemonic3, _ := mnemonic.FromPrivateKey(acc3.PrivateKey) + + mnemonics := []string{mnemonic1, mnemonic2, mnemonic3} + pks := map[int]string{1: "", 2: "", 3: ""} + var sks = make(map[int][]byte) + + for i, m := range mnemonics { + var err error + sk, err := mnemonic.ToPrivateKey(m) + sks[i+1] = sk + if err != nil { + fmt.Printf("Issue with account %d private key conversion.", i+1) + } + // derive public address from Secret Key. + pk := sk.Public() + var a types.Address + cpk := pk.(ed25519.PublicKey) + copy(a[:], cpk[:]) + pks[i+1] = a.String() + fmt.Printf("Loaded Key %d: %s\n", i+1, pks[i+1]) + } + return sks, pks +} diff --git a/_examples/lsig.go b/_examples/lsig.go new file mode 100644 index 00000000..f6deb891 --- /dev/null +++ b/_examples/lsig.go @@ -0,0 +1,130 @@ +package main + +import ( + "context" + "encoding/base64" + "encoding/binary" + "io/ioutil" + "log" + + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + algodClient := getAlgodClient() + accts, err := getSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + seedAcct := accts[0] + + // example: LSIG_COMPILE + teal, err := ioutil.ReadFile("lsig/simple.teal") + if err != nil { + log.Fatalf("failed to read approval program: %s", err) + } + + result, err := algodClient.TealCompile(teal).Do(context.Background()) + if err != nil { + log.Fatalf("failed to compile program: %s", err) + } + + lsigBinary, err := base64.StdEncoding.DecodeString(result.Result) + if err != nil { + log.Fatalf("failed to decode compiled program: %s", err) + } + // example: LSIG_COMPILE + + // example: LSIG_INIT + lsig := crypto.LogicSigAccount{ + Lsig: types.LogicSig{Logic: lsigBinary, Args: nil}, + } + // example: LSIG_INIT + _ = lsig + + // example: LSIG_PASS_ARGS + encodedArg := make([]byte, 8) + binary.BigEndian.PutUint64(encodedArg, 123) + + lsigWithArgs := crypto.LogicSigAccount{ + Lsig: types.LogicSig{Logic: lsigBinary, Args: [][]byte{encodedArg}}, + } + // example: LSIG_PASS_ARGS + _ = lsigWithArgs + + // seed lsig so the pay from the lsig works + lsa, err := lsig.Address() + if err != nil { + log.Fatalf("failed to get lsig address: %s", err) + } + seedAddr := seedAcct.Address.String() + sp, _ := algodClient.SuggestedParams().Do(context.Background()) + txn, _ := transaction.MakePaymentTxn(seedAddr, lsa.String(), 1000000, nil, "", sp) + txid, stx, _ := crypto.SignTransaction(seedAcct.PrivateKey, txn) + algodClient.SendRawTransaction(stx).Do(context.Background()) + transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + + // example: LSIG_SIGN_FULL + sp, err = algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("failed to get suggested params: %s", err) + } + + lsigAddr, err := lsig.Address() + if err != nil { + log.Fatalf("failed to get lsig address: %s", err) + } + ptxn, err := transaction.MakePaymentTxn(lsigAddr.String(), seedAddr, 10000, nil, "", sp) + if err != nil { + log.Fatalf("failed to make transaction: %s", err) + } + txid, stxn, err := crypto.SignLogicSigAccountTransaction(lsig, ptxn) + if err != nil { + log.Fatalf("failed to sign transaction with lsig: %s", err) + } + _, err = algodClient.SendRawTransaction(stxn).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + payResult, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("failed while waiting for transaction: %s", err) + } + log.Printf("Lsig pay confirmed in round: %d", payResult.ConfirmedRound) + // example: LSIG_SIGN_FULL + + // example: LSIG_DELEGATE_FULL + // account signs the logic, and now the logic may be passed instead + // of a signature for a transaction + var args [][]byte + delSig, err := crypto.MakeLogicSigAccountDelegated(lsigBinary, args, seedAcct.PrivateKey) + if err != nil { + log.Fatalf("failed to make delegate lsig: %s", err) + } + + delSigPay, err := transaction.MakePaymentTxn(seedAddr, lsigAddr.String(), 10000, nil, "", sp) + if err != nil { + log.Fatalf("failed to make transaction: %s", err) + } + + delTxId, delStxn, err := crypto.SignLogicSigAccountTransaction(delSig, delSigPay) + if err != nil { + log.Fatalf("failed to sign with delegate sig: %s", err) + } + + _, err = algodClient.SendRawTransaction(delStxn).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + delPayResult, err := transaction.WaitForConfirmation(algodClient, delTxId, 4, context.Background()) + if err != nil { + log.Fatalf("failed while waiting for transaction: %s", err) + } + + log.Printf("Delegated Lsig pay confirmed in round: %d", delPayResult.ConfirmedRound) + // example: LSIG_DELEGATE_FULL +} diff --git a/_examples/lsig/sample_arg.teal b/_examples/lsig/sample_arg.teal new file mode 100644 index 00000000..8f21008e --- /dev/null +++ b/_examples/lsig/sample_arg.teal @@ -0,0 +1,5 @@ +#pragma version 5 +arg_0 +btoi +int 123 +== \ No newline at end of file diff --git a/_examples/lsig/simple.teal b/_examples/lsig/simple.teal new file mode 100644 index 00000000..d6298656 --- /dev/null +++ b/_examples/lsig/simple.teal @@ -0,0 +1,3 @@ +#pragma version 5 +int 1 +return \ No newline at end of file diff --git a/_examples/overview.go b/_examples/overview.go new file mode 100644 index 00000000..c3b64bb0 --- /dev/null +++ b/_examples/overview.go @@ -0,0 +1,70 @@ +package main + +import ( + "context" + "fmt" + "log" + "strings" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" + "github.com/algorand/go-algorand-sdk/v2/client/v2/common" + "github.com/algorand/go-algorand-sdk/v2/transaction" +) + +func main() { + + algodClient := getAlgodClient() + + nodeStatus, err := algodClient.Status().Do(context.Background()) + if err != nil { + fmt.Printf("Failed to get status: %s\n", err) + return + } + + fmt.Printf("Last Round: %d\n", nodeStatus.LastRound) + + // example: SP_MIN_FEE + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Printf("failed to %s", err) + } + // example: SP_MIN_FEE + + // example: CONST_MIN_FEE + log.Printf("Min fee const: %d", transaction.MinTxnFee) + // example: CONST_MIN_FEE + + // example: TRANSACTION_FEE_OVERRIDE + // by using fee pooling and setting our fee to 2x min tx fee + // we can cover the fee for another transaction in the group + sp.Fee = 2 * transaction.MinTxnFee + sp.FlatFee = true + // ... + // example: TRANSACTION_FEE_OVERRIDE + +} +func exampleAlgod() { + // example: ALGOD_CREATE_CLIENT + // Create a new algod client, configured to connect to out local sandbox + var algodAddress = "http://localhost:4001" + var algodToken = strings.Repeat("a", 64) + algodClient, _ := algod.MakeClient( + algodAddress, + algodToken, + ) + + // Or, if necessary, pass alternate headers + + var algodHeader common.Header + algodHeader.Key = "X-API-Key" + algodHeader.Value = algodToken + algodClientWithHeaders, _ := algod.MakeClientWithHeaders( + algodAddress, + algodToken, + []*common.Header{&algodHeader}, + ) + // example: ALGOD_CREATE_CLIENT + + _ = algodClientWithHeaders + _ = algodClient +} diff --git a/_examples/participation.go b/_examples/participation.go new file mode 100644 index 00000000..0c5afc10 --- /dev/null +++ b/_examples/participation.go @@ -0,0 +1,67 @@ +package main + +import ( + "context" + "fmt" + "strings" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" + "github.com/algorand/go-algorand-sdk/v2/transaction" +) + +func main() { + markOnline() +} + +func setupConnection() (c *algod.Client, err error) { + algod_token := strings.Repeat("a", 64) + algod_server := "http://127.0.0.1:4001" + algod_client, err := algod.MakeClient(algod_server, algod_token) + c = (*algod.Client)(algod_client) + return +} + +func markOnline() { + // setup connection + algodClient, err := setupConnection() + if err != nil { + fmt.Printf("error getting suggested tx params: %s\n", err) + return + } + + // get network suggested parameters + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + fmt.Printf("error getting suggested tx params: %s\n", err) + return + } + + // Mark Account as "Online" (participating) + // example: TRANSACTION_KEYREG_ONLINE_CREATE + fromAddr := "MWAPNXBDFFD2V5KWXAHWKBO7FO4JN36VR4CIBDKDDE7WAUAGZIXM3QPJW4" + voteKey := "87iBW46PP4BpTDz6+IEGvxY6JqEaOtV0g+VWcJqoqtc=" + selKey := "1V2BE2lbFvS937H7pJebN0zxkqe1Nrv+aVHDTPbYRlw=" + sProofKey := "f0CYOA4yXovNBFMFX+1I/tYVBaAl7VN6e0Ki5yZA3H6jGqsU/LYHNaBkMQ/rN4M4F3UmNcpaTmbVbq+GgDsrhQ==" + voteFirst := uint64(16532750) + voteLast := uint64(19532750) + keyDilution := uint64(1732) + nonpart := false + tx, err := transaction.MakeKeyRegTxnWithStateProofKey( + fromAddr, + []byte{}, + sp, + voteKey, + selKey, + sProofKey, + voteFirst, + voteLast, + keyDilution, + nonpart, + ) + // example: TRANSACTION_KEYREG_ONLINE_CREATE + if err != nil { + fmt.Printf("Error creating transaction: %s\n", err) + return + } + _ = tx +} diff --git a/_examples/util.go b/_examples/util.go new file mode 100644 index 00000000..0c9e40b8 --- /dev/null +++ b/_examples/util.go @@ -0,0 +1,182 @@ +package main + +import ( + "context" + "encoding/base64" + "fmt" + "io/ioutil" + "log" + "strings" + + "github.com/algorand/go-algorand-sdk/v2/client/kmd" + "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +// add sandbox and other stuff +var ( + ALGOD_ADDRESS = "http://localhost:4001" + ALGOD_TOKEN = strings.Repeat("a", 64) + + KMD_ADDRESS = "http://localhost:4002" + KMD_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + KMD_WALLET_NAME = "unencrypted-default-wallet" + KMD_WALLET_PASSWORD = "" +) + +func getAlgodClient() *algod.Client { + algodClient, err := algod.MakeClient( + ALGOD_ADDRESS, + ALGOD_TOKEN, + ) + + if err != nil { + log.Fatalf("Failed to create algod client: %s", err) + } + + return algodClient +} + +func getSandboxAccounts() ([]crypto.Account, error) { + client, err := kmd.MakeClient(KMD_ADDRESS, KMD_TOKEN) + if err != nil { + return nil, fmt.Errorf("Failed to create client: %+v", err) + } + + resp, err := client.ListWallets() + if err != nil { + return nil, fmt.Errorf("Failed to list wallets: %+v", err) + } + + var walletId string + for _, wallet := range resp.Wallets { + if wallet.Name == KMD_WALLET_NAME { + walletId = wallet.ID + } + } + + if walletId == "" { + return nil, fmt.Errorf("No wallet named %s", KMD_WALLET_NAME) + } + + whResp, err := client.InitWalletHandle(walletId, KMD_WALLET_PASSWORD) + if err != nil { + return nil, fmt.Errorf("Failed to init wallet handle: %+v", err) + } + + addrResp, err := client.ListKeys(whResp.WalletHandleToken) + if err != nil { + return nil, fmt.Errorf("Failed to list keys: %+v", err) + } + + var accts []crypto.Account + for _, addr := range addrResp.Addresses { + expResp, err := client.ExportKey(whResp.WalletHandleToken, KMD_WALLET_PASSWORD, addr) + if err != nil { + return nil, fmt.Errorf("Failed to export key: %+v", err) + } + + acct, err := crypto.AccountFromPrivateKey(expResp.PrivateKey) + if err != nil { + return nil, fmt.Errorf("Failed to create account from private key: %+v", err) + } + + accts = append(accts, acct) + } + + return accts, nil +} + +func compileTeal(algodClient *algod.Client, path string) []byte { + teal, err := ioutil.ReadFile(path) + if err != nil { + log.Fatalf("failed to read approval program: %s", err) + } + + result, err := algodClient.TealCompile(teal).Do(context.Background()) + if err != nil { + log.Fatalf("failed to compile program: %s", err) + } + + bin, err := base64.StdEncoding.DecodeString(result.Result) + if err != nil { + log.Fatalf("failed to decode compiled program: %s", err) + } + return bin +} + +func deployApp(algodClient *algod.Client, creator crypto.Account) uint64 { + + var ( + approvalBinary = make([]byte, 1000) + clearBinary = make([]byte, 1000) + ) + + // Compile approval program + approvalTeal, err := ioutil.ReadFile("calculator/approval.teal") + if err != nil { + log.Fatalf("failed to read approval program: %s", err) + } + + approvalResult, err := algodClient.TealCompile(approvalTeal).Do(context.Background()) + if err != nil { + log.Fatalf("failed to compile program: %s", err) + } + + _, err = base64.StdEncoding.Decode(approvalBinary, []byte(approvalResult.Result)) + if err != nil { + log.Fatalf("failed to decode compiled program: %s", err) + } + + // Compile clear program + clearTeal, err := ioutil.ReadFile("calculator/clear.teal") + if err != nil { + log.Fatalf("failed to read clear program: %s", err) + } + + clearResult, err := algodClient.TealCompile(clearTeal).Do(context.Background()) + if err != nil { + log.Fatalf("failed to compile program: %s", err) + } + + _, err = base64.StdEncoding.Decode(clearBinary, []byte(clearResult.Result)) + if err != nil { + log.Fatalf("failed to decode compiled program: %s", err) + } + + // Create application + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakeApplicationCreateTx( + false, approvalBinary, clearBinary, + types.StateSchema{}, types.StateSchema{}, + nil, nil, nil, nil, + sp, creator.Address, nil, + types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + return confirmedTxn.ApplicationIndex +} From 055c8d2b174ac8638ff6ea3f3f82ab51ff34b0fb Mon Sep 17 00:00:00 2001 From: Eric Warehime Date: Fri, 24 Mar 2023 13:03:19 -0700 Subject: [PATCH 09/16] Fix extractError parsing (#492) --- client/v2/common/common.go | 2 +- client/v2/common/common_test.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/client/v2/common/common.go b/client/v2/common/common.go index 88357347..f3987615 100644 --- a/client/v2/common/common.go +++ b/client/v2/common/common.go @@ -73,7 +73,7 @@ type InternalError error // If so, it returns the error. // Otherwise, it returns nil. func extractError(code int, errorBuf []byte) error { - if code == 200 { + if code >= 200 && code < 300 { return nil } diff --git a/client/v2/common/common_test.go b/client/v2/common/common_test.go index 8fdbe6e8..a9a27bb1 100644 --- a/client/v2/common/common_test.go +++ b/client/v2/common/common_test.go @@ -2,6 +2,7 @@ package common import ( "context" + "fmt" "net/http" "net/http/httptest" "testing" @@ -10,6 +11,27 @@ import ( "github.com/stretchr/testify/require" ) +func TestExtractError(t *testing.T) { + testcases := []struct { + name string + code int + err error + }{ + {name: "400", code: 400, err: BadRequest(fmt.Errorf("HTTP 400: "))}, + {name: "401", code: 401, err: InvalidToken(fmt.Errorf("HTTP 401: "))}, + {name: "404", code: 404, err: NotFound(fmt.Errorf("HTTP 404: "))}, + {name: "500", code: 500, err: InternalError(fmt.Errorf("HTTP 500: "))}, + {name: "200", code: 200, err: nil}, + {name: "201", code: 201, err: nil}, + } + + for _, tc := range testcases { + t.Run(tc.name, func(t *testing.T) { + assert.Equal(t, tc.err, extractError(tc.code, []byte{})) + }) + } +} + func TestClient_Verbs(t *testing.T) { path := "/some/path" From 721d2bba434d657193a8662904598bcdef3e8de0 Mon Sep 17 00:00:00 2001 From: Ben Guidarelli Date: Wed, 5 Apr 2023 10:12:07 -0400 Subject: [PATCH 10/16] Docs: Examples (#491) --- Makefile | 3 + examples/README.md | 10 + examples/account/main.go | 91 ++++ examples/application/approval.teal | 100 ++++ examples/application/approval_refactored.teal | 107 +++++ examples/application/clear.teal | 3 + examples/apps/main.go | 447 ++++++++++++++++++ examples/asa/main.go | 373 +++++++++++++++ examples/atc/main.go | 113 +++++ examples/atomic_transactions/main.go | 82 ++++ examples/calculator/approval.teal | 181 +++++++ examples/calculator/clear.teal | 3 + examples/calculator/contract.json | 74 +++ examples/codec/main.go | 110 +++++ examples/debug/main.go | 83 ++++ examples/gen-addresses/main.go | 152 ------ examples/indexer/main.go | 139 ++++++ examples/kmd/main.go | 232 +++++++++ examples/lsig/main.go | 131 +++++ examples/lsig/sample_arg.teal | 5 + examples/lsig/simple.teal | 3 + examples/overview/main.go | 115 +++++ examples/participation/main.go | 57 +++ examples/smoke_test.sh | 19 + examples/utils.go | 233 +++++++++ test/docker/Dockerfile | 2 +- 26 files changed, 2715 insertions(+), 153 deletions(-) create mode 100644 examples/README.md create mode 100644 examples/account/main.go create mode 100644 examples/application/approval.teal create mode 100644 examples/application/approval_refactored.teal create mode 100644 examples/application/clear.teal create mode 100644 examples/apps/main.go create mode 100644 examples/asa/main.go create mode 100644 examples/atc/main.go create mode 100644 examples/atomic_transactions/main.go create mode 100644 examples/calculator/approval.teal create mode 100644 examples/calculator/clear.teal create mode 100644 examples/calculator/contract.json create mode 100644 examples/codec/main.go create mode 100644 examples/debug/main.go delete mode 100644 examples/gen-addresses/main.go create mode 100644 examples/indexer/main.go create mode 100644 examples/kmd/main.go create mode 100644 examples/lsig/main.go create mode 100644 examples/lsig/sample_arg.teal create mode 100644 examples/lsig/simple.teal create mode 100644 examples/overview/main.go create mode 100644 examples/participation/main.go create mode 100755 examples/smoke_test.sh create mode 100644 examples/utils.go diff --git a/Makefile b/Makefile index 698e71bb..239a3a85 100644 --- a/Makefile +++ b/Makefile @@ -45,6 +45,9 @@ docker-gosdk-run: docker ps -a docker run -it --network host go-sdk-testing:latest +smoke-test-examples: + cd "$(SRCPATH)/examples" && bash smoke_test.sh && cd - + docker-test: harness docker-gosdk-build docker-gosdk-run diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..0d60aacb --- /dev/null +++ b/examples/README.md @@ -0,0 +1,10 @@ +Algorand Go SDK Examples +----------------------- + +This directory contains examples of how to use the Algorand Go SDK. + +Assuming a sandbox node is running locally, any example can be run with the following command: + +```sh +go run /main.go +``` diff --git a/examples/account/main.go b/examples/account/main.go new file mode 100644 index 00000000..263a0cba --- /dev/null +++ b/examples/account/main.go @@ -0,0 +1,91 @@ +package main + +import ( + "context" + "fmt" + "log" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/examples" + "github.com/algorand/go-algorand-sdk/v2/mnemonic" + "github.com/algorand/go-algorand-sdk/v2/transaction" +) + +func main() { + // example: ACCOUNT_GENERATE + account := crypto.GenerateAccount() + mn, err := mnemonic.FromPrivateKey(account.PrivateKey) + + if err != nil { + log.Fatalf("failed to generate account: %s", err) + } + + log.Printf("Address: %s\n", account.Address) + log.Printf("Mnemonic: %s\n", mn) + // example: ACCOUNT_GENERATE + + // example: ACCOUNT_RECOVER_MNEMONIC + k, err := mnemonic.ToPrivateKey(mn) + if err != nil { + log.Fatalf("failed to parse mnemonic: %s", err) + } + + recovered, err := crypto.AccountFromPrivateKey(k) + if err != nil { + log.Fatalf("failed to recover account from key: %s", err) + } + + log.Printf("%+v", recovered) + // example: ACCOUNT_RECOVER_MNEMONIC + accts, err := examples.GetSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + rekeyAccount(examples.GetAlgodClient(), accts[0], accts[1]) +} + +func rekeyAccount(algodClient *algod.Client, acct crypto.Account, rekeyTarget crypto.Account) { + // example: ACCOUNT_REKEY + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("failed to get suggested params: %s", err) + } + + addr := acct.Address.String() + // here we create a payment transaction but rekey is valid + // on any transaction type + rktxn, err := transaction.MakePaymentTxn(addr, addr, 0, nil, "", sp) + if err != nil { + log.Fatalf("failed to creating transaction: %s\n", err) + } + // Set the rekey parameter + rktxn.RekeyTo = rekeyTarget.Address + + _, stxn, err := crypto.SignTransaction(acct.PrivateKey, rktxn) + if err != nil { + fmt.Printf("Failed to sign transaction: %s\n", err) + } + + txID, err := algodClient.SendRawTransaction(stxn).Do(context.Background()) + if err != nil { + fmt.Printf("failed to send transaction: %s\n", err) + return + } + + result, err := transaction.WaitForConfirmation(algodClient, txID, 4, context.Background()) + if err != nil { + fmt.Printf("Error waiting for confirmation on txID: %s\n", txID) + return + } + + fmt.Printf("Confirmed Transaction: %s in Round %d\n", txID, result.ConfirmedRound) + // example: ACCOUNT_REKEY + + // rekey back + rktxn, _ = transaction.MakePaymentTxn(addr, addr, 0, nil, "", sp) + rktxn.RekeyTo = acct.Address + _, stxn, _ = crypto.SignTransaction(rekeyTarget.PrivateKey, rktxn) + txID, _ = algodClient.SendRawTransaction(stxn).Do(context.Background()) + result, _ = transaction.WaitForConfirmation(algodClient, txID, 4, context.Background()) +} diff --git a/examples/application/approval.teal b/examples/application/approval.teal new file mode 100644 index 00000000..eabde624 --- /dev/null +++ b/examples/application/approval.teal @@ -0,0 +1,100 @@ +#pragma version 4 +// Handle each possible OnCompletion type. We don't have to worry about +// handling ClearState, because the ClearStateProgram will execute in that +// case, not the ApprovalProgram. +txn ApplicationID +int 0 +== +bnz handle_approve + +txn OnCompletion +int NoOp +== +bnz handle_noop + +txn OnCompletion +int OptIn +== +bnz handle_approve + +txn OnCompletion +int CloseOut +== +bnz handle_closeout + +txn OnCompletion +int UpdateApplication +== +bnz handle_updateapp + +txn OnCompletion +int DeleteApplication +== +bnz handle_deleteapp + +// Unexpected OnCompletion value. Should be unreachable. +err + +handle_noop: +// Handle NoOp + +// read global state +byte "counter" +dup +app_global_get + +// increment the value +int 1 ++ + +// store to scratch space +dup +store 0 + +// update global state +app_global_put + +// read local state for sender +int 0 +byte "counter" +app_local_get + +// increment the value +int 1 ++ +store 1 + +// update local state for sender +int 0 +byte "counter" +load 1 +app_local_put + +// load return value as approval +load 0 +return + + +handle_closeout: +// Handle CloseOut +//approval +int 1 +return + +handle_deleteapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_updateapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_approve: +int 1 +return diff --git a/examples/application/approval_refactored.teal b/examples/application/approval_refactored.teal new file mode 100644 index 00000000..1af5a7eb --- /dev/null +++ b/examples/application/approval_refactored.teal @@ -0,0 +1,107 @@ +#pragma version 4 +// Handle each possible OnCompletion type. We don't have to worry about +// handling ClearState, because the ClearStateProgram will execute in that +// case, not the ApprovalProgram. + +txn ApplicationID +int 0 +== +bnz handle_approve + +txn OnCompletion +int NoOp +== +bnz handle_noop + +txn OnCompletion +int OptIn +== +bnz handle_approve + +txn OnCompletion +int CloseOut +== +bnz handle_closeout + +txn OnCompletion +int UpdateApplication +== +bnz handle_updateapp + +txn OnCompletion +int DeleteApplication +== +bnz handle_deleteapp + +// Unexpected OnCompletion value. Should be unreachable. +err + +handle_noop: +// Handle NoOp + +// read global state +byte "counter" +dup +app_global_get + +// increment the value +int 1 ++ + +// store to scratch space +dup +store 0 + +// update global state +app_global_put + +// read local state for sender +int 0 +byte "counter" +app_local_get + +// increment the value +int 1 ++ +store 1 + +// update local state for sender +// update "counter" +int 0 +byte "counter" +load 1 +app_local_put + +// update "timestamp" +int 0 +byte "timestamp" +txn ApplicationArgs 0 +app_local_put + +// load return value as approval +load 0 +return + +handle_closeout: +// Handle CloseOut +//approval +int 1 +return + +handle_deleteapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_updateapp: +// Check for creator +global CreatorAddress +txn Sender +== +return + +handle_approve: +int 1 +return \ No newline at end of file diff --git a/examples/application/clear.teal b/examples/application/clear.teal new file mode 100644 index 00000000..d793651c --- /dev/null +++ b/examples/application/clear.teal @@ -0,0 +1,3 @@ +#pragma version 4 +int 1 +return \ No newline at end of file diff --git a/examples/apps/main.go b/examples/apps/main.go new file mode 100644 index 00000000..092c4d6f --- /dev/null +++ b/examples/apps/main.go @@ -0,0 +1,447 @@ +package main + +import ( + "context" + "encoding/base64" + "io/ioutil" + "log" + "time" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/examples" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + + algodClient := examples.GetAlgodClient() + accts, err := examples.GetSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + acct1 := accts[0] + appID := appCreate(algodClient, acct1) + appOptIn(algodClient, appID, acct1) + + // example: APP_READ_STATE + // grab global state and config of application + appInfo, err := algodClient.GetApplicationByID(appID).Do(context.Background()) + if err != nil { + log.Fatalf("failed to get app info: %s", err) + } + log.Printf("app info: %+v", appInfo) + + // grab local state for an app id for a single account + acctInfo, err := algodClient.AccountApplicationInformation( + acct1.Address.String(), appID, + ).Do(context.Background()) + if err != nil { + log.Fatalf("failed to get app info: %s", err) + } + log.Printf("app info: %+v", acctInfo) + // example: APP_READ_STATE + + appNoOp(algodClient, appID, acct1) + appUpdate(algodClient, appID, acct1) + appCall(algodClient, appID, acct1) + appCloseOut(algodClient, appID, acct1) + appDelete(algodClient, appID, acct1) + +} + +func appCreate(algodClient *algod.Client, creator crypto.Account) uint64 { + // example: APP_SCHEMA + // declare application state storage (immutable) + var ( + localInts uint64 = 1 + localBytes uint64 = 1 + globalInts uint64 = 1 + globalBytes uint64 = 0 + ) + + // define schema + globalSchema := types.StateSchema{NumUint: globalInts, NumByteSlice: globalBytes} + localSchema := types.StateSchema{NumUint: localInts, NumByteSlice: localBytes} + // example: APP_SCHEMA + + // example: APP_SOURCE + approvalTeal, err := ioutil.ReadFile("application/approval.teal") + if err != nil { + log.Fatalf("failed to read approval program: %s", err) + } + clearTeal, err := ioutil.ReadFile("application/clear.teal") + if err != nil { + log.Fatalf("failed to read clear program: %s", err) + } + // example: APP_SOURCE + + // example: APP_COMPILE + approvalResult, err := algodClient.TealCompile(approvalTeal).Do(context.Background()) + if err != nil { + log.Fatalf("failed to compile program: %s", err) + } + + approvalBinary, err := base64.StdEncoding.DecodeString(approvalResult.Result) + if err != nil { + log.Fatalf("failed to decode compiled program: %s", err) + } + + clearResult, err := algodClient.TealCompile(clearTeal).Do(context.Background()) + if err != nil { + log.Fatalf("failed to compile program: %s", err) + } + + clearBinary, err := base64.StdEncoding.DecodeString(clearResult.Result) + if err != nil { + log.Fatalf("failed to decode compiled program: %s", err) + } + // example: APP_COMPILE + + // example: APP_CREATE + // Create application + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakeApplicationCreateTx( + false, approvalBinary, clearBinary, globalSchema, localSchema, + nil, nil, nil, nil, sp, creator.Address, nil, + types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + appID := confirmedTxn.ApplicationIndex + log.Printf("Created app with id: %d", appID) + // example: APP_CREATE + return appID +} + +func appOptIn(algodClient *algod.Client, appID uint64, caller crypto.Account) { + // example: APP_OPTIN + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + // Create a new clawback transaction with the target of the user address and the recipient as the creator + // address, being sent from the address marked as `clawback` on the asset, in this case the same as creator + txn, err := transaction.MakeApplicationOptInTx( + appID, nil, nil, nil, nil, sp, + caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("OptIn Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_OPTIN +} + +func appNoOp(algodClient *algod.Client, appID uint64, caller crypto.Account) { + // example: APP_NOOP + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + var ( + appArgs [][]byte + accts []string + apps []uint64 + assets []uint64 + ) + + // Add an arg to our app call + appArgs = append(appArgs, []byte("arg0")) + + txn, err := transaction.MakeApplicationNoOpTx( + appID, appArgs, accts, apps, assets, sp, + caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("NoOp Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_NOOP +} + +func appUpdate(algodClient *algod.Client, appID uint64, caller crypto.Account) { + approvalBinary := examples.CompileTeal(algodClient, "application/approval_refactored.teal") + clearBinary := examples.CompileTeal(algodClient, "application/clear.teal") + + // example: APP_UPDATE + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + var ( + appArgs [][]byte + accts []string + apps []uint64 + assets []uint64 + ) + + txn, err := transaction.MakeApplicationUpdateTx( + appID, appArgs, accts, apps, assets, approvalBinary, clearBinary, + sp, caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Update Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_UPDATE +} + +func appCloseOut(algodClient *algod.Client, appID uint64, caller crypto.Account) { + // example: APP_CLOSEOUT + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + var ( + appArgs [][]byte + accts []string + apps []uint64 + assets []uint64 + ) + + txn, err := transaction.MakeApplicationCloseOutTx( + appID, appArgs, accts, apps, assets, sp, + caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Closeout Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_CLOSEOUT +} + +func appClearState(algodClient *algod.Client, appID uint64, caller crypto.Account) { + // example: APP_CLEAR + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + var ( + appArgs [][]byte + accts []string + apps []uint64 + assets []uint64 + ) + + txn, err := transaction.MakeApplicationClearStateTx( + appID, appArgs, accts, apps, assets, sp, + caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("ClearState Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_CLEAR +} + +func appCall(algodClient *algod.Client, appID uint64, caller crypto.Account) { + // example: APP_CALL + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + var ( + appArgs [][]byte + accts []string + apps []uint64 + assets []uint64 + ) + + datetime := time.Now().Format("2006-01-02 at 15:04:05") + appArgs = append(appArgs, []byte(datetime)) + + txn, err := transaction.MakeApplicationNoOpTx( + appID, appArgs, accts, apps, assets, sp, + caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("NoOp Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_CALL +} + +func appDelete(algodClient *algod.Client, appID uint64, caller crypto.Account) { + // example: APP_DELETE + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + var ( + appArgs [][]byte + accts []string + apps []uint64 + assets []uint64 + ) + + txn, err := transaction.MakeApplicationDeleteTx( + appID, appArgs, accts, apps, assets, sp, + caller.Address, nil, types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(caller.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Delete Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: APP_DELETE +} diff --git a/examples/asa/main.go b/examples/asa/main.go new file mode 100644 index 00000000..2787a7a0 --- /dev/null +++ b/examples/asa/main.go @@ -0,0 +1,373 @@ +package main + +import ( + "context" + "log" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/examples" + "github.com/algorand/go-algorand-sdk/v2/transaction" +) + +func main() { + + algodClient := examples.GetAlgodClient() + accts, err := examples.GetSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + + creator := accts[0] + user := accts[1] + + assetID := createAsset(algodClient, creator) + // example: ASSET_INFO + info, err := algodClient.GetAssetByID(assetID).Do(context.Background()) + if err != nil { + log.Fatalf("failed to get asset info: %s", err) + } + log.Printf("Asset info for %d: %+v", assetID, info) + // example: ASSET_INFO + + configureAsset(algodClient, assetID, creator) + optInAsset(algodClient, assetID, user) + xferAsset(algodClient, assetID, creator, user) + freezeAsset(algodClient, assetID, creator, user) + clawbackAsset(algodClient, assetID, creator, user) + deleteAsset(algodClient, assetID, creator) +} + +func createAsset(algodClient *algod.Client, creator crypto.Account) uint64 { + // example: ASSET_CREATE + // Configure parameters for asset creation + var ( + creatorAddr = creator.Address.String() + assetName = "Really Useful Gift" + unitName = "rug" + assetURL = "https://path/to/my/asset/details" + assetMetadataHash = "thisIsSomeLength32HashCommitment" + defaultFrozen = false + decimals = uint32(0) + totalIssuance = uint64(1000) + + manager = creatorAddr + reserve = creatorAddr + freeze = creatorAddr + clawback = creatorAddr + + note []byte + ) + + // Get network-related transaction parameters and assign + txParams, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + // Construct the transaction + txn, err := transaction.MakeAssetCreateTxn( + creatorAddr, note, txParams, totalIssuance, decimals, + defaultFrozen, manager, reserve, freeze, clawback, + unitName, assetName, assetURL, assetMetadataHash, + ) + + if err != nil { + log.Fatalf("failed to make transaction: %s", err) + } + + // sign the transaction + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Create Transaction: %s confirmed in Round %d with new asset id: %d\n", + txid, confirmedTxn.ConfirmedRound, confirmedTxn.AssetIndex) + // example: ASSET_CREATE + return confirmedTxn.AssetIndex +} + +func configureAsset(algodClient *algod.Client, assetID uint64, creator crypto.Account) { + // example: ASSET_CONFIG + creatorAddr := creator.Address.String() + var ( + newManager = creatorAddr + newFreeze = creatorAddr + newClawback = creatorAddr + newReserve = "" + + strictAddrCheck = false + note []byte + ) + + // Get network-related transaction parameters and assign + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakeAssetConfigTxn(creatorAddr, note, sp, assetID, newManager, newReserve, newFreeze, newClawback, strictAddrCheck) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Asset Config Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_CONFIG +} + +func optInAsset(algodClient *algod.Client, assetID uint64, user crypto.Account) { + // example: ASSET_OPTIN + userAddr := user.Address.String() + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakeAssetAcceptanceTxn(userAddr, nil, sp, assetID) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(user.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("OptIn Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_OPTIN +} + +func optOutAsset(algodClient *algod.Client, assetID uint64, creator, user crypto.Account) { + // example: ASSET_OPT_OUT + userAddr := user.Address.String() + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakeAssetTransferTxn(userAddr, creator.Address.String(), 0, nil, sp, creator.Address.String(), assetID) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(user.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("OptOut Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_OPT_OUT +} + +func xferAsset(algodClient *algod.Client, assetID uint64, creator crypto.Account, user crypto.Account) { + // example: ASSET_XFER + var ( + creatorAddr = creator.Address.String() + userAddr = user.Address.String() + ) + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakeAssetTransferTxn(creatorAddr, userAddr, 1, nil, sp, "", assetID) + if err != nil { + log.Fatalf("failed to make asset txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Asset Transfer Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_XFER +} + +func freezeAsset(algodClient *algod.Client, assetID uint64, creator crypto.Account, user crypto.Account) { + // example: ASSET_FREEZE + var ( + creatorAddr = creator.Address.String() + userAddr = user.Address.String() + ) + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + // Create a freeze asset transaction with the target of the user address + // and the new freeze setting of `true` + txn, err := transaction.MakeAssetFreezeTxn(creatorAddr, nil, sp, assetID, userAddr, true) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Freeze Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_FREEZE +} + +func clawbackAsset(algodClient *algod.Client, assetID uint64, creator crypto.Account, user crypto.Account) { + // example: ASSET_CLAWBACK + var ( + creatorAddr = creator.Address.String() + userAddr = user.Address.String() + ) + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + // Create a new clawback transaction with the target of the user address and the recipient as the creator + // address, being sent from the address marked as `clawback` on the asset, in this case the same as creator + txn, err := transaction.MakeAssetRevocationTxn(creatorAddr, userAddr, 1, creatorAddr, nil, sp, assetID) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Clawback Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_CLAWBACK +} + +func deleteAsset(algodClient *algod.Client, assetID uint64, creator crypto.Account) { + // example: ASSET_DELETE + var ( + creatorAddr = creator.Address.String() + ) + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + // Create a new clawback transaction with the target of the user address and the recipient as the creator + // address, being sent from the address marked as `clawback` on the asset, in this case the same as creator + txn, err := transaction.MakeAssetDestroyTxn(creatorAddr, nil, sp, assetID) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + // sign the transaction + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Broadcast the transaction to the network + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + // Wait for confirmation + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + log.Printf("Destroy Transaction: %s confirmed in Round %d\n", txid, confirmedTxn.ConfirmedRound) + // example: ASSET_DELETE +} diff --git a/examples/atc/main.go b/examples/atc/main.go new file mode 100644 index 00000000..d0184ec0 --- /dev/null +++ b/examples/atc/main.go @@ -0,0 +1,113 @@ +package main + +import ( + "context" + "encoding/json" + "io/ioutil" + "log" + + "github.com/algorand/go-algorand-sdk/v2/abi" + "github.com/algorand/go-algorand-sdk/v2/examples" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + algodClient := examples.GetAlgodClient() + accts, err := examples.GetSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + + acct1 := accts[0] + + appID := examples.DeployApp(algodClient, acct1) + log.Printf("%d", appID) + + // example: ATC_CONTRACT_INIT + b, err := ioutil.ReadFile("calculator/contract.json") + if err != nil { + log.Fatalf("failed to read contract file: %s", err) + } + + contract := &abi.Contract{} + if err := json.Unmarshal(b, contract); err != nil { + log.Fatalf("failed to unmarshal contract: %s", err) + } + // example: ATC_CONTRACT_INIT + + // example: ATC_CREATE + // Create the atc we'll use to compose our transaction group + var atc = transaction.AtomicTransactionComposer{} + // example: ATC_CREATE + + // example: ATC_ADD_TRANSACTION + // Get suggested params and make a transaction as usual + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakePaymentTxn(acct1.Address.String(), acct1.Address.String(), 10000, nil, "", sp) + if err != nil { + log.Fatalf("failed to make transaction: %s", err) + } + + // Construct a TransactionWithSigner and pass it to the atc + signer := transaction.BasicAccountTransactionSigner{Account: acct1} + atc.AddTransaction(transaction.TransactionWithSigner{Txn: txn, Signer: signer}) + // example: ATC_ADD_TRANSACTION + + // example: ATC_ADD_METHOD_CALL + // Grab the method from out contract object + addMethod, err := contract.GetMethodByName("add") + if err != nil { + log.Fatalf("failed to get add method: %s", err) + } + + // Set up method call params + mcp := transaction.AddMethodCallParams{ + AppID: appID, + Sender: acct1.Address, + SuggestedParams: sp, + OnComplete: types.NoOpOC, + Signer: signer, + Method: addMethod, + MethodArgs: []interface{}{1, 1}, + } + if err := atc.AddMethodCall(mcp); err != nil { + log.Fatalf("failed to add method call: %s", err) + } + // example: ATC_ADD_METHOD_CALL + + // example: ATC_RESULTS + result, err := atc.Execute(algodClient, context.Background(), 4) + if err != nil { + log.Fatalf("failed to get add method: %s", err) + } + + for _, r := range result.MethodResults { + log.Printf("%s => %v", r.Method.Name, r.ReturnValue) + } + // example: ATC_RESULTS + + // example: ATC_BOX_REF + boxName := "coolBoxName" + mcp = transaction.AddMethodCallParams{ + AppID: appID, + Sender: acct1.Address, + SuggestedParams: sp, + OnComplete: types.NoOpOC, + Signer: signer, + Method: addMethod, + MethodArgs: []interface{}{1, 1}, + // Here we're passing a box reference so our app + // can reference it during evaluation + BoxReferences: []types.AppBoxReference{ + {AppID: appID, Name: []byte(boxName)}, + }, + } + // ... + // example: ATC_BOX_REF + +} diff --git a/examples/atomic_transactions/main.go b/examples/atomic_transactions/main.go new file mode 100644 index 00000000..3d0f5815 --- /dev/null +++ b/examples/atomic_transactions/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "context" + "fmt" + "log" + + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/examples" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + algodClient := examples.GetAlgodClient() + accts, err := examples.GetSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + + acct1 := accts[0] + acct2 := accts[1] + + // example: ATOMIC_CREATE_TXNS + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("failed to get suggested params: %s", err) + } + + tx1, err := transaction.MakePaymentTxn(acct1.Address.String(), acct2.Address.String(), 100000, nil, "", sp) + if err != nil { + log.Fatalf("failed creating transaction: %s", err) + } + + // from account 2 to account 1 + tx2, err := transaction.MakePaymentTxn(acct2.Address.String(), acct1.Address.String(), 100000, nil, "", sp) + if err != nil { + log.Fatalf("failed creating transaction: %s", err) + } + // example: ATOMIC_CREATE_TXNS + + // example: ATOMIC_GROUP_TXNS + // compute group id and put it into each transaction + gid, _ := crypto.ComputeGroupID([]types.Transaction{tx1, tx2}) + tx1.Group = gid + tx2.Group = gid + // example: ATOMIC_GROUP_TXNS + + // example: ATOMIC_GROUP_SIGN + _, stx1, err := crypto.SignTransaction(acct1.PrivateKey, tx1) + if err != nil { + fmt.Printf("Failed to sign transaction: %s\n", err) + return + } + _, stx2, err := crypto.SignTransaction(acct2.PrivateKey, tx2) + if err != nil { + fmt.Printf("Failed to sign transaction: %s\n", err) + } + // example: ATOMIC_GROUP_SIGN + + // example: ATOMIC_GROUP_ASSEMBLE + var signedGroup []byte + signedGroup = append(signedGroup, stx1...) + signedGroup = append(signedGroup, stx2...) + + // example: ATOMIC_GROUP_ASSEMBLE + + // example: ATOMIC_GROUP_SEND + pendingTxID, err := algodClient.SendRawTransaction(signedGroup).Do(context.Background()) + if err != nil { + fmt.Printf("failed to send transaction: %s\n", err) + return + } + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, pendingTxID, 4, context.Background()) + if err != nil { + fmt.Printf("Error waiting for confirmation on txID: %s\n", pendingTxID) + return + } + fmt.Printf("Confirmed Transaction: %s in Round %d\n", pendingTxID, confirmedTxn.ConfirmedRound) + // example: ATOMIC_GROUP_SEND + +} diff --git a/examples/calculator/approval.teal b/examples/calculator/approval.teal new file mode 100644 index 00000000..32acbb84 --- /dev/null +++ b/examples/calculator/approval.teal @@ -0,0 +1,181 @@ +#pragma version 8 +intcblock 0 1 +bytecblock 0x151f7c75 +txn NumAppArgs +intc_0 // 0 +== +bnz main_l10 +txna ApplicationArgs 0 +pushbytes 0xfe6bdf69 // "add(uint64,uint64)uint64" +== +bnz main_l9 +txna ApplicationArgs 0 +pushbytes 0xe2f188c5 // "mul(uint64,uint64)uint64" +== +bnz main_l8 +txna ApplicationArgs 0 +pushbytes 0x78b488b7 // "sub(uint64,uint64)uint64" +== +bnz main_l7 +txna ApplicationArgs 0 +pushbytes 0x16e80f08 // "div(uint64,uint64)uint64" +== +bnz main_l6 +err +main_l6: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 9 +txna ApplicationArgs 2 +btoi +store 10 +load 9 +load 10 +callsub div_3 +store 11 +bytec_0 // 0x151f7c75 +load 11 +itob +concat +log +intc_1 // 1 +return +main_l7: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 6 +txna ApplicationArgs 2 +btoi +store 7 +load 6 +load 7 +callsub sub_2 +store 8 +bytec_0 // 0x151f7c75 +load 8 +itob +concat +log +intc_1 // 1 +return +main_l8: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 3 +txna ApplicationArgs 2 +btoi +store 4 +load 3 +load 4 +callsub mul_1 +store 5 +bytec_0 // 0x151f7c75 +load 5 +itob +concat +log +intc_1 // 1 +return +main_l9: +txn OnCompletion +intc_0 // NoOp +== +txn ApplicationID +intc_0 // 0 +!= +&& +assert +txna ApplicationArgs 1 +btoi +store 0 +txna ApplicationArgs 2 +btoi +store 1 +load 0 +load 1 +callsub add_0 +store 2 +bytec_0 // 0x151f7c75 +load 2 +itob +concat +log +intc_1 // 1 +return +main_l10: +txn OnCompletion +intc_0 // NoOp +== +bnz main_l12 +err +main_l12: +txn ApplicationID +intc_0 // 0 +== +assert +intc_1 // 1 +return + +// add +add_0: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 ++ +frame_bury 0 +retsub + +// mul +mul_1: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 +* +frame_bury 0 +retsub + +// sub +sub_2: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 +- +frame_bury 0 +retsub + +// div +div_3: +proto 2 1 +intc_0 // 0 +frame_dig -2 +frame_dig -1 +/ +frame_bury 0 +retsub \ No newline at end of file diff --git a/examples/calculator/clear.teal b/examples/calculator/clear.teal new file mode 100644 index 00000000..e741f0e5 --- /dev/null +++ b/examples/calculator/clear.teal @@ -0,0 +1,3 @@ +#pragma version 8 +pushint 0 // 0 +return \ No newline at end of file diff --git a/examples/calculator/contract.json b/examples/calculator/contract.json new file mode 100644 index 00000000..4b23fa17 --- /dev/null +++ b/examples/calculator/contract.json @@ -0,0 +1,74 @@ +{ + "name": "Calculator", + "methods": [ + { + "name": "add", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Add a and b, return the result" + }, + { + "name": "mul", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Multiply a and b, return the result" + }, + { + "name": "sub", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Subtract b from a, return the result" + }, + { + "name": "div", + "args": [ + { + "type": "uint64", + "name": "a" + }, + { + "type": "uint64", + "name": "b" + } + ], + "returns": { + "type": "uint64" + }, + "desc": "Divide a by b, return the result" + } + ], + "networks": {} +} \ No newline at end of file diff --git a/examples/codec/main.go b/examples/codec/main.go new file mode 100644 index 00000000..b896912b --- /dev/null +++ b/examples/codec/main.go @@ -0,0 +1,110 @@ +package main + +import ( + "context" + "encoding/base64" + "encoding/binary" + "log" + "os" + + "github.com/algorand/go-algorand-sdk/v2/abi" + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/encoding/msgpack" + "github.com/algorand/go-algorand-sdk/v2/examples" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + algodClient := examples.GetAlgodClient() + accts, err := examples.GetSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + + acct1 := accts[0] + + // example: CODEC_TRANSACTION_UNSIGNED + // Error handling omitted for brevity + sp, _ := algodClient.SuggestedParams().Do(context.Background()) + ptxn, _ := transaction.MakePaymentTxn( + acct1.Address.String(), acct1.Address.String(), 10000, nil, "", sp, + ) + + // Encode the txn as bytes, + // if sending over the wire (like to a frontend) it should also be b64 encoded + encodedTxn := msgpack.Encode(ptxn) + os.WriteFile("pay.txn", encodedTxn, 0655) + + var recoveredPayTxn = types.Transaction{} + + msgpack.Decode(encodedTxn, &recoveredPayTxn) + log.Printf("%+v", recoveredPayTxn) + // example: CODEC_TRANSACTION_UNSIGNED + + // example: CODEC_TRANSACTION_SIGNED + // Assuming we already have a pay transaction `ptxn` + + // Sign the transaction + _, signedTxn, err := crypto.SignTransaction(acct1.PrivateKey, ptxn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + // Save the signed transaction to file + os.WriteFile("pay.stxn", signedTxn, 0644) + + signedPayTxn := types.SignedTxn{} + err = msgpack.Decode(signedTxn, &signedPayTxn) + if err != nil { + log.Fatalf("failed to decode signed transaction: %s", err) + } + // example: CODEC_TRANSACTION_SIGNED + + // cleanup + os.Remove("pay.stxn") + os.Remove("pay.txn") + + // example: CODEC_ADDRESS + address := "4H5UNRBJ2Q6JENAXQ6HNTGKLKINP4J4VTQBEPK5F3I6RDICMZBPGNH6KD4" + pk, _ := types.DecodeAddress(address) + addr := pk.String() + // example: CODEC_ADDRESS + _ = addr + + // example: CODEC_BASE64 + encoded := "SGksIEknbSBkZWNvZGVkIGZyb20gYmFzZTY0" + decoded, _ := base64.StdEncoding.DecodeString(encoded) + reencoded := base64.StdEncoding.EncodeToString(decoded) + // example: CODEC_BASE64 + _ = reencoded + + // example: CODEC_UINT64 + val := 1337 + encodedInt := make([]byte, 8) + binary.BigEndian.PutUint64(encodedInt, uint64(val)) + + decodedInt := binary.BigEndian.Uint64(encodedInt) + // decodedInt == val + // example: CODEC_UINT64 + _ = decodedInt + + // example: CODEC_ABI + tupleCodec, _ := abi.TypeOf("(string,string)") + + tupleVal := []string{"hello", "world"} + encodedTuple, _ := tupleCodec.Encode(tupleVal) + log.Printf("%x", encodedTuple) + + decodedTuple, _ := tupleCodec.Decode(encodedTuple) + log.Printf("%v", decodedTuple) // [hello world] + + arrCodec, _ := abi.TypeOf("uint64[]") + arrVal := []uint64{1, 2, 3, 4, 5} + encodedArr, _ := arrCodec.Encode(arrVal) + log.Printf("%x", encodedArr) + + decodedArr, _ := arrCodec.Decode(encodedArr) + log.Printf("%v", decodedArr) // [1 2 3 4 5] + // example: CODEC_ABI +} diff --git a/examples/debug/main.go b/examples/debug/main.go new file mode 100644 index 00000000..2d9fda18 --- /dev/null +++ b/examples/debug/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "context" + "log" + "os" + + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/encoding/msgpack" + "github.com/algorand/go-algorand-sdk/v2/examples" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + + algodClient := examples.GetAlgodClient() + accts, err := examples.GetSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + + acct1 := accts[0] + + appID := examples.DeployApp(algodClient, acct1) + + // example: DEBUG_DRYRUN_DUMP + var ( + args [][]byte + accounts []string + apps []uint64 + assets []uint64 + ) + + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("failed to get suggested params: %s", err) + } + + appCallTxn, err := transaction.MakeApplicationNoOpTx( + appID, args, accounts, apps, assets, sp, acct1.Address, + nil, types.Digest{}, [32]byte{}, types.Address{}, + ) + if err != nil { + log.Fatalf("Failed to create app call txn: %+v", err) + } + + _, stxn, err := crypto.SignTransaction(acct1.PrivateKey, appCallTxn) + if err != nil { + log.Fatalf("Failed to sign app txn: %+v", err) + } + + signedAppCallTxn := types.SignedTxn{} + msgpack.Decode(stxn, &signedAppCallTxn) + + drr, err := transaction.CreateDryrun(algodClient, []types.SignedTxn{signedAppCallTxn}, nil, context.Background()) + if err != nil { + log.Fatalf("Failed to create dryrun: %+v", err) + } + + os.WriteFile("dryrun.msgp", msgpack.Encode(drr), 0666) + // example: DEBUG_DRYRUN_DUMP + + // example: DEBUG_DRYRUN_SUBMIT + // Create the dryrun request object + drReq, err := transaction.CreateDryrun(algodClient, []types.SignedTxn{signedAppCallTxn}, nil, context.Background()) + if err != nil { + log.Fatalf("Failed to create dryrun: %+v", err) + } + + // Pass dryrun request to algod server + dryrunResponse, err := algodClient.TealDryrun(drReq).Do(context.Background()) + if err != nil { + log.Fatalf("failed to dryrun request: %s", err) + } + + // Inspect the response to check result + for _, txn := range dryrunResponse.Txns { + log.Printf("%+v", txn.AppCallTrace) + } + // example: DEBUG_DRYRUN_SUBMIT + os.Remove("dryrun.msgp") +} diff --git a/examples/gen-addresses/main.go b/examples/gen-addresses/main.go deleted file mode 100644 index 90301ee6..00000000 --- a/examples/gen-addresses/main.go +++ /dev/null @@ -1,152 +0,0 @@ -package main - -import ( - "bytes" - "context" - "fmt" - "github.com/algorand/go-algorand-sdk/v2/transaction" - "strings" - - "github.com/algorand/go-algorand-sdk/v2/client/kmd" - "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" - "github.com/algorand/go-algorand-sdk/v2/crypto" - "github.com/algorand/go-algorand-sdk/v2/types" -) - -// CHANGE ME -const ( - exampleWalletName = "unencrypted-default-wallet" - exampleWalletPassword = "" - exampleWalletDriver = kmd.DefaultWalletDriver -) - -var ( - kmdAddress = "http://localhost:4002" - kmdToken = strings.Repeat("a", 64) - - algodAddress = "http://localhost:4001" - algodToken = strings.Repeat("a", 64) -) - -func main() { - // Create a kmd client - kmdClient, err := kmd.MakeClient(kmdAddress, kmdToken) - if err != nil { - fmt.Printf("failed to make kmd client: %s\n", err) - return - } - fmt.Println("Made a kmd client") - - // Create an algod client - algodClient, err := algod.MakeClient(algodAddress, algodToken) - if err != nil { - fmt.Printf("failed to make algod client: %s\n", err) - return - } - - // Print algod status - nodeStatus, err := algodClient.Status().Do(context.Background()) - if err != nil { - fmt.Printf("error getting algod status: %s\n", err) - return - } - fmt.Printf("algod last round: %d\n", nodeStatus.LastRound) - - // List existing wallets, and check if our example wallet already exists - resp0, err := kmdClient.ListWallets() - if err != nil { - fmt.Printf("error listing wallets: %s\n", err) - return - } - fmt.Printf("Got %d wallet(s):\n", len(resp0.Wallets)) - var exampleExists bool - var exampleWalletID string - for _, wallet := range resp0.Wallets { - fmt.Printf("ID: %s\tName: %s\n", wallet.ID, wallet.Name) - if wallet.Name == exampleWalletName { - exampleWalletID = wallet.ID - exampleExists = true - } - } - - // Create the example wallet, if it doesn't already exist - if !exampleExists { - resp1, err := kmdClient.CreateWallet(exampleWalletName, exampleWalletPassword, exampleWalletDriver, types.MasterDerivationKey{}) - if err != nil { - fmt.Printf("error creating wallet: %s\n", err) - return - } - exampleWalletID = resp1.Wallet.ID - fmt.Printf("Created wallet '%s' with ID: %s\n", resp1.Wallet.Name, exampleWalletID) - } - - // Get a wallet handle - resp2, err := kmdClient.InitWalletHandle(exampleWalletID, exampleWalletPassword) - if err != nil { - fmt.Printf("Error initializing wallet: %s\n", err) - return - } - - // Extract the wallet handle - exampleWalletHandleToken := resp2.WalletHandleToken - - // Generate some addresses in the wallet - fmt.Println("Generating 10 addresses") - var addresses []string - for i := 0; i < 10; i++ { - resp3, err := kmdClient.GenerateKey(exampleWalletHandleToken) - if err != nil { - fmt.Printf("Error generating key: %s\n", err) - return - } - fmt.Printf("Generated address %s\n", resp3.Address) - addresses = append(addresses, resp3.Address) - } - - // Extract the private key of the first address - fmt.Printf("Extracting private key for %s\n", addresses[0]) - resp4, err := kmdClient.ExportKey(exampleWalletHandleToken, exampleWalletPassword, addresses[0]) - if err != nil { - fmt.Printf("Error extracting secret key: %s\n", err) - return - } - privateKey := resp4.PrivateKey - - // Get the suggested transaction parameters - txParams, err := algodClient.SuggestedParams().Do(context.Background()) - if err != nil { - fmt.Printf("error getting suggested tx params: %s\n", err) - return - } - - tx, err := transaction.MakePaymentTxn(addresses[0], addresses[1], 100, nil, "", txParams) - if err != nil { - fmt.Printf("Error creating transaction: %s\n", err) - return - } - fmt.Printf("Made unsigned transaction: %+v\n", tx) - fmt.Println("Signing transaction with go-algo-sdk library function (not kmd)") - - txid, stx, err := crypto.SignTransaction(privateKey, tx) - if err != nil { - fmt.Printf("Failed to sign transaction: %s\n", err) - return - } - - fmt.Printf("Made signed transaction with TxID %s: %x\n", txid, stx) - - // Sign the same transaction with kmd - fmt.Println("Signing same transaction with kmd") - resp5, err := kmdClient.SignTransaction(exampleWalletHandleToken, exampleWalletPassword, tx) - if err != nil { - fmt.Printf("Failed to sign transaction with kmd: %s\n", err) - return - } - - fmt.Printf("kmd made signed transaction with bytes: %x\n", resp5.SignedTransaction) - if bytes.Equal(resp5.SignedTransaction, stx) { - fmt.Println("signed transactions match!") - } else { - fmt.Println("signed transactions don't match!") - } -} diff --git a/examples/indexer/main.go b/examples/indexer/main.go new file mode 100644 index 00000000..2a856ac6 --- /dev/null +++ b/examples/indexer/main.go @@ -0,0 +1,139 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/common" + "github.com/algorand/go-algorand-sdk/v2/client/v2/indexer" + "github.com/algorand/go-algorand-sdk/v2/examples" +) + +func main() { + // example: INDEXER_CREATE_CLIENT + // Create a new indexer client, configured to connect to out local sandbox + var indexerAddress = "http://localhost:8980" + var indexerToken = strings.Repeat("a", 64) + indexerClient, _ := indexer.MakeClient( + indexerAddress, + indexerToken, + ) + + // Or, if necessary, pass alternate headers + + var indexerHeader common.Header + indexerHeader.Key = "X-API-Key" + indexerHeader.Value = indexerToken + indexerClientWithHeaders, err := indexer.MakeClientWithHeaders( + indexerAddress, + indexerToken, + []*common.Header{&indexerHeader}, + ) + // example: INDEXER_CREATE_CLIENT + + // Suppress X declared but not used + _ = indexerClientWithHeaders + _ = indexerClient + + indexerClient = examples.GetIndexerClient() + + if err != nil { + fmt.Printf("failed to make indexer client: %s\n", err) + return + } + + indexerHealth, err := indexerClient.HealthCheck().Do(context.Background()) + if err != nil { + fmt.Printf("Failed to get status: %s\n", err) + return + } + + fmt.Printf("Indexer Round: %d\n", indexerHealth.Round) + + // example: INDEXER_LOOKUP_ASSET + // query parameters + var assetId uint64 = 2044572 + var minBalance uint64 = 50 + + // Lookup accounts with minimum balance of asset + assetResult, _ := indexerClient. + LookupAssetBalances(assetId). + CurrencyGreaterThan(minBalance). + Do(context.Background()) + + // Print the results + assetJson, _ := json.MarshalIndent(assetResult, "", "\t") + fmt.Printf(string(assetJson) + "\n") + // example: INDEXER_LOOKUP_ASSET + + assetJson = nil + + // example: INDEXER_SEARCH_MIN_AMOUNT + // query parameters + var transactionMinAmount uint64 = 10 + + // Query + transactionResult, _ := indexerClient. + SearchForTransactions(). + CurrencyGreaterThan(transactionMinAmount). + Do(context.Background()) + + // Print results + transactionJson, _ := json.MarshalIndent(transactionResult, "", "\t") + fmt.Printf(string(transactionJson) + "\n") + // example: INDEXER_SEARCH_MIN_AMOUNT + + // example: INDEXER_PAGINATE_RESULTS + var nextToken = "" + var numTx = 1 + var numPages = 1 + var pagedMinAmount uint64 = 10 + var limit uint64 = 1 + + for numTx > 0 { + // Query + pagedResults, err := indexerClient. + SearchForTransactions(). + CurrencyGreaterThan(pagedMinAmount). + Limit(limit). + NextToken(nextToken). + Do(context.Background()) + if err != nil { + return + } + pagedTransactions := pagedResults.Transactions + numTx = len(pagedTransactions) + nextToken = pagedResults.NextToken + + if numTx > 0 { + // Print results + pagedJson, err := json.MarshalIndent(pagedTransactions, "", "\t") + if err != nil { + return + } + fmt.Printf(string(pagedJson) + "\n") + fmt.Println("End of page : ", numPages) + fmt.Println("Transaction printed : ", len(pagedTransactions)) + fmt.Println("Next Token : ", nextToken) + numPages++ + } + } + // example: INDEXER_PAGINATE_RESULTS + + // example: INDEXER_PREFIX_SEARCH + // Parameters + var notePrefix = "showing prefix" + + // Query + prefixResult, _ := indexerClient. + SearchForTransactions(). + NotePrefix([]byte(notePrefix)). + Do(context.Background()) + + // Print results + prefixJson, _ := json.MarshalIndent(prefixResult, "", "\t") + fmt.Printf(string(prefixJson) + "\n") + // example: INDEXER_PREFIX_SEARCH +} diff --git a/examples/kmd/main.go b/examples/kmd/main.go new file mode 100644 index 00000000..acb57aa4 --- /dev/null +++ b/examples/kmd/main.go @@ -0,0 +1,232 @@ +package main + +import ( + "crypto/ed25519" + "fmt" + "strings" + + "github.com/algorand/go-algorand-sdk/v2/client/kmd" + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/examples" + "github.com/algorand/go-algorand-sdk/v2/mnemonic" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + var ( + exampleWalletID string + exampleWalletHandleToken string + genResponse kmd.GenerateKeyResponse + initResponse kmd.InitWalletHandleResponse + ) + + // example: KMD_CREATE_CLIENT + // Create a new kmd client, configured to connect to out local sandbox + var kmdAddress = "http://localhost:4002" + var kmdToken = strings.Repeat("a", 64) + kmdClient, err := kmd.MakeClient( + kmdAddress, + kmdToken, + ) + // example: KMD_CREATE_CLIENT + _ = kmdClient + kmdClient = examples.GetKmdClient() + + if err != nil { + fmt.Printf("failed to make kmd client: %s\n", err) + return + } + + // example: KMD_CREATE_WALLET + // Create the example wallet, if it doesn't already exist + createResponse, err := kmdClient.CreateWallet( + "DemoWallet", + "password", + kmd.DefaultWalletDriver, + types.MasterDerivationKey{}, + ) + if err != nil { + fmt.Printf("error creating wallet: %s\n", err) + return + } + + // We need the wallet ID in order to get a wallet handle, so we can add accounts + exampleWalletID = createResponse.Wallet.ID + fmt.Printf("Created wallet '%s' with ID: %s\n", createResponse.Wallet.Name, exampleWalletID) + // example: KMD_CREATE_WALLET + + // example: KMD_CREATE_ACCOUNT + // Get a wallet handle. + initResponse, _ = kmdClient.InitWalletHandle( + exampleWalletID, + "password", + ) + exampleWalletHandleToken = initResponse.WalletHandleToken + + // Generate a new address from the wallet handle + genResponse, err = kmdClient.GenerateKey(exampleWalletHandleToken) + if err != nil { + fmt.Printf("Error generating key: %s\n", err) + return + } + accountAddress := genResponse.Address + fmt.Printf("New Account: %s\n", accountAddress) + // example: KMD_CREATE_ACCOUNT + + // example: KMD_EXPORT_ACCOUNT + // Extract the account sk + accountKeyResponse, _ := kmdClient.ExportKey( + exampleWalletHandleToken, + "password", + accountAddress, + ) + accountKey := accountKeyResponse.PrivateKey + // Convert sk to mnemonic + mn, err := mnemonic.FromPrivateKey(accountKey) + if err != nil { + fmt.Printf("Error getting backup phrase: %s\n", err) + return + } + fmt.Printf("Account Mnemonic: %v ", mn) + // example: KMD_EXPORT_ACCOUNT + + // example: KMD_IMPORT_ACCOUNT + account := crypto.GenerateAccount() + fmt.Println("Account Address: ", account.Address) + mn, err = mnemonic.FromPrivateKey(account.PrivateKey) + if err != nil { + fmt.Printf("Error getting backup phrase: %s\n", err) + return + } + fmt.Printf("Account Mnemonic: %s\n", mn) + importedAccount, _ := kmdClient.ImportKey( + exampleWalletHandleToken, + account.PrivateKey, + ) + fmt.Println("Account Successfully Imported: ", importedAccount.Address) + // example: KMD_IMPORT_ACCOUNT + + // Get the MDK for Recovery example + backupResponse, err := kmdClient.ExportMasterDerivationKey(exampleWalletHandleToken, "password") + if err != nil { + fmt.Printf("error exporting mdk: %s\n", err) + return + } + backupPhrase, _ := mnemonic.FromMasterDerivationKey(backupResponse.MasterDerivationKey) + fmt.Printf("Backup: %s\n", backupPhrase) + + // example: KMD_RECOVER_WALLET + keyBytes, err := mnemonic.ToKey(backupPhrase) + if err != nil { + fmt.Printf("failed to get key: %s\n", err) + return + } + + var mdk types.MasterDerivationKey + copy(mdk[:], keyBytes) + recoverResponse, err := kmdClient.CreateWallet( + "RecoveryWallet", + "password", + kmd.DefaultWalletDriver, + mdk, + ) + if err != nil { + fmt.Printf("error creating wallet: %s\n", err) + return + } + + // We need the wallet ID in order to get a wallet handle, so we can add accounts + exampleWalletID = recoverResponse.Wallet.ID + fmt.Printf("Created wallet '%s' with ID: %s\n", recoverResponse.Wallet.Name, exampleWalletID) + + // Get a wallet handle. The wallet handle is used for things like signing transactions + // and creating accounts. Wallet handles do expire, but they can be renewed + initResponse, err = kmdClient.InitWalletHandle( + exampleWalletID, + "password", + ) + if err != nil { + fmt.Printf("Error initializing wallet handle: %s\n", err) + return + } + + // Extract the wallet handle + exampleWalletHandleToken = initResponse.WalletHandleToken + fmt.Printf("Got wallet handle: '%s'\n", exampleWalletHandleToken) + + // Generate a new address from the wallet handle + genResponse, err = kmdClient.GenerateKey(exampleWalletHandleToken) + if err != nil { + fmt.Printf("Error generating key: %s\n", err) + return + } + fmt.Printf("Recovered address %s\n", genResponse.Address) + // example: KMD_RECOVER_WALLET + + // example: ACCOUNT_GENERATE + newAccount := crypto.GenerateAccount() + passphrase, err := mnemonic.FromPrivateKey(newAccount.PrivateKey) + + if err != nil { + fmt.Printf("Error creating transaction: %s\n", err) + } else { + fmt.Printf("My address: %s\n", newAccount.Address) + fmt.Printf("My passphrase: %s\n", passphrase) + } + // example: ACCOUNT_GENERATE + + // example: MULTISIG_CREATE + // Get pre-defined set of keys for example + _, pks := loadAccounts() + addr1, _ := types.DecodeAddress(pks[1]) + addr2, _ := types.DecodeAddress(pks[2]) + addr3, _ := types.DecodeAddress(pks[3]) + + ma, err := crypto.MultisigAccountWithParams(1, 2, []types.Address{ + addr1, + addr2, + addr3, + }) + + if err != nil { + panic("invalid multisig parameters") + } + fromAddr, _ := ma.Address() + // Print multisig account + fmt.Printf("Multisig address : %s \n", fromAddr) + // example: MULTISIG_CREATE +} + +// Accounts to be used through examples +func loadAccounts() (map[int][]byte, map[int]string) { + // Shown for demonstration purposes. NEVER reveal secret mnemonics in practice. + // Change these values to use the accounts created previously. + // Paste in mnemonic phrases for all three accounts + acc1 := crypto.GenerateAccount() + mnemonic1, _ := mnemonic.FromPrivateKey(acc1.PrivateKey) + acc2 := crypto.GenerateAccount() + mnemonic2, _ := mnemonic.FromPrivateKey(acc2.PrivateKey) + acc3 := crypto.GenerateAccount() + mnemonic3, _ := mnemonic.FromPrivateKey(acc3.PrivateKey) + + mnemonics := []string{mnemonic1, mnemonic2, mnemonic3} + pks := map[int]string{1: "", 2: "", 3: ""} + var sks = make(map[int][]byte) + + for i, m := range mnemonics { + var err error + sk, err := mnemonic.ToPrivateKey(m) + sks[i+1] = sk + if err != nil { + fmt.Printf("Issue with account %d private key conversion.", i+1) + } + // derive public address from Secret Key. + pk := sk.Public() + var a types.Address + cpk := pk.(ed25519.PublicKey) + copy(a[:], cpk[:]) + pks[i+1] = a.String() + fmt.Printf("Loaded Key %d: %s\n", i+1, pks[i+1]) + } + return sks, pks +} diff --git a/examples/lsig/main.go b/examples/lsig/main.go new file mode 100644 index 00000000..528019ee --- /dev/null +++ b/examples/lsig/main.go @@ -0,0 +1,131 @@ +package main + +import ( + "context" + "encoding/base64" + "encoding/binary" + "io/ioutil" + "log" + + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/examples" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +func main() { + algodClient := examples.GetAlgodClient() + accts, err := examples.GetSandboxAccounts() + if err != nil { + log.Fatalf("failed to get sandbox accounts: %s", err) + } + seedAcct := accts[0] + + // example: LSIG_COMPILE + teal, err := ioutil.ReadFile("lsig/simple.teal") + if err != nil { + log.Fatalf("failed to read approval program: %s", err) + } + + result, err := algodClient.TealCompile(teal).Do(context.Background()) + if err != nil { + log.Fatalf("failed to compile program: %s", err) + } + + lsigBinary, err := base64.StdEncoding.DecodeString(result.Result) + if err != nil { + log.Fatalf("failed to decode compiled program: %s", err) + } + // example: LSIG_COMPILE + + // example: LSIG_INIT + lsig := crypto.LogicSigAccount{ + Lsig: types.LogicSig{Logic: lsigBinary, Args: nil}, + } + // example: LSIG_INIT + _ = lsig + + // example: LSIG_PASS_ARGS + encodedArg := make([]byte, 8) + binary.BigEndian.PutUint64(encodedArg, 123) + + lsigWithArgs := crypto.LogicSigAccount{ + Lsig: types.LogicSig{Logic: lsigBinary, Args: [][]byte{encodedArg}}, + } + // example: LSIG_PASS_ARGS + _ = lsigWithArgs + + // seed lsig so the pay from the lsig works + lsa, err := lsig.Address() + if err != nil { + log.Fatalf("failed to get lsig address: %s", err) + } + seedAddr := seedAcct.Address.String() + sp, _ := algodClient.SuggestedParams().Do(context.Background()) + txn, _ := transaction.MakePaymentTxn(seedAddr, lsa.String(), 1000000, nil, "", sp) + txid, stx, _ := crypto.SignTransaction(seedAcct.PrivateKey, txn) + algodClient.SendRawTransaction(stx).Do(context.Background()) + transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + + // example: LSIG_SIGN_FULL + sp, err = algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("failed to get suggested params: %s", err) + } + + lsigAddr, err := lsig.Address() + if err != nil { + log.Fatalf("failed to get lsig address: %s", err) + } + ptxn, err := transaction.MakePaymentTxn(lsigAddr.String(), seedAddr, 10000, nil, "", sp) + if err != nil { + log.Fatalf("failed to make transaction: %s", err) + } + txid, stxn, err := crypto.SignLogicSigAccountTransaction(lsig, ptxn) + if err != nil { + log.Fatalf("failed to sign transaction with lsig: %s", err) + } + _, err = algodClient.SendRawTransaction(stxn).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + payResult, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("failed while waiting for transaction: %s", err) + } + log.Printf("Lsig pay confirmed in round: %d", payResult.ConfirmedRound) + // example: LSIG_SIGN_FULL + + // example: LSIG_DELEGATE_FULL + // account signs the logic, and now the logic may be passed instead + // of a signature for a transaction + var args [][]byte + delSig, err := crypto.MakeLogicSigAccountDelegated(lsigBinary, args, seedAcct.PrivateKey) + if err != nil { + log.Fatalf("failed to make delegate lsig: %s", err) + } + + delSigPay, err := transaction.MakePaymentTxn(seedAddr, lsigAddr.String(), 10000, nil, "", sp) + if err != nil { + log.Fatalf("failed to make transaction: %s", err) + } + + delTxId, delStxn, err := crypto.SignLogicSigAccountTransaction(delSig, delSigPay) + if err != nil { + log.Fatalf("failed to sign with delegate sig: %s", err) + } + + _, err = algodClient.SendRawTransaction(delStxn).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + delPayResult, err := transaction.WaitForConfirmation(algodClient, delTxId, 4, context.Background()) + if err != nil { + log.Fatalf("failed while waiting for transaction: %s", err) + } + + log.Printf("Delegated Lsig pay confirmed in round: %d", delPayResult.ConfirmedRound) + // example: LSIG_DELEGATE_FULL +} diff --git a/examples/lsig/sample_arg.teal b/examples/lsig/sample_arg.teal new file mode 100644 index 00000000..8f21008e --- /dev/null +++ b/examples/lsig/sample_arg.teal @@ -0,0 +1,5 @@ +#pragma version 5 +arg_0 +btoi +int 123 +== \ No newline at end of file diff --git a/examples/lsig/simple.teal b/examples/lsig/simple.teal new file mode 100644 index 00000000..d6298656 --- /dev/null +++ b/examples/lsig/simple.teal @@ -0,0 +1,3 @@ +#pragma version 5 +int 1 +return \ No newline at end of file diff --git a/examples/overview/main.go b/examples/overview/main.go new file mode 100644 index 00000000..78ac009f --- /dev/null +++ b/examples/overview/main.go @@ -0,0 +1,115 @@ +package main + +import ( + "context" + "fmt" + "log" + "strings" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" + "github.com/algorand/go-algorand-sdk/v2/client/v2/common" + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/examples" + "github.com/algorand/go-algorand-sdk/v2/transaction" +) + +func main() { + + // example: ALGOD_CREATE_CLIENT + // Create a new algod client, configured to connect to out local sandbox + var algodAddress = "http://localhost:4001" + var algodToken = strings.Repeat("a", 64) + algodClient, _ := algod.MakeClient( + algodAddress, + algodToken, + ) + + // Or, if necessary, pass alternate headers + + var algodHeader common.Header + algodHeader.Key = "X-API-Key" + algodHeader.Value = algodToken + algodClientWithHeaders, _ := algod.MakeClientWithHeaders( + algodAddress, + algodToken, + []*common.Header{&algodHeader}, + ) + // example: ALGOD_CREATE_CLIENT + + _ = algodClientWithHeaders + _ = algodClient + + // Override with the version that has correct port + algodClient = examples.GetAlgodClient() + + accts, err := examples.GetSandboxAccounts() + if err != nil { + log.Fatalf("failed to get accounts: %s", err) + } + acct := accts[0] + + // example: ALGOD_FETCH_ACCOUNT_INFO + acctInfo, err := algodClient.AccountInformation(acct.Address.String()).Do(context.Background()) + if err != nil { + log.Fatalf("failed to fetch account info: %s", err) + } + log.Printf("Account balance: %d microAlgos", acctInfo.Amount) + // example: ALGOD_FETCH_ACCOUNT_INFO + + // example: TRANSACTION_PAYMENT_CREATE + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("failed to get suggested params: %s", err) + } + // payment from account to itself + ptxn, err := transaction.MakePaymentTxn(acct.Address.String(), acct.Address.String(), 100000, nil, "", sp) + if err != nil { + log.Fatalf("failed creating transaction: %s", err) + } + // example: TRANSACTION_PAYMENT_CREATE + + // example: TRANSACTION_PAYMENT_SIGN + _, sptxn, err := crypto.SignTransaction(acct.PrivateKey, ptxn) + if err != nil { + fmt.Printf("Failed to sign transaction: %s\n", err) + return + } + // example: TRANSACTION_PAYMENT_SIGN + + // example: TRANSACTION_PAYMENT_SUBMIT + pendingTxID, err := algodClient.SendRawTransaction(sptxn).Do(context.Background()) + if err != nil { + fmt.Printf("failed to send transaction: %s\n", err) + return + } + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, pendingTxID, 4, context.Background()) + if err != nil { + fmt.Printf("Error waiting for confirmation on txID: %s\n", pendingTxID) + return + } + fmt.Printf("Confirmed Transaction: %s in Round %d\n", pendingTxID, confirmedTxn.ConfirmedRound) + // example: TRANSACTION_PAYMENT_SUBMIT + + // example: SP_MIN_FEE + suggestedParams, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("failed to %s", err) + } + log.Printf("Min fee from suggested params: %d", suggestedParams.MinFee) + // example: SP_MIN_FEE + + // example: CONST_MIN_FEE + log.Printf("Min fee const: %d", transaction.MinTxnFee) + // example: CONST_MIN_FEE + + // example: TRANSACTION_FEE_OVERRIDE + // by using fee pooling and setting our fee to 2x min tx fee + // we can cover the fee for another transaction in the group + sp.Fee = 2 * transaction.MinTxnFee + sp.FlatFee = true + // ... + // example: TRANSACTION_FEE_OVERRIDE + +} +func exampleAlgod() { +} diff --git a/examples/participation/main.go b/examples/participation/main.go new file mode 100644 index 00000000..fef59af8 --- /dev/null +++ b/examples/participation/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "context" + "fmt" + + "github.com/algorand/go-algorand-sdk/v2/examples" + "github.com/algorand/go-algorand-sdk/v2/transaction" +) + +func main() { + markOnline() +} + +func markOnline() { + // setup connection + algodClient := examples.GetAlgodClient() + + // get network suggested parameters + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + fmt.Printf("error getting suggested tx params: %s\n", err) + return + } + + // Mark Account as "Online" (participating) + // example: TRANSACTION_KEYREG_ONLINE_CREATE + fromAddr := "MWAPNXBDFFD2V5KWXAHWKBO7FO4JN36VR4CIBDKDDE7WAUAGZIXM3QPJW4" + voteKey := "87iBW46PP4BpTDz6+IEGvxY6JqEaOtV0g+VWcJqoqtc=" + selKey := "1V2BE2lbFvS937H7pJebN0zxkqe1Nrv+aVHDTPbYRlw=" + sProofKey := "f0CYOA4yXovNBFMFX+1I/tYVBaAl7VN6e0Ki5yZA3H6jGqsU/LYHNaBkMQ/rN4M4F3UmNcpaTmbVbq+GgDsrhQ==" + voteFirst := uint64(16532750) + voteLast := uint64(19532750) + keyDilution := uint64(1732) + nonpart := false + tx, err := transaction.MakeKeyRegTxnWithStateProofKey( + fromAddr, + []byte{}, + sp, + voteKey, + selKey, + sProofKey, + voteFirst, + voteLast, + keyDilution, + nonpart, + ) + // example: TRANSACTION_KEYREG_ONLINE_CREATE + if err != nil { + fmt.Printf("Error creating transaction: %s\n", err) + return + } + _ = tx + + // disabled example: TRANSACTION_KEYREG_OFFLINE_CREATE + // disabled example: TRANSACTION_KEYREG_OFFLINE_CREATE +} diff --git a/examples/smoke_test.sh b/examples/smoke_test.sh new file mode 100755 index 00000000..68a0a9d7 --- /dev/null +++ b/examples/smoke_test.sh @@ -0,0 +1,19 @@ +#!/bin/env/bin bash + +export ALGOD_PORT="60000" +export INDEXER_PORT="59999" +export KMD_PORT="60001" + +# Loop through each directory in the current working directory +for dir in */; do + # Check if main.go exists in the directory + if [ -f "${dir}main.go" ]; then + # Run the "go run" command with the relative path + go run "${dir}main.go" + # Check if the test failed + if [ $? -ne 0 ]; then + echo "Test failed, stopping script" + exit 1 + fi + fi +done \ No newline at end of file diff --git a/examples/utils.go b/examples/utils.go new file mode 100644 index 00000000..eaa54e13 --- /dev/null +++ b/examples/utils.go @@ -0,0 +1,233 @@ +package examples + +import ( + "context" + "encoding/base64" + "fmt" + "io/ioutil" + "log" + "os" + "strings" + + "github.com/algorand/go-algorand-sdk/v2/client/kmd" + "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" + "github.com/algorand/go-algorand-sdk/v2/client/v2/indexer" + "github.com/algorand/go-algorand-sdk/v2/crypto" + "github.com/algorand/go-algorand-sdk/v2/transaction" + "github.com/algorand/go-algorand-sdk/v2/types" +) + +var ( + ALGOD_ADDRESS = "http://localhost" + ALGOD_PORT = "4001" + ALGOD_URL = "" + ALGOD_TOKEN = strings.Repeat("a", 64) + + INDEXER_ADDRESS = "http://localhost" + INDEXER_PORT = "8980" + INDEXER_TOKEN = strings.Repeat("a", 64) + INDEXER_URL = "" + + KMD_ADDRESS = "http://localhost" + KMD_PORT = "4002" + KMD_TOKEN = strings.Repeat("a", 64) + KMD_URL = "" + + KMD_WALLET_NAME = "unencrypted-default-wallet" + KMD_WALLET_PASSWORD = "" +) + +func init() { + if aport, ok := os.LookupEnv("ALGOD_PORT"); ok { + ALGOD_PORT = aport + } + ALGOD_URL = fmt.Sprintf("%s:%s", ALGOD_ADDRESS, ALGOD_PORT) + + if iport, ok := os.LookupEnv("INDEXER_PORT"); ok { + INDEXER_PORT = iport + } + INDEXER_URL = fmt.Sprintf("%s:%s", INDEXER_ADDRESS, INDEXER_PORT) + + if kport, ok := os.LookupEnv("KMD_PORT"); ok { + KMD_PORT = kport + } + KMD_URL = fmt.Sprintf("%s:%s", KMD_ADDRESS, KMD_PORT) +} + +func GetAlgodClient() *algod.Client { + algodClient, err := algod.MakeClient( + ALGOD_URL, + ALGOD_TOKEN, + ) + + if err != nil { + log.Fatalf("Failed to create algod client: %s", err) + } + + return algodClient +} + +func GetKmdClient() kmd.Client { + kmdClient, err := kmd.MakeClient( + KMD_URL, + KMD_TOKEN, + ) + + if err != nil { + log.Fatalf("Failed to create kmd client: %s", err) + } + + return kmdClient +} + +func GetIndexerClient() *indexer.Client { + indexerClient, err := indexer.MakeClient( + INDEXER_URL, + INDEXER_TOKEN, + ) + + if err != nil { + log.Fatalf("Failed to create indexer client: %s", err) + } + + return indexerClient +} + +func GetSandboxAccounts() ([]crypto.Account, error) { + client := GetKmdClient() + + resp, err := client.ListWallets() + if err != nil { + return nil, fmt.Errorf("Failed to list wallets: %+v", err) + } + + var walletId string + for _, wallet := range resp.Wallets { + if wallet.Name == KMD_WALLET_NAME { + walletId = wallet.ID + } + } + + if walletId == "" { + return nil, fmt.Errorf("No wallet named %s", KMD_WALLET_NAME) + } + + whResp, err := client.InitWalletHandle(walletId, KMD_WALLET_PASSWORD) + if err != nil { + return nil, fmt.Errorf("Failed to init wallet handle: %+v", err) + } + + addrResp, err := client.ListKeys(whResp.WalletHandleToken) + if err != nil { + return nil, fmt.Errorf("Failed to list keys: %+v", err) + } + + var accts []crypto.Account + for _, addr := range addrResp.Addresses { + expResp, err := client.ExportKey(whResp.WalletHandleToken, KMD_WALLET_PASSWORD, addr) + if err != nil { + return nil, fmt.Errorf("Failed to export key: %+v", err) + } + + acct, err := crypto.AccountFromPrivateKey(expResp.PrivateKey) + if err != nil { + return nil, fmt.Errorf("Failed to create account from private key: %+v", err) + } + + accts = append(accts, acct) + } + + return accts, nil +} + +func CompileTeal(algodClient *algod.Client, path string) []byte { + teal, err := ioutil.ReadFile(path) + if err != nil { + log.Fatalf("failed to read approval program: %s", err) + } + + result, err := algodClient.TealCompile(teal).Do(context.Background()) + if err != nil { + log.Fatalf("failed to compile program: %s", err) + } + + bin, err := base64.StdEncoding.DecodeString(result.Result) + if err != nil { + log.Fatalf("failed to decode compiled program: %s", err) + } + return bin +} + +func DeployApp(algodClient *algod.Client, creator crypto.Account) uint64 { + + var ( + approvalBinary = make([]byte, 1000) + clearBinary = make([]byte, 1000) + ) + + // Compile approval program + approvalTeal, err := ioutil.ReadFile("calculator/approval.teal") + if err != nil { + log.Fatalf("failed to read approval program: %s", err) + } + + approvalResult, err := algodClient.TealCompile(approvalTeal).Do(context.Background()) + if err != nil { + log.Fatalf("failed to compile program: %s", err) + } + + _, err = base64.StdEncoding.Decode(approvalBinary, []byte(approvalResult.Result)) + if err != nil { + log.Fatalf("failed to decode compiled program: %s", err) + } + + // Compile clear program + clearTeal, err := ioutil.ReadFile("calculator/clear.teal") + if err != nil { + log.Fatalf("failed to read clear program: %s", err) + } + + clearResult, err := algodClient.TealCompile(clearTeal).Do(context.Background()) + if err != nil { + log.Fatalf("failed to compile program: %s", err) + } + + _, err = base64.StdEncoding.Decode(clearBinary, []byte(clearResult.Result)) + if err != nil { + log.Fatalf("failed to decode compiled program: %s", err) + } + + // Create application + sp, err := algodClient.SuggestedParams().Do(context.Background()) + if err != nil { + log.Fatalf("error getting suggested tx params: %s", err) + } + + txn, err := transaction.MakeApplicationCreateTx( + false, approvalBinary, clearBinary, + types.StateSchema{}, types.StateSchema{}, + nil, nil, nil, nil, + sp, creator.Address, nil, + types.Digest{}, [32]byte{}, types.ZeroAddress, + ) + if err != nil { + log.Fatalf("failed to make txn: %s", err) + } + + txid, stx, err := crypto.SignTransaction(creator.PrivateKey, txn) + if err != nil { + log.Fatalf("failed to sign transaction: %s", err) + } + + _, err = algodClient.SendRawTransaction(stx).Do(context.Background()) + if err != nil { + log.Fatalf("failed to send transaction: %s", err) + } + + confirmedTxn, err := transaction.WaitForConfirmation(algodClient, txid, 4, context.Background()) + if err != nil { + log.Fatalf("error waiting for confirmation: %s", err) + } + + return confirmedTxn.ApplicationIndex +} diff --git a/test/docker/Dockerfile b/test/docker/Dockerfile index f7f70745..3e2b33ee 100644 --- a/test/docker/Dockerfile +++ b/test/docker/Dockerfile @@ -7,4 +7,4 @@ COPY . $HOME/go-algorand-sdk WORKDIR $HOME/go-algorand-sdk # Run integration tests -CMD ["/bin/bash", "-c", "make unit && make integration"] +CMD ["/bin/bash", "-c", "make unit && make integration && make smoke-test-examples"] From 74a4694fe36a39ec275d10ab705ab48ebc7f128b Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Fri, 28 Apr 2023 15:46:10 -0400 Subject: [PATCH 11/16] api: Regenerate client interfaces for timestamp, ready, and simulate endpoints (#513) --- client/v2/algod/algod.go | 16 +++++++++ client/v2/algod/getBlockTimeStampOffset.go | 19 +++++++++++ client/v2/algod/getReady.go | 18 ++++++++++ client/v2/algod/setBlockTimeStampOffset.go | 23 +++++++++++++ client/v2/algod/simulateTransaction.go | 33 +++++++++++++++++++ .../get_block_time_stamp_offset_response.go | 8 +++++ .../v2/common/models/node_status_response.go | 2 +- .../models/pending_transaction_response.go | 8 ++--- client/v2/common/models/simulate_request.go | 14 ++++++++ .../simulate_request_transaction_group.go | 9 +++++ client/v2/common/models/simulate_response.go | 19 +++++++++++ .../simulate_transaction_group_result.go | 25 ++++++++++++++ .../models/simulate_transaction_result.go | 15 +++++++++ .../models/simulation_eval_overrides.go | 16 +++++++++ test/algodclientv2_test.go | 29 ++++++++++++++++ test/responses_unit_test.go | 3 ++ test/unit.tags | 7 +++- 17 files changed, 258 insertions(+), 6 deletions(-) create mode 100644 client/v2/algod/getBlockTimeStampOffset.go create mode 100644 client/v2/algod/getReady.go create mode 100644 client/v2/algod/setBlockTimeStampOffset.go create mode 100644 client/v2/algod/simulateTransaction.go create mode 100644 client/v2/common/models/get_block_time_stamp_offset_response.go create mode 100644 client/v2/common/models/simulate_request.go create mode 100644 client/v2/common/models/simulate_request_transaction_group.go create mode 100644 client/v2/common/models/simulate_response.go create mode 100644 client/v2/common/models/simulate_transaction_group_result.go create mode 100644 client/v2/common/models/simulate_transaction_result.go create mode 100644 client/v2/common/models/simulation_eval_overrides.go diff --git a/client/v2/algod/algod.go b/client/v2/algod/algod.go index fe03e39e..ab0cc50f 100644 --- a/client/v2/algod/algod.go +++ b/client/v2/algod/algod.go @@ -57,6 +57,10 @@ func (c *Client) HealthCheck() *HealthCheck { return &HealthCheck{c: c} } +func (c *Client) GetReady() *GetReady { + return &GetReady{c: c} +} + func (c *Client) GetGenesis() *GetGenesis { return &GetGenesis{c: c} } @@ -109,6 +113,10 @@ func (c *Client) SendRawTransaction(rawtxn []byte) *SendRawTransaction { return &SendRawTransaction{c: c, rawtxn: rawtxn} } +func (c *Client) SimulateTransaction(request models.SimulateRequest) *SimulateTransaction { + return &SimulateTransaction{c: c, request: request} +} + func (c *Client) SuggestedParams() *SuggestedParams { return &SuggestedParams{c: c} } @@ -169,6 +177,14 @@ func (c *Client) TealDryrun(request models.DryrunRequest) *TealDryrun { return &TealDryrun{c: c, request: request} } +func (c *Client) GetBlockTimeStampOffset() *GetBlockTimeStampOffset { + return &GetBlockTimeStampOffset{c: c} +} + +func (c *Client) SetBlockTimeStampOffset(offset uint64) *SetBlockTimeStampOffset { + return &SetBlockTimeStampOffset{c: c, offset: offset} +} + func (c *Client) BlockRaw(round uint64) *BlockRaw { return &BlockRaw{c: c, round: round} } diff --git a/client/v2/algod/getBlockTimeStampOffset.go b/client/v2/algod/getBlockTimeStampOffset.go new file mode 100644 index 00000000..34fd37f8 --- /dev/null +++ b/client/v2/algod/getBlockTimeStampOffset.go @@ -0,0 +1,19 @@ +package algod + +import ( + "context" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/common" + "github.com/algorand/go-algorand-sdk/v2/client/v2/common/models" +) + +// GetBlockTimeStampOffset gets the current timestamp offset. +type GetBlockTimeStampOffset struct { + c *Client +} + +// Do performs the HTTP request +func (s *GetBlockTimeStampOffset) Do(ctx context.Context, headers ...*common.Header) (response models.GetBlockTimeStampOffsetResponse, err error) { + err = s.c.get(ctx, &response, "/v2/devmode/blocks/offset", nil, headers) + return +} diff --git a/client/v2/algod/getReady.go b/client/v2/algod/getReady.go new file mode 100644 index 00000000..998581a0 --- /dev/null +++ b/client/v2/algod/getReady.go @@ -0,0 +1,18 @@ +package algod + +import ( + "context" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/common" +) + +// GetReady returns OK if healthy and fully caught up. +type GetReady struct { + c *Client +} + +// Do performs the HTTP request +func (s *GetReady) Do(ctx context.Context, headers ...*common.Header) (response string, err error) { + err = s.c.get(ctx, &response, "/ready", nil, headers) + return +} diff --git a/client/v2/algod/setBlockTimeStampOffset.go b/client/v2/algod/setBlockTimeStampOffset.go new file mode 100644 index 00000000..7487a472 --- /dev/null +++ b/client/v2/algod/setBlockTimeStampOffset.go @@ -0,0 +1,23 @@ +package algod + +import ( + "context" + "fmt" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/common" +) + +// SetBlockTimeStampOffset sets the timestamp offset (seconds) for blocks in dev +// mode. Providing an offset of 0 will unset this value and try to use the real +// clock for the timestamp. +type SetBlockTimeStampOffset struct { + c *Client + + offset uint64 +} + +// Do performs the HTTP request +func (s *SetBlockTimeStampOffset) Do(ctx context.Context, headers ...*common.Header) (response string, err error) { + err = s.c.post(ctx, &response, fmt.Sprintf("/v2/devmode/blocks/offset/%s", common.EscapeParams(s.offset)...), nil, headers, nil) + return +} diff --git a/client/v2/algod/simulateTransaction.go b/client/v2/algod/simulateTransaction.go new file mode 100644 index 00000000..ef5fdae6 --- /dev/null +++ b/client/v2/algod/simulateTransaction.go @@ -0,0 +1,33 @@ +package algod + +import ( + "context" + + "github.com/algorand/go-algorand-sdk/v2/client/v2/common" + "github.com/algorand/go-algorand-sdk/v2/client/v2/common/models" +) + +// SimulateTransactionParams contains all of the query parameters for url serialization. +type SimulateTransactionParams struct { + + // Format configures whether the response object is JSON or MessagePack encoded. If + // not provided, defaults to JSON. + Format string `url:"format,omitempty"` +} + +// SimulateTransaction simulates a raw transaction or transaction group as it would +// be evaluated on the network. The simulation will use blockchain state from the +// latest committed round. +type SimulateTransaction struct { + c *Client + + request models.SimulateRequest + + p SimulateTransactionParams +} + +// Do performs the HTTP request +func (s *SimulateTransaction) Do(ctx context.Context, headers ...*common.Header) (response models.SimulateResponse, err error) { + err = s.c.post(ctx, &response, "/v2/transactions/simulate", s.p, headers, s.request) + return +} diff --git a/client/v2/common/models/get_block_time_stamp_offset_response.go b/client/v2/common/models/get_block_time_stamp_offset_response.go new file mode 100644 index 00000000..9cb3b9df --- /dev/null +++ b/client/v2/common/models/get_block_time_stamp_offset_response.go @@ -0,0 +1,8 @@ +package models + +// GetBlockTimeStampOffsetResponse response containing the timestamp offset in +// seconds +type GetBlockTimeStampOffsetResponse struct { + // Offset timestamp offset in seconds. + Offset uint64 `json:"offset"` +} diff --git a/client/v2/common/models/node_status_response.go b/client/v2/common/models/node_status_response.go index 00a820f3..b372fedc 100644 --- a/client/v2/common/models/node_status_response.go +++ b/client/v2/common/models/node_status_response.go @@ -79,7 +79,7 @@ type NodeStatusResponse struct { // UpgradeNodeVote this node's upgrade vote UpgradeNodeVote bool `json:"upgrade-node-vote,omitempty"` - // UpgradeVoteRounds total voting ounds for current upgrade + // UpgradeVoteRounds total voting rounds for current upgrade UpgradeVoteRounds uint64 `json:"upgrade-vote-rounds,omitempty"` // UpgradeVotes total votes cast for consensus upgrade diff --git a/client/v2/common/models/pending_transaction_response.go b/client/v2/common/models/pending_transaction_response.go index e8dd79e7..68bbe7b1 100644 --- a/client/v2/common/models/pending_transaction_response.go +++ b/client/v2/common/models/pending_transaction_response.go @@ -26,18 +26,18 @@ type PendingTransactionResponse struct { // ConfirmedRound the round where this transaction was confirmed, if present. ConfirmedRound uint64 `json:"confirmed-round,omitempty"` - // GlobalStateDelta (gd) Global state key/value changes for the application being + // GlobalStateDelta global state key/value changes for the application being // executed by this transaction. GlobalStateDelta []EvalDeltaKeyValue `json:"global-state-delta,omitempty"` // InnerTxns inner transactions produced by application execution. InnerTxns []PendingTransactionResponse `json:"inner-txns,omitempty"` - // LocalStateDelta (ld) Local state key/value changes for the application being - // executed by this transaction. + // LocalStateDelta local state key/value changes for the application being executed + // by this transaction. LocalStateDelta []AccountStateDelta `json:"local-state-delta,omitempty"` - // Logs (lg) Logs for the application being executed by this transaction. + // Logs logs for the application being executed by this transaction. Logs [][]byte `json:"logs,omitempty"` // PoolError indicates that the transaction was kicked out of this node's diff --git a/client/v2/common/models/simulate_request.go b/client/v2/common/models/simulate_request.go new file mode 100644 index 00000000..048ed202 --- /dev/null +++ b/client/v2/common/models/simulate_request.go @@ -0,0 +1,14 @@ +package models + +// SimulateRequest request type for simulation endpoint. +type SimulateRequest struct { + // AllowEmptySignatures allow transactions without signatures to be simulated as if + // they had correct signatures. + AllowEmptySignatures bool `json:"allow-empty-signatures,omitempty"` + + // AllowMoreLogging lifts limits on log opcode usage during simulation. + AllowMoreLogging bool `json:"allow-more-logging,omitempty"` + + // TxnGroups the transaction groups to simulate. + TxnGroups []SimulateRequestTransactionGroup `json:"txn-groups"` +} diff --git a/client/v2/common/models/simulate_request_transaction_group.go b/client/v2/common/models/simulate_request_transaction_group.go new file mode 100644 index 00000000..25126386 --- /dev/null +++ b/client/v2/common/models/simulate_request_transaction_group.go @@ -0,0 +1,9 @@ +package models + +import "github.com/algorand/go-algorand-sdk/v2/types" + +// SimulateRequestTransactionGroup a transaction group to simulate. +type SimulateRequestTransactionGroup struct { + // Txns an atomic transaction group. + Txns []types.SignedTxn `json:"txns"` +} diff --git a/client/v2/common/models/simulate_response.go b/client/v2/common/models/simulate_response.go new file mode 100644 index 00000000..51cdd10c --- /dev/null +++ b/client/v2/common/models/simulate_response.go @@ -0,0 +1,19 @@ +package models + +// SimulateResponse result of a transaction group simulation. +type SimulateResponse struct { + // EvalOverrides the set of parameters and limits override during simulation. If + // this set of parameters is present, then evaluation parameters may differ from + // standard evaluation in certain ways. + EvalOverrides SimulationEvalOverrides `json:"eval-overrides,omitempty"` + + // LastRound the round immediately preceding this simulation. State changes through + // this round were used to run this simulation. + LastRound uint64 `json:"last-round"` + + // TxnGroups a result object for each transaction group that was simulated. + TxnGroups []SimulateTransactionGroupResult `json:"txn-groups"` + + // Version the version of this response object. + Version uint64 `json:"version"` +} diff --git a/client/v2/common/models/simulate_transaction_group_result.go b/client/v2/common/models/simulate_transaction_group_result.go new file mode 100644 index 00000000..43429fc5 --- /dev/null +++ b/client/v2/common/models/simulate_transaction_group_result.go @@ -0,0 +1,25 @@ +package models + +// SimulateTransactionGroupResult simulation result for an atomic transaction group +type SimulateTransactionGroupResult struct { + // AppBudgetAdded total budget added during execution of app calls in the + // transaction group. + AppBudgetAdded uint64 `json:"app-budget-added,omitempty"` + + // AppBudgetConsumed total budget consumed during execution of app calls in the + // transaction group. + AppBudgetConsumed uint64 `json:"app-budget-consumed,omitempty"` + + // FailedAt if present, indicates which transaction in this group caused the + // failure. This array represents the path to the failing transaction. Indexes are + // zero based, the first element indicates the top-level transaction, and + // successive elements indicate deeper inner transactions. + FailedAt []uint64 `json:"failed-at,omitempty"` + + // FailureMessage if present, indicates that the transaction group failed and + // specifies why that happened + FailureMessage string `json:"failure-message,omitempty"` + + // TxnResults simulation result for individual transactions + TxnResults []SimulateTransactionResult `json:"txn-results"` +} diff --git a/client/v2/common/models/simulate_transaction_result.go b/client/v2/common/models/simulate_transaction_result.go new file mode 100644 index 00000000..3dab4b68 --- /dev/null +++ b/client/v2/common/models/simulate_transaction_result.go @@ -0,0 +1,15 @@ +package models + +// SimulateTransactionResult simulation result for an individual transaction +type SimulateTransactionResult struct { + // AppBudgetConsumed budget used during execution of an app call transaction. This + // value includes budged used by inner app calls spawned by this transaction. + AppBudgetConsumed uint64 `json:"app-budget-consumed,omitempty"` + + // LogicSigBudgetConsumed budget used during execution of a logic sig transaction. + LogicSigBudgetConsumed uint64 `json:"logic-sig-budget-consumed,omitempty"` + + // TxnResult details about a pending transaction. If the transaction was recently + // confirmed, includes confirmation details like the round and reward details. + TxnResult PendingTransactionResponse `json:"txn-result"` +} diff --git a/client/v2/common/models/simulation_eval_overrides.go b/client/v2/common/models/simulation_eval_overrides.go new file mode 100644 index 00000000..59510676 --- /dev/null +++ b/client/v2/common/models/simulation_eval_overrides.go @@ -0,0 +1,16 @@ +package models + +// SimulationEvalOverrides the set of parameters and limits override during +// simulation. If this set of parameters is present, then evaluation parameters may +// differ from standard evaluation in certain ways. +type SimulationEvalOverrides struct { + // AllowEmptySignatures if true, transactions without signatures are allowed and + // simulated as if they were properly signed. + AllowEmptySignatures bool `json:"allow-empty-signatures,omitempty"` + + // MaxLogCalls the maximum log calls one can make during simulation + MaxLogCalls uint64 `json:"max-log-calls,omitempty"` + + // MaxLogSize the maximum byte number to log during simulation + MaxLogSize uint64 `json:"max-log-size,omitempty"` +} diff --git a/test/algodclientv2_test.go b/test/algodclientv2_test.go index 350453de..def58c4c 100644 --- a/test/algodclientv2_test.go +++ b/test/algodclientv2_test.go @@ -55,9 +55,12 @@ func AlgodClientV2Context(s *godog.Suite) { s.Step(`^we make a GetStateProof call for round (\d+)$`, weMakeAGetStateProofCallForRound) s.Step(`^we make a GetTransactionProof call for round (\d+) txid "([^"]*)" and hashtype "([^"]*)"$`, weMakeAGetTransactionProofCallForRoundTxidAndHashtype) s.Step(`^we make a Lookup Block Hash call against round (\d+)$`, weMakeALookupBlockHashCallAgainstRound) + s.Step(`^we make a Ready call$`, weMakeAReadyCall) s.Step(`^we make a SetSyncRound call against round (\d+)$`, weMakeASetSyncRoundCallAgainstRound) s.Step(`^we make a GetSyncRound call$`, weMakeAGetSyncRoundCall) s.Step(`^we make a UnsetSyncRound call$`, weMakeAUnsetSyncRoundCall) + s.Step(`^we make a SetBlockTimeStampOffset call against offset (\d+)$`, weMakeASetBlockTimeStampOffsetCallAgainstOffset) + s.Step(`^we make a GetBlockTimeStampOffset call$`, weMakeAGetBlockTimeStampOffsetCall) s.BeforeScenario(func(interface{}) { globalErrForExamination = nil @@ -309,6 +312,15 @@ func weMakeALookupBlockHashCallAgainstRound(round int) error { return nil } +func weMakeAReadyCall() error { + algodClient, err := algod.MakeClient(mockServer.URL, "") + if err != nil { + return err + } + algodClient.GetReady().Do(context.Background()) + return nil +} + func weMakeASetSyncRoundCallAgainstRound(round int) error { algodClient, err := algod.MakeClient(mockServer.URL, "") if err != nil { @@ -335,3 +347,20 @@ func weMakeAUnsetSyncRoundCall() error { algodClient.UnsetSyncRound().Do(context.Background()) return nil } +func weMakeASetBlockTimeStampOffsetCallAgainstOffset(offset int) error { + algodClient, err := algod.MakeClient(mockServer.URL, "") + if err != nil { + return err + } + algodClient.SetBlockTimeStampOffset(uint64(offset)).Do(context.Background()) + return nil +} + +func weMakeAGetBlockTimeStampOffsetCall() error { + algodClient, err := algod.MakeClient(mockServer.URL, "") + if err != nil { + return err + } + algodClient.GetBlockTimeStampOffset().Do(context.Background()) + return nil +} diff --git a/test/responses_unit_test.go b/test/responses_unit_test.go index f1b70d4a..7b9a6d28 100644 --- a/test/responses_unit_test.go +++ b/test/responses_unit_test.go @@ -181,6 +181,9 @@ func weMakeAnyCallTo(client /* algod/indexer */, endpoint string) (err error) { case "GetSyncRound": response, err = algodC.GetSyncRound().Do(context.Background()) + case "GetBlockTimeStampOffset": + response, err = + algodC.GetBlockTimeStampOffset().Do(context.Background()) case "any": // This is an error case // pickup the error as the response diff --git a/test/unit.tags b/test/unit.tags index 6d0ef444..24c201b2 100644 --- a/test/unit.tags +++ b/test/unit.tags @@ -1,11 +1,11 @@ @unit.abijson @unit.abijson.byname @unit.algod -@unit.blocksummary @unit.algod.ledger_refactoring @unit.applications @unit.applications.boxes @unit.atomic_transaction_composer +@unit.blocksummary @unit.dryrun @unit.dryrun.trace.application @unit.feetest @@ -15,6 +15,7 @@ @unit.indexer.rekey @unit.offline @unit.program_sanity_check +@unit.ready @unit.rekey @unit.responses @unit.responses.231 @@ -23,11 +24,15 @@ @unit.responses.messagepack @unit.responses.messagepack.231 @unit.responses.participationupdates +@unit.responses.sync +@unit.responses.timestamp @unit.responses.unlimited_assets @unit.sourcemap @unit.stateproof.paths @unit.stateproof.responses +@unit.sync @unit.tealsign +@unit.timestamp @unit.transactions @unit.transactions.keyreg @unit.transactions.payment From 9333dc8c16bea75b598a1118ef14ec34e3672e15 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 1 May 2023 08:36:18 -0400 Subject: [PATCH 12/16] Regenerate code from specification file (#522) Co-authored-by: Algorand Generation Bot --- client/v2/algod/getApplicationBoxByName.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/v2/algod/getApplicationBoxByName.go b/client/v2/algod/getApplicationBoxByName.go index 1058fd40..87c8ed8e 100644 --- a/client/v2/algod/getApplicationBoxByName.go +++ b/client/v2/algod/getApplicationBoxByName.go @@ -18,11 +18,11 @@ type GetApplicationBoxByNameParams struct { Name string `url:"name,omitempty"` } -// GetApplicationBoxByName given an application ID and box name, it returns the box -// name and value (each base64 encoded). Box names must be in the goal app call arg -// encoding form 'encoding:value'. For ints, use the form 'int:1234'. For raw -// bytes, use the form 'b64:A=='. For printable strings, use the form 'str:hello'. -// For addresses, use the form 'addr:XYZ...'. +// GetApplicationBoxByName given an application ID and box name, it returns the +// round, box name, and value (each base64 encoded). Box names must be in the goal +// app call arg encoding form 'encoding:value'. For ints, use the form 'int:1234'. +// For raw bytes, use the form 'b64:A=='. For printable strings, use the form +// 'str:hello'. For addresses, use the form 'addr:XYZ...'. type GetApplicationBoxByName struct { c *Client From 8a9ce2d5556c167602a38d6eea9ad680d1b3e4d1 Mon Sep 17 00:00:00 2001 From: Patrick Bennett Date: Wed, 3 May 2023 13:34:27 -0400 Subject: [PATCH 13/16] Performance: Add MakeClientWithTransport client override that allows the user to pass a custom http transport (#520) * Add MakeClientWithTransport client override that allows the user to pass a custom http RoundTripper (transport) The sdk previously only used the default http client and the default client, using the default transport has severe limitations for apps making multiple connections to the same host. This new method will allow users to pass a new Transport instance which has new values for things like MaxIdleConnsPerHost and MaxIdleConns. * Remove unicode character accidentally inserted into a comment. * Add generated files * Regenerate import for indexer --------- Co-authored-by: algochoi <86622919+algochoi@users.noreply.github.com> --- client/v2/algod/algod.go | 9 +++++++++ client/v2/common/common.go | 25 ++++++++++++++++++++----- client/v2/indexer/indexer.go | 9 +++++++++ examples/overview/main.go | 17 +++++++++++++++++ 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/client/v2/algod/algod.go b/client/v2/algod/algod.go index ab0cc50f..00c27afb 100644 --- a/client/v2/algod/algod.go +++ b/client/v2/algod/algod.go @@ -2,6 +2,7 @@ package algod import ( "context" + "net/http" "github.com/algorand/go-algorand-sdk/v2/client/v2/common" "github.com/algorand/go-algorand-sdk/v2/client/v2/common/models" @@ -53,6 +54,14 @@ func MakeClientWithHeaders(address string, apiToken string, headers []*common.He return } +// MakeClientWithTransport is the factory for constructing a Client for a given endpoint with a +// custom HTTP Transport as well as optional additional user defined headers. +func MakeClientWithTransport(address string, apiToken string, headers []*common.Header, transport http.RoundTripper) (c *Client, err error) { + commonClientWithTransport, err := common.MakeClientWithTransport(address, authHeader, apiToken, headers, transport) + c = (*Client)(commonClientWithTransport) + return +} + func (c *Client) HealthCheck() *HealthCheck { return &HealthCheck{c: c} } diff --git a/client/v2/common/common.go b/client/v2/common/common.go index f3987615..538fc63a 100644 --- a/client/v2/common/common.go +++ b/client/v2/common/common.go @@ -3,7 +3,6 @@ package common import ( "bytes" "context" - "fmt" "io" "io/ioutil" @@ -35,6 +34,7 @@ type Client struct { apiHeader string apiToken string headers []*Header + transport http.RoundTripper } // MakeClient is the factory for constructing a Client for a given endpoint. @@ -64,6 +64,19 @@ func MakeClientWithHeaders(address string, apiHeader, apiToken string, headers [ return } +// MakeClientWithTransport is the factory for constructing a Client for a given endpoint with a +// custom HTTP Transport as well as optional additional user defined headers. +func MakeClientWithTransport(address string, apiHeader, apiToken string, headers []*Header, transport http.RoundTripper) (c *Client, err error) { + c, err = MakeClientWithHeaders(address, apiHeader, apiToken, headers) + if err != nil { + return + } + + c.transport = transport + + return +} + type BadRequest error type InvalidToken error type NotFound error @@ -108,9 +121,11 @@ func (client *Client) submitFormRaw(ctx context.Context, path string, params int queryURL := client.serverURL queryURL.Path += path - var req *http.Request - var bodyReader io.Reader - var v url.Values + var ( + req *http.Request + bodyReader io.Reader + v url.Values + ) if params != nil { v, err = query.Values(params) @@ -148,7 +163,7 @@ func (client *Client) submitFormRaw(ctx context.Context, path string, params int req.Header.Add(header.Key, header.Value) } - httpClient := &http.Client{} + httpClient := &http.Client{Transport: client.transport} req = req.WithContext(ctx) resp, err = httpClient.Do(req) diff --git a/client/v2/indexer/indexer.go b/client/v2/indexer/indexer.go index 0ead745f..e0e5cf2c 100644 --- a/client/v2/indexer/indexer.go +++ b/client/v2/indexer/indexer.go @@ -2,6 +2,7 @@ package indexer import ( "context" + "net/http" "github.com/algorand/go-algorand-sdk/v2/client/v2/common" ) @@ -52,6 +53,14 @@ func MakeClientWithHeaders(address string, apiToken string, headers []*common.He return } +// MakeClientWithTransport is the factory for constructing a Client for a given endpoint with a +// custom HTTP Transport as well as optional additional user defined headers. +func MakeClientWithTransport(address string, apiToken string, headers []*common.Header, transport http.RoundTripper) (c *Client, err error) { + commonClientWithTransport, err := common.MakeClientWithTransport(address, authHeader, apiToken, headers, transport) + c = (*Client)(commonClientWithTransport) + return +} + func (c *Client) HealthCheck() *HealthCheck { return &HealthCheck{c: c} } diff --git a/examples/overview/main.go b/examples/overview/main.go index 78ac009f..b6fc4cda 100644 --- a/examples/overview/main.go +++ b/examples/overview/main.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "net/http" "strings" "github.com/algorand/go-algorand-sdk/v2/client/v2/algod" @@ -34,8 +35,24 @@ func main() { algodToken, []*common.Header{&algodHeader}, ) + + // Or, for better performance, pass a custom Transport, in this case allowing + // up to 100 simultaneous connections to the same host (ie: an algod node) + // Clone Go's default transport settings but increase connection values + customTransport := http.DefaultTransport.(*http.Transport).Clone() + customTransport.MaxIdleConns = 100 + customTransport.MaxConnsPerHost = 100 + customTransport.MaxIdleConnsPerHost = 100 + + algodClientWithCustomTransport, _ := algod.MakeClientWithTransport( + algodAddress, + algodToken, + nil, // accepts additional headers like MakeClientWithHeaders + customTransport, + ) // example: ALGOD_CREATE_CLIENT + _ = algodClientWithCustomTransport _ = algodClientWithHeaders _ = algodClient From 04b5d2dae4829adc9d9457d05ee69305afefada3 Mon Sep 17 00:00:00 2001 From: John Lee Date: Thu, 4 May 2023 10:54:33 -0400 Subject: [PATCH 14/16] DevOps: Add CODEOWNERS to restrict workflow editing (#524) --- .github/workflows/pr-type-category.yml | 4 +++- CODEOWNERS | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 CODEOWNERS diff --git a/.github/workflows/pr-type-category.yml b/.github/workflows/pr-type-category.yml index 0ae6369c..c33fce94 100644 --- a/.github/workflows/pr-type-category.yml +++ b/.github/workflows/pr-type-category.yml @@ -17,8 +17,10 @@ jobs: labels: "New Feature, Enhancement, Bug-Fix, Not-Yet-Enabled, Skip-Release-Notes" - name: "Checking for PR Category in PR title. Should be like ': '." + env: + PR_TITLE: ${{ github.event.pull_request.title }} run: | - if [[ ! "${{ github.event.pull_request.title }}" =~ ^.{2,}\:.{2,} ]]; then + if [[ ! "$PR_TITLE" =~ ^.{2,}\:.{2,} ]]; then echo "## PR Category is missing from PR title. Please add it like ': '." >> GITHUB_STEP_SUMMARY exit 1 fi diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..aa26c82a --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +.github/ @algorand/dev +.circleci/ @algorand/dev From 5800071cee4bbe5d5e8830a64a0b826e43cd8fa5 Mon Sep 17 00:00:00 2001 From: algochoi <86622919+algochoi@users.noreply.github.com> Date: Thu, 4 May 2023 15:41:00 -0400 Subject: [PATCH 15/16] Performance: Add custom http transport to MakeClientWithTransport (#523) * Add MakeClientWithTransport client override that allows the user to pass a custom http RoundTripper (transport) The sdk previously only used the default http client and the default client, using the default transport has severe limitations for apps making multiple connections to the same host. This new method will allow users to pass a new Transport instance which has new values for things like MaxIdleConnsPerHost and MaxIdleConns. * Remove unicode character accidentally inserted into a comment. * Add generated files * Regenerate import for indexer * Add test for new client method * Refactor and cleanup test --- client/v2/common/common_test.go | 35 +++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/client/v2/common/common_test.go b/client/v2/common/common_test.go index a9a27bb1..375041dc 100644 --- a/client/v2/common/common_test.go +++ b/client/v2/common/common_test.go @@ -6,6 +6,7 @@ import ( "net/http" "net/http/httptest" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -81,3 +82,37 @@ func TestClient_Verbs(t *testing.T) { }) } } + +func TestClientWithTransport(t *testing.T) { + var receivedMethod string + var receivedPath string + var receivedHeaderValue string + path := "/some/path" + + const headerKey string = "hello" + const headerValue string = "world" + + mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + receivedMethod = r.Method + receivedPath = r.URL.String() + receivedHeaderValue = r.Header.Get(headerKey) + })) + + var header []*Header = []*Header{{Key: headerKey, Value: headerValue}} + var customTransport http.RoundTripper = &http.Transport{ + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + } + c, err := MakeClientWithTransport(mockServer.URL, "API-Header", "ASDF", header, customTransport) + require.NoError(t, err) + + // Call the test function. + err = c.Get(context.Background(), nil, path, nil, nil) + assert.Equal(t, "GET", receivedMethod) + assert.Equal(t, path, receivedPath) + assert.Equal(t, headerValue, receivedHeaderValue) + assert.Equal(t, c.transport, customTransport) +} From 9aa610b32289e28488797639e6da4fdd6b249693 Mon Sep 17 00:00:00 2001 From: Arthur Kepler <610274+excalq@users.noreply.github.com> Date: Mon, 8 May 2023 18:28:20 -0400 Subject: [PATCH 16/16] Updated CHANGELOG.md --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf232f3..777858fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +# 2.1.0 + +## What's Changed + +Supports new devmode block timestamp offset endpoints. + +### Bugfixes +* bugfix: Fix wrong response error type. by @winder in https://github.com/algorand/go-algorand-sdk/pull/461 +* debug: Remove debug output. by @winder in https://github.com/algorand/go-algorand-sdk/pull/465 +* bug: Fix extractError parsing by @Eric-Warehime in https://github.com/algorand/go-algorand-sdk/pull/492 +### Enhancements +* enhancement: add genesis type by @shiqizng in https://github.com/algorand/go-algorand-sdk/pull/443 +* docs: Update README.md by @algochoi in https://github.com/algorand/go-algorand-sdk/pull/460 +* tests: Add disassembly test for Go SDK by @algochoi in https://github.com/algorand/go-algorand-sdk/pull/462 +* marshaling: Lenient Address and BlockHash go-codec unmarshalers. by @winder in https://github.com/algorand/go-algorand-sdk/pull/464 +* API: Add types for ledgercore.StateDelta. by @winder in https://github.com/algorand/go-algorand-sdk/pull/467 +* docs: Create runnable examples to be pulled into docs by @barnjamin in https://github.com/algorand/go-algorand-sdk/pull/480 +* Docs: Examples by @barnjamin in https://github.com/algorand/go-algorand-sdk/pull/491 +* api: Regenerate client interfaces for timestamp, ready, and simulate endpoints by @algochoi in https://github.com/algorand/go-algorand-sdk/pull/513 +* Performance: Add MakeClientWithTransport client override that allows the user to pass a custom http transport by @pbennett in https://github.com/algorand/go-algorand-sdk/pull/520 +* DevOps: Add CODEOWNERS to restrict workflow editing by @onetechnical in https://github.com/algorand/go-algorand-sdk/pull/524 +* Performance: Add custom http transport to MakeClientWithTransport by @algochoi in https://github.com/algorand/go-algorand-sdk/pull/523 +### Other +* Regenerate code with the latest specification file (c90fd645) by @github-actions in https://github.com/algorand/go-algorand-sdk/pull/522 + +## New Contributors +* @pbennett made their first contribution in https://github.com/algorand/go-algorand-sdk/pull/520 + +**Full Changelog**: https://github.com/algorand/go-algorand-sdk/compare/v2.0.0...v2.1.0 + # 2.0.0 ## What's Changed