@allorc/a423-sdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/ESCROW.md +161 -0
- package/INTEGRATION.md +7 -1
- package/NOTICE +24 -0
- package/README.md +6 -0
- package/SECURITY.md +5 -0
- package/contracts/A423Escrow.sol +131 -0
- package/package.json +36 -4
- package/src/escrow/artifact.json +661 -0
- package/src/escrow/index.cjs +137 -0
- package/src/escrow/index.d.mts +36 -0
- package/src/escrow/index.d.ts +36 -0
- package/src/escrow/index.mjs +2 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const { Contract, Interface, TypedDataEncoder, Transaction, getAddress, sha256, toUtf8Bytes, ZeroHash } = require('ethers');
|
|
3
|
+
const { hashAgreement } = require('../workflow.cjs');
|
|
4
|
+
const artifact = require('./artifact.json');
|
|
5
|
+
const ABI = artifact.abi;
|
|
6
|
+
const TOKEN_ABI = ['function allowance(address,address) view returns(uint256)','function approve(address,uint256) returns(bool)'];
|
|
7
|
+
const TERMS_TYPES = { Terms: [ ['jobId','bytes32'],['payer','address'],['worker','address'],['verifier','address'],['arbitrator','address'],['amount','uint256'],['workDeadline','uint64'],['reviewDeadline','uint64'],['resolutionDeadline','uint64'],['timeoutWorkerBps','uint16'],['agreementHash','bytes32'] ].map(([name,type])=>({name,type})) };
|
|
8
|
+
const transitions = {
|
|
9
|
+
dispute: {from:['submitted','verified'],roles:['requester','worker','verifier'],to:'disputed',effect:true},
|
|
10
|
+
resolve: {from:['disputed'],roles:['arbitrator'],to:'settled',effect:true},
|
|
11
|
+
timeout: {from:['funded','running','submitted','verified','disputed'],roles:['requester','worker','verifier','arbitrator'],to:'settled',effect:true}
|
|
12
|
+
};
|
|
13
|
+
function check(condition,message) { if(!condition) throw new Error('Escrow: '+message); }
|
|
14
|
+
function positive(value,name) { check(typeof value==='string' && /^[1-9][0-9]*$/.test(value),name+' must be a positive decimal string');const n=BigInt(value);check(n<(1n<<256n),name+' exceeds uint256');return n; }
|
|
15
|
+
function bytes32(value) { check(typeof value==='string' && /^0x[0-9a-fA-F]{64}$/.test(value) && value!==ZeroHash,'nonzero bytes32 required');return value; }
|
|
16
|
+
function escrowTerms(job,{chainId,contractAddress,tokenAddress}) {
|
|
17
|
+
check(job.agreement && hashAgreement(job.agreement.terms)===job.agreement.hash,'agreement hash mismatch');
|
|
18
|
+
const e=job.agreement.terms.escrow;
|
|
19
|
+
check(e && e.chain_id===String(chainId) && getAddress(e.contract)===getAddress(contractAddress) && getAddress(e.token)===getAddress(tokenAddress),'chain, contract or token mismatch');
|
|
20
|
+
const integer=(value,name)=>{check(Number.isSafeInteger(value)&&value>0,name+' must be positive integer seconds');return value;};
|
|
21
|
+
const t={jobId:sha256(toUtf8Bytes('a423:escrow:v1:'+job.id)),payer:getAddress(e.payer),worker:getAddress(e.worker),verifier:getAddress(e.verifier),arbitrator:getAddress(e.arbitrator),amount:positive(e.amount,'amount'),workDeadline:integer(e.work_deadline,'work_deadline'),reviewDeadline:integer(e.review_deadline,'review_deadline'),resolutionDeadline:integer(e.resolution_deadline,'resolution_deadline'),timeoutWorkerBps:e.timeout_worker_bps,agreementHash:bytes32('0x'+job.agreement.hash)};
|
|
22
|
+
check(t.workDeadline<t.reviewDeadline && t.reviewDeadline<t.resolutionDeadline,'deadlines must be increasing');
|
|
23
|
+
check(Number.isInteger(t.timeoutWorkerBps)&&t.timeoutWorkerBps>=0&&t.timeoutWorkerBps<=10000,'invalid timeout allocation');
|
|
24
|
+
check(t.payer!==t.worker && ![t.payer,t.worker].includes(t.arbitrator),'invalid party separation');
|
|
25
|
+
return t;
|
|
26
|
+
}
|
|
27
|
+
function workerConsent(job,configuration) { const terms=escrowTerms(job,configuration);return {domain:{name:'A423Escrow',version:'1',chainId:configuration.chainId,verifyingContract:getAddress(configuration.contractAddress)},types:TERMS_TYPES,value:terms}; }
|
|
28
|
+
/** Test-only journal. Production must persist records and reserve nonces atomically across instances. */
|
|
29
|
+
function createMemoryTransactionStore() {
|
|
30
|
+
const records=new Map(),nonces=new Map(),reservations=new Map();
|
|
31
|
+
return {
|
|
32
|
+
async get(id){return records.has(id)?structuredClone(records.get(id)):null;},
|
|
33
|
+
async create(id,record){if(records.has(id))return false;records.set(id,structuredClone(record));return true;},
|
|
34
|
+
async reserveNonce(scope,minimum,operationId){const key=scope+':'+operationId;if(reservations.has(key))return reservations.get(key);const nonce=Math.max(minimum,nonces.get(scope)||0);nonces.set(scope,nonce+1);reservations.set(key,nonce);return nonce;}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function createEscrowAdapters({provider,chainId,contractAddress,tokenAddress,signerFor,addressFor,transactions,confirmations,validateArtifact}={}) {
|
|
38
|
+
check(provider && typeof signerFor==='function' && typeof addressFor==='function','provider, signerFor and addressFor required');
|
|
39
|
+
check(Number.isSafeInteger(chainId)&&chainId>0 && Number.isSafeInteger(confirmations)&&confirmations>0,'explicit chain ID and positive confirmations required');
|
|
40
|
+
check(transactions && ['get','create','reserveNonce'].every(k=>typeof transactions[k]==='function'),'durable transaction journal required');
|
|
41
|
+
const config={chainId,contractAddress:getAddress(contractAddress),tokenAddress:getAddress(tokenAddress)};
|
|
42
|
+
const contract=new Contract(config.contractAddress,ABI,provider), iface=new Interface(ABI), token=new Contract(config.tokenAddress,TOKEN_ABI,provider);
|
|
43
|
+
async function context(job) {
|
|
44
|
+
check(Number((await provider.getNetwork()).chainId)===chainId,'RPC chain mismatch');
|
|
45
|
+
check(getAddress(await contract.token())===config.tokenAddress,'contract token mismatch');
|
|
46
|
+
const t=escrowTerms(job,config);
|
|
47
|
+
for(const [role,field] of [['requester','payer'],['worker','worker'],['verifier','verifier'],['arbitrator','arbitrator']]) check(job.participants[role] && getAddress(await addressFor(job.participants[role]))===t[field],role+' wallet mismatch');
|
|
48
|
+
return t;
|
|
49
|
+
}
|
|
50
|
+
function match(snapshot,t) {
|
|
51
|
+
for(const key of Object.keys(t)) check(String(snapshot.terms[key]).toLowerCase()===String(t[key]).toLowerCase(),'on-chain '+key+' mismatch');
|
|
52
|
+
}
|
|
53
|
+
async function receipt(hash) {
|
|
54
|
+
const r=await provider.getTransactionReceipt(hash);
|
|
55
|
+
if(!r)return null;
|
|
56
|
+
const head=await provider.getBlock('latest');
|
|
57
|
+
const block=await provider.getBlock(r.blockNumber);
|
|
58
|
+
if(!block || block.hash!==r.blockHash || head.number-r.blockNumber+1<confirmations)return null;
|
|
59
|
+
return r;
|
|
60
|
+
}
|
|
61
|
+
async function send(ctx,suffix,to,data,reconcile) {
|
|
62
|
+
const id=ctx.operation.id+':'+suffix;
|
|
63
|
+
let record=await transactions.get(id);
|
|
64
|
+
if(!record && reconcile)return {status:'pending',reference:id};
|
|
65
|
+
if(!record) {
|
|
66
|
+
const signer=await signerFor(ctx.operation.actor);
|
|
67
|
+
const address=getAddress(await signer.getAddress());
|
|
68
|
+
check(address===getAddress(await addressFor(ctx.operation.actor)),'signer ownership mismatch');
|
|
69
|
+
const minimum=await provider.getTransactionCount(address,'pending');
|
|
70
|
+
let request;
|
|
71
|
+
try {request=await signer.populateTransaction({to,data,nonce:minimum,chainId});}
|
|
72
|
+
catch(err) {if(err.code==='CALL_EXCEPTION' || err.code==='INSUFFICIENT_FUNDS')return {status:'rejected',reason:'Transaction preflight rejected before signing'};throw err;}
|
|
73
|
+
const nonce=await transactions.reserveNonce(`${chainId}:${address}`,minimum,id);
|
|
74
|
+
request.nonce=nonce;
|
|
75
|
+
const raw=await signer.signTransaction(request);
|
|
76
|
+
const tx=Transaction.from(raw);
|
|
77
|
+
check(tx.from===address && Number(tx.chainId)===chainId && tx.to===getAddress(to) && tx.data===data && tx.nonce===nonce && tx.value===0n,'signed transaction mismatch');
|
|
78
|
+
await transactions.create(id,{hash:tx.hash,raw,to:getAddress(to),data,from:address});
|
|
79
|
+
record=await transactions.get(id);
|
|
80
|
+
}
|
|
81
|
+
check(record.to===getAddress(to) && record.data===data && record.from===getAddress(await addressFor(ctx.operation.actor)),'journal request mismatch');
|
|
82
|
+
let r=await receipt(record.hash);
|
|
83
|
+
if(!r && !reconcile) {
|
|
84
|
+
try {await provider.broadcastTransaction(record.raw);} catch(err) {if(!await provider.getTransaction(record.hash))throw err;}
|
|
85
|
+
r=await receipt(record.hash);
|
|
86
|
+
}
|
|
87
|
+
if(!r)return {status:'pending',reference:record.hash};
|
|
88
|
+
if(r.status!==1)return {status:'rejected',reason:'Transaction reverted',reference:record.hash};
|
|
89
|
+
return {receipt:r};
|
|
90
|
+
}
|
|
91
|
+
async function observe(ctx,t,suffix,method,args,eventName,validate,reconcile) {
|
|
92
|
+
let sent;
|
|
93
|
+
if(ctx.input.transaction_hash) {
|
|
94
|
+
check(/^0x[0-9a-fA-F]{64}$/.test(ctx.input.transaction_hash),'invalid transaction hash');
|
|
95
|
+
const r=await receipt(ctx.input.transaction_hash);
|
|
96
|
+
sent=!r?{status:'pending',reference:ctx.input.transaction_hash}:r.status===1?{receipt:r}:{status:'rejected',reason:'Transaction reverted',reference:ctx.input.transaction_hash};
|
|
97
|
+
} else sent=await send(ctx,suffix,config.contractAddress,iface.encodeFunctionData(method,args),reconcile);
|
|
98
|
+
if(!sent.receipt)return sent;
|
|
99
|
+
const r=sent.receipt;
|
|
100
|
+
const logs=r.logs.filter(log=>getAddress(log.address)===config.contractAddress).map(log=>{try{return iface.parseLog(log);}catch{return null;}});
|
|
101
|
+
const event=logs.find(log=>log?.name===eventName && log.args.jobId===t.jobId);
|
|
102
|
+
check(event,'matching '+eventName+' event missing');
|
|
103
|
+
const snapshot=await contract.getJob(t.jobId,{blockTag:r.blockNumber});match(snapshot,t);validate(snapshot,event);
|
|
104
|
+
return {status:'confirmed',evidence:{chain_id:String(chainId),contract:config.contractAddress,token:config.tokenAddress,job_id:t.jobId,agreement_hash:t.agreementHash,transaction_hash:r.hash,block_number:r.blockNumber,block_hash:r.blockHash,confirmations,event:eventName,amount:t.amount.toString(),payer:t.payer,worker:t.worker,verifier:t.verifier,arbitrator:t.arbitrator,result_hash:snapshot.resultHash,worker_paid:snapshot.workerPaid.toString(),state:Number(snapshot.state),observed_at:new Date().toISOString()}};
|
|
105
|
+
}
|
|
106
|
+
function adapter(run){return {execute:ctx=>run(ctx,false),reconcile:ctx=>run(ctx,true)};}
|
|
107
|
+
const state=(s,n)=>check(Number(s.state)===n,'unexpected escrow state');
|
|
108
|
+
const settled=(s,e,t)=>{check([5,6,7].includes(Number(s.state)),'not settled');check(e.args.workerAmount+e.args.payerAmount===t.amount && e.args.workerAmount===s.workerPaid,'settlement amount mismatch');};
|
|
109
|
+
return {
|
|
110
|
+
fund:adapter(async(ctx,reconcile)=>{
|
|
111
|
+
const t=await context(ctx.job);
|
|
112
|
+
check([ctx.job.participants.requester,ctx.job.participants.worker].every(p=>ctx.job.agreement.accepted_by.includes(p)),'both acceptances required');
|
|
113
|
+
if(typeof ctx.input.worker_signature!=='string')return {status:'rejected',reason:'worker_signature required'};
|
|
114
|
+
// Consent is checked locally as well as by the contract before moving tokens.
|
|
115
|
+
const {verifyTypedData}=require('ethers');const c=workerConsent(ctx.job,config);
|
|
116
|
+
try {if(getAddress(verifyTypedData(c.domain,c.types,c.value,ctx.input.worker_signature))!==t.worker)return {status:'rejected',reason:'Invalid worker consent'};} catch {return {status:'rejected',reason:'Invalid worker consent'};}
|
|
117
|
+
// Never send further approvals if the funding transaction already exists in the journal.
|
|
118
|
+
if(!ctx.input.transaction_hash && !await transactions.get(ctx.operation.id+':fund')) {
|
|
119
|
+
let allowance=await token.allowance(t.payer,config.contractAddress);
|
|
120
|
+
if(allowance<t.amount) {
|
|
121
|
+
if(allowance>0n){const reset=await send(ctx,'approve_zero',config.tokenAddress,token.interface.encodeFunctionData('approve',[config.contractAddress,0]),reconcile);if(!reset.receipt)return reset;}
|
|
122
|
+
const approval=await send(ctx,'approve',config.tokenAddress,token.interface.encodeFunctionData('approve',[config.contractAddress,t.amount]),reconcile);if(!approval.receipt)return approval;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return observe(ctx,t,'fund','fund',[t,ctx.input.worker_signature],'Funded',(s,e)=>{check(Number(s.state)>0,'missing funded job');check(e.args.amount===t.amount && getAddress(e.args.payer)===t.payer && getAddress(e.args.worker)===t.worker && e.args.agreementHash===t.agreementHash,'funding event mismatch');},reconcile);
|
|
126
|
+
}),
|
|
127
|
+
start:adapter(async ctx=>{const t=await context(ctx.job);const block=await provider.getBlock('latest');const safeNumber=block.number-confirmations+1;check(safeNumber>=0,'insufficient confirmation depth');const safe=await provider.getBlock(safeNumber);const confirmed=await contract.getJob(t.jobId,{blockTag:safeNumber});match(confirmed,t);state(confirmed,1);const s=await contract.getJob(t.jobId,{blockTag:block.number});match(s,t);state(s,1);check(block.timestamp<=t.workDeadline,'work deadline passed');return {status:'confirmed',evidence:{chain_id:String(chainId),contract:config.contractAddress,job_id:t.jobId,block_number:safe.number,block_hash:safe.hash,confirmations,amount:t.amount.toString(),agreement_hash:t.agreementHash}};}),
|
|
128
|
+
submit:adapter(async(ctx,r)=>{const t=await context(ctx.job);const hash=bytes32(ctx.input.result_hash);return observe(ctx,t,'submit','submit',[t.jobId,hash],'Submitted',(s,e)=>{check(Number(s.state)>=2,'missing submission');check(s.resultHash===hash && e.args.resultHash===hash,'artifact commitment mismatch');},r);}),
|
|
129
|
+
verify:adapter(async(ctx,r)=>{const t=await context(ctx.job);const hash=bytes32(ctx.job.submission?.result_hash);if(!r && !await transactions.get(ctx.operation.id+':verify')){check(typeof validateArtifact==='function','validateArtifact callback required');if(await validateArtifact({job:structuredClone(ctx.job),resultHash:hash})!==true)return {status:'rejected',reason:'Artifact verification failed'};}return observe(ctx,t,'verify','verify',[t.jobId,hash],'Verified',(s,e)=>{check(Number(s.state)>=3,'missing verification');check(s.resultHash===hash && e.args.resultHash===hash,'verified artifact mismatch');},r);}),
|
|
130
|
+
release:adapter(async(ctx,r)=>{const t=await context(ctx.job);return observe(ctx,t,'release','release',[t.jobId],'Settled',(s,e)=>{state(s,5);settled(s,e,t);check(e.args.workerAmount===t.amount && Number(e.args.reason)===0,'not a full release');},r);}),
|
|
131
|
+
refund:adapter(async(ctx,r)=>{const t=await context(ctx.job);return observe(ctx,t,'refund','refundExpired',[t.jobId],'Settled',(s,e)=>{state(s,6);settled(s,e,t);check(e.args.payerAmount===t.amount && Number(e.args.reason)===1,'not an expiry refund');},r);}),
|
|
132
|
+
dispute:adapter(async(ctx,r)=>{const t=await context(ctx.job);const reason=bytes32(ctx.input.reason_hash);return observe(ctx,t,'dispute','dispute',[t.jobId,reason],'Disputed',(s,e)=>{check(Number(s.state)>=4,'missing dispute');check(e.args.reasonHash===reason,'reason mismatch');},r);}),
|
|
133
|
+
resolve:adapter(async(ctx,r)=>{const t=await context(ctx.job);check(typeof ctx.input.worker_amount==='string' && /^(0|[1-9][0-9]*)$/.test(ctx.input.worker_amount),'worker_amount must be decimal string');const amount=BigInt(ctx.input.worker_amount);check(amount<=t.amount,'resolution exceeds escrow');return observe(ctx,t,'resolve','resolve',[t.jobId,amount],'Settled',(s,e)=>{settled(s,e,t);check(e.args.workerAmount===amount && Number(e.args.reason)===2,'resolution mismatch');},r);}),
|
|
134
|
+
timeout:adapter(async(ctx,r)=>{const t=await context(ctx.job);const method=['funded','running'].includes(ctx.job.state)?'refundExpired':'timeout';return observe(ctx,t,'timeout',method,[t.jobId],'Settled',(s,e)=>{settled(s,e,t);const expected=method==='refundExpired'?0n:(t.amount*BigInt(t.timeoutWorkerBps))/10000n;check(e.args.workerAmount===expected && Number(e.args.reason)===(method==='refundExpired'?1:3),'timeout allocation mismatch');},r);})
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
module.exports={createEscrowAdapters,createMemoryTransactionStore,workerConsent,escrowTerms,escrowTransitions:transitions,ESCROW_ABI:ABI,ESCROW_BYTECODE:artifact.bytecode};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Provider, Signer, TypedDataField, InterfaceAbi } from 'ethers';
|
|
2
|
+
import type { WorkflowJob, AdapterContext, AdapterResult, Transition } from '../workflow.js';
|
|
3
|
+
export interface EscrowConfiguration { chainId: number; contractAddress: string; tokenAddress: string }
|
|
4
|
+
export interface ContractTerms {
|
|
5
|
+
jobId: string; payer: string; worker: string; verifier: string; arbitrator: string;
|
|
6
|
+
amount: bigint; workDeadline: number; reviewDeadline: number; resolutionDeadline: number;
|
|
7
|
+
timeoutWorkerBps: number; agreementHash: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TransactionRecord { hash: string; raw: string; to: string; data: string; from: string }
|
|
10
|
+
export interface TransactionStore {
|
|
11
|
+
get(id: string): Promise<TransactionRecord | null>;
|
|
12
|
+
/** Atomic insert if absent; caller reads winner before broadcasting. */
|
|
13
|
+
create(id: string, record: TransactionRecord): Promise<boolean>;
|
|
14
|
+
/** Atomically reserve a nonce per chain/address; same operationId always returns same nonce. */
|
|
15
|
+
reserveNonce(scope: string, minimum: number, operationId: string): Promise<number>;
|
|
16
|
+
}
|
|
17
|
+
export interface EscrowAdapter { execute(context: AdapterContext): Promise<AdapterResult>; reconcile(context: AdapterContext): Promise<AdapterResult> }
|
|
18
|
+
export interface EscrowAdapterOptions extends EscrowConfiguration {
|
|
19
|
+
provider: Provider;
|
|
20
|
+
/** Requires populateTransaction + offline signTransaction support; no silent wallet approval. */
|
|
21
|
+
signerFor(actor: string): Promise<Signer>;
|
|
22
|
+
addressFor(actor: string): Promise<string>;
|
|
23
|
+
transactions: TransactionStore;
|
|
24
|
+
confirmations: number;
|
|
25
|
+
validateArtifact?(context: { job: WorkflowJob; resultHash: string }): Promise<boolean>;
|
|
26
|
+
}
|
|
27
|
+
export function createEscrowAdapters(options: EscrowAdapterOptions): Record<'fund'|'start'|'submit'|'verify'|'release'|'refund'|'dispute'|'resolve'|'timeout', EscrowAdapter>;
|
|
28
|
+
export function createMemoryTransactionStore(): TransactionStore;
|
|
29
|
+
export function escrowTerms(job: WorkflowJob, config: EscrowConfiguration): ContractTerms;
|
|
30
|
+
export function workerConsent(job: WorkflowJob, config: EscrowConfiguration): {
|
|
31
|
+
domain: { name: string; version: string; chainId: number; verifyingContract: string };
|
|
32
|
+
types: Record<string, TypedDataField[]>; value: ContractTerms;
|
|
33
|
+
};
|
|
34
|
+
export const escrowTransitions: Record<string, Transition>;
|
|
35
|
+
export const ESCROW_ABI: InterfaceAbi;
|
|
36
|
+
export const ESCROW_BYTECODE: string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Provider, Signer, TypedDataField, InterfaceAbi } from 'ethers';
|
|
2
|
+
import type { WorkflowJob, AdapterContext, AdapterResult, Transition } from '../workflow.js';
|
|
3
|
+
export interface EscrowConfiguration { chainId: number; contractAddress: string; tokenAddress: string }
|
|
4
|
+
export interface ContractTerms {
|
|
5
|
+
jobId: string; payer: string; worker: string; verifier: string; arbitrator: string;
|
|
6
|
+
amount: bigint; workDeadline: number; reviewDeadline: number; resolutionDeadline: number;
|
|
7
|
+
timeoutWorkerBps: number; agreementHash: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TransactionRecord { hash: string; raw: string; to: string; data: string; from: string }
|
|
10
|
+
export interface TransactionStore {
|
|
11
|
+
get(id: string): Promise<TransactionRecord | null>;
|
|
12
|
+
/** Atomic insert if absent; caller reads winner before broadcasting. */
|
|
13
|
+
create(id: string, record: TransactionRecord): Promise<boolean>;
|
|
14
|
+
/** Atomically reserve a nonce per chain/address; same operationId always returns same nonce. */
|
|
15
|
+
reserveNonce(scope: string, minimum: number, operationId: string): Promise<number>;
|
|
16
|
+
}
|
|
17
|
+
export interface EscrowAdapter { execute(context: AdapterContext): Promise<AdapterResult>; reconcile(context: AdapterContext): Promise<AdapterResult> }
|
|
18
|
+
export interface EscrowAdapterOptions extends EscrowConfiguration {
|
|
19
|
+
provider: Provider;
|
|
20
|
+
/** Requires populateTransaction + offline signTransaction support; no silent wallet approval. */
|
|
21
|
+
signerFor(actor: string): Promise<Signer>;
|
|
22
|
+
addressFor(actor: string): Promise<string>;
|
|
23
|
+
transactions: TransactionStore;
|
|
24
|
+
confirmations: number;
|
|
25
|
+
validateArtifact?(context: { job: WorkflowJob; resultHash: string }): Promise<boolean>;
|
|
26
|
+
}
|
|
27
|
+
export function createEscrowAdapters(options: EscrowAdapterOptions): Record<'fund'|'start'|'submit'|'verify'|'release'|'refund'|'dispute'|'resolve'|'timeout', EscrowAdapter>;
|
|
28
|
+
export function createMemoryTransactionStore(): TransactionStore;
|
|
29
|
+
export function escrowTerms(job: WorkflowJob, config: EscrowConfiguration): ContractTerms;
|
|
30
|
+
export function workerConsent(job: WorkflowJob, config: EscrowConfiguration): {
|
|
31
|
+
domain: { name: string; version: string; chainId: number; verifyingContract: string };
|
|
32
|
+
types: Record<string, TypedDataField[]>; value: ContractTerms;
|
|
33
|
+
};
|
|
34
|
+
export const escrowTransitions: Record<string, Transition>;
|
|
35
|
+
export const ESCROW_ABI: InterfaceAbi;
|
|
36
|
+
export const ESCROW_BYTECODE: string;
|