Skip to content

Commit

Permalink
Hack to get simulateTransaction token.approve working (#22)
Browse files Browse the repository at this point in the history
* [WIP] trying to get simulate token.approve working

* nonce is a bigint

* Getting approve/deposit flow working

* HACK: use hardcoded key for now until freighter is ready

* minor fix to txn footprint parsing

* remove console.debug
  • Loading branch information
paulbellamy authored Oct 5, 2022
1 parent 8be44ce commit 264e79c
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 31 deletions.
4 changes: 4 additions & 0 deletions contracts/crowdfund/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ impl Crowdfund {
e.data().set(DataKey::Token, token);
}

pub fn owner(e: Env) -> Identifier {
get_owner(&e)
}

pub fn deadline(e: Env) -> u64 {
get_deadline(&e)
}
Expand Down
44 changes: 27 additions & 17 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ import { ContractValue, useNetwork, useAccount, useContractValue, useSendTransac
let xdr = SorobanSdk.xdr;

// Stub dummy data for now.
const source = new SorobanSdk.Account('GCEZWKCA5VLDNRLN3RPRJMRZOX3Z6G5CHCGSNFHEYVXM3XOJMDS674JZ', '0');
const CROWDFUND_ID = "0000000000000000000000000000000000000000000000000000000000000000";
const TOKEN_ID: string = process.env.TOKEN_ID ?? "";

const Home: NextPage = () => {
const { data: account } = useAccount();
// Call the contract rpcs to fetch values
const token = {
balance: useContractValue(TOKEN_ID, "balance", accountIdentifier(Buffer.from(CROWDFUND_ID, 'hex'))),
balance: useContractValue(TOKEN_ID, "balance", contractIdentifier(Buffer.from(CROWDFUND_ID, 'hex'))),
decimals: useContractValue(TOKEN_ID, "decimals"),
name: useContractValue(TOKEN_ID, "name"),
symbol: useContractValue(TOKEN_ID, "symbol"),
Expand Down Expand Up @@ -95,15 +94,13 @@ function formatAmount(value: BigNumber, decimals=7): string {
}

function DepositForm({account, decimals}: {account: {address: string}, decimals: number}) {
const { activeChain } = useNetwork();
const { activeChain, server } = useNetwork();
const networkPassphrase = activeChain?.networkPassphrase ?? "";

const user = accountIdentifier(SorobanSdk.StrKey.decodeEd25519PublicKey(account.address));
const spender = xdr.ScVal.scvObject(xdr.ScObject.scoVec([
xdr.ScVal.scvSymbol("Contract"),
// TODO: Parse this as an address or whatever.
xdr.ScVal.scvObject(xdr.ScObject.scoBytes(Buffer.from(CROWDFUND_ID, 'hex')))
]));
let address = "GBZXN7PIRZGNMHGA7MUUUF4GWPY5AYPV6LY4UV2GL6VJGIQRXFDNMADI";
let secret = "SC5O7VZUXDJ6JBDSZ74DSERXL7W3Y5LTOAMRF7RQRL3TAGAPS7LUVG3L";
const user = accountIdentifier(SorobanSdk.StrKey.decodeEd25519PublicKey(address));
const spender = contractIdentifier(Buffer.from(CROWDFUND_ID, 'hex'));
const allowanceScval = useContractValue(TOKEN_ID, "allowance", user, spender);
const allowance = convert.scvalToBigNumber(allowanceScval.result);

Expand All @@ -116,22 +113,26 @@ function DepositForm({account, decimals}: {account: {address: string}, decimals:
return (
<form onSubmit={async e => {
e.preventDefault();
// TODO: These will change depending on how auth works.
let accountKey = SorobanSdk.StrKey.decodeEd25519PublicKey(account.address);
let from = xdr.ScVal.scvObject(xdr.ScObject.scoBytes(accountKey));
let nonce = xdr.ScVal.scvU32(0);
if (!amount) {
// TODO: Alert here or something
return;
}
let { sequence } = await server.getAccount(address);
let source = new SorobanSdk.Account(address, sequence);
let from = xdr.ScVal.scvObject(xdr.ScObject.scoVec([xdr.ScVal.scvSymbol("Invoker")]));
let nonce = convert.bigNumberToScBigInt(BigNumber(0));
const amountScVal = convert.bigNumberToScBigInt(parsedAmount.multipliedBy(decimals).decimalPlaces(0));
let txn = needsApproval
? contractTransaction(networkPassphrase, TOKEN_ID, "approve", from, nonce, spender, amountScVal)
: contractTransaction(networkPassphrase, CROWDFUND_ID, "deposit", user, amountScVal);
? contractTransaction(networkPassphrase, source, TOKEN_ID, "approve", from, nonce, spender, amountScVal)
: contractTransaction(networkPassphrase, source, CROWDFUND_ID, "deposit", accountIdentifier(SorobanSdk.StrKey.decodeEd25519PublicKey(address)), amountScVal);
let result = await sendTransaction(txn);
// TODO: Show some user feedback while we are awaiting, and then based on the result
console.debug(result);
}}>
<input name="amount" type="text" value={amount} onChange={e => {
setAmount(e.currentTarget.value);
}} />
<button type="button" disabled={allowanceScval.loading}>
<button type="submit" disabled={allowanceScval.loading}>
{needsApproval ? "Approve" : "Deposit"}
</button>
</form>
Expand Down Expand Up @@ -166,7 +167,7 @@ function YourDeposits(
}

// Small helper to build a contract invokation transaction
function contractTransaction(networkPassphrase: string, contractId: string, method: string, ...params: SorobanSdk.xdr.ScVal[]): SorobanSdk.Transaction {
function contractTransaction(networkPassphrase: string, source: SorobanSdk.Account, contractId: string, method: string, ...params: SorobanSdk.xdr.ScVal[]): SorobanSdk.Transaction {
const contract = new SorobanSdk.Contract(contractId);
return new SorobanSdk.TransactionBuilder(source, {
// TODO: Figure out the fee
Expand All @@ -189,4 +190,13 @@ function accountIdentifier(account: Buffer): SorobanSdk.xdr.ScVal {
);
}

function contractIdentifier(contract: Buffer): SorobanSdk.xdr.ScVal {
return xdr.ScVal.scvObject(
xdr.ScObject.scoVec([
xdr.ScVal.scvSymbol("Contract"),
xdr.ScVal.scvObject(xdr.ScObject.scoBytes(contract))
])
);
}

export default Home
52 changes: 38 additions & 14 deletions wallet/hooks/useSendTransaction.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function useSendTransaction<E = Error>(defaultTxn?: Transaction, defaultO
const [status, setState] = React.useState<TransactionStatus>('idle');

const sendTransaction = React.useCallback(async function(passedTxn?: Transaction, passedOptions?: SendTransactionOptions): Promise<SorobanSdk.xdr.ScVal> {
const txn = passedTxn ?? defaultTxn;
let txn = passedTxn ?? defaultTxn;
if (!txn || !activeWallet || !activeChain) {
throw new Error("No transaction or wallet or chain");
}
Expand All @@ -50,10 +50,14 @@ export function useSendTransaction<E = Error>(defaultTxn?: Transaction, defaultO
// preflight and add the footprint
if (!skipAddingFootprint) {
let {footprint} = await server.simulateTransaction(txn);
addFootprint(txn, footprint);
txn = addFootprint(txn, networkPassphrase, footprint);
}

const signed = await activeWallet.signTransaction(txn.toXDR(), activeChain.id.toUpperCase());
// TODO: Use freighter to sign when that is supported
txn.sign(SorobanSdk.Keypair.fromSecret("SC5O7VZUXDJ6JBDSZ74DSERXL7W3Y5LTOAMRF7RQRL3TAGAPS7LUVG3L"));
const signed = txn.toEnvelope().toXDR('base64');
// const signed = await activeWallet.signTransaction(txn.toXDR(), activeChain.id.toUpperCase());

const transactionToSubmit = SorobanSdk.TransactionBuilder.fromXDR(signed, networkPassphrase);
const { id } = await server.sendTransaction(transactionToSubmit);
const sleepTime = Math.min(1000, timeout);
Expand Down Expand Up @@ -95,19 +99,39 @@ export function useSendTransaction<E = Error>(defaultTxn?: Transaction, defaultO
};
}

function addFootprint(txn: Transaction, footprint: SorobanSdk.SorobanRpc.SimulateTransactionResponse['footprint']): Transaction {
if ('innerTransaction' in txn) {
// It's a feebump, modify the inner.
addFootprint(txn.innerTransaction, footprint);
return txn;
// TODO: Transaction is immutable, so we need to re-build it here. :(
function addFootprint(raw: Transaction, networkPassphrase: string, footprint: SorobanSdk.SorobanRpc.SimulateTransactionResponse['footprint']): Transaction {
if ('innerTransaction' in raw) {
// TODO: Handle feebump transactions
return addFootprint(raw.innerTransaction, networkPassphrase, footprint);
}
txn.operations = txn.operations.map(op => {
if ('function' in op) {
op.footprint = new SorobanSdk.xdr.LedgerFootprint.fromXDR(footprint, 'base64');
}
return op;
// TODO: Figure out a cleaner way to clone this transaction.
const source = new SorobanSdk.Account(raw.source, raw.sequence);
const txn = new SorobanSdk.TransactionBuilder(source, {
fee: raw.fee,
memo: raw.memo,
networkPassphrase,
timebounds: raw.timeBounds,
ledgerbounds: raw.ledgerBounds,
minAccountSequence: raw.minAccountSequence,
minAccountSequenceAge: raw.minAccountSequenceAge,
minAccountSequenceLedgerGap: raw.minAccountSequenceLedgerGap,
extraSigners: raw.extraSigners,
});
return txn;
for (let rawOp of raw.operations) {
if ('function' in rawOp) {
// TODO: Figure out a cleaner way to clone these operations
txn.addOperation(SorobanSdk.Operation.invokeHostFunction({
function: rawOp.function,
parameters: rawOp.parameters,
footprint: SorobanSdk.xdr.LedgerFootprint.fromXDR(footprint, 'base64'),
}));
} else {
// TODO: Handle this.
throw new Error("Unsupported operation type");
}
}
return txn.build();
}

async function sleep(ms: number) {
Expand Down

0 comments on commit 264e79c

Please sign in to comment.