sporeprotocol / spore-sdk

The Ultimate TypeScript SDK for Spore Protocol
https://docs.spore.pro
MIT License
12 stars 10 forks source link

feat(sdk): cluster proxy/agent #45

Closed ShookLyngs closed 8 months ago

ShookLyngs commented 9 months ago

Changes

Checklist

Details

What is ClusterProxy/ClusterAgent

A question to start: If there is a private Cluster belonging to A, how can B create clustered Spores using this Cluster without relying on A's signatures/approvals to reference the Cluster in the Spore creation transactions?

With just the cluster, B cannot do this. Therefore, we're having two new type scripts, ClusterProxy and ClusterAgent, to address the permission distribution problem of Cluster.

Here's a summary of the two type scripts:

Example flow

Here's a simple flow to illutrate the usage:

  1. A -> Creates Cluster#1
  2. A -> Creates ClusterProxy#1 with Cluster#1
    • ClusterProxy#1 lock = A
    • ClusterProxy#1 minPayment = 10
    • Need A's signature to reference Cluster#1 in the transaction
  3. A -> Creates ClusterAgent#1 with ClusterProxy#1
    • ClusterAgent#1 lock = B
    • Need A's signature to reference ClusterProxy#1 in the transaction
  4. B -> Creates Spore#1 with ClusterAgent#1
    • Spore#1 referenced cluster = Cluster#1
    • Need B's signature to reference ClusterAgent#1 in the transaction
  5. C -> Creates ClusterAgent#2 with payment
    • ClusterAgent#2 lock = B
    • C pays A 10^10 shannons in the transaction, as ClusterProxy#1 required
    • Doesn't need A's approval/signature in the transaction
  6. C -> Creates Spore#2 with ClusterAgent#12
    • Spore#2 referenced cluster = Cluster#1
    • Need C's signature to reference ClusterAgent#2 in the transaction

From the above flow, we can observe that in step 4, B creates Spore#1 that it's a clustered Spore and references Cluster#1, but Cluster#1 is not actually included in the transction, and A's signature is not needed. The transaction only references ClusterAgent#1 as the proof to create the clustered Spore, and ClusterAgent#1 belongs to B, so only B's signature is required in the transaction.

In steps 5-6, C takes a different approach by paying a fee to A and creating ClsuterAgent#2. The fee amount is determined by the minPayment property of ClusterProxy#1. And the rest of the process is the same as B's flow.

ClusterProxy

New Composed APIs

Create ClusterProxy

// Target Cluster to reference in the transaction,
// Need to make sure the referenced Cluster is unlockable
const referencedClusterOutPoint: OutPoint = {
  txHash: '0x...',
  index: '0x...',
};

const { txSkeleton, reference } = await createClusterProxy({  
  clusterOutPoint: referencedClusterOutPoint,  
  minPayment: 10, // Optional payment
  fromInfos: [CHARLIE.address],
  toLock: CHARLIE.lock,
});

console.log(reference);
// {  
//   referenceType: 'cell' | 'lockProxy';
//   cluster?: {
//     inputIndex: number;  
//     outputIndex: number;  
//   };
// }

// Referencedd Cluster cell directly or has referenced its LockProxy
// 'cell' - Referenced the Clsuter cell directly
// 'lockProxy' - Referenced the Cluster's LockProxy
console.log(reference.referenceType);

// If referenced Clustere cell, it should exist
console.log(reference.cluster);

ClusterAgent

New Composed APIs

Create ClusterAgent

Create by referencing ClusterProxy:

// Target ClusterProxy to reference in the transaction,
// need to make sure the referenced ClusterProxy is unlockable
const referencedClusterProxyOutPoint: OutPoint = {
  txHash: '0x...',
  index: '0x...',
};

// Two methods to create a ClusterAgent
// 'cell' - Reference ClusterProxy in the transaction
// 'payment' - Pay the owner of the ClusterProxy in the transaction
const referenceType: 'cell' | 'payment' = 'cell';

const { txSkeleton, reference } = await createClusterAgent({  
  clusterProxyOutPoint: referencedClusterProxyOutPoint,  
  referenceType: referenceType,  
  fromInfos: [CHARLIE.address],  
  toLock: ALICE.lock,
});

