You may integrate above described process of wallet account deployment into your backend code.
Note, that similar to the CLI approach described above, you have to sponsor a user account before deploying contract code. The sample assumes you use Acki Nacki faucet, where you can request test tokens to the contract address generated by the sample. In a production environment you may set up a giver to sponsor your contract deployment operations.
async function main(client: TvmClient) {
//
// 1. ------------------ Deploy multisig wallet --------------------------------
//
// Generate a key pair for the wallet to be deployed
const keypair = await client.crypto.generate_random_sign_keys();
// TODO: Save generated keypair!
console.log('Generated wallet keys:', JSON.stringify(keypair))
console.log('Do not forget to save the keys!')
// To deploy a wallet we need its TVC and ABI files
const msigTVC: string =
readFileSync(path.resolve(__dirname, "../contract/wallet.tvc")).toString("base64")
const msigABI: string =
readFileSync(path.resolve(__dirname, "../contract/wallet.abi.json")).toString("utf8")
// We need to know the future address of the wallet account,
// because its balance must be positive for the contract to be deployed
// Future address can be calculated by encoding the deploy message.
// https://dev.ackinacki.com/reference/types-and-methods/mod_abi#encode_message
const messageParams: ParamsOfEncodeMessage = {
abi: { type: 'Json', value: msigABI },
deploy_set: { tvc: msigTVC, initial_data: {} },
signer: { type: 'Keys', keys: keypair },
processing_try_index: 1
}
const encoded: ResultOfEncodeMessage = await client.abi.encode_message(messageParams)
const msigAddress = encoded.address
console.log(`You can topup your wallet from Acki Nacki faucet at https://coming-soon`)
console.log(`Please send >= ${MINIMAL_BALANCE} tokens to ${msigAddress}`)
console.log(`awaiting...`)
// Blocking here, waiting for account balance changes.
// It is assumed that you replanish the account now
let balance: number
for (; ;) {
// The idiomatic way to send a request is to specify
// query and variables as separate properties.
const getBalanceQuery = `
query getBalance($address: String!) {
blockchain {
account(address: $address) {
info {
balance
}
}
}
}
`
const resultOfQuery: ResultOfQuery = await client.net.query({
query: getBalanceQuery,
variables: { address: msigAddress }
})
const nanotokens = parseInt(resultOfQuery.result.data.blockchain.account.info?.balance, 16)
if (nanotokens > MINIMAL_BALANCE * 1e9) {
balance = nanotokens / 1e9
break
}
// TODO: rate limiting
await sleep(1000)
}
console.log(`Account balance is: ${balance.toString(10)} tokens`)
console.log(`Deploying wallet contract to address: ${msigAddress} and waiting for transaction...`)
// This function returns type `ResultOfProcessMessage`, see:
// https://dev.ackinacki.com/reference/types-and-methods/mod_processing#process_message
let result: ResultOfProcessMessage = await client.processing.process_message({
message_encode_params: {
...messageParams, // use the same params as for `encode_message`,
call_set: { // plus add `call_set`
function_name: 'constructor',
input: {
owners: [`0x${keypair.public}`],
reqConfirms: 1,
lifetime: 3600
}
},
},
send_events: false,
})
console.log('Contract deployed. Transaction hash', result.transaction?.id)
assert.equal(result.transaction?.status, 3)
assert.equal(result.transaction?.status_name, "finalized")
//
Monitoring transactions
Pagination of all transactions
This sample queries and displays all transactions from the beginning. We can get all the transaction and filter by account addresses on the backend side.
Note: By default the Blockchain API queries, such as the one used here provide only data from the past 7 days. To retrieve older data, make sure to use the archive: true flag, as shown in the sample:
async function main(client: TonClient) {
// In this example, we want the query to return 2 items per page.
const itemsPerPage = 25
// Pagination connection pattern requires a cursor, which will be set latter
let cursor: string = undefined
// The idiomatic way to send a request is to specify
// query and variables as separate properties.
const transactionsQuery = `
query listTransactions($cursor: String, $count: Int) {
blockchain {
transactions(
first: $count
after: $cursor
) {
edges {
node {
id
balance_delta
account_addr
# other transaction fields
}
}
pageInfo { hasNextPage endCursor }
}
}
}`
for (; ;) {
const queryResult: ResultOfQuery = await client.net.query({
query: transactionsQuery,
variables: {
count: itemsPerPage,
cursor
}
});
const transactions = queryResult.result.data.blockchain.transactions;
for (const edge of transactions.edges) {
console.log("Transaction id:", edge.node.id);
}
if (transactions.pageInfo.hasNextPage === false) {
break;
}
// To read next page we initialize the cursor:
cursor = transactions.pageInfo.endCursor;
// TODO: rate limiting
await sleep(1000);
}
}
console.log("Getting all transactions from the beginning/")
console.log("Most likely this process will never end, so press CTRL+C to interrupt it")
main(client)
.then(() => {
process.exit(0)
})
.catch(error => {
console.error(error);
process.exit(1);
})
// This helper function is used for limiting request rate
function sleep(ms: number) { return new Promise(r => setTimeout(r, ms)) }
Pagination of account transactions
Simply replace the query used in the sample above with this one:
The are two main cases regarding transfers to user accounts: a user may already have an active account to which they want to withdraw funds (set bounce to true), or they may want to withdraw funds to a completely new account, that doesn't exist at the time withdraw is requested (set bounce to false).
The status of the account provided by the user may be checked with the following Everdev command:
tvm-cli account <YourAddress>
The possible results of this command are the following:
Doesn't exist - account does not exist. It needs to be sponsored, then deployed, and only then will it be active.
Uninit - account already has some funds on it but contract code has not been deployed yet. User needs to deploy it.
Active - account already exists, and its code is deployed.
In the first to cases, the service might first transfer a small portion of the requested amount (~1 Shell) and request that the user deploys their contract. Upon the user's confirmation that the account is deployed, its status may be rechecked, and if it became active, the remaining amount of requested funds may be safely transferred.
Using SDK
Same way you can check it using Client Libraries:
let balance: number
let accType: number
for (; ;) {
// The idiomatic way to send a request is to specify
// query and variables as separate properties.
const getInfoQuery = `
query getBalance($address: String!) {
blockchain {
account(address: $address) {
info {
balance
acc_type
}
}
}
}
`
const resultOfQuery: ResultOfQuery = await client.net.query({
query: getInfoQuery,
variables: { address: msigAddress }
})
const nanotokens = parseInt(resultOfQuery.result.data.blockchain.account.info?.balance, 16)
accType = resultOfQuery.result.data.blockchain.account.info?.acc_type;
if (nanotokens > MINIMAL_BALANCE * 1e9) {
balance = nanotokens / 1e9
break
}
// TODO: rate limiting
await sleep(1000)
}
console.log(`Account balance is: ${balance.toString(10)} tokens. Account type is ${accType}`)
Withdrawing from wallet accounts
Using CLI tool
This example shows how to generate a withdrawal transaction from the wallet, using its sendTransaction method. Note, that if Wallet has multiple custodians, the transaction will have to be confirmed with the confirmTransaction method.
In this example tokens are withdrawn from the user account to the account specified in dest. In a proper implementation, the account given by user should be used instead.
You may integrate withdrawals from wallet account into your backend using SDK as well.
This example shows how to generate a withdrawal transaction from the wallet, using its sendTransaction method. Note, that if Wallet has multiple custodians, the transaction will have to be confirmed with the confirmTransaction method.
In this example tokens are withdrawn from the user account to the account specified in dest. In a proper implementation, the account given by user should be used instead.
// We send 0.5 tokens. Value is written in nanotokens
const amount = 0.5e9
const dest = "-1:7777777777777777777777777777777777777777777777777777777777777777"
console.log('Sending 0.5 token to', dest)
result = await client.processing.process_message({
message_encode_params: {
address: msigAddress,
...messageParams, // use the same params as for `encode_message`,
call_set: { // plus add `call_set`
function_name: 'sendTransaction',
input: {
dest: dest,
value: amount,
bounce: true,
flags: 64,
payload: ''
}
},
},
send_events: false, // do not send intermidate events
})
console.log('Transfer completed. Transaction hash', result.transaction?.id)
assert.equal(result.transaction?.status, 3)
assert.equal(result.transaction?.status_name, "finalized")
How to determine a successful transaction?
Not all aborted = true transactions are failed.
It depends on the account state before and after the transaction (fields orig_status and end_status):
If the account was already deployed, i.e. if (tx.orig_status == tx.end_status == active) then you can use tx.aborted field. If it is true, then the transaction is not successful.
If the account was not yet deployed then
if (orig_status == nonExist && end_status == uninit && aborted == true) then transaction is successful.
All the transactions executed on non-deployed accounts are aborted by definition but if we see the state has changed to uninit, it means that the transfer was successfully received.
if (orig_status == uninit && end_status == uninit && aborted == true && in_message.bounce==false)then transaction is successful.
Non-bounced messages are successfully received by non-deployed accounts, though the transaction status is aborted.
Instead of checking tx.in_message.bounce==false you can check if tx.bounce.bounce_type<2 (tx.bounce.bounce_type==2(Ok) is equal to in_message.bounce==true)
Soon the official multisig wallet files will be published here:.
Soon the wallet files will be published here:
Lets assume we need to reliably know when customers receive or transfer funds from their wallets. Sample of transaction is available below.
Not all transactions that are successful are valid transfers and not all transactions that are aborted actually failed. .