diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4d16170..3e21e3b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,9 +23,23 @@ jobs: restore-keys: | ${{ runner.os }}-go- + - uses: dfinity/setup-dfx@main + - name: Pull ganache-cli image run: docker pull ${{ env.ganache-image }} + - name: Payment Channel ICP + working-directory: payment-channel-icp + run: | + chmod +x ./startdeploy.sh + chmod +x ./startdfx.sh + chmod +x ./stopdfx.sh + dfx start & + ./startdeploy.sh + + sleep 30 + go run ./ + - name: Simple Client working-directory: simple-client env: diff --git a/payment-channel-icp/go.mod b/payment-channel-icp/go.mod new file mode 100644 index 0000000..216d451 --- /dev/null +++ b/payment-channel-icp/go.mod @@ -0,0 +1,32 @@ +module perun.network/perun-examples/payment-channel-icp + +go 1.22 + +require ( + github.com/aviate-labs/agent-go v0.3.0-alpha.1 + perun.network/go-perun v0.10.6 + perun.network/perun-icp-backend v0.0.0-20230818121554-97eb2fa7924e +) + +require ( + github.com/aviate-labs/leb128 v0.3.0 // indirect + github.com/aviate-labs/secp256k1 v0.0.0-5e6736a // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/di-wu/parser v0.3.0 // indirect + github.com/fxamacker/cbor/v2 v2.4.0 // indirect + github.com/google/uuid v1.1.5 // indirect + github.com/herumi/bls-go-binary v1.35.1 // indirect + github.com/oasisprotocol/curve25519-voi v0.0.0-20230110094441-db37f07504ce // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/stretchr/testify v1.8.2 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/crypto v0.7.0 // indirect + golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.6.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + perun.network/perun-demo-tui v0.0.0-20230321094013-3e474bfabc8f // indirect + polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37 // indirect +) diff --git a/payment-channel-icp/go.sum b/payment-channel-icp/go.sum new file mode 100644 index 0000000..a3e1c3f --- /dev/null +++ b/payment-channel-icp/go.sum @@ -0,0 +1,71 @@ +github.com/aviate-labs/agent-go v0.3.0-alpha.1 h1:DzJNW5WMpM/eJ6ksMiuZjlfEttXSYIGeiq6IzsWlA50= +github.com/aviate-labs/agent-go v0.3.0-alpha.1/go.mod h1:fy9UekeyeJEq17Cj01Snq23B4HqVyar03wu3uk1eL9U= +github.com/aviate-labs/leb128 v0.3.0 h1:s9htRv3OYk8nuHqJu9PiVFJxv1jIUTIcpEeiURa91uQ= +github.com/aviate-labs/leb128 v0.3.0/go.mod h1:GclhBOjhIKmcDlgHKhj0AEZollzERfZUbcRUKiQVqgY= +github.com/aviate-labs/secp256k1 v0.0.0-5e6736a h1:aQkG/D+l8Y7tr809l8pN+KebH2jzacWReSFQmeEKFgM= +github.com/aviate-labs/secp256k1 v0.0.0-5e6736a/go.mod h1:C/lr3F9TimrVkdZckG5mz+VU0TrmpeyVKUjzv2YyGwA= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/di-wu/parser v0.3.0 h1:NMOvy5ifswgt4gsdhySVcKOQtvjC43cHZIfViWctqQY= +github.com/di-wu/parser v0.3.0/go.mod h1:SLp58pW6WamdmznrVRrw2NTyn4wAvT9rrEFynKX7nYo= +github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88= +github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo= +github.com/google/uuid v1.1.5 h1:kxhtnfFVi+rYdOALN0B3k9UT86zVJKfBimRaciULW4I= +github.com/google/uuid v1.1.5/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/herumi/bls-eth-go-binary v1.36.1 h1:SfLjxbO1fWkKtKS7J3Ezd1/5QXrcaTZgWynxdSe10hQ= +github.com/herumi/bls-eth-go-binary v1.36.1/go.mod h1:luAnRm3OsMQeokhGzpYmc0ZKwawY7o87PUEP11Z7r7U= +github.com/herumi/bls-go-binary v1.28.2 h1:F0AezsC0M1a9aZjk7g0l2hMb1F56Xtpfku97pDndNZE= +github.com/herumi/bls-go-binary v1.28.2/go.mod h1:O4Vp1AfR4raRGwFeQpr9X/PQtncEicMoOe6BQt1oX0Y= +github.com/herumi/bls-go-binary v1.35.1 h1:bCt7bZADjrpFY2sUw/Nx3FE3KuJBssLE4Y12dp4RSRo= +github.com/herumi/bls-go-binary v1.35.1/go.mod h1:O4Vp1AfR4raRGwFeQpr9X/PQtncEicMoOe6BQt1oX0Y= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230110094441-db37f07504ce h1:/pEpMk55wH0X+E5zedGEMOdLuWmV8P4+4W3+LZaM6kg= +github.com/oasisprotocol/curve25519-voi v0.0.0-20230110094441-db37f07504ce/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= +golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +perun.network/go-perun v0.10.6 h1:uj1e33yfCSfE75DK/uwjNp+TwvGG85Qhi6HuYQ9EPrQ= +perun.network/go-perun v0.10.6/go.mod h1:BGBZC3npkX457u87pjDd0NEIXr1a4dsH4H/YpLdGGe8= +perun.network/perun-demo-tui v0.0.0-20230321094013-3e474bfabc8f h1:o38fexcR2G0HH+soMVmpV42oCBidgv9YPgeFwe+7jvI= +perun.network/perun-demo-tui v0.0.0-20230321094013-3e474bfabc8f/go.mod h1:GtxsCSD83BFAME680zlg4ifoi2WVfqj2+rntxbdJCz8= +perun.network/perun-icp-backend v0.0.0-20230818121554-97eb2fa7924e h1:VIltGjFbL/xOrxYKh/E1mbx6+q+zVfx+KSU4vEKJDGk= +perun.network/perun-icp-backend v0.0.0-20230818121554-97eb2fa7924e/go.mod h1:zZr7kz3T24VECiSHqWkejFLaUwPC5D1pUlEHrA6I01k= +polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37 h1:iA5GzEa/hHfVlQpimEjPV09NATwHXxSjWNB0VVodtew= +polycry.pt/poly-go v0.0.0-20220301085937-fb9d71b45a37/go.mod h1:XUBrNtqgEhN3EEOP/5gh7IBd3xVHKidCjXDZfl9+kMU= diff --git a/payment-channel-icp/main.go b/payment-channel-icp/main.go new file mode 100644 index 0000000..d3071c2 --- /dev/null +++ b/payment-channel-icp/main.go @@ -0,0 +1,87 @@ +package main + +import ( + "fmt" + "log" + + "perun.network/go-perun/channel" + "perun.network/go-perun/wire" + "perun.network/perun-icp-backend/client" + "perun.network/perun-icp-backend/wallet" +) + +const ( + Host = "http://127.0.0.1" + Port = 4943 + perunPrincipal = "be2us-64aaa-aaaaa-qaabq-cai" + ledgerPrincipal = "bkyz2-fmaaa-aaaaa-qaaaq-cai" + userAId = "97520b79b03e38d3f6b38ce5026a813ccc9d1a3e830edb6df5970e6ca6ad84be" + userBId = "40fd2dc85bc7d264b31f1fa24081d7733d303b49b7df84e3d372338f460aa678" + userAPemPath = "./userdata/identities/usera_identity.pem" + userBPemPath = "./userdata/identities/userb_identity.pem" + channelCollateral = 50000 +) + +func main() { + + log.Println("Setting up wallets for Alice and Bob") + perunWalletAlice := wallet.NewWallet() + perunWalletBob := wallet.NewWallet() + + log.Println("Create communication channel between Alice and Bob") + bus := wire.NewLocalBus() + + log.Println("Setting up Payment Clients") + alice, err := client.SetupPaymentClient("Alice", perunWalletAlice, bus, perunPrincipal, ledgerPrincipal, Host, Port, userAPemPath) + if err != nil { + panic(err) + } + + bob, err := client.SetupPaymentClient("Bob", perunWalletBob, bus, perunPrincipal, ledgerPrincipal, Host, Port, userBPemPath) + if err != nil { + panic(err) + } + + log.Println("Alice opens Channel with Bob") + alice.OpenChannel(bob.WireAddress(), channelCollateral) + achan := alice.Channel + log.Println("Bob accepts Channel from Alice") + bob.AcceptedChannel() + bchan := bob.Channel + + log.Println("Initial Balances in the Channel") + printBalances(achan.GetChannelState().Balances) + + // sending payment/s + + log.Println("Sending payments...") + achan.SendPayment(1000) + + log.Println("Balance after first Payment:") + printBalances(achan.GetChannelState().Balances) + + bchan.SendPayment(2000) + + log.Println("Balance after second Payment:") + printBalances(achan.GetChannelState().Balances) + + log.Println("Settle channel") + achan.Settle() + + log.Println("Final Balance after settling:") + printBalances(achan.GetChannelState().Balances) + + log.Println("Shutdown Channel") + alice.Shutdown() + bob.Shutdown() + log.Println("Done") +} + +func printBalances(balances channel.Balances) { + log.Println("Balances:") + for i, assetBalances := range balances { + fmt.Printf("Asset %d:\n", i+1) + fmt.Printf(" Alice: %s\n", assetBalances[0].String()) // Access balance for Alice + fmt.Printf(" Bob: %s\n", assetBalances[1].String()) // Access balance for Bob + } +} diff --git a/payment-channel-icp/startdeploy.sh b/payment-channel-icp/startdeploy.sh new file mode 100644 index 0000000..9f5a04b --- /dev/null +++ b/payment-channel-icp/startdeploy.sh @@ -0,0 +1,77 @@ +#!/bin/bash + +# Get the directory of the current script +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +# Define the function to create the ledger argument +createLedgerArg() { + local ICP_PERUN_MINT_ACC="433bd8e9dd65bdfb34259667578e749136f3e0ea1566e10af1e0dd324cbd9144" + local ICP_PERUN_USERA_ACC="97520b79b03e38d3f6b38ce5026a813ccc9d1a3e830edb6df5970e6ca6ad84be" + local ICP_PERUN_USERB_ACC="40fd2dc85bc7d264b31f1fa24081d7733d303b49b7df84e3d372338f460aa678" + + echo "(record {minting_account = \"$ICP_PERUN_MINT_ACC\"; initial_values = vec { record { \"$ICP_PERUN_USERA_ACC\"; record { e8s=10_000_000} }; record { \"$ICP_PERUN_USERB_ACC\"; record { e8s=10_000_000 } }}; send_whitelist = vec {}})" +} + +ICP_PERUN_PRINCIPAL="be2us-64aaa-aaaaa-qaabq-cai" +ICP_LEDGER_PRINCIPAL="bkyz2-fmaaa-aaaaa-qaaaq-cai" + +# Exporting them so that they can be accessed by other commands or scripts invoked after this script +export ICP_PERUN_PRINCIPAL +export ICP_LEDGER_PRINCIPAL + +# Define the function to deploy Perun +deployPerun() { + local execPath=$1 + + path=$(which dfx) + if [ -z "$path" ]; then + echo "Error: dfx not found in PATH" + return 1 + fi + + echo "Deploying Perun" + cd $execPath + deployMsg=$($path deploy icp_perun 2>&1) + status=$? + + if [ $status -ne 0 ]; then + echo "Error deploying icp_perun:\n$deployMsg\n" + return $status + else + echo "$deployMsg" + return 0 + fi +} + +# Define the function to deploy the ledger +deployLedger() { + local execPath=$1 + local ledgerArg=$2 + + path=$(which dfx) + if [ -z "$path" ]; then + echo "Error: dfx not found in PATH" + return 1 + fi + + echo "Deploying the Ledger with the following parameters: $ledgerArg" + cd $execPath + outputLedger=$($path deploy ledger --argument "$ledgerArg" 2>&1) + status=$? + + if [ $status -ne 0 ]; then + echo "Error deploying ledger:\n$outputLedger\n" + return $status + else + echo "$outputLedger" + return 0 + fi +} + +# Call the functions +ledgerArg=$(createLedgerArg) +./startdfx.sh +execPath="$DIR/userdata" + +deployLedger $execPath "$ledgerArg" +deployPerun $execPath diff --git a/payment-channel-icp/startdfx.sh b/payment-channel-icp/startdfx.sh new file mode 100644 index 0000000..962f51c --- /dev/null +++ b/payment-channel-icp/startdfx.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +startDFX() { + path=$(which dfx) + if [ -z "$path" ]; then + echo "Error: dfx not found in PATH" + return 1 + fi + execPath="./userdata" + cd $execPath + $path start --background --clean --host 127.0.0.1:4943 & + status=$? + + # Sleep to allow process to start + sleep 3 + + if [ $status -ne 0 ]; then + echo "Error starting DFX..." + return $status + else + echo "Starting DFX..." + return $! + fi +} + +# Call the function +startDFX diff --git a/payment-channel-icp/stopdfx.sh b/payment-channel-icp/stopdfx.sh new file mode 100644 index 0000000..7436636 --- /dev/null +++ b/payment-channel-icp/stopdfx.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# Send a SIGTERM signal to all processes that have 'dfx' in their command line +pkill -f dfx +dfx_status=$? + +# Send a SIGTERM signal to all processes that have 'icx' in their command line +pkill -f icx +icx_status=$? + +if [ $dfx_status -ne 0 ] || [ $icx_status -ne 0 ]; then + echo "Error stopping DFX or ICX..." + exit 1 +else + echo "DFX and ICX processes stopped." +fi \ No newline at end of file diff --git a/payment-channel-icp/userdata/.dfx/network/local/ic-btc-adapter-config.json b/payment-channel-icp/userdata/.dfx/network/local/ic-btc-adapter-config.json new file mode 100644 index 0000000..e69de29 diff --git a/payment-channel-icp/userdata/.dfx/network/local/ic-btc-adapter-pid b/payment-channel-icp/userdata/.dfx/network/local/ic-btc-adapter-pid new file mode 100644 index 0000000..e69de29 diff --git a/payment-channel-icp/userdata/.dfx/network/local/ic-canister-http-config.json b/payment-channel-icp/userdata/.dfx/network/local/ic-canister-http-config.json new file mode 100644 index 0000000..63dae14 --- /dev/null +++ b/payment-channel-icp/userdata/.dfx/network/local/ic-canister-http-config.json @@ -0,0 +1,8 @@ +{ + "incoming_source": { + "Path": "/tmp/ic-https-outcalls-adapter-socket.4513.1726666364" + }, + "logger": { + "level": "error" + } +} \ No newline at end of file diff --git a/payment-channel-icp/userdata/.dfx/network/local/ic-canister-http-socket-path b/payment-channel-icp/userdata/.dfx/network/local/ic-canister-http-socket-path new file mode 100644 index 0000000..07a98ce --- /dev/null +++ b/payment-channel-icp/userdata/.dfx/network/local/ic-canister-http-socket-path @@ -0,0 +1 @@ +/tmp/ic-https-outcalls-adapter-socket.4513.1726666364 \ No newline at end of file diff --git a/payment-channel-icp/userdata/.dfx/network/local/ic-https-outcalls-adapter-pid b/payment-channel-icp/userdata/.dfx/network/local/ic-https-outcalls-adapter-pid new file mode 100644 index 0000000..e873683 --- /dev/null +++ b/payment-channel-icp/userdata/.dfx/network/local/ic-https-outcalls-adapter-pid @@ -0,0 +1 @@ +4527 \ No newline at end of file diff --git a/payment-channel-icp/userdata/.dfx/network/local/network-id b/payment-channel-icp/userdata/.dfx/network/local/network-id new file mode 100644 index 0000000..a07dfe8 --- /dev/null +++ b/payment-channel-icp/userdata/.dfx/network/local/network-id @@ -0,0 +1,4 @@ +{ + "created": "2024-09-18 13:32:44.22426929 +00:00:00", + "settings_digest": "bca6309a6dca651e73d342cea2b80adefb1f5eff7266f958af3f66fb1da2fff1" +} \ No newline at end of file diff --git a/payment-channel-icp/userdata/.dfx/network/local/pid b/payment-channel-icp/userdata/.dfx/network/local/pid new file mode 100644 index 0000000..49f27dc --- /dev/null +++ b/payment-channel-icp/userdata/.dfx/network/local/pid @@ -0,0 +1 @@ +4513 \ No newline at end of file diff --git a/payment-channel-icp/userdata/.dfx/network/local/pocket-ic-port b/payment-channel-icp/userdata/.dfx/network/local/pocket-ic-port new file mode 100644 index 0000000..e69de29 diff --git a/payment-channel-icp/userdata/.dfx/network/local/pocket-ic-proxy-pid b/payment-channel-icp/userdata/.dfx/network/local/pocket-ic-proxy-pid new file mode 100644 index 0000000..e69de29 diff --git a/payment-channel-icp/userdata/.dfx/network/local/pocket-ic-proxy-port b/payment-channel-icp/userdata/.dfx/network/local/pocket-ic-proxy-port new file mode 100644 index 0000000..e69de29 diff --git a/payment-channel-icp/userdata/.dfx/network/local/replica-configuration/replica-pid b/payment-channel-icp/userdata/.dfx/network/local/replica-configuration/replica-pid new file mode 100644 index 0000000..fd9201b --- /dev/null +++ b/payment-channel-icp/userdata/.dfx/network/local/replica-configuration/replica-pid @@ -0,0 +1 @@ +4538 \ No newline at end of file diff --git a/payment-channel-icp/userdata/.dfx/network/local/replica-effective-config.json b/payment-channel-icp/userdata/.dfx/network/local/replica-effective-config.json new file mode 100644 index 0000000..dffe2c6 --- /dev/null +++ b/payment-channel-icp/userdata/.dfx/network/local/replica-effective-config.json @@ -0,0 +1,30 @@ +{ + "replica_rev": "3d0b3f10417fc6708e8b5d844a0bac5e86f3e17d", + "type": "replica", + "config": { + "http_handler": { + "port": null, + "write_port_to": "/mnt/c/Users/CAC/Documents/Go/perun/perun-examples/payment-channel-icp/userdata/.dfx/network/local/replica-configuration/replica-1.port" + }, + "state_manager": { + "state_root": "/mnt/c/Users/CAC/Documents/Go/perun/perun-examples/payment-channel-icp/userdata/.dfx/network/local/state/replicated_state" + }, + "crypto": { + "crypto_root": "/mnt/c/Users/CAC/Documents/Go/perun/perun-examples/payment-channel-icp/userdata/.dfx/network/local/state/crypto_store" + }, + "artifact_pool": { + "consensus_pool_path": "/mnt/c/Users/CAC/Documents/Go/perun/perun-examples/payment-channel-icp/userdata/.dfx/network/local/state/consensus_pool" + }, + "subnet_type": "system", + "btc_adapter": { + "enabled": false, + "socket_path": null + }, + "canister_http_adapter": { + "enabled": true, + "socket_path": "/tmp/ic-https-outcalls-adapter-socket.4513.1726666364" + }, + "log_level": "error", + "artificial_delay": 600 + } +} \ No newline at end of file diff --git a/payment-channel-icp/userdata/.dfx/network/local/state/replicated_state/ic.json5 b/payment-channel-icp/userdata/.dfx/network/local/state/replicated_state/ic.json5 new file mode 100644 index 0000000..70a4f1e --- /dev/null +++ b/payment-channel-icp/userdata/.dfx/network/local/state/replicated_state/ic.json5 @@ -0,0 +1 @@ +{"registry_client":{"local_store":"/mnt/c/Users/CAC/Documents/Go/perun/perun-examples/payment-channel-icp/userdata/.dfx/network/local/state/replicated_state/ic_registry_local_store"},"transport":{"node_ip":"0.0.0.0","listening_port":0,"send_queue_size":1024,"max_streams":1},"state_manager":{"state_root":"/mnt/c/Users/CAC/Documents/Go/perun/perun-examples/payment-channel-icp/userdata/.dfx/network/local/state/replicated_state/node-100/state","file_backed_memory_allocator":"Disabled","lsmt_config":{"lsmt_status":"Enabled","shard_num_pages":10485760}},"hypervisor":{"embedders_config":{"query_execution_threads_per_canister":2,"max_globals":1000,"max_functions":50000,"max_custom_sections":16,"max_custom_sections_size":1048576,"max_number_exported_functions":1000,"max_sum_exported_function_name_lengths":20000,"cost_to_compile_wasm_instruction":6000,"num_rayon_compilation_threads":10,"feature_flags":{"rate_limiting_of_debug_prints":"Disabled","write_barrier":"Disabled","wasm_native_stable_memory":"Enabled","wasm64":"Enabled","best_effort_responses":"Enabled"},"metering_type":"New","stable_memory_accessed_page_limit":{"message":524288,"upgrade":2097152,"query":262144},"stable_memory_dirty_page_limit":{"message":524288,"upgrade":2097152,"query":262144},"min_sandbox_count":500,"max_sandbox_count":1000,"max_sandbox_idle_time":{"secs":1800,"nanos":0},"subnet_type":"application","dirty_page_overhead":0,"trace_execution":"Disabled","max_dirty_pages_without_optimization":262144,"dirty_page_copy_overhead":3000,"wasm_max_size":104857600},"create_funds_whitelist":"","max_instructions_for_message_acceptance_calls":200000000,"subnet_memory_threshold":483183820800,"subnet_memory_capacity":751619276800,"subnet_message_memory_capacity":26843545600,"ingress_history_memory_capacity":4294967296,"subnet_wasm_custom_sections_memory_capacity":2147483648,"subnet_memory_reservation":10737418240,"max_canister_memory_size":433791696896,"default_provisional_cycles_balance":100000000000000,"default_freeze_threshold":2592000,"max_controllers":10,"canister_sandboxing_flag":"Enabled","query_execution_threads_total":4,"query_scheduling_time_slice_per_canister":{"secs":0,"nanos":20000000},"max_query_call_graph_depth":6,"max_query_call_graph_instructions":5000000000,"max_query_call_walltime":{"secs":10,"nanos":0},"instruction_overhead_per_query_call":50000000,"rate_limiting_of_heap_delta":"Disabled","rate_limiting_of_instructions":"Disabled","allocatable_compute_capacity_in_percent":50,"deterministic_time_slicing":"Enabled","bitcoin":{"privileged_access":["g4xu7-jiaaa-aaaan-aaaaq-cai","ghsi2-tqaaa-aaaan-aaaca-cai","gsvzx-syaaa-aaaan-aaabq-cai"],"testnet_canister_id":"g4xu7-jiaaa-aaaan-aaaaq-cai","mainnet_canister_id":"ghsi2-tqaaa-aaaan-aaaca-cai"},"composite_queries":"Enabled","query_caching":"Enabled","query_cache_capacity":209715200,"query_cache_max_expiry_time":{"secs":600,"nanos":0},"query_cache_data_certificate_expiry_time":{"secs":60,"nanos":0},"max_compilation_cache_size":10737418240,"query_stats_aggregation":"Enabled","query_stats_epoch_length":60,"stop_canister_timeout_duration":{"secs":300,"nanos":0},"canister_snapshots":"Enabled","dirty_page_logging":"Disabled","ic00_compute_initial_i_dkg_dealings":"Enabled","ic00_schnorr_public_key":"Enabled","ic00_sign_with_schnorr":"Enabled"},"http_handler":{"listen_addr":"127.0.0.1:0","port_file_path":"/mnt/c/Users/CAC/Documents/Go/perun/perun-examples/payment-channel-icp/userdata/.dfx/network/local/replica-configuration/replica-1.port","connection_read_timeout_seconds":1200,"request_timeout_seconds":300,"http_max_concurrent_streams":1000,"max_request_size_bytes":5242880,"max_delegation_certificate_size_bytes":1048576,"max_read_state_concurrent_requests":100,"max_status_concurrent_requests":100,"max_catch_up_package_concurrent_requests":100,"max_dashboard_concurrent_requests":100,"max_call_concurrent_requests":50,"max_query_concurrent_requests":400,"max_pprof_concurrent_requests":5,"ingress_message_certificate_timeout_seconds":10,"max_tracing_flamegraph_concurrent_requests":5},"metrics":null,"artifact_pool":{"consensus_pool_path":"/mnt/c/Users/CAC/Documents/Go/perun/perun-examples/payment-channel-icp/userdata/.dfx/network/local/state/replicated_state/node-100/ic_consensus_pool","ingress_pool_max_count":18446744073709551615,"ingress_pool_max_bytes":18446744073709551615},"crypto":{"crypto_root":"/mnt/c/Users/CAC/Documents/Go/perun/perun-examples/payment-channel-icp/userdata/.dfx/network/local/state/replicated_state/node-100/crypto","csp_vault_type":"in_replica"},"logger":{"level":"error","format":"text_full","log_destination":"Stdout","block_on_overflow":false},"tracing":null,"orchestrator_logger":null,"csp_vault_logger":null,"message_routing":null,"malicious_behaviour":null,"firewall":null,"boundary_node_firewall":null,"registration":null,"nns_registry_replicator":null,"adapters_config":{"bitcoin_mainnet_uds_path":null,"bitcoin_mainnet_uds_metrics_path":null,"bitcoin_testnet_uds_path":null,"bitcoin_testnet_uds_metrics_path":null,"https_outcalls_uds_path":"/tmp/ic-https-outcalls-adapter-socket.4513.1726666364","https_outcalls_uds_metrics_path":null},"bitcoin_payload_builder_config":null,"initial_ipv4_config":null,"domain":null} \ No newline at end of file diff --git a/payment-channel-icp/userdata/.dfx/network/local/webserver-port b/payment-channel-icp/userdata/.dfx/network/local/webserver-port new file mode 100644 index 0000000..4229458 --- /dev/null +++ b/payment-channel-icp/userdata/.dfx/network/local/webserver-port @@ -0,0 +1 @@ +4943 \ No newline at end of file diff --git a/payment-channel-icp/userdata/dfx.json b/payment-channel-icp/userdata/dfx.json new file mode 100644 index 0000000..cd138d6 --- /dev/null +++ b/payment-channel-icp/userdata/dfx.json @@ -0,0 +1,26 @@ +{ + "canisters": { + "ledger": { + "wasm": "ledger.wasm", + "candid": "ledger.private.did", + "type": "custom" + }, + "icp_perun": { + "candid": "icp_perun.did", + "wasm": "icp_perun-opt.wasm", + "type": "custom" + } + }, + "networks": { + "local": { + "bind": "127.0.0.1:4943", + "type": "ephemeral" + } + }, + "defaults": { + "replica": { + "subnet_type": "system" + } + }, + "version": 1 +} \ No newline at end of file diff --git a/payment-channel-icp/userdata/icp_perun-opt.wasm b/payment-channel-icp/userdata/icp_perun-opt.wasm new file mode 100644 index 0000000..1e4595d Binary files /dev/null and b/payment-channel-icp/userdata/icp_perun-opt.wasm differ diff --git a/payment-channel-icp/userdata/icp_perun.did b/payment-channel-icp/userdata/icp_perun.did new file mode 100644 index 0000000..9575b4b --- /dev/null +++ b/payment-channel-icp/userdata/icp_perun.did @@ -0,0 +1,104 @@ +// Copyright 2021 PolyCrypt GmbH +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +type Error = text; + +type EventTxt = text; + +type Timestamp = nat64; +type Duration = nat64; +type Hash = vec nat8; +type Nonce = vec nat8; +type ChannelId = vec nat8; +type Amount = nat; +type Memo = nat64; +type Signature = Hash; +type L2Account = Hash; + +type Funding = record { channel : ChannelId; participant : L2Account }; + +type ChannelTime = record { + chanid : ChannelId; + time : Timestamp; +}; +type Event = variant { + Disputed : RegisteredState; + Funded : record { who : L2Account; total : Amount }; + Concluded; +}; + +type RegEvent = record { + chanid : ChannelId; + time : nat64; + event : Event; +}; + +type Params = record { + nonce : Nonce; + participants : vec L2Account; + challenge_duration : Duration; +}; + +type State = record { + channel : ChannelId; + version : nat64; + allocation : vec Amount; + finalized : bool; +}; + +type FullySignedState = record { + state : State; + sigs : vec blob; +}; + +type AdjRequest = record { + nonce : Nonce; + participants : vec L2Account; + challenge_duration : Duration; + channel : ChannelId; + version : nat64; + allocation : vec Amount; + finalized : bool; + sigs : vec Signature; +}; + +type WithdrawalRequest = record { + channel : ChannelId; + participant : L2Account; + receiver : principal; + signature : Signature; + time : Timestamp; +}; + +type RegisteredState = record { + state : State; + timeout : Timestamp; +}; + +service : { + "deposit" : (Funding) -> (opt Error); + "query_holdings" : (Funding) -> (opt Amount) query; + "conclude" : (AdjRequest) -> (opt Error); + "dispute" : (AdjRequest) -> (opt Error); + "query_state" : (ChannelId) -> (opt RegisteredState); + "withdraw" : (WithdrawalRequest) -> (opt Error); + "query_fid" : (Funding) -> (opt Memo) query; + "query_memo" : (Memo) -> (opt Memo) query; + "transaction_notification" : (nat64) -> (opt Amount); + "query_funding_only" : (Funding) -> (opt Funding) query; + "__get_candid_interface_tmp_hack" : () -> (text) query; + "query_events" : (ChannelTime) -> (opt EventTxt) query; + "register_event" : (vec nat8, nat64, Event) -> (); + "register_event_isolated" : (RegEvent) -> (); +}; diff --git a/payment-channel-icp/userdata/identities/minter_identity.pem b/payment-channel-icp/userdata/identities/minter_identity.pem new file mode 100644 index 0000000..c814e95 --- /dev/null +++ b/payment-channel-icp/userdata/identities/minter_identity.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQACg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIDLxozbkDh6ei4ym46mQHzn3q5vJAI3mt9b66oRJBLfToAcGBSuBBAAK +oUQDQgAESqNTsQujX3Q3Y2kPqTK7s3BVw7tLQuzZbzpd6VscXhPyX9v/RaoRP/RJ +rYXsfn2Jg2GfHkbhwn/EB9fHyFjv4w== +-----END EC PRIVATE KEY----- diff --git a/payment-channel-icp/userdata/identities/usera_identity.pem b/payment-channel-icp/userdata/identities/usera_identity.pem new file mode 100644 index 0000000..cb6c73b --- /dev/null +++ b/payment-channel-icp/userdata/identities/usera_identity.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQACg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEICzYbb84xc0bRy7hO885eziNZK+Xik25oF35Z7OTN/+hoAcGBSuBBAAK +oUQDQgAEAZnhcdDAUYxVcfgYs3PXsL/A1M1+BDCECeQd6HdWyINRr1b4KyT27WUS +NwKRi/JLdg7xFx+1uf0m50FffLHVuw== +-----END EC PRIVATE KEY----- diff --git a/payment-channel-icp/userdata/identities/userb_identity.pem b/payment-channel-icp/userdata/identities/userb_identity.pem new file mode 100644 index 0000000..6ad10e0 --- /dev/null +++ b/payment-channel-icp/userdata/identities/userb_identity.pem @@ -0,0 +1,8 @@ +-----BEGIN EC PARAMETERS----- +BgUrgQQACg== +-----END EC PARAMETERS----- +-----BEGIN EC PRIVATE KEY----- +MHQCAQEEIEnWgkz7ggIgXZpNZSE8wBIKVqeRyonM0DZQe6anqpbGoAcGBSuBBAAK +oUQDQgAEGIJfm5XvK7bItox7QYbimE65Z42qcrAzywFU+ZJFxDKnFyrhx17gx8zP +vFtBpMAFXBRuIGCkuRIjl6U2+EMvlg== +-----END EC PRIVATE KEY----- diff --git a/payment-channel-icp/userdata/ledger.private.did b/payment-channel-icp/userdata/ledger.private.did new file mode 100644 index 0000000..537315c --- /dev/null +++ b/payment-channel-icp/userdata/ledger.private.did @@ -0,0 +1,103 @@ +type Tokens = record { + e8s : nat64; +}; + +type Duration = record { + secs: nat64; + nanos: nat32; +}; + +// Number of nanoseconds from the UNIX epoch in UTC timezone. +type TimeStamp = record { + timestamp_nanos: nat64; +}; + +type ArchiveOptions = record { + trigger_threshold : nat64; + num_blocks_to_archive : nat64; + node_max_memory_size_bytes: opt nat64; + max_message_size_bytes: opt nat64; + controller_id: principal; + cycles_for_archive_creation: opt nat64; +}; + +// Height of a ledger block. +type BlockHeight = nat64; + +// A number associated with a transaction. +// Can be set by the caller in `send` call as a correlation identifier. +type Memo = nat64; + +// Account identifier encoded as a 64-byte ASCII hex string. +type AccountIdentifier = text; + +// Subaccount is an arbitrary 32-byte byte array. +type SubAccount = blob; + +type Transfer = variant { + Burn: record { + from: AccountIdentifier; + amount: Tokens; + }; + Mint: record { + to: AccountIdentifier; + amount: Tokens; + }; + Send: record { + from: AccountIdentifier; + to: AccountIdentifier; + amount: Tokens; + }; +}; + +type Transaction = record { + transfer: Transfer; + memo: Memo; + created_at: BlockHeight; +}; + +// Arguments for the `send_dfx` call. +type SendArgs = record { + memo: Memo; + amount: Tokens; + fee: Tokens; + from_subaccount: opt SubAccount; + to: AccountIdentifier; + created_at_time: opt TimeStamp; +}; + +// Arguments for the `notify` call. +type NotifyCanisterArgs = record { + // The of the block to send a notification about. + block_height: BlockHeight; + // Max fee, should be 10000 e8s. + max_fee: Tokens; + // Subaccount the payment came from. + from_subaccount: opt SubAccount; + // Canister that received the payment. + to_canister: principal; + // Subaccount that received the payment. + to_subaccount: opt SubAccount; +}; + +type AccountBalanceArgs = record { + account: AccountIdentifier; +}; + +type LedgerCanisterInitPayload = record { + minting_account: AccountIdentifier; + initial_values: vec record {AccountIdentifier; Tokens}; + max_message_size_bytes: opt nat64; + transaction_window: opt Duration; + archive_options: opt ArchiveOptions; + send_whitelist: vec principal; + transfer_fee: opt Tokens; + token_symbol: opt text; + token_name: opt text; +}; + +service: (LedgerCanisterInitPayload) -> { + send_dfx : (SendArgs) -> (BlockHeight); + notify_dfx: (NotifyCanisterArgs) -> (); + account_balance_dfx : (AccountBalanceArgs) -> (Tokens) query; +} diff --git a/payment-channel-icp/userdata/ledger.public.did b/payment-channel-icp/userdata/ledger.public.did new file mode 100644 index 0000000..155ea76 --- /dev/null +++ b/payment-channel-icp/userdata/ledger.public.did @@ -0,0 +1,249 @@ +// This is the official Ledger interface that is guaranteed to be backward compatible. + +// Amount of tokens, measured in 10^-8 of a token. +type Tokens = record { + e8s : nat64; +}; + +// Number of nanoseconds from the UNIX epoch in UTC timezone. +type TimeStamp = record { + timestamp_nanos: nat64; +}; + +// AccountIdentifier is a 32-byte array. +// The first 4 bytes is big-endian encoding of a CRC32 checksum of the last 28 bytes. +type AccountIdentifier = blob; + +// Subaccount is an arbitrary 32-byte byte array. +// Ledger uses subaccounts to compute the source address, which enables one +// principal to control multiple ledger accounts. +type SubAccount = blob; + +// Sequence number of a block produced by the ledger. +type BlockIndex = nat64; + +// An arbitrary number associated with a transaction. +// The caller can set it in a `transfer` call as a correlation identifier. +type Memo = nat64; + +// Arguments for the `transfer` call. +type TransferArgs = record { + // Transaction memo. + // See comments for the `Memo` type. + memo: Memo; + // The amount that the caller wants to transfer to the destination address. + amount: Tokens; + // The amount that the caller pays for the transaction. + // Must be 10000 e8s. + fee: Tokens; + // The subaccount from which the caller wants to transfer funds. + // If null, the ledger uses the default (all zeros) subaccount to compute the source address. + // See comments for the `SubAccount` type. + from_subaccount: opt SubAccount; + // The destination account. + // If the transfer is successful, the balance of this address increases by `amount`. + to: AccountIdentifier; + // The point in time when the caller created this request. + // If null, the ledger uses current IC time as the timestamp. + created_at_time: opt TimeStamp; +}; + +type TransferError = variant { + // The fee that the caller specified in the transfer request was not the one that ledger expects. + // The caller can change the transfer fee to the `expected_fee` and retry the request. + BadFee : record { expected_fee : Tokens; }; + // The account specified by the caller doesn't have enough funds. + InsufficientFunds : record { balance: Tokens; }; + // The request is too old. + // The ledger only accepts requests created within 24 hours window. + // This is a non-recoverable error. + TxTooOld : record { allowed_window_nanos: nat64 }; + // The caller specified `created_at_time` that is too far in future. + // The caller can retry the request later. + TxCreatedInFuture : null; + // The ledger has already executed the request. + // `duplicate_of` field is equal to the index of the block containing the original transaction. + TxDuplicate : record { duplicate_of: BlockIndex; } +}; + +type TransferResult = variant { + Ok : BlockIndex; + Err : TransferError; +}; + +// Arguments for the `account_balance` call. +type AccountBalanceArgs = record { + account: AccountIdentifier; +}; + +type TransferFeeArg = record {}; + +type TransferFee = record { + // The fee to pay to perform a transfer + transfer_fee: Tokens; +}; + +type GetBlocksArgs = record { + // The index of the first block to fetch. + start : BlockIndex; + // Max number of blocks to fetch. + length : nat64; +}; + +type Operation = variant { + Mint : record { + to : AccountIdentifier; + amount : Tokens; + }; + Burn : record { + from : AccountIdentifier; + amount : Tokens; + }; + Transfer : record { + from : AccountIdentifier; + to : AccountIdentifier; + amount : Tokens; + fee : Tokens; + }; +}; + +type Transaction = record { + memo : Memo; + operation : opt Operation; + created_at_time : TimeStamp; +}; + +type Block = record { + parent_hash : opt blob; + transaction : Transaction; + timestamp : TimeStamp; +}; + +// A prefix of the block range specified in the [GetBlocksArgs] request. +type BlockRange = record { + // A prefix of the requested block range. + // The index of the first block is equal to [GetBlocksArgs.from]. + // + // Note that the number of blocks might be less than the requested + // [GetBlocksArgs.len] for various reasons, for example: + // + // 1. The query might have hit the replica with an outdated state + // that doesn't have the full block range yet. + // 2. The requested range is too large to fit into a single reply. + // + // NOTE: the list of blocks can be empty if: + // 1. [GetBlocksArgs.len] was zero. + // 2. [GetBlocksArgs.from] was larger than the last block known to the canister. + blocks : vec Block; +}; + +// An error indicating that the arguments passed to [QueryArchiveFn] were invalid. +type QueryArchiveError = variant { + // [GetBlocksArgs.from] argument was smaller than the first block + // served by the canister that received the request. + BadFirstBlockIndex : record { + requested_index : BlockIndex; + first_valid_index : BlockIndex; + }; + + // Reserved for future use. + Other : record { + error_code : nat64; + error_message : text; + }; +}; + +type QueryArchiveResult = variant { + // Successfully fetched zero or more blocks. + Ok : BlockRange; + // The [GetBlocksArgs] request was invalid. + Err : QueryArchiveError; +}; + +// A function that is used for fetching archived ledger blocks. +type QueryArchiveFn = func (GetBlocksArgs) -> (QueryArchiveResult) query; + +// The result of a "query_blocks" call. +// +// The structure of the result is somewhat complicated because the main ledger canister might +// not have all the blocks that the caller requested: One or more "archive" canisters might +// store some of the requested blocks. +// +// Note: as of Q4 2021 when this interface is authored, the IC doesn't support making nested +// query calls within a query call. +type QueryBlocksResponse = record { + // The total number of blocks in the chain. + // If the chain length is positive, the index of the last block is `chain_len - 1`. + chain_length : nat64; + + // System certificate for the hash of the latest block in the chain. + // Only present if `query_blocks` is called in a non-replicated query context. + certificate : opt blob; + + // List of blocks that were available in the ledger when it processed the call. + // + // The blocks form a contiguous range, with the first block having index + // [first_block_index] (see below), and the last block having index + // [first_block_index] + len(blocks) - 1. + // + // The block range can be an arbitrary sub-range of the originally requested range. + blocks : vec Block; + + // The index of the first block in "blocks". + // If the blocks vector is empty, the exact value of this field is not specified. + first_block_index : BlockIndex; + + // Encoding of instructions for fetching archived blocks whose indices fall into the + // requested range. + // + // For each entry `e` in [archived_blocks], `[e.from, e.from + len)` is a sub-range + // of the originally requested block range. + archived_blocks : vec record { + // The index of the first archived block that can be fetched using the callback. + start : BlockIndex; + + // The number of blocks that can be fetch using the callback. + length : nat64; + + // The function that should be called to fetch the archived blocks. + // The range of the blocks accessible using this function is given by [from] + // and [len] fields above. + callback : QueryArchiveFn; + }; +}; + +type Archive = record { + canister_id: principal; +}; + +type Archives = record { + archives: vec Archive; +}; + +service : { + // Transfers tokens from a subaccount of the caller to the destination address. + // The source address is computed from the principal of the caller and the specified subaccount. + // When successful, returns the index of the block containing the transaction. + transfer : (TransferArgs) -> (TransferResult); + + // Returns the amount of Tokens on the specified account. + account_balance : (AccountBalanceArgs) -> (Tokens) query; + + // Returns the current transfer_fee. + transfer_fee : (TransferFeeArg) -> (TransferFee) query; + + // Queries blocks in the specified range. + query_blocks : (GetBlocksArgs) -> (QueryBlocksResponse) query; + + // Returns token symbol. + symbol : () -> (record { symbol: text }) query; + + // Returns token name. + name : () -> (record { name: text }) query; + + // Returns token decimals. + decimals : () -> (record { decimals: nat32 }) query; + + // Returns the existing archive canisters information. + archives : () -> (Archives) query; +} diff --git a/payment-channel-icp/userdata/ledger.wasm b/payment-channel-icp/userdata/ledger.wasm new file mode 100644 index 0000000..08dd23e Binary files /dev/null and b/payment-channel-icp/userdata/ledger.wasm differ diff --git a/payment-channel-icp/utils/encoding.go b/payment-channel-icp/utils/encoding.go new file mode 100644 index 0000000..1ff30d6 --- /dev/null +++ b/payment-channel-icp/utils/encoding.go @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: Apache-2.0 + +package utils + +// here we defien the encoding structure to use the candid package of the agent-go library + +import ( + "fmt" + "github.com/aviate-labs/agent-go/principal" + "regexp" + "strconv" + "strings" +) + +// We encode the data according to the Motoko format as a vec{nat8} represenation + +func ByteToVecString(memo []byte) string { + var str string + for i, b := range memo { + str += strconv.Itoa(int(b)) + if i < len(memo)-1 { + str += "; " + } + } + return "vec{" + str + "}" +} + +func DecodePrincipal(principalString string) (*principal.Principal, error) { + decPrincipal, err := principal.Decode(principalString) + if err != nil { + return &principal.Principal{}, fmt.Errorf("error decoding Principal String: %w", err) + } + return &decPrincipal, nil +} + +func FormatWithUnderscores(n uint64) string { + s := fmt.Sprintf("%d", n) + parts := make([]string, 0, (len(s)+2)/3) + + for len(s) > 0 { + chunkSize := len(s) % 3 + if chunkSize == 0 { + chunkSize = 3 + } + parts = append(parts, s[:chunkSize]) + s = s[chunkSize:] + } + + return strings.Join(parts, "_") +} + +func FormatGetBalanceArgs(sendTo string) string { + + return fmt.Sprintf("(record{ account = \"%s\"})", sendTo) +} + +func FormatFundingArgs(chanId, addr []byte) string { + return fmt.Sprintf("( record { channel = blob \"%s\"; participant = blob \"%s\"} )", FormatHexByte(chanId), FormatHexByte(addr)) +} +func FormatFidArgs(addr, chanId []byte) string { + return fmt.Sprintf("(record {channel = %s; participant = %s})", FormatVec(chanId), FormatVec(addr)) +} + +func FormatParamsArgs(nonce []byte, parts [][]byte, duration uint64) string { + return fmt.Sprintf("(record {nonce = %s; participants = vec{%s ; %s} ; duration = %d : nat64})", FormatVec(nonce), FormatVec(parts[0]), FormatVec(parts[1]), duration) +} + +func FormatStateArgs(chanId []byte, version uint64, alloc []uint64, finalized bool) string { + return fmt.Sprintf("(record {channel = %s; version = %d : nat64; allocation = vec{%d ; %d} ; isFinal = %t : bool})", FormatVec(chanId), version, alloc[0], alloc[1], finalized) +} + +// gwjf3-rxk3d-lfwux-5evls-qw4gc-fyh4e-ohkeg-zg32g-vqfcw-yaaqs-tqe +func FormatWithdrawalArgs(addr, chanId, sig []byte) string { //, prince string + return fmt.Sprintf("(record { channel = blob \"%s\"; participant = %s ; receiver = principal \"exqrz-uemtb-qnd6t-mvbn7-mxjre-bodlr-jnqql-tnaxm-ur6uc-mmgb4-jqe\" ; signature = %s})", FormatHexByte(chanId), FormatVec(addr), FormatVec(sig)) +} + +func FormatConcludeCLIArgs(nonce []byte, addrs [][]byte, chDur uint64, chanId []byte, version uint64, alloc []int, finalized bool, sig [][]byte) string { //, prince string + return fmt.Sprintf( + "(record { nonce = blob \"%s\"; participants = vec{ %s ; %s } ; challenge_duration = %d: nat64 ; channel = blob \"%s\" ; version = %d : nat64; allocation = vec{ %d : nat ; %d : nat } ; finalized = %t : bool ; sigs = vec{ %s ; %s}})", + FormatHexByte(nonce), FormatVec(addrs[0]), FormatVec(addrs[1]), chDur, FormatHexByte(chanId), version, alloc[0], alloc[1], finalized, FormatVec(sig[0]), FormatVec(sig[1])) +} + +func FormatConcludeAGArgs(nonce []byte, addrs [][]byte, chDur uint64, chanId []byte, version uint64, alloc []int, finalized bool, sig [][]byte) string { //, prince string + return fmt.Sprintf( + "(record { nonce = blob \"%s\"; participants = vec{ blob \"%s\"; blob \"%s\"} ; challenge_duration = %d: nat64 ; channel = blob \"%s\" ; version = %d : nat64; allocation = vec{ %d : nat ; %d : nat } ; finalized = %t : bool ; sigs = vec{ blob \"%s\" ; blob \"%s\"}})", + FormatHexByte(nonce), FormatHexByte(addrs[0]), FormatHexByte(addrs[1]), chDur, FormatHexByte(chanId), version, alloc[0], alloc[1], finalized, FormatHexByte(sig[0]), FormatHexByte(sig[1])) +} + +func FormatFundingMemoArgs(chanId, addr []byte, memo uint64) string { + var builder strings.Builder + + builder.WriteString(fmt.Sprintf( + "(record {channel = blob \"%s\"; participant = blob \"%s\"; memo = %d : nat64 })", + FormatHexByte(chanId), + FormatHexByte(addr), + memo, + )) + + return builder.String() +} + +func FormatHexByte(input []byte) string { + var result strings.Builder + + for _, b := range input { + result.WriteString(fmt.Sprintf("%02x", b)) + } + + return result.String() +} + +func FormatHex(hexStr string) string { + var result strings.Builder + + for i := 0; i < len(hexStr); i += 2 { + if i > 0 || i == 0 { + result.WriteString("\\") + } + result.WriteString(hexStr[i : i+2]) + } + + return result.String() +} + +func InsertBackslash(hash string) string { + modified := "" + for i := 0; i < len(hash); i++ { + if i%2 == 0 { + modified += "\\" + } + modified += string(hash[i]) + } + return modified +} + +func FormatVec(data []uint8) string { + var elements []string + for _, element := range data { + elements = append(elements, fmt.Sprintf("%d", element)) + } + return "vec {" + strings.Join(elements, "; ") + "}" +} + +func FormatNotifyArgs(blocknum uint64) string { + fullArg := "(" + blocknumArg := fmt.Sprintf("%d : nat64", blocknum) + ")" + + fullArg += blocknumArg + + return fullArg +} + +func ExtractBlock(s string) (uint64, error) { + re := regexp.MustCompile(`\d+ = (\d+)`) + matches := re.FindStringSubmatch(s) + if len(matches) < 2 { + return 0, fmt.Errorf("no value found extracting the block") + } + + value, err := strconv.ParseUint(matches[1], 10, 64) + if err != nil { + return 0, fmt.Errorf("failed to convert value: %w", err) + } + + return value, nil +} + +func FormatVerifySigArgs(addrs [][]byte, sig [][]byte, chanId []byte, version uint64, alloc []int, finalized bool) string { + return fmt.Sprintf( + "(record { participants = vec{ %s; %s} ; signatures = vec{ %s ; %s}} ; channel = blob \"%s\" ; version = %d : nat64; allocation = vec{ %d ; %d } ; finalized = %t : bool ; )", + FormatVec(addrs[0]), FormatVec(addrs[1]), FormatVec(sig[0]), FormatVec(sig[1]), FormatHexByte(chanId), version, alloc[0], alloc[1], finalized) +} + +func FormatTransferArgs(memo, amount, fee uint64, sendTo string) string { + var builder strings.Builder + + builder.WriteString(fmt.Sprintf( + "(record {memo = %d : nat64; amount = record { e8s=%s : nat64}; fee = record { e8s=%s : nat64}; from_subaccount = null; to = blob \"%s\"; created_at_time = null; })", + memo, + FormatWithUnderscores(amount), + FormatWithUnderscores(fee), + FormatHex(sendTo), + )) + + return builder.String() +} + +func ExtractTxAmount(s string) (int, error) { + re := regexp.MustCompile(`\d+ = (\d+)`) + matches := re.FindStringSubmatch(s) + if len(matches) < 2 { + return 0, fmt.Errorf("no value found extracting the amount") + } + + value, err := strconv.Atoi(matches[1]) + if err != nil { + return 0, fmt.Errorf("failed to convert value: %w", err) + } + + return value, nil +} + +func ExtractBalanceNumber(s string) (uint64, error) { + re := regexp.MustCompile(`e8s = ([\d_]+) : nat64`) + matches := re.FindStringSubmatch(s) + + if len(matches) != 2 { + return 0, fmt.Errorf("invalid input format") + } + + // Remove underscores + numStr := strings.ReplaceAll(matches[1], "_", "") + + num, err := strconv.ParseUint(numStr, 10, 64) + if err != nil { + return 0, fmt.Errorf("unable to convert to uint64: %v", err) + } + + return num, nil +} + +func ExtractHoldingsNat(input string) (uint64, error) { + re := regexp.MustCompile(`\d+(_\d+)*`) + numberWithUnderscores := re.FindString(input) + numberWithoutUnderscores := strings.Replace(numberWithUnderscores, "_", "", -1) + + natValue, err := strconv.Atoi(numberWithoutUnderscores) + if err != nil { + return 0, err + } + + return uint64(natValue), nil +} + +func FormatChanTimeArgs(chanId []byte, tstamp uint64) string { + var builder strings.Builder + + builder.WriteString(fmt.Sprintf( + "(record {chanid = blob \"%s\"; time = %d : nat64 })", + FormatHexByte(chanId), + tstamp, + )) + + return builder.String() +}