Skip to main content

Integration Flow

Master the full smart contract architecture for crypto-native crowdfunding on Oak Network. This guide covers contract deployment, treasury management, and advanced integration patterns.


Campaign Creation

Step 1: Create Campaign

const ethers = require('ethers');

// Prepare campaign data
const campaignData = {
launchTime: Math.floor(Date.now() / 1000) + 86400, // 1 day from now
deadline: Math.floor(Date.now() / 1000) + 30 * 86400, // 30 days
goalAmount: ethers.utils.parseUnits('10000', 6), // 10,000 USDC
};

// Generate unique identifier
const identifierHash = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes('my-campaign-2026')
);

// Create campaign
const tx = await campaignFactory.createCampaign(
creatorAddress,
identifierHash,
[platformHash], // Selected platforms
[], // Platform data keys
[], // Platform data values
campaignData
);

const receipt = await tx.wait();
const campaignAddress = receipt.events.find(
e => e.event === 'CampaignInfoFactoryCampaignCreated'
).args.campaignAddress;

// Connect to campaign
const campaign = new ethers.Contract(campaignAddress, CampaignInfoABI, signer);

Or using the Contracts SDK:

import { createOakContractsClient, CHAIN_IDS, keccak256, toHex, getCurrentTimestamp, addDays } from '@oaknetwork/contracts-sdk';

const oak = createOakContractsClient({
chainId: CHAIN_IDS.CELO_TESTNET_SEPOLIA,
rpcUrl: 'https://forno.celo-sepolia.celo-testnet.org',
privateKey: process.env.PRIVATE_KEY!,
});

const factory = oak.campaignInfoFactory('0x...factoryAddress');

const identifierHash = keccak256(toHex('my-campaign-2026'));
const PLATFORM_HASH = keccak256(toHex('my-platform'));
const now = getCurrentTimestamp();

const txHash = await factory.createCampaign({
creator: '0x...creatorAddress',
identifierHash,
selectedPlatformHash: [PLATFORM_HASH],
campaignData: {
launchTime: now + 86_400n, // 1 day from now
deadline: addDays(now, 30), // 30 days
goalAmount: 10_000_000_000n, // 10,000 USDC (6 decimals)
currency: toHex('USD', { size: 32 }),
},
nftName: 'My Campaign NFT',
nftSymbol: 'MCN',
nftImageURI: 'https://example.com/nft.png',
contractURI: 'https://example.com/contract.json',
});

const receipt = await oak.waitForReceipt(txHash);
const campaignAddress = await factory.identifierToCampaignInfo(identifierHash);
console.log('Campaign deployed at:', campaignAddress);

Step 2: Deploy Treasury

// Deploy treasury for this campaign
const treasuryTx = await treasuryFactory.deploy(
platformHash,
campaignAddress,
1, // AllOrNothing implementation ID
'My Campaign Treasury',
'MCT'
);

const treasuryReceipt = await treasuryTx.wait();
const treasuryAddress = treasuryReceipt.events.find(
e => e.event === 'TreasuryDeployed'
).args.treasuryAddress;

// Connect to treasury
const treasury = new ethers.Contract(treasuryAddress, AllOrNothingABI, signer);

Step 3: Configure Rewards

// Add reward tiers before launch
const rewards = [
{
rewardValue: ethers.utils.parseUnits('50', 6), // $50 minimum
isRewardTier: true,
itemId: [ethers.utils.keccak256(ethers.utils.toUtf8Bytes('early-bird'))],
itemValue: [ethers.utils.parseUnits('50', 6)],
itemQuantity: [1],
},
{
rewardValue: ethers.utils.parseUnits('100', 6), // $100 minimum
isRewardTier: true,
itemId: [
ethers.utils.keccak256(ethers.utils.toUtf8Bytes('product')),
ethers.utils.keccak256(ethers.utils.toUtf8Bytes('exclusive-nft')),
],
itemValue: [
ethers.utils.parseUnits('80', 6),
ethers.utils.parseUnits('20', 6),
],
itemQuantity: [1, 1],
},
];

const rewardNames = [
ethers.utils.keccak256(ethers.utils.toUtf8Bytes('tier-early-bird')),
ethers.utils.keccak256(ethers.utils.toUtf8Bytes('tier-product')),
];

await treasury.addRewards(rewardNames, rewards);

Backer Interactions

Pledging for a Reward

// Backer approves token transfer
const usdc = new ethers.Contract(USDC_ADDRESS, ERC20ABI, backerSigner);
const pledgeAmount = ethers.utils.parseUnits('100', 6);
const shippingFee = ethers.utils.parseUnits('10', 6);

await usdc.approve(treasuryAddress, pledgeAmount.add(shippingFee));

// Backer pledges for a reward
const rewardName = ethers.utils.keccak256(
ethers.utils.toUtf8Bytes('tier-product')
);

const pledgeTx = await treasury.connect(backerSigner).pledgeForAReward(
rewardName,
pledgeAmount,
shippingFee
);

const pledgeReceipt = await pledgeTx.wait();
const tokenId = pledgeReceipt.events.find(
e => e.event === 'Receipt'
).args.tokenId;

console.log('Backer received NFT receipt:', tokenId.toString());

Checking Pledge Details

// Get pledge info from NFT
const pledgeInfo = await treasury.getPledgeInfo(tokenId);
console.log('Pledge amount:', ethers.utils.formatUnits(pledgeInfo.amount, 6));
console.log('Reward ID:', pledgeInfo.rewardId);
console.log('Shipping fee:', ethers.utils.formatUnits(pledgeInfo.shippingFee, 6));

Campaign Settlement

Successful Campaign

// Check campaign status
const deadline = await campaign.getDeadline();
const goal = await campaign.getGoalAmount();
const totalRaised = await treasury.getTotalPledged();

const now = Math.floor(Date.now() / 1000);
const hasEnded = now >= deadline.toNumber();
const isSuccessful = totalRaised.gte(goal);

console.log('Goal:', ethers.utils.formatUnits(goal, 6), 'USDC');
console.log('Raised:', ethers.utils.formatUnits(totalRaised, 6), 'USDC');

if (hasEnded && isSuccessful) {
// 1. Disburse fees first
const disburseTx = await treasury.disburseFees();
await disburseTx.wait();
console.log('Protocol and platform fees disbursed');

// 2. Withdraw remaining funds
const withdrawTx = await treasury.withdraw();
await withdrawTx.wait();
console.log('Funds withdrawn to creator');
}

Failed Campaign (Refunds)

if (hasEnded && !isSuccessful) {
// Each backer claims their refund with their NFT token ID
const tokenId = 1; // Backer's NFT token ID

const refundTx = await treasury.connect(backerSigner).claimRefund(tokenId);
await refundTx.wait();

// NFT is burned, funds returned
console.log('Refund claimed successfully');
}

Fund Distribution

When a campaign succeeds, funds are distributed as follows:

Fund Distribution Breakdown

RecipientDescriptionCalculation
Protocol FeeOak Network's fee for infrastructure2.5% of total raised
Platform FeeFee for the crowdfunding platformVariable (set by platform)
Creator PayoutNet funds received by campaign creatorTotal - Protocol Fee - Platform Fee
// Fund distribution calculation
const totalRaised = await treasury.getTotalPledged();
const protocolFeePercent = await globalParams.getProtocolFeePercent(); // 250 = 2.5%
const platformFeePercent = await campaign.getPlatformFeePercent();

const protocolFee = totalRaised.mul(protocolFeePercent).div(10000);
const platformFee = totalRaised.mul(platformFeePercent).div(10000);
const creatorPayout = totalRaised.sub(protocolFee).sub(platformFee);

console.log('Protocol fee:', ethers.utils.formatUnits(protocolFee, 6));
console.log('Platform fee:', ethers.utils.formatUnits(platformFee, 6));
console.log('Creator payout:', ethers.utils.formatUnits(creatorPayout, 6));

Campaign State Machine

Campaign States


Events Reference

ContractEventWhen Emitted
CampaignInfoFactoryCampaignInfoFactoryCampaignCreatedCampaign created
TreasuryFactoryTreasuryDeployedTreasury deployed
AllOrNothingReceiptBacker pledges
AllOrNothingRefundClaimedBacker claims refund
AllOrNothingFeesDisbursedFees distributed
AllOrNothingWithdrawnCreator withdraws

Listening to Events

// Listen for new pledges
treasury.on('Receipt', (tokenId, backer, amount, rewardId, event) => {
console.log(`New pledge: ${backer} pledged ${ethers.utils.formatUnits(amount, 6)} USDC`);
console.log(`Token ID: ${tokenId.toString()}`);
});

// Listen for refunds
treasury.on('RefundClaimed', (tokenId, backer, amount, event) => {
console.log(`Refund: ${backer} received ${ethers.utils.formatUnits(amount, 6)} USDC`);
});

Multi-Platform Support

A campaign can be listed on multiple platforms simultaneously.

// Create campaign for multiple platforms
const platformHashes = [
ethers.utils.keccak256(ethers.utils.toUtf8Bytes('platform-a')),
ethers.utils.keccak256(ethers.utils.toUtf8Bytes('platform-b')),
];

const tx = await campaignFactory.createCampaign(
creatorAddress,
identifierHash,
platformHashes, // Multiple platforms
[],
[],
campaignData
);

// Deploy treasury for each platform
for (const platformHash of platformHashes) {
await treasuryFactory.deploy(
platformHash,
campaignAddress,
1,
`Treasury ${platformHash}`,
'TRS'
);
}

Security Considerations

RiskMitigation
ReentrancyChecks-effects-interactions pattern in all fund transfers
Front-runningPledge amounts are fixed per reward tier
Oracle manipulationNo external price oracles; all amounts in stablecoin
Access controlRole-based permissions for admin functions
Upgrade safetyImmutable core logic; only parameters configurable

Gas Optimization

OperationEstimated GasNotes
createCampaign~200kClone pattern reduces cost
deploy (treasury)~250kClone pattern reduces cost
pledgeForAReward~150kIncludes NFT mint
claimRefund~80kBurns NFT
withdraw~60kSingle transfer

Use oak.multicall() to batch multiple reads into a single RPC call:

const [goal, raised, deadline] = await oak.multicall([
() => ci.getGoalAmount(),
() => aon.getRaisedAmount(),
() => ci.getDeadline(),
]);

Integration Patterns

Backend Event Listener

// Poll events for backend integration
async function pollEvents(treasury, fromBlock) {
const events = await treasury.queryFilter(
treasury.filters.Receipt(),
fromBlock
);

for (const event of events) {
await processNewPledge(event);
}

return events.length > 0
? events[events.length - 1].blockNumber
: fromBlock;
}

Subgraph Integration

# Query campaigns
query Campaigns($creator: String!) {
campaigns(where: { creator: $creator }) {
id
creator
goalAmount
deadline
totalRaised
treasuries {
id
pledges {
backer
amount
tokenId
}
}
}
}

Contract Function Reference

CampaignInfoFactory

FunctionParametersReturnsDescription
createCampaigncreator, identifierHash, platformHashes[], dataKeys[], dataValues[], campaignDataCampaign addressDeploy new campaign

campaignData struct:

FieldTypeDescription
launchTimeuint256Unix timestamp when campaign opens for pledges
deadlineuint256Unix timestamp when campaign ends
goalAmountuint256Target funding amount (in token decimals)

TreasuryFactory

FunctionParametersReturnsDescription
deployplatformHash, campaignAddress, implementationId, name, symbolTreasury addressDeploy treasury for campaign
registerTreasuryImplementationplatformHash, implementationId, implementationAddressRegister treasury type
approveTreasuryImplementationplatformHash, implementationIdApprove treasury for use

AllOrNothing Treasury

FunctionParametersReturnsDescription
addRewardsrewardNames[], rewards[]Configure reward tiers (before launch)
pledgeForARewardrewardName, amount, shippingFeeToken IDBacker pledges and receives NFT
claimRefundtokenIdBacker claims refund (if campaign failed)
disburseFeesDistribute protocol and platform fees
withdrawCreator withdraws remaining funds
getTotalPledgeduint256Total amount pledged
getPledgeInfotokenIdPledge structGet pledge details by NFT

reward struct:

FieldTypeDescription
rewardValueuint256Minimum pledge amount for this tier
isRewardTierboolWhether this is a reward tier
itemIdbytes32[]Item identifiers in this reward
itemValueuint256[]Value of each item
itemQuantityuint256[]Quantity of each item

CampaignInfo

FunctionParametersReturnsDescription
getDeadlineuint256Campaign end timestamp
getGoalAmountuint256Target funding amount
getLaunchTimeuint256Campaign start timestamp
getPlatformFeePercentuint256Platform fee in basis points

Next Steps