console.log(reference);
// {  
//   referenceType: 'cell' | 'payment';  
//   clusterProxy?: {  
//     inputIndex: number;  
//     outputIndex: number;  
//   };  
//   payment?: {  
//     outputIndex: number;  
//   };  
// }

console.log(reference.referenceType); // 'cell'
console.log(reference.clusterProxy); // undefined
console.log(reference.payment); // { outputIndex: xx }

Create by paying to the owner of the ClusterProxy:

// Target ClusterProxy to reference in the transaction,
// need to make sure the referenced ClusterProxy is unlockable
const referencedClusterProxyOutPoint: OutPoint = {
  txHash: '0x...',
  index: '0x...',
};

// Two methods to create a ClusterAgent
// 'cell' - Reference ClusterProxy in the transaction
// 'payment' - Pay the owner of the ClusterProxy in the transaction
const referenceType: 'cell' | 'payment' = 'payment';

// The amount of shannons pay to the owner of the ClusterProxy,
// optional, and if undefined, the amount will be `10^minPayment` required by the ClusterProxy.
const paymentAmount: BIish = BI.from(100_0000_0000);

const { txSkeleton, reference } = await createClusterAgent({  
  clusterProxyOutPoint: referencedClusterProxyOutPoint,  
  referenceType: referenceType,  
  paymentAmount: paymentAmount,
  fromInfos: [CHARLIE.address],  
  toLock: ALICE.lock,
});

console.log(reference);
// {  
//   referenceType: 'cell' | 'payment';  
//   clusterProxy?: {  
//     inputIndex: number;  
//     outputIndex: number;  
//   };  
//   payment?: {  
//     outputIndex: number;  
//   };  
// }

console.log(reference.referenceType); // 'payment'
console.log(reference.clusterProxy); // { inputIndex: x, outputIndex: x }
console.log(reference.payment); // undefined

Spore

Create Spore with ClusterAgent

Previously, one can only create a clustered Spore by referencing the target Cluster or its LockProxy in the Spore creation transation, now props.clusterAgentOutPoint.

// The ClusterAgent should be an agent of the referenced Cluster,
// otherwise the transaction will fail because there's no relationship between them
const referencedClusterId: Hash = '0x...';
const referencedClusterAgentOutPoint: OutPoint = {  
  txHash: '0x...',  
  index: '0x...',  
};  

const { txSkeleton, reference } = await createSpore({  
  data: {  
    contentType: 'text/plain',  
    content: bytifyRawString('spore content'),  
    clusterId: referencedClusterId,  
  },
  clusterAgentOutPoint: referencedClusterAgentOutPoint,  
  fromInfos: [CHARLIE.address],  
  toLock: CHARLIE.lock,
});

console.log(reference);
// {  
//   referenceTarget: 'cluster' | 'clusterAgent' | 'none';  
//   referenceType?: 'cell' | 'lockProxy';  
//   cluster?: {  
//     inputIndex: number;  
//     outputIndex: number;  
//   };  
//   clusterAgent?: {  
//     inputIndex: number;  
//     outputIndex: number;  
//   };  
// }

// 'cluster' - Referenced Cluster cell or its LockProxy
// 'clusterAgent' - Referenced ClusterAgent cell or its LockProxy
// 'none' - The Spore has no props.data.clusterId provided
console.log(reference.referenceTarget); // 'clusterAgent'

// 'cell' - Referenced the `referenceTarget` directly
// 'lockProxy' - Referenced the LockProxy of the `referenceTarget`
console.log(reference.referenceType);

// Should exist if referenceTarget == 'cluster' && reference == 'cell'
console.log(reference.cluster); // undefined

// Should exist if referenceTarget == 'clusterAgent' && reference == 'cell'
console.log(reference.clusterAgent);
roughpandaz commented 9 months ago

@ShookLyngs do we have some basic tests that we can run as github actions?

ShookLyngs commented 9 months ago

@ShookLyngs do we have some basic tests that we can run as github actions?

We have 2 kinds of tests right now:

@Dawn-githup and I plan to address the capacity issue and create a github action to test the sdk automatically. Just opened an issue for it, feel free to follow #47 for later updates.

ShookLyngs commented 9 months ago

Was using a uint type that was too large for the ClusterProxyArgs.minPayment codec, changed Uint64 -> Uint16: