@allorc/a423-sdk 0.3.0 → 0.4.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 +10 -0
- package/ESCROW.md +7 -6
- package/INTEGRATION.md +8 -2
- package/LEGACY.md +251 -0
- package/OPERATIONS.md +182 -0
- package/README.md +65 -317
- package/SECURITY.md +8 -7
- package/contracts/A423Escrow.sol +5 -1
- package/contracts/standard-input.json +80 -0
- package/examples/operations-dashboard.html +16 -0
- package/examples/operations-server.mjs +19 -0
- package/package.json +35 -5
- package/scripts/deploy-escrow.mjs +24 -0
- package/scripts/operations-worker.mjs +22 -0
- package/src/authority/index.cjs +81 -0
- package/src/authority/index.d.ts +24 -0
- package/src/authority/index.mjs +2 -0
- package/src/escrow/artifact.json +1 -1
- package/src/escrow/index.cjs +22 -9
- package/src/escrow/index.d.mts +13 -1
- package/src/escrow/index.d.ts +13 -1
- package/src/escrow/index.mjs +1 -1
- package/src/escrow/services.cjs +119 -0
- package/src/operations/application.cjs +38 -0
- package/src/operations/factory.cjs +33 -0
- package/src/operations/index.cjs +105 -0
- package/src/operations/index.d.ts +29 -0
- package/src/operations/index.mjs +2 -0
- package/src/storage/index.cjs +83 -0
- package/src/storage/index.d.ts +37 -0
- package/src/storage/index.mjs +2 -0
- package/src/workflow.cjs +14 -2
- package/src/workflow.d.ts +3 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Opt-in operational loop; application config must explicitly supply signer approvals and execution callbacks.
|
|
2
|
+
import {pathToFileURL} from 'node:url';
|
|
3
|
+
import {setTimeout} from 'node:timers/promises';
|
|
4
|
+
import {watchEvidenceOnce,runMaintenanceOnce,enqueueRunningJobs,runExecutionOnce} from '../src/operations/index.mjs';
|
|
5
|
+
if(!process.env.A423_CONFIG_MODULE)throw Error('A423_CONFIG_MODULE required');
|
|
6
|
+
const {applicationOptions:app,workerOptions:worker}=await import(pathToFileURL(process.env.A423_CONFIG_MODULE).href);
|
|
7
|
+
if(!worker?.actorFor)throw Error('workerOptions.actorFor must explicitly delegate maintenance actors');
|
|
8
|
+
const controller=new AbortController();for(const signal of ['SIGINT','SIGTERM'])process.on(signal,()=>controller.abort());
|
|
9
|
+
while(!controller.signal.aborted){
|
|
10
|
+
try{
|
|
11
|
+
await watchEvidenceOnce({store:app.store,revalidate:app.revalidate});
|
|
12
|
+
if(worker.index)await worker.index();
|
|
13
|
+
if(worker.replaceFees)await worker.replaceFees();
|
|
14
|
+
const maintenance=await runMaintenanceOnce({workflow:app.workflow,store:app.store,actorFor:worker.actorFor,allowExecute:worker.allowExecute===true});
|
|
15
|
+
for(const result of maintenance)if(result.error)console.error('A423 maintenance failed for job',result.id);
|
|
16
|
+
await enqueueRunningJobs({store:app.store,queue:app.queue});
|
|
17
|
+
if(worker.execute)await runExecutionOnce({workflow:app.workflow,queue:app.queue,execute:worker.execute,beforeExecute:worker.beforeExecute});
|
|
18
|
+
if(worker.webhooks){await worker.webhooks.project(app.store);for(let i=0;i<10;i++)if(!await worker.webhooks.deliverOne())break;}
|
|
19
|
+
if(worker.authority)await worker.authority.pruneChallenges();
|
|
20
|
+
}catch(error){console.error('A423 worker tick failed:',error.code||error.name);}
|
|
21
|
+
try{await setTimeout(5000,undefined,{signal:controller.signal});}catch{}
|
|
22
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const {createPublicKey,sign,verify,randomBytes,createHash}=require('node:crypto');
|
|
3
|
+
const encode=x=>Buffer.from(JSON.stringify(x)).toString('base64url');
|
|
4
|
+
const fingerprint=key=>createHash('sha256').update(createPublicKey(key).export({type:'spki',format:'der'})).digest('hex');
|
|
5
|
+
function requireValue(ok,message){if(!ok)throw Error(message);}
|
|
6
|
+
function publicKey(pem){const key=createPublicKey(pem);requireValue(key.asymmetricKeyType==='ed25519','Ed25519 public key required');return key;}
|
|
7
|
+
function createAuthority({issuer,audience,privateKey,kv,credentialTtlSeconds=3600,challengeTtlSeconds=120,now=()=>Date.now()}={}){
|
|
8
|
+
requireValue(issuer&&audience&&privateKey&&kv,'Authority configuration required');publicKey(createPublicKey(privateKey).export({type:'spki',format:'pem'}));
|
|
9
|
+
requireValue(Number.isInteger(credentialTtlSeconds)&&credentialTtlSeconds>0&&credentialTtlSeconds<=86400,'Credential TTL must be 1..86400 seconds');
|
|
10
|
+
requireValue(Number.isInteger(challengeTtlSeconds)&&challengeTtlSeconds>0&&challengeTtlSeconds<=600,'Challenge TTL must be 1..600 seconds');
|
|
11
|
+
const ns='authority:'+issuer+':'+audience;
|
|
12
|
+
const read=(kind,id)=>kv.get(ns+':'+kind,id),put=(kind,id,v)=>kv.put(ns+':'+kind,id,v);
|
|
13
|
+
const safe=x=>requireValue(typeof x==='string'&&/^[A-Za-z0-9:_-]{1,256}$/.test(x),'Invalid identity');
|
|
14
|
+
async function records(agentId){const agent=await read('agents',agentId);requireValue(agent&&!agent.revoked,'Unknown/revoked agent');const org=await read('organizations',agent.organization);requireValue(org&&!org.revoked,'Unknown/revoked organization');return {agent,org};}
|
|
15
|
+
const registration=({organization,agentId,publicKey: key,scopes})=>({version:'a423.enrollment.v1',issuer,audience,organization,agent_id:agentId,key_fingerprint:fingerprint(key),scopes:[...scopes].sort()});
|
|
16
|
+
return {
|
|
17
|
+
publicKey:createPublicKey(privateKey).export({type:'spki',format:'pem'}),
|
|
18
|
+
registration,
|
|
19
|
+
async isActive(agentId){try{await records(agentId);return true;}catch{return false;}},
|
|
20
|
+
/** Administrative capability: perform real-world approval outside this module before calling. */
|
|
21
|
+
async registerOrganization({id,name,publicKey:key}){safe(id);requireValue(typeof name==='string'&&name.trim()&&name.length<=256,'Organization name required');publicKey(key);requireValue(await kv.create(ns+':organizations',id,{id,name,publicKey:key,revoked:false}),'Organization already registered');},
|
|
22
|
+
async revokeOrganization(id){const org=await read('organizations',id);requireValue(org,'Unknown organization');await put('organizations',id,{...org,revoked:true});},
|
|
23
|
+
async registerAgent({organization,agentId,publicKey:key,scopes=['workflow'],organizationSignature}){
|
|
24
|
+
safe(agentId);const org=await read('organizations',organization);requireValue(org&&!org.revoked,'Organization not approved');publicKey(key);
|
|
25
|
+
requireValue(Array.isArray(scopes)&&scopes.length>0&&scopes.length<=64&&scopes.every(s=>typeof s==='string'&&/^[a-z0-9:_-]{1,128}$/.test(s)),'Invalid scopes');
|
|
26
|
+
const statement=registration({organization,agentId,publicKey:key,scopes});
|
|
27
|
+
requireValue(verify(null,Buffer.from(JSON.stringify(statement)),publicKey(org.publicKey),Buffer.from(organizationSignature,'base64url')),'Invalid organization enrollment signature');
|
|
28
|
+
requireValue(await kv.create(ns+':agents',agentId,{id:agentId,organization,publicKey:key,scopes:[...new Set(scopes)],revoked:false}),'Agent already registered');
|
|
29
|
+
},
|
|
30
|
+
async revokeAgent(agentId){const agent=await read('agents',agentId);requireValue(agent,'Unknown agent');await put('agents',agentId,{...agent,revoked:true});},
|
|
31
|
+
async challenge(agentId){const {agent}=await records(agentId);const id=randomBytes(32).toString('base64url');const statement={version:'a423.challenge.v1',issuer,audience,id,agent_id:agentId,key_fingerprint:fingerprint(agent.publicKey),expires_at:Math.floor(now()/1000)+challengeTtlSeconds};await kv.create(ns+':challenges',id,statement);return statement;},
|
|
32
|
+
async exchange({challengeId,signature}){
|
|
33
|
+
safe(challengeId);const challenge=await kv.take(ns+':challenges',challengeId);requireValue(challenge&&challenge.expires_at>Math.floor(now()/1000),'Challenge expired, consumed or unknown');const {agent}=await records(challenge.agent_id);
|
|
34
|
+
requireValue(verify(null,Buffer.from(JSON.stringify(challenge)),publicKey(agent.publicKey),Buffer.from(signature,'base64url')),'Invalid challenge signature');
|
|
35
|
+
const issued=Math.floor(now()/1000);const payload={version:'a423.credential.v1',iss:issuer,aud:audience,sub:agent.id,org:agent.organization,key_fingerprint:fingerprint(agent.publicKey),scopes:agent.scopes,iat:issued,exp:issued+credentialTtlSeconds,jti:randomBytes(24).toString('base64url')};const content=encode(payload);return {credential:content+'.'+sign(null,Buffer.from(content),privateKey).toString('base64url'),expires_at:payload.exp};
|
|
36
|
+
},
|
|
37
|
+
async verifyCredential(credential,{scope='workflow'}={}){
|
|
38
|
+
requireValue(typeof credential==='string'&&credential.length<=16000,'Invalid credential');const parts=credential.split('.');requireValue(parts.length===2,'Invalid credential encoding');
|
|
39
|
+
requireValue(verify(null,Buffer.from(parts[0]),createPublicKey(privateKey),Buffer.from(parts[1],'base64url')),'Invalid authority signature');
|
|
40
|
+
const p=JSON.parse(Buffer.from(parts[0],'base64url').toString('utf8'));const time=Math.floor(now()/1000);
|
|
41
|
+
requireValue(p.version==='a423.credential.v1'&&p.iss===issuer&&p.aud===audience&&Number.isSafeInteger(p.exp)&&Number.isSafeInteger(p.iat)&&p.iat<=time&&p.exp>time&&p.exp-p.iat<=credentialTtlSeconds,'Expired or invalid credential');
|
|
42
|
+
const {agent}=await records(p.sub);requireValue(p.org===agent.organization&&p.key_fingerprint===fingerprint(agent.publicKey)&&p.scopes.includes(scope)&&agent.scopes.includes(scope),'Credential binding/scope mismatch');return p;
|
|
43
|
+
},
|
|
44
|
+
async pruneChallenges(){let removed=0;for(const r of await kv.list(ns+':challenges'))if(r.value.expires_at<=Math.floor(now()/1000)){await kv.take(ns+':challenges',r.id);removed++;}return removed;}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function createCredentialAuthenticator({authority,scope='workflow'}={}){return async request=>{const h=request.headers.get('authorization')||'';if(!h.startsWith('Bearer '))return null;try{return (await authority.verifyCredential(h.slice(7),{scope})).sub;}catch{return null;}};}
|
|
48
|
+
/** Renew using a fresh proof of possession. Never prolong a credential from a bearer token alone. */
|
|
49
|
+
function createCredentialProvider({agentId,challenge,exchange,signChallenge,refreshBeforeSeconds=60,now=()=>Date.now()}={}){
|
|
50
|
+
let current=null,inflight=null;
|
|
51
|
+
return async()=>{
|
|
52
|
+
if(current&¤t.expires_at-Math.floor(now()/1000)>refreshBeforeSeconds)return current.credential;
|
|
53
|
+
if(!inflight)inflight=(async()=>{const c=await challenge(agentId);const signature=await signChallenge(Buffer.from(JSON.stringify(c)));current=await exchange({challengeId:c.id,signature});return current.credential;})().finally(()=>{inflight=null;});
|
|
54
|
+
return inflight;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
module.exports={createAuthority,createCredentialAuthenticator,createCredentialProvider};
|
|
58
|
+
/** Public proof endpoints only. Administrative enrollment is deliberately not exposed. */
|
|
59
|
+
function createAuthorityHandler({authority,basePath='/api/a423-auth',allowRequest=async()=>false}={}){
|
|
60
|
+
return async request=>{
|
|
61
|
+
const path=new URL(request.url).pathname;
|
|
62
|
+
if(request.method!=='POST'||![basePath+'/challenge',basePath+'/exchange'].includes(path))return Response.json({error:'not found'},{status:404});
|
|
63
|
+
if(await allowRequest(request)!==true)return Response.json({error:'rate limit or policy denied'},{status:429});
|
|
64
|
+
try{if(!request.body||!(request.headers.get('content-type')||'').startsWith('application/json'))throw Error('JSON required');const reader=request.body.getReader();let size=0;const chunks=[];try{while(true){const {done,value}=await reader.read();if(done)break;size+=value.byteLength;if(size>16384){await reader.cancel();throw Error('Body too large');}chunks.push(Buffer.from(value));}}finally{reader.releaseLock();}const body=JSON.parse(Buffer.concat(chunks).toString('utf8'));
|
|
65
|
+
const result=path.endsWith('/challenge')?await authority.challenge(body.agent_id):await authority.exchange(body);
|
|
66
|
+
return Response.json(result,{headers:{'cache-control':'no-store'}});
|
|
67
|
+
}catch{return Response.json({error:'invalid proof or request'},{status:400});}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
module.exports.createAuthorityHandler=createAuthorityHandler;
|
|
71
|
+
/** Receiver-side verification uses public trust roots and a revocation/status lookup, never the authority private key. */
|
|
72
|
+
function createCredentialVerifier({issuer,audience,publicKey:trustedKey,isActive,maxTtlSeconds=86400,now=()=>Date.now()}={}){
|
|
73
|
+
const key=publicKey(trustedKey);requireValue(typeof isActive==='function','Explicit revocation/status lookup required');
|
|
74
|
+
return async(credential,{scope='workflow'}={})=>{
|
|
75
|
+
requireValue(typeof credential==='string'&&credential.length<=16000,'Invalid credential');const parts=credential.split('.');requireValue(parts.length===2&&verify(null,Buffer.from(parts[0]),key,Buffer.from(parts[1],'base64url')),'Invalid authority signature');
|
|
76
|
+
const p=JSON.parse(Buffer.from(parts[0],'base64url').toString('utf8')),time=Math.floor(now()/1000);
|
|
77
|
+
requireValue(p.version==='a423.credential.v1'&&p.iss===issuer&&p.aud===audience&&Number.isSafeInteger(p.exp)&&Number.isSafeInteger(p.iat)&&p.iat<=time&&p.exp>time&&p.exp-p.iat<=maxTtlSeconds&&Array.isArray(p.scopes)&&p.scopes.includes(scope),'Expired or invalid credential');
|
|
78
|
+
requireValue(await isActive(p)===true,'Agent/organization revoked or unavailable');return p;
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
module.exports.createCredentialVerifier=createCredentialVerifier;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { KeyObject } from 'node:crypto';
|
|
2
|
+
import type { KeyValueStore } from '../storage/index.js';
|
|
3
|
+
export interface Challenge {version: string;issuer: string;audience: string;id: string;agent_id: string;key_fingerprint: string;expires_at: number}
|
|
4
|
+
export interface Credential {credential: string;expires_at: number}
|
|
5
|
+
export interface Enrollment {organization: string;agentId: string;publicKey: string;scopes: string[]}
|
|
6
|
+
export interface Authority {
|
|
7
|
+
publicKey: string;
|
|
8
|
+
isActive(agentId: string): Promise<boolean>;
|
|
9
|
+
registration(input: Enrollment): object;
|
|
10
|
+
registerOrganization(input: {id: string;name: string;publicKey: string}): Promise<void>;
|
|
11
|
+
revokeOrganization(id: string): Promise<void>;
|
|
12
|
+
registerAgent(input: Enrollment & {organizationSignature: string}): Promise<void>;
|
|
13
|
+
revokeAgent(id: string): Promise<void>;
|
|
14
|
+
challenge(agentId: string): Promise<Challenge>;
|
|
15
|
+
exchange(input: {challengeId: string;signature: string}): Promise<Credential>;
|
|
16
|
+
verifyCredential(token: string, options?: {scope?: string}): Promise<{sub: string;org: string;scopes: string[];exp: number;[key: string]: unknown}>;
|
|
17
|
+
pruneChallenges(): Promise<number>;
|
|
18
|
+
}
|
|
19
|
+
export function createAuthority(options: {issuer: string;audience: string;privateKey: string | KeyObject;kv: KeyValueStore;credentialTtlSeconds?: number;challengeTtlSeconds?: number;now?(): number}): Authority;
|
|
20
|
+
export function createCredentialAuthenticator(options: {authority: Pick<Authority,'verifyCredential'>;scope?: string}): (request: Request) => Promise<string | null>;
|
|
21
|
+
export function createCredentialProvider(options: {agentId: string;challenge(id: string): Promise<Challenge>;exchange(input: {challengeId: string;signature: string}): Promise<Credential>;signChallenge(bytes: Buffer): Promise<string>;refreshBeforeSeconds?: number;now?(): number}): () => Promise<string>;
|
|
22
|
+
export function createAuthorityHandler(options: {authority: Authority;basePath?: string;allowRequest?(request: Request): Promise<boolean>}): (request: Request) => Promise<Response>;
|
|
23
|
+
|
|
24
|
+
export function createCredentialVerifier(options: {issuer: string;audience: string;publicKey: string;isActive(payload: any): Promise<boolean>;maxTtlSeconds?: number;now?(): number}): Authority['verifyCredential'];
|
package/src/escrow/artifact.json
CHANGED
|
@@ -657,5 +657,5 @@
|
|
|
657
657
|
"type": "function"
|
|
658
658
|
}
|
|
659
659
|
],
|
|
660
|
-
"bytecode": "0x610180604052348015610010575f5ffd5b5060405161269d38038061269d83398101604081905261002f916101df565b6040518060400160405280600a81526020016941343233457363726f7760b01b815250604051806040016040528060018152602001603160f81b81525060015f8190555061008760018361016760201b90919060201c565b61012052610096816002610167565b61014052815160208084019190912060e052815190820120610100524660a05261012260e05161010051604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201529081019290925260608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b60805250503060c0526001600160a01b0381163b5f0361015557604051630256965f60e01b815260040160405180910390fd5b6001600160a01b0316610160526103cc565b5f6020835110156101825761017b83610199565b9050610193565b8161018d84826102a4565b5060ff90505b92915050565b5f5f829050601f815111156101cc578260405163305a27a960e01b81526004016101c3919061035e565b60405180910390fd5b80516101d7826103a9565b179392505050565b5f602082840312156101ef575f5ffd5b81516001600160a01b0381168114610205575f5ffd5b9392505050565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061023457607f821691505b60208210810361025257634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561029f57805f5260205f20601f840160051c8101602085101561027d5750805b601f840160051c820191505b8181101561029c575f8155600101610289565b50505b505050565b81516001600160401b038111156102bd576102bd61020c565b6102d1816102cb8454610220565b84610258565b6020601f821160018114610303575f83156102ec5750848201515b5f19600385901b1c1916600184901b17845561029c565b5f84815260208120601f198516915b828110156103325787850151825560209485019460019092019101610312565b508482101561034f57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b602081525f82518060208401525f5b8181101561038a576020818601810151604086840101520161036d565b505f604082850101526040601f19601f83011684010191505092915050565b80516020808301519190811015610252575f1960209190910360031b1b16919050565b60805160a05160c05160e0516101005161012051610140516101605161223e61045f5f395f81816101f601528181610c3001528181610cc001528181610d0f015281816115760152818161160b01528181611683015281816116c9015261176b01525f61137b01525f61134901525f61150f01525f6114e701525f61144201525f61146c01525f611496015261223e5ff3fe608060405234801561000f575f5ffd5b50600436106100cb575f3560e01c806384b0196e11610088578063d26ff86e11610063578063d26ff86e146101ab578063edb70d9e146101be578063f729cf0d146101d1578063fc0c546a146101f1575f5ffd5b806384b0196e1461016a5780638a92631714610185578063cc3e049b14610198575f5ffd5b8063099d2518146100cf5780633aa36987146100f55780634e8fee001461011c57806367d42a8b14610131578063795ea18e1461014457806382cbf88b14610157575b5f5ffd5b6100e26100dd366004611b84565b610230565b6040519081526020015b60405180910390f35b6100e27f3a82e9543b510404d05948b3e6cbf4e32f66690f864ccb5ef67ee5927a49a4ba81565b61012f61012a366004611ba6565b610288565b005b61012f61013f366004611bc6565b61039f565b61012f610152366004611bc6565b61046f565b61012f610165366004611ba6565b6105cb565b61017261070e565b6040516100ec9796959493929190611c20565b61012f610193366004611cb6565b610750565b61012f6101a6366004611bc6565b610e23565b61012f6101b9366004611ba6565b610eab565b61012f6101cc366004611ba6565b610fb1565b6101e46101df366004611bc6565b611089565b6040516100ec9190611d69565b6102187f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100ec565b5f6102827f3a82e9543b510404d05948b3e6cbf4e32f66690f864ccb5ef67ee5927a49a4ba83604051602001610267929190611ed6565b6040516020818303038152906040528051906020012061120b565b92915050565b5f828152600360208190526040909120908101546001600160a01b031633146102c3576040516282b42960e81b815260040160405180910390fd5b6002600882015460ff1660078111156102de576102de611d35565b146102fc57604051636f20b45d60e11b815260040160405180910390fd5b6006810154600160401b90046001600160401b03164211156103315760405163637910d560e11b815260040160405180910390fd5b8060090154821461035557604051630256965f60e01b815260040160405180910390fd5b60088101805460ff1916600317905560405182815283907f5c83c73bb0b96f8bc4b95aabcb7980de29ee8ec40cf1aa83c5bd999b44077557906020015b60405180910390a2505050565b6103a7611237565b5f818152600360208190526040909120908101546001600160a01b031633146103e2576040516282b42960e81b815260040160405180910390fd5b6003600882015460ff1660078111156103fd576103fd611d35565b1461041b57604051636f20b45d60e11b815260040160405180910390fd5b6006810154600160801b90046001600160401b03164211156104505760405163637910d560e11b815260040160405180910390fd5b6104628282835f01600501545f61125f565b5061046c60015f55565b50565b610477611237565b5f8181526003602052604090206002600882015460ff16600781111561049f5761049f611d35565b141580156104c657506003600882015460ff1660078111156104c3576104c3611d35565b14155b80156104eb57506004600882015460ff1660078111156104e8576104e8611d35565b14155b1561050957604051636f20b45d60e11b815260040160405180910390fd5b6006810154600160801b90046001600160401b0316421161053d5760405163637910d560e11b815260040160405180910390fd5b600681015460058201545f9161271091600160c01b90910461ffff1690610565908390611fff565b61056f9190612026565b610579919061203d565b60068301546005840154600160c01b90910461ffff169061059d906127109061203d565b6105a79190612026565b6105b19190612050565b90506105c0838383600361125f565b505061046c60015f55565b5f82815260036020526040902060018101546001600160a01b03163314801590610602575060028101546001600160a01b03163314155b801561061b575060038101546001600160a01b03163314155b15610638576040516282b42960e81b815260040160405180910390fd5b6002600882015460ff16600781111561065357610653611d35565b1415801561067a57506003600882015460ff16600781111561067757610677611d35565b14155b1561069857604051636f20b45d60e11b815260040160405180910390fd5b6006810154600160801b90046001600160401b03164211156106cd5760405163637910d560e11b815260040160405180910390fd5b60088101805460ff1916600417905560405182815283907f8e39eab2c454d024fb5c89b50dabc51f5068c7a283891d06aa91f5ba96acded190602001610392565b5f6060805f5f5f606061071f611342565b610727611374565b604080515f80825260208201909252600f60f81b9b939a50919850469750309650945092509050565b610758611237565b6107686040840160208501612063565b6001600160a01b0316336001600160a01b031614610798576040516282b42960e81b815260040160405180910390fd5b82355f9081526003602052604081206008015460ff1660078111156107bf576107bf611d35565b146107dd57604051636f20b45d60e11b815260040160405180910390fd5b823515806107ee5750610140830135155b806107fb575060a0830135155b8061081d57505f6108126060850160408601612063565b6001600160a01b0316145b8061085757506108336040840160208501612063565b6001600160a01b031661084c6060850160408601612063565b6001600160a01b0316145b8061087957505f61086e6080850160608601612063565b6001600160a01b0316145b8061089b57505f61089060a0850160808601612063565b6001600160a01b0316145b806108d557506108b16040840160208501612063565b6001600160a01b03166108ca60a0850160808601612063565b6001600160a01b0316145b8061090f57506108eb6060840160408501612063565b6001600160a01b031661090460a0850160808601612063565b6001600160a01b0316145b80610930575061271061092a6101408501610120860161207c565b61ffff16115b1561094e57604051630256965f60e01b815260040160405180910390fd5b4261095f60e0850160c08601612095565b6001600160401b03161115806109a6575061098060e0840160c08501612095565b6001600160401b031661099a610100850160e08601612095565b6001600160401b031611155b806109e457506109bd610100840160e08501612095565b6001600160401b03166109d861012085016101008601612095565b6001600160401b031611155b15610a0257604051630256965f60e01b815260040160405180910390fd5b610a126060840160408501612063565b6001600160a01b0316610a62610a2785610230565b84848080601f0160208091040260200160405190810160405280939291908181526020018383808284375f920191909152506113a192505050565b6001600160a01b031614610a88576040516282b42960e81b815260040160405180910390fd5b604051806080016040528084803603810190610aa491906120e3565b8152602001600181525f602080830182905260409283018290528635825260038082529183902084518051825580830151600180840180546001600160a01b039384166001600160a01b03199182161790915596830151600285018054918416918916919091179055606083015195840180549683169688169690961790955560808201516004840180549190921696169590951790945560a0840151600582015560c084015160068201805460e087015161010088015161012089015161ffff16600160c01b0261ffff60c01b196001600160401b03928316600160801b021669ffffffffffffffffffff60801b19938316600160401b026fffffffffffffffffffffffffffffffff19909516929096169190911792909217169290921791909117905561014090930151600780850191909155908401516008840180549193909260ff19909216918490811115610bff57610bff611d35565b02179055506040828101516009830155606090920151600a90910155516370a0823160e01b81523060048201525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015610c7d573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610ca191906121ac565b9050610cec610cb66040860160208701612063565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016903060a08801356113c9565b610cfa60a085013582612050565b6040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015610d5c573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d8091906121ac565b14610d9e5760405163570883e560e01b815260040160405180910390fd5b610dae6040850160208601612063565b6001600160a01b031661014085013585357f32f3ba658fc1eefa6d5ca6ba35e8715bb439f7713edf2d61c03f4920fcb8cbab610df06060890160408a01612063565b604080516001600160a01b03909216825260a08a013560208301520160405180910390a450610e1e60015f55565b505050565b610e2b611237565b5f8181526003602052604090206001600882015460ff166007811115610e5357610e53611d35565b14610e7157604051636f20b45d60e11b815260040160405180910390fd5b60068101546001600160401b03164211610e9e5760405163637910d560e11b815260040160405180910390fd5b61046282825f600161125f565b5f82815260036020526040902060028101546001600160a01b03163314610ee4576040516282b42960e81b815260040160405180910390fd5b6001600882015460ff166007811115610eff57610eff611d35565b14610f1d57604051636f20b45d60e11b815260040160405180910390fd5b60068101546001600160401b0316421115610f4b5760405163637910d560e11b815260040160405180910390fd5b81610f6957604051630256965f60e01b815260040160405180910390fd5b6009810182905560088101805460ff1916600217905560405182815283907fa2f32e666157494cac7b795a43a2914dcb77efd9515b47db67dd15d332234f9790602001610392565b610fb9611237565b5f82815260036020526040902060048101546001600160a01b03163314610ff2576040516282b42960e81b815260040160405180910390fd5b6004600882015460ff16600781111561100d5761100d611d35565b1461102b57604051636f20b45d60e11b815260040160405180910390fd5b6006810154600160801b90046001600160401b03164211806110505750600581015482115b1561106e57604051630256965f60e01b815260040160405180910390fd5b61107b838284600261125f565b5061108560015f55565b5050565b604080516101e0810182525f6080820181815260a0830182905260c0830182905260e08301829052610100830182905261012083018290526101408301829052610160830182905261018083018290526101a083018290526101c0830182905282526020820181905291810182905260608101919091525f8281526003602081815260409283902083516101e0810190945280546080850190815260018201546001600160a01b0390811660a08701526002830154811660c087015293820154841660e08601526004820154909316610100850152600581015461012085015260068101546001600160401b03808216610140870152600160401b82048116610160870152600160801b820416610180860152600160c01b900461ffff166101a08501526007808201546101c0860152928452600881015490929184019160ff909116908111156111dc576111dc611d35565b60078111156111ed576111ed611d35565b815260098201546020820152600a9091015460409091015292915050565b5f610282611217611436565b8360405161190160f01b8152600281019290925260228201526042902090565b60025f540361125957604051633ee5aeb560e01b815260040160405180910390fd5b60025f55565b60058301545f906112719084906121c3565b9050821561128d578015611286576007611290565b6005611290565b60065b60088501805460ff191660018360078111156112ae576112ae611d35565b0217905550600a840183905582156112d85760028401546112d8906001600160a01b03168461155f565b80156112f65760018401546112f6906001600160a01b03168261155f565b604080518481526020810183905260ff841681830152905186917fb3a77f60f2042a66aa50b5810774aab5f1a2dd6799ee891ebb9fe98b95fec57f919081900360600190a25050505050565b606061136f7f000000000000000000000000000000000000000000000000000000000000000060016117f5565b905090565b606061136f7f000000000000000000000000000000000000000000000000000000000000000060026117f5565b5f5f5f5f6113af868661189e565b9250925092506113bf82826118e7565b5090949350505050565b6040516001600160a01b0384811660248301528381166044830152606482018390526114309186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b0383818316178352505050506119a4565b50505050565b5f306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614801561148e57507f000000000000000000000000000000000000000000000000000000000000000046145b156114b857507f000000000000000000000000000000000000000000000000000000000000000090565b61136f604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b6040516370a0823160e01b81523060048201525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa1580156115c3573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906115e791906121ac565b6040516370a0823160e01b81526001600160a01b0385811660048301529192505f917f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa158015611650573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061167491906121ac565b90506116aa6001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168585611a10565b6116b483836121c3565b6040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015611716573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061173a91906121ac565b1415806117d7575061174c8382612050565b6040516370a0823160e01b81526001600160a01b0386811660048301527f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa1580156117b0573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906117d491906121ac565b14155b156114305760405163570883e560e01b815260040160405180910390fd5b606060ff831461180f5761180883611a41565b9050610282565b81805461181b906121d6565b80601f0160208091040260200160405190810160405280929190818152602001828054611847906121d6565b80156118925780601f1061186957610100808354040283529160200191611892565b820191905f5260205f20905b81548152906001019060200180831161187557829003601f168201915b50505050509050610282565b5f5f5f83516041036118d5576020840151604085015160608601515f1a6118c788828585611a7e565b9550955095505050506118e0565b505081515f91506002905b9250925092565b5f8260038111156118fa576118fa611d35565b03611903575050565b600182600381111561191757611917611d35565b036119355760405163f645eedf60e01b815260040160405180910390fd5b600282600381111561194957611949611d35565b0361196f5760405163fce698f760e01b8152600481018290526024015b60405180910390fd5b600382600381111561198357611983611d35565b03611085576040516335e2f38360e21b815260048101829052602401611966565b5f5f60205f8451602086015f885af1806119c3576040513d5f823e3d81fd5b50505f513d915081156119da5780600114156119e7565b6001600160a01b0384163b155b1561143057604051635274afe760e01b81526001600160a01b0385166004820152602401611966565b6040516001600160a01b03838116602483015260448201839052610e1e91859182169063a9059cbb906064016113fe565b60605f611a4d83611b46565b6040805160208082528183019092529192505f91906020820181803683375050509182525060208101929092525090565b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115611ab757505f91506003905082611b3c565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015611b08573d5f5f3e3d5ffd5b5050604051601f1901519150506001600160a01b038116611b3357505f925060019150829050611b3c565b92505f91508190505b9450945094915050565b5f60ff8216601f81111561028257604051632cd44ac360e21b815260040160405180910390fd5b5f6101608284031215611b7e575f5ffd5b50919050565b5f6101608284031215611b95575f5ffd5b611b9f8383611b6d565b9392505050565b5f5f60408385031215611bb7575f5ffd5b50508035926020909101359150565b5f60208284031215611bd6575f5ffd5b5035919050565b5f81518084525f5b81811015611c0157602081850181015186830182015201611be5565b505f602082860101526020601f19601f83011685010191505092915050565b60ff60f81b8816815260e060208201525f611c3e60e0830189611bdd565b8281036040840152611c508189611bdd565b606084018890526001600160a01b038716608085015260a0840186905283810360c0850152845180825260208087019350909101905f5b81811015611ca5578351835260209384019390920191600101611c87565b50909b9a5050505050505050505050565b5f5f5f6101808486031215611cc9575f5ffd5b611cd38585611b6d565b92506101608401356001600160401b03811115611cee575f5ffd5b8401601f81018613611cfe575f5ffd5b80356001600160401b03811115611d13575f5ffd5b866020828401011115611d24575f5ffd5b939660209190910195509293505050565b634e487b7160e01b5f52602160045260245ffd5b60088110611d6557634e487b7160e01b5f52602160045260245ffd5b9052565b5f6101c0820190508251805183526020810151611d9160208501826001600160a01b03169052565b506040810151611dac60408501826001600160a01b03169052565b506060810151611dc760608501826001600160a01b03169052565b506080810151611de260808501826001600160a01b03169052565b5060a081015160a084015260c0810151611e0760c08501826001600160401b03169052565b5060e0810151611e2260e08501826001600160401b03169052565b50610100810151611e3f6101008501826001600160401b03169052565b50610120810151611e5761012085018261ffff169052565b50610140810151610140840152506020830151611e78610160840182611d49565b5060408301516101808301526060909201516101a09091015290565b80356001600160a01b0381168114611eaa575f5ffd5b919050565b80356001600160401b0381168114611eaa575f5ffd5b803561ffff81168114611eaa575f5ffd5b8281528135602080830191909152610180820190611ef5908401611e94565b6001600160a01b038116604084015250611f1160408401611e94565b6001600160a01b038116606084015250611f2d60608401611e94565b6001600160a01b038116608084015250611f4960808401611e94565b6001600160a01b03811660a08401525060a083013560c083810191909152611f72908401611eaf565b6001600160401b03811660e084015250611f8e60e08401611eaf565b6001600160401b03811661010084015250611fac6101008401611eaf565b6001600160401b03811661012084015250611fca6101208401611ec5565b61ffff81166101408401525061014092909201356101609190910152919050565b634e487b7160e01b5f52601260045260245ffd5b5f8261200d5761200d611feb565b500690565b634e487b7160e01b5f52601160045260245ffd5b808202811582820484141761028257610282612012565b5f8261204b5761204b611feb565b500490565b8082018082111561028257610282612012565b5f60208284031215612073575f5ffd5b611b9f82611e94565b5f6020828403121561208c575f5ffd5b611b9f82611ec5565b5f602082840312156120a5575f5ffd5b611b9f82611eaf565b60405161016081016001600160401b03811182821017156120dd57634e487b7160e01b5f52604160045260245ffd5b60405290565b5f6101608284031280156120f5575f5ffd5b506120fe6120ae565b8235815261210e60208401611e94565b602082015261211f60408401611e94565b604082015261213060608401611e94565b606082015261214160808401611e94565b608082015260a0838101359082015261215c60c08401611eaf565b60c082015261216d60e08401611eaf565b60e082015261217f6101008401611eaf565b6101008201526121926101208401611ec5565b610120820152610140928301359281019290925250919050565b5f602082840312156121bc575f5ffd5b5051919050565b8181038181111561028257610282612012565b600181811c908216806121ea57607f821691505b602082108103611b7e57634e487b7160e01b5f52602260045260245ffdfea26469706673582212209a4cf890584b4e1fcda7ef5edc35397cda09ec7966b5a78649bc7c637ecb7d2264736f6c634300081e0033"
|
|
660
|
+
"bytecode": "0x610180604052348015610010575f5ffd5b506040516127c43803806127c483398101604081905261002f916101df565b6040518060400160405280600a81526020016941343233457363726f7760b01b815250604051806040016040528060018152602001603160f81b81525060015f8190555061008760018361016760201b90919060201c565b61012052610096816002610167565b61014052815160208084019190912060e052815190820120610100524660a05261012260e05161010051604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201529081019290925260608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b60805250503060c0526001600160a01b0381163b5f0361015557604051630256965f60e01b815260040160405180910390fd5b6001600160a01b0316610160526103cc565b5f6020835110156101825761017b83610199565b9050610193565b8161018d84826102a4565b5060ff90505b92915050565b5f5f829050601f815111156101cc578260405163305a27a960e01b81526004016101c3919061035e565b60405180910390fd5b80516101d7826103a9565b179392505050565b5f602082840312156101ef575f5ffd5b81516001600160a01b0381168114610205575f5ffd5b9392505050565b634e487b7160e01b5f52604160045260245ffd5b600181811c9082168061023457607f821691505b60208210810361025257634e487b7160e01b5f52602260045260245ffd5b50919050565b601f82111561029f57805f5260205f20601f840160051c8101602085101561027d5750805b601f840160051c820191505b8181101561029c575f8155600101610289565b50505b505050565b81516001600160401b038111156102bd576102bd61020c565b6102d1816102cb8454610220565b84610258565b6020601f821160018114610303575f83156102ec5750848201515b5f19600385901b1c1916600184901b17845561029c565b5f84815260208120601f198516915b828110156103325787850151825560209485019460019092019101610312565b508482101561034f57868401515f19600387901b60f8161c191681555b50505050600190811b01905550565b602081525f82518060208401525f5b8181101561038a576020818601810151604086840101520161036d565b505f604082850101526040601f19601f83011684010191505092915050565b80516020808301519190811015610252575f1960209190910360031b1b16919050565b60805160a05160c05160e0516101005161012051610140516101605161236561045f5f395f81816101f601528181610cfa01528181610d8a01528181610dd901528181611641015281816116d60152818161174e01528181611794015261183601525f61144601525f61141401525f6115da01525f6115b201525f61150d01525f61153701525f61156101526123655ff3fe608060405234801561000f575f5ffd5b50600436106100cb575f3560e01c806384b0196e11610088578063d26ff86e11610063578063d26ff86e146101ab578063edb70d9e146101be578063f729cf0d146101d1578063fc0c546a146101f1575f5ffd5b806384b0196e1461016a5780638a92631714610185578063cc3e049b14610198575f5ffd5b8063099d2518146100cf5780633aa36987146100f55780634e8fee001461011c57806367d42a8b14610131578063795ea18e1461014457806382cbf88b14610157575b5f5ffd5b6100e26100dd366004611c4f565b610230565b6040519081526020015b60405180910390f35b6100e27f3a82e9543b510404d05948b3e6cbf4e32f66690f864ccb5ef67ee5927a49a4ba81565b61012f61012a366004611c71565b610288565b005b61012f61013f366004611c91565b61039f565b61012f610152366004611c91565b61046f565b61012f610165366004611c71565b6105cb565b61017261070e565b6040516100ec9796959493929190611ceb565b61012f610193366004611d81565b610750565b61012f6101a6366004611c91565b610eee565b61012f6101b9366004611c71565b610f76565b61012f6101cc366004611c71565b61107c565b6101e46101df366004611c91565b611154565b6040516100ec9190611e34565b6102187f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100ec565b5f6102827f3a82e9543b510404d05948b3e6cbf4e32f66690f864ccb5ef67ee5927a49a4ba83604051602001610267929190611fa1565b604051602081830303815290604052805190602001206112d6565b92915050565b5f828152600360208190526040909120908101546001600160a01b031633146102c3576040516282b42960e81b815260040160405180910390fd5b6002600882015460ff1660078111156102de576102de611e00565b146102fc57604051636f20b45d60e11b815260040160405180910390fd5b6006810154600160401b90046001600160401b03164211156103315760405163637910d560e11b815260040160405180910390fd5b8060090154821461035557604051630256965f60e01b815260040160405180910390fd5b60088101805460ff1916600317905560405182815283907f5c83c73bb0b96f8bc4b95aabcb7980de29ee8ec40cf1aa83c5bd999b44077557906020015b60405180910390a2505050565b6103a7611302565b5f818152600360208190526040909120908101546001600160a01b031633146103e2576040516282b42960e81b815260040160405180910390fd5b6003600882015460ff1660078111156103fd576103fd611e00565b1461041b57604051636f20b45d60e11b815260040160405180910390fd5b6006810154600160801b90046001600160401b03164211156104505760405163637910d560e11b815260040160405180910390fd5b6104628282835f01600501545f61132a565b5061046c60015f55565b50565b610477611302565b5f8181526003602052604090206002600882015460ff16600781111561049f5761049f611e00565b141580156104c657506003600882015460ff1660078111156104c3576104c3611e00565b14155b80156104eb57506004600882015460ff1660078111156104e8576104e8611e00565b14155b1561050957604051636f20b45d60e11b815260040160405180910390fd5b6006810154600160801b90046001600160401b0316421161053d5760405163637910d560e11b815260040160405180910390fd5b600681015460058201545f9161271091600160c01b90910461ffff16906105659083906120ca565b61056f91906120f1565b6105799190612108565b60068301546005840154600160c01b90910461ffff169061059d9061271090612108565b6105a791906120f1565b6105b1919061211b565b90506105c0838383600361132a565b505061046c60015f55565b5f82815260036020526040902060018101546001600160a01b03163314801590610602575060028101546001600160a01b03163314155b801561061b575060038101546001600160a01b03163314155b15610638576040516282b42960e81b815260040160405180910390fd5b6002600882015460ff16600781111561065357610653611e00565b1415801561067a57506003600882015460ff16600781111561067757610677611e00565b14155b1561069857604051636f20b45d60e11b815260040160405180910390fd5b6006810154600160801b90046001600160401b03164211156106cd5760405163637910d560e11b815260040160405180910390fd5b60088101805460ff1916600417905560405182815283907f8e39eab2c454d024fb5c89b50dabc51f5068c7a283891d06aa91f5ba96acded190602001610392565b5f6060805f5f5f606061071f61140d565b61072761143f565b604080515f80825260208201909252600f60f81b9b939a50919850469750309650945092509050565b610758611302565b610768604084016020850161212e565b6001600160a01b0316336001600160a01b031614610798576040516282b42960e81b815260040160405180910390fd5b82355f9081526003602052604081206008015460ff1660078111156107bf576107bf611e00565b146107dd57604051636f20b45d60e11b815260040160405180910390fd5b823515806107ee5750610140830135155b806107fb575060a0830135155b8061081d57505f610812606085016040860161212e565b6001600160a01b0316145b806108575750610833604084016020850161212e565b6001600160a01b031661084c606085016040860161212e565b6001600160a01b0316145b8061087957505f61086e608085016060860161212e565b6001600160a01b0316145b8061089b57505f61089060a085016080860161212e565b6001600160a01b0316145b806108d557506108b1604084016020850161212e565b6001600160a01b03166108ca60a085016080860161212e565b6001600160a01b0316145b8061090f57506108eb606084016040850161212e565b6001600160a01b031661090460a085016080860161212e565b6001600160a01b0316145b80610930575061271061092a61014085016101208601612147565b61ffff16115b1561094e57604051630256965f60e01b815260040160405180910390fd5b4261095f60e0850160c08601612160565b6001600160401b03161115806109a6575061098060e0840160c08501612160565b6001600160401b031661099a610100850160e08601612160565b6001600160401b031611155b806109e457506109bd610100840160e08501612160565b6001600160401b03166109d861012085016101008601612160565b6001600160401b031611155b15610a0257604051630256965f60e01b815260040160405180910390fd5b5f610a13606085016040860161212e565b6001600160a01b03163b5f03610a9457610a33606085016040860161212e565b6001600160a01b0316610a83610a4886610230565b85858080601f0160208091040260200160405190810160405280939291908181526020018383808284375f9201919091525061146c92505050565b6001600160a01b0316149050610b35565b610aa4606085016040860161212e565b6001600160a01b0316631626ba7e610abb86610230565b85856040518463ffffffff1660e01b8152600401610adb93929190612179565b602060405180830381865afa925050508015610b14575060408051601f3d908101601f19168201909252610b11918101906121ae565b60015b610b1f57505f610b35565b6001600160e01b031916630b135d3f60e11b1490505b80610b52576040516282b42960e81b815260040160405180910390fd5b604051806080016040528085803603810190610b6e919061220a565b8152602001600181525f602080830182905260409283018290528735825260038082529183902084518051825580830151600180840180546001600160a01b039384166001600160a01b03199182161790915596830151600285018054918416918916919091179055606083015195840180549683169688169690961790955560808201516004840180549190921696169590951790945560a0840151600582015560c084015160068201805460e087015161010088015161012089015161ffff16600160c01b0261ffff60c01b196001600160401b03928316600160801b021669ffffffffffffffffffff60801b19938316600160401b026fffffffffffffffffffffffffffffffff19909516929096169190911792909217169290921791909117905561014090930151600780850191909155908401516008840180549193909260ff19909216918490811115610cc957610cc9611e00565b02179055506040828101516009830155606090920151600a90910155516370a0823160e01b81523060048201525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015610d47573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610d6b91906122d3565b9050610db6610d80604087016020880161212e565b6001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016903060a0890135611494565b610dc460a08601358261211b565b6040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa158015610e26573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e4a91906122d3565b14610e685760405163570883e560e01b815260040160405180910390fd5b610e78604086016020870161212e565b6001600160a01b031661014086013586357f32f3ba658fc1eefa6d5ca6ba35e8715bb439f7713edf2d61c03f4920fcb8cbab610eba60608a0160408b0161212e565b604080516001600160a01b03909216825260a08b013560208301520160405180910390a45050610ee960015f55565b505050565b610ef6611302565b5f8181526003602052604090206001600882015460ff166007811115610f1e57610f1e611e00565b14610f3c57604051636f20b45d60e11b815260040160405180910390fd5b60068101546001600160401b03164211610f695760405163637910d560e11b815260040160405180910390fd5b61046282825f600161132a565b5f82815260036020526040902060028101546001600160a01b03163314610faf576040516282b42960e81b815260040160405180910390fd5b6001600882015460ff166007811115610fca57610fca611e00565b14610fe857604051636f20b45d60e11b815260040160405180910390fd5b60068101546001600160401b03164211156110165760405163637910d560e11b815260040160405180910390fd5b8161103457604051630256965f60e01b815260040160405180910390fd5b6009810182905560088101805460ff1916600217905560405182815283907fa2f32e666157494cac7b795a43a2914dcb77efd9515b47db67dd15d332234f9790602001610392565b611084611302565b5f82815260036020526040902060048101546001600160a01b031633146110bd576040516282b42960e81b815260040160405180910390fd5b6004600882015460ff1660078111156110d8576110d8611e00565b146110f657604051636f20b45d60e11b815260040160405180910390fd5b6006810154600160801b90046001600160401b031642118061111b5750600581015482115b1561113957604051630256965f60e01b815260040160405180910390fd5b611146838284600261132a565b5061115060015f55565b5050565b604080516101e0810182525f6080820181815260a0830182905260c0830182905260e08301829052610100830182905261012083018290526101408301829052610160830182905261018083018290526101a083018290526101c0830182905282526020820181905291810182905260608101919091525f8281526003602081815260409283902083516101e0810190945280546080850190815260018201546001600160a01b0390811660a08701526002830154811660c087015293820154841660e08601526004820154909316610100850152600581015461012085015260068101546001600160401b03808216610140870152600160401b82048116610160870152600160801b820416610180860152600160c01b900461ffff166101a08501526007808201546101c0860152928452600881015490929184019160ff909116908111156112a7576112a7611e00565b60078111156112b8576112b8611e00565b815260098201546020820152600a9091015460409091015292915050565b5f6102826112e2611501565b8360405161190160f01b8152600281019290925260228201526042902090565b60025f540361132457604051633ee5aeb560e01b815260040160405180910390fd5b60025f55565b60058301545f9061133c9084906122ea565b9050821561135857801561135157600761135b565b600561135b565b60065b60088501805460ff1916600183600781111561137957611379611e00565b0217905550600a840183905582156113a35760028401546113a3906001600160a01b03168461162a565b80156113c15760018401546113c1906001600160a01b03168261162a565b604080518481526020810183905260ff841681830152905186917fb3a77f60f2042a66aa50b5810774aab5f1a2dd6799ee891ebb9fe98b95fec57f919081900360600190a25050505050565b606061143a7f000000000000000000000000000000000000000000000000000000000000000060016118c0565b905090565b606061143a7f000000000000000000000000000000000000000000000000000000000000000060026118c0565b5f5f5f5f61147a8686611969565b92509250925061148a82826119b2565b5090949350505050565b6040516001600160a01b0384811660248301528381166044830152606482018390526114fb9186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050611a6f565b50505050565b5f306001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614801561155957507f000000000000000000000000000000000000000000000000000000000000000046145b1561158357507f000000000000000000000000000000000000000000000000000000000000000090565b61143a604080517f8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f60208201527f0000000000000000000000000000000000000000000000000000000000000000918101919091527f000000000000000000000000000000000000000000000000000000000000000060608201524660808201523060a08201525f9060c00160405160208183030381529060405280519060200120905090565b6040516370a0823160e01b81523060048201525f907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa15801561168e573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116b291906122d3565b6040516370a0823160e01b81526001600160a01b0385811660048301529192505f917f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa15801561171b573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061173f91906122d3565b90506117756001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000168585611adb565b61177f83836122ea565b6040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316906370a0823190602401602060405180830381865afa1580156117e1573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061180591906122d3565b1415806118a25750611817838261211b565b6040516370a0823160e01b81526001600160a01b0386811660048301527f000000000000000000000000000000000000000000000000000000000000000016906370a0823190602401602060405180830381865afa15801561187b573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061189f91906122d3565b14155b156114fb5760405163570883e560e01b815260040160405180910390fd5b606060ff83146118da576118d383611b0c565b9050610282565b8180546118e6906122fd565b80601f0160208091040260200160405190810160405280929190818152602001828054611912906122fd565b801561195d5780601f106119345761010080835404028352916020019161195d565b820191905f5260205f20905b81548152906001019060200180831161194057829003601f168201915b50505050509050610282565b5f5f5f83516041036119a0576020840151604085015160608601515f1a61199288828585611b49565b9550955095505050506119ab565b505081515f91506002905b9250925092565b5f8260038111156119c5576119c5611e00565b036119ce575050565b60018260038111156119e2576119e2611e00565b03611a005760405163f645eedf60e01b815260040160405180910390fd5b6002826003811115611a1457611a14611e00565b03611a3a5760405163fce698f760e01b8152600481018290526024015b60405180910390fd5b6003826003811115611a4e57611a4e611e00565b03611150576040516335e2f38360e21b815260048101829052602401611a31565b5f5f60205f8451602086015f885af180611a8e576040513d5f823e3d81fd5b50505f513d91508115611aa5578060011415611ab2565b6001600160a01b0384163b155b156114fb57604051635274afe760e01b81526001600160a01b0385166004820152602401611a31565b6040516001600160a01b03838116602483015260448201839052610ee991859182169063a9059cbb906064016114c9565b60605f611b1883611c11565b6040805160208082528183019092529192505f91906020820181803683375050509182525060208101929092525090565b5f80807f7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0841115611b8257505f91506003905082611c07565b604080515f808252602082018084528a905260ff891692820192909252606081018790526080810186905260019060a0016020604051602081039080840390855afa158015611bd3573d5f5f3e3d5ffd5b5050604051601f1901519150506001600160a01b038116611bfe57505f925060019150829050611c07565b92505f91508190505b9450945094915050565b5f60ff8216601f81111561028257604051632cd44ac360e21b815260040160405180910390fd5b5f6101608284031215611c49575f5ffd5b50919050565b5f6101608284031215611c60575f5ffd5b611c6a8383611c38565b9392505050565b5f5f60408385031215611c82575f5ffd5b50508035926020909101359150565b5f60208284031215611ca1575f5ffd5b5035919050565b5f81518084525f5b81811015611ccc57602081850181015186830182015201611cb0565b505f602082860101526020601f19601f83011685010191505092915050565b60ff60f81b8816815260e060208201525f611d0960e0830189611ca8565b8281036040840152611d1b8189611ca8565b606084018890526001600160a01b038716608085015260a0840186905283810360c0850152845180825260208087019350909101905f5b81811015611d70578351835260209384019390920191600101611d52565b50909b9a5050505050505050505050565b5f5f5f6101808486031215611d94575f5ffd5b611d9e8585611c38565b92506101608401356001600160401b03811115611db9575f5ffd5b8401601f81018613611dc9575f5ffd5b80356001600160401b03811115611dde575f5ffd5b866020828401011115611def575f5ffd5b939660209190910195509293505050565b634e487b7160e01b5f52602160045260245ffd5b60088110611e3057634e487b7160e01b5f52602160045260245ffd5b9052565b5f6101c0820190508251805183526020810151611e5c60208501826001600160a01b03169052565b506040810151611e7760408501826001600160a01b03169052565b506060810151611e9260608501826001600160a01b03169052565b506080810151611ead60808501826001600160a01b03169052565b5060a081015160a084015260c0810151611ed260c08501826001600160401b03169052565b5060e0810151611eed60e08501826001600160401b03169052565b50610100810151611f0a6101008501826001600160401b03169052565b50610120810151611f2261012085018261ffff169052565b50610140810151610140840152506020830151611f43610160840182611e14565b5060408301516101808301526060909201516101a09091015290565b80356001600160a01b0381168114611f75575f5ffd5b919050565b80356001600160401b0381168114611f75575f5ffd5b803561ffff81168114611f75575f5ffd5b8281528135602080830191909152610180820190611fc0908401611f5f565b6001600160a01b038116604084015250611fdc60408401611f5f565b6001600160a01b038116606084015250611ff860608401611f5f565b6001600160a01b03811660808401525061201460808401611f5f565b6001600160a01b03811660a08401525060a083013560c08381019190915261203d908401611f7a565b6001600160401b03811660e08401525061205960e08401611f7a565b6001600160401b038116610100840152506120776101008401611f7a565b6001600160401b038116610120840152506120956101208401611f90565b61ffff81166101408401525061014092909201356101609190910152919050565b634e487b7160e01b5f52601260045260245ffd5b5f826120d8576120d86120b6565b500690565b634e487b7160e01b5f52601160045260245ffd5b8082028115828204841417610282576102826120dd565b5f82612116576121166120b6565b500490565b80820180821115610282576102826120dd565b5f6020828403121561213e575f5ffd5b611c6a82611f5f565b5f60208284031215612157575f5ffd5b611c6a82611f90565b5f60208284031215612170575f5ffd5b611c6a82611f7a565b83815260406020820152816040820152818360608301375f818301606090810191909152601f909201601f1916010192915050565b5f602082840312156121be575f5ffd5b81516001600160e01b031981168114611c6a575f5ffd5b60405161016081016001600160401b038111828210171561220457634e487b7160e01b5f52604160045260245ffd5b60405290565b5f61016082840312801561221c575f5ffd5b506122256121d5565b8235815261223560208401611f5f565b602082015261224660408401611f5f565b604082015261225760608401611f5f565b606082015261226860808401611f5f565b608082015260a0838101359082015261228360c08401611f7a565b60c082015261229460e08401611f7a565b60e08201526122a66101008401611f7a565b6101008201526122b96101208401611f90565b610120820152610140928301359281019290925250919050565b5f602082840312156122e3575f5ffd5b5051919050565b81810381811115610282576102826120dd565b600181811c9082168061231157607f821691505b602082108103611c4957634e487b7160e01b5f52602260045260245ffdfea26469706673582212205baa27a5ede03269f91b23ac0ffdafa9d221f7764d1f438eee508bec48f9def564736f6c634300081e0033"
|
|
661
661
|
}
|
package/src/escrow/index.cjs
CHANGED
|
@@ -31,10 +31,13 @@ function createMemoryTransactionStore() {
|
|
|
31
31
|
return {
|
|
32
32
|
async get(id){return records.has(id)?structuredClone(records.get(id)):null;},
|
|
33
33
|
async create(id,record){if(records.has(id))return false;records.set(id,structuredClone(record));return true;},
|
|
34
|
+
async list(){return [...records].map(([id,record])=>({id,...structuredClone(record)}));},
|
|
35
|
+
async appendReplacement(id,expectedHash,replacement){const record=records.get(id);check(record&&(record.replacements?.at(-1)?.hash||record.hash)===expectedHash,'Replacement conflict');record.replacements=[...(record.replacements||[]),structuredClone(replacement)];},
|
|
34
36
|
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
37
|
};
|
|
36
38
|
}
|
|
37
|
-
function createEscrowAdapters({provider,chainId,contractAddress,tokenAddress,signerFor,addressFor,transactions,confirmations,validateArtifact}={}) {
|
|
39
|
+
function createEscrowAdapters({provider,chainId,contractAddress,tokenAddress,signerFor,addressFor,transactions,confirmations,validateArtifact,authorizeTransaction}={}) {
|
|
40
|
+
check(typeof authorizeTransaction==='function','authorizeTransaction is required before any signing or broadcast');
|
|
38
41
|
check(provider && typeof signerFor==='function' && typeof addressFor==='function','provider, signerFor and addressFor required');
|
|
39
42
|
check(Number.isSafeInteger(chainId)&&chainId>0 && Number.isSafeInteger(confirmations)&&confirmations>0,'explicit chain ID and positive confirmations required');
|
|
40
43
|
check(transactions && ['get','create','reserveNonce'].every(k=>typeof transactions[k]==='function'),'durable transaction journal required');
|
|
@@ -63,6 +66,7 @@ function createEscrowAdapters({provider,chainId,contractAddress,tokenAddress,sig
|
|
|
63
66
|
let record=await transactions.get(id);
|
|
64
67
|
if(!record && reconcile)return {status:'pending',reference:id};
|
|
65
68
|
if(!record) {
|
|
69
|
+
if(await authorizeTransaction({job:structuredClone(ctx.job),operation:structuredClone(ctx.operation),actor:ctx.operation.actor,step:suffix,to:getAddress(to),data,phase:'sign'})!==true)return {status:'rejected',reason:'Signing policy denied before signing'};
|
|
66
70
|
const signer=await signerFor(ctx.operation.actor);
|
|
67
71
|
const address=getAddress(await signer.getAddress());
|
|
68
72
|
check(address===getAddress(await addressFor(ctx.operation.actor)),'signer ownership mismatch');
|
|
@@ -75,17 +79,20 @@ function createEscrowAdapters({provider,chainId,contractAddress,tokenAddress,sig
|
|
|
75
79
|
const raw=await signer.signTransaction(request);
|
|
76
80
|
const tx=Transaction.from(raw);
|
|
77
81
|
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});
|
|
82
|
+
await transactions.create(id,{hash:tx.hash,raw,to:getAddress(to),data,from:address,created_at:new Date().toISOString()});
|
|
79
83
|
record=await transactions.get(id);
|
|
80
84
|
}
|
|
81
85
|
check(record.to===getAddress(to) && record.data===data && record.from===getAddress(await addressFor(ctx.operation.actor)),'journal request mismatch');
|
|
82
|
-
|
|
86
|
+
const attempts=[record,...(record.replacements||[])];
|
|
87
|
+
let active=attempts.at(-1),r=null;
|
|
88
|
+
for(const attempt of attempts){const observed=await receipt(attempt.hash);if(observed){r=observed;active=attempt;break;}}
|
|
83
89
|
if(!r && !reconcile) {
|
|
84
|
-
|
|
85
|
-
|
|
90
|
+
check(await authorizeTransaction({job:structuredClone(ctx.job),operation:structuredClone(ctx.operation),actor:ctx.operation.actor,step:suffix,to:getAddress(to),data,phase:'broadcast'})===true,'broadcast policy denied');
|
|
91
|
+
try {await provider.broadcastTransaction(active.raw);} catch(err) {if(!await provider.getTransaction(active.hash))throw err;}
|
|
92
|
+
r=await receipt(active.hash);
|
|
86
93
|
}
|
|
87
|
-
if(!r)return {status:'pending',reference:
|
|
88
|
-
if(r.status!==1)return {status:'rejected',reason:'Transaction reverted',reference:
|
|
94
|
+
if(!r)return {status:'pending',reference:active.hash};
|
|
95
|
+
if(r.status!==1)return {status:'rejected',reason:'Transaction reverted',reference:active.hash};
|
|
89
96
|
return {receipt:r};
|
|
90
97
|
}
|
|
91
98
|
async function observe(ctx,t,suffix,method,args,eventName,validate,reconcile) {
|
|
@@ -101,7 +108,7 @@ function createEscrowAdapters({provider,chainId,contractAddress,tokenAddress,sig
|
|
|
101
108
|
const event=logs.find(log=>log?.name===eventName && log.args.jobId===t.jobId);
|
|
102
109
|
check(event,'matching '+eventName+' event missing');
|
|
103
110
|
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()}};
|
|
111
|
+
return {status:'confirmed',evidence:require('../operations/index.cjs').validateEscrowEvidence({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,settlement_reason:eventName==='Settled'?Number(event.args.reason):null,reason_hash:eventName==='Disputed'?event.args.reasonHash:null,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
112
|
}
|
|
106
113
|
function adapter(run){return {execute:ctx=>run(ctx,false),reconcile:ctx=>run(ctx,true)};}
|
|
107
114
|
const state=(s,n)=>check(Number(s.state)===n,'unexpected escrow state');
|
|
@@ -113,7 +120,11 @@ function createEscrowAdapters({provider,chainId,contractAddress,tokenAddress,sig
|
|
|
113
120
|
if(typeof ctx.input.worker_signature!=='string')return {status:'rejected',reason:'worker_signature required'};
|
|
114
121
|
// Consent is checked locally as well as by the contract before moving tokens.
|
|
115
122
|
const {verifyTypedData}=require('ethers');const c=workerConsent(ctx.job,config);
|
|
116
|
-
|
|
123
|
+
const digest=TypedDataEncoder.hash(c.domain,c.types,c.value);
|
|
124
|
+
if(await provider.getCode(t.worker)!=='0x'){
|
|
125
|
+
const smart=new Contract(t.worker,['function isValidSignature(bytes32,bytes) view returns(bytes4)'],provider);
|
|
126
|
+
try {if(await smart.isValidSignature(digest,ctx.input.worker_signature)!=='0x1626ba7e')return {status:'rejected',reason:'Invalid smart-account worker consent'};}catch(err){if(err.code==='CALL_EXCEPTION')return {status:'rejected',reason:'Smart-account consent reverted'};throw err;}
|
|
127
|
+
}else {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
128
|
// Never send further approvals if the funding transaction already exists in the journal.
|
|
118
129
|
if(!ctx.input.transaction_hash && !await transactions.get(ctx.operation.id+':fund')) {
|
|
119
130
|
let allowance=await token.allowance(t.payer,config.contractAddress);
|
|
@@ -135,3 +146,5 @@ function createEscrowAdapters({provider,chainId,contractAddress,tokenAddress,sig
|
|
|
135
146
|
};
|
|
136
147
|
}
|
|
137
148
|
module.exports={createEscrowAdapters,createMemoryTransactionStore,workerConsent,escrowTerms,escrowTransitions:transitions,ESCROW_ABI:ABI,ESCROW_BYTECODE:artifact.bytecode};
|
|
149
|
+
|
|
150
|
+
Object.assign(module.exports,require('./services.cjs'));
|
package/src/escrow/index.d.mts
CHANGED
|
@@ -6,9 +6,11 @@ export interface ContractTerms {
|
|
|
6
6
|
amount: bigint; workDeadline: number; reviewDeadline: number; resolutionDeadline: number;
|
|
7
7
|
timeoutWorkerBps: number; agreementHash: string;
|
|
8
8
|
}
|
|
9
|
-
export interface TransactionRecord { hash: string; raw: string; to: string; data: string; from: string }
|
|
9
|
+
export interface TransactionRecord { created_at?: string; replacements?: TransactionRecord[]; hash: string; raw: string; to: string; data: string; from: string }
|
|
10
10
|
export interface TransactionStore {
|
|
11
11
|
get(id: string): Promise<TransactionRecord | null>;
|
|
12
|
+
list(): Promise<Array<TransactionRecord & {id: string}>>;
|
|
13
|
+
appendReplacement(id: string, expectedHash: string, replacement: TransactionRecord): Promise<void>;
|
|
12
14
|
/** Atomic insert if absent; caller reads winner before broadcasting. */
|
|
13
15
|
create(id: string, record: TransactionRecord): Promise<boolean>;
|
|
14
16
|
/** Atomically reserve a nonce per chain/address; same operationId always returns same nonce. */
|
|
@@ -21,6 +23,7 @@ export interface EscrowAdapterOptions extends EscrowConfiguration {
|
|
|
21
23
|
signerFor(actor: string): Promise<Signer>;
|
|
22
24
|
addressFor(actor: string): Promise<string>;
|
|
23
25
|
transactions: TransactionStore;
|
|
26
|
+
authorizeTransaction(context: { job: WorkflowJob; operation: AdapterContext['operation']; actor: string; step: string; to: string; data: string; phase: 'sign' | 'broadcast' }): Promise<boolean>;
|
|
24
27
|
confirmations: number;
|
|
25
28
|
validateArtifact?(context: { job: WorkflowJob; resultHash: string }): Promise<boolean>;
|
|
26
29
|
}
|
|
@@ -34,3 +37,12 @@ export function workerConsent(job: WorkflowJob, config: EscrowConfiguration): {
|
|
|
34
37
|
export const escrowTransitions: Record<string, Transition>;
|
|
35
38
|
export const ESCROW_ABI: InterfaceAbi;
|
|
36
39
|
export const ESCROW_BYTECODE: string;
|
|
40
|
+
|
|
41
|
+
export function createEscrowRegistry(configurations: EscrowAdapterOptions[]): ReturnType<typeof createEscrowAdapters>;
|
|
42
|
+
export function createEvidenceRevalidator(options: EscrowConfiguration & {provider: Provider;confirmations: number}): (job: WorkflowJob,evidence: any) => Promise<{status: 'valid' | 'invalid' | 'insufficient_confirmations';[key: string]: unknown}>;
|
|
43
|
+
export function createManagedSigner(options: {address: string;provider: Provider;signDigest(digest: string,context: any): Promise<string>;authorize(context: any): Promise<boolean>}): Signer;
|
|
44
|
+
export function createDelegationAuthorizer(options: {expiresAt: number;chainIds: number[];contracts: string[];selectors: string[];maxValue?: string;maxFeePerGas: string;maxGasLimit: string;now?(): number}): (context: any) => Promise<boolean>;
|
|
45
|
+
export function replacePendingTransaction(options: {id: string;transactions: TransactionStore;provider: Provider;signer: Signer;authorize(context: any): Promise<boolean>;maxFeePerGas: bigint;maxPriorityFeePerGas: bigint}): Promise<{hash: string;raw: string}>;
|
|
46
|
+
export function createFeeReplacementWorker(options: {transactions: TransactionStore;provider: Provider;signerForRecord(record: TransactionRecord): Promise<Signer>;authorize(context: any): Promise<boolean>;minAgeMs?: number;maxAttempts?: number;maxFeePerGas: bigint;maxPriorityFeePerGas: bigint}): () => Promise<any[]>;
|
|
47
|
+
|
|
48
|
+
export function createEscrowIndexer(options: {provider: Provider;chainId: number;contractAddress: string;confirmations: number;fromBlock: number;kv: import('../storage/index.js').KeyValueStore;onEvent(event: any): Promise<void>;onReorg(event: any): Promise<void>;batchSize?: number}): () => Promise<{indexed: number;through?: number}>;
|
package/src/escrow/index.d.ts
CHANGED
|
@@ -6,9 +6,11 @@ export interface ContractTerms {
|
|
|
6
6
|
amount: bigint; workDeadline: number; reviewDeadline: number; resolutionDeadline: number;
|
|
7
7
|
timeoutWorkerBps: number; agreementHash: string;
|
|
8
8
|
}
|
|
9
|
-
export interface TransactionRecord { hash: string; raw: string; to: string; data: string; from: string }
|
|
9
|
+
export interface TransactionRecord { created_at?: string; replacements?: TransactionRecord[]; hash: string; raw: string; to: string; data: string; from: string }
|
|
10
10
|
export interface TransactionStore {
|
|
11
11
|
get(id: string): Promise<TransactionRecord | null>;
|
|
12
|
+
list(): Promise<Array<TransactionRecord & {id: string}>>;
|
|
13
|
+
appendReplacement(id: string, expectedHash: string, replacement: TransactionRecord): Promise<void>;
|
|
12
14
|
/** Atomic insert if absent; caller reads winner before broadcasting. */
|
|
13
15
|
create(id: string, record: TransactionRecord): Promise<boolean>;
|
|
14
16
|
/** Atomically reserve a nonce per chain/address; same operationId always returns same nonce. */
|
|
@@ -21,6 +23,7 @@ export interface EscrowAdapterOptions extends EscrowConfiguration {
|
|
|
21
23
|
signerFor(actor: string): Promise<Signer>;
|
|
22
24
|
addressFor(actor: string): Promise<string>;
|
|
23
25
|
transactions: TransactionStore;
|
|
26
|
+
authorizeTransaction(context: { job: WorkflowJob; operation: AdapterContext['operation']; actor: string; step: string; to: string; data: string; phase: 'sign' | 'broadcast' }): Promise<boolean>;
|
|
24
27
|
confirmations: number;
|
|
25
28
|
validateArtifact?(context: { job: WorkflowJob; resultHash: string }): Promise<boolean>;
|
|
26
29
|
}
|
|
@@ -34,3 +37,12 @@ export function workerConsent(job: WorkflowJob, config: EscrowConfiguration): {
|
|
|
34
37
|
export const escrowTransitions: Record<string, Transition>;
|
|
35
38
|
export const ESCROW_ABI: InterfaceAbi;
|
|
36
39
|
export const ESCROW_BYTECODE: string;
|
|
40
|
+
|
|
41
|
+
export function createEscrowRegistry(configurations: EscrowAdapterOptions[]): ReturnType<typeof createEscrowAdapters>;
|
|
42
|
+
export function createEvidenceRevalidator(options: EscrowConfiguration & {provider: Provider;confirmations: number}): (job: WorkflowJob,evidence: any) => Promise<{status: 'valid' | 'invalid' | 'insufficient_confirmations';[key: string]: unknown}>;
|
|
43
|
+
export function createManagedSigner(options: {address: string;provider: Provider;signDigest(digest: string,context: any): Promise<string>;authorize(context: any): Promise<boolean>}): Signer;
|
|
44
|
+
export function createDelegationAuthorizer(options: {expiresAt: number;chainIds: number[];contracts: string[];selectors: string[];maxValue?: string;maxFeePerGas: string;maxGasLimit: string;now?(): number}): (context: any) => Promise<boolean>;
|
|
45
|
+
export function replacePendingTransaction(options: {id: string;transactions: TransactionStore;provider: Provider;signer: Signer;authorize(context: any): Promise<boolean>;maxFeePerGas: bigint;maxPriorityFeePerGas: bigint}): Promise<{hash: string;raw: string}>;
|
|
46
|
+
export function createFeeReplacementWorker(options: {transactions: TransactionStore;provider: Provider;signerForRecord(record: TransactionRecord): Promise<Signer>;authorize(context: any): Promise<boolean>;minAgeMs?: number;maxAttempts?: number;maxFeePerGas: bigint;maxPriorityFeePerGas: bigint}): () => Promise<any[]>;
|
|
47
|
+
|
|
48
|
+
export function createEscrowIndexer(options: {provider: Provider;chainId: number;contractAddress: string;confirmations: number;fromBlock: number;kv: import('../storage/index.js').KeyValueStore;onEvent(event: any): Promise<void>;onReorg(event: any): Promise<void>;batchSize?: number}): () => Promise<{indexed: number;through?: number}>;
|
package/src/escrow/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import escrow from './index.cjs';
|
|
2
|
-
export const { createEscrowAdapters, createMemoryTransactionStore, workerConsent, escrowTerms, escrowTransitions, ESCROW_ABI, ESCROW_BYTECODE } = escrow;
|
|
2
|
+
export const { createEscrowAdapters, createMemoryTransactionStore, workerConsent, escrowTerms, escrowTransitions, ESCROW_ABI, ESCROW_BYTECODE, createEscrowRegistry, createEvidenceRevalidator, createManagedSigner, createDelegationAuthorizer, replacePendingTransaction, createFeeReplacementWorker, createEscrowIndexer } = escrow;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const {Contract,Interface,AbstractSigner,Transaction,Signature,verifyMessage,verifyTypedData,recoverAddress,hashMessage,TypedDataEncoder,getAddress,ZeroHash}=require('ethers');
|
|
3
|
+
const {ESCROW_ABI,escrowTerms,createEscrowAdapters}=require('./index.cjs');
|
|
4
|
+
const {validateEscrowEvidence}=require('../operations/index.cjs');
|
|
5
|
+
function check(ok,message){if(!ok)throw Error(message);}
|
|
6
|
+
function createEscrowRegistry(configurations){
|
|
7
|
+
const map=new Map();for(const c of configurations){const key=c.chainId+':'+getAddress(c.contractAddress).toLowerCase();check(!map.has(key),'Duplicate escrow configuration');map.set(key,createEscrowAdapters(c));}
|
|
8
|
+
const actions=['fund','start','submit','verify','release','refund','dispute','resolve','timeout'];const result={};for(const action of actions){result[action]={};for(const method of ['execute','reconcile'])result[action][method]=ctx=>{const e=ctx.job.agreement?.terms.escrow;const adapter=map.get(e?.chain_id+':'+getAddress(e?.contract).toLowerCase());check(adapter,'Escrow not registered');return adapter[action][method](ctx);};}return result;
|
|
9
|
+
}
|
|
10
|
+
/** Fresh historical evidence verification. Returns invalid, insufficient_confirmations, or valid; RPC failures throw. */
|
|
11
|
+
function createEvidenceRevalidator({provider,chainId,contractAddress,tokenAddress,confirmations}={}){
|
|
12
|
+
const config={chainId,contractAddress:getAddress(contractAddress),tokenAddress:getAddress(tokenAddress)},contract=new Contract(config.contractAddress,ESCROW_ABI,provider),iface=new Interface(ESCROW_ABI);
|
|
13
|
+
check(Number.isSafeInteger(confirmations)&&confirmations>0,'Explicit confirmations required');
|
|
14
|
+
return async(job,evidence)=>{
|
|
15
|
+
validateEscrowEvidence(evidence);const t=escrowTerms(job,config);
|
|
16
|
+
if(Number((await provider.getNetwork()).chainId)!==chainId)throw Error('RPC chain mismatch');
|
|
17
|
+
if(getAddress(await contract.token())!==config.tokenAddress)throw Error('Escrow token mismatch');
|
|
18
|
+
const invalid=message=>({status:'invalid',message});
|
|
19
|
+
if(evidence.chain_id!==String(chainId)||getAddress(evidence.contract)!==config.contractAddress||getAddress(evidence.token)!==config.tokenAddress)return invalid('Evidence destination mismatch');
|
|
20
|
+
const receipt=await provider.getTransactionReceipt(evidence.transaction_hash);
|
|
21
|
+
if(!receipt||receipt.status!==1||receipt.blockHash!==evidence.block_hash||receipt.blockNumber!==evidence.block_number)return invalid('Receipt missing, reverted or changed');
|
|
22
|
+
const block=await provider.getBlock(receipt.blockNumber),head=await provider.getBlock('latest');
|
|
23
|
+
if(!block||block.hash!==receipt.blockHash)return invalid('Orphaned receipt');
|
|
24
|
+
if(head.number-block.number+1<confirmations)return {status:'insufficient_confirmations'};
|
|
25
|
+
const logs=receipt.logs.filter(l=>getAddress(l.address)===config.contractAddress).map(l=>{try{return iface.parseLog(l);}catch{return null;}});
|
|
26
|
+
const event=logs.find(l=>l?.name===evidence.event&&l.args.jobId===t.jobId);if(!event)return invalid('Event absent');
|
|
27
|
+
const snapshot=await contract.getJob(t.jobId,{blockTag:receipt.blockNumber});
|
|
28
|
+
for(const [key,value] of Object.entries(t))if(String(snapshot.terms[key]).toLowerCase()!==String(value).toLowerCase())return invalid('Agreement '+key+' mismatch');
|
|
29
|
+
const expected={amount:t.amount.toString(),payer:t.payer,worker:t.worker,verifier:t.verifier,arbitrator:t.arbitrator,job_id:t.jobId,agreement_hash:t.agreementHash,result_hash:snapshot.resultHash,worker_paid:snapshot.workerPaid.toString(),state:Number(snapshot.state)};
|
|
30
|
+
for(const [key,value] of Object.entries(expected))if(String(evidence[key]).toLowerCase()!==String(value).toLowerCase())return invalid('Cached '+key+' mismatch');
|
|
31
|
+
if(event.name==='Funded'&&(event.args.amount!==t.amount||getAddress(event.args.payer)!==t.payer||getAddress(event.args.worker)!==t.worker||event.args.agreementHash!==t.agreementHash))return invalid('Funding fields mismatch');
|
|
32
|
+
if(['Submitted','Verified'].includes(event.name)&&event.args.resultHash!==snapshot.resultHash)return invalid('Artifact mismatch');
|
|
33
|
+
if(event.name==='Disputed'&&event.args.reasonHash!==evidence.reason_hash)return invalid('Dispute reason mismatch');
|
|
34
|
+
if(event.name==='Settled'&&(event.args.workerAmount!==snapshot.workerPaid||event.args.payerAmount+event.args.workerAmount!==t.amount||Number(event.args.reason)!==evidence.settlement_reason))return invalid('Settlement mismatch');
|
|
35
|
+
return {status:'valid',transaction_hash:receipt.hash,block_hash:block.hash,confirmations:head.number-block.number+1};
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/** Noncustodial integration surface: the injected digest signer may call a KMS/HSM or remote signer. */
|
|
39
|
+
function createManagedSigner({address,provider,signDigest,authorize}={}){
|
|
40
|
+
address=getAddress(address);check(typeof signDigest==='function'&&typeof authorize==='function','Digest signer and authorization required');
|
|
41
|
+
class ManagedSigner extends AbstractSigner {
|
|
42
|
+
async getAddress(){return address;}
|
|
43
|
+
connect(next){return createManagedSigner({address,provider:next,signDigest,authorize});}
|
|
44
|
+
async signature(digest,context){check(await authorize(context)===true,'Managed signer authorization denied');const signature=Signature.from(await signDigest(digest,context));check(getAddress(recoverAddress(digest,signature))===address,'Remote signer returned wrong key');return signature;}
|
|
45
|
+
async signTransaction(request){const copy={...request};if(copy.from){check(getAddress(copy.from)===address,'Wrong signing account');delete copy.from;}const transaction=Transaction.from(copy);const signature=await this.signature(transaction.unsignedHash,{kind:'transaction',transaction:copy});transaction.signature=signature;return transaction.serialized;}
|
|
46
|
+
async signMessage(message){return (await this.signature(hashMessage(message),{kind:'message',message})).serialized;}
|
|
47
|
+
async signTypedData(domain,types,value){return (await this.signature(TypedDataEncoder.hash(domain,types,value),{kind:'typed_data',domain,types,value})).serialized;}
|
|
48
|
+
}
|
|
49
|
+
return new ManagedSigner(provider);
|
|
50
|
+
}
|
|
51
|
+
/** Explicit bounded same-nonce replacement; never change recipient/calldata/value. Journal preserves lineage. */
|
|
52
|
+
async function replacePendingTransaction({id,transactions,provider,signer,authorize,maxFeePerGas,maxPriorityFeePerGas}={}){
|
|
53
|
+
const record=await transactions.get(id);check(record,'Unknown transaction');
|
|
54
|
+
for(const attempt of [record,...(record.replacements||[])])check(!await provider.getTransactionReceipt(attempt.hash),'An attempt is already mined; reconcile instead');
|
|
55
|
+
const last=record.replacements?.at(-1)||record;const old=Transaction.from(last.raw);
|
|
56
|
+
check(old.type===2,'Only EIP-1559 replacements supported');
|
|
57
|
+
const fee=BigInt(maxFeePerGas),tip=BigInt(maxPriorityFeePerGas);
|
|
58
|
+
check(fee>=(old.maxFeePerGas*125n+99n)/100n&&tip>=(old.maxPriorityFeePerGas*125n+99n)/100n&&tip<=fee,'Replacement must raise both fees at least 25%');
|
|
59
|
+
check(await authorize({record,fee,tip})===true,'Replacement not authorized');
|
|
60
|
+
check(getAddress(await signer.getAddress())===old.from,'Wrong replacement signer');
|
|
61
|
+
const request={chainId:old.chainId,nonce:old.nonce,to:old.to,data:old.data,value:old.value,gasLimit:old.gasLimit,type:2,accessList:old.accessList,maxFeePerGas:fee,maxPriorityFeePerGas:tip};
|
|
62
|
+
const raw=await signer.signTransaction(request),tx=Transaction.from(raw);check(tx.from===old.from&&tx.to===old.to&&tx.data===old.data&&tx.value===old.value&&tx.nonce===old.nonce&&tx.chainId===old.chainId&&tx.gasLimit===old.gasLimit&&tx.maxFeePerGas===fee&&tx.maxPriorityFeePerGas===tip,'Replacement signer changed intent');
|
|
63
|
+
check(typeof transactions.appendReplacement==='function','Durable replacement journal required');
|
|
64
|
+
await transactions.appendReplacement(id,last.hash,{hash:tx.hash,raw,to:record.to,data:record.data,from:record.from,created_at:new Date().toISOString()});
|
|
65
|
+
return {hash:tx.hash,raw}; // persisted first; adapter resume broadcasts all known attempts safely.
|
|
66
|
+
}
|
|
67
|
+
module.exports={createEscrowRegistry,createEvidenceRevalidator,createManagedSigner,replacePendingTransaction};
|
|
68
|
+
/** Opt-in bounded fee replacement planning; broadcasting remains the original action's resume path. */
|
|
69
|
+
function createFeeReplacementWorker({transactions,provider,signerForRecord,authorize,minAgeMs=60000,maxAttempts=3,maxFeePerGas,maxPriorityFeePerGas}={}){
|
|
70
|
+
check(Number.isSafeInteger(minAgeMs)&&minAgeMs>0&&Number.isSafeInteger(maxAttempts)&&maxAttempts>=1,'Invalid replacement bounds');
|
|
71
|
+
const cap=BigInt(maxFeePerGas),tipCap=BigInt(maxPriorityFeePerGas);
|
|
72
|
+
return async()=>{const results=[];for(const record of await transactions.list()){
|
|
73
|
+
if((record.replacements?.length||0)>=maxAttempts||!record.created_at||Date.now()-Date.parse(record.replacements?.at(-1)?.created_at||record.created_at)<minAgeMs)continue;
|
|
74
|
+
const attempts=[record,...(record.replacements||[])];let mined=false;for(const a of attempts)if(await provider.getTransactionReceipt(a.hash)){mined=true;break;}if(mined)continue;
|
|
75
|
+
const last=Transaction.from(attempts.at(-1).raw);if(last.type!==2)continue;
|
|
76
|
+
const fee=(last.maxFeePerGas*125n+99n)/100n,tip=(last.maxPriorityFeePerGas*125n+99n)/100n;if(fee>cap||tip>tipCap){results.push({id:record.id,status:'cap_reached'});continue;}
|
|
77
|
+
try{const result=await replacePendingTransaction({id:record.id,transactions,provider,signer:await signerForRecord(record),authorize,maxFeePerGas:fee,maxPriorityFeePerGas:tip});results.push({id:record.id,status:'prepared',hash:result.hash});}catch(error){results.push({id:record.id,status:'error',error:error.message});}
|
|
78
|
+
}return results;};
|
|
79
|
+
}
|
|
80
|
+
module.exports.createFeeReplacementWorker=createFeeReplacementWorker;
|
|
81
|
+
/** Concrete transaction-only delegation policy, evaluated again for every signature. */
|
|
82
|
+
function createDelegationAuthorizer({expiresAt,chainIds,contracts,selectors,maxValue='0',maxFeePerGas,maxGasLimit,now=()=>Date.now()}={}){
|
|
83
|
+
check(Number.isSafeInteger(expiresAt)&&Array.isArray(chainIds)&&Array.isArray(contracts)&&Array.isArray(selectors),'Explicit delegation bounds required');
|
|
84
|
+
const allowed=new Set(contracts.map(a=>getAddress(a))),methods=new Set(selectors);
|
|
85
|
+
const valueCap=BigInt(maxValue),feeCap=BigInt(maxFeePerGas),gasCap=BigInt(maxGasLimit);
|
|
86
|
+
return async context=>{if(context.kind!=='transaction'||Math.floor(now()/1000)>=expiresAt)return false;const t=context.transaction;
|
|
87
|
+
if(!chainIds.map(String).includes(String(t.chainId))||!t.to||!allowed.has(getAddress(t.to))||!methods.has((t.data||'0x').slice(0,10)))return false;
|
|
88
|
+
if(BigInt(t.value||0)>valueCap||BigInt(t.gasLimit||0)>gasCap||BigInt(t.maxFeePerGas||t.gasPrice||0)>feeCap)return false;
|
|
89
|
+
return true;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
module.exports.createDelegationAuthorizer=createDelegationAuthorizer;
|
|
93
|
+
/** Incremental confirmed log indexer. Consumers must deduplicate key and handle orphan notifications. */
|
|
94
|
+
function createEscrowIndexer({provider,chainId,contractAddress,confirmations,fromBlock,kv,onEvent,onReorg,batchSize=1000}={}){
|
|
95
|
+
check(Number.isSafeInteger(fromBlock)&&fromBlock>=0&&Number.isSafeInteger(confirmations)&&confirmations>0&&Number.isSafeInteger(batchSize)&&batchSize>0&&batchSize<=10000,'Explicit indexing bounds required');
|
|
96
|
+
check(kv&&typeof onEvent==='function'&&typeof onReorg==='function','Durable cursor and callbacks required');
|
|
97
|
+
const address=getAddress(contractAddress),iface=new Interface(ESCROW_ABI),scope=chainId+':'+address;
|
|
98
|
+
return async()=>{
|
|
99
|
+
check(Number((await provider.getNetwork()).chainId)===chainId,'Indexer RPC chain mismatch');
|
|
100
|
+
let cursor=await kv.get('index-cursors',scope);
|
|
101
|
+
if(cursor){const block=await provider.getBlock(cursor.block);if(!block||block.hash!==cursor.hash){await onReorg({scope,previous:cursor});
|
|
102
|
+
for(const row of await kv.list('index-events:'+scope)){const b=await provider.getBlock(row.value.block_number);if(!b||b.hash!==row.value.block_hash)await kv.put('index-events:'+scope,row.id,{...row.value,orphaned:true});}
|
|
103
|
+
cursor=null;await kv.put('index-cursors',scope,null);
|
|
104
|
+
}}
|
|
105
|
+
const head=await provider.getBlock('latest'),start=cursor?cursor.block+1:fromBlock,end=Math.min(head.number-confirmations+1,start+batchSize-1);if(end<start)return {indexed:0};
|
|
106
|
+
const scanEnd=await provider.getBlock(end);
|
|
107
|
+
const logs=await provider.getLogs({address,fromBlock:start,toBlock:end});let indexed=0;
|
|
108
|
+
for(const log of logs){let parsed;try{parsed=iface.parseLog(log);}catch{continue;}if(!parsed)continue;
|
|
109
|
+
const key=log.blockHash+':'+log.transactionHash+':'+log.index;
|
|
110
|
+
const old=await kv.get('index-events:'+scope,key);if(old?.delivered&&!old.orphaned)continue;
|
|
111
|
+
const event={key,chain_id:String(chainId),contract:address,block_number:log.blockNumber,block_hash:log.blockHash,transaction_hash:log.transactionHash,log_index:log.index,event:parsed.name,args:parsed.fragment.inputs.reduce((obj,input,i)=>{const value=parsed.args[i];obj[input.name]=typeof value==='bigint'?value.toString():value;return obj;},{}),orphaned:false,delivered:false};
|
|
112
|
+
await kv.put('index-events:'+scope,key,event);await onEvent(event);await kv.put('index-events:'+scope,key,{...event,delivered:true});indexed++;
|
|
113
|
+
}
|
|
114
|
+
const endBlock=await provider.getBlock(end);
|
|
115
|
+
if(!endBlock||endBlock.hash!==scanEnd.hash){await onReorg({scope,during_scan:true});for(const row of await kv.list('index-events:'+scope)){const block=await provider.getBlock(row.value.block_number);if(!block||block.hash!==row.value.block_hash)await kv.put('index-events:'+scope,row.id,{...row.value,orphaned:true});}return {indexed,reorg:true};}
|
|
116
|
+
await kv.put('index-cursors',scope,{block:end,hash:endBlock.hash});return {indexed,through:end};
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
module.exports.createEscrowIndexer=createEscrowIndexer;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const {createWorkflowHandler}=require('../workflow.cjs');
|
|
3
|
+
function createChatTools({workflow,store,queue,actor}={}){
|
|
4
|
+
const {enqueueRunningJobs}=require('./index.cjs');
|
|
5
|
+
const act=(action)=>async({job_id,input={},idempotency_key})=>workflow.act(job_id,action,input,{actor,idempotencyKey:idempotency_key});
|
|
6
|
+
return {
|
|
7
|
+
send_job_offer:async({id,worker_id,verifier_id,arbitrator_id,terms,metadata={}})=>{
|
|
8
|
+
let job=await workflow.create({id,participants:{requester:actor,worker:worker_id,verifier:verifier_id,arbitrator:arbitrator_id},metadata},actor);
|
|
9
|
+
if(terms)job=await workflow.act(job.id,'quote',{terms},{actor,idempotencyKey:'initial_quote'});return job;
|
|
10
|
+
},
|
|
11
|
+
propose_quote:act('quote'),accept_quote:act('accept'),fund_escrow:act('fund'),submit_job:act('submit'),verify_job:act('verify'),release_escrow:act('release'),refund_escrow:act('refund'),dispute_job:act('dispute'),resolve_job:act('resolve'),timeout_job:act('timeout'),cancel_job:act('cancel'),
|
|
12
|
+
work_on_job:async(input)=>{const job=await act('start')(input);if(!job.pending&&job.state==='running')await enqueueRunningJobs({store,queue});return job;}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function createApplicationHandler({workflow,store,queue,authenticate,revalidate,consentFor,legacyState=async()=>({}),authorityHandler,basePath='/api/a423'}={}){
|
|
16
|
+
const {createEventStream,freshReceipts,renderReceiptPdf}=require('./index.cjs');
|
|
17
|
+
const protocol=createWorkflowHandler({workflow,authenticate,basePath});const events=createEventStream({store,authenticate});
|
|
18
|
+
return async request=>{
|
|
19
|
+
const path=new URL(request.url).pathname;
|
|
20
|
+
if(path.startsWith('/api/a423-auth/')&&authorityHandler)return authorityHandler(request);
|
|
21
|
+
if(path.startsWith(basePath+'/'))return protocol(request);
|
|
22
|
+
if(request.method!=='GET')return Response.json({error:'not found'},{status:404});
|
|
23
|
+
if(path==='/api/a423-events')return events(request);
|
|
24
|
+
const actor=await authenticate(request);if(!actor)return Response.json({error:'unauthenticated'},{status:401});
|
|
25
|
+
try{
|
|
26
|
+
if(path==='/api/state'){
|
|
27
|
+
const jobs=await Promise.all((await store.list({actor,limit:1000})).map(async job=>({...job,safety:await store.getSafety(job.id)})));const wallet_transactions=jobs.flatMap(j=>j.evidence.filter(e=>e.evidence.transaction_hash).map(e=>({job_id:j.id,action:e.action,...e.evidence})));
|
|
28
|
+
return Response.json({...await legacyState(actor),a423:{jobs,wallet_transactions}},{headers:{'cache-control':'no-store'}});
|
|
29
|
+
}
|
|
30
|
+
const receipt=/^\/api\/a423-receipts\/([A-Za-z0-9_-]{1,128})(\.pdf)?$/.exec(path);
|
|
31
|
+
if(receipt){const data=await freshReceipts({workflow,id:receipt[1],actor,revalidate});if(receipt[2])return new Response(Buffer.from(await renderReceiptPdf(data)),{headers:{'content-type':'application/pdf','cache-control':'no-store'}});return Response.json(data,{headers:{'cache-control':'no-store'}});}
|
|
32
|
+
const consent=/^\/api\/a423-worker-consent\/([A-Za-z0-9_-]{1,128})$/.exec(path);
|
|
33
|
+
if(consent){const job=await workflow.get(consent[1],actor);if(job.participants.worker!==actor)return Response.json({error:'worker required'},{status:403});if(!consentFor)throw Error('Wallet consent not configured');const typed=await consentFor(job);return Response.json(JSON.parse(JSON.stringify(typed,(_,v)=>typeof v==='bigint'?v.toString():v)),{headers:{'cache-control':'no-store'}});}
|
|
34
|
+
return Response.json({error:'not found'},{status:404});
|
|
35
|
+
}catch(error){return Response.json({error:error.code||'EVIDENCE_OR_CONFIGURATION_UNAVAILABLE'},{status:error.code==='NOT_FOUND'?404:503});}
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
module.exports={createChatTools,createApplicationHandler};
|