Skip to content

Commit 690d0cc

Browse files
author
Hack.bg R&D
committed
fix(sdk): partial typefix
1 parent 4370d11 commit 690d0cc

File tree

2 files changed

+72
-76
lines changed

2 files changed

+72
-76
lines changed

src/sdk.ts

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
import type Bitcoin from '../../Bitcoin/index.ts';
1+
import type Btc from '../../Bitcoin/index.ts';
22
import Fn from '../../../library/Fn.ts';
3-
import { Log } from '../../../library/Log.ts';
43
import { Num, Base16 } from '../../../library/Num.ts';
54
import process from 'node:process';
65

@@ -12,7 +11,7 @@ import type {
1211
} from '../pkg/fadroma_simf.d.ts';
1312

1413
/** WASM cache to download the binary only once. */
15-
let blob = null;
14+
let blob: unknown = null;
1615
/** Instance of SimplicityHL WASM module. */
1716
export type Wasm = InitOutput;
1817
/** Load SimplicityHL WASM module. */
@@ -45,25 +44,25 @@ export interface Spend {
4544
/** Set the transaction fee. */
4645
fee (amount: Num): this;
4746
/** Add a P2WPKH input with signer. */
48-
input (utxo: Btc.Utxo, signer: Signer): this;
47+
input (utxo: Btc.Utxo, signer: Keypair): this;
4948
/** Add a SimplicityHL/Taproot input with witness .*/
5049
input (utxo: Btc.Utxo, program: Program, witness: Fn): this;
5150
/** Add a transaction output. */
5251
output (address: string, amount: Num): this;
5352
/** Broadcast the signed transaction. */
54-
broadcast (chain: Chain): Promise<Btc.TxInfo>;
53+
broadcast (chain: Btc): Promise<Btc.Tx>;
5554
}
5655

5756
/** Start building a spend transaction. */
5857
export function Spend (): Spend {
59-
let utxo = null;
60-
let signer = null;
61-
let program = null;
62-
let witness = null;
63-
let address = null;
64-
let amount = null;
65-
let fee = null;
66-
let asset = null;
58+
let utxo = null as unknown as Btc.Utxo;
59+
let signer = null as unknown as Keypair;
60+
let program = null as unknown as Program;
61+
let witness = null as unknown as Fn;
62+
let address = null as unknown as string;
63+
let amount = null as unknown as Num;
64+
let fee = null as unknown as Num;
65+
let asset = null as unknown as string;
6766
const spend = {
6867
asset (id: string) {
6968
if (asset && asset != id) {
@@ -78,22 +77,22 @@ export function Spend (): Spend {
7877
}
7978
utxo = x;
8079
if (args.length === 1) {
81-
signer = args[0];
80+
signer = args[0] as Keypair;
8281
} else if (args.length === 2) {
8382
if (typeof program !== 'object') {
8483
throw new Error('.input(utxo, program <- must be object, ...')
8584
}
86-
program = args[0];
85+
program = args[0] as Program;
8786
if (typeof witness !== 'function') {
8887
throw new Error('.input(utxo, program, witness <- must be function')
8988
}
90-
witness = args[1];
89+
witness = args[1] as Fn;
9190
} else {
9291
throw new Error('use .input(utxo, signer) or .input(utxo, program, witness)')
9392
}
9493
return spend;
9594
},
96-
output (x: string, y: num) {
95+
output (x: string, y: Num) {
9796
if (!asset) {
9897
throw new Error('use .asset(id) first to assert asset id')
9998
}
@@ -106,14 +105,13 @@ export function Spend (): Spend {
106105
return spend;
107106
},
108107
async broadcast (chain: Btc) {
109-
const debug = (chain.debug ?? console.debug) || (() => {}); // ha!
110108
if (!utxo) throw new Error('no input specified')
111109
if (!address || !amount) throw new Error('no output specified')
112110
if (!fee) throw new Error('no fee specified')
113111
const sender = chain.P2WPKH(signer.publicKey()).address;
114112
const options = { recipient: address, sender, utxos: [utxo], asset: utxo.asset, amount, fee };
115113
const { sendSigned } = await Wasm();
116-
const { hex } = await sendSigned(signer, options);
114+
const { hex } = sendSigned(signer, options);
117115
return await chain.broadcast(hex);
118116
}
119117
};
@@ -173,10 +171,7 @@ export namespace Arg {
173171
}
174172

175173
/** SimplicityHL program compiler for specific chain and parameters. */
176-
export type Program = WasmProgram & {
177-
rpcCommit: Fn,
178-
rpcRedeem: Fn,
179-
};
174+
export type Program = WasmProgram & { p2tr: string };
180175
/** Load WASM with default settings, create [Compiler], and compile program.
181176
*
182177
* Example:
@@ -200,23 +195,25 @@ export type Program = WasmProgram & {
200195
*
201196
**/
202197
export async function Program (source: string, {
203-
args,
204-
chain = 'elementsregtest',
198+
args = {} as unknown as Args,
199+
chain = 'elementsregtest' as Btc["ID"],
205200
genesis = '0000000000000000000000000000000000000000000000000000000000000000',
206-
}: {
207-
args?: Args,
208-
chain?: 'elementsregtest'|'liquidtestnet',
209-
genesis?: string
201+
address = null as string|null,
210202
} = {}): Promise<Program> {
211203
const missing = (name: string) => { throw new Error(`missing: ${name}`) }
212204
// Compilation is synchronous, but we have to wait for the WASM the first time (FIXME?)
213205
const compiler = (await Wasm()).compiler({ chain, genesis });
214206
// Compile the program for the target chain, receiving a WASM descriptor.
215-
const program = compiler.compile(source, { args, chain });
207+
const program = compiler.compile(source, { args, chain }) as unknown as Program;
216208
// Inspect WASM program descriptor, receiving the P2TR.
217209
const fields = (program as unknown as { toJSON (): Program }).toJSON();
218210
// Manually attach properties and methods:
219-
return Object.assign(program, fields);
211+
Object.assign(program, fields);
212+
// If a different address is expected, throw:
213+
if (program.p2tr !== address) {
214+
throw new Error(`Program compiled to ${program.p2tr} instead of expected ${address}`)
215+
}
216+
return program;
220217
}
221218

222219
export type * from './pkg/fadroma_simf.d.ts';

src/test.ts

Lines changed: 44 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,6 @@ export function TestOnLocalnet () {
111111
);
112112
}
113113

114-
interface TestProgram extends Pick<Btc, 'rpc'|'rest'|'esplora'> {
115-
ID,
116-
ASSETS,
117-
P2WPKH,
118-
}
119-
120114
/** Define example program. */
121115
function TestProgram (name: string, src: string, {
122116
/** Program runs that should fail. */
@@ -134,73 +128,78 @@ function TestProgram (name: string, src: string, {
134128
/** Function that provides witness data. */
135129
provideWits = null as null|Fn<[Uint8Array<ArrayBufferLike>], Fn.Async<object>>,
136130
} = {}) {
137-
const cost = fee;
131+
132+
const commitAmount = 1_00000000n;
133+
const redeemFee = 1e-4;
134+
const redeemAmount = 1. - redeemFee;
135+
138136
return Fn.Name(`${name} (${p2tr||'unspecified P2TR'})`, testProgram, {
139-
shouldFail, name, src, cost, cmr, p2tr, argTypes, witTypes, provideArgs, provideWits,
137+
shouldFail, name, src, fee, cmr, p2tr, argTypes, witTypes, provideArgs, provideWits,
140138
});
141-
async function testProgram (chain: Btc) {
142-
// Need chain's genesis hash to compile for the chain.
143-
const genesis = await chain.getBlockHash(0);
144139

145-
// Parameter values are specified by the test case.
146-
// It's a function so they can be made context-dependent,
147-
// but for now they are constant.
148-
const args = provideArgs ? await provideArgs() : undefined;
140+
// Test the SimplicityHL program specified above on the given chain.
141+
async function testProgram (chain: Btc) {
149142

150-
// Ok, compile this program for this chain with these arguments.
151-
const prog = await SimplicityHL.Program(src, { chain: chain.ID, genesis, args });
143+
// Compile this program with these arguments for this chain.
144+
const program = await SimplicityHL.Program(src, {
145+
// Expected program address, optional. Makes it safer.
146+
address: p2tr,
147+
// Represents config such as HRP, prefix bytes...
148+
// TODO expose
149+
chain: chain.ID,
150+
// Need chain's genesis hash to compile for the chain.
151+
genesis: await chain.getBlockHash(0),
152+
// Parameter values are specified by the test case.
153+
// It's a function so they can be made context-dependent,
154+
// but for now they are constant.
155+
args: provideArgs ? await provideArgs() : undefined,
156+
});
152157

153158
// Check against expected program address, if provided.
154-
if (p2tr) equal(prog.p2tr, p2tr);
159+
if (p2tr) equal(program.p2tr, p2tr);
155160

156161
// Fund program from deployer:
157-
// TODO: Use sendSigned
158-
const commitAmount = 1_00000000n;
159162
const commitSource = await chain.getUtxo(chain.P2WPKH(keypair1.publicKey()).address);
160-
const commitTxid = await SimplicityHL.Spend()
163+
const commitTxid = await SimplicityHL.Spend() // TODO wrap as program.commit() ?
161164
.asset(commitSource.asset)
162165
.input(commitSource, keypair1)
163166
.output(p2tr, commitAmount)
164167
.fee(fee)
165168
.broadcast(chain);
166169

167-
// Create local spender wallet and import it to RPC:
170+
// Note current recipient balance:
168171
const recipient = chain.P2WPKH(keypair1.publicKey()).address;
169-
await rpc.importaddress(recipient);
170-
171-
// Note current balance:
172-
await rpc.rescanblockchain();
173-
const balance = ((await rpc.getreceivedbyaddress(recipient, 0)) as { bitcoin: number }).bitcoin;
172+
const balance = (await chain.getBalance(recipient, 0))['bitcoin'];
174173

175174
// Find commit (deploy) output = redeem (spend) input:
176-
const asset = ASSETS.DEFAULT;
177-
const prev = await rest.tx(id);
178-
assertTxOuts(prev, p2tr, 1, cost);
175+
const asset = commitSource.asset;
176+
const prev = await chain.getTxInfo(commitTxid);
177+
assertTxOuts(prev, p2tr, 1, fee);
179178
const txid = prev.txid;
180179
const vout = prev.vout.filter(x=>x.scriptPubKey.address === p2tr)[0];
181180
if (!vout) throw new Error('no corresponding vout found');
182181
const utxos = [{ txid, asset, vout: vout.n, address: vout.scriptPubKey.address, amount: vout.value }];
183182

184183
// To get SIGHASH_ALL for signing, first the rest of the transaction must be specified:
185-
const redeemFee = 1e-4;
186-
const redeemAmount = 1. - redeemFee;
187184
const sighashOpts = { asset, utxos, recipient, amount: redeemAmount, fee: redeemFee };
188-
const sighash = prog.redeemSighash(sighashOpts);
185+
const sighash = program.redeemSighash(sighashOpts);
189186
ok(sighash instanceof Uint8Array, 'sighash expected to be returned from WASM as Uint8Array')
190187
ok(Base16.encode(sighash), 'sighash expected to be base16-encodable');
191-
// Try spending from program:
192-
const redeemArgs = { rpc, rest, ...sighashOpts, witness: provideWits ? await provideWits(sighash) : {} };
188+
189+
// Construct redeem transaction with witnesses:
190+
const witness = provideWits ? await provideWits(sighash) : {};
193191
if (shouldFail) {
194-
// TX is expected to fail
195-
rejects(()=>prog.rpcRedeem(redeemArgs));
196-
// Balance is expected to remain the same
197-
equal(await rpc.getreceivedbyaddress(recipient, 0), { bitcoin: balance });
198-
} else {
199-
// TX is expected to pass
200-
await prog.rpcRedeem(redeemArgs);
201-
// Balance is expected to increase
202-
equal(await rpc.getreceivedbyaddress(recipient, 0), { bitcoin: balance + redeemAmount });
192+
// Program runtime failure is caught at redeem TX construction.
193+
throws(()=>program.redeemTx({ ...sighashOpts, witness }));
194+
return
203195
}
196+
197+
// Spend from program:
198+
const redeemTx = program.redeemTx({ ...sighashOpts, witness });
199+
// TX is expected to pass
200+
await chain.broadcast(redeemTx.hex);
201+
// Balance is expected to increase
202+
equal(await chain.getBalance(recipient, 0), { bitcoin: balance + redeemAmount });
204203
}
205204
}
206205

@@ -213,7 +212,7 @@ function assertTxOuts (
213212
debug = console.debug,
214213
) {
215214
equal(tx.vout.length, 3);
216-
debug('TX:', tx);
215+
//debug('TX:', tx);
217216
hasVout(isBalance, _ => `balance: program ${p2tr} must receive ${amount}`);
218217
hasVout(isFee, _ => `fee: no deploy fee matching ${cost}`);
219218
//hasVout((x: Btc.Vout)=>x.value===remaining, v => `remaining: must be ${v}`);

0 commit comments

Comments
 (0)