Skip to content

Instantly share code, notes, and snippets.

@dakom
Last active April 2, 2022 19:54
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dakom/f76f70794044f0c4f454ecddbd53bfd5 to your computer and use it in GitHub Desktop.
Save dakom/f76f70794044f0c4f454ecddbd53bfd5 to your computer and use it in GitHub Desktop.
Terra helpers
import {AccAddress, Coins, Wallet, CreateTxOptions, MsgStoreCode, MsgInstantiateContract, MsgExecuteContract} from "@terra-money/terra.js";
import {TxResult} from '@terra-dev/wallet-types';
// frequency of polling a transaction to see if it's ready
export const DEFAULT_TRANSACTION_INFO_POLL_WAIT:number = 500; //500ms
// amount of time to wait before giving up on transaction finishing
export const DEFAULT_TRANSACTION_INFO_TIMEOUT:number = 1000 * 60 * 5; // 5 mins
// amount of time to wait after uploading, due to localterra weirdness
// not sure if this is strictly necessary, but w/e
export const UPLOAD_DELAY_AFTER:number = 1000;
// contract should be supplied via fs.readFile(filepath, 'base64')
export function contractUpload(wallet:Wallet, contract:string):Promise<number> {
const msg = new MsgStoreCode(wallet.key.accAddress, contract);
return postWallet(wallet, { msgs: [msg] })
.then(res => {
if(!res.success) {
return Promise.reject(res);
} else {
return requestTxInfo({
wallet,
hash: res.result.txhash,
validator: (res:any) => {
if(res.logs?.length > 0) {
const code_ids = res.logs[0].eventsByType?.store_code?.code_id;
if(code_ids?.length > 0) {
return new Promise(resolve => {
setTimeout(() => {
resolve(parseInt(code_ids[0]));
}, UPLOAD_DELAY_AFTER);
})
}
}
return Promise.reject("bad code id!");
}
})
}
})
}
export function contractInstantiate(wallet:Wallet, codeId: number, payload: any, admin:string = "", init_coins?: Coins.Input):Promise<string> {
const msg = new MsgInstantiateContract(wallet.key.accAddress, admin, codeId, payload, init_coins);
return postWallet(wallet, { msgs: [msg] })
.then(res => {
if(!res.success) {
return Promise.reject(res);
} else {
return requestTxInfo({
wallet,
hash: res.result.txhash,
validator: (res:any) => {
if(res.logs?.length > 0) {
const addrs = res.logs[0].eventsByType?.instantiate_contract?.contract_address;
if(addrs?.length > 0) {
return Promise.resolve(addrs[0]);
}
}
return Promise.reject("bad contract addr!");
}
});
}
})
}
export function contractExecute(wallet:Wallet, contract: AccAddress, payload: any, coins?: Coins.Input):Promise<string> {
const msg = new MsgExecuteContract(wallet.key.accAddress, contract, payload, coins);
return postWallet(wallet, { msgs: [msg] })
.then(res => {
if(!res.success) {
return Promise.reject(res);
} else {
return requestTxInfo({
wallet,
hash: res.result.txhash,
validator: (res:any) => {
//TODO - get binary! https://github.com/terra-money/terra.js/issues/133
if(res.logs?.length > 0) {
return Promise.resolve(res.logs[0].eventsByType?.from_contract);
}
return Promise.resolve(null);
}
});
}
})
}
export function contractQuery(wallet:Wallet, addr:string, query:any):Promise<any> {
return wallet.lcd.wasm.contractQuery(addr, query)
}
export function taxRateQuery(wallet: Wallet):Promise<any> {
return wallet.lcd.treasury.taxRate()
}
/// below here is all internal helpers
interface TxInfoRequest {
timeout?: number,
pollWait?: number,
//async validation function to sanity check the result
validator?: (res:any) => Promise<any>,
hash: string,
wallet: Wallet
}
//we need to poll at a regular interval, but also timeout at some point
//see discussion at https://github.com/terra-money/wallet-provider/issues/23#issuecomment-918725271
//The validator allows for async verification (failure there will reject properly, not case a re-poll)
function requestTxInfo({timeout, pollWait, hash, wallet, validator}:TxInfoRequest):Promise<any> {
return new Promise((resolve, reject) => {
const DEFAULT_VALIDATOR = (res:any) => {
if(res == null) {
return Promise.reject("no result!");
} else {
return Promise.resolve(res);
}
}
const TIMEOUT = timeout == null ? DEFAULT_TRANSACTION_INFO_TIMEOUT : timeout;
const POLL_WAIT = pollWait == null ? DEFAULT_TRANSACTION_INFO_POLL_WAIT : pollWait;
const VALIDATOR = validator == null ? DEFAULT_VALIDATOR : validator;
let stopProcessing = false;
let pollId:NodeJS.Timeout;
function clearTimeouts() {
stopProcessing = true;
clearTimeout(pollId);
clearTimeout(timeoutId);
}
let timeoutId = setTimeout(() => {
clearTimeouts();
reject("timeout");
}, TIMEOUT);
function poll() {
if(!stopProcessing) {
wallet.lcd.tx.txInfo(hash)
.then(res => {
return VALIDATOR(res)
.then(data => ({valid: true, data}))
.catch(reason => Promise.resolve({valid: false, data: reason}))
})
.then(
({valid, data}) => {
clearTimeouts();
if(!valid) {
// validation failed - don't poll again, fully reject
reject(data);
} else {
// validation succeeded - resolve
resolve(data);
}
},
// error in tx itself, i.e. 404, try again!
(_err) => {
pollId = setTimeout(() => {
poll();
}, POLL_WAIT);
}
)
}
}
poll();
})
}
function postWallet(wallet: Wallet, tx: CreateTxOptions):Promise<TxResult> {
return wallet
.createAndSignTx(tx)
.then(tx => wallet.lcd.tx.broadcastSync(tx))
.then(res => {
if(!res) {
return {
success: false,
...tx
} as TxResult
} else {
return {
result: res,
success: true,
...tx
}
}
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment