Skip to content

Instantly share code, notes, and snippets.

@matthewhammer
Forked from ninegua/ledger.did
Created May 18, 2021 22:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save matthewhammer/bcdf447279e40c51e07790e3f897d983 to your computer and use it in GitHub Desktop.
Save matthewhammer/bcdf447279e40c51e07790e3f897d983 to your computer and use it in GitHub Desktop.
Internet computer ledger utility supporting 2 step transfer (separate sign and send)
type ICPTs = record {
e8s : nat64;
};
type Duration = record {
secs: nat64;
nanos: nat32;
};
type TimeStamp = record {
timestamp_nanos: nat64;
};
type ArchiveOptions = record {
node_max_memory_size_bytes: opt nat32;
max_message_size_bytes: opt nat32;
controller_id: principal;
};
type BlockHeight = nat64;
type Memo = nat64;
type AccountIdentifier = text;
type SubAccount = vec nat8;
type Transfer = variant {
Burn: record {
from: AccountIdentifier;
amount: ICPTs;
};
Mint: record {
to: AccountIdentifier;
amount: ICPTs;
};
Send: record {
from: AccountIdentifier;
to: AccountIdentifier;
amount: ICPTs;
};
};
type Transaction = record {
transfer: Transfer;
memo: Memo;
created_at: BlockHeight;
};
type SendArgs = record {
memo: Memo;
amount: ICPTs;
fee: ICPTs;
from_subaccount: opt SubAccount;
to: AccountIdentifier;
created_at_time: opt TimeStamp;
};
type NotifyCanisterArgs = record {
block_height: BlockHeight;
max_fee: ICPTs;
from_subaccount: opt SubAccount;
to_canister: principal;
to_subaccount: opt SubAccount;
};
type AccountBalanceArgs = record {
account: AccountIdentifier;
};
type LedgerCanisterInitPayload = record {
minting_account: AccountIdentifier;
initial_values: vec record {AccountIdentifier; ICPTs};
max_message_size_bytes: opt nat32;
transaction_window: opt Duration;
archive_options: opt ArchiveOptions;
send_whitelist: vec record {principal};
}
service: (LedgerCanisterInitPayload) -> {
send_dfx : (SendArgs) -> (BlockHeight);
notify_dfx: (NotifyCanisterArgs) -> ();
account_balance_dfx : (AccountBalanceArgs) -> (ICPTs) query;
}
set -euo pipefail
USAGE="$0 [balance|transfer|sign|send] ...
Sub-command:
balance <account_id>
transfer <amount> <account_id>
sign <amount> <account_id> <output_file>
send <file>
Note that amount must be specified as ICP in decimal, not e8s.
"
FEE=${FEE:-0.0001}
DFX="dfx canister --no-wallet --network=ic"
DFX_VERSION=$(dfx --version | sed -e 's/dfx //')
test -n "$DFX_VERSION" || (echo "Unable to get dfx version number." && exit 1)
test -f "${LEDGER_DID:-}" || (echo "Cannot find ledger.did. Please specify it in the environment variable LEDGER_DID." && exit 1)
prepare() {
PROJ_DIR=$(mktemp -td dfx-transfer-XXXXXXXX)
test -d "$PROJ_DIR" || exit 1
cp "$LEDGER_DID" "$PROJ_DIR/ledger.did" || exit 1
cat >"$PROJ_DIR/dfx.json" <<END
{
"version": 1,
"dfx": "$DFX_VERSION",
"canisters": {
"ledger":{ "build": "", "wasm": "", "type": "custom", "candid": "leger.did" }
},
"defaults": { "build": { "packtool": "" } },
"networks": {}
}
END
cat >"$PROJ_DIR/canister_ids.json" <<END
{ "ledger": { "ic": "ryjl3-tyaaa-aaaaa-aaaba-cai" } }
END
pushd "$PROJ_DIR"
}
cleanup() {
popd
rm -rf "$PROJ_DIR"
}
get_balance() {
prepare
local account=${1:-$(dfx ledger account-id)}
local result=$($DFX call ledger account_balance_dfx --type idl "(record { account = \"$account\" })")
local balance=$(echo "$result"| sed -e 's/.*= \([^ ]*\).*/\1/' -e 's/_//g')
printf "%08d" "$balance" | sed -e 's/\(.*\)\(........\)$/\1.\2/'
}
transfer_args() {
local amount=$(printf "%.8f" "$1" | sed -e 's/\.//')
local fee=$(printf "%.8f" "$FEE" | sed -e 's/\.//')
local account=$2
echo "(record {
memo = 0 : nat64;
amount = record { e8s = $amount : nat64 };
fee = record { e8s = $fee : nat64 };
to = \"$account\";
created_at_time = record { timestamp_nanos = $(date +%s)000000000 : nat64 }
})"
}
transfer_balance() {
local args=$(transfer_args "$1" "$2")
prepare
$DFX call ledger send_dfx --type idl "$args"
cleanup
}
sign_transfer() {
local args=$(transfer_args "$1" "$2")
local outfile="$(realpath $3)"
prepare
$DFX sign ledger send_dfx --file "$outfile" --type idl "$args"
cleanup
}
send_transfer() {
local infile="$(realpath $1)"
prepare
local request_id=$($DFX send "$infile")
echo
echo "Request ID: $request_id"
# Cannot do the following because signer needs to sign rqeuest-status on AG setup first.
# $DFX request-status "$request_id" ledger
cleanup
}
case "${1:-}" in
"balance") get_balance "${2:-}" ;;
"transfer")
test "$#" -eq "3" || (echo "$USAGE" && exit 1)
transfer_balance "$2" "$3"
;;
"sign")
test "$#" -eq "4" || (echo "$USAGE" && exit 1)
sign_transfer "$2" "$3" "$4"
;;
"send")
test "$#" -eq "2" || (echo "$USAGE" && exit 1)
send_transfer "$2"
;;
*) echo "$USAGE" ;;
esac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